fix: restore fnos live recorder deployment
This commit is contained in:
@@ -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 重启或版本更新后能直接发现录制服务回退。
|
||||
|
||||
@@ -32,7 +32,12 @@ services:
|
||||
# 3. Poll GET /api/status-lite or /api/recordings
|
||||
# 4. Read output via GET /api/downloads or /downloads/<path>
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
137
scripts/deploy_fnos_storyforge_live_recorder.sh
Executable file
137
scripts/deploy_fnos_storyforge_live_recorder.sh
Executable file
@@ -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" <<EOF
|
||||
ARG BASE_IMAGE=$LIVE_RECORDER_BASE_IMAGE
|
||||
FROM \$BASE_IMAGE
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY . /app
|
||||
|
||||
RUN apt-get update && \\
|
||||
apt-get install -y curl gnupg && \\
|
||||
curl -sL https://deb.nodesource.com/setup_20.x | bash - && \\
|
||||
apt-get install -y nodejs
|
||||
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
RUN apt-get update && \\
|
||||
apt-get install -y ffmpeg tzdata && \\
|
||||
ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \\
|
||||
dpkg-reconfigure -f noninteractive tzdata
|
||||
|
||||
CMD ["python", "main.py"]
|
||||
EOF
|
||||
|
||||
echo "[1/5] prepare remote directories"
|
||||
"$FNOS_SSH" "$(shell_quote "rm -rf $(shell_quote "$REMOTE_SOURCE_DIR") && mkdir -p $(shell_quote "$REMOTE_SOURCE_PARENT") $(shell_quote "$REMOTE_COMPOSE_DIR") $(shell_quote "$LIVE_RECORDER_STATE_ROOT")")"
|
||||
run_remote_sudo "$PASSWORD" "mkdir -p \
|
||||
$(shell_quote "$LIVE_RECORDER_STATE_ROOT/config") \
|
||||
$(shell_quote "$LIVE_RECORDER_STATE_ROOT/logs") \
|
||||
$(shell_quote "$LIVE_RECORDER_STATE_ROOT/backup_config") \
|
||||
$(shell_quote "$LIVE_RECORDER_STATE_ROOT/downloads")"
|
||||
|
||||
echo "[2/5] sync live recorder source"
|
||||
"$FNOS_SCP" "$REMOTE_SOURCE_PARENT" "$FILTERED_SOURCE_DIR"
|
||||
|
||||
echo "[3/5] sync compose file"
|
||||
"$FNOS_SCP" "$REMOTE_COMPOSE_DIR" "$ROOT/deploy/storyforge-fnos-live-recorder.compose.yaml"
|
||||
|
||||
echo "[4/5] build and restart live recorder"
|
||||
run_remote_sudo "$PASSWORD" "cd $(shell_quote "$REMOTE_COMPOSE_DIR") && \
|
||||
STORYFORGE_LIVE_RECORDER_PORT=$(shell_quote "$LIVE_RECORDER_PORT") \
|
||||
STORYFORGE_LIVE_RECORDER_IMAGE=$(shell_quote "$LIVE_RECORDER_IMAGE") \
|
||||
STORYFORGE_LIVE_RECORDER_BASE_IMAGE=$(shell_quote "$LIVE_RECORDER_BASE_IMAGE") \
|
||||
STORYFORGE_LIVE_RECORDER_STATE_ROOT=$(shell_quote "$LIVE_RECORDER_STATE_ROOT") \
|
||||
docker compose -f $(shell_quote "$REMOTE_COMPOSE_FILE") up -d --build --force-recreate storyforge-live-recorder && \
|
||||
STORYFORGE_LIVE_RECORDER_PORT=$(shell_quote "$LIVE_RECORDER_PORT") docker compose -f $(shell_quote "$REMOTE_COMPOSE_FILE") ps"
|
||||
|
||||
echo "[5/5] verify live recorder health"
|
||||
for _attempt in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15; do
|
||||
if curl -fsS "http://$FNOS_HOST:$LIVE_RECORDER_PORT/api/healthz" >/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/"
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user