900c870212
Part 4 — restore: RestoreSharesScratch + PlaceSharesRestore as SIBLINGS of the
per-app scratch/place pair. Files merged missing-only (never overwriting), each
destination PREFIX-ASSERTED against registered LIVE storage roots; definitions
merged with existing-wins; ReconcileSamba via a seam (backup must not import
stacks); credential restored best-effort into the samba named volume.
New routes POST /backup/shares/{restore,place} + a restore-page entry that renders
'Megosztasok', never the raw reserved key.
Also adds scratchJoin: reconstructing an absolute captured path under a scratch
must strip the volume name rather than rely on filepath.Join.
Part 5 — liveness: EffectiveProtected gains a settings-backed dynamic extra so the
samba CONTAINER (not the stack name — they differ) is watched exactly while sharing
is on. FINDING: the issue -> health 'fail' -> existing health_critical event ->
alert -> Hungarian degradation e-mail path needs NO further change, and introduces
no new event type, so the allowlist gotcha does not apply.
Part 6 — UI: per-tier backup status lines on the Megosztas page (amber only on
deviation). Verified the two warning-prose sites (offbox_capture/tier2_capture)
only ever receive per-app stack names, so no mapping is needed there.
RED-PROOFS RUN AND REVERTED (both fired):
4. prefix-assert removed -> place-guard traversal test FAILS
5. dynamic samba extra removed -> Scenario E enabled-case FAILS
68 lines
2.6 KiB
Go
68 lines
2.6 KiB
Go
package monitor
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/config"
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/infra"
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
|
|
)
|
|
|
|
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, settings.SMBSettings{})
|
|
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, settings.SMBSettings{}), "cloudflared") {
|
|
t.Error("cloudflared must remain protected when a tunnel token is configured")
|
|
}
|
|
}
|
|
|
|
// R-7b Scenario E, BOTH directions. Sharing is a customer-toggled feature, so the samba container can
|
|
// never be in the golden controller.yaml — the effective set must add it dynamically when sharing is
|
|
// ON (so a dead sharing service raises the same protected-container issue as a dead traefik) and must
|
|
// leave it out when sharing is OFF (so a box that never enabled it never reports a missing container).
|
|
// Red-proof: delete the `if smb.Enabled` append and the enabled case fails.
|
|
func TestEffectiveProtectedTracksSharingToggle(t *testing.T) {
|
|
cfg := &config.Config{Stacks: config.StacksConfig{Protected: []string{"traefik", "felhom-controller"}}}
|
|
|
|
on := EffectiveProtected(cfg, settings.SMBSettings{Enabled: true})
|
|
if !contains(on, infra.SambaContainerName) {
|
|
t.Errorf("sharing ON: %q must be watched, got %v", infra.SambaContainerName, on)
|
|
}
|
|
off := EffectiveProtected(cfg, settings.SMBSettings{Enabled: false})
|
|
if contains(off, infra.SambaContainerName) {
|
|
t.Errorf("sharing OFF: %q must NOT be watched, got %v", infra.SambaContainerName, off)
|
|
}
|
|
// The base set is untouched in both directions.
|
|
for _, set := range [][]string{on, off} {
|
|
for _, must := range []string{"traefik", "felhom-controller"} {
|
|
if !contains(set, must) {
|
|
t.Errorf("%s must remain protected: %v", must, set)
|
|
}
|
|
}
|
|
}
|
|
}
|