From 4546c95e8cbcab4a02dc1925e4a1c47fd586ae58 Mon Sep 17 00:00:00 2001 From: kris Date: Mon, 6 Apr 2026 08:55:56 +0800 Subject: [PATCH] fix: restore fnos live recorder deployment --- CHANGELOG.md | 5 + ...storyforge-fnos-live-recorder.compose.yaml | 7 +- scripts/deploy_fnos_storyforge_lan_stack.sh | 11 +- .../deploy_fnos_storyforge_live_recorder.sh | 137 ++++++++++++++++++ scripts/smoke_fnos_storyforge_lan.sh | 15 +- 5 files changed, 169 insertions(+), 6 deletions(-) create mode 100755 scripts/deploy_fnos_storyforge_live_recorder.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 4239fa7..9685413 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -523,3 +523,8 @@ - agent: 遗留 `创建 Agent` 入口现在也会优先 direct-execute,当前项目已就绪时直接创建 Agent,只有缺上下文时才回退到旧表单。 - pipeline: 全局旧入口 `AI 视频 / 实拍剪辑` 现在也会优先围绕最近完成任务 direct-execute,只有没有可承接任务时才回退到旧表单。 - review: `任务详情 -> 写复盘` 旧入口改成 direct-execute,带 `source_job_id` 直接生成复盘草稿,不再优先打开旧复盘表单。 +# 2026-04-06 + +- 修复 fnOS `live_recorder` 部署链,改成同步 `DouyinLiveRecorder-main` 源码到 NAS 并在 NAS 构建,避免错误预构建镜像里缺少 `webui.py` 导致容器启动即失败。 +- 新增 `scripts/deploy_fnos_storyforge_live_recorder.sh`,并把 live recorder 并入 `deploy_fnos_storyforge_lan_stack.sh`。 +- `smoke_fnos_storyforge_lan.sh` 新增 `live_recorder` 健康检查,后续 NAS 重启或版本更新后能直接发现录制服务回退。 diff --git a/deploy/storyforge-fnos-live-recorder.compose.yaml b/deploy/storyforge-fnos-live-recorder.compose.yaml index 5b24b16..def5400 100644 --- a/deploy/storyforge-fnos-live-recorder.compose.yaml +++ b/deploy/storyforge-fnos-live-recorder.compose.yaml @@ -32,7 +32,12 @@ services: # 3. Poll GET /api/status-lite or /api/recordings # 4. Read output via GET /api/downloads or /downloads/ storyforge-live-recorder: - image: ihmily/douyin-live-recorder:latest + image: ${STORYFORGE_LIVE_RECORDER_IMAGE:-storyforge-live-recorder:fnos} + build: + context: ../../storyforge/live-recorder-source + dockerfile: Dockerfile.storyforge + args: + BASE_IMAGE: ${STORYFORGE_LIVE_RECORDER_BASE_IMAGE:-docker.m.daocloud.io/library/python:3.11-slim} container_name: storyforge-live-recorder restart: unless-stopped tty: true diff --git a/scripts/deploy_fnos_storyforge_lan_stack.sh b/scripts/deploy_fnos_storyforge_lan_stack.sh index c1ee7f1..efdf8b8 100755 --- a/scripts/deploy_fnos_storyforge_lan_stack.sh +++ b/scripts/deploy_fnos_storyforge_lan_stack.sh @@ -9,20 +9,23 @@ BACKEND_URL="${STORYFORGE_FNOS_BACKEND_URL:-http://$FNOS_HOST:$COLLECTOR_PORT}" SKIP_TUNNEL="${SKIP_TUNNEL:-0}" SKIP_SMOKE="${SKIP_SMOKE:-0}" -echo "[1/4] ensure fnOS cutvideo tunnel" +echo "[1/5] ensure fnOS cutvideo tunnel" if [ "$SKIP_TUNNEL" = "1" ]; then echo "skip tunnel deployment because SKIP_TUNNEL=1" else STORYFORGE_FNOS_BACKEND_URL="$BACKEND_URL" bash "$ROOT/scripts/deploy_fnos_cutvideo_tunnel.sh" fi -echo "[2/4] deploy fnOS collector" +echo "[2/5] deploy fnOS live recorder" +bash "$ROOT/scripts/deploy_fnos_storyforge_live_recorder.sh" + +echo "[3/5] deploy fnOS collector" STORYFORGE_FNOS_COLLECTOR_URL="$BACKEND_URL" bash "$ROOT/scripts/deploy_fnos_storyforge_collector.sh" -echo "[3/4] deploy fnOS web" +echo "[4/5] deploy fnOS web" STORYFORGE_FNOS_BACKEND_URL="$BACKEND_URL" bash "$ROOT/scripts/deploy_fnos_storyforge_web.sh" -echo "[4/4] smoke fnOS lan stack" +echo "[5/5] smoke fnOS lan stack" if [ "$SKIP_SMOKE" = "1" ]; then echo "skip smoke because SKIP_SMOKE=1" else diff --git a/scripts/deploy_fnos_storyforge_live_recorder.sh b/scripts/deploy_fnos_storyforge_live_recorder.sh new file mode 100755 index 0000000..655b14a --- /dev/null +++ b/scripts/deploy_fnos_storyforge_live_recorder.sh @@ -0,0 +1,137 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)" + +export CODEX_HOME="${CODEX_HOME:-$HOME/.codex}" +export FNOS_SKILL="${FNOS_SKILL:-$CODEX_HOME/skills/fnos-hyzq-deploy}" +export FNOS_SSH="${FNOS_SSH:-$FNOS_SKILL/scripts/fnos_ssh.sh}" +export FNOS_SCP="${FNOS_SCP:-$FNOS_SKILL/scripts/fnos_scp.sh}" + +FNOS_HOST="${FNOS_HOST:-192.168.31.188}" +FNOS_USER="${FNOS_USER:-krisolo}" +LIVE_RECORDER_SOURCE_DIR="${LIVE_RECORDER_SOURCE_DIR:-/Users/kris/code/DouyinLiveRecorder-main}" +REMOTE_ROOT="${STORYFORGE_FNOS_REMOTE_ROOT:-/vol1/docker/hyzq-stack/current/storyforge}" +REMOTE_SOURCE_PARENT="$REMOTE_ROOT" +REMOTE_SOURCE_DIR="$REMOTE_ROOT/live-recorder-source" +REMOTE_COMPOSE_DIR="${STORYFORGE_FNOS_COMPOSE_DIR:-/vol1/docker/hyzq-stack/current/deploy/fnos}" +REMOTE_COMPOSE_FILE="$REMOTE_COMPOSE_DIR/storyforge-fnos-live-recorder.compose.yaml" +LIVE_RECORDER_PORT="${STORYFORGE_LIVE_RECORDER_PORT:-19106}" +LIVE_RECORDER_IMAGE="${STORYFORGE_LIVE_RECORDER_IMAGE:-storyforge-live-recorder:fnos}" +LIVE_RECORDER_BASE_IMAGE="${STORYFORGE_LIVE_RECORDER_BASE_IMAGE:-docker.m.daocloud.io/library/python:3.11-slim}" +LIVE_RECORDER_STATE_ROOT="${STORYFORGE_LIVE_RECORDER_STATE_ROOT:-/vol1/docker/hyzq-stack/shared/storyforge-live-recorder}" + +need_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "missing required command: $1" >&2 + exit 1 + fi +} + +need_cmd python3 +need_cmd security +need_cmd rsync + +if [ ! -f "$LIVE_RECORDER_SOURCE_DIR/webui.py" ]; then + echo "live recorder source missing webui.py: $LIVE_RECORDER_SOURCE_DIR" >&2 + exit 1 +fi + +shell_quote() { + python3 - "$1" <<'PY' +import shlex +import sys +print(shlex.quote(sys.argv[1])) +PY +} + +resolve_password() { + if [ -n "${FNOS_PASSWORD:-}" ]; then + printf '%s' "$FNOS_PASSWORD" + return 0 + fi + security find-internet-password -s "$FNOS_HOST" -a "$FNOS_USER" -w +} + +run_remote_sudo() { + local password="$1" + shift + local remote_cmd="$*" + local password_quoted + password_quoted="$(shell_quote "$password")" + "$FNOS_SSH" "$(shell_quote "printf '%s\\n' $password_quoted | sudo -S -p '' sh -lc $(shell_quote "$remote_cmd")")" +} + +PASSWORD="$(resolve_password)" +TMPDIR_DEPLOY="$(mktemp -d)" +FILTERED_SOURCE_DIR="$TMPDIR_DEPLOY/live-recorder-source" +trap 'rm -rf "$TMPDIR_DEPLOY"' EXIT + +mkdir -p "$FILTERED_SOURCE_DIR" +rsync -a --delete \ + --exclude '.git' \ + --exclude '.github' \ + --exclude '__pycache__' \ + --exclude '*.pyc' \ + --exclude '.venv' \ + --exclude 'downloads' \ + --exclude 'logs' \ + --exclude 'backup_config' \ + "$LIVE_RECORDER_SOURCE_DIR/" "$FILTERED_SOURCE_DIR/" + +cat >"$FILTERED_SOURCE_DIR/Dockerfile.storyforge" </dev/null; then + break + fi + sleep 2 +done +curl -fsS "http://$FNOS_HOST:$LIVE_RECORDER_PORT/api/healthz" >/dev/null + +echo "fnOS StoryForge live recorder ready: http://$FNOS_HOST:$LIVE_RECORDER_PORT/" diff --git a/scripts/smoke_fnos_storyforge_lan.sh b/scripts/smoke_fnos_storyforge_lan.sh index a5b6292..0344992 100755 --- a/scripts/smoke_fnos_storyforge_lan.sh +++ b/scripts/smoke_fnos_storyforge_lan.sh @@ -10,6 +10,7 @@ WEB_URL="${STORYFORGE_FNOS_WEB_URL:-http://$FNOS_HOST:$WEB_PORT}" BACKEND_URL="${STORYFORGE_FNOS_COLLECTOR_URL:-http://$FNOS_HOST:$COLLECTOR_PORT}" CUTVIDEO_URL="${CUTVIDEO_BASE_URL:-http://$FNOS_HOST:$CUTVIDEO_FORWARD_PORT}" COMPAT_URL="${STORYFORGE_COMPAT_BASE_URL:-http://$FNOS_HOST:$STORYFORGE_COMPAT_FORWARD_PORT}" +LIVE_RECORDER_URL="${LIVE_RECORDER_BASE_URL:-http://$FNOS_HOST:19106}" CURL_MAX_TIME="${STORYFORGE_FNOS_CURL_MAX_TIME:-60}" need_cmd() { @@ -39,6 +40,7 @@ action_registry_file="$tmp_dir/action-registry.json" integrations_file="$tmp_dir/integrations.json" bootstrap_file="$tmp_dir/bootstrap.json" compat_file="$tmp_dir/compat.html" +live_recorder_health_file="$tmp_dir/live-recorder-health.json" token_file="$tmp_dir/token.txt" project_id_file="$tmp_dir/project-id.txt" @@ -138,7 +140,7 @@ if not cutvideo.get("supports_uploads"): print("integrations ok") ' "$integrations_file" "$CUTVIDEO_URL" -echo "[7/7] check fnOS tunnel endpoints" +echo "[7/8] check fnOS tunnel endpoints" curl_fetch "$CUTVIDEO_URL/api/bootstrap" >"$bootstrap_file" curl_fetch "$COMPAT_URL/" >"$compat_file" python3 -c ' @@ -154,8 +156,19 @@ if ! rg -Fq "数字人网页业务台" "$compat_file" && ! rg -Fq "BUSINESS CONS fi echo "compat ok" +echo "[8/8] check live recorder health" +curl_fetch "$LIVE_RECORDER_URL/api/healthz" >"$live_recorder_health_file" +python3 -c ' +import json, pathlib, sys +payload = json.loads(pathlib.Path(sys.argv[1]).read_text()) +if payload.get("ok") is not True: + raise SystemExit(f"unexpected live recorder health payload: {payload!r}") +print("live recorder ok") +' "$live_recorder_health_file" + echo "fnOS lan smoke passed:" echo " web: $WEB_URL/" echo " collector: $BACKEND_URL" echo " cutvideo: $CUTVIDEO_URL" echo " compat: $COMPAT_URL" +echo " live_recorder: $LIVE_RECORDER_URL"