feat: add fnos storyforge web dev deploy
This commit is contained in:
9
deploy/storyforge-fnos-web-v4.compose.yaml
Normal file
9
deploy/storyforge-fnos-web-v4.compose.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
services:
|
||||
storyforge-web-v4-dev:
|
||||
image: docker.m.daocloud.io/library/nginx:alpine
|
||||
container_name: storyforge-web-v4-dev
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${STORYFORGE_WEB_V4_DEV_PORT:-19192}:80"
|
||||
volumes:
|
||||
- ../../storyforge/web/storyforge-web-v4:/usr/share/nginx/html:ro
|
||||
111
scripts/deploy_fnos_storyforge_web.sh
Executable file
111
scripts/deploy_fnos_storyforge_web.sh
Executable file
@@ -0,0 +1,111 @@
|
||||
#!/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/"
|
||||
@@ -110,7 +110,7 @@ python3 -m http.server 3918
|
||||
|
||||
- [http://127.0.0.1:3918/index.html](http://127.0.0.1:3918/index.html)
|
||||
|
||||
首次进入需要手动连接后端,默认地址是:
|
||||
如果本地是 `127.0.0.1 / localhost`,前端默认会连:
|
||||
|
||||
- `http://127.0.0.1:8081`
|
||||
|
||||
@@ -122,6 +122,18 @@ python3 -m http.server 3918
|
||||
|
||||
- `https://storyforge.hyzq.net`
|
||||
|
||||
如果页面部署在 NAS 之类的局域网静态站点上,推荐通过运行时配置显式指定后端:
|
||||
|
||||
- `assets/storyforge-runtime-config.js`
|
||||
- `window.__STORYFORGE_RUNTIME_CONFIG__.backendUrl = "https://storyforge.hyzq.net"`
|
||||
|
||||
仓库内已经提供一键部署脚本,可直接把前端发到飞牛 NAS 上跑开发测试:
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/StoryForge-gitea
|
||||
./scripts/deploy_fnos_storyforge_web.sh
|
||||
```
|
||||
|
||||
## 后续建议
|
||||
|
||||
- 继续补多平台各自更深的专属采集与解析能力,而不只是一套统一抽象层
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
if (typeof window === "undefined") {
|
||||
return "http://127.0.0.1:8081";
|
||||
}
|
||||
const runtimeConfig = window.__STORYFORGE_RUNTIME_CONFIG__ || window.__STORYFORGE_CONFIG__ || {};
|
||||
const configuredBackendUrl = String(runtimeConfig.backendUrl || runtimeConfig.backendURL || "").trim();
|
||||
if (configuredBackendUrl) {
|
||||
return configuredBackendUrl.replace(/\/$/, "");
|
||||
}
|
||||
const { origin, hostname, port, pathname } = window.location;
|
||||
if (/^https?:/i.test(origin) && hostname === "storyforge.hyzq.net") {
|
||||
return origin;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
(function () {
|
||||
window.__STORYFORGE_RUNTIME_CONFIG__ = Object.assign(
|
||||
{
|
||||
backendUrl: ""
|
||||
},
|
||||
window.__STORYFORGE_RUNTIME_CONFIG__ || {}
|
||||
);
|
||||
})();
|
||||
@@ -1912,6 +1912,7 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="./assets/storyforge-runtime-config.js"></script>
|
||||
<script src="./assets/storyforge-session-store.js"></script>
|
||||
<script src="./assets/storyforge-api-client.js"></script>
|
||||
<script src="./assets/storyforge-platform-runtime.js"></script>
|
||||
|
||||
Reference in New Issue
Block a user