v0.324.0: the database password comes back with the data (R-1106, R-127b, R-926); restic 0.19.1 (R-1107); R-1108, R-1065, R-1017; dashboard hardening (R-983, R-976)
gates / gates (push) Successful in 44s

This commit is contained in:
2026-10-16 22:46:00 +02:00
parent 00a0d91976
commit f75b3aa778
212 changed files with 2414 additions and 398 deletions
+33 -1
View File
@@ -511,6 +511,19 @@ func (m *Manager) unitRestoreEnv(stackName, composeDir string, manifest *Recover
m.logger.Printf("[ERROR] [backup] Restore REFUSED for %s: %v", stackName, err)
return nil, missing, err
}
// R-1106 (`07` §7.4, decision 1A): the database password comes back WITH the data — the unit's value, or the
// current install's only when that install is older than the data; otherwise refused here, before any change.
dbPlan, perr := m.dbCredentialPlanFor(stackName, filepath.Join(composeDir, "docker-compose.yml"), unitSecrets, manifestDataTime(manifest))
if perr != nil {
return nil, missing, perr
}
for n, v := range dbPlan.FromUnit {
fullEnv[n] = v
}
dbCredSet := make(map[string]bool, len(dbPlan.Names))
for _, n := range dbPlan.Names {
dbCredSet[n] = true
}
// O4: a missing RESETTABLE secret used to redeploy blank (compose "Defaulting to a blank
// string" → exit 1). Generate a replacement via the deploy flow's generator instead —
// RecreateStackFromUnit persists fullEnv through SaveAppConfig, so the new value lands
@@ -534,7 +547,9 @@ func (m *Manager) unitRestoreEnv(stackName, composeDir string, manifest *Recover
}
var generated, unresolved []string
for _, name := range missing {
if !dataKeySet[name] && m.generateSecret != nil {
// R-1106: never a database password — a generated one cannot open the restored data directory.
// (dbCredentialPlanFor refused already if one was unrecoverable; this is the second line.)
if !dataKeySet[name] && !dbCredSet[name] && m.generateSecret != nil {
if v, ok := m.generateSecret(stackName, name); ok && v != "" {
fullEnv[name] = v
generated = append(generated, name)
@@ -554,3 +569,20 @@ func (m *Manager) unitRestoreEnv(stackName, composeDir string, manifest *Recover
}
return fullEnv, missing, nil
}
// manifestDataTime is when the unit's DATA was captured: the data stamp (v0.275.0+), else the dump leg's end, else the
// unit's own creation time. Zero when none parses — and zero never licenses keeping the current install's password.
func manifestDataTime(man *RecoveryManifest) time.Time {
if man == nil {
return time.Time{}
}
if t, ok := man.Data.DataTime(); ok {
return t
}
for _, s := range []string{man.DumpsAt, man.CreatedAt} {
if t, err := time.Parse(time.RFC3339, s); err == nil {
return t
}
}
return time.Time{}
}