Files
felhom-controller/controller/internal/stacks/life_records.go
T
admin 977665d8c0
gates / gates (push) Successful in 27s
The family gate (decisions 63/64, R-780): family members with their own logins, a permanent forwardAuth door per family app, anchored exceptions, min_controller
- internal/family: the family list (bcrypt, generated 4x4 passwords shown once) + 30-day sessions in family.json
  (0600, atomic); a reset (generation), a removal or a logout ends sessions at the next request.
- internal/stacks/family_gate.go: family_gate / family_gate_except / min_controller in .felhom.yml; the door is written
  BEFORE the first start (install and a removed app's restore), a life record in app.yaml, reconciled by the gate loop;
  priority below the install hold, setup gate and sign-up block; every exception anchored ^/prefix(/|$) (finding F1).
- internal/web/family_gate.go: forwardAuth /__felhom_gate/family (app cookie felhom_famgate, host-only, names a store
  session); /__family/start|login|logout on the dashboard host (session cookie felhom_family, Path=/__family);
  sign-in counted per visitor (clientIP) AND per name, short windows; the household's dashboard session vouches.
  RequireAuth never reads a family cookie. The "Család" card on the security page: add / new password / remove.
Red-proofs RP-F1..RP-F7 (felhom.eu audits/family-gate-2026-10-02/A/).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-02 07:42:47 +02:00

51 lines
2.9 KiB
Go

package stacks
import "log"
// carryLifeRecords copies, from the app.yaml a restore is about to replace (prior, as on disk), the records
// that describe the app's LIFE on this box rather than its definition (R-697, v0.276.0). The restore's
// write is a fresh AppConfig by design — the env, the locked fields and the pin come from the unit — and it
// used to drop these with it:
//
// - conversion_copy + earlier_conversion_copies: a kept pre-conversion datadir copy whose record is dropped
// is never released (R-697, measured on 9202). A restore does not remove the volume, so it must not
// forget it either.
// - desired_state: the household's intent. Dropped, a dead app after a restore was read as "unknown
// intent" and never alarmed (R-166's absent case).
// - failed_update_step, last_update_undone, last_auto_update: the ladder's history on THIS box; the
// automatic leg must not re-press a step that already failed here because a restore happened.
//
// NOT carried: pinned_images (the restore pins to the unit's definition right after — carrying the old pin
// would freeze a failed SetPin onto the wrong version), installed_images (an observation of what ran
// before the restore, possibly another version), restored_logins (computed per restore).
// Pinned by TestR697_ARestoreKeepsTheConversionCopyRecordSoTheCopyIsReleased.
func carryLifeRecords(logger *log.Logger, name string, prior, cfg *AppConfig) {
if prior == nil {
return
}
cfg.ConversionCopy = prior.ConversionCopy
cfg.EarlierConversionCopies = prior.EarlierConversionCopies
if cfg.DesiredState == "" {
cfg.DesiredState = prior.DesiredState
}
cfg.FailedStep = prior.FailedStep
cfg.LastUpdateUndone = prior.LastUpdateUndone
cfg.LastAutoUpdate = prior.LastAutoUpdate
cfg.AfterInstall = prior.AfterInstall // v0.279.0: what the install's one-time command did stays true after a restore
// v0.280.0 (decision 46): the setup gate is the app's life here too. A restore never re-gates an app whose gate
// opened; a gate that was still closed stays closed (its probe opens it if the restored data is set up).
// No prior record (a removed app, kept data, a rebuilt guest) = no gate: the data comes back with its admin.
cfg.SetupGate = prior.SetupGate
cfg.FamilyGate = prior.FamilyGate // v0.287.0: a restore never un-gates a family app
cfg.InstallHold = prior.InstallHold // R-741: the loop opens it when the restored record says the login was replaced
cfg.DefaultLogin = prior.DefaultLogin
cfg.AfterSetup = prior.AfterSetup
if n := len(prior.EarlierConversionCopies); prior.ConversionCopy != nil || n > 0 {
cur := ""
if prior.ConversionCopy != nil {
cur = prior.ConversionCopy.Copy
}
logger.Printf("[INFO] [stacks] %s: the restore keeps the record of the kept pre-conversion copy %q (+%d earlier) — it is released when a backup written by the converted engine is proven", name, cur, n)
}
}