v0.41.0: first-boot base-infra bring-up + self-heal (+ Section-G mount fix)

New internal/infra package renders traefik/cloudflared/filebrowser from config
(pinned images, single source of truth; web filebrowser path delegates here).
stacks.EnsureBaseStack deploys the traefik-public network + the three stacks,
single-flight + idempotent + non-fatal; wired to first boot and every health
tick. monitor.EffectiveProtected drops cloudflared when no tunnel token.
Section-G fix lives in felhom-agent build-golden.sh (same-path stacks bind).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-11 14:56:42 +02:00
parent ba0e1eb04a
commit abbd9488c6
13 changed files with 873 additions and 111 deletions
@@ -0,0 +1,39 @@
package monitor
import (
"testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/config"
)
func contains(ss []string, want string) bool {
for _, s := range ss {
if s == want {
return true
}
}
return false
}
// EffectiveProtected must drop cloudflared when no tunnel token is configured (LAN-only node), so the
// health loop doesn't report it missing forever — but keep it when a token IS configured.
func TestEffectiveProtectedDropsCloudflaredWithoutToken(t *testing.T) {
base := config.StacksConfig{Protected: []string{"traefik", "cloudflared", "felhom-controller", "filebrowser"}}
cfgNoTok := &config.Config{Stacks: base}
got := EffectiveProtected(cfgNoTok)
if contains(got, "cloudflared") {
t.Errorf("cloudflared must be dropped when no tunnel token: %v", got)
}
for _, must := range []string{"traefik", "felhom-controller", "filebrowser"} {
if !contains(got, must) {
t.Errorf("%s must remain protected: %v", must, got)
}
}
cfgTok := &config.Config{Stacks: base}
cfgTok.Infrastructure.CFTunnelToken = "tok"
if !contains(EffectiveProtected(cfgTok), "cloudflared") {
t.Error("cloudflared must remain protected when a tunnel token is configured")
}
}