v0.276.0: a restore and a drive move keep the app's records (R-697, R-700)
gates / gates (push) Successful in 26s

A drive move persisted through the restore's fresh app.yaml write and dropped the pin: the syncer
then copied the catalog verbatim and the next start jumped the app past its ladder (R-700).
persistDriveFlip now changes HDD_PATH and nothing else. The restore's write carries the life
records (conversion copies, desired_state, update history) from the app.yaml it replaces, and a
second conversion no longer overwrites the first kept copy's record (R-697).

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:
2026-09-27 17:59:04 +02:00
parent 54bb4da343
commit 820e8efde1
7 changed files with 331 additions and 42 deletions
+11
View File
@@ -169,6 +169,11 @@ type AppConfig struct {
// ConversionCopy (v0.273.0, `09` §6.4 part 10) is the OLD datadir's copy kept after a successful
// PostgreSQL major conversion, until a backup of the converted app is proven (ReleaseConversionCopies).
ConversionCopy *ConversionCopy `yaml:"conversion_copy,omitempty" json:"conversion_copy,omitempty"`
// EarlierConversionCopies (v0.276.0, R-697) are older kept copies a later conversion superseded — a
// restore to the old major, then the ladder converting again. Released by the same rule as
// ConversionCopy; without this list the newer record overwrote the older one and its volume was orphaned
// (TestR697_ASecondConversionDoesNotOrphanTheFirstCopy).
EarlierConversionCopies []ConversionCopy `yaml:"earlier_conversion_copies,omitempty" json:"earlier_conversion_copies,omitempty"`
// RestoredLogins (v0.275.0, R-694) are the `type: password` fields whose stored value was GENERATED by a
// restore (the unit never carries an admin login, D5, and the guest had none — a load of kept data, a
// removed app, a rebuilt guest) while the app's own login came back with its data. The page then shows
@@ -659,6 +664,11 @@ func (m *Manager) RedeployFromEnv(name string, env map[string]string) error {
if err := m.PersistUnitRedeployConfig(name, env); err != nil {
return err
}
return m.upFromAppConfig(name)
}
// upFromAppConfig is RedeployFromEnv's up-and-report tail: `compose up -d` from the stored app.yaml.
func (m *Manager) upFromAppConfig(name string) error {
stack, ok := m.GetStack(name)
if !ok {
return fmt.Errorf("stack %q not found", name)
@@ -703,6 +713,7 @@ func (m *Manager) PersistUnitRedeployConfig(name string, env map[string]string)
}
}
cfg.RestoredLogins = restoredLoginFields(name, meta, prior, env)
carryLifeRecords(m.logger, name, LoadAppConfig(stackDir), cfg)
if len(cfg.RestoredLogins) > 0 {
m.logger.Printf("[INFO] [stacks] %s: the restore generated %v — the app's own login came back with its data; the page will not show the new value as the password", name, cfg.RestoredLogins)
}