feat(samba): lifecycle — ensureSamba/ReconcileSamba/password/disable (R-7 slice 1, Part 2)

ensureSamba joins EnsureBaseStack after filebrowser, gated on SMB.Enabled
(cloudflared conditional precedent); reconcile is idempotent (unchanged config +
running container = ZERO compose calls, asserted via seam). Atomic tmp+fsync+
rename config writes. Password applied via smbpasswd on STDIN (never argv/log/
settings). Disable = compose down, volumes + folders KEPT. samba added to
IsProtectedStack in code (controller.yaml is golden-generated and predates it),
which also makes the app-backup loops correctly skip it.
This commit is contained in:
2026-07-18 11:30:04 +02:00
parent b0c5ef4823
commit 0dcbea90b2
5 changed files with 600 additions and 1 deletions
+15 -1
View File
@@ -396,8 +396,22 @@ func validate(cfg *Config) error {
return nil
}
// IsProtectedStack checks if a stack name is in the protected list.
// alwaysProtectedStacks are controller-MANAGED infra stacks that are protected regardless of the
// configured list. cfg.Stacks.Protected comes from controller.yaml (golden/bootstrap-generated) and
// predates these, so a box whose controller.yaml has not been regenerated would otherwise expose
// them as ordinary, stoppable/deletable apps. Protection means: no stop/delete from the UI, and the
// app-backup loops skip them (they are infrastructure, not customer apps — samba's share data is
// classified through its own registry instead, see stacks.ClassifiedBinds).
var alwaysProtectedStacks = map[string]bool{
"samba": true, // LAN network-sharing infra stack (R-7)
}
// IsProtectedStack checks if a stack name is protected — either controller-managed infra (always)
// or listed in the configured protected list.
func (cfg *Config) IsProtectedStack(name string) bool {
if alwaysProtectedStacks[strings.ToLower(name)] {
return true
}
for _, p := range cfg.Stacks.Protected {
if strings.EqualFold(p, name) {
return true