forked from bai/curriculum-project-hub
feat: make agent roles and skills dynamic
This commit is contained in:
+38
-2
@@ -54,6 +54,34 @@ Default state paths are:
|
||||
/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
|
||||
@@ -129,16 +157,24 @@ sudo INSTANCE_ID=org-a \
|
||||
bash hub/deploy/backup_silo.sh
|
||||
```
|
||||
|
||||
The business set contains the PostgreSQL custom dump and workspace archive. The
|
||||
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, verify checksums, then run:
|
||||
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
|
||||
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
INSTANCE_ID="${INSTANCE_ID:?INSTANCE_ID required}"
|
||||
ENV_FILE="${ENV_FILE:?ENV_FILE required}"
|
||||
SERVICE_USER="${SERVICE_USER:-cph-$INSTANCE_ID}"
|
||||
HUB_DIR="${HUB_DIR:-/srv/curriculum-project-hub/current/hub}"
|
||||
|
||||
[ "$(id -u)" -eq 0 ] || { echo "agent config console must run as root" >&2; exit 1; }
|
||||
[ -r "$ENV_FILE" ] || { echo "environment file is not readable: $ENV_FILE" >&2; exit 1; }
|
||||
[ -f "$HUB_DIR/dist/deployment/agent-config-cli.js" ] || { echo "Agent config CLI missing below $HUB_DIR" >&2; exit 1; }
|
||||
id "$SERVICE_USER" >/dev/null 2>&1 || { echo "service user missing: $SERVICE_USER" >&2; exit 1; }
|
||||
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
. "$ENV_FILE"
|
||||
set +a
|
||||
: "${DATABASE_URL:?DATABASE_URL missing from ENV_FILE}"
|
||||
: "${HUB_SILO_ORGANIZATION_ID:?HUB_SILO_ORGANIZATION_ID missing from ENV_FILE}"
|
||||
|
||||
exec runuser --user "$SERVICE_USER" -- \
|
||||
env -i \
|
||||
DATABASE_URL="$DATABASE_URL" \
|
||||
HUB_SILO_ORGANIZATION_ID="$HUB_SILO_ORGANIZATION_ID" \
|
||||
HUB_SKILL_STORE_ROOT="${HUB_SKILL_STORE_ROOT:-/var/lib/cph-hub/$INSTANCE_ID/state/skills}" \
|
||||
XDG_STATE_HOME="${XDG_STATE_HOME:-/var/lib/cph-hub/$INSTANCE_ID/state}" \
|
||||
PATH="${PATH:-/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin}" \
|
||||
node "$HUB_DIR/dist/deployment/agent-config-cli.js" "$@"
|
||||
@@ -9,6 +9,7 @@ KEYRING_FILE="${KEYRING_FILE:?KEYRING_FILE required}"
|
||||
BUSINESS_BACKUP_DIR="${BUSINESS_BACKUP_DIR:?BUSINESS_BACKUP_DIR required}"
|
||||
RECOVERY_BACKUP_DIR="${RECOVERY_BACKUP_DIR:?RECOVERY_BACKUP_DIR required}"
|
||||
SERVICE_UNIT="cph-hub-$INSTANCE_ID.service"
|
||||
SKILL_STORE_ROOT="${SKILL_STORE_ROOT:-/var/lib/cph-hub/$INSTANCE_ID/state/skills}"
|
||||
|
||||
[ "$(id -u)" -eq 0 ] || { echo "backup must run as root" >&2; exit 1; }
|
||||
umask 077
|
||||
@@ -37,12 +38,14 @@ set +a
|
||||
: "${DATABASE_URL:?DATABASE_URL missing from ENV_FILE}"
|
||||
: "${HUB_PROJECT_WORKSPACE_ROOT:?HUB_PROJECT_WORKSPACE_ROOT missing from ENV_FILE}"
|
||||
[ -d "$HUB_PROJECT_WORKSPACE_ROOT" ] || { echo "workspace root missing" >&2; exit 1; }
|
||||
[ -d "$SKILL_STORE_ROOT" ] || { echo "skill store root missing" >&2; exit 1; }
|
||||
|
||||
install -d -o root -g root -m 0700 "$BUSINESS_BACKUP_DIR" "$RECOVERY_BACKUP_DIR"
|
||||
business_root="$(realpath -m "$BUSINESS_BACKUP_DIR")"
|
||||
recovery_root="$(realpath -m "$RECOVERY_BACKUP_DIR")"
|
||||
workspace_root="$(realpath -m "$HUB_PROJECT_WORKSPACE_ROOT")"
|
||||
secret_root="$(realpath -m "$(dirname "$KEYRING_FILE")")"
|
||||
skill_root="$(realpath -m "$SKILL_STORE_ROOT")"
|
||||
paths_overlap() {
|
||||
local left="$1" right="$2"
|
||||
[ "$left" = "$right" ] || [[ "$left/" == "$right/"* ]] || [[ "$right/" == "$left/"* ]]
|
||||
@@ -58,6 +61,10 @@ for pair in \
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
if paths_overlap "$business_root" "$skill_root" || paths_overlap "$recovery_root" "$skill_root" || paths_overlap "$workspace_root" "$skill_root"; then
|
||||
echo "backup destinations, workspace and skill store must not overlap: $skill_root" >&2
|
||||
exit 1
|
||||
fi
|
||||
stamp="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||
business="$business_root/$INSTANCE_ID-$stamp"
|
||||
recovery="$recovery_root/$INSTANCE_ID-$stamp"
|
||||
@@ -65,13 +72,14 @@ install -d -o root -g root -m 0700 "$business" "$recovery"
|
||||
|
||||
pg_dump --format=custom --file="$business/database.dump" "$DATABASE_URL"
|
||||
tar --create --file="$business/workspaces.tar" --directory="$HUB_PROJECT_WORKSPACE_ROOT" .
|
||||
tar --create --file="$business/agent-skills.tar" --directory="$SKILL_STORE_ROOT" .
|
||||
cp --preserve=mode,ownership,timestamps "$KEYRING_FILE" "$recovery/secret-keyring.json"
|
||||
cp --preserve=mode,ownership,timestamps "$ENV_FILE" "$recovery/platform.env"
|
||||
chmod 0600 "$recovery/secret-keyring.json" "$recovery/platform.env"
|
||||
|
||||
(
|
||||
cd "$business"
|
||||
sha256sum database.dump workspaces.tar > SHA256SUMS
|
||||
sha256sum database.dump workspaces.tar agent-skills.tar > SHA256SUMS
|
||||
)
|
||||
(
|
||||
cd "$recovery"
|
||||
|
||||
@@ -18,6 +18,7 @@ EnvironmentFile=__ENV_FILE__
|
||||
Environment=HOME=__SERVICE_HOME__
|
||||
Environment=XDG_STATE_HOME=__STATE_DIR__
|
||||
Environment=XDG_CACHE_HOME=__CACHE_DIR__
|
||||
Environment=HUB_SKILL_STORE_ROOT=__SKILL_STORE_ROOT__
|
||||
Environment=PATH=__RUNTIME_PATH__
|
||||
# ADR-0024: the root-owned source remains unreadable by the service account;
|
||||
# systemd materializes a read-only per-unit credential at runtime.
|
||||
|
||||
@@ -23,6 +23,7 @@ SERVICE_GROUP="${SERVICE_GROUP:-$SERVICE_USER}"
|
||||
SERVICE_HOME="${SERVICE_HOME:-/var/lib/cph-hub/$INSTANCE_ID/home}"
|
||||
STATE_DIR="${STATE_DIR:-/var/lib/cph-hub/$INSTANCE_ID/state}"
|
||||
CACHE_DIR="${CACHE_DIR:-/var/cache/cph-hub/$INSTANCE_ID}"
|
||||
SKILL_STORE_ROOT="${SKILL_STORE_ROOT:-$STATE_DIR/skills}"
|
||||
WORKSPACE_ROOT="${WORKSPACE_ROOT:?WORKSPACE_ROOT required (use a short per-Silo path such as /w/997)}"
|
||||
HOST="${HOST:-127.0.0.1}"
|
||||
PORT="${PORT:?PORT is required and must be unique on the host}"
|
||||
@@ -105,6 +106,7 @@ for pair in \
|
||||
"SERVICE_HOME:$SERVICE_HOME" \
|
||||
"STATE_DIR:$STATE_DIR" \
|
||||
"CACHE_DIR:$CACHE_DIR" \
|
||||
"SKILL_STORE_ROOT:$SKILL_STORE_ROOT" \
|
||||
"WORKSPACE_ROOT:$WORKSPACE_ROOT" \
|
||||
"ENV_FILE:$ENV_FILE" \
|
||||
"KEYRING_FILE:$KEYRING_FILE" \
|
||||
@@ -281,6 +283,7 @@ provision_directory() {
|
||||
provision_directory "$SERVICE_HOME"
|
||||
provision_directory "$STATE_DIR"
|
||||
provision_directory "$CACHE_DIR"
|
||||
provision_directory "$SKILL_STORE_ROOT"
|
||||
provision_directory "$WORKSPACE_ROOT"
|
||||
|
||||
# Resolve every provisioned path again and verify uid/gid/mode before writing
|
||||
@@ -297,6 +300,7 @@ sed \
|
||||
-e "s|__SERVICE_HOME__|$SERVICE_HOME|g" \
|
||||
-e "s|__STATE_DIR__|$STATE_DIR|g" \
|
||||
-e "s|__CACHE_DIR__|$CACHE_DIR|g" \
|
||||
-e "s|__SKILL_STORE_ROOT__|$SKILL_STORE_ROOT|g" \
|
||||
-e "s|__WORKSPACE_ROOT__|$WORKSPACE_ROOT|g" \
|
||||
-e "s|__HUB_DIR__|$HUB_DIR|g" \
|
||||
-e "s|__ENV_FILE__|$ENV_FILE|g" \
|
||||
@@ -315,5 +319,5 @@ install -o root -g root -m 0644 "$TMP_UNIT" "$UNIT"
|
||||
systemctl daemon-reload
|
||||
systemctl enable "$SERVICE_UNIT"
|
||||
echo "[install] installed $SERVICE_UNIT for $SERVICE_USER:$SERVICE_GROUP"
|
||||
echo "[install] home=$SERVICE_HOME state=$STATE_DIR cache=$CACHE_DIR workspaces=$WORKSPACE_ROOT"
|
||||
echo "[install] home=$SERVICE_HOME state=$STATE_DIR cache=$CACHE_DIR skills=$SKILL_STORE_ROOT workspaces=$WORKSPACE_ROOT"
|
||||
echo "[install] start with: systemctl start $SERVICE_UNIT"
|
||||
|
||||
Reference in New Issue
Block a user