v0.308.0 (R-554, held-back-able): recovery-info.txt points at the dashboard's Restore page and the recovery code; the first-boot setup wizard is deleted
Operator ruling 2026-10-12 (S27, 09 §3 decision 222). The operator approves the Hungarian text before release. A box whose first ingestion failed now serves one bilingual waiting page and retries every minute instead of the wizard. Own commit so the lead can hold it back. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
@@ -10,6 +9,7 @@ import (
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/sockheal"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -54,7 +54,6 @@ import (
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/selftest"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/selfupdate"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/setup"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
|
||||
catalogsync "gitea.dooplex.hu/admin/felhom-controller/internal/sync"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/system"
|
||||
@@ -325,11 +324,25 @@ func main() {
|
||||
system.DebugLogger = logger
|
||||
}
|
||||
|
||||
// --- Setup mode: if no customer ID configured, run setup wizard ---
|
||||
if setup.NeedsSetup(cfg) {
|
||||
logger.Printf("[INFO] felhom-controller %s — setup mode", Version)
|
||||
runSetupMode(cfg, logger)
|
||||
return
|
||||
// --- Not configured yet: wait for the hub (R-554, v0.308.0) ---
|
||||
// The first-boot setup wizard is deleted. A box whose first ingestion failed shows ONE waiting page
|
||||
// (both languages) and retries every minute; once configured it falls through to the normal start
|
||||
// below, on the same listener. Pinned by internal/bootstrap TestWaitForConfig_ServesWaitingPageThenStarts.
|
||||
if bootstrap.Unconfigured(cfg) {
|
||||
logger.Printf("[INFO] felhom-controller %s — not configured yet: waiting for the hub", Version)
|
||||
waitCtx, waitStop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
ln, lerr := net.Listen("tcp", cfg.Web.Listen)
|
||||
if lerr != nil {
|
||||
logger.Printf("[ERROR] waiting page: cannot listen on %s: %v — retrying the configuration without it", cfg.Web.Listen, lerr)
|
||||
ln = nil
|
||||
}
|
||||
next := bootstrap.WaitForConfig(waitCtx, ln, *configPath, cfg, logger, pull, bootstrap.WaitRetryInterval, Version)
|
||||
waitStop()
|
||||
if next == nil {
|
||||
logger.Printf("[INFO] Received a stop signal while waiting for the configuration — exiting")
|
||||
return
|
||||
}
|
||||
cfg = next
|
||||
}
|
||||
|
||||
// --- Local API connectivity probe (slice 8A) ---
|
||||
@@ -388,7 +401,7 @@ func main() {
|
||||
stackMgr.MigrateEncryption()
|
||||
|
||||
// --- First-boot base-infrastructure bring-up ---
|
||||
// We are guaranteed configured here (setup.NeedsSetup returned false above), so deploy the base
|
||||
// We are guaranteed configured here (bootstrap.Unconfigured returned false above), so deploy the base
|
||||
// stack (traefik-public network → traefik → cloudflared → filebrowser) the controller needs for
|
||||
// routing + external access. Runs in a goroutine so a slow first-boot image pull never delays the
|
||||
// web server; non-fatal (idempotent + single-flight, the health loop re-attempts each tick).
|
||||
@@ -1713,6 +1726,8 @@ func main() {
|
||||
HubURL: cfg.Hub.URL,
|
||||
SupportEmail: "support@felhom.eu",
|
||||
SupportURL: "https://felhom.eu/kapcsolat",
|
||||
DashboardURL: "https://felhom." + cfg.Customer.Domain, // R-554: the Restore page, not the wizard
|
||||
Lang: sett.GetLanguage(),
|
||||
}
|
||||
if err := recovery.GenerateRecoveryFile(info, Version, cfg.Paths.DataDir); err != nil {
|
||||
logger.Printf("[WARN] Failed to generate recovery-info.txt: %v", err)
|
||||
@@ -3696,71 +3711,6 @@ func probeLocalAPI(cfg *config.Config, logger *log.Logger) {
|
||||
}
|
||||
}
|
||||
|
||||
// runSetupMode starts the setup wizard on dual listeners and blocks until signal.
|
||||
func runSetupMode(cfg *config.Config, logger *log.Logger) {
|
||||
ips := setup.DetectLocalIPs()
|
||||
setup.LogSetupMode(cfg.Customer.Domain, ips, cfg.Web.SetupListen, logger)
|
||||
|
||||
setupSrv := setup.NewServer(cfg, cfg.Paths.DataDir, logger, Version)
|
||||
handler := setupSrv.Handler()
|
||||
|
||||
// Health endpoint wrapper (returns setup_mode: true)
|
||||
healthHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"ok": true, "message": "felhom-controller is healthy",
|
||||
"setup_mode": true, "version": Version,
|
||||
})
|
||||
})
|
||||
|
||||
// Mux for both listeners
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/api/health", healthHandler)
|
||||
mux.Handle("/", handler)
|
||||
|
||||
// Start main listener (:8080, behind Traefik for domain access)
|
||||
mainServer := &http.Server{
|
||||
Addr: cfg.Web.Listen,
|
||||
Handler: mux,
|
||||
ReadTimeout: 30 * time.Second,
|
||||
WriteTimeout: 60 * time.Second,
|
||||
IdleTimeout: 120 * time.Second,
|
||||
}
|
||||
go func() {
|
||||
logger.Printf("[INFO] Setup wizard (main) listening on %s", cfg.Web.Listen)
|
||||
if err := mainServer.ListenAndServe(); err != http.ErrServerClosed {
|
||||
logger.Printf("[ERROR] Main HTTP server error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Start setup-only listener (:8081, direct HTTP for LAN access)
|
||||
setupServer := &http.Server{
|
||||
Addr: cfg.Web.SetupListen,
|
||||
Handler: mux,
|
||||
ReadTimeout: 30 * time.Second,
|
||||
WriteTimeout: 60 * time.Second,
|
||||
IdleTimeout: 120 * time.Second,
|
||||
}
|
||||
go func() {
|
||||
logger.Printf("[INFO] Setup wizard (LAN) listening on %s", cfg.Web.SetupListen)
|
||||
if err := setupServer.ListenAndServe(); err != http.ErrServerClosed {
|
||||
logger.Printf("[ERROR] Setup HTTP server error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for signal
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
||||
sig := <-sigCh
|
||||
logger.Printf("[INFO] Received signal %v, shutting down setup wizard...", sig)
|
||||
|
||||
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer shutdownCancel()
|
||||
mainServer.Shutdown(shutdownCtx)
|
||||
setupServer.Shutdown(shutdownCtx)
|
||||
logger.Println("[INFO] Setup wizard stopped")
|
||||
}
|
||||
|
||||
// discoverHDDPaths scans deployed apps' app.yaml for HDD_PATH env values.
|
||||
func discoverHDDPaths(stacksDir string, logger *log.Logger) []string {
|
||||
entries, err := os.ReadDir(stacksDir)
|
||||
|
||||
Reference in New Issue
Block a user