v0.22.0: First-run setup wizard, local infra backup, hub verification

New controller features:
- Web-based setup wizard replaces docker-setup.sh interactive config
  - Dual listener: :8080 (Traefik) + :8081 (direct HTTP for LAN)
  - Drive scanner finds .felhom-infra-backup/ on all block devices
  - Hub recovery pull (GET /api/v1/recovery/{id}) with retrieval password
  - Fresh install: Hub config download or manual wizard
  - CSRF protection, state persistence, Hungarian UI
- Local infra backup written to all connected drives after each backup cycle
  - .felhom-infra-backup/backup.json + metadata.json with SHA256 checksum
- Hub verification: parse customer_blocked from report push response
  - Limited mode after 7 days without verification
- Recovery info page on Settings + recovery-info.txt file generation
- Pending events queue: DR events sent to Hub on next report push
- docker-setup.sh v6.0.0: removed interactive wizard, minimal controller.yaml only

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-21 12:33:17 +01:00
parent e217c3a445
commit 6eb75204b6
28 changed files with 2970 additions and 505 deletions
+27 -4
View File
@@ -56,6 +56,7 @@ type PathsConfig struct {
type WebConfig struct {
Listen string `yaml:"listen"`
SetupListen string `yaml:"setup_listen"` // Plain HTTP listener for setup wizard (only active during setup mode)
PasswordHash string `yaml:"password_hash"`
SessionSecret string `yaml:"session_secret"`
}
@@ -149,6 +150,31 @@ type HubConfig struct {
// Load reads and parses the config file, applies defaults, and validates.
func Load(path string) (*Config, error) {
cfg, err := loadAndParse(path)
if err != nil {
return nil, err
}
if err := validate(cfg); err != nil {
return nil, fmt.Errorf("config validation: %w", err)
}
return cfg, nil
}
// LoadPermissive reads and parses the config file, applies defaults, but skips validation.
// Used during setup mode where customer.id and domain may not be set yet.
func LoadPermissive(path string) (*Config, error) {
return loadAndParse(path)
}
// Default returns a Config with all defaults applied. Used when the config file
// is missing or unreadable and the controller needs to enter setup mode.
func Default() *Config {
cfg := &Config{}
applyDefaults(cfg)
return cfg
}
func loadAndParse(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("reading config file: %w", err)
@@ -165,10 +191,6 @@ func Load(path string) (*Config, error) {
applyDefaults(cfg)
applyEnvOverrides(cfg)
if err := validate(cfg); err != nil {
return nil, fmt.Errorf("config validation: %w", err)
}
return cfg, nil
}
@@ -212,6 +234,7 @@ func applyDefaults(cfg *Config) {
d(&cfg.Paths.DataDir, "/opt/docker/felhom-controller/data")
d(&cfg.Paths.SystemDataPath, "/mnt/sys_drive")
d(&cfg.Web.Listen, ":8080")
d(&cfg.Web.SetupListen, ":8081")
d(&cfg.Git.Branch, "main")
d(&cfg.Git.SyncInterval, "15m")
d(&cfg.Stacks.UpdateWindow, "03:00-05:00")