3b70a9e9ab
The samba_classify.go SCOPE NOTE and the README KNOWN GAP both described a gap that R-7b closes; both now describe the sibling-shares-source execution instead.
71 lines
2.9 KiB
Go
71 lines
2.9 KiB
Go
package stacks
|
||
|
||
import (
|
||
"path/filepath"
|
||
"strings"
|
||
|
||
"gitea.dooplex.hu/admin/felhom-controller/internal/appbackup"
|
||
)
|
||
|
||
// Backup classification for the samba stack (R-7 slice 1, [R4]).
|
||
//
|
||
// samba is CONTROLLER-GENERATED infra: it has no .felhom.yml and its compose binds are absolute
|
||
// share paths, so the catalog path (LoadMetadata → ParseComposeClassifiableBinds → ClassifyBinds)
|
||
// cannot classify it. Its classification comes from the SHARES REGISTRY instead:
|
||
//
|
||
// share.Offsite == true → ClassMandatory → TierOffsite (offsite) AND TierSecondary (tier-2)
|
||
// share.Offsite == false → ClassOptional → TierSecondary only (never offsite)
|
||
//
|
||
// The smb.conf / passdb mounts are deliberately NOT emitted — they are config, not customer data.
|
||
//
|
||
// EXECUTION (R-7b, v0.145.0 — the gap this note used to describe is CLOSED). Share data now reaches
|
||
// BOTH live tiers, but not through this seam: the engines remain structured around a per-app recovery
|
||
// unit (backup.RunTier2 still short-circuits on `os.Stat(unitDir)`; the offsite runner still
|
||
// enumerates settings.GetOffboxApps()), and Model B′ deliberately left those paths byte-identical.
|
||
// Instead the backup package runs a SIBLING shares source off the SAME registry this file reads —
|
||
// backup.RunSharesTier2 and backup.runOffboxSharesLeg — applying the identical rule below.
|
||
//
|
||
// So this function and the shares sources agree by construction on WHAT each share's class is, while
|
||
// the sources own WHERE it goes. Keep the rule here in sync with internal/backup/shares_payload.go's
|
||
// classifiedShares if it ever changes.
|
||
func (m *Manager) sambaClassifiedBinds() ([]appbackup.ClassifiedBind, bool) {
|
||
if m.settings == nil {
|
||
return nil, false
|
||
}
|
||
shares := m.settings.GetSMBShares()
|
||
out := make([]appbackup.ClassifiedBind, 0, len(shares))
|
||
for _, sh := range shares {
|
||
cls := appbackup.ClassOptional
|
||
if sh.Offsite {
|
||
cls = appbackup.ClassMandatory
|
||
}
|
||
out = append(out, appbackup.ClassifiedBind{
|
||
ComposeBind: appbackup.ComposeBind{
|
||
Root: appbackup.RootHDD,
|
||
RelPath: m.shareRelPath(sh.Path),
|
||
ReadOnly: sh.ReadOnly,
|
||
},
|
||
Class: cls,
|
||
Origin: appbackup.OriginExplicit, // the customer's per-share Felhőmentés toggle IS explicit
|
||
})
|
||
}
|
||
return out, true
|
||
}
|
||
|
||
// shareRelPath expresses a share's absolute path relative to its owning registered storage root, so
|
||
// the emitted bind matches the (Root, RelPath) + root-path resolution shape the capture-set helpers
|
||
// use. A share under no registered root falls back to the path minus its leading slash (never
|
||
// absolute — the structural guards reject absolute RelPaths).
|
||
func (m *Manager) shareRelPath(path string) string {
|
||
p := filepath.ToSlash(path)
|
||
if m.settings != nil {
|
||
for _, sp := range m.settings.GetStoragePaths() {
|
||
root := filepath.ToSlash(sp.Path)
|
||
if strings.HasPrefix(p, root+"/") {
|
||
return strings.TrimPrefix(p, root+"/")
|
||
}
|
||
}
|
||
}
|
||
return strings.TrimPrefix(p, "/")
|
||
}
|