The proxy and daemon services, the add-on seam and the task worker
  • Python 100%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-14 09:00:00 +02:00
debian Initial import 2026-09-14 09:00:00 +02:00
src/pmx_cork_host Initial import 2026-09-14 09:00:00 +02:00
systemd Initial import 2026-09-14 09:00:00 +02:00
tests Initial import 2026-09-14 09:00:00 +02:00
.gitignore Initial import 2026-09-14 09:00:00 +02:00
LICENSE Initial import 2026-09-14 09:00:00 +02:00
pyproject.toml Initial import 2026-09-14 09:00:00 +02:00
README.md Initial import 2026-09-14 09:00:00 +02:00

pmx-cork-host

The services of the pmx-cork suite, the counterpart of pveproxy and pvedaemon. The package ships the two privilege-separated request services every add-on runs on:

  • pmx-cork-proxy.service, user www-data, listening on /run/pmx-cork-proxy/proxy.sock behind the port 443 front. It authenticates every request (PVE ticket or API token, CSRF on writes, a delay after a failed login), enforces PVE RBAC, serves the UI core and each add-on's static files over a scoped public lane, and forwards protected operations to the daemon.
  • pmx-cork-daemon.service, user root, listening on /run/pmx-cork-daemon/daemon.sock. It authenticates the forwarded ticket again and repeats the RBAC check before running a protected handler; the proxy's word is not taken. Nothing listens on TCP.

Nothing announces the runtime to the front. The front routes /pmx-cork/* and /api2/extjs/pmx-cork/* to the proxy socket by a static rule in its Caddyfile, so there is no registration step and no unit to run one.

The runtime never imports an add-on. An add-on drops a TOML file into /etc/pmx-cork/apps.d/ naming its Python modules; the runtime imports them at boot and each module registers its routes, static files and UI entries. With nothing registered the services still start and serve the auth gate, the task API and the service API.

Background tasks

A route registered with task=True does not run its handler inside the request. The daemon starts a detached worker process (python3 -m pmx_cork_host.task_worker, in its own session) and answers with the task's UPID in PVE's text format, so the browser parses it with the stock Proxmox.Utils.parse_task_upid. Workers survive a daemon restart (KillMode=process in the unit). State and log live root-only under /var/lib/pmx-cork/tasks/<UPID>/ as status.json, log and .lock; the handler's return value is stored as result, capped at 256 KiB.

The runtime owns the polling API at /pmx-cork/api/v1/tasks. All routes are protected and forwarded with proxyto:

Route Meaning
GET .../tasks/nodes/{node} the task list; owners see their own tasks, Sys.Audit all of them
GET .../tasks/nodes/{node}/{upid}/status one record, with result once finished
GET .../tasks/nodes/{node}/{upid}/log?start=...&limit=... a line-numbered slice of the log, at most 10000 lines per call
DELETE .../tasks/nodes/{node}/{upid} stop (owner or Sys.Modify): SIGTERM to the worker's process group, SIGKILL after five seconds

A worker that died without finalizing is detected by comparing the recorded pid and process start time with /proc, and its record is closed as an error. Retention rides the task lifecycle rather than a scheduled job, which is where PVE puts it too: every task start and finish prunes finished tasks older than seven days and beyond 1000 records, at most once per minute. Those numbers are this suite's own; PVE bounds its active index by file size instead. The task viewer and the Tasks tab ship in pmx-cork-ui.

Services API

service_api.py is the PVE::API2::Services analog for the suite's own units, listed in constants.SERVICE_UNITS: the proxy, the daemon, the scheduler, the front and its rewrite worker. A unit whose package is not installed on the node is left out. Routes at /pmx-cork/api/v1/services:

Route Meaning
GET .../services/nodes/{node} status of every unit, from systemctl show (Sys.Audit)
POST .../services/nodes/{node}/{service}/start, .../stop, .../restart, .../reload task routes (Sys.Modify); the unit name is the task's id, so the history reads restart pmx-cork-proxy
GET .../services/nodes/{node}/{service}/log the journal tail, at most 10000 lines

The service parameter is an enum over the unit list. Units marked essential (proxy, daemon, front) can be restarted but not stopped. The Services tab ships in pmx-cork-ui.

Scheduler seam

The runtime carries the registration seam only: registry.register_scheduled_jobs(provider), where provider is a list of job dicts (id, run, optional as_task, and exactly one of interval_seconds or schedule) or a zero-argument callable returning the current list, re-evaluated every tick. The service that runs the jobs, pmx-cork-scheduler.service, ships in python3-pmx-cork-scheduler. Without it, registered jobs never run.

Modules

The PVE-parity primitives live below this package: route declaration and validation in pmx_cork_sdk, the task store and the file primitives in pmx_cork_tasks, the engine, the dispatch pipeline and cross-node forwarding in pmx_cork_dispatch, the auth implementation in the provider named by runtime.conf. This package assembles them into the running services:

  • registry: the add-on seam (register_app, register_routes, register_static, register_ui, register_ui_pages, register_scheduled_jobs), load_apps() over /etc/pmx-cork/apps.d/*.conf (a failing file or module is skipped; enabled = false keeps an add-on dormant), and assemble(), which loads the add-ons, resolves the auth provider and builds the application on the engine.
  • proxy and daemon: the two service definitions (ProxyService, DaemonService: identity, the delay after a failed login, where the client IP is read from) and the create_app() factory each unit runs under uvicorn.
  • task_worker: the detached worker that task routes and as_task jobs spawn.
  • task_api and service_api: the runtime's own route tables, written with the SDK and mounted through the same seam every add-on uses.
  • constants: the served URL layout (URL_ROOT, API_BASE, WEB_BASE) and the unit list.
  • config: the loader for /etc/pmx-cork/runtime.conf. Every key is required (except the optional ones the file documents), there are no code defaults, and a bad file refuses service start.

The presentation, meaning the UI core pmx-cork-ui.js, the OpenAPI document and the Tasks and Services tabs, ships in pmx-cork-ui, an add-on like any other.

Layering: pmx_cork_tasks at the bottom; pmx_cork_sdk and pmx_cork_pmxcfs above it; pmx_cork_dispatch and pmx_cork_access above those; this package assembles them and resolves the auth provider by name rather than importing it; add-ons sit on top. The runtime imports nothing from the add-on layer, and a test pins that.

Development

make dev at the repository root creates the venv, make test runs every suite, make lint runs ruff with the repository's ruff.toml, the version check and the documentation check.

License

AGPL-3.0-only.