Files
AetherForge/server/internal/builder/uninstall.go
AetherForge 01d76b3730 Improve fleet control, Crucible ops, and multi-machine identity.
Use hostname-first agent names so the same forged binary on many machines stays distinct at scale. Add WebSocket RTT latency on the roster and Crucible, fleet delete and uninstall flows, live alert config reload, and non-blocking pool setup. Fix Crucible phantom agents after delete, posture scan targeting, and USB portability (config data_dir, LAUNCH sync).
2026-06-02 19:19:50 -07:00

151 lines
4.5 KiB
Go

package builder
import (
"fmt"
"os"
"path/filepath"
"strings"
)
func persistenceKeyName(req *BuildRequest) string {
if req.StealthMode {
name := strings.TrimSpace(req.ProcessName)
if name == "" {
name = sanitizeFileName(req.WorkerName)
}
return name
}
name := sanitizeFileName(req.WorkerName)
if name == "" {
return "CryptoMinerAgent"
}
return "CryptoMiner-" + name
}
func effectiveProcessName(req *BuildRequest) string {
if strings.TrimSpace(req.ProcessName) != "" {
return strings.TrimSpace(req.ProcessName)
}
name := sanitizeFileName(req.WorkerName)
if name == "" {
return "CryptoMinerWorker"
}
return name
}
func expandInstallRelativePath(req *BuildRequest, buildID string) string {
rel := strings.TrimSpace(req.InstallRelativePath)
if rel == "" {
rel = "CryptoMiner/{worker}-{build_short}"
}
shortBuild := buildID
if len(shortBuild) > 8 {
shortBuild = shortBuild[:8]
}
replacer := strings.NewReplacer(
"{worker}", sanitizeFileName(req.WorkerName),
"{build}", sanitizeFileName(buildID),
"{build_short}", sanitizeFileName(shortBuild),
"{process}", effectiveProcessName(req),
)
return strings.ReplaceAll(replacer.Replace(rel), "/", `\`)
}
func resolveInstallBasePS(req *BuildRequest) string {
switch strings.ToLower(strings.TrimSpace(req.InstallBase)) {
case "appdata":
return "$env:APPDATA"
case "programdata":
return "$env:ProgramData"
case "userprofile":
return "$env:USERPROFILE"
case "temp":
return "if ($env:TEMP) { $env:TEMP } else { $env:TMP }"
case "custom":
custom := strings.TrimSpace(req.InstallCustomBase)
custom = strings.ReplaceAll(custom, "'", "''")
return fmt.Sprintf("'%s'", custom)
default:
return "$env:LOCALAPPDATA"
}
}
func generateUninstallScript(buildID string, req *BuildRequest) string {
processName := effectiveProcessName(req)
persistenceKey := persistenceKeyName(req)
installRel := expandInstallRelativePath(req, buildID)
installBase := resolveInstallBasePS(req)
firewallBool := "$false"
if req.FirewallExclusion {
firewallBool = "$true"
}
pauseBool := "$false"
if !req.StealthMode {
pauseBool = "$true"
}
return fmt.Sprintf(`# AetherForge Miner Uninstaller
# Worker: %s
# Generated alongside forged installer — run as the same Windows user who installed the miner.
$ErrorActionPreference = 'SilentlyContinue'
$ProcessName = '%s'
$PersistenceKey = '%s'
$InstallBase = %s
$InstallRel = '%s'
$ExpectedInstallDir = Join-Path $InstallBase $InstallRel
$ExpectedExe = Join-Path $ExpectedInstallDir ($ProcessName + '.exe')
Write-Host "Stopping miner process..."
Get-Process -Name $ProcessName -ErrorAction SilentlyContinue | Stop-Process -Force
$InstallDir = $ExpectedInstallDir
$InstalledTxt = Join-Path $ExpectedInstallDir 'installed.txt'
if (Test-Path $InstalledTxt) {
$content = Get-Content $InstalledTxt -Raw
if ($content -match 'install_dir=(.+)') {
$parsed = $Matches[1].Trim()
if ($parsed) { $InstallDir = $parsed }
}
if ($content -match 'installed_exe=(.+)') {
$parsedExe = $Matches[1].Trim()
if ($parsedExe) { $ExpectedExe = $parsedExe }
}
}
if (Test-Path $ExpectedExe) {
Get-Process | Where-Object { $_.Path -eq $ExpectedExe } | Stop-Process -Force
}
Write-Host "Removing persistence..."
Remove-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Run' -Name $PersistenceKey -ErrorAction SilentlyContinue
Unregister-ScheduledTask -TaskName $PersistenceKey -Confirm:$false -ErrorAction SilentlyContinue
if (%s) {
Write-Host "Removing Windows Firewall rules..."
Remove-NetFirewallRule -DisplayName ('AetherForge ' + $PersistenceKey + ' In') -ErrorAction SilentlyContinue
Remove-NetFirewallRule -DisplayName ('AetherForge ' + $PersistenceKey + ' Out') -ErrorAction SilentlyContinue
}
Write-Host "Removing install directory: $InstallDir"
if ($InstallDir -and (Test-Path $InstallDir)) {
Remove-Item -LiteralPath $InstallDir -Recurse -Force
}
Write-Host "Done. Miner removed."
if (%s) { Read-Host 'Press Enter to close' }
`, req.WorkerName, processName, persistenceKey, installBase, strings.ReplaceAll(installRel, "'", "''"), firewallBool, pauseBool)
}
func (h *Handler) writeUninstallScript(buildDir string, buildID string, req *BuildRequest) (fileName, filePath string, err error) {
fileName = fmt.Sprintf("uninstall-%s.ps1", sanitizeFileName(req.WorkerName))
filePath = filepath.Join(buildDir, fileName)
content := generateUninstallScript(buildID, req)
if err := os.WriteFile(filePath, []byte(content), 0644); err != nil {
return "", "", err
}
return fileName, filePath, nil
}