diff --git a/internal/reconcile/bringup.go b/internal/reconcile/bringup.go index adeac4e..30af2de 100644 --- a/internal/reconcile/bringup.go +++ b/internal/reconcile/bringup.go @@ -302,6 +302,19 @@ func (e *Engine) runBringUp(ctx context.Context, spec BringUpSpec, res *BringUpR } } + // R-834: DR keeps the archive's `onboot: 1`, binds the host's REAL drives (4d) and STARTS the guest + // — right on a replaced host, where the original is gone. Beside a LIVE original it would be a + // second controller for the same household on the same drives. So DR refuses when this host + // still carries the original (the archive's source VMID) or any guest that binds the drives. + // A copy beside the original is the restore-test's job (onboot=0, throwaway stand-ins, torn + // down) or the runbook's beside-restore. Pinned by TestRunBringUp_DRRefusesBesideALiveOriginal. + if spec.Mode == ModeDRGuestLoss { + if why := e.liveOriginalBeside(ctx, lxc, spec.Archive); why != "" { + res.Err = fmt.Errorf("reconcile: dr bring-up refused: %s — a DR restore beside a live original would run two boxes on the same drives (R-834)", why) + return + } + } + base := JournalEntry{OpID: e.bringUpOpID(spec.VMID), VMID: spec.VMID, Kind: bringUpKind, Rollback: true} // OWN the rollback BEFORE any mutation. From here a crash leaves an in-flight Rollback @@ -734,3 +747,47 @@ func net0MAC(cfg proxmox.GuestConfig) string { } return "" } + +// archiveSourceVMID reads the source guest's VMID from a backup volid: a vzdump file +// (`…/vzdump-lxc--.tar.zst`) or a PBS snapshot (`…:backup/ct//