#!/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"