Files
storyforge/scripts/deploy_fnos_storyforge_web.sh
2026-03-26 21:14:23 +08:00

112 lines
3.4 KiB
Bash
Executable File

#!/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}"
REMOTE_ROOT="${STORYFORGE_FNOS_REMOTE_ROOT:-/vol1/docker/hyzq-stack/current/storyforge}"
REMOTE_WEB_PARENT="$REMOTE_ROOT/web"
REMOTE_WEB_DIR="$REMOTE_WEB_PARENT/storyforge-web-v4"
REMOTE_ASSETS_DIR="$REMOTE_WEB_DIR/assets"
REMOTE_COMPOSE_DIR="${STORYFORGE_FNOS_COMPOSE_DIR:-/vol1/docker/hyzq-stack/current/deploy/fnos}"
REMOTE_COMPOSE_FILE="$REMOTE_COMPOSE_DIR/storyforge-fnos-web-v4.compose.yaml"
BACKEND_URL="${STORYFORGE_FNOS_BACKEND_URL:-https://storyforge.hyzq.net}"
WEB_PORT="${STORYFORGE_WEB_V4_DEV_PORT:-19192}"
need_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "missing required command: $1" >&2
exit 1
fi
}
need_cmd python3
if [ -z "${FNOS_PASSWORD:-}" ]; then
need_cmd security
fi
shell_quote() {
python3 - "$1" <<'PY'
import shlex
import sys
print(shlex.quote(sys.argv[1]))
PY
}
json_quote() {
python3 - "$1" <<'PY'
import json
import sys
print(json.dumps(sys.argv[1], ensure_ascii=False))
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)"
RUNTIME_CONFIG_FILE="$TMPDIR_DEPLOY/storyforge-runtime-config.js"
BACKEND_URL_JSON="$(json_quote "$BACKEND_URL")"
trap 'rm -rf "$TMPDIR_DEPLOY"' EXIT
cat >"$RUNTIME_CONFIG_FILE" <<EOF
(function () {
window.__STORYFORGE_RUNTIME_CONFIG__ = Object.assign(
{},
window.__STORYFORGE_RUNTIME_CONFIG__ || {},
{
backendUrl: $BACKEND_URL_JSON
}
);
})();
EOF
echo "[1/6] prepare remote directories"
"$FNOS_SSH" "$(shell_quote "mkdir -p $(shell_quote "$REMOTE_WEB_PARENT") $(shell_quote "$REMOTE_ASSETS_DIR") $(shell_quote "$REMOTE_COMPOSE_DIR")")"
echo "[2/6] sync web files"
"$FNOS_SCP" "$REMOTE_WEB_PARENT" "$ROOT/web/storyforge-web-v4"
echo "[3/6] sync runtime config override"
"$FNOS_SCP" "$REMOTE_ASSETS_DIR" "$RUNTIME_CONFIG_FILE"
echo "[4/6] sync compose file"
"$FNOS_SCP" "$REMOTE_COMPOSE_DIR" "$ROOT/deploy/storyforge-fnos-web-v4.compose.yaml"
echo "[5/6] restart fnOS web container"
run_remote_sudo "$PASSWORD" "cd $(shell_quote "$REMOTE_COMPOSE_DIR") && STORYFORGE_WEB_V4_DEV_PORT=$(shell_quote "$WEB_PORT") docker compose -f $(shell_quote "$REMOTE_COMPOSE_FILE") up -d --force-recreate storyforge-web-v4-dev && STORYFORGE_WEB_V4_DEV_PORT=$(shell_quote "$WEB_PORT") docker compose -f $(shell_quote "$REMOTE_COMPOSE_FILE") ps"
echo "[6/6] verify lan web"
for _attempt in 1 2 3 4 5 6 7 8 9 10; do
if curl -fsS "http://$FNOS_HOST:$WEB_PORT/" >/dev/null; then
break
fi
sleep 2
done
curl -fsS "http://$FNOS_HOST:$WEB_PORT/" >/dev/null
curl -fsS "http://$FNOS_HOST:$WEB_PORT/assets/storyforge-runtime-config.js" | grep -q "$BACKEND_URL"
echo "fnOS StoryForge Web V4 ready: http://$FNOS_HOST:$WEB_PORT/"