# cv4pve-cli-tui [![CI](https://git.duckboot.net/Duckboot/cv4pve-cli-tui/actions/workflows/ci.yaml/badge.svg)](https://git.duckboot.net/Duckboot/cv4pve-cli-tui/actions) Textual TUI for [cv4pve-cli](https://github.com/Corsinvest/cv4pve-cli): list Proxmox VE guests (VMs and LXC), start/shutdown/stop/reboot them, and change cores, memory and disk size. The table shows CPU % and Mem % of running guests and the info screen shows in-use values (refresh with `g`). It shells out to `cv4pve-cli` and uses its current context (`cv4pve-cli config use `); it stores no credentials itself. Tested with cv4pve-cli 2.4.0; with another version the app warns at startup, because it parses output formats that are not a stable API. ## Install Needs [uv](https://docs.astral.sh/uv/) and a working `cv4pve-cli` on `PATH`. ```sh uv tool install git+https://git.duckboot.net/Duckboot/cv4pve-cli-tui cv4pve-tui # from any directory uv tool upgrade cv4pve-tui # later: get the newest main uv tool uninstall cv4pve-tui ``` From a local checkout instead: `uv tool install .` (add `--force` to reinstall after changes). ## Run Installed: `cv4pve-tui`. From a checkout without installing: ```sh uv run cv4pve-tui ``` | Key | Action | |-----|--------| | `s` | start | | `S` | shutdown (confirm) | | `x` | stop - hard power-off (confirm) | | `r` | reboot (confirm) | | `Enter` | info: resources (CPU, memory, swap, disks), backups, backup job, network (config and runtime IPs) | | `Enter` then `e` | in info: edit cores / memory / grow a disk (disk growth asks again; it cannot be undone) | | `Enter` then `b` | in info: backup now (snapshot, zstd, storage choice); `a` there = also show backups of earlier guests that had the same VMID | | `Enter` then `n` | in info: edit network (bridge, VLAN, firewall, IPv4/IPv6; VM IP via cloud-init at next boot); `u` there = undo the last network change of this guest (this session only) | | `Enter` then `o` | in info: edit autostart, startup order (order, up and down delay) and tags (one confirmed `set config` call; empty fields remove the setting) | | `Enter` then `p` / `R` / `D` | in info: take a snapshot (name, description, VM: include RAM) / roll back to the snapshot under the cursor (confirms; starts the guest again if it is running) / delete the snapshot under the cursor (confirms) | | `Enter` then `f` | in info: fstrim in a running VM with the guest agent enabled (confirms; containers: use `pct fstrim` on the node) | | `Enter` then `r` | in info: restore the backup under the cursor, as a new VMID (default, the original stays untouched; a copy keeps the original's network settings, so leave Start off until they are changed) or overwriting the guest itself (stopped guests only; type the VMID, then confirm); container backups: optional smaller root disk, see *Shrinking a container's root disk* | | `t` | shell in a running LXC container (`ssh -t root@ pct enter `) | | `l` | tasks: recent cluster tasks (start, duration, node, type, VMID, user, status; `!` marks failures); `Enter` shows the log of the task under the cursor, `g` reloads. In info: `l` = only this guest's tasks | | `U` | package updates: pending updates per online node (node, package, current, new, origin, priority), read-only; `g` reloads. The list is as of the node's last `apt update`. The frame's bottom border shows the count | | `g` | refresh | | `/` | filter the guest table as you type: space-separated terms, all must match (case-insensitive); plain term = part of name, VMID or a tag; `is:running` / `is:stopped`; `type:lxc` (or `ct`) / `type:vm` (or `qemu`). `Enter` keeps the filter and returns to the table, `Escape` clears it. The frame's bottom border shows `filter: ยท N of M` | | `c` | switch cv4pve-cli context (cluster) | | `q` | quit | Warning: link changes (bridge, VLAN, firewall) apply immediately on a running guest and can cut its network (and DNS for others if the guest is your DNS server). IP changes on a running container may need a reboot of the container (seen: static -> DHCP gave no IPv4 until a reboot). The shell key needs key-based root SSH to the PVE node (LXC only). Switching context runs `cv4pve-cli config use`, which changes cv4pve-cli's global current context: other shells and scripts follow, and it stays after the app exits. A `!` in the first column marks a guest that no backup job covers (Proxmox's `not-backed-up` list); the frame's bottom border shows how many. Empty fields in the edit dialog are left unchanged. If a CPU/memory change cannot be hot-plugged, the app tells you it applies at the next reboot. ## Shrinking a container's root disk Proxmox only grows disks. Two ways to get a smaller container root disk, both **dangerous**; take a backup first and test on a throwaway container. **In the TUI** (`Enter`, then `r` on a container backup): fill in *Root disk GiB*. This restores the backup as a **new** guest with a new, smaller root disk (Proxmox's "advanced restore mode"); overwriting the original with a smaller disk is refused, because Proxmox deletes the old disks before it unpacks the backup. In this mode every mount point (`mpN`) of the backup is dropped: files of mount points with `backup=1` land inside the root disk, `backup=0` ones are not in the backup at all. Raw `lxc.*` lines (bind mounts too) can come back if cv4pve-cli runs as root@pam, so do not run both guests at once. A disk that is too small makes the restore fail; the original is untouched. **On the node** (root shell, container stopped, keeps the VMID): ```sh scripts/pct-shrink-rootfs # measure and print the plan scripts/pct-shrink-rootfs --yes # do it ``` It adds a temporary `mp9` of the new size (Proxmox allocates and formats it), copies the root disk into it with rsync, then swaps it into `rootfs` under Proxmox's config lock. The old root disk stays as an `unusedK` volume until you delete it with the exact `pct set --delete unusedK` the script prints; the old config is copied to `/root/pct-shrink--.conf`. It refuses containers with snapshots, pending changes, a lock, template or protection flag, HA, replication, or any mount point (`mpN`, `lxc.mount.entry`), and sizes the data does not fit with headroom (5 % ext4 reserve, metadata, inodes). chattr flags (`+i`, `+a`) are not copied. Even the dry run mounts the container briefly (`pct mount`). Design: `docs/plans/lxc-shrink.md`. ## Development ```sh uv sync uv run pytest uv run ruff check src tests && uv run ruff format --check src tests ``` Tests mock `cv4pve-cli`; they never touch a cluster. CI (`.gitea/workflows/ci.yaml`) runs the same commands on every PR into `main`; ruff is pinned in the dev group so local runs and CI check the same rules. Design: `docs/plans/guest-tui-mvp.md`.