Task: Add compass site start/stop/status (detached lifecycle)
Table of Contents
This page documents a task in the Documentation improvements story. It captures the goal, current status, acceptance, and any notes or results.
Goal
`compass site serve` currently only runs as an ad hoc foreground/background shell process (manually backgrounded, killed via `pkill`). Give it a proper systemd-managed lifecycle – `compass site start`/`compass site stop`/`compass site status` – backed by a real `systemctl –user` unit, matching every other ORE Studio process's lifecycle instead of inventing another bookkeeping mechanism.
(This went through two implementations. The first pass used a PID-file lifecycle matching the Qt client's, reasoning by analogy from `compass_services.py`'s own docstring – the client was never DB-registered either, and opted out of systemd for that reason. On review, that analogy doesn't actually hold: systemd doesn't need a DB-registered service_definition to manage a process at all – that requirement is specific to the fleet's dependency-graph generator, not to systemd itself. A concrete, standalone unit works fine for a single local dev tool with no DB dependency, and is strictly better than PID files (cgroup-owned process tracking, no PID file to go stale, journald logging for free). Redone as a real unit; see `* Plan`.)
Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Documentation improvements |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-08-04 |
Acceptance
- `compass site start`/`compass site stop`/`compass site status` start/stop/report the site-preview server via a real `systemctl –user` unit, replacing manual backgrounding (nohup/pkill) and the earlier PID-file lifecycle.
- No orphaned processes left behind after stop – systemd owns the process via its cgroup, not a PID file that can go stale.
- `start` does not rebuild the site (that stays a separate `compass build –direct site` step); `compass site serve` (the existing foreground command, `–compile` included) is unchanged for interactive terminal use.
Plan
Added `render_site_unit()` to `systemd_generate.py`, alongside the existing `render_nats_unit()` – both are concrete, non-DB-driven, standalone unit renderers (the module's own docstring already explains why every unit here is concrete rather than templated: an earlier `%i`-specifier version had a real systemd bug that wedged the user manager on daemon-reload). `Type=simple`, `ExecStart=/usr/bin/ python3 -m http.server <port> –directory <build_dir>` directly – no shell wrapper or `EnvironmentFile=` needed, since the port and directory are both known at generation time. Deliberately not `PartOf=`/`Wants=`-linked to `ores-<env>.target`: previewing the site is an occasional dev convenience, not something every fleet start/stop should cascade to.
`compass.py` gained `_site_generate_and_deploy()` (render + write to `systemd/units/` + copy to `~/.config/systemd/user/` + `daemon-reload` only if changed – the same add/update-only-if-different shape as `systemd_generate.cmd_deploy`, scoped to just this one unit rather than re-running the full DB-driven pass) and a `_cmd_site_start/stop/status` trio that shells out to `systemctl –user start/stop/is-active` on the generated unit name (`site-<env>.service`). `start` checks the build directory exists first (erroring with the `compass build –direct site` hint if not) but never triggers a rebuild itself.
Notes
Test Scenarios
Manual QA scenarios (scaffolded via compass add test_scenario, run
through the QA Validation Runner panel) that verify this task. Link
new ones here as they're created; the scenario doc itself links back
via its "Verifies task" field.
| Scenario | State | Notes |
|---|---|---|
PRs
| PR | Title |
|---|---|
| #1829 | [compass] Add compass site start/stop/status detached lifecycle |
Review
| Comment summary | File | Decision | Notes |
|---|---|---|---|
Result
Real `systemctl –user`-managed unit (`site-<env>.service`), not PID files. Hand-verified: `start` renders+deploys the unit, starts it, and prints the URLs; `systemctl –user status` shows a genuine cgroup with the correct `python3 -m http.server` command line and a real PID; `curl` against the served port returns 200; `stop` cleanly stops the unit with no orphaned process; `status` correctly reports active/inactive. Deleted the earlier PID-file implementation's stray log file (`build/output/site-run/`) and updated How do I serve the site locally? and its cross-link from How do I start the ORE Studio services? to describe the new approach.