forked from bai/curriculum-project-hub
7269480abb
Add deploy_fleet_release.sh and a workflow that rolls immutable Hub releases (including admin-web) to educraft-dev on push/PR and educraft on main/tags, selecting silos by HUB_PUBLIC_BASE_URL middle domain.
226 lines
8.6 KiB
Markdown
226 lines
8.6 KiB
Markdown
# Alpha Silo service installation
|
|
|
|
For a brand-new Organization, start with the repeatable wizard and end-to-end
|
|
operator runbook:
|
|
|
|
```sh
|
|
bash hub/deploy/new_silo.sh
|
|
```
|
|
|
|
See [NEW_SILO_RUNBOOK.md](NEW_SILO_RUNBOOK.md). The remainder of this document
|
|
describes the individual installer and maintenance primitives used by the
|
|
generated bundle.
|
|
|
|
## CI: roll Hub + admin SPA by fleet (`educraft` / `educraft-dev`)
|
|
|
|
`.gitea/workflows/deploy-admin.yml` deploys Hub releases (including the org-admin
|
|
SPA) to the managed Alpha host via `deploy_fleet_release.sh`. Fleets share the
|
|
host and are selected by the middle DNS label of `HUB_PUBLIC_BASE_URL`:
|
|
|
|
| Fleet | Domain | CI trigger |
|
|
|-------|--------|------------|
|
|
| **dev** | `https://<slug>.educraft-dev.paradigm-edu.net` | every push + PR (paths under `hub/`) |
|
|
| **prod** | `https://<slug>.educraft.paradigm-edu.net` | push to `main` + tags |
|
|
|
|
Example prod tenant:
|
|
`https://para-26071100.educraft.paradigm-edu.net/auth/feishu/para-26071100`.
|
|
|
|
Required Gitea secret: `DEPLOY_SSH_KEY` (private key for `root@39.107.254.4`).
|
|
Optional: `DEPLOY_HOST` / `DEPLOY_USER` / `DEPLOY_SSH_PORT` / `DEPLOY_BASE`.
|
|
Set repository var `ALLOW_EMPTY_FLEET=1` until the first silo exists for a fleet.
|
|
|
|
Local dry-run against the host:
|
|
|
|
```sh
|
|
CPH_FLEET=dev \
|
|
PLATFORM_DEPLOY_SSH_KEY=$HOME/.ssh/id_ed25519 \
|
|
bash hub/deploy/deploy_fleet_release.sh
|
|
```
|
|
|
|
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
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```text
|
|
/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:
|
|
|
|
```sh
|
|
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.
|
|
|
|
```json
|
|
{
|
|
"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" }]
|
|
}
|
|
```
|
|
|
|
```sh
|
|
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
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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.
|