Add LXC shell via ssh and pct enter
Press t on a running container to suspend the TUI and run `ssh -t root@<node> pct enter <vmid>`. cv4pve-cli has no shell or exec for LXC, so this goes around it and needs key-based root SSH to the PVE node. - VMs, stopped containers and busy guests get a warning instead. - Only exit code 255 (ssh failed) is reported as an error. Other codes are the last status of the container's shell. - OSError is caught inside suspend(). Textual's suspend() has no try/finally, so an exception escaping it would leave the app suspended. - The guest table reloads when the shell returns. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
62b79ab29e
commit
40feb125b4
4 files changed
+272
-1
No files matched your search
@@ -179,3 +179,55 @@ Adding/editing/deleting contexts, `config verify`, restoring the original contex
|
||||
(only `config use` and read-only listing; no guest actions); final context
|
||||
is `mainframe`.
|
||||
- [ ] Push `feat/guest-tui-mvp`, merge to `main` (user-approved).
|
||||
|
||||
---
|
||||
|
||||
# LXC shell
|
||||
|
||||
## Problem
|
||||
|
||||
Open an interactive shell in a running LXC container from the TUI. cv4pve-cli 2.4.0 has
|
||||
no console/shell/exec for LXC (verified: no command; the Proxmox API has no LXC exec),
|
||||
so the app goes around it: SSH to the PVE node as root and run `pct enter <vmid>`.
|
||||
|
||||
## Decision
|
||||
|
||||
Option 1 from the discussion: `ssh -t root@<node> pct enter <vmid>`. The node name is
|
||||
the SSH host (resolves via DNS here: `mainframe`, `mainframe-edge`); the user's key is in
|
||||
root's `authorized_keys` (verified: `ssh -o BatchMode=yes root@mainframe pct status 104`
|
||||
-> `status: running`). This bypasses cv4pve-cli and its API token.
|
||||
|
||||
## `app.py` (UI contract)
|
||||
|
||||
- Key `t` ("Shell") on the selected guest.
|
||||
- Only for `type == "lxc"` and `status == "running"`; otherwise a warning notify that
|
||||
says why (not a container / not running) and nothing runs. Empty table: nothing.
|
||||
- Busy guest (action running) -> warning notify, nothing runs.
|
||||
- Runs on the main thread inside `with self.suspend():` -
|
||||
`subprocess.run(["ssh", "-t", f"root@{node}", "pct", "enter", str(vmid)])`
|
||||
(no capture, no timeout: the user's terminal is handed over; `import subprocess` in
|
||||
app.py so tests patch `cv4pve_tui.app.subprocess.run`).
|
||||
- Exit 255 (ssh itself failed) -> error notify naming the guest and the code. Other
|
||||
exit codes are the container shell's last status (`exit` after a failed command
|
||||
gives 1) and are not errors.
|
||||
- `OSError` from `subprocess.run` is caught *inside* the `suspend()` block: Textual
|
||||
8.2.8's `suspend()` has no try/finally, so an exception escaping it leaves the app
|
||||
suspended.
|
||||
- The guest table reloads after the shell returns (the user may have stopped or
|
||||
rebooted the container from inside).
|
||||
- `textual.app.SuspendNotSupported` (headless / unsupported driver) and `OSError`
|
||||
(no `ssh` binary) -> error notify, app keeps running.
|
||||
- Tests: `App.suspend` raises `SuspendNotSupported` under `run_test`, so tests replace
|
||||
`app.suspend` with a no-op context manager (e.g. `contextlib.nullcontext`) unless
|
||||
testing that error path.
|
||||
|
||||
## Out of scope
|
||||
|
||||
VM consoles (serial/VNC), configurable SSH user/host/options, termproxy/websocket.
|
||||
|
||||
## Steps
|
||||
|
||||
- [x] Tests for the UI contract above - red first.
|
||||
- [x] Implementation - full suite green, ruff clean. README key table gets `t`.
|
||||
- [ ] Interactive test by the user: `t` on CT 104, `exit`, back in the table.
|
||||
- [ ] Push `feat/lxc-shell`, merge to `main` (user-approved).
|
||||
Reference in new issue
Block a user