Proxxx
Terminal cockpit for Proxmox VE & PBS β Rust TUI + CLI, single static binary, six-stage commit gate, no agent on the cluster.
Ask AI about Proxxx
Powered by Claude Β· Grounded in docs
I know everything about Proxxx. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
proxxx
Terminal cockpit for Proxmox VE & Proxmox Backup Server.
Rust Β· async Β· single static binary Β· no installer Β· no agent.
Talks to the things that already exist on your cluster β REST against PVE and PBS, SSH for the rest β instead of asking you to deploy a new daemon.
Who is this for?
Pick the row that matches you and jump straight to the right page.
| If you areβ¦ | β¦you'll care about | Start here |
|---|---|---|
| Homelab solo running 1-3 nodes | wizard, fast TUI, single binary, no daemons | 5-min homelab quickstart |
| Platform / SRE on 10-50 nodes with on-call | HITL Telegram gate, alert daemon, --format json for CI, --profile for multi-cluster | Production checklist Β· HITL |
| DevOps scripting Proxmox in pipelines | typed exit codes, deterministic JSON, pre-flight risk gate, batch ops with --yes | CLI reference Β· Exit codes |
| LLM / agent integrator wiring Claude/Cursor to a cluster | MCP stdio server, compile-time-fixed 10-tool registry, SHA-256 pinned for supply-chain audit | LLM/MCP quickstart |
| Security / compliance evaluating before deploy | typed errors, HITL replay protection, sigstore-signed releases, CycloneDX SBOM, gate on every commit | Production checklist Β· SECURITY.md |
| Contributor sending a PR | 7-stage commit gate (live cluster + mutation lifecycle), no-skip-flags policy | CONTRIBUTING.md Β· Pre-commit gate |
What you get
- One binary β
proxxx. CLI, TUI, MCP server, alert daemon, HITL daemon all in the same executable. - Cluster-wide read in a second β
proxxx ls nodes,proxxx ls guests, fuzzy search across the whole cluster from/. - Pipeline writes β start, stop, migrate, snapshot, clone, backup, patch, disk-move, with
--format jsonfor jq. - Pre-flight risk gate β 11 risk variants (
Locked,Running,LongUptime,TaggedProd,ActiveNetTraffic,HaManaged, β¦) refuse destructive ops on running guests without--allow-risk. - HITL β Telegram-mediated human approval gate, deny-on-timeout (120 s), policy-driven by tag / vmid / wildcard.
- Console handoff β SSH (system
ssh+ QGA / lxc-interfaces auto-discovery), serial (termproxy WebSocket), SPICE (.vv0600), noVNC (system browser) β all fromproxxx <verb> <vmid>. - PBS browse + restore β REST browse plus
proxmox-backup-clientrestore withkill_on_dropsupervision. - MCP server β stdio JSON-RPC for LLM agents, compile-time-fixed tool registry, surface SHA-256 pinned.
- Verifiable releases β every tarball ships with three layers: SHA-256 sidecar, sigstore keyless cosign signature pinned to this exact workflow path (offline-verifiable; transparency-log inclusion proof embedded), and a CycloneDX SBOM generated from
Cargo.lock. Audit withcosign verify-blob+grype/trivy.
Install
Pre-built binaries for macOS Apple Silicon and Linux x86_64-musl are attached to each tagged release. ARM64 Linux builds from source (cross-link toolchain bug; tracked).
Download + verify the full supply-chain trio:
TARGET=x86_64-unknown-linux-musl # or aarch64-apple-darwin
VERSION=0.1.7 # latest at time of writing
gh release download v${VERSION} --repo fabriziosalmi/proxxx \
--pattern "*-${TARGET}.tar.gz" \
--pattern "*-${TARGET}.tar.gz.sha256" \
--pattern "*-${TARGET}.tar.gz.cosign.bundle"
# 1. Checksum
shasum -a 256 -c proxxx-${VERSION}-${TARGET}.tar.gz.sha256
# 2. Sigstore keyless signature (offline; cert pinned to release.yml)
cosign verify-blob \
--bundle proxxx-${VERSION}-${TARGET}.tar.gz.cosign.bundle \
--certificate-identity-regexp 'https://github.com/fabriziosalmi/proxxx/.github/workflows/release.yml@.*' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
proxxx-${VERSION}-${TARGET}.tar.gz
# 3. (optional) Audit the CycloneDX SBOM
gh release download v${VERSION} --repo fabriziosalmi/proxxx \
--pattern "*.cdx.json" --pattern "*.cdx.json.sha256"
shasum -a 256 -c proxxx-${VERSION}.cdx.json.sha256
grype sbom:proxxx-${VERSION}.cdx.json # or trivy / cyclonedx-cli
tar xzf proxxx-${VERSION}-${TARGET}.tar.gz
./proxxx-${VERSION}-${TARGET}/proxxx --version
If you only want the binary fast (no verification), skip steps 2β3 and run just shasum -a 256 -c β¦ from the snippet above. Production deployments should run all three β see the Production checklist.
Or build from source (needs Rust 1.95+):
git clone https://github.com/fabriziosalmi/proxxx.git
cd proxxx && cargo build --release
./target/release/proxxx --version
The Linux musl artifact is statically linked β runs on every distro from RHEL 6 to Alpine 3.x without GLIBC drama.
Quick start
proxxx init --interactive # 5-step wizard: prompts for URL, auth, TLS, optional
# SSH + Telegram, validates each input against the
# live cluster before write. Recommended for first
# run β wrong field caught here, never lands in TOML.
proxxx init # non-interactive variant: writes a commented
# starter config.toml; refuses to overwrite β pass
# --force if you mean it. Edit url / user /
# token_id / token_secret manually after.
proxxx ls nodes # validates the connection.
proxxx # TUI (no args). Press ? for the keymap; the
# bottom-row footer shows contextual binds always.
proxxx --help # full subcommand list.
proxxx version --json # build + capability metadata.
The starter config.toml carries inline comments for every secret-resolution path (CLI flag β env var β 0600 file β OS keychain). Optional sections β HITL via Telegram, SSH layer, PBS, alerts, policies β are commented out so the API-only operator doesn't have to delete anything.
Daily-driver TUI
Run with no arguments. Vim keys, fuzzy search across the cluster (/), command palette (:), quick-open palette (Ctrl+K). 18 views over the same Elm-pattern reducer:
1 Dashboard | 2 Nodes | 3 Guests | 4 Storage |
|---|---|---|---|
H Heatmap | B Backup board | G Config grep | Q Operation queue |
T Audit timeline | Z Snapshot tree | D Drift compare | W Hardware passthrough |
Multi-select + bulk ops with pre-flight risk preview. Operation queue with dry-run, diff preview, replay-as-script export (proxxx CLI / pvesh / curl / Ansible), and HITL approval gate (Telegram, policy-driven).
The terminal is restored on every exit path β happy, ? early-return, panic. RAII TerminalGuard plus a flight-recorder panic hook installed in main() before the runtime starts.
Pipeline-friendly CLI
# Read
proxxx ls guests --format json | jq '.[] | select(.status == "running") | .vmid'
proxxx ha preview --node pve1 # failover what-if
proxxx hw conflicts --node pve1 # PCI passthrough audit
proxxx perms root@pam --node pve1 # effective permissions
# Write β every destructive op routes through the pre-flight risk gate
proxxx start 100 101 102
proxxx delete 100 --yes
proxxx migrate 100 pve2 --yes
proxxx snapshot create 100 --name pre-upgrade
proxxx disk move 100 --disk scsi0 --storage ceph-rbd --yes
proxxx patch apply --reboot=auto --dry-run
# Console handoff
proxxx ssh 100 # interactive ssh into guest (system ssh +
# QGA / lxc-interfaces auto-discovery; falls
# back to [ssh.guests."100"] when explicit)
proxxx serial 100 --node pve1 # raw termproxy WebSocket
proxxx spice 100 --node pve1 # writes 0600 .vv, launches remote-viewer
proxxx novnc 100 --node pve1 # opens browser to web UI's noVNC
# Long-running daemons
proxxx alerts watch --interval 30 # rule-driven alert daemon
proxxx hitl serve # Telegram approval daemon
proxxx mcp serve # stdio JSON-RPC for LLM agents
proxxx mcp tools --checksum # registry SHA-256 for audit pinning
Exit codes are stable contract: 0 success, 1 runtime error, 2 argument / config error, 3 HITL denied, 4 precondition refused (running guest, missing config, etc.).
Configuration
Default location follows the directories project-dirs convention:
| Platform | Path |
|---|---|
| Linux | ~/.config/proxxx/config.toml |
| macOS | ~/Library/Application Support/dev.proxxx.proxxx/config.toml |
Secrets resolve in order: CLI flag β PROXXX_TOKEN_SECRET env β token_secret_file (0600 enforced) β inline TOML β OS keychain. Loaded values live in Zeroizing<String> and are wiped from the heap on Drop.
| Optional section | Unlocks |
|---|---|
[telegram] | HITL approvals + alert routing |
[ssh] | Patching orchestrator, proxxx perms, guest SSH |
[ssh.guests.X] | Per-guest SSH overrides (optional β proxxx ssh <vmid> auto-discovers via QGA / lxc-interfaces by default; pin only when the agent's off, only loopback/link-local IPs are returned, or you want a stable DNS name) |
[pbs] | PBS browse + restore |
[[alerts]] | Alerting daemon β node_offline, storage_above, replication_failing |
[[policies]] | HITL gating rules β match by tag / vmid / wildcard |
Quality gate
Six stages, run as both a pre-commit hook and the CI contract in .github/workflows/ci.yml.
| Stage | What | Time |
|---|---|---|
| 1 | cargo fmt --all -- --check | ~3 s |
| 2 | cargo clippy --release --all-targets | 10β60 s |
| 3 | cargo audit --deny warnings | 3β5 s |
| 4 | cargo test --release --all-targets | 10β90 s |
| 5 | tests/live/test_run.sh (88 read-only probes against the live cluster) | ~30 s |
| 6 | tests/live/test_mutation.sh (LXC + cluster-level CRUD + QEMU; opt-in QGA via PROXXX_E2E_QGA_VMID=<vmid>) | ~60 s |
git config core.hooksPath .githooks
chmod +x scripts/gate.sh .githooks/pre-commit .githooks/pre-push
cargo install cargo-audit --locked
The clippy [lints.clippy] block in Cargo.toml denies unwrap_used, expect_used, panic, todo, await_holding_lock in production code.
Architecture
Pure Elm-pattern TUI over a typed REST client. The reducer is sync, total, and tested without a runtime.
crossterm key tokio::mpsc<DataMsg>
user ββββββββββββββββββΊ event::map_key ββΊ Action
β
βΌ
app::update(state, action)
β
βββββββββββββββ΄βββββββββββββ
βΌ βΌ
AppState mutation Option<SideEffect>
β
βΌ
enforce_preflight β check_hitl
(risk gate) (Telegram round-trip)
β
βΌ
ProxmoxGateway / PbsGateway / SshPool
| Module | Responsibility |
|---|---|
src/app.rs | Pure reducer. No I/O, no async. ~70 Action variants, ~20 SideEffect. |
src/api/ | ProxmoxGateway trait, typed ApiError enum (8 categorical variants), reqwest client with 32 MiB body cap and rate limiter. |
src/api/error.rs | Unauthorized, Forbidden, NotFound, RateLimited, PayloadTooLarge, StorageHang, Transport, Schema. Callers .downcast_ref() for differentiated handling. |
src/pbs/ | PBS REST browse + kill_on_drop(true) supervision over proxmox-backup-client restore. |
src/ssh/ | russh, publickey only, dedicated TOFU known_hosts (separate from ~/.ssh/), per-node connection pool. |
src/app/cache.rs | SQLite-backed time-travel cache, drives proxxx replay <timestamp>. |
src/app/preflight.rs | 11 risk variants with per-op weighting and --allow-risk override. |
src/hitl/ | Real Telegram round-trip via HitlCoordinator + a single shared getUpdates poller. Deny on 120 s timeout, deny when Telegram unconfigured but a policy matched. |
src/mcp/ | Stdio JSON-RPC server. Compile-time-fixed tool registry (10 tools). Surface SHA-256 pinned via proxxx mcp tools --checksum. |
src/util/ | panic_hook (flight recorder), terminal_guard (RAII raw-mode), shutdown (SIGTERM / SIGINT for daemons). |
Documentation
- VitePress site β
docs/and the live build. Local preview:cd docs && npm install && npx vitepress dev. CHANGELOG.mdβ what shipped, with the SemVer contract for CLI / JSON / config / MCP registry surfaces.pre-commit/β four matrices distinguishing implemented from verified end-to-end:01-feature-coverage.mdΒ·02-error-handling.mdΒ·03-security-invariants.mdΒ·04-resiliency-and-chaos.mdSECURITY.mdβ vulnerability reporting policy + scope + hardening snapshot.CONTRIBUTING.mdβ onboarding, the gate, live-cluster verification format..cargo/audit.tomlβ supply-chain advisory ignore policy.
Live cluster harness
tests/live/ drives the release binary against a real PVE cluster β separate from the cargo integration tests in tests/ which use wiremock.
| File | Tracked | Purpose |
|---|---|---|
test_run.sh | β | 88 read-only probes covering the full CLI surface; logs to test_run.log |
test_mutation.sh | β | Full mutation lifecycle with trap EXIT cleanup: LXC 9999 (create β start β snapshot β stop β delete), cluster-level CRUD (pool / firewall-cluster alias+group+ipset / backup-job / notifications endpoint+matcher / storage-defs), QEMU 9998 from alpine ISO, opt-in QGA round-trips via PROXXX_E2E_QGA_VMID=<vmid> |
test_*.log | β | Generated by the harness |
Honest non-goals
Design boundaries β proxxx will not ship these.
- No GUI. Proxmox already has a web UI; proxxx is for terminal users who want CLI / TUI / scripting parity.
- No frame rendering for graphical SPICE or VNC. proxxx hands off to
remote-viewer/virt-viewer(SPICE) or the system browser (noVNC). It never holds pixel buffers. - No re-implementation of Perl algorithms in Rust where the Perl on the node is the ground truth.
proxxx permsshells out topveum user permissionsover SSH and parses, since thepve-access-controlevaluator is canonical. The API-sideproxxx access permissionsis also available β same typed tree from/access/permissions, no SSH dependency β for the common case where the evaluator's full expansion isn't needed. - No new dependencies for trivial things. Three-line per-platform
Command::newbeats pullingopenerfor a launcher. - No multi-cluster aggregation in the TUI β single profile per process by architectural decision; switch with
--profile. - No Ceph cluster writes. Operators reach for the
cephCLI directly on the node where the kernel module is loaded; proxxx wraps Ceph reads (status, metadata, flags) but not destructive ops (osd add/down, mon create, pool prune). - No SDN config writes. PVE SDN is opt-in cluster config that few clusters enable, and the wire shape changes between PVE versions. Skipped rather than ship a fragile surface.
- No browser-only auth flows. U2F/WebAuthn registration and OIDC's redirect-callback dance both need a browser to drive them. proxxx exposes the API-driven primitives (token CRUD, password change, ACL editing) but stays out of
/access/openid/*and/access/tfa/u2fβ there's no terminal UX for those that beats the web UI. - No snapshot rollback as a destructive trigger. The snapshot-tree TUI shows a read-only rollback impact preview (what would be discarded + time delta); the actual rollback runs through
qm rollback/pct rollbackor the PVE web UI. Read-only inspector views never expose destructive entry points by design.
License
MIT. Copyright Β© 2026 Fabrizio Salmi. See LICENSE.
