diff --git a/README.md b/README.md index 7497ebc..e96836d 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,17 @@ N8N_BASE_URL=http://127.0.0.1:5670 - 支持 `user -> project -> knowledge base / assistant(agent) / job / content source` 的多租户边界 - 素材入口支持文字、视频链接、视频上传;内容源账号通过 `content_sources` 建模持久化,并可派生父子分析任务 - `cutvideo` 继续运行在 Windows 机器,本系统通过 API 调度 +- fnOS / 局域网调试环境下,`cutvideo` 建议通过 NAS SSH 隧道接入,默认入口为 `http://192.168.31.188:19186` - `huobao-drama` 继续作为 AI 生成视频主链的核心引擎 - 详细审计、阶段计划和联调步骤见 `docs/` - Windows `cutvideo` 的恢复与常驻维护见 [`WINDOWS_CUTVIDEO_OPERATIONS_2026-03-27.md`](/Users/kris/code/StoryForge-gitea/docs/WINDOWS_CUTVIDEO_OPERATIONS_2026-03-27.md) + +fnOS 局域网联调推荐先执行: + +```bash +./scripts/deploy_fnos_cutvideo_tunnel.sh +./scripts/deploy_fnos_storyforge_collector.sh +./scripts/deploy_fnos_storyforge_web.sh +``` + +这套顺序会先把 Windows `cutvideo` 通过 NAS 本地隧道暴露到 `19186/19181`,再让 NAS collector 和 Web 统一切到这条更稳定的入口。 diff --git a/deploy/storyforge-fnos-collector.compose.yaml b/deploy/storyforge-fnos-collector.compose.yaml index 634bdcb..3c78307 100644 --- a/deploy/storyforge-fnos-collector.compose.yaml +++ b/deploy/storyforge-fnos-collector.compose.yaml @@ -29,7 +29,7 @@ services: WEB_AUTOLOGIN_USERNAME: ${WEB_AUTOLOGIN_USERNAME:-} WEB_AUTOLOGIN_PASSWORD: ${WEB_AUTOLOGIN_PASSWORD:-} ORCHESTRATOR_SHARED_SECRET: ${ORCHESTRATOR_SHARED_SECRET:-storyforge-local-secret} - CUTVIDEO_BASE_URL: ${CUTVIDEO_BASE_URL:-http://192.168.31.18:7860} + CUTVIDEO_BASE_URL: ${CUTVIDEO_BASE_URL:-http://192.168.31.188:19186} CUTVIDEO_API_KEY: ${CUTVIDEO_API_KEY:-} CUTVIDEO_BASE_CONFIG: ${CUTVIDEO_BASE_CONFIG:-example.job.yaml} CUTVIDEO_POLL_INTERVAL_SEC: ${CUTVIDEO_POLL_INTERVAL_SEC:-10} diff --git a/docs/LAN_E2E_GUIDE_2026-03-18.md b/docs/LAN_E2E_GUIDE_2026-03-18.md index e36be5b..775d935 100644 --- a/docs/LAN_E2E_GUIDE_2026-03-18.md +++ b/docs/LAN_E2E_GUIDE_2026-03-18.md @@ -32,10 +32,23 @@ cp .env.example .env - 如果你单独重建 `collector`,要确保运行时仍带上 `CUTVIDEO_BASE_URL`,否则容器会退回空值 - `collector` 容器不要直接复用宿主机的 `N8N_BASE_URL=http://127.0.0.1:5670`,否则容器内会连回自己并导致 webhook 调度失败 -- 当前已验证可用的 Windows `cutvideo` 地址是 `http://192.168.31.18:7860` +- 当前更稳定的 NAS 转发地址是 `http://192.168.31.188:19186` +- Windows 直连地址 `http://192.168.31.18:7860` 仍可作为主机内自检入口,但不再建议作为 StoryForge 主链默认值 - 当前已验证可用的本机 HTTP ASR 入口是 `http://host.docker.internal:8088/transcribe` - 如果你用的是本机 `mac-whisper-service`,建议同时以 `WHISPER_TIMEOUT_MS=120000` 启动,否则长视频会直接 504 +推荐先执行: + +```bash +./scripts/deploy_fnos_cutvideo_tunnel.sh +``` + +它会做三件事: + +- 在 fnOS 上生成并持久化 Windows SSH 隧道密钥 +- 把 fnOS 公钥写入 Windows OpenSSH 管理员授权文件 +- 在 fnOS 上常驻 `19186 -> Windows 127.0.0.1:7860` 和 `19181 -> Windows 127.0.0.1:8081`,并写入 `@reboot` 自启动 + `cutvideo` 维护补充(2026-03-27): - 当前 Windows 主机 SSH 别名是 `shuziren-win`,对应 `192.168.31.18` @@ -248,6 +261,7 @@ npm run capture -- \ - `GET /api/bootstrap` 恢复为 `200`,`GET /api/uploads` 返回 `405 Method Not Allowed` - 上面的 `405` 是正常现象,说明上传接口存在且只接受 `POST` - `StoryForge collector` 的 `/v2/integrations/health` 已重新识别到 `cutvideo.reachable=true`、`supports_uploads=true` +- fnOS 局域网调试链现在默认走 `http://192.168.31.188:19186`,不再依赖 Windows 机器直接开放 `7860` - 如果 UI 里 `自动剪辑` 再次掉线,先按 [`WINDOWS_CUTVIDEO_OPERATIONS_2026-03-27.md`](/Users/kris/code/StoryForge-gitea/docs/WINDOWS_CUTVIDEO_OPERATIONS_2026-03-27.md) 检查 Windows 任务计划程序和 `.venv` ## 8. `huobao-drama` AI 视频链路验证 diff --git a/scripts/deploy_fnos_cutvideo_tunnel.sh b/scripts/deploy_fnos_cutvideo_tunnel.sh new file mode 100755 index 0000000..332e06a --- /dev/null +++ b/scripts/deploy_fnos_cutvideo_tunnel.sh @@ -0,0 +1,170 @@ +#!/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_SCP="${FNOS_SCP:-$FNOS_SKILL/scripts/fnos_scp.sh}" + +FNOS_HOST="${FNOS_HOST:-192.168.31.188}" +FNOS_USER="${FNOS_USER:-krisolo}" +WINDOWS_HOST="${WINDOWS_HOST:-192.168.31.18}" +WINDOWS_USER="${WINDOWS_USER:-kris}" +WINDOWS_SSH_ALIAS="${WINDOWS_SSH_ALIAS:-shuziren-win}" +CUTVIDEO_FORWARD_PORT="${CUTVIDEO_FORWARD_PORT:-19186}" +STORYFORGE_COMPAT_FORWARD_PORT="${STORYFORGE_COMPAT_FORWARD_PORT:-19181}" +FNOS_TUNNEL_KEY_PATH="${FNOS_TUNNEL_KEY_PATH:-/home/$FNOS_USER/.ssh/cutvideo_lan_tunnel}" +FNOS_TUNNEL_SCRIPT_PATH="${FNOS_TUNNEL_SCRIPT_PATH:-/home/$FNOS_USER/codex_start_cutvideo_tunnel_on_fnos.sh}" +FNOS_TUNNEL_CRON_SCRIPT_PATH="${FNOS_TUNNEL_CRON_SCRIPT_PATH:-/home/$FNOS_USER/codex_enable_cutvideo_tunnel_cron_on_fnos.sh}" + +need_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "missing required command: $1" >&2 + exit 1 + fi +} + +shell_quote() { + python3 - "$1" <<'PY' +import shlex +import sys +print(shlex.quote(sys.argv[1])) +PY +} + +need_cmd python3 +need_cmd ssh +need_cmd scp +need_cmd expect +need_cmd ssh-keygen + +if [ -z "${FNOS_PASSWORD:-}" ]; then + need_cmd security +fi + +resolve_fnos_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_fnos_cmd() { + local remote_cmd="$1" + local remote_cmd_quoted + remote_cmd_quoted="$(shell_quote "$remote_cmd")" + export FNOS_REMOTE_CMD_QUOTED="$remote_cmd_quoted" + export FNOS_REMOTE_HOST="$FNOS_HOST" + export FNOS_REMOTE_USER="$FNOS_USER" + export FNOS_REMOTE_PASSWORD="$FNOS_PASSWORD_VALUE" + /usr/bin/expect <<'EOF' +set timeout -1 +set pw $env(FNOS_REMOTE_PASSWORD) +set host $env(FNOS_REMOTE_HOST) +set user $env(FNOS_REMOTE_USER) +set remote_cmd_quoted $env(FNOS_REMOTE_CMD_QUOTED) +set argv [list ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $user@$host "sh -lc $remote_cmd_quoted"] +spawn {*}$argv +expect { + -re {Are you sure you want to continue connecting \(yes/no(/\[fingerprint\])?\)\?} { + send "yes\r" + exp_continue + } + -re {[Pp]assword:} { + send "$pw\r" + exp_continue + } + eof +} +catch wait result +set exit_code [lindex $result 3] +exit $exit_code +EOF +} + +TMPDIR_DEPLOY="$(mktemp -d)" +trap 'rm -rf "$TMPDIR_DEPLOY"' EXIT + +FNOS_PASSWORD_VALUE="$(resolve_fnos_password)" +START_SCRIPT_LOCAL="$TMPDIR_DEPLOY/codex_start_cutvideo_tunnel_on_fnos.sh" +CRON_SCRIPT_LOCAL="$TMPDIR_DEPLOY/codex_enable_cutvideo_tunnel_cron_on_fnos.sh" + +echo "[1/6] ensure fnOS tunnel key" +FNOS_PUBKEY="$( + run_fnos_cmd "mkdir -p ~/.ssh && chmod 700 ~/.ssh && if [ ! -f ~/.ssh/cutvideo_lan_tunnel ]; then ssh-keygen -t ed25519 -N '' -f ~/.ssh/cutvideo_lan_tunnel >/dev/null; fi && cat ~/.ssh/cutvideo_lan_tunnel.pub" +)" +FNOS_PUBKEY="$(printf '%s\n' "$FNOS_PUBKEY" | tail -n 1)" + +echo "[2/6] authorize fnOS key on Windows OpenSSH" +ssh "$WINDOWS_SSH_ALIAS" "powershell -NoProfile -Command \"\ + \$adminKeys='C:\\ProgramData\\ssh\\administrators_authorized_keys'; \ + \$userKeys='C:\\Users\\$WINDOWS_USER\\.ssh\\authorized_keys'; \ + \$pub='$FNOS_PUBKEY'; \ + New-Item -ItemType Directory -Force -Path 'C:\\ProgramData\\ssh' | Out-Null; \ + if (-not (Test-Path \$adminKeys)) { New-Item -ItemType File -Force -Path \$adminKeys | Out-Null }; \ + if (-not (Select-String -Path \$adminKeys -SimpleMatch \$pub -Quiet)) { Add-Content -Path \$adminKeys -Value \$pub }; \ + New-Item -ItemType Directory -Force -Path 'C:\\Users\\$WINDOWS_USER\\.ssh' | Out-Null; \ + if (-not (Test-Path \$userKeys)) { New-Item -ItemType File -Force -Path \$userKeys | Out-Null }; \ + if (-not (Select-String -Path \$userKeys -SimpleMatch \$pub -Quiet)) { Add-Content -Path \$userKeys -Value \$pub }; \ + Write-Output 'WINDOWS_AUTHORIZED_OK'\"" + +echo "[3/6] verify fnOS -> Windows key login" +run_fnos_cmd "ssh -i $FNOS_TUNNEL_KEY_PATH -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $WINDOWS_USER@$WINDOWS_HOST cmd /c echo FNOS_WINDOWS_TUNNEL_OK" + +cat >"$START_SCRIPT_LOCAL" </dev/null 2>&1 || true + +nohup ssh \\ + -i "\$TUNNEL_KEY" \\ + -o StrictHostKeyChecking=no \\ + -o UserKnownHostsFile=/dev/null \\ + -o ExitOnForwardFailure=yes \\ + -o ServerAliveInterval=30 \\ + -o ServerAliveCountMax=3 \\ + -g \\ + -N \\ + -L 0.0.0.0:${CUTVIDEO_FORWARD_PORT}:127.0.0.1:7860 \\ + -L 0.0.0.0:${STORYFORGE_COMPAT_FORWARD_PORT}:127.0.0.1:8081 \\ + $WINDOWS_USER@$WINDOWS_HOST \\ + >"\$LOG_FILE" 2>&1 & + +sleep 2 +pgrep -af "${CUTVIDEO_FORWARD_PORT}:127.0.0.1:7860" +EOF + +cat >"$CRON_SCRIPT_LOCAL" </dev/null | grep -v "codex_start_cutvideo_tunnel_on_fnos.sh" >"\$TMP_CRON" || true +echo "@reboot \$SCRIPT_PATH" >>"\$TMP_CRON" +crontab "\$TMP_CRON" +rm -f "\$TMP_CRON" + +crontab -l +EOF + +echo "[4/6] upload fnOS tunnel scripts" +"$FNOS_SCP" "/home/$FNOS_USER" "$START_SCRIPT_LOCAL" "$CRON_SCRIPT_LOCAL" + +echo "[5/6] start tunnel and enable reboot recovery" +run_fnos_cmd "chmod +x $FNOS_TUNNEL_SCRIPT_PATH $FNOS_TUNNEL_CRON_SCRIPT_PATH && $FNOS_TUNNEL_SCRIPT_PATH && $FNOS_TUNNEL_CRON_SCRIPT_PATH" + +echo "[6/6] verify forwarded ports" +curl -fsS "http://$FNOS_HOST:$CUTVIDEO_FORWARD_PORT/api/bootstrap" >/dev/null +curl -fsS "http://$FNOS_HOST:$STORYFORGE_COMPAT_FORWARD_PORT/" >/dev/null + +echo "fnOS cutvideo tunnel ready:" +echo " cutvideo: http://$FNOS_HOST:$CUTVIDEO_FORWARD_PORT" +echo " storyforge-live-compat: http://$FNOS_HOST:$STORYFORGE_COMPAT_FORWARD_PORT" diff --git a/scripts/deploy_fnos_storyforge_collector.sh b/scripts/deploy_fnos_storyforge_collector.sh index ba77c89..0401457 100755 --- a/scripts/deploy_fnos_storyforge_collector.sh +++ b/scripts/deploy_fnos_storyforge_collector.sh @@ -54,7 +54,7 @@ LOCAL_OPENAI_API_KEY="${LOCAL_OPENAI_API_KEY:-}" N8N_BASE_URL="${N8N_BASE_URL:-${HOST_LAN_IP:+http://$HOST_LAN_IP:5670}}" ASR_HTTP_BASE_URL="${ASR_HTTP_BASE_URL:-${HOST_LAN_IP:+http://$HOST_LAN_IP:8088}}" HUOBAO_BASE_URL="${HUOBAO_BASE_URL:-${HOST_LAN_IP:+http://$HOST_LAN_IP:5678}}" -CUTVIDEO_BASE_URL="${CUTVIDEO_BASE_URL:-http://192.168.31.18:7860}" +CUTVIDEO_BASE_URL="${CUTVIDEO_BASE_URL:-http://$FNOS_HOST:19186}" LIVE_RECORDER_BASE_URL="${LIVE_RECORDER_BASE_URL:-http://192.168.31.188:19106}" CLOUD_DB_PATH="${CLOUD_DB_PATH:-/home/ubuntu/storyforge/data/collector/storyforge.db}" CLOUD_DB_SNAPSHOT_PATH="${CLOUD_DB_SNAPSHOT_PATH:-/home/ubuntu/storyforge/data/collector/storyforge-fnos-sync.db}"