v0.275.0: a backup's data and its version travel together (R-696, 07 §6.6, D4 option A); R-695, R-691, R-694
gates / gates (push) Successful in 23s
gates / gates (push) Successful in 23s
The unit's data files are stamped with the versions that wrote them; the capture keeps the definition the data belongs to; a restore never starts data under another version's definition (unit restores refuse a mismatch; the off-site restore writes the snapshot's definition); every tier's time is its data's; the conversion-copy release needs a dump on the new engine. File-browser sync single-flight + no empty kept folder (R-695); the kept view joins the folder's owning group, language switch resyncs (R-691); a restore-generated login is not shown as the password (R-694). Red-proofs in felhom.eu/documentation/audits/version-travel-2026-09-26/. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -43,6 +43,9 @@ type Tier2RestorePoint struct {
|
||||
PackagePreserved bool
|
||||
// CopyLastSuccess — the RFC3339 time of the last Tier-2 copy that succeeded.
|
||||
CopyLastSuccess string
|
||||
// DataDate (v0.275.0, R-696) — the mirrored unit's DATA time (unitNewestArtifact on the mirror): when
|
||||
// the data the copy holds was written, which a mirror run copies but never makes newer. "" = unknown.
|
||||
DataDate string
|
||||
}
|
||||
|
||||
// restorePointFromCoverage is the pure half of the predicate.
|
||||
@@ -54,6 +57,7 @@ func restorePointFromCoverage(cov Tier2Coverage) Tier2RestorePoint {
|
||||
CopyDateProven: cov.CopyLastSuccess != "",
|
||||
PackagePreserved: preserved,
|
||||
CopyLastSuccess: cov.CopyLastSuccess,
|
||||
DataDate: cov.UnitDataDate,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +97,12 @@ func (p Tier2RestorePoint) ProvenCopyTime() (time.Time, bool) {
|
||||
if err != nil {
|
||||
return time.Time{}, false
|
||||
}
|
||||
// v0.275.0 (R-696): a mirror run copies the unit's data; it never makes the data newer. When the
|
||||
// mirror's data time is known and older than the copy, the data time is the copy's age — a mirror
|
||||
// taken right after an update, of a unit whose dump is from before it, is as old as that dump.
|
||||
if d, derr := time.Parse(time.RFC3339, p.DataDate); derr == nil && d.Before(t) {
|
||||
return d, true
|
||||
}
|
||||
return t, true
|
||||
}
|
||||
|
||||
@@ -209,8 +219,9 @@ var updateOffsiteCheckTimeout = 15 * time.Second
|
||||
// UpdateTierPoint is one proven, restorable copy of an app on one tier.
|
||||
type UpdateTierPoint struct {
|
||||
Tier int
|
||||
// At is when the data in that copy was last proven written: Tier 2 ProvenCopyTime, Tier 1 the
|
||||
// newest artifact of the unit (ListRestorePoints), Tier 3 the newest snapshot for the app.
|
||||
// At is when the data in that copy was last proven written: Tier 2 ProvenCopyTime (capped by the
|
||||
// mirror's data time), Tier 1 the unit's DATA time (ListRestorePoints → unitNewestArtifact), Tier 3
|
||||
// the newest snapshot, capped by the data time the box recorded when it pushed it (v0.275.0, R-696).
|
||||
At time.Time
|
||||
}
|
||||
|
||||
@@ -281,7 +292,7 @@ func (m *Manager) updateTierPoint(ctx context.Context, stackName string, tier in
|
||||
return UpdateTierPoint{}, false
|
||||
}
|
||||
if at, ok := got[stackName]; ok && !at.IsZero() {
|
||||
return UpdateTierPoint{Tier: tier, At: at}, true
|
||||
return UpdateTierPoint{Tier: tier, At: m.offsiteDataTime(stackName, at)}, true
|
||||
}
|
||||
}
|
||||
return UpdateTierPoint{}, false
|
||||
@@ -391,11 +402,12 @@ func (m *Manager) RunAppBackupNow(ctx context.Context, stackName string) error {
|
||||
if db.StackName != stackName {
|
||||
continue
|
||||
}
|
||||
res := DumpOne(ctx, db, AppDBDumpPath(nsRoot, stackName), m.logger, m.isDebug())
|
||||
res := m.dumpOneOrDefault(ctx, db, AppDBDumpPath(nsRoot, stackName))
|
||||
if res.Error != nil {
|
||||
return util.MsgError("err.backup.adatbazis_mentes_sikertelen", db.ContainerName, res.Error)
|
||||
}
|
||||
dumped++
|
||||
m.stampDataFile(stackName, RecoveryUnitPath(nsRoot, stackName), "db-dumps/"+filepath.Base(res.FilePath))
|
||||
m.logger.Printf("[INFO] [backup] update pre-backup for %s: database dump OK (%s, %s)", stackName, db.ContainerName, humanizeBytes(res.Size))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user