179 lines
5.2 KiB
Bash
Executable File
179 lines
5.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
BASE_URL="${STORYFORGE_PUBLIC_BASE_URL:-https://storyforge.hyzq.net}"
|
|
CURL_MAX_TIME="${STORYFORGE_PUBLIC_CURL_MAX_TIME:-60}"
|
|
|
|
need_cmd() {
|
|
if ! command -v "$1" >/dev/null 2>&1; then
|
|
echo "missing required command: $1" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
need_cmd curl
|
|
need_cmd python3
|
|
need_cmd rg
|
|
|
|
curl_fetch() {
|
|
curl -fsS --max-time "$CURL_MAX_TIME" "$@"
|
|
}
|
|
|
|
tmp_dir="$(mktemp -d)"
|
|
trap 'rm -rf "$tmp_dir"' EXIT
|
|
|
|
health_file="$tmp_dir/health.json"
|
|
html_file="$tmp_dir/index.html"
|
|
js_file="$tmp_dir/app.js"
|
|
openapi_file="$tmp_dir/openapi.json"
|
|
session_file="$tmp_dir/session.json"
|
|
token_file="$tmp_dir/token.txt"
|
|
integrations_file="$tmp_dir/integrations.json"
|
|
asr_url_file="$tmp_dir/asr-url.txt"
|
|
asr_wav="$tmp_dir/asr.wav"
|
|
asr_result="$tmp_dir/asr.json"
|
|
|
|
echo "[1/7] check public healthz"
|
|
curl_fetch "$BASE_URL/healthz" >"$health_file"
|
|
python3 - "$health_file" "$asr_url_file" <<'PY'
|
|
import json
|
|
import pathlib
|
|
import sys
|
|
|
|
payload = json.loads(pathlib.Path(sys.argv[1]).read_text())
|
|
status = str(payload.get("status") or "").lower()
|
|
if status != "ok":
|
|
raise SystemExit(f"unexpected health status: {status!r}")
|
|
asr_url = str(payload.get("asrHttpBaseUrl") or "").strip()
|
|
pathlib.Path(sys.argv[2]).write_text(asr_url, encoding="utf-8")
|
|
print("healthz ok")
|
|
PY
|
|
|
|
echo "[2/7] check public auto-session"
|
|
curl_fetch -X POST "$BASE_URL/v2/auth/auto-session" \
|
|
-H 'content-type: application/json' \
|
|
-d '{}' >"$session_file"
|
|
python3 - "$session_file" "$token_file" <<'PY'
|
|
import json
|
|
import pathlib
|
|
import sys
|
|
|
|
payload = json.loads(pathlib.Path(sys.argv[1]).read_text())
|
|
token = str(payload.get("token") or "")
|
|
mode = str(payload.get("mode") or "")
|
|
username = str((payload.get("account") or {}).get("username") or "")
|
|
if not token:
|
|
raise SystemExit("auto-session did not return token")
|
|
if mode != "auto":
|
|
raise SystemExit(f"unexpected mode: {mode!r}")
|
|
if not username:
|
|
raise SystemExit("auto-session returned empty username")
|
|
pathlib.Path(sys.argv[2]).write_text(token, encoding="utf-8")
|
|
print(f"auto-session ok: {username}")
|
|
PY
|
|
token="$(cat "$token_file")"
|
|
|
|
echo "[3/7] check public integrations health"
|
|
curl_fetch "$BASE_URL/v2/integrations/health" \
|
|
-H "Authorization: Bearer $token" >"$integrations_file"
|
|
python3 - "$integrations_file" <<'PY'
|
|
import json
|
|
import pathlib
|
|
import sys
|
|
|
|
payload = json.loads(pathlib.Path(sys.argv[1]).read_text())
|
|
required = {
|
|
"n8n": "服务器",
|
|
"huobao": "服务器",
|
|
"asr": "Windows",
|
|
"live_recorder": "NAS",
|
|
}
|
|
for key, label in required.items():
|
|
detail = payload.get(key) or {}
|
|
if not detail.get("reachable"):
|
|
raise SystemExit(f"{key} is not reachable")
|
|
if detail.get("deployment_label") != label:
|
|
raise SystemExit(f"{key} deployment label mismatch: {detail.get('deployment_label')!r}")
|
|
cutvideo = payload.get("cutvideo") or {}
|
|
if not cutvideo.get("reachable"):
|
|
raise SystemExit("cutvideo is not reachable")
|
|
if str(cutvideo.get("deployment_label") or "") not in {"Windows", "NAS 隧道"}:
|
|
raise SystemExit(f"cutvideo deployment label mismatch: {cutvideo.get('deployment_label')!r}")
|
|
local_model = payload.get("local_model") or {}
|
|
if str(local_model.get("error") or "") != "not_configured":
|
|
raise SystemExit(f"local_model should be not_configured, got {local_model.get('error')!r}")
|
|
if str((payload.get("asr") or {}).get("active_device") or "").strip() == "":
|
|
raise SystemExit("asr active_device missing")
|
|
print("integrations ok")
|
|
PY
|
|
|
|
echo "[4/7] check public ASR transcribe"
|
|
python3 - "$asr_wav" <<'PY'
|
|
import math
|
|
import struct
|
|
import sys
|
|
import wave
|
|
|
|
path = sys.argv[1]
|
|
sample_rate = 16000
|
|
duration = 0.25
|
|
freq = 440.0
|
|
samples = int(sample_rate * duration)
|
|
with wave.open(path, "wb") as handle:
|
|
handle.setnchannels(1)
|
|
handle.setsampwidth(2)
|
|
handle.setframerate(sample_rate)
|
|
for i in range(samples):
|
|
value = int(16000 * math.sin(2 * math.pi * freq * (i / sample_rate)))
|
|
handle.writeframes(struct.pack("<h", value))
|
|
PY
|
|
asr_url="$(cat "$asr_url_file")"
|
|
case "$asr_url" in
|
|
http://127.0.0.1*|http://localhost*|https://127.0.0.1*|https://localhost*)
|
|
echo "asr transcribe skipped (server-local url: $asr_url)"
|
|
;;
|
|
*)
|
|
curl_fetch -X POST -F "wav=@$asr_wav" "$asr_url/transcribe" >"$asr_result"
|
|
python3 - "$asr_result" <<'PY'
|
|
import json
|
|
import pathlib
|
|
import sys
|
|
|
|
payload = json.loads(pathlib.Path(sys.argv[1]).read_text())
|
|
if "duration_ms" not in payload:
|
|
raise SystemExit("missing duration_ms")
|
|
if "success" not in payload:
|
|
raise SystemExit("missing success flag")
|
|
print("asr transcribe ok")
|
|
PY
|
|
;;
|
|
esac
|
|
|
|
echo "[5/7] check public index"
|
|
curl_fetch "$BASE_URL/" >"$html_file"
|
|
rg -q "StoryForge" "$html_file"
|
|
echo "index ok"
|
|
|
|
echo "[6/7] check deployed web bundle"
|
|
curl_fetch "$BASE_URL/assets/app.js" >"$js_file"
|
|
rg -q "select-platform" "$js_file"
|
|
rg -q "trackingCursorMap" "$js_file"
|
|
rg -q "renderPlatformSwitchChips" "$js_file"
|
|
echo "bundle ok"
|
|
|
|
echo "[7/7] check public openapi routes"
|
|
curl_fetch "$BASE_URL/openapi.json" >"$openapi_file"
|
|
for route in \
|
|
'"/v2/xiaohongshu/accounts"' \
|
|
'"/v2/bilibili/accounts"' \
|
|
'"/v2/kuaishou/accounts"' \
|
|
'"/v2/wechat_video/accounts"' \
|
|
'"/v2/platform-agents"' \
|
|
'"/v2/tenant/quota"'
|
|
do
|
|
rg -q "$route" "$openapi_file"
|
|
done
|
|
echo "openapi ok"
|
|
|
|
echo "public smoke passed: $BASE_URL"
|