feat(setup): hub mode triggers setup wizard with infra backup restore
docker-setup.sh --hub-customer now generates a minimal controller.yaml (no customer.id) instead of installing full hub config, triggering the setup wizard on first run. Hub credentials are passed via env vars (FELHOM_SETUP_CUSTOMER_ID, FELHOM_SETUP_PASSWORD) so the wizard auto-fills and auto-processes Hub API calls. Welcome page shows three options in hub mode: restore from Hub (primary), restore from local drives, or fresh install. On error, falls back to manual form with error displayed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -70,6 +70,20 @@ func NewServer(cfg *config.Config, dataDir string, logger *log.Logger, version s
|
||||
version: version,
|
||||
}
|
||||
s.loadTemplates()
|
||||
|
||||
// Pre-seed setup state from env vars (set by docker-setup.sh --hub-customer)
|
||||
if cid := os.Getenv("FELHOM_SETUP_CUSTOMER_ID"); cid != "" {
|
||||
s.state.SetFormField("customer_id", cid)
|
||||
logger.Printf("[INFO] Setup: pre-seeded customer_id from env: %s", cid)
|
||||
}
|
||||
if pw := os.Getenv("FELHOM_SETUP_PASSWORD"); pw != "" {
|
||||
s.state.SetFormField("retrieval_password", pw)
|
||||
logger.Printf("[INFO] Setup: pre-seeded retrieval_password from env")
|
||||
}
|
||||
if s.isHubPreseeded() {
|
||||
s.state.Save()
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -78,6 +92,12 @@ func (s *Server) isDebug() bool {
|
||||
return s.cfg != nil && s.cfg.Logging.Level == "debug"
|
||||
}
|
||||
|
||||
// isHubPreseeded returns true if both customer_id and retrieval_password are
|
||||
// pre-seeded in setup state (set by docker-setup.sh --hub-customer).
|
||||
func (s *Server) isHubPreseeded() bool {
|
||||
return s.state.GetFormField("customer_id") != "" && s.state.GetFormField("retrieval_password") != ""
|
||||
}
|
||||
|
||||
func (s *Server) loadTemplates() {
|
||||
s.tmpl = template.Must(
|
||||
template.New("").Funcs(template.FuncMap{
|
||||
@@ -130,9 +150,11 @@ func (s *Server) handleWelcome(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"CSRF": csrf,
|
||||
"AccessURLs": accessURLs,
|
||||
"Version": s.version,
|
||||
"CSRF": csrf,
|
||||
"AccessURLs": accessURLs,
|
||||
"Version": s.version,
|
||||
"HubMode": s.isHubPreseeded(),
|
||||
"HubCustomerID": s.state.GetFormField("customer_id"),
|
||||
}
|
||||
s.render(w, "setup_welcome", data)
|
||||
}
|
||||
@@ -181,6 +203,14 @@ func (s *Server) handleHubRestore(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Auto-process if credentials are pre-seeded (hub mode from docker-setup.sh)
|
||||
if s.isHubPreseeded() {
|
||||
customerID := s.state.GetFormField("customer_id")
|
||||
password := s.state.GetFormField("retrieval_password")
|
||||
s.autoProcessHubRestore(w, r, customerID, password)
|
||||
return
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"CSRF": csrf,
|
||||
"CustomerID": s.state.GetFormField("customer_id"),
|
||||
@@ -244,6 +274,14 @@ func (s *Server) handleFreshHub(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Auto-process if credentials are pre-seeded (hub mode from docker-setup.sh)
|
||||
if s.isHubPreseeded() {
|
||||
customerID := s.state.GetFormField("customer_id")
|
||||
password := s.state.GetFormField("retrieval_password")
|
||||
s.autoProcessFreshHub(w, r, customerID, password)
|
||||
return
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"CSRF": csrf,
|
||||
"CustomerID": s.state.GetFormField("customer_id"),
|
||||
@@ -302,6 +340,111 @@ func (s *Server) handleLogo(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, web.FelhomLogoSVG)
|
||||
}
|
||||
|
||||
// --- Auto-Processing (Hub pre-seeded mode) ---
|
||||
|
||||
// autoProcessHubRestore calls PullRecovery with pre-seeded credentials and
|
||||
// renders the confirmation page directly, skipping the manual form.
|
||||
// Falls back to the form with an error message on failure.
|
||||
func (s *Server) autoProcessHubRestore(w http.ResponseWriter, r *http.Request, customerID, password string) {
|
||||
hubURL := DefaultHubURL
|
||||
|
||||
s.logger.Printf("[INFO] Setup: auto-processing hub restore for %s (pre-seeded credentials)", customerID)
|
||||
|
||||
recovery, err := report.PullRecovery(hubURL, customerID, password)
|
||||
if err != nil {
|
||||
s.logger.Printf("[WARN] Setup: auto hub restore failed: %v — falling back to form", err)
|
||||
var msg string
|
||||
switch {
|
||||
case isError(err, report.ErrHubUnreachable):
|
||||
msg = "A Hub (hub.felhom.eu) nem elérhető. Ellenőrizze az internetkapcsolatot."
|
||||
case isError(err, report.ErrAuthFailed):
|
||||
msg = "Helytelen ügyfél-azonosító vagy jelszó."
|
||||
case isError(err, report.ErrNotFound):
|
||||
msg = "Ez az ügyfél-azonosító nem található a Hub-on."
|
||||
default:
|
||||
msg = fmt.Sprintf("Hiba történt: %v", err)
|
||||
}
|
||||
// Clear pre-seeded password so form is shown on next attempt
|
||||
s.state.SetFormField("retrieval_password", "")
|
||||
s.state.Save()
|
||||
s.renderError(w, r, "setup_hub_restore", msg, customerID)
|
||||
return
|
||||
}
|
||||
|
||||
if s.isDebug() {
|
||||
s.logger.Printf("[DEBUG] Setup: hub recovery received — hasInfra=%v, configLen=%d", recovery.HasInfraBackup, len(recovery.ConfigYAML))
|
||||
}
|
||||
|
||||
// Store recovery data in state for restore execution
|
||||
s.state.SelectedBackup = &SelectedBackup{
|
||||
Source: "hub",
|
||||
CustomerID: customerID,
|
||||
}
|
||||
s.state.SetFormField("hub_config_yaml", recovery.ConfigYAML)
|
||||
if recovery.HasInfraBackup && recovery.InfraBackup != nil {
|
||||
ibJSON, _ := json.Marshal(recovery.InfraBackup)
|
||||
s.state.SetFormField("hub_infra_backup", string(ibJSON))
|
||||
s.state.SelectedBackup.Timestamp = recovery.InfraBackup.Timestamp
|
||||
}
|
||||
s.state.SetStep("restore-confirm")
|
||||
s.state.Save()
|
||||
|
||||
// Show confirmation page with backup details
|
||||
csrf := ensureCSRFToken(w, r)
|
||||
data := map[string]interface{}{
|
||||
"CSRF": csrf,
|
||||
"CustomerID": customerID,
|
||||
"HasInfraBackup": recovery.HasInfraBackup,
|
||||
"HasConfig": recovery.ConfigYAML != "",
|
||||
"Source": "hub",
|
||||
}
|
||||
if recovery.HasInfraBackup && recovery.InfraBackup != nil {
|
||||
data["Timestamp"] = recovery.InfraBackup.Timestamp
|
||||
data["StackCount"] = len(recovery.InfraBackup.DeployedStacks)
|
||||
}
|
||||
s.render(w, "setup_restore_exec", data)
|
||||
}
|
||||
|
||||
// autoProcessFreshHub calls PullConfig with pre-seeded credentials and
|
||||
// proceeds with fresh install, skipping the manual form.
|
||||
func (s *Server) autoProcessFreshHub(w http.ResponseWriter, r *http.Request, customerID, password string) {
|
||||
hubURL := DefaultHubURL
|
||||
|
||||
s.logger.Printf("[INFO] Setup: auto-processing fresh hub install for %s (pre-seeded credentials)", customerID)
|
||||
|
||||
configYAML, err := report.PullConfig(hubURL, customerID, password)
|
||||
if err != nil {
|
||||
s.logger.Printf("[WARN] Setup: auto fresh hub failed: %v — falling back to form", err)
|
||||
// Clear pre-seeded password so form is shown on next attempt
|
||||
s.state.SetFormField("retrieval_password", "")
|
||||
s.state.Save()
|
||||
var msg string
|
||||
switch {
|
||||
case isError(err, report.ErrHubUnreachable):
|
||||
msg = "A Hub (hub.felhom.eu) nem elérhető. Ellenőrizze az internetkapcsolatot."
|
||||
case isError(err, report.ErrAuthFailed):
|
||||
msg = "Helytelen ügyfél-azonosító vagy jelszó."
|
||||
case isError(err, report.ErrNotFound):
|
||||
msg = "Ez az ügyfél-azonosító nem található a Hub-on."
|
||||
default:
|
||||
msg = fmt.Sprintf("Hiba történt: %v", err)
|
||||
}
|
||||
s.renderError(w, r, "setup_fresh_hub", msg, customerID)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("[INFO] Setup: config downloaded (%d bytes), writing config...", len(configYAML))
|
||||
|
||||
if err := s.writeFreshConfig(configYAML, password); err != nil {
|
||||
s.logger.Printf("[ERROR] Setup: writeFreshConfig failed: %v", err)
|
||||
s.renderError(w, r, "setup_fresh_hub", fmt.Sprintf("Konfigurációs hiba: %v", err), customerID)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("[INFO] Setup: fresh install from Hub completed for %s", customerID)
|
||||
s.finishSetup()
|
||||
}
|
||||
|
||||
// --- Processing Logic ---
|
||||
|
||||
func (s *Server) processHubRestore(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -22,6 +22,26 @@
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .HubMode}}
|
||||
<div class="alert alert-info" style="margin-bottom: 1.5rem;">
|
||||
Hub-mód: <strong>{{.HubCustomerID}}</strong> — az azonosító és jelszó automatikusan betöltődik.
|
||||
</div>
|
||||
|
||||
<a href="/setup/hub-restore" class="setup-card" style="display: block; text-decoration: none; color: inherit; border-color: var(--accent-blue, #0088cc);">
|
||||
<h3>Visszaállítás a Hub-ról</h3>
|
||||
<p>A Hub-on tárolt infrastruktúra mentés visszaállítása (beállítások, titkosítási kulcsok, lemez-kiosztás).</p>
|
||||
</a>
|
||||
|
||||
<a href="/setup/scan" class="setup-card" style="display: block; text-decoration: none; color: inherit;">
|
||||
<h3>Visszaállítás helyi meghajtóról</h3>
|
||||
<p>Csatlakoztatott meghajtók keresése infrastruktúra mentésért.</p>
|
||||
</a>
|
||||
|
||||
<a href="/setup/fresh" class="setup-card" style="display: block; text-decoration: none; color: inherit;">
|
||||
<h3>Friss telepítés</h3>
|
||||
<p>Új konfiguráció letöltése a Hub-ról. Korábbi beállítások nem állítódnak vissza.</p>
|
||||
</a>
|
||||
{{else}}
|
||||
<a href="/setup/scan" class="setup-card" style="display: block; text-decoration: none; color: inherit;">
|
||||
<h3>Visszaállítás mentésből</h3>
|
||||
<p>Rendszerhiba utáni visszaállítás helyi meghajtóról vagy a Hub-ról. Válassza ezt, ha az operációs rendszert újratelepítette.</p>
|
||||
@@ -31,6 +51,7 @@
|
||||
<h3>Új telepítés</h3>
|
||||
<p>Új ügyfél beállítása. Konfiguráció letöltése a Hub-ról vagy kézi beállítás.</p>
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user