Configuration schema
The full TOML schema, section by section. Defaults shown when applicable.
Top level
default = "homelab" # optional: name of profile to use when --profile omitted
url = "..." # if no [profiles.X] tables, top-level fields ARE the default profile
user = "..."
auth = "token" | "password"
token_id = "..."
token_secret = "..."
verify_tls = trueIf both default and [profiles.X] are present, default selects which profile to load. If only top-level fields are present, those fields are the implicit profile.
[profiles.<name>]
[profiles.homelab]
url = "https://pve1.lan:8006/"
user = "root@pam"
auth = "token" # token | password
token_id = "proxxx"
token_secret = "..." # plain string OR
token_secret_file = "/etc/proxxx/token"
password = "..." # only if auth = "password"
password_file = "..."
verify_tls = true # validate the cluster cert. DEFAULT true
# since v0.13.4 (was false). Proxmox ships
# a self-signed cert: set false deliberately
# for a homelab, or prefer tls_pin_mode.
rate_limit = 10 # max API requests/second (default 10).
# ALSO governs TUI refresh latency: a
# refresh costs ~3 requests per node, so
# on an N-node cluster a cycle takes
# roughly 3N/rate_limit seconds. The TUI
# targets a 5 s refresh and warns on
# screen when it cannot keep up (#277).
# 3 nodes ≈ 1 s; 20 nodes ≈ 6 s;
# 50 nodes ≈ 15 s at the default.
read_only = false # true → refuse all mutations on this
# profile client-side (reads still work);
# exit code 8. Default false. Pair with a
# PVEAuditor PVE token for server-side lock.
mcp_token = "..." # bearer token for `proxxx mcp serve-http`.
# Optional; an empty/whitespace value counts as
# ABSENT. serve-http binds loopback by default; on
# a non-loopback bind (e.g. 0.0.0.0) it REFUSES to
# start unless this is set — override consciously
# with --insecure-bind, in which case a network-
# exposed server with no token denies every request
# (fail-closed, and this survives a SIGHUP that
# clears the token). The --token CLI flag overrides
# this field.[profiles.<name>.reconcile] (GitOps controller)
Opt-in continuous reconciliation. When present, proxxx daemon serve runs the drift-watch pillar for this profile; absent → no watch. The one-shot reconcile run / reconcile converge commands take --source/--path on the CLI instead and don't read this block.
[profiles.homelab.reconcile]
source = "git@github.com:me/cluster.git" # file, dir, or git URL (shallow-cloned each tick)
path = "state.toml" # state file within a dir/git source (default "state.toml")
interval_secs = 300 # drift-watch poll interval, floored at 30 (default 300)
# ── Layer 3: auto-converge (unmanned mutation) — default OFF ──
auto_converge = false # true → after detecting drift each tick, apply it.
# ALWAYS force=false: a Severe-risk drift is NEVER
# auto-applied — it alerts for human review and
# mutates nothing. Respects read_only + incident
# freeze (skips quietly, no alert storm). Disable
# per-process with the `--no-converge` flag or the
# PROXXX_NO_CONVERGE env var.
converge_prune = false # true → auto-converge MAY execute deletes (maps to
# `state apply --prune`) — but ONLY when
# `max_unmanned_changes` is also set. converge_prune=true
# on its own HOLDS every unmanned delete: creates/updates
# converge while deletes stay previewed-but-held. Default
# false: deletes are previewed and held. Enable only
# against a repo with branch protection / atomic pushes.
# ── Unmanned-converge guardrails (both narrow the blast radius; default = none) ──
allowed_families = ["pool", "acl"] # restrict the UNMANNED converge to these state
# families (matched against a change's resource:
# pool, acl, storage, backup-job, firewall-*, ha-*,
# notification-matcher, mappings-pci/usb, …). Absent
# or empty = every family (current behaviour). Lets
# you auto-converge low-stakes families while keeping
# high-stakes ones human-only. The manual
# `reconcile converge` command is NOT restricted.
max_unmanned_changes = 5 # hard cap on changes-per-tick for the UNMANNED path,
# counted AFTER allowed_families filtering, regardless
# of severity. Absent = no cap. Above it → the daemon
# refuses and alerts "needs human review (too many
# changes)". Catches a Warning-tier flood (e.g. a
# partial git revert) that the Severe bulk-change
# circuit-breaker (≥50) would miss. ALSO the gate that
# unlocks unmanned prune: converge_prune only deletes
# while this cap is set. Size it SMALL (single digits) —
# a large cap (e.g. 49) re-opens the 10–49
# unmanned-delete band.[ssh] (top-level default)
[ssh]
key = "/home/fab/.ssh/proxxx_homelab" # ed25519 / rsa private key path
host = "10.0.0.1" # default node for `proxxx perms` and patching
port = 22 # optional, default 22
user = "root" # optional, default "root"
known_hosts = "~/.config/proxxx/known_hosts" # optional, default to XDG path[ssh.guests.<vmid>] (optional override)
proxxx ssh <vmid> resolves a guest's connection details in two steps: it consults this section first, and on miss auto-discovers via QGA (network-get-interfaces for QEMU) or /lxc/{vmid}/interfaces (for LXC). Most operators don't need to populate this block at all.
Pin a per-guest entry only when:
- the guest has no qemu-guest-agent installed (or the agent is off / not running),
- QGA returns only loopback (
127.0.0.0/8) or link-local (169.254.0.0/16) IPs — i.e. the guest is on a private bridge with no usable address from your machine's perspective, - you want a stable DNS name (
web1.lab.example) instead of the rotating DHCP IP QGA would surface.
[ssh.guests."100"]
host = "10.10.10.100"
port = 22
user = "fab"
key_path = "~/.ssh/k8s_master" # optional, falls back to [ssh].key_pathVMIDs must be quoted (TOML key restriction). The wizard's proxxx init --interactive step 4 sub-prompt builds this section interactively; you can also hand-edit it later.
[telegram]
Used by HITL and alert routing.
[telegram]
bot_token = "123456:ABC..." # from @BotFather
chat_id = -1001234567890 # from getUpdates response
allowed_approvers = [123456789] # REQUIRED. Telegram numeric user ids
# permitted to approve/deny. The callback
# HMAC proves proxxx minted the keyboard,
# not who pressed it — without this list
# any member of chat_id could approve a
# destructive op. Numeric ids only
# (usernames are mutable). Absent or
# empty => every callback is refused.[[policies]] (HITL)
[[policies]]
action = "delete" # delete | stop | restart | migrate | exec | move_disk | resize_disk
target = "tag:prod" # tag:<X> | <vmid> | * (numeric or wildcard)
require_approval = true
timeout_secs = 120 # default 120Multiple [[policies]] arrays are evaluated in order. The first matching one wins.
[pbs]
For Proxmox Backup Server browse and restore.
[pbs]
url = "https://pbs.lan:8007/"
user = "proxxx@pbs"
token_id = "reader"
token_secret = "..."
token_secret_file = "/etc/proxxx/pbs-token"
verify_tls = false
rate_limit = 10WARNING
PBS uses : between token_id and secret in the auth header, not = like PVE. proxxx handles this internally — you don't need to pre-format the secret.
[[alerts]]
[[alerts]]
name = "node_down"
trigger = "node_offline" # closed enum: see Alerts integration
threshold = 60 # seconds
storage = "ceph-rbd" # filter for storage_above (optional)
threshold_percent = 85 # for storage_above
severity = "critical" # info | warning | critical
route = ["telegram", "ntfy:topic"]
dedup_secs = 600 # don't re-fire within N secondsThe trigger is a closed enum (node_offline, storage_above, replication_failing). New triggers require code changes — that is intentional, see Alerts.
Resolution order for secrets
For each of token_secret, password, pbs.token_secret:
- CLI flag (
--token-secret VALUE) - Env var (
PROXXX_TOKEN_SECRET,PROXXX_PASSWORD,PROXXX_PBS_TOKEN_SECRET) - Inline TOML value (
<...>_secret = "...") - File reference (
<...>_secret_file = "...") - OS keychain (service
proxxx, key matches the field name)
The first one that resolves wins.
Inline beats the file reference
Steps 3 and 4 were documented in the opposite order until v0.13.4. If you are moving a secret out of the TOML into a 0600 file, delete the inline value — otherwise the stale inline secret keeps winning, your file is never read, and rotating it has no effect. proxxx logs a warning when both are set.
Loaded values live in SecretString: Debug prints [REDACTED] (not even the length, which would leak which credential class it is), there is no Display and no Serialize, so interpolating one into a string or a JSON dump is a compile error, and the wrapped value is zeroized on drop. Note the guarantee covers the value once constructed — the toml parse tree still holds an unwiped copy of any inline secret until config load completes, which is another reason to prefer the file or the keychain.
Environment variables
Beyond the secret env vars above and PROXXX_CONFIG (see File location), these tune runtime behaviour:
| Variable | Effect |
|---|---|
PROXXX_NO_CONVERGE | Disable auto-converge for this process (same as --no-converge): the drift-watch still detects and alerts, but mutates nothing. |
PROXXX_AUDIT_DIR | Relocate the audit DB and its HMAC key off the default path (e.g. onto a separate volume). The directory is created 0700. |
PROXXX_AUDIT_KEY | Relocate only the HMAC key, off the DB volume. A group/world-readable audit.key is refused on load (unix); proxxx audit verify exits non-zero on tamper. |
Defaults summary
| Field | Default |
|---|---|
verify_tls | true |
rate_limit | 10 |
port (SSH) | 22 |
user (SSH) | "root" |
timeout_secs (HITL policy) | 120 |
dedup_secs (alert) | 600 |
severity (alert) | "warning" |
auth | "token" |
File location
| Platform | Path |
|---|---|
| Linux | ~/.config/proxxx/config.toml |
| macOS | ~/Library/Application Support/dev.proxxx.proxxx/config.toml |
| Windows | %APPDATA%\dev\proxxx\proxxx\config.toml |
Set PROXXX_CONFIG=/path/to/file.toml to override.
Validation
proxxx validates the schema on load. A failed validation prints the section and key, then exits with code 3 (Configuration error). Common errors:
urlmissing trailing colon — proxxx adds/api2/jsonitself, so the URL stops at:8006/token_secrethas trailing whitespace — copy-paste from the web UI sometimes appends a newline; trim itauth = "token"withouttoken_id— both fields are required[ssh]referenced butkeyis missing —proxxx ssh <vmid>,proxxx perms, andproxxx patch applyall require it