4.6 KiB
Hub service installation
The initial production target is a supported Linux host with systemd,
PostgreSQL, Node.js 20 or newer, pg_isready, runuser, setpriv, bubblewrap,
socat and cph. The Hub runs as the dedicated non-login cph-hub user; do not
create or run the unit as root.
Build or deploy the Hub under /srv/curriculum-project-hub, then run:
sudo BASE=/srv/curriculum-project-hub \
HUB_DIR=/srv/curriculum-project-hub/hub \
bash /srv/curriculum-project-hub/hub/deploy/install_service.sh
On a clean host the first invocation creates
/srv/curriculum-project-hub/.secrets/platform.env and a random root-owned
secret-keyring.json, then exits with status 78. Copy the keyring to a
separately protected recovery location before continuing; it must not live only
on this host or in the ordinary database/workspace backup. Fill every required
environment blank, set CPH_BIN to an executable compatible cph, keep both
source files root-only at mode 0600, and run the same command again. No
systemd unit is installed until configuration,
paths, Node, bubblewrap, socat, cph --version, PostgreSQL connectivity and directory
ownership all pass preflight.
The service account cannot read the keyring source. The installed unit uses
systemd LoadCredential to materialize a read-only cph-secret-keyring for the
running service. Hub refuses production startup if this credential is missing,
malformed, or does not contain its declared active key. Provider credentials
are configured through the Organization admin connection API, never through
process-global ANTHROPIC_* variables; preflight rejects those legacy settings.
Rotate the local KEK only from the controlled host console. The command first atomically adds a new active key while retaining every previous key, then authenticates and rewraps every stored Provider DEK, writes redacted org audit events, and verifies the complete set again:
sudo bash -c '
set -euo pipefail
systemctl stop cph-hub.service
set -a
. /srv/curriculum-project-hub/.secrets/platform.env
set +a
node /srv/curriculum-project-hub/hub/dist/deployment/rotate-secret-kek.js \
--keyring-file /srv/curriculum-project-hub/.secrets/secret-keyring.json
systemctl start cph-hub.service
'
The CLI refuses to rotate while cph-hub.service is active; stopping the only
writer prevents a concurrent BYOK version from being appended under the old
in-memory keyring. A PostgreSQL transaction advisory lock excludes a second
rotation and is released automatically if the CLI or host crashes. The
operation is therefore rerunnable after interruption: old and new keys remain
in the atomically replaced keyring, so partially rewrapped rows stay
decryptable. It also refuses success while any envelope still names a
non-active KEK. Back up the updated keyring to the separate recovery location
and complete a restore preflight before considering old-key retirement. The
pilot deliberately does not auto-delete previous keys.
The deployment path also runs npm run audit:production from the locked clean
install before building or restarting the service. A current high or critical
production advisory therefore blocks deployment instead of becoming a warning
buried in CI output.
An existing service account is accepted only when its primary group, home and
non-login shell match the requested configuration and it has no supplementary
groups. After provisioning, the installer executes path-access, built Hub,
Prisma, an authenticated database query, cph, socat and bubblewrap namespace probes
as that account with no_new_privs set. Inaccessible parents, UID-specific
database/network policy, or a host that forbids unprivileged user namespaces
therefore fail before unit installation.
The default persistent layout is deliberately outside every source/release tree:
/var/lib/cph-hub/home
/var/lib/cph-hub/state
/var/lib/cph-hub/workspaces
/var/cache/cph-hub
Custom paths are supported with SERVICE_HOME, STATE_DIR, CACHE_DIR and
WORKSPACE_ROOT, but all must be absolute. The installer rejects any persistent
path that is equal to, above, or below BASE; this keeps rsync, release
switching, rollback and pruning unable to traverse customer workspaces. Set the
same custom workspace in HUB_PROJECT_WORKSPACE_ROOT inside platform.env.
After a successful installation:
sudo systemctl start cph-hub.service
sudo systemctl status cph-hub.service
HOST and PORT in platform.env are passed to the real HTTP listener. The
default 127.0.0.1:8788 expects a local TLS reverse proxy; the public OAuth URL
must be an HTTPS HUB_PUBLIC_BASE_URL.