feat(shares): R-7b Parts 4-6 — shares restore, samba liveness, UI truth-up
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
This commit is contained in:
@@ -261,6 +261,10 @@ func main() {
|
||||
// O4: restore-from-unit generates a replacement for an unrecoverable RESETTABLE secret
|
||||
// (data-keys stay fail-closed) so the app redeploys with a fresh credential, not a blank one.
|
||||
backupMgr.SetSecretGenerator(stackMgr.GenerateSecretForField)
|
||||
// R-7b: after a shares restore re-adds definitions to the registry, smb.conf must be
|
||||
// re-rendered or the restored shares exist on paper but are not exported. A seam rather than a
|
||||
// direct call — the backup package must not depend on the stacks package.
|
||||
backupMgr.SetSharesReconciler(stackMgr.ReconcileSamba)
|
||||
}
|
||||
|
||||
// SLICE 2: the offsite apply-bridge — on startup (async, non-blocking) reconcile the hub-served offsite
|
||||
@@ -385,7 +389,7 @@ func main() {
|
||||
healthInterval = 5 * time.Minute
|
||||
}
|
||||
sched.Every("system-health", healthInterval, func(ctx context.Context) error {
|
||||
healthReport := monitor.RunHealthCheck(cfg, cpuCollector, sett.GetStoragePaths(), logger)
|
||||
healthReport := monitor.RunHealthCheck(cfg, cpuCollector, sett.GetStoragePaths(), sett.GetSMBSettings(), logger)
|
||||
// Self-heal the base stack: call unconditionally every tick. EnsureBaseStack is single-flight
|
||||
// + idempotent (skips running stacks ⇒ a cheap 3× docker-inspect no-op when healthy), so there
|
||||
// is no need to couple to the health-report issue strings. Runs in a goroutine — never blocks
|
||||
@@ -629,7 +633,7 @@ func main() {
|
||||
pushInterval = 15 * time.Minute
|
||||
}
|
||||
sched.Every("hub-report", pushInterval, func(ctx context.Context) error {
|
||||
r := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), sett.GetGeoRestriction(), logger)
|
||||
r := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), sett.GetGeoRestriction(), sett.GetSMBSettings(), logger)
|
||||
r.Claimed = sett.GetClaimed() // v0.122.0 (F-4): set-only claim flag for the hub
|
||||
if err := hubPusher.Push(r); err != nil {
|
||||
return err
|
||||
@@ -735,7 +739,7 @@ func main() {
|
||||
// Hub report
|
||||
if hubPusher != nil {
|
||||
if cfg.Hub.Enabled {
|
||||
r := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), sett.GetGeoRestriction(), logger)
|
||||
r := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), sett.GetGeoRestriction(), sett.GetSMBSettings(), logger)
|
||||
r.Claimed = sett.GetClaimed() // v0.122.0 (F-4): set-only claim flag for the hub
|
||||
var pushErr error
|
||||
for attempt := 1; attempt <= 3; attempt++ {
|
||||
@@ -803,7 +807,7 @@ func main() {
|
||||
|
||||
// Initial alert refresh (so alerts appear immediately, not after first 5min health check)
|
||||
go func() {
|
||||
report := monitor.RunHealthCheck(cfg, cpuCollector, sett.GetStoragePaths(), logger)
|
||||
report := monitor.RunHealthCheck(cfg, cpuCollector, sett.GetStoragePaths(), sett.GetSMBSettings(), logger)
|
||||
alertMgr.Refresh(report, cfg, backupMgr, false, "")
|
||||
}()
|
||||
|
||||
@@ -817,7 +821,7 @@ func main() {
|
||||
var reportTrigger *report.Trigger
|
||||
if hubPusher != nil && cfg.Hub.Enabled {
|
||||
fireReport := func() error {
|
||||
rep := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), sett.GetGeoRestriction(), logger)
|
||||
rep := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), sett.GetGeoRestriction(), sett.GetSMBSettings(), logger)
|
||||
rep.Claimed = sett.GetClaimed() // v0.122.0 (F-4): set-only claim flag for the hub
|
||||
return hubPusher.Push(rep)
|
||||
}
|
||||
@@ -960,7 +964,7 @@ func main() {
|
||||
dc := &web.DebugCallbacks{}
|
||||
if hubPusher != nil {
|
||||
dc.TriggerHubReportPush = func() error {
|
||||
r := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), sett.GetGeoRestriction(), logger)
|
||||
r := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), sett.GetGeoRestriction(), sett.GetSMBSettings(), logger)
|
||||
r.Claimed = sett.GetClaimed() // v0.122.0 (F-4): set-only claim flag for the hub
|
||||
return hubPusher.Push(r)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user