Proxmox only grows disks. The restore dialog gets an optional "Root
disk GiB" field for container backups; with a size, the backup is
restored as a new guest with --rootfs <storage>:<N> (Proxmox's advanced
restore mode).
- A smaller disk is only allowed for a new VMID, checked in the dialog
and again in cli.restore(): with force, Proxmox deletes the old disks
before it unpacks, and a failed unpack then removes the guest.
- The size must be a positive integer (0 gives an unlimited subvol on
ZFS/dir storage) and above the used root disk space of a running
guest.
- The confirm screen is a separate DANGER screen with a red border: it
says mount points are dropped and their backed-up files land in the
root disk, raw lxc.* lines can come back under root@pam, root disk
options are not carried over, and the used space is no fit check.
- Never --unique or ignore-unpack-errors.
Design: docs/plans/lxc-shrink.md (section A).
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>
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>
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>
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>
U opens "Package updates": the pending apt updates of every online
node, with node, package, current and new version, origin and
priority. A node that fails is shown as "<node>: unavailable", and the
other nodes are still listed. A hint says the list is as of the
node's last apt update; this call does not refresh it.
The main frame's bottom border shows "· N package updates" after the
backup count, with a singular for 1. The count is fetched on mount,
after a context switch and whenever the updates screen loads, not on
every guest reload, since it costs one CLI call per node. It uses the
same generation guard as the guest list, so a count from the old
cluster is dropped after a switch.
Running apt update or upgrade stays out of scope; it is a write.
Read-only live check: "mainframe · 23 package updates", and 23 rows
in the screen.
Closes#13
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
l on the main screen opens "Tasks", the recent cluster tasks newest
first. l on the info screen shows only that guest's tasks. Columns:
start, duration (or "running"), node, type, VMID ("-" for node
tasks), user and status. A failed task gets a "!" in the leading
column. Enter opens the task's log in a plain-text widget, so the
output is never parsed as markup.
`task list -o json` has no start or end times, so the list comes from
`/cluster/tasks`. The upid is validated (it must start with "UPID:")
before it goes in as a positional argument.
Review fixes:
- A finished task without a start time no longer crashes the app; its
duration shows "-".
- "running" now means there is no endtime. A finished task with an
empty status shows "unknown" and is flagged.
Read-only live check: 25 tasks listed, and the log of the newest
(vzdelsnapshot 104) ends with TASK OK.
Closes#10
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The guest table gets two last columns, CPU % (cpu * 100) and Mem %
(mem / maxmem), each with one decimal. They show "-" for a guest that
is not running or lacks the field.
The info screen's Resources table gets a header and an "In use"
column: CPU %, memory MiB with %, swap for containers, and rootfs used
GiB with % for containers. VM disks show "-", because the API reports
0 without guest-agent disk info. The info screen reads status and
usage in one status/current call (new cli.current).
After an action, the acted guest's status and usage both come from
status/current, so a guest that has just started no longer shows the
lagging listing's 0.0 %.
One blind-written test expected a VM's CPU cell to read "4 cores". VMs
have always shown sockets ("4 cores × 1 socket", as the resources
spec and its tests define), so that expectation was corrected.
Closes#12
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A "Snapshots" section on the info screen lists the snapshots, newest
first, without Proxmox's "current" pseudo entry. Keys:
- p takes a snapshot. The name defaults to tui_YYYYMMDD_HHMM, the
description is optional, and VMs can include RAM.
- R rolls back to the snapshot under the cursor. The confirm names the
snapshot and its date. A running guest is started again; its status
is read right before the rollback, and if it cannot be read the
rollback does not run.
- D deletes the snapshot under the cursor, after a confirm.
`delete ct|vm <node> <vmid> -y` without the snapshot subcommand
destroys the whole guest. delete_snapshot therefore validates the name
first (letter first, then [A-Za-z0-9_-], not "current"), writes
"snapshot" into the argv as a literal, and checks the argv shape
before running it. A property test covers rejected names.
The info screen now scrolls, because four sections do not fit short
terminals. A description starting with "-" is refused, since it is a
positional argument and could be read as an option.
Live test on CT 104: take, roll back (started again) and delete. The
snapshot list, status, config and IPv4/IPv6 match the baseline.
Closes#8
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The guest table marks with "!" each guest that Proxmox's not-backed-up
list returns. The frame's bottom border shows the count, e.g.
"mainframe · 2 without backup job". This uses Proxmox's own answer, so
it also counts jobs that select guests by pool.
If the coverage call fails, the guests are still listed and a warning
says the coverage is unknown.
Closes#9
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Press n in the info screen to edit one interface: bridge, VLAN tag,
firewall, IPv4 (DHCP, static or none) and IPv6 (SLAAC, DHCP, static or
none). The netN string is parsed, the chosen fields are changed and
the string is written back, so hwaddr, model=MAC, mtu, rate, trunks
and type are kept. For VMs, IP settings go to cloud-init ipconfigN and
only when the VM has a cloud-init drive.
A confirm shows the old and new raw values before anything is sent.
Press u to restore the values from before the last change to that
guest in this session.
Review fixes:
- Undo entries are cleared on a context switch, because VMIDs repeat
across clusters.
- has_cloudinit only looks at disks.
- CIDR needs a decimal prefix: no netmask form and no /0.
- Fields you did not touch are not rewritten.
Live test on CT 104: firewall, VLAN 99, IPv6 DHCP and static IPv4 were
each applied and undone, and net0 ended identical to the baseline.
Going from static back to DHCP left the CT without IPv4 until a
reboot, so the confirm now warns that IP changes on a running
container may need one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Press i on a guest to see its backups and network. Backups from every
backup-capable storage are listed newest first, with the backup jobs
that cover the guest. The network part shows the netN settings
(bridge, VLAN, MAC, IPv4/IPv6 setting, firewall) and the addresses in
use.
Press b in the info screen to back the guest up now: snapshot mode and
zstd, like the scheduled job, to a storage you pick (the job's storage
is preselected). It asks for confirmation, marks the guest busy and
uses --wait.
Fixed after review and a read-only live check:
- prune-backups arrives as a JSON string; parsing it as a dict
crashed the app.
- CT addresses come from ip-addresses. inet6 holds only the
link-local address, so the global SLAAC address was missing.
- The info screen uses the guest's live status, and odd API data
(null size, unknown address types) no longer kills the app.
- The test fake now copies guest dicts; a shared constant was being
mutated across tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Press c to list cv4pve-cli contexts and switch between them. The
active context is always shown in the header.
- Switching runs `cv4pve-cli config use`, which changes the global
current context. cv4pve-cli 2.4.0 has no per-call context option.
- `config list` has no JSON output, so its text lines are parsed.
- The guest table is cleared as soon as another context is chosen, so
the old cluster's guests cannot be acted on.
- A generation counter drops guest listings that started before a
switch, and g and c are refused while a switch runs. Without this,
a reload already in flight could refill the table with the old
cluster's guests, and VMIDs collide across clusters.
103 tests pass. The switch was tested live from mainframe to
mainframe-edge and back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
List VMs and LXC containers from the current cv4pve-cli context and
start, shutdown, stop and reboot them, change cores and memory, and
grow disks. The app shells out to `cv4pve-cli -o json` and stores no
credentials.
- Every mutating call uses --wait: without it cv4pve-cli exits 0 even
when the task fails.
- shutdown, stop and reboot need confirmation; disk growth is
increment-only and asks again because it cannot be undone.
- After an action the acted-on guest's status is read from
status/current, because /cluster/resources lags a few seconds.
- Notifies when a cores/memory change is pending until reboot.
Tests mock cv4pve-cli (85 tests). Live-tested edit, shutdown, start
and reboot on CT 104 (mainframe), restored afterwards; disk resize
was not run live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>