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 = false
rate_limit = 10 # max API requests/second (default 10)
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[[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) - File reference (
<...>_secret_file = "...") - Inline TOML value (
<...>_secret = "...") - OS keychain (service
proxxx, key matches the field name)
The first one that resolves wins. Loaded values live in Zeroizing<String> and are wiped from the heap on Drop.
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