fix(auth): harden backup restore lifecycle cleanup

This commit is contained in:
2026-08-18 02:01:42 +02:00
parent dee17893b4
commit 39a0fdbd00
5 changed files with 764 additions and 50 deletions
+20 -2
View File
@@ -94,6 +94,11 @@ func recoverRestoreTransaction(ctx context.Context, installation config.Installa
var resultErr error
if err := runCompose(ctx, installation, deps.runner, "stop"); err != nil {
resultErr = errors.Join(resultErr, err)
// The first stop may already have taken effect before Docker lost its response. Retry the
// idempotent command so no recovery mutation starts while the candidate core is running.
if retryErr := runCompose(ctx, installation, deps.runner, "stop"); retryErr != nil {
return errors.Join(resultErr, retryErr)
}
}
archive, err := recovery.RevalidateArchive()
if err != nil {
@@ -110,10 +115,23 @@ func recoverRestoreTransaction(ctx context.Context, installation config.Installa
}
if wasRunning {
if err := composeStartAndVerify(ctx, installation, deps.runner); err != nil {
return errors.Join(resultErr, err)
resultErr = errors.Join(resultErr, err)
// As with stop, a start can succeed while the client loses its response. A successful
// retry includes its own health check before recovery verification proceeds.
if retryErr := composeStartAndVerify(ctx, installation, deps.runner); retryErr != nil {
return errors.Join(resultErr, retryErr)
}
}
}
return resultErr
if err := verifyRestoreTransaction(ctx, installation, deps); err != nil {
return errors.Join(resultErr, err)
}
if resultErr != nil {
// A prior response was lost, but the checkpoint has been restored and fully verified. The
// caller may safely remove maintenance while still returning every observed error.
return &verifiedRecoveryError{err: resultErr}
}
return nil
}
func restoreCheckpointPath(installation config.Installation, now time.Time) (string, error) {