proximo-proxmox 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. proximo_proxmox-0.1.1/.github/workflows/ci.yml +24 -0
  2. proximo_proxmox-0.1.1/.gitignore +33 -0
  3. proximo_proxmox-0.1.1/CHANGELOG.md +180 -0
  4. proximo_proxmox-0.1.1/Dockerfile +16 -0
  5. proximo_proxmox-0.1.1/LANDSCAPE.md +173 -0
  6. proximo_proxmox-0.1.1/LICENSE +201 -0
  7. proximo_proxmox-0.1.1/PKG-INFO +159 -0
  8. proximo_proxmox-0.1.1/POSITIONING.md +159 -0
  9. proximo_proxmox-0.1.1/README.md +131 -0
  10. proximo_proxmox-0.1.1/ROADMAP.md +134 -0
  11. proximo_proxmox-0.1.1/debian/README.Debian +23 -0
  12. proximo_proxmox-0.1.1/debian/changelog +7 -0
  13. proximo_proxmox-0.1.1/debian/control +25 -0
  14. proximo_proxmox-0.1.1/debian/copyright +23 -0
  15. proximo_proxmox-0.1.1/debian/rules +5 -0
  16. proximo_proxmox-0.1.1/debian/source/format +1 -0
  17. proximo_proxmox-0.1.1/packaging/optional-daemon-mode.service.example +35 -0
  18. proximo_proxmox-0.1.1/packaging/proximo.env.example +26 -0
  19. proximo_proxmox-0.1.1/pyproject.toml +55 -0
  20. proximo_proxmox-0.1.1/scripts/live-smoke/README.md +144 -0
  21. proximo_proxmox-0.1.1/scripts/live-smoke/lib.sh +179 -0
  22. proximo_proxmox-0.1.1/scripts/live-smoke/netplane-smoke.py +147 -0
  23. proximo_proxmox-0.1.1/scripts/live-smoke/netplane-smoke.sh +51 -0
  24. proximo_proxmox-0.1.1/scripts/live-smoke/phase1-smoke.py +229 -0
  25. proximo_proxmox-0.1.1/scripts/live-smoke/phase1-smoke.sh +57 -0
  26. proximo_proxmox-0.1.1/scripts/live-smoke/readonly-smoke.py +88 -0
  27. proximo_proxmox-0.1.1/scripts/live-smoke/readonly-smoke.sh +41 -0
  28. proximo_proxmox-0.1.1/src/proximo/__init__.py +7 -0
  29. proximo_proxmox-0.1.1/src/proximo/__main__.py +9 -0
  30. proximo_proxmox-0.1.1/src/proximo/_a2a_entry.py +36 -0
  31. proximo_proxmox-0.1.1/src/proximo/_tls.py +27 -0
  32. proximo_proxmox-0.1.1/src/proximo/a2a/__init__.py +22 -0
  33. proximo_proxmox-0.1.1/src/proximo/a2a/__main__.py +6 -0
  34. proximo_proxmox-0.1.1/src/proximo/a2a/app.py +183 -0
  35. proximo_proxmox-0.1.1/src/proximo/a2a/card.py +92 -0
  36. proximo_proxmox-0.1.1/src/proximo/a2a/executor.py +144 -0
  37. proximo_proxmox-0.1.1/src/proximo/a2a/skills.py +282 -0
  38. proximo_proxmox-0.1.1/src/proximo/access.py +669 -0
  39. proximo_proxmox-0.1.1/src/proximo/access_governance.py +763 -0
  40. proximo_proxmox-0.1.1/src/proximo/access_users.py +810 -0
  41. proximo_proxmox-0.1.1/src/proximo/audit.py +234 -0
  42. proximo_proxmox-0.1.1/src/proximo/backends.py +224 -0
  43. proximo_proxmox-0.1.1/src/proximo/backup.py +351 -0
  44. proximo_proxmox-0.1.1/src/proximo/cloudinit.py +426 -0
  45. proximo_proxmox-0.1.1/src/proximo/cluster_ops.py +557 -0
  46. proximo_proxmox-0.1.1/src/proximo/config.py +102 -0
  47. proximo_proxmox-0.1.1/src/proximo/config_edit.py +474 -0
  48. proximo_proxmox-0.1.1/src/proximo/diagnose.py +139 -0
  49. proximo_proxmox-0.1.1/src/proximo/disk_ops.py +514 -0
  50. proximo_proxmox-0.1.1/src/proximo/firewall.py +716 -0
  51. proximo_proxmox-0.1.1/src/proximo/network.py +582 -0
  52. proximo_proxmox-0.1.1/src/proximo/observability.py +465 -0
  53. proximo_proxmox-0.1.1/src/proximo/pbs.py +938 -0
  54. proximo_proxmox-0.1.1/src/proximo/planning.py +373 -0
  55. proximo_proxmox-0.1.1/src/proximo/provisioning.py +357 -0
  56. proximo_proxmox-0.1.1/src/proximo/server.py +2081 -0
  57. proximo_proxmox-0.1.1/src/proximo/storage.py +236 -0
  58. proximo_proxmox-0.1.1/src/proximo/storage_admin.py +445 -0
  59. proximo_proxmox-0.1.1/src/proximo/tasks_pools.py +490 -0
  60. proximo_proxmox-0.1.1/tests/test_a2a_auth.py +112 -0
  61. proximo_proxmox-0.1.1/tests/test_a2a_card.py +87 -0
  62. proximo_proxmox-0.1.1/tests/test_a2a_e2e.py +61 -0
  63. proximo_proxmox-0.1.1/tests/test_a2a_entry.py +64 -0
  64. proximo_proxmox-0.1.1/tests/test_a2a_executor.py +249 -0
  65. proximo_proxmox-0.1.1/tests/test_a2a_integration.py +144 -0
  66. proximo_proxmox-0.1.1/tests/test_a2a_skills.py +268 -0
  67. proximo_proxmox-0.1.1/tests/test_access.py +849 -0
  68. proximo_proxmox-0.1.1/tests/test_access_governance.py +1061 -0
  69. proximo_proxmox-0.1.1/tests/test_access_users.py +1230 -0
  70. proximo_proxmox-0.1.1/tests/test_backends.py +294 -0
  71. proximo_proxmox-0.1.1/tests/test_backup.py +600 -0
  72. proximo_proxmox-0.1.1/tests/test_cloudinit.py +649 -0
  73. proximo_proxmox-0.1.1/tests/test_cluster_ops.py +740 -0
  74. proximo_proxmox-0.1.1/tests/test_config.py +57 -0
  75. proximo_proxmox-0.1.1/tests/test_config_edit.py +802 -0
  76. proximo_proxmox-0.1.1/tests/test_diagnose.py +173 -0
  77. proximo_proxmox-0.1.1/tests/test_disk_ops.py +678 -0
  78. proximo_proxmox-0.1.1/tests/test_firewall.py +935 -0
  79. proximo_proxmox-0.1.1/tests/test_ledger.py +233 -0
  80. proximo_proxmox-0.1.1/tests/test_main_module.py +11 -0
  81. proximo_proxmox-0.1.1/tests/test_mcp_stdio_e2e.py +49 -0
  82. proximo_proxmox-0.1.1/tests/test_network.py +850 -0
  83. proximo_proxmox-0.1.1/tests/test_observability.py +821 -0
  84. proximo_proxmox-0.1.1/tests/test_pbs.py +1178 -0
  85. proximo_proxmox-0.1.1/tests/test_planning.py +368 -0
  86. proximo_proxmox-0.1.1/tests/test_provisioning.py +641 -0
  87. proximo_proxmox-0.1.1/tests/test_server_new_wiring.py +257 -0
  88. proximo_proxmox-0.1.1/tests/test_server_plan.py +571 -0
  89. proximo_proxmox-0.1.1/tests/test_server_round3_wiring.py +158 -0
  90. proximo_proxmox-0.1.1/tests/test_storage.py +390 -0
  91. proximo_proxmox-0.1.1/tests/test_storage_admin.py +784 -0
  92. proximo_proxmox-0.1.1/tests/test_tasks_pools.py +905 -0
@@ -0,0 +1,24 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.12", "3.13"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: ${{ matrix.python-version }}
19
+ - name: Install
20
+ run: python -m pip install -e ".[dev]"
21
+ - name: Lint
22
+ run: ruff check .
23
+ - name: Test
24
+ run: pytest -q -ra
@@ -0,0 +1,33 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+
9
+ # Virtualenvs / caches
10
+ .venv/
11
+ venv/
12
+ .pytest_cache/
13
+ .ruff_cache/
14
+ .mypy_cache/
15
+
16
+ # Debian build artifacts
17
+ debian/proximo/
18
+ debian/.debhelper/
19
+ debian/files
20
+ debian/*.substvars
21
+ debian/*.log
22
+ *.deb
23
+ *.buildinfo
24
+ *.changes
25
+
26
+ # Proximo runtime — never commit audit logs or real config
27
+ *.log
28
+ audit.log
29
+ *.env
30
+ !*.env.example
31
+
32
+ # Lockfile (library — consumers resolve their own deps)
33
+ uv.lock
@@ -0,0 +1,180 @@
1
+ # Changelog
2
+
3
+ All notable changes to Proximo. Format loosely follows Keep a Changelog; versions are SemVer.
4
+
5
+ ## [Unreleased]
6
+
7
+ _Nothing yet._
8
+
9
+ ---
10
+
11
+ ## [0.1.1] — 2026-06-10 — "Spaniard"
12
+
13
+ Hardening + release-readiness pass driven by an independent multi-team audit (3 cold reviewers,
14
+ 40 doc claims source-verified, full-history leak audit, adversarial verification of every finding).
15
+
16
+ ### Added
17
+ - **Realm options dict** (`8d2dac0`): `pve_realm_create` and `pve_realm_update` now accept a
18
+ type-specific `options` dict — LDAP (`server1`/`base_dn`/`user_attr`), AD (`domain`/`server1`),
19
+ OpenID (`issuer-url`/`client-id`). Previously, creating any LDAP/AD/OpenID realm was impossible
20
+ through the tool. Live-proven against a real PVE 9.2 API.
21
+ - **Governance/dangerous plane — live-proven to execute** (milestone): the governance and dangerous
22
+ plane (identity role/group/user/ACL; storage; SDN apply; network apply; realm create) that was
23
+ previously built+redteamed but MOCKED-only is now **proven to execute create→read→delete against
24
+ a real PVE 9.2 API on a nested test cluster**. Also proven on a nested 3-node test cluster:
25
+ offline guest migration (including local-disk) and HA-config operations (resource add/list/remove)
26
+ execute. PROVE ledger verified throughout. **Honest scope:** "nested test cluster" — not
27
+ production scale; HA **fencing** (hardware watchdog) and **online** live-migration (shared storage)
28
+ remain unproven.
29
+ - **CI**: GitHub Actions workflow — ruff + the full pytest suite on Python 3.12 and 3.13.
30
+
31
+ ### Security
32
+ - **A2A perimeter hardening** (`a8ce10b`, `0d952a6`): fail-closed by design — non-localhost bind
33
+ is **refused** unless `PROXIMO_A2A_TOKEN_FILE` is set; bearer auth (constant-time comparison) on
34
+ the JSON-RPC control endpoint when a token is set; Host-header allowlist + DNS-rebind defense
35
+ (`PROXIMO_A2A_ALLOWED_HOSTS`); `'*'` in the allowlist warns rather than silently disabling. The
36
+ agent card declares the bearer scheme. localhost-default dev behavior unchanged; A2A stays opt-in.
37
+ - **Audit ledger file permissions:** the ledger is now created `0600` (owner-only) instead of the
38
+ umask default — entries can carry command/SQL detail and were world-readable on typical umasks.
39
+ Applies at creation; an existing file keeps the mode its operator set.
40
+
41
+ ### Fixed
42
+ - Realm create/update no longer silently ignores type-specific options (LDAP/AD/OpenID realms
43
+ were uncreatable before this fix).
44
+ - **Audit-integrity:** `ct_logs` now enforces the CTID allowlist at the server layer like its
45
+ siblings — a forbidden CTID ledgers as `blocked:allowlist` instead of surfacing as a backend error,
46
+ so allowlist denials are uniformly traceable in the PROVE ledger. Blocked entries for read-only
47
+ tools (`ct_logs`, `ct_diagnose`) now ledger `mutation: false`, matching the tool's true class.
48
+ - **Packaging:** `proximo-a2a` without the `[a2a]` extra now prints a one-line
49
+ `pip install "proximo[a2a]"` hint (exit 2) instead of a raw `ModuleNotFoundError` traceback —
50
+ including when only `uvicorn` is missing; a missing *submodule* of an installed dependency still
51
+ tracebacks (that is a real environment bug, not a missing extra).
52
+
53
+ ### Notes
54
+ - **117 MCP tools; 1964 tests passing (0 skipped); ruff clean.** Public on GitHub 2026-06-10; PyPI/GHCR pending.
55
+ - Docs: public-readiness scrub of ROADMAP/CHANGELOG/POSITIONING; README install command made
56
+ copy-pasteable; claim wording tightened to carry its own scope. Lint: 3 leftover warnings in the
57
+ live-smoke scripts cleaned.
58
+
59
+ ## [0.1.0] — 2026-06-09 — "Spaniard"
60
+
61
+ First blood — the foundation of the ethical Proxmox MCP. _Tagged `v0.1.0`; not yet published to
62
+ PyPI/GHCR (local/private). Honest scope: 117 MCP tools, most exercised against mocks only; the trust
63
+ spine + core lifecycle are live-proven, the governance plane is built/redteamed but not yet live-fired._
64
+
65
+ ### Added
66
+ - **MCP stdio transport, proven end-to-end:** `python -m proximo` entry point; the `initialize` handshake
67
+ advertises Proximo's own version (not the MCP SDK's); covered by a real-client integration test
68
+ (`test_mcp_stdio_e2e.py`: client → stdio → FastMCP dispatch → tool → back).
69
+ - Two backends: **REST API management** (scoped token) + **`ssh`→`pct` in-container exec** (local or remote).
70
+ - **MCP tool surface** (FastMCP): `pve_node_status`, `pve_list_guests`, `pve_guest_status`,
71
+ `pve_guest_power`, `ct_exec`, `ct_psql`, `ct_logs`.
72
+ - **Ethical spine:** append-only audit log (records real outcomes), confirm-gates on every mutating tool,
73
+ fail-closed CTID allowlist, input validation on API path components (vmid/kind/node).
74
+ - Tests (13) + ruff lint config. Clean run.
75
+
76
+ ### Security
77
+ - Security redteam (2026-06-07): **5 findings, all fixed** —
78
+ `ct_exec`/`ct_psql` now confirm-gated; allowlist now fails **closed**; audit records real outcomes
79
+ (errors included); `vmid`/`kind`/`node` validated against injection; TLS-disabled now warns.
80
+ - Verified solid: command injection (shlex-correct on local + ssh + psql paths); the API token is never
81
+ logged, never enters the audit log, subprocess argv, or error messages.
82
+
83
+ ### PROVE pillar — tamper-evident ledger (2026-06-07)
84
+ - The audit log is now a **hash-chained, tamper-evident ledger**: `entry_hash = sha256(prev_hash + body)`,
85
+ flock-guarded, fsync'd. `verify()` and the `audit_verify` MCP tool detect any altered / deleted /
86
+ inserted / reordered entry and pinpoint the break; `head()` is anchorable off-box. Tamper-**evident**,
87
+ not tamper-proof (honestly scoped). +6 tamper-detection tests. Redteam: 2 findings fixed.
88
+ - This is one of the four trust-layer pillars (PLAN · UNDO · **PROVE** · DIAGNOSE) — see POSITIONING.md.
89
+
90
+ ### PLAN pillar — dry-run by default (2026-06-07)
91
+ - New `proximo.planning` module: **every mutating tool now previews before it acts.** Called without
92
+ `confirm=True`, `pve_guest_power` / `ct_exec` / `ct_psql` return a **plan** — the exact change, the
93
+ guest's live state (power), blast radius, and an **advisory, heuristic risk rating** — instead of
94
+ executing. `confirm=True` then executes. You structurally cannot mutate without a plan first existing.
95
+ - **PLAN ⊗ PROVE:** the previewed plan (including the live state it was based on) is written to the
96
+ tamper-evident ledger with `outcome="planned"`; a confirmed execution records `confirmed=true`. The
97
+ approval trail — *what preview was shown before the action* — is now verifiable, not just *that* it ran.
98
+ - **Honest by design (guard every path to LOW):** `LOW` means "does not change state," not "safe";
99
+ the absence of a `HIGH` flag is not a safety signal; destructive signatures are curated, not exhaustive.
100
+ - Adversarial review: confirmed bypasses fixed — whitelist audit (`find -delete`, `ip route add`,
101
+ `mount <dev>` no longer rate "read-only"); SQL `SELECT pg_terminate_backend()/lo_import()`,
102
+ `COPY ... PROGRAM` (RCE) now escalate; failed dry-runs are audited; `current` state recorded; latent
103
+ `_max_risk`/`_fmt_uptime` edge crashes fixed. Every confirmed bypass became a regression test.
104
+ - Tests: **81 total** (was 21), ruff clean.
105
+ - **Guarantee enforced:** the plan is recorded on BOTH paths — even a one-shot `confirm=True` records
106
+ its `planned` entry before mutating (no plan, no mutation). The PLAN→PROVE triplet
107
+ (`planned → ok/confirmed`) is uniform; a one-shot confirm can't bypass the recorded preview.
108
+
109
+ ### UNDO pillar — auto-snapshot before mutating + one-call revert (2026-06-07)
110
+ - **Snapshot backend + tools:** `pve_snapshot_list` (read), `pve_snapshot_create`, `pve_rollback`
111
+ (DESTRUCTIVE — discards changes since the snapshot), `pve_snapshot_delete` (all PLAN-gated), and
112
+ `pve_task_status` to poll the async task UPIDs these return. Endpoints verified against PVE docs.
113
+ - **The headline — auto-undo before exec:** `ct_exec`/`ct_psql` gain `snapshot=True`. With `confirm=True`
114
+ it takes a `proximo_undo_<ts>` snapshot **and waits for the task to finish** before running the
115
+ mutation, records the undo point, and returns it. **Fail-closed:** if the snapshot can't be created
116
+ or doesn't finish OK (e.g. storage doesn't support snapshots), the command is **NOT run**.
117
+ - **Honest:** snapshots are storage-dependent (ZFS/BTRFS/LVM-thin; not directory/raw) — surfaced in the
118
+ plan, never assumed. Rollback's PLAN spells out the blast radius. Async ops record `outcome="submitted"`
119
+ (not "ok") so the ledger never claims an in-flight task is done.
120
+ - Adversarial review: confirmed fixes, each a regression test — regex anchors `$`→`\Z` (newline bypass),
121
+ UPID length cap + reserved-name (`current`) guard, microsecond-unique undo names, strict task-exit
122
+ (fail-closed on missing `exitstatus`), server-layer allowlist gate (no orphaned snapshot for a
123
+ forbidden CTID), non-contradictory rollback preview when the snapshot is missing.
124
+ - Tests: **116 total**, ruff clean.
125
+
126
+ ### DIAGNOSE pillar — read-first "what's broken" (2026-06-07)
127
+ - New `proximo.diagnose` module + tools: `ct_diagnose` (API guest status + a FIXED read-only
128
+ in-container battery — failed units, disk, recent errors, memory, listening ports) and
129
+ `pve_diagnose` (node status + storage usage + recent failed tasks). Both strictly READ-ONLY
130
+ (no confirm, no mutation), audited. Backend reads: `node_storage`, `node_tasks`.
131
+ - **Honest by design:** advisory flags, never causation ("signal present", not "the cause is X").
132
+ Flags also surface **incompleteness** — partial mode (exec off → API-only + a skipped-probes flag),
133
+ a failed read, or a failed probe all flag, so an empty `flags` list can never read as a false clean
134
+ bill of health. Inactive/offline storage is reported as offline, not as "full" (no stale-data alarm).
135
+ - Adversarial review — read-only guarantee held (no injection, gates correct); the task-list `status`
136
+ field was **verified against the live PVE API**. Fixes, each a regression test: incompleteness flags,
137
+ inactive-storage handling, removed dead `--no-legend` guard, `_frac` inf/overflow guard, transient/
138
+ WARNINGS tasks no longer counted as failed, `node_tasks` limit clamp, `ExecBackend` vmid validation.
139
+ - Tests: **141 total**, ruff clean. **All four trust-layer pillars (PLAN · UNDO · PROVE · DIAGNOSE) now built.**
140
+
141
+ ### Coverage expansion — phases 1–7 (2026-05 → 2026-06)
142
+ - Grew the MCP surface from the 7 foundation tools to **117** `@mcp.tool()` tools, every mutating one
143
+ wearing PLAN+UNDO+PROVE by construction: provisioning/backup/restore, config/disk/cloud-init mutation, the
144
+ four "dangerous plane" domains (**firewall · network/SDN · cluster HA/migration · ACL/users/roles/realms**),
145
+ observability, task/pool control, storage admin, and **PBS-native** deep tools (GC/verify/prune/snapshots/
146
+ namespaces; separate `:8007` backend, TLS fail-closed).
147
+ - **Live-proven** against a real PVE: the core provisioning/config mutate cycle (create→config→revert→
148
+ clone→backup→restore→delete, ledger verified) + read shapes across node/storage/observability + a
149
+ PBS datastore. **Honest scope:** the bulk of the 117-tool surface — *including the dangerous plane* —
150
+ is **MOCKED-only** (unit-tested against fakes, not fired against real Proxmox). A broad live smoke needs a
151
+ wider scoped token. See the `ROADMAP.md` reality-check and `LANDSCAPE.md`.
152
+
153
+ ### A2A (Agent2Agent) face — experimental (2026-06-09)
154
+ - Optional second protocol head (`pip install 'proximo[a2a]'` → `proximo-a2a`): a curated **16-skill slice**
155
+ exposed over A2A, routing to the same server tools so PLAN/PROVE/UNDO/fail-closed are inherited. Serves an
156
+ agent card at `/.well-known/agent-card.json`; localhost by default (no built-in auth — warns on
157
+ non-localhost). Built + redteamed (PLAN-bypass + slice-boundary: **0 findings**); +47 tests. **Proven
158
+ end-to-end against a real a2a-sdk client** — agent-card resolve over HTTP + a real `message/send` invoking a
159
+ skill → completed task with a `result` artifact (real-socket proof + an in-process integration test,
160
+ `test_a2a_e2e.py`).
161
+
162
+ ### PROVE — opt-in HMAC-keyed audit chain (2026-06-09)
163
+ - The audit ledger now supports an **opt-in keyed mode**: set `PROXIMO_AUDIT_KEY_PATH` to chain entries
164
+ with **HMAC-SHA256** instead of bare SHA-256 (key auto-generated at 0600 via an atomic temp+link, hex
165
+ stored, fail-closed on empty/non-hex/<32-byte). The **ledger's key is authoritative** — a downgrade
166
+ (strip the HMAC, recompute as SHA-256 without the key) is rejected; a keyed log must be all-keyed. Default
167
+ stays **unkeyed and byte-identical** (existing logs + tests unaffected); `audit_verify` reports `keyed`.
168
+ Adversarial review (forge / key-handling / verify lenses): no exploitable forgery; +12 tests incl. the downgrade
169
+ attack. **Honest scope:** keying resists forward-rewrite by an attacker *without* the key, but a same-user
170
+ attacker who can write the 0600 log can often read the 0600 key — the **off-box `head()` anchor remains
171
+ the strong guarantee.** Not a "cryptographic depth" moat.
172
+
173
+ ### Honesty note (2026-06-09)
174
+ - The PBS cert fingerprint is stored but **not yet wire-enforced**.
175
+
176
+ ### Notes
177
+ - Not yet released; **pre-alpha.** Apache-2.0 LICENSE: ✅ added. Pending: broad live smoke of the
178
+ mocked surface (needs a properly-scoped token), publish (PyPI/GHCR + CI) so the install commands work.
179
+
180
+ _Strength and honor._
@@ -0,0 +1,16 @@
1
+ # Proximo — self-contained, sovereign, on-demand.
2
+ # The MCP client launches it per session: `docker run -i --rm ... proximo` (stdio).
3
+ FROM python:3.13-slim
4
+
5
+ # openssh-client powers the in-container exec edge (ssh -> pct). Everything else is bundled by pip,
6
+ # so the image is self-contained and the host stays untouched.
7
+ RUN apt-get update \
8
+ && apt-get install -y --no-install-recommends openssh-client \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ WORKDIR /app
12
+ COPY . /app
13
+ RUN pip install --no-cache-dir .
14
+
15
+ # MCP stdio server — no daemon, no open port. Launched on demand by the client.
16
+ ENTRYPOINT ["proximo"]
@@ -0,0 +1,173 @@
1
+ # Proximo — The Proxmox Automation Landscape
2
+
3
+ > **Created** 2026-06-08 · **Refreshed** 2026-06-09 (method-corrected sweep; see Method).
4
+ > **Refreshed 2026-06-10** (competitor numbers re-verified; Proximo governance plane now live-proven to
5
+ > execute; A2A perimeter parity closed; official-Proxmox-MCP threat added).
6
+ > An honest, source-verified survey of the Proxmox automation field across four modalities
7
+ > (MCP / A2A / AI / API-IaC), with Proximo's own position stated plainly — including where it is late,
8
+ > unproven, or simply not first. No manufactured foils.
9
+
10
+ > **Method.** Competitors are ranked by real popularity (GitHub stars, registry presence). Tool counts
11
+ > and safety features were re-read from the repositories on 2026-06-09 — not inferred from READMEs or
12
+ > star counts, which miss feature drift. A keyword-only search (`proxmox` + `mcp`/`ai`) misses tools
13
+ > whose primary identity is something else — e.g. a multi-backend homelab cockpit that exposes Proxmox
14
+ > as one backend among many — so this survey adds an **off-keyword pass** for that class. A survey's
15
+ > silence means "not found," never "does not exist"; treat each finding as as-of its date.
16
+
17
+ ---
18
+
19
+ ## TL;DR — the Proxmox field, four lanes
20
+
21
+ 1. **MCP (AI-native) — contested; Proximo is not first.** Two ~250★ leaders (**canvrno** ~261★, read-only
22
+ "safe inspector" with ~6 tools; **RekklesNA/ProxmoxMCP-Plus** ~241★, the active governance leader), a
23
+ long <50★ tail, and one real *trust* peer (**proxxx**). **Raw tool-count is not a moat:**
24
+ `chajus1/proxmox-mcp-enhanced` already claims **115 tools** — within a couple of Proximo's 117, and a
25
+ raw count says nothing about whether the tools are proven (a significant portion of Proximo's lifecycle
26
+ surface still runs against mocks — see *Where Proximo stands*).
27
+ 2. **A2A (agent-to-agent) — greenfield for Proxmox.** No dedicated open A2A agent-card/server for Proxmox
28
+ found (off-keyword sweep included). Homelabbers run multi-agent systems *on* Proxmox; nobody exposes
29
+ Proxmox *as* an A2A participant. Demand is unverified — flagged, not claimed.
30
+ 3. **AI (non-MCP automation) — hobby-tier, unconsolidated.** n8n templates, Prox-AI, OpenClaw, Clawdbot,
31
+ Paperclip — real activity, near-zero stars, no serious product. The serious builders are choosing MCP,
32
+ so this lane mostly feeds Lane 1.
33
+ 4. **API / IaC — the mature base layer (different modality, not a rival).** Terraform (Telmate ~2.9k★, bpg
34
+ ~2k★), Ansible `community.general.proxmox`, proxmoxer ~787★. Proximo doesn't compete here — but its
35
+ PLAN/UNDO maps onto the **plan/apply/destroy** idiom those users already trust.
36
+
37
+ **The honest summary:** Proximo is a late, unstarred entrant in the contested MCP lane. Its defensible
38
+ thesis is **not** "first," "most tools," or "the only one with trust." It is **trust *by construction***
39
+ (every mutation carries PLAN + UNDO + PROVE in-band, fail-closed) **× coverage of the dangerous/governance
40
+ plane** (firewall/SDN/ACL/roles/realms/PBS) that even the leaders keep off the MCP surface **× auto-UNDO**
41
+ (fail-closed snapshot-before-mutate). You can bolt a `--dry-run` flag onto one tool; you cannot retrofit
42
+ auto-snapshot + hash-chain + blast-radius across 100+ existing flat tools after the fact.
43
+
44
+ ---
45
+
46
+ ## Lane 1 — MCP servers for Proxmox VE (ranked, stars re-pulled 2026-06-10)
47
+
48
+ | # | Repo | ★ | Tools | Lang | What it is / safety posture (source-verified 06-10) |
49
+ |---|---|---|---|---|---|
50
+ | 1 | **canvrno/ProxmoxMCP** | ~261 | ~6 | Py | Most-starred. Read-mostly "safe inspector," built for Cline. Popular *because* it doesn't hold the knives. |
51
+ | 2 | **RekklesNA/ProxmoxMCP-Plus** | ~241 | ~42 | Py | **Active governance leader** (v0.5.8, Jun 7 2026). VMs/CT/snap/backup/ISO/storage/cluster + SSH-exec + job-control (list/get/poll/cancel/retry). Gov = `command_policy` + `approval_token` + TLS + DNS-rebind. **NO dry-run / NO auto-rollback / NO blast-radius / NO firewall·SDN·ACL·HA.** Permissive by default. |
52
+ | 3 | **chajus1/proxmox-mcp-enhanced** | ~10 | **115** | Py | ⚠️ **The breadth rival.** Claims 115 tools "covering every aspect." Trust layer **unverified/absent** — but it rules out any "most complete by count" claim. Its dangerous-op coverage should be verified before anyone claims the completeness lane. |
53
+ | 4 | **fabriziosalmi/proxxx** | ~15 | **25 (MCP)** | Rust | **The real TRUST peer** (v0.8.4, May 30 2026, 43 releases). MCP = VM ops + cluster diag + PBS *browse/restore* + GitOps state export/diff/**`--dry-run`** + audit-verify. Has a pre-flight **risk gate (PLAN-ish)** + **HMAC-keyed offline-verifiable audit chain** (PROVE — arguably stronger than Proximo's default-unkeyed ledger) + Telegram **HITL**. **No auto-UNDO** (read-only rollback *preview* only). **Firewall/SDN/ACL/PBS-writes are NOT on the MCP surface** — they live in the human TUI. |
54
+ | 5 | gilby125/mcp-proxmox | ~41 | ~55 | JS | Configurable permissions. |
55
+ | 6 | Markermav/ProxmoxMCP-advance | ~24 | — | Py | Multi-client (Claude/Goose/Cline). |
56
+ | 7 | bsahane/mcp-proxmox | ~18 | — | Py | FastMCP, token auth, monitoring. |
57
+ | 8 | mdlmarkham/TailOpsMCP | ~13 | — | Py | Homelab ops over Tailscale (multi-host; see Cross-cut A). |
58
+ | 9 | tyxak/remotepower | ~12 | — | Py | Dashboard + CVE/patch + Proxmox MCP. |
59
+ | 10 | antonio-mello-ai/mcp-proxmox | ~11 | — | Py | Cluster mgmt via AI. |
60
+ | — | mjrestivo16 (35 tools), Samik081 (TS), jmerelnyc, GethosTheWalrus, agentify-sh, husniadil (PyPI), heybearc, ry-ops, plgonzalezrx8 (read-only+confirm), Zaptimist (ssh-exec), rodaddy (forks) | <10 | — | mix | The long tail. Registry-listed (PulseMCP/mcpmarket/Glama). |
61
+
62
+ **PBS-specific MCPs (PBS is not whitespace):** `szoran53/pbs-mcp-server` (TS), `ahmetem/pbs-mcp` (Py) —
63
+ datastore/snapshot/GC/verify/prune. proxxx covers PBS *browse/restore* inside its cockpit.
64
+
65
+ **Where Proximo differs (Lane 1):** the governance leader (RekklesNA) is permissive-by-default with no
66
+ PLAN/UNDO; the trust peer (proxxx) has PLAN+PROVE+HITL but keeps the dangerous plane out of MCP and has no
67
+ auto-UNDO; the breadth rival (chajus1) has count but no verified trust. The intersection none of them
68
+ holds — *the full firewall/SDN/ACL/roles/realms/PBS plane, exposed to the agent over MCP, every tool
69
+ PLAN+UNDO+PROVE by construction, fail-closed by default* — is where Proximo aims. That intersection is
70
+ currently empty.
71
+
72
+ ## Lane 2 — A2A (agent-to-agent) for Proxmox — greenfield
73
+
74
+ - **No open A2A agent-card / server for Proxmox found** (incl. off-keyword sweep). A2A itself is large
75
+ (Google → Linux Foundation, 50+ partners; JSON-RPC/gRPC/REST + SSE + Agent Cards) — but nobody has
76
+ shipped "Proxmox as a first-class A2A participant."
77
+ - Closest: commercial **Mindflow** "Proxmox VE agent"; homelabbers running *multi-agent systems on*
78
+ Proxmox (Paperclip, Clawdbot) — agents-hosted-on-Proxmox, not Proxmox-as-agent.
79
+ - **Proximo's position:** an A2A Agent Card for Proxmox ("the Proxmox operator agent; here are its skills")
80
+ would be first in this lane. Demand is unverified — no signal yet that anyone is asking.
81
+
82
+ ## Lane 3 — AI (non-MCP automation) for Proxmox — hobby-tier
83
+
84
+ | Project | Signal | What it is |
85
+ |---|---|---|
86
+ | **n8n "Proxmox AI agent" template** | popular no-code path | NL→Proxmox API via n8n + genAI; the most-reached-for non-coder route. |
87
+ | **folkvarlabs/Prox-AI** | ~5★, HCL | Terraform-config *generator* via Google Forms + cost-approval. Not an agent. |
88
+ | **OpenClaw** | — | Read-only LLM-backed Proxmox interface. |
89
+ | **Clawdbot / Paperclip** | — | LLM-with-tools runtimes deployed *in* Proxmox LXCs. |
90
+
91
+ No serious consolidated product here; the real builders pick MCP (Lane 1). This lane is a feeder, not a
92
+ separate front — though a one-command no-code on-ramp (e.g. an n8n node) would reach the non-coder crowd.
93
+
94
+ ## Lane 4 — API / IaC clients — the mature base layer (different modality)
95
+
96
+ Terraform **bpg** ~2k★ (modern leader, plan/apply/destroy) & **Telmate** ~2.9k★ (legacy) · Ansible
97
+ `community.general.proxmox` (huge usage) · **proxmoxer** ~787★ (dominant Py wrapper) · Go (Telmate ~487,
98
+ luthermonson ~267) · **Corsinvest cv4pve** suite (admin/autosnap/diag/metrics/botgram) · **CAPMOX** ~447★
99
+ (K8s Cluster-API). Proximo doesn't rival these — but their users already trust **plan/apply** semantics, so
100
+ Proximo's PLAN/UNDO speaks a dialect they know. Leverage, not competition.
101
+
102
+ ---
103
+
104
+ ## Cross-cut A — the multi-backend / homelab-fleet shape
105
+
106
+ These never surface under "proxmox mcp"; Proxmox is one backend among several. This is the class a
107
+ keyword search misses, and the one to keep watching:
108
+ - **bshandley/homelab-mcp** — MCP over Docker + OPNsense (firewall) + TrueNAS + **Proxmox** + Home Assistant;
109
+ read-only→write capability tiers. A unified-homelab play on the "AI runs my whole homelab" pitch.
110
+ - **AI-Engineering-at/homelab-mcp-bundle** (~4★) — 8-server homelab bundle incl. Proxmox.
111
+ - **mdlmarkham/TailOpsMCP** (~13★) — multi-host homelab ops over Tailscale.
112
+ - **shareed2k/Honey** — searches service instances across platforms incl. Proxmox.
113
+
114
+ **Implication:** Proximo's bet is *depth on one platform with trust*; this class's bet is *breadth across
115
+ the homelab*. Different bets — but if "AI runs my homelab" becomes the dominant framing, the breadth
116
+ players, not the Proxmox-MCP leaders, are the more direct competition.
117
+
118
+ ## Cross-cut B — generic trust layers: can someone add PLAN/UNDO *for* Proxmox without building it?
119
+
120
+ The 2026 hot category is **MCP gateways / guardrails** — they sit in front of *any* MCP and add governance:
121
+ **IBM ContextForge** (3.8k★, Apache-2.0; proxies MCP **and A2A** and REST), **Docker MCP Gateway**,
122
+ **Lasso**, **Bifrost**, **TrueFoundry**, **Portkey**, **MintMCP**, **Obot**, **Traefik Hub**, **AWS Bedrock
123
+ AgentCore**. The adjacent AI-SRE market (Hyground, StackGen, NeuBird, Azure SRE Agent, SRE.ai) is
124
+ well-funded.
125
+
126
+ **Why this matters, source-verified:** every one of these gateways provides **auth + policy + audit +
127
+ (some) human-approval** — but **none provide dry-run, auto-rollback, or blast-radius**, because a gateway
128
+ is **resource-blind**: it can gate or log a tool call, but it can't snapshot a Proxmox VM or compute the
129
+ blast radius of an ACL change, because it doesn't understand the resource. **PLAN and UNDO are
130
+ domain-specific by necessity** — a generic layer can't do them *for* Proxmox.
131
+
132
+ **The honest threat (assemble-from-parts):** ContextForge (audit/policy/approval) + RekklesNA (coverage)
133
+ = a credible audit + policy + HITL story over Proxmox *today*, with no new code. That combo lacks
134
+ PLAN/UNDO/blast-radius — but if a buyer's bar is "audit + approval," it clears now. The defensible ground
135
+ is the part that can't be assembled: PLAN (live-state + blast-radius), UNDO (auto-snapshot/revert), and
136
+ coverage of the governance plane — not "has an audit log."
137
+
138
+ ---
139
+
140
+ ## Where Proximo honestly stands (no spin, no shrink)
141
+
142
+ - **Popularity:** zero (local, unpublished — v0.1.1, not on PyPI/GHCR, zero public stars). MCP leaders
143
+ ~250★; IaC leaders ~2–3k★.
144
+ - **"First":** no. **"Most tools":** no (chajus1 ≈115). **"Only one with trust":** no (proxxx: risk-gate +
145
+ keyed audit chain + HITL; RekklesNA: command_policy + approval-token).
146
+ - **Genuinely differentiated, execution now confirmed on the key plane:**
147
+ 1. **Trust by construction** — every mutation PLAN+UNDO+PROVE *in-band & fail-closed-by-default*, vs
148
+ proxxx's human-gates-each-act and RekklesNA's opt-in/audit-only.
149
+ 2. **The dangerous/governance plane on the MCP surface** — firewall/SDN/ACL/roles/realms/PBS exposed *to
150
+ the agent*. RekklesNA doesn't cover it; proxxx keeps it in the TUI. This is the empty intersection.
151
+ The identity/storage/SDN/network/realm surface and offline migration + HA-config have been
152
+ **live-proven to execute** against a real PVE 9.2 API, including a nested 3-node test cluster, with
153
+ PROVE verified throughout. Still unproven: real HA fencing (hardware watchdog), online live-migration
154
+ (shared storage), and production scale.
155
+ 3. **Auto-UNDO** — none of the above takes the snapshot for you; proxxx previews a rollback, it doesn't
156
+ perform one.
157
+ - **A2A face perimeter-parity closed:** fail-closed public bind, bearer auth on the control endpoint,
158
+ `PROXIMO_A2A_ALLOWED_HOSTS` Host/DNS-rebind allowlist — previously warn-only. Matches the leader's
159
+ perimeter posture.
160
+ - **Maturity, stated straight:** v0.1.1, unpublished; a significant portion of the broader ~117-tool
161
+ lifecycle surface still runs against mocks; MCP face is local stdio (no network bind); perimeter
162
+ hardening for MCP becomes required the day a remote transport is added. The differentiation above is now
163
+ execution-confirmed on the governance plane; lifecycle breadth and production scale remain to prove.
164
+ - **Emerging threat:** field chatter that Proxmox may ship an **official MCP server**. An official basic
165
+ server would commoditize the VM/LXC/snapshot/backup lifecycle layer. Proximo's durable answer is the
166
+ dangerous/governance plane and trust-by-construction — capabilities an official day-one server is
167
+ unlikely to ship. This risk is real; watch the Proxmox project for announcements.
168
+
169
+ ## Sources
170
+ GitHub (stars + repo READMEs re-read 2026-06-10: canvrno, RekklesNA v0.5.8, proxxx v0.8.4, chajus1,
171
+ gilby125, bshandley/homelab-mcp); PulseMCP / mcpmarket / Glama registries; IBM/mcp-context-forge (3.8k★,
172
+ v1.0.2); Integrate.io & TrueFoundry MCP-gateway roundups; agamm/awesome-ai-sre; a2a-protocol.org;
173
+ Mindflow; n8n Proxmox-AI template.
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2026 John Broadway
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.