Restore a guest from a backup in the info screen
r on the info screen restores the backup under the cursor of the backup list. - New guest (default): the next free VMID from `get cluster nextid`, editable. The original is left untouched. An id already used by a listed guest is refused. - Overwrite: replaces the guest itself. The guest must be stopped; its live status is read at apply and again right before the call. The VMID must be typed to confirm, then an "Overwrite" confirm follows. --storage is always passed. It is preselected to the storage of the guest's rootfs or first disk, and the choices are the node's storages that can hold CT (rootdir) or VM (images) disks. Start after restore is off by default, because a copy keeps the original's MAC address and IP. Safety: - cli.restore refuses force (overwrite) for any VMID other than the guest's own (explicit raise). A hypothesis property checks that no other VMID ever reaches the CLI with force. - The volid and storage are validated before they become arguments. - A backup of the other guest type is refused. A reused VMID can carry a qemu backup for a CT or the other way round, and whether PVE rejects a mismatched archive before removing the old guest is not verified. - An earlier guest's backup (visible with a) is labelled with that guest's name in the dialog and the confirm. - r before the info screen has loaded says "still loading". - Restore gets a 1-hour CLI timeout. Not run live: a live test would create a guest that then has to be deleted, which the issue reserves for the user's approval. A read-only check opened the dialog for CT 104 (next id 105, local-lvm preselected) and closed it without any call. Closes #15 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit was merged in pull request #32.
This commit is contained in:
1 parent
262441916c
commit
0df928ab11
6 files changed
+862
-3
No files matched your search
@@ -735,3 +735,63 @@ CLI facts:
|
||||
- yes -> busy guard, worker: `cli.agent_ping` then `cli.fstrim`; done notify with the
|
||||
trimmed total in GiB/MiB and the number of paths (errors per path mentioned);
|
||||
CliError -> error notify (ping failure says the agent did not answer).
|
||||
|
||||
---
|
||||
|
||||
# Restore a guest from a backup (issue #15)
|
||||
|
||||
## Decision
|
||||
|
||||
From the info screen's backup list, key `r` ("Restore backup"; `R` stays snapshot
|
||||
rollback). Two targets: a new VMID (default, original untouched) or overwriting the
|
||||
guest itself (destructive). Not run live: a live test would create a guest that then
|
||||
needs deleting, which the issue reserves for the user's approval. Tested with mocks.
|
||||
|
||||
CLI facts (cv4pve-cli 2.4.0, read-only probes):
|
||||
|
||||
- `do restore ct|vm <node> <vmid> <volume> [--key value] -y --wait` - "creates/overwrites
|
||||
the target vmid". API params: `storage` (target storage for the disks), `force`
|
||||
(bool; required to overwrite an existing guest), `start` (bool), `unique` (VM: new
|
||||
MACs).
|
||||
- `get cluster nextid -o json` -> a JSON number, e.g. `105`.
|
||||
- Backup entries carry `volid` (`truenas-backup:backup/vzdump-lxc-104-...tar.zst`),
|
||||
`subtype` (`lxc`|`qemu`), `vmid`.
|
||||
- Node storages: `content` lists `rootdir` (CT disks) and/or `images` (VM disks).
|
||||
|
||||
### `cli.py`
|
||||
|
||||
- `next_id() -> int`.
|
||||
- `valid_volid(volid) -> str` - ValueError unless it matches
|
||||
`^[A-Za-z0-9][A-Za-z0-9_.-]*:backup/vzdump-(lxc|qemu)-\d+-\S+$` (positional argument).
|
||||
- `restore_storages(guest) -> list[str]` - enabled+active storages on the guest's node
|
||||
whose `content` has `rootdir` (CT) or `images` (VM), API order.
|
||||
- `restore(guest, volid, vmid, storage, force=False, start=False)` ->
|
||||
`do restore <kind> <node> <vmid> <volid> --storage <storage> [--force 1] [--start 1]
|
||||
-y --wait`; kind from the backup's guest kind (the guest dict's `type`); `vmid` a
|
||||
positive int; volid validated; `force=True` only allowed when `vmid ==
|
||||
guest["vmid"]` (ValueError otherwise - never overwrite some other guest).
|
||||
|
||||
### `app.py` (UI contract)
|
||||
|
||||
- `r` on the info screen: the backup under the cursor of `#backups` (the visible rows;
|
||||
hidden earlier-guest backups are not restorable unless shown with `a`). No backup ->
|
||||
info notify.
|
||||
- `RestoreScreen` (DialogScreen, title "Restore <date> backup of <name> (<vmid>)"):
|
||||
`RadioSet#target` with `RadioButton#new` (default, "New guest") and
|
||||
`RadioButton#overwrite` ("Overwrite <name> (<vmid>)"); `Input#newid` prefilled with
|
||||
`cli.next_id()` (used for "new"); `Select#storage` (`restore_storages`, preselected
|
||||
to the storage of the guest's rootfs/first disk if listed); `Checkbox#start` (off);
|
||||
`Input#confirm` (typed VMID, only needed for overwrite); `#apply`/`#cancel`; escape =
|
||||
cancel. A note says a new copy keeps the original's network settings (same static
|
||||
IP), so leave Start off until it is changed.
|
||||
- Apply, new: `newid` must be a positive int not used by any guest in the current
|
||||
listing (app.guests) -> else error notify, no call. Then `ConfirmScreen` titled
|
||||
"Restore" summarising backup, target VMID, storage, start; yes -> busy guard on the
|
||||
source guest, worker `cli.restore(guest, volid, newid, storage, force=False,
|
||||
start=...)`, notify, main list reloads.
|
||||
- Apply, overwrite: the guest must be stopped (live `cli.status` at apply time) -> else
|
||||
error notify; `Input#confirm` must equal the VMID -> else error notify; then
|
||||
`ConfirmScreen` titled "Overwrite" saying the guest's current disks and config are
|
||||
replaced and cannot be undone; yes -> `cli.restore(guest, volid, guest vmid, storage,
|
||||
force=True, start=...)`.
|
||||
- All CliErrors -> error notify; busy released; markup-safe texts.
|
||||
Reference in new issue
Block a user