- Python 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| debian | ||
| src/pmx_cork_host | ||
| systemd | ||
| tests | ||
| .gitignore | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
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, userwww-data, listening on/run/pmx-cork-proxy/proxy.sockbehind 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, userroot, 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 = falsekeeps an add-on dormant), andassemble(), which loads the add-ons, resolves the auth provider and builds the application on the engine.proxyanddaemon: the two service definitions (ProxyService,DaemonService: identity, the delay after a failed login, where the client IP is read from) and thecreate_app()factory each unit runs under uvicorn.task_worker: the detached worker that task routes andas_taskjobs spawn.task_apiandservice_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.