proximo for Debian
==================

STATUS: first working build, now tracking 0.16.0-1, now with a man page, a clean
`lintian --pedantic --info` pass, and a real autopkgtest smoke test.
`dpkg-buildpackage -us -uc -b` produces an installable .deb; `dpkg -i` +
`man proximo` + `proximo doctor` + `dpkg -P` have all been run end-to-end,
confirmed zero files left behind after purge. Closer to
archive-adjacent quality (see "Still rough" below for what's left) --
but it is still not an actual Debian-archive submission (no sponsor,
no archive-quality changelog distribution, no piuparts/reproducible-build
check).

Approach: self-contained via **dh-virtualenv**
------------------------------------------------

Proximo's runtime deps (mcp, httpx) aren't packaged for Debian. DECIDED
(sovereign, no host clutter): bundle a private venv so Proximo carries its
own dependency closure instead of fighting the system Python.

debian/rules drives `dh $@ --with python-virtualenv --buildsystem=pybuild`.
`--buildsystem=pybuild` is kept only so `dh_auto_configure` can sanity-check
the tree is pyproject-buildable; the `python-virtualenv` sequence add-on
removes `dh_auto_build`/`dh_auto_install`/`dh_auto_test`/`dh_auto_clean`
outright, since `dh_virtualenv` (inserted right before `dh_installinit`)
does the real work:

1. `python3 -m venv` (via `--builtin-venv`, so the build doesn't need the
   separate `virtualenv` PyPI tool as a build-dep) creates a venv at
   `/opt/venvs/proximo`.
2. `pip install .` (build-isolated, so pip also fetches the hatchling
   build backend from PyPI) installs proximo-proxmox + mcp + httpx + their
   transitive deps into it.
3. Shebangs/activate paths get fixed up to the final install location.

`/usr/bin/proximo` symlinks to the venv's `proximo` console script
(`debian/proximo.links`). `Architecture: any` (not `all`) -- the venv
bundles arch-specific compiled wheels (pydantic-core, rpds-py, cryptography,
cffi are Rust/C extensions with no pure-Python fallback), so this can't be
an arch-independent package; `Architecture: any` gets `dh_strip` and
`dh_shlibdeps` to actually run over those binaries (an earlier `all` build
shipped unstripped .so files with no libc6 dependency -- lintian caught it,
see `debian/proximo.lintian-overrides` history in git log for the fix).

Two build-time gotchas we hit and fixed by hand in `override_dh_virtualenv`
(debian/rules), because dh-virtualenv 1.2.2 predates `--builtin-venv`
having its own quirks:

- **Build-path leak.** `--builtin-venv`'s `pyvenv.cfg` and `bin/activate`
  embed the literal build-tree absolute path (e.g. a CI workdir or a
  throwaway git worktree under the builder's home).
  dh-virtualenv's own `fix_activate_path()` only matches the classic
  `virtualenv` tool's quoted activate-script format, so with
  `--builtin-venv` it silently no-ops. We sed-replace the build path with
  the final `/opt/venvs/proximo` by hand, delete pip's `direct_url.json`
  (records the local source checkout path, pure provenance metadata,
  not needed at runtime), and strip all `.pyc`/`__pycache__` (their
  `co_filename` bakes in the build path too -- Python recompiles them
  lazily and harmlessly on first import). Verified clean: extracted the
  built .deb and grepped for the worktree path, home-directory paths, and
  internal-hostname shapes -- zero hits (aside from two third-party PyPI wheels,
  pydantic-core and rpds-py, whose Rust build embeds a generic
  cargo-registry path from *their own* upstream CI container -- not
  this machine's path, not introduced by this build, not fixable short of
  rebuilding those wheels from source).
- **Orphaned `__pycache__` on removal.** Since we ship without `.pyc`
  caches, Python recompiles them at runtime, writing fresh
  `__pycache__` dirs under `/opt/venvs/proximo` that dpkg never tracked --
  `dpkg -r`/`apt remove` used to warn "directory not empty" and leave the
  tree behind. Fixed with `debian/proximo.postrm`: unconditionally
  `rm -rf /opt/venvs/proximo` on remove/purge (nothing else ever writes
  there). Verified: install, run `proximo doctor`, remove -- `/opt/venvs`
  is gone, no leftover files, no dpkg record.

`debian/proximo.lintian-overrides` documents four tags that are expected
for *any* dh-virtualenv package (or this specific package's naming) and
aren't defects here: files under `/opt` (that's the whole point of a
self-contained venv), console-scripts shebanging to the venv's bare
`python` (pip's standard codegen), two non-executable files vendored
inside pip itself (`pip/_vendor/...`, upstream's choice, not ours), and
the pedantic-only `repeated-path-segment` hit on
`.../site-packages/proximo/` (dh-virtualenv names the venv after the
source package, and upstream's own top-level import package happens to
also be named `proximo` -- two independently-correct naming choices
colliding, not a build mistake).

Man page
--------

`debian/proximo.1` is **hand-written**, not `help2man`-generated: `proximo
--help` doesn't print help text at all -- with no subcommand, `main()`
(see `src/proximo/server.py`) unconditionally starts the MCP stdio server
and blocks reading stdin, so `help2man ./proximo` genuinely fails
(`can't get '--help' info`, confirmed while writing this). The page was
written by reading `src/proximo/__main__.py`, `server.py`'s `main()`, and
`doctor.py`'s `doctor_check()` directly, so every claim in it (the
`doctor` subcommand's `--target` flag, its JSON-report-then-exit-0-even-
if-unreachable behavior, the exit-1 config-error path, the three required
`PROXIMO_*` vars) is checked against the actual code, not inferred from
prose docs. It deliberately does NOT restate the full `PROXIMO_*`
reference (60+ variables across PVE/PBS/PMG/PDM and the opt-in trust-spine
extensions) -- it names the three required variables plus two notable
safety toggles, then points at `/usr/share/doc/proximo/proximo.env.example`
(now shipped via `debian/proximo.docs`) and the upstream README for the
rest. Wired in via `debian/proximo.manpages`; verified with `groff -man
-Tutf8 -ww` (no warnings), `man --warnings`, and a real `dpkg -i` +
`man proximo` end-to-end.

lintian --pedantic pass
------------------------

Before this pass, `lintian --pedantic --info` on the source .changes (both
the `proximo` and `proximo-dbgsym` binaries) showed 5 tags: `no-manual-page`
(warning), the pedantic-only `repeated-path-segment` (see above), and the
three `debug-file-with-no-debug-symbols` warnings on `-dbgsym` (already
known, see below). After this pass: 3 tags, all three the same
`-dbgsym` `debug-file-with-no-debug-symbols` warnings -- the bundled
cryptography/pydantic-core/rpds-py wheels are pre-stripped manylinux
binaries with no DWARF info to extract; fixing that would mean rebuilding
those wheels from source, out of scope here. Plain (non-pedantic)
`lintian` was already clean before this pass except those same three; it
still is. No overrides were added for the `-dbgsym` tags -- they're
genuinely unfixed, not silenced, and the reasoning is recorded here
instead of hidden behind an override.

autopkgtest
-----------

`debian/tests/control` + `debian/tests/smoke` add one smoke test: install
the real `.deb` (dh-virtualenv venv + `/usr/bin/proximo` symlink, exactly
as an end user would) and run `proximo doctor` with
`PROXIMO_API_BASE_URL` pointed at a loopback port nothing listens on --
the connection is refused immediately, so the test needs no real Proxmox
host and touches no real infrastructure. `doctor` is designed to treat an
unreachable target as a reported preflight finding, not a crash, so the
test doesn't assert on the exit status (0 or 1 are both "the binary
ran") -- it asserts that the JSON report actually came out (`{` as the
first line, a `"note"` field, `"reachable": false`). Verified two ways:
manually with `sh -e debian/tests/smoke` against a real `dpkg -i`
install (both with the package absent -- confirmed the test correctly
fails closed -- and installed -- confirmed it passes), and through the
actual harness: `autopkgtest ../proximo_0.16.0-1_amd64.changes -- null`
(the `null` backend runs directly on this host with no isolation, which
is fine for a disposable packaging-verification container) --
`smoke PASS`. Both runs left `/opt/venvs/proximo` and `/usr/bin/proximo`
purged afterward (verified `find` for those paths returns nothing).

Still rough
-----------

- **`python3 (>= 3.12)` stays a real runtime dependency**, not just a
  build one: `python3 -m venv`'s `bin/python3` is a symlink to the system
  interpreter (not a copy), so the venv isn't fully self-contained down to
  the interpreter itself. dh-virtualenv's postinst/prerm autoscripts
  handle re-linking across a system Python point-release upgrade
  (`dh_venv_safe_interpreter_update`, triggered off `/usr/bin/python?.*`)
  -- that path is untested here (only a fresh install + remove cycle was
  verified, not an upgrade-under-a-python-bump scenario).
- **Not run through a real Debian archive gate.** `lintian --pedantic` and
  a real autopkgtest smoke run are now done (see above), but there is
  still no `piuparts` run and no reproducible-build check. This is an
  "it builds, installs, runs, passes one smoke test, and removes cleanly"
  bar, not a full archive-acceptance bar.
- **Version 0.16.0-1 with a `-1` revision on a `3.0 (native)` source
  format** is slightly non-canonical (native packages conventionally carry
  no Debian revision suffix); harmless in practice (dpkg builds it fine),
  but a purist would either drop the `-1` or switch source format --
  left as-is since the task asked for a versioned entry and this isn't
  headed for archive upload.
- **A2A extra (a2a-sdk, uvicorn* already pulled in transitively by mcp,
  starlette, cryptography) is deliberately NOT bundled** beyond what mcp
  itself needs -- matches upstream's opt-in design. `proximo-a2a` ships as
  a console script but errors with a friendly message until someone pip
  installs the `[a2a]` extra into the venv by hand. Not exposed under
  `/usr/bin` either way (only `debian/proximo.links` symlinks the plain
  `proximo` command); the man page documents only what's actually reachable
  from `/usr/bin`.
- **The autopkgtest smoke test covers exactly one path**: `proximo doctor`
  against an unreachable target. It does not exercise
  `PROXIMO_ENABLE_EXEC` / ssh->pct, PBS/PMG/PDM configuration, multi-target
  registries, or a reachable-but-under-privileged token (the "capability
  gaps" branch of `doctor_check`) -- those would need either a real (or
  faked-up) Proxmox API to talk to, which is out of scope for a
  no-infrastructure autopkgtest, or dedicated unit tests (already covered
  separately by `tests/test_doctor.py` et al in the upstream test suite,
  not by this packaging pass).
- **`dh_shlibdeps` warns** (not a lintian tag) that `pydantic_core`'s
  compiled `.so` links against `libdl.so.2` without using any of its
  symbols -- an artifact of how that wheel's Rust build was linked
  upstream, not something this packaging introduced or can fix without
  rebuilding the wheel.

Transport (unchanged, already decided)
---------------------------------------

Proximo speaks MCP over **stdio**, launched by the MCP client on demand --
no always-on service, no held-open port. An OPTIONAL daemon-mode unit
(network transport) lives at `packaging/optional-daemon-mode.service.example`
for anyone who wants one; it is NOT installed by default.

Deployment shapes (see the project README):
  - On the PVE host: PROXIMO_SSH_TARGET=local (direct pct; isolate the venv).
  - Off-host / dedicated LXC (recommended): PROXIMO_SSH_TARGET=pve (API + ssh).

 -- John Broadway <271895126+john-broadway@users.noreply.github.com>
