7.6 KiB
Alpha Silo service installation
For a brand-new Organization, start with the repeatable wizard and end-to-end operator runbook:
bash hub/deploy/new_silo.sh
See NEW_SILO_RUNBOOK.md. The remainder of this document describes the individual installer and maintenance primitives used by the generated bundle.
The supervised alpha runs one Organization per named Silo. The supported host
has systemd, PostgreSQL, Node.js 24+, pg_isready, runuser, setpriv,
bubblewrap, socat, pg_dump, tar, sha256sum, and a compatible cph.
Each Silo needs its own logical database/role, instance id, service user,
environment, keyring, workspace, port, domain, and host filesystem quota.
Application code lives in immutable versioned directories under
/srv/curriculum-project-hub/releases/; releases contain no customer state.
Install an instance
sudo BASE=/srv/curriculum-project-hub \
HUB_DIR=/srv/curriculum-project-hub/releases/<release-id>/hub \
INSTANCE_ID=org-a \
WORKSPACE_ROOT=/w/997 \
PORT=8788 \
MEMORY_MAX=16G CPU_QUOTA=400% TASKS_MAX=512 \
bash /srv/curriculum-project-hub/releases/<release-id>/hub/deploy/install_service.sh
The first invocation creates root-owned 0600 files below
/srv/curriculum-project-hub/.secrets/org-a/ and exits 78. Copy the keyring to
a separately protected recovery destination and fill every blank in
platform.env. Before rerunning the installer, migrate the empty Silo database:
sudo bash -c '
set -euo pipefail
set -a; . /srv/curriculum-project-hub/.secrets/org-a/platform.env; set +a
node /srv/curriculum-project-hub/releases/<release-id>/hub/node_modules/prisma/build/index.js \
migrate deploy \
--schema /srv/curriculum-project-hub/releases/<release-id>/hub/prisma/schema.prisma
'
Then rerun the installer so it can complete preflight and install the stopped unit. Resource numbers above are examples, not defaults; the operator must set measured ceilings explicitly.
The installer refuses overlapping release/persistent paths, root service users,
missing Node/cph/PostgreSQL/bubblewrap prerequisites, invalid credentials, and
unreachable database roles. The installed unit is cph-hub-org-a.service and
uses systemd LoadCredential; the service user cannot read the source keyring.
Default state paths are:
/var/lib/cph-hub/org-a/home
/var/lib/cph-hub/org-a/state
/w/997
/var/cache/cph-hub/org-a
Organization Agent roles and skills are runtime configuration. Skill versions
are stored below the Silo state directory (state/skills) and are included in
backup_silo.sh as agent-skills.tar; PostgreSQL stores role bundles, skill
metadata and role-to-skill selection. Operate them as the Silo service user so
content ownership remains correct:
sudo INSTANCE_ID=org-a \
ENV_FILE=/srv/curriculum-project-hub/.secrets/org-a/platform.env \
bash hub/deploy/agent_config.sh install-skill \
--organization org-a --source /staging/typst --version 1
sudo INSTANCE_ID=org-a \
ENV_FILE=/srv/curriculum-project-hub/.secrets/org-a/platform.env \
bash hub/deploy/agent_config.sh upsert-role \
--organization org-a --role draft --label 草稿 --tools-json null
sudo INSTANCE_ID=org-a \
ENV_FILE=/srv/curriculum-project-hub/.secrets/org-a/platform.env \
bash hub/deploy/agent_config.sh set-role-skills \
--organization org-a --role draft --skills outline,lesson-project,typst
sudo INSTANCE_ID=org-a \
ENV_FILE=/srv/curriculum-project-hub/.secrets/org-a/platform.env \
bash hub/deploy/agent_config.sh list --organization org-a
--tools-json null means the full registered tool surface; [] means no
ordinary tools. SDK-bundled skills and workspace/user setting sources remain
disabled regardless of runtime configuration.
Bootstrap the only Organization
Prepare a root-owned 0600 JSON file containing
organization, owner, feishu, provider, and optionally teams. Secrets
never appear in command-line arguments.
{
"organization": { "id": "org_a", "slug": "org-a", "name": "Org A" },
"owner": { "openId": "ou_owner", "displayName": "Owner" },
"feishu": {
"appId": "cli_xxx",
"appSecret": "...",
"botOpenId": "ou_bot"
},
"provider": {
"providerId": "openrouter",
"baseUrl": "https://openrouter.ai/api",
"authToken": "..."
},
"teams": [{ "slug": "teachers", "name": "Teachers" }]
}
sudo bash -c '
set -euo pipefail
set -a; . /srv/curriculum-project-hub/.secrets/org-a/platform.env; set +a
node /srv/curriculum-project-hub/releases/<release-id>/hub/dist/deployment/bootstrap-silo-cli.js \
--config-file /root/org-a-bootstrap.json \
--keyring-file /srv/curriculum-project-hub/.secrets/org-a/secret-keyring.json
'
Bootstrap encrypts and activates both connection records and is rerunnable when provider configuration fails after Organization creation. Hub startup refuses to become healthy unless the sole Organization, active provider, active Feishu application, and Feishu listener are ready. Delete the bootstrap input after the recovery copy is current. Production has no process-global Feishu/provider credential fallback.
Start and rotate
sudo systemctl start cph-hub-org-a.service
sudo systemctl status cph-hub-org-a.service
KEK rotation requires the named unit to be stopped. Source its instance env so
HUB_SYSTEMD_UNIT and DATABASE_URL identify the same Silo:
sudo bash -c '
set -euo pipefail
systemctl stop cph-hub-org-a.service
set -a; . /srv/curriculum-project-hub/.secrets/org-a/platform.env; set +a
node /srv/curriculum-project-hub/hub/dist/deployment/rotate-secret-kek.js \
--keyring-file /srv/curriculum-project-hub/.secrets/org-a/secret-keyring.json
systemctl start cph-hub-org-a.service
'
Backup and restore drill (recommended after the demo is running)
Stop the unit and run backup_silo.sh with distinct root-owned destinations:
sudo INSTANCE_ID=org-a \
ENV_FILE=/srv/curriculum-project-hub/.secrets/org-a/platform.env \
KEYRING_FILE=/srv/curriculum-project-hub/.secrets/org-a/secret-keyring.json \
BUSINESS_BACKUP_DIR=/backup/business \
RECOVERY_BACKUP_DIR=/separate-recovery \
bash hub/deploy/backup_silo.sh
The business set contains the PostgreSQL custom dump, workspace archive and
agent-skills.tar. The
separate recovery set contains the keyring and environment. Both include
checksums; neither destination may be the live host's only disk.
Restore into a separate drill database, workspace and skill-store directory; verify checksums before extracting both tar archives, then run:
set -a; . /path/to/restored/platform.env; set +a
mkdir -p "$HUB_PROJECT_WORKSPACE_ROOT" "$HUB_SKILL_STORE_ROOT"
tar -xf /path/to/business/workspaces.tar -C "$HUB_PROJECT_WORKSPACE_ROOT"
tar -xf /path/to/business/agent-skills.tar -C "$HUB_SKILL_STORE_ROOT"
node hub/dist/deployment/restore-preflight.js \
--keyring-file /path/to/restored/secret-keyring.json
sudo INSTANCE_ID=org-a ENV_FILE=/path/to/restored/platform.env \
HUB_DIR=/path/to/restored/release/hub \
bash hub/deploy/agent_config.sh verify-store --organization org-a
Traffic stays disabled until the sole Organization and every Feishu/provider envelope authenticate, the workspace exists, and an end-to-end test run passes.
For the first supervised demo, install → bootstrap → start → health check is the deployment gate. A completed off-host restore drill is an Alpha hardening item, not a prerequisite for bringing up that first controlled Silo.
The default bind remains loopback; expose it only through a TLS reverse proxy. The platform admin surface is not part of the alpha and must not be exposed.