feat(hub): 真实飞书读取 + 状态卡片 + 权限 gate + 部署脚本

FeishuRead(ADR-0003): feishuContextTool 从 stub 换成真实 lark
  im.v1.message.get(单条 trigger/reply/status_card)+ list(thread 回复);
  ADR-0003 chat 授权不变式不变(handler 已 gate)。
StatusCard: sendCard + buildRunStatusCard——完成/出错发 interactive card
  (status 文案 + model 选择器 + 取消按钮带 runId),替代纯文本。
Permission(ADR-0004, project-wise): triggerAgent 查 PermissionGrant 对
  project 的 edit+ 能力(manage⊇edit 单调);sender open_id 当 principal
  (子类型学 OPEN,务实选择,permission.ts 标注);per-artifact OPEN;
  settings 组合规则 OPEN。无权限回 '无权限触发'。
Deploy: cph-hub.service systemd unit(ExecStartPre 跑 prisma migrate)+
  install_service.sh(idempotent,seed env)+ deploy_platform.sh
  (rsync + npm ci + build + restart + healthz)。
client.ts 抽 createLarkClient/startFeishuListenerWithClient,tools 与
  ws 共用同一 client。
tsc rc=0;prisma validate 通过;deploy 脚本 bash -n 通过。
This commit is contained in:
2026-07-06 23:44:07 +08:00
parent a4d52e1850
commit ce767e9cab
11 changed files with 433 additions and 30 deletions
+21
View File
@@ -0,0 +1,21 @@
[Unit]
Description=Curriculum Project Hub (Feishu agent + cph)
After=network-online.target postgresql.service
Wants=network-online.target
[Service]
Type=simple
User=__SERVICE_USER__
WorkingDirectory=__HUB_DIR__
EnvironmentFile=__ENV_FILE__
# Run prisma migrations before each start, then launch the Hub.
ExecStartPre=__NODE_BIN__ __HUB_DIR__/node_modules/prisma/build/index.js migrate deploy --schema __HUB_DIR__/prisma/schema.prisma
ExecStart=__NODE_BIN__ __HUB_DIR__/dist/server.js
Restart=on-failure
RestartSec=5
# Graceful shutdown: lark ws close + in-flight runs.
KillSignal=SIGTERM
TimeoutStopSec=30
[Install]
WantedBy=multi-user.target
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
# Deploy the Hub to a host: rsync the repo, install deps, build, restart service.
#
# Configure these env vars (e.g. in CI secrets):
# PLATFORM_DEPLOY_HOST required
# PLATFORM_DEPLOY_SSH_KEY required (private key for the deploy user)
# PLATFORM_DEPLOY_USER optional, defaults to deploy
# PLATFORM_DEPLOY_PORT optional, defaults to 22
# PLATFORM_DEPLOY_BASE optional, defaults to /srv/curriculum-project-hub
#
# The target host must have Node.js, npm, rsync, PostgreSQL, systemd, and a
# writable $PLATFORM_DEPLOY_BASE. Use a dedicated `deploy` user that has
# passwordless sudo for:
# systemctl restart cph-hub.service
# systemctl is-active --quiet cph-hub.service
set -euo pipefail
HOST="${PLATFORM_DEPLOY_HOST:?PLATFORM_DEPLOY_HOST required}"
SSH_KEY="${PLATFORM_DEPLOY_SSH_KEY:?PLATFORM_DEPLOY_SSH_KEY required}"
DEPLOY_USER="${PLATFORM_DEPLOY_USER:-deploy}"
PORT="${PLATFORM_DEPLOY_PORT:-22}"
BASE="${PLATFORM_DEPLOY_BASE:-/srv/curriculum-project-hub}"
HUB_DIR="$BASE/hub"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
SSH_OPTS=(-i "$SSH_KEY" -p "$PORT" -o StrictHostKeyChecking=accept-new)
# 1. Rsync the hub/ directory (exclude node_modules/dist, they rebuild on host).
rsync -az --delete \
--exclude node_modules --exclude dist --exclude .env \
-e "ssh ${SSH_OPTS[*]}" \
"$REPO_ROOT/hub/" "$DEPLOY_USER@$HOST:$HUB_DIR/"
# 2. Install deps + build on the host.
ssh "${SSH_OPTS[@]}" "$DEPLOY_USER@$HOST" "cd '$HUB_DIR' && npm ci && npm run build"
# 3. Ensure the service is installed (idempotent), then restart.
ssh "${SSH_OPTS[@]}" "$DEPLOY_USER@$HOST" "
BASE='$BASE' HUB_DIR='$HUB_DIR' bash '$HUB_DIR/deploy/install_service.sh' || true
sudo systemctl restart cph-hub.service
sleep 2
sudo systemctl is-active --quiet cph-hub.service || { echo 'service failed to start'; exit 1; }
curl -sf http://127.0.0.1:8788/api/healthz || { echo 'healthz failed'; exit 1; }
"
echo "Deployed cph-hub to $HOST ($HUB_DIR)"
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env bash
# Install the cph-hub systemd service on a host. Idempotent.
#
# Prerequisites already on the host: Node.js, npm, PostgreSQL, systemd.
# The Hub repo is expected at HUB_DIR (default /srv/curriculum-project-hub/hub)
# with `npm ci` + `npm run build` already run by deploy_platform.sh.
#
# Usage:
# sudo BASE=/srv/curriculum-project-hub bash deploy/install_service.sh
set -euo pipefail
BASE="${BASE:-/srv/curriculum-project-hub}"
HUB_DIR="${HUB_DIR:-$BASE/hub}"
SERVICE_USER="${SERVICE_USER:-root}"
HOST="${HOST:-127.0.0.1}"
PORT="${PORT:-8788}"
ENV_FILE="${ENV_FILE:-$BASE/.secrets/platform.env}"
NODE_BIN="${NODE_BIN:-$(command -v node || true)}"
if [ -z "$NODE_BIN" ]; then
echo "node must be on PATH, or set NODE_BIN." >&2
exit 1
fi
if [ ! -d "$HUB_DIR" ]; then
echo "HUB_DIR not found: $HUB_DIR (set HUB_DIR or clone the repo there)." >&2
exit 1
fi
if [ ! -f "$HUB_DIR/dist/server.js" ]; then
echo "build missing: run 'npm ci && npm run build' in $HUB_DIR first." >&2
exit 1
fi
# Seed the env file if absent. Secrets stay on the server, never in the repo.
if [ ! -f "$ENV_FILE" ]; then
install -d -m 0700 "$(dirname "$ENV_FILE")"
cat >"$ENV_FILE" <<EOF
NODE_ENV=production
DATABASE_URL=postgresql://paradigm:change-me@127.0.0.1:5432/paradigm
OPENROUTER_API_KEY=
FEISHU_APP_ID=
FEISHU_APP_SECRET=
FEISHU_BOT_OPEN_ID=
PORT=$PORT
HOST=$HOST
EOF
echo "Seeded $ENV_FILE — edit it to fill in OPENROUTER_API_KEY and Feishu creds, then re-run."
exit 0
fi
# Render the unit with concrete paths.
UNIT=/etc/systemd/system/cph-hub.service
sed \
-e "s|__SERVICE_USER__|$SERVICE_USER|g" \
-e "s|__HUB_DIR__|$HUB_DIR|g" \
-e "s|__ENV_FILE__|$ENV_FILE|g" \
-e "s|__NODE_BIN__|$NODE_BIN|g" \
"$HUB_DIR/deploy/cph-hub.service" >"$UNIT"
systemctl daemon-reload
systemctl enable cph-hub.service
echo "Installed cph-hub.service. Start with: systemctl start cph-hub"
echo "Check health: curl http://127.0.0.1:$PORT/api/healthz"