//go:build !windows package deploy import ( "fmt" "os" "os/exec" "path/filepath" "runtime" "strings" "crypto-miner-agent/config" ) func CurrentExecutable() (string, error) { path, err := os.Executable() if err != nil { return filepath.Abs(os.Args[0]) } return filepath.Abs(path) } func configureAutoStart(cfg config.RuntimeConfig, binPath string) error { switch runtime.GOOS { case "darwin": return configureLaunchAgent(cfg, binPath) default: return configureSystemdUser(cfg, binPath) } } func configureRunMode(cfg config.RuntimeConfig, installedBin string) error { if cfg.RunAs == "scheduled" || cfg.RunAs == "service" { return configureAutoStart(cfg, installedBin) } return nil } func configureSystemdUser(cfg config.RuntimeConfig, binPath string) error { unitName := PersistenceKeyName(cfg) + ".service" unitDir := filepath.Join(os.Getenv("HOME"), ".config", "systemd", "user") if err := os.MkdirAll(unitDir, 0755); err != nil { return err } unitPath := filepath.Join(unitDir, unitName) content := fmt.Sprintf(`[Unit] Description=AetherForge Worker %s After=network.target [Service] Type=simple ExecStart=%s %s Restart=always RestartSec=60 [Install] WantedBy=default.target `, cfg.WorkerName, binPath, runFlag) if err := os.WriteFile(unitPath, []byte(content), 0644); err != nil { return err } _ = exec.Command("systemctl", "--user", "daemon-reload").Run() _ = exec.Command("systemctl", "--user", "enable", unitName).Run() _ = exec.Command("systemctl", "--user", "start", unitName).Run() return nil } func configureLaunchAgent(cfg config.RuntimeConfig, binPath string) error { label := "com.aetherforge." + sanitizeName(PersistenceKeyName(cfg)) plistDir := filepath.Join(os.Getenv("HOME"), "Library", "LaunchAgents") if err := os.MkdirAll(plistDir, 0755); err != nil { return err } plistPath := filepath.Join(plistDir, label+".plist") content := fmt.Sprintf(` Label%s ProgramArguments %s%s RunAtLoad KeepAlive ProcessTypeBackground `, label, binPath, runFlag) if err := os.WriteFile(plistPath, []byte(content), 0644); err != nil { return err } _ = exec.Command("launchctl", "load", plistPath).Run() return nil } func applyDetachedStart(cmd *exec.Cmd) { if cmd == nil { return } cmd.Stdin = nil cmd.Stdout = nil cmd.Stderr = nil } func HostOSVersion() string { out, err := exec.Command("uname", "-sr").CombinedOutput() if err != nil { return runtime.GOOS } return strings.TrimSpace(string(out)) } func killWorkerProcess(cfg config.RuntimeConfig) { name := BinaryName(cfg) if runtime.GOOS == "darwin" { _ = exec.Command("pkill", "-f", name).Run() } else { _ = exec.Command("pkill", "-x", cfg.EffectiveProcessName()).Run() } } func removePersistence(cfg config.RuntimeConfig) { keyName := PersistenceKeyName(cfg) switch runtime.GOOS { case "darwin": label := "com.aetherforge." + sanitizeName(keyName) plistPath := filepath.Join(os.Getenv("HOME"), "Library", "LaunchAgents", label+".plist") _ = exec.Command("launchctl", "unload", plistPath).Run() _ = os.Remove(plistPath) default: unitName := keyName + ".service" _ = exec.Command("systemctl", "--user", "disable", unitName).Run() _ = exec.Command("systemctl", "--user", "stop", unitName).Run() _ = os.Remove(filepath.Join(os.Getenv("HOME"), ".config", "systemd", "user", unitName)) } } func selfUninstallSpawn(installDir string) { script := fmt.Sprintf("#!/bin/sh\nsleep 2\nrm -rf %q\n", installDir) tmp := filepath.Join(os.TempDir(), "af-uninstall.sh") _ = os.WriteFile(tmp, []byte(script), 0755) cmd := exec.Command("/bin/sh", tmp) _ = cmd.Start() }