Service lifecycle API
The self-hosted Gantry applications are converging on one predictable service namespace. Cortex is the current reference implementation: foreground execution stays simple, while project service … handles persistence, lifecycle, logs and status when you want the app to behave like a normal host service.
The command surface
The reference service API is:
project service install [flags]
project service start
project service stop
project service restart
project service status
project service logs [--follow]
project service uninstall
On Cortex, cortex service with no verb is equivalent to cortex service status. Unknown service verbs exit with usage error status rather than silently falling back to foreground execution.
service install
Install creates and enables a per-user systemd unit and starts the service. On Cortex the unit is written to ~/.config/systemd/user/cortex.service and is managed through systemctl --user; installation does not require a system-wide daemon or root-owned unit.
The install command accepts the runtime configuration that must survive terminal closure and reboot:
| Flag | Meaning |
|---|---|
--host | HTTP bind host. Cortex defaults to 127.0.0.1; CORTEX_HOST may provide the default and the CLI wins. |
--port | HTTP port, validated in the normal TCP port range. Cortex defaults to 7331; CORTEX_PORT may provide the default and the CLI wins. |
--listen | Legacy single-address form. It cannot be combined with --host or --port. |
--root | Workspace/application root recorded in the unit. Cortex requires an absolute path and defaults to the user home directory. |
--data | Persistent application data directory recorded in the unit. Cortex requires an absolute path and defaults under the user config directory. |
--public-origin | Canonical external origin used when the browser-facing URL differs from the bind address, typically behind a reverse proxy. |
--trust-proxy | Accept forwarding metadata from the directly connected trusted reverse proxy. Do not turn this on merely because the service has a public hostname. |
Cortex resolves its executable to a stable absolute path before writing the unit. It refuses an unmanaged foreign unit instead of overwriting it. Reinstall is transactional: the existing managed unit and its enablement/activity state are inspected first, and a failed change rolls back to the previous reproducible state.
Install also verifies readiness. After start or restart, Cortex polls systemd state and the public read-only GET /api/health endpoint. Success requires the expected {"ok":true} health contract; a random process returning some other 2xx JSON on the same port cannot impersonate a healthy Cortex service. A failed fresh install is stopped, disabled and cleaned up so the next attempt starts from a known state.
Start, stop and restart
start, stop and restart act on the managed user unit. They are intentionally boring: the service manager owns process lifetime, so closing the shell that issued the command does not stop the application.
cortex service stop
cortex service start
cortex service restart
A service that was installed to a non-default port keeps that listener because the resolved host/port are written into the unit command rather than inherited from whatever shell happens to start systemd later.
Status is operational, not decorative
Cortex status reads the managed unit, queries systemd and performs a live health check. It reports:
unit: cortex.service
file: ~/.config/systemd/user/cortex.service
enabled: enabled
active: active
pid: 12345
version: ...
listen: 127.0.0.1:7331
url: http://127.0.0.1:7331
health: ok
Status exits non-zero if the managed unit is missing, failed/inactive when it should be active, invalid, or active but not healthy. That makes it useful in scripts and support checks instead of merely printing whatever systemd says.
Logs
service logs reads the service journal through the user unit. Add --follow to stream new output:
cortex service logs
cortex service logs --follow
This is deliberately a first-class command so a user does not need to remember the exact journalctl --user-unit … incantation for every Gantry service.
Uninstall does not mean delete your data
service uninstall stops and removes the managed service unit while preserving Cortex configuration, conversations and application data. Removing the service is a lifecycle operation, not a destructive reset.
User services and lingering
A systemd user service runs as your OS user. It can survive the terminal that installed it, but whether the user manager survives logout depends on the host. For an always-on server, check and enable lingering when appropriate:
loginctl show-user "$USER" -p Linger
loginctl enable-linger "$USER"
Cortex currently documents service install --system as unsupported: user mode is the default. That is preferable to pretending a system-wide mode exists when its account, credential and keyring semantics have not been solved.
Reverse-proxy flags
Bind the Gantry service to loopback whenever a local reverse proxy is the public entry point. Then set the externally visible origin and trust proxy metadata only for the direct proxy relationship:
cortex service install \
--host 127.0.0.1 \
--port 7331 \
--public-origin https://cortex.example.com \
--trust-proxy
See Serving & deployment for complete Caddy and nginx examples.
The Gantry rule
The CLI should make the common operational action obvious. The operating system service manager still owns the process.
Gantry projects should not invent a different daemon manager, background PID convention or vocabulary for every application. The shared service namespace gives users one set of muscle memory while letting each project keep the runtime flags it actually needs.