D48: a restore downloads first — every restore path resolves and pulls every image of the definition that will run before the stop, under the image-work lock held until the restore returns; a failed pull refuses with the app untouched (a deleted version and a busy registry read differently), one operator line. Red-proved. Commit only.
gates / gates (push) Successful in 1m26s

This commit is contained in:
2026-10-25 09:38:28 +01:00
parent b680db85cb
commit 2d71270858
16 changed files with 404 additions and 3 deletions
@@ -463,6 +463,21 @@ func (m *Manager) RestoreFromRecoveryUnitAtWith(stackName, unitDir string, opt U
}
undoExists = ex
}
// D48 (`09` §3 decision 257): DOWNLOAD FIRST. Every image the definition that will run names is on the box before
// the stop — or the restore is refused here, with the app running and its data where it was. The lock is held
// until this function returns, past the last start (R-863). The CheckOnly pass carries it too (R-1178), so the
// second drive's file half never runs before a pull that would refuse.
runCompose := filepath.Join(composeDir, "docker-compose.yml")
if !fileExists(runCompose) {
if live, ok := m.stackProvider.GetStackComposePath(stackName); ok && live != "" {
runCompose = live
}
}
releaseImages, pErr := m.prepareRestoreImages(stackName, runCompose)
if pErr != nil {
return res, pErr
}
defer releaseImages()
if opt.CheckOnly {
return res, nil
}