r on the info screen restores the backup under the cursor of the backup list. There are two targets:
New guest (the default): the restore gets the next free VMID from get cluster nextid, which you can edit. The original guest is not touched. A VMID that a listed guest already uses is refused.
Overwrite: replaces the guest itself.
The guest must be stopped. Its live status is read when you apply, and again right before the call.
You must type the VMID to confirm, and then confirm once more in an "Overwrite" dialog.
--storage is always passed. It defaults to the storage of the guest's rootfs or first disk. The choices are the node's storages that can hold container disks (rootdir) or VM disks (images). "Start after restore" is off by default, because a copy keeps the original's MAC address and IP.
Safety
Overwrite only the guest's own VMID:cli.restore refuses force (overwrite) for any VMID other than the guest's own, with an explicit raise. A hypothesis property checks that no other VMID ever reaches the CLI with force.
Validated arguments: the volid and the storage name are checked before they are passed to the CLI.
Backup type must match the guest: a reused VMID can carry a qemu backup for a container, or the reverse, so a mismatch is refused. This came out of the adversarial review. Whether PVE would reject such an archive before removing the old guest is not verified.
Earlier guests' backups are labelled: a backup from an earlier guest with the same VMID (shown with a) carries that guest's name in both the dialog and the confirm.
Before load:r says "still loading" until the info screen has loaded.
Timeout: restore gets a one-hour CLI timeout.
Verification
677 tests pass.
ruff check and ruff format --check report no issues.
betterleaks found no secrets.
Read-only live check: the dialog opened for CT 104 with next id 105 and local-lvm preselected. Escape closed it without any call.
Not run live: a live restore would create a guest that then has to be deleted, and the issue leaves that to the user's approval.
## What
`r` on the info screen restores the backup under the cursor of the backup list. There are two targets:
- **New guest** (the default): the restore gets the next free VMID from `get cluster nextid`, which you can edit. The original guest is not touched. A VMID that a listed guest already uses is refused.
- **Overwrite**: replaces the guest itself.
- The guest must be stopped. Its live status is read when you apply, and again right before the call.
- You must type the VMID to confirm, and then confirm once more in an "Overwrite" dialog.
`--storage` is always passed. It defaults to the storage of the guest's rootfs or first disk. The choices are the node's storages that can hold container disks (`rootdir`) or VM disks (`images`). "Start after restore" is off by default, because a copy keeps the original's MAC address and IP.
## Safety
- **Overwrite only the guest's own VMID:** `cli.restore` refuses `force` (overwrite) for any VMID other than the guest's own, with an explicit raise. A hypothesis property checks that no other VMID ever reaches the CLI with `force`.
- **Validated arguments:** the volid and the storage name are checked before they are passed to the CLI.
- **Backup type must match the guest:** a reused VMID can carry a qemu backup for a container, or the reverse, so a mismatch is refused. This came out of the adversarial review. Whether PVE would reject such an archive before removing the old guest is not verified.
- **Earlier guests' backups are labelled:** a backup from an earlier guest with the same VMID (shown with `a`) carries that guest's name in both the dialog and the confirm.
- **Before load:** `r` says "still loading" until the info screen has loaded.
- **Timeout:** restore gets a one-hour CLI timeout.
## Verification
- 677 tests pass.
- `ruff check` and `ruff format --check` report no issues.
- `betterleaks` found no secrets.
- Read-only live check: the dialog opened for CT 104 with next id 105 and `local-lvm` preselected. Escape closed it without any call.
- Not run live: a live restore would create a guest that then has to be deleted, and the issue leaves that to the user's approval.
This PR is stacked on #31 → #30 → #29 → #28 → #27.
Closes #15
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
At startup the app runs `cv4pve-cli --version`. If it differs from
cli.TESTED_VERSION (2.4.0), a warning says which version was found
and which one was tested. The app parses output formats that are not
a stable API: the config list text, prune-backups as a JSON string,
the CT ip-addresses JSON string and the exit codes with --wait. A
newer cv4pve-cli could break them in confusing ways.
If cv4pve-cli cannot be run at all, an error says so once, instead of
every later call failing without explanation. The README notes the
tested version.
Closes#14
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The README gets an "Install" section: install with uv tool from the
Gitea repo or from a local checkout, then upgrade and uninstall. The
"Run" section says to use `cv4pve-tui` when installed and
`uv run cv4pve-tui` from a checkout.
Verified by installing into throwaway tool dirs (UV_TOOL_DIR and
UV_TOOL_BIN_DIR under /tmp): `uv tool install .` gave a cv4pve-tui
binary. Started from /tmp in a pseudo-terminal, it drew the guest
frame and ran until it was stopped. The pyproject metadata (entry
point, readme, requires-python) was already enough.
Closes#17
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/ opens a filter box under the guest table, and the table filters as
you type. Terms are separated by spaces, all must match, and case is
ignored:
- a plain term matches part of the name, the VMID or a tag
- is:running and is:stopped match the status
- type:lxc/ct and type:vm/qemu match the guest type
Enter keeps the filter and goes back to the table. Escape clears it,
from the box or from the table. The frame shows
"· filter: <text> · N of M". Actions only ever see the visible rows,
and the filter survives reloads and context switches.
Review fixes:
- Typing "[/b]" or "[/]" crashed the app, because the filter text went
into the border subtitle, which Textual parses as markup. The
subtitle is now plain text, so "[red]" is also shown literally.
- Escape on the table now clears a filter that was applied with Enter.
- guest_matches tolerates a None status or type.
Read-only live check on mainframe: "debian" gives 104, "type:vm" gives
108, "community-script is:running" gives 14 of 19, and "is:stopped"
gives 0 of 19.
Closes#16
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The info screen's Resources table shows Autostart (yes/no), Startup
(order, up and down delays) and Tags. o opens a dialog to change them.
Only keys that differ are sent, in one `set <kind> config` call
through the existing set_net. A confirm lists the old and new value
of each key.
Tags are removed with `--delete tags` on set config. The CLI also has
a `delete ct|vm ... tags` command, but it shares the form that
destroys the whole guest, so the app never uses it; nothing in the
code builds a delete ct|vm argv. Inputs are validated before sending:
startup values must be ints >= 0, and tags must match
[A-Za-z0-9_][A-Za-z0-9_+.-]*. They are split on ; , or whitespace and
deduplicated.
Live test on CT 104: onboot 1 -> 0, startup order=5,up=10, tag
tui-test added, then all set back through the dialog. The config
ended identical to the baseline.
Closes#18
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
f on the info screen runs fstrim in a running VM with the guest agent
enabled. A container gets an info note (no guest agent; use pct
fstrim on the node). A stopped VM or one without an agent gets a
warning, and nothing runs. Otherwise a confirm comes first, then
agent ping and fstrim. The result reports the trimmed total
(GiB/MiB), the number of paths and any per-path errors.
Review fixes:
- Odd agent output no longer turns a finished trim into "fstrim
failed". Unreadable output gives "no results". Odd entries are
repaired: non-dict entries are dropped, a non-int trimmed becomes 0
and a missing path becomes "?". Well-formed entries are returned
unchanged.
- fstrim gets a 30-minute CLI timeout instead of 330 s, because a big
disk can take longer.
- Notifications are never parsed as markup (GuestApp.notify defaults
to markup=False). Guest names, paths and CLI or agent errors are
untrusted text, so a "[/b]" in them could break the toast.
- f before the info screen has loaded says "still loading" instead of
a wrong "not running".
- "1 path" in the singular.
Not run live: the issue asks for approval before fstrim on VM 108.
Only the harmless agent ping was run there, and it answered.
Closes#19
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What
ron the info screen restores the backup under the cursor of the backup list. There are two targets:get cluster nextid, which you can edit. The original guest is not touched. A VMID that a listed guest already uses is refused.--storageis always passed. It defaults to the storage of the guest's rootfs or first disk. The choices are the node's storages that can hold container disks (rootdir) or VM disks (images). "Start after restore" is off by default, because a copy keeps the original's MAC address and IP.Safety
cli.restorerefusesforce(overwrite) for any VMID other than the guest's own, with an explicit raise. A hypothesis property checks that no other VMID ever reaches the CLI withforce.a) carries that guest's name in both the dialog and the confirm.rsays "still loading" until the info screen has loaded.Verification
ruff checkandruff format --checkreport no issues.betterleaksfound no secrets.local-lvmpreselected. Escape closed it without any call.This PR is stacked on #31 → #30 → #29 → #28 → #27.
Closes #15
Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com