feat: organize scripts and add portal validation assets

This commit is contained in:
phamnazage-jpg
2026-05-27 09:39:05 +08:00
parent c1172d7714
commit 02580cda0b
180 changed files with 3392 additions and 76 deletions

40
scripts/README.md Normal file
View File

@@ -0,0 +1,40 @@
# scripts 目录说明
日期2026-05-27
`scripts/` 当前按职责拆成三层,避免部署、验收和回归入口继续平铺混杂。
## 目录划分
- `scripts/deploy/`
- 部署、构建、远端环境拉起
- 例如:
- `build_local_image.sh`
- `deploy_tksea_portal.sh`
- `setup_remote43_patched_stack.sh`
- `scripts/acceptance/`
- 真实宿主验收、upstream 直探、artifact 安全化
- 例如:
- `real_host_acceptance.sh`
- `import_remote43_provider.sh`
- `check_deepseek_completion_split.sh`
- `scripts/test/`
- 脚本自身的回归与资产检查
- 例如:
- `test_real_host_scripts.sh`
- `test_tksea_portal_assets.sh`
## 放置规则
- 新增脚本前先判断它属于 `deploy``acceptance` 还是 `test`
- 需要被目标机直接消费的静态文件不要放这里,应放到 `deploy/`
- 真实验收产物不要放这里,应落到 `artifacts/`
## 常用入口
```bash
bash ./scripts/test/test_real_host_scripts.sh
bash ./scripts/test/test_tksea_portal_assets.sh
scripts/deploy/build_local_image.sh
bash ./scripts/acceptance/real_host_acceptance.sh
```

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python3
"""Helpers for redacting real-host acceptance artifacts."""
import hashlib
import json
import pathlib

View File

@@ -1,6 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
# 环境变量驱动,便于被不同验收 harness 复用。
require_var() {
local name="$1"
if [[ -z "${!name:-}" ]]; then

View File

@@ -1,6 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
# SQL 和缓存 key 组装 helper供 remote acceptance 脚本复用。
sql_escape_literal() {
local value="$1"
local squote="'"

View File

@@ -6,10 +6,10 @@ model_name="${2:?model_name required}"
env_var="${3:?env var required}"
key_file="${4:-}"
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
# shellcheck disable=SC1091
source "$ROOT_DIR/scripts/host_access_prep_lib.sh"
ARTIFACT_REDACTION_SCRIPT="$ROOT_DIR/scripts/artifact_redaction.py"
source "$ROOT_DIR/scripts/acceptance/host_access_prep_lib.sh"
ARTIFACT_REDACTION_SCRIPT="$ROOT_DIR/scripts/acceptance/artifact_redaction.py"
KEY="${KEY:-/home/long/下载/zjsea.pem}"
REMOTE="${REMOTE:-ubuntu@43.155.133.187}"
@@ -33,12 +33,12 @@ mkdir -p "$ART"
artifact_redact_key_json() {
local value="$1"
python3 "$ROOT_DIR/scripts/artifact_redaction.py" redact-key "$value"
python3 "$ROOT_DIR/scripts/acceptance/artifact_redaction.py" redact-key "$value"
}
artifact_redact_id() {
local value="$1"
python3 "$ROOT_DIR/scripts/artifact_redaction.py" redact-id "$value"
python3 "$ROOT_DIR/scripts/acceptance/artifact_redaction.py" redact-id "$value"
}
write_json_file() {
@@ -49,20 +49,20 @@ write_json_file() {
sanitize_headers_file() {
local path="$1"
python3 "$ROOT_DIR/scripts/artifact_redaction.py" sanitize-headers "$path" "$path"
python3 "$ROOT_DIR/scripts/acceptance/artifact_redaction.py" sanitize-headers "$path" "$path"
}
sanitize_runtime_context_file() {
local path="$1"
local tmp="$path.tmp"
python3 "$ROOT_DIR/scripts/artifact_redaction.py" sanitize-runtime-context "$path" "$tmp"
python3 "$ROOT_DIR/scripts/acceptance/artifact_redaction.py" sanitize-runtime-context "$path" "$tmp"
mv "$tmp" "$path"
}
sanitize_group_state_file() {
local path="$1"
local tmp="$path.tmp"
python3 "$ROOT_DIR/scripts/artifact_redaction.py" sanitize-group-state "$path" "$tmp"
python3 "$ROOT_DIR/scripts/acceptance/artifact_redaction.py" sanitize-group-state "$path" "$tmp"
mv "$tmp" "$path"
}

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python3
"""Normalize historical real-host artifacts into repo-safe form."""
import json
import pathlib
import shutil

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
ARTIFACT_DIR="${ARTIFACT_DIR:-$ROOT_DIR/artifacts/real-host-acceptance/$TIMESTAMP}"
DRY_RUN="${DRY_RUN:-0}"
@@ -47,7 +47,7 @@ save_json() {
artifact_redact_key_json() {
local value="$1"
python3 "$ROOT_DIR/scripts/artifact_redaction.py" redact-key "$value"
python3 "$ROOT_DIR/scripts/acceptance/artifact_redaction.py" redact-key "$value"
}
write_checklist_guide() {

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
IMAGE_TAG="${IMAGE_TAG:-sub2api-cn-relay-manager:local}"
BINARY_PATH="${BINARY_PATH:-$ROOT_DIR/bin/sub2api-cn-relay-manager}"

View File

@@ -0,0 +1,161 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
KEY="${KEY:-/home/long/下载/zjsea.pem}"
REMOTE="${REMOTE:-ubuntu@43.155.133.187}"
REMOTE_PORTAL_DIR="${REMOTE_PORTAL_DIR:-/var/www/sub2api-portal}"
REMOTE_NGINX_SITE="${REMOTE_NGINX_SITE:-/etc/nginx/sites-available/tksea}"
REMOTE_HOST_PORT="${REMOTE_HOST_PORT:-18169}"
LOCAL_PORTAL_INDEX="${LOCAL_PORTAL_INDEX:-$ROOT_DIR/deploy/tksea-portal/index.html}"
REMOTE_STAGE_DIR="${REMOTE_STAGE_DIR:-/tmp/sub2api-portal-deploy}"
DRY_RUN="${DRY_RUN:-0}"
die() {
echo "$*" >&2
exit 1
}
require_cmd() {
command -v "$1" >/dev/null 2>&1 || die "missing command: $1"
}
run_cmd() {
if [[ "$DRY_RUN" == "1" ]]; then
printf 'DRY_RUN:'
printf ' %q' "$@"
printf '\n'
return 0
fi
"$@"
}
ssh_remote() {
run_cmd ssh -i "$KEY" -o StrictHostKeyChecking=no "$REMOTE" "$@"
}
scp_remote() {
run_cmd scp -i "$KEY" -o StrictHostKeyChecking=no "$@"
}
main() {
require_cmd python3
require_cmd ssh
require_cmd scp
[[ -f "$LOCAL_PORTAL_INDEX" ]] || die "missing portal index: $LOCAL_PORTAL_INDEX"
if [[ "$DRY_RUN" != "1" ]]; then
[[ -f "$KEY" ]] || die "missing ssh key: $KEY"
fi
local tmpdir patch_file index_copy
tmpdir="$(mktemp -d)"
trap "rm -rf $(printf '%q' "$tmpdir")" EXIT
patch_file="$tmpdir/patch_tksea_portal_nginx.py"
index_copy="$tmpdir/index.html"
cp "$LOCAL_PORTAL_INDEX" "$index_copy"
cat > "$patch_file" <<EOF
from pathlib import Path
import re
import textwrap
path = Path(${REMOTE_NGINX_SITE@Q})
text = path.read_text()
block = textwrap.dedent("""\
location = /portal {
return 302 /portal/;
}
location = /kimi-portal {
return 302 /portal/;
}
# BEGIN sub2api-portal
location /portal/ {
alias ${REMOTE_PORTAL_DIR}/;
index index.html;
try_files \$uri \$uri/ /portal/index.html;
}
location /portal-proxy/ {
proxy_pass http://127.0.0.1:${REMOTE_HOST_PORT}/;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_http_version 1.1;
}
location /kimi-portal/ {
return 302 /portal/;
}
location /kimi-portal-proxy/ {
proxy_pass http://127.0.0.1:${REMOTE_HOST_PORT}/;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_http_version 1.1;
}
location /kimi/ {
proxy_pass http://127.0.0.1:${REMOTE_HOST_PORT}/;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_http_version 1.1;
}
location /kimi-v1/ {
proxy_pass http://127.0.0.1:${REMOTE_HOST_PORT}/v1/;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_http_version 1.1;
}
# END sub2api-portal
""")
patterns = [
re.compile(r"\\n\\s*location = /portal \\{.*?# END sub2api-portal\\n\\n", re.S),
re.compile(r"\\n\\s*location = /kimi-portal \\{.*?# END kimi-portal\\n\\n", re.S),
]
for pattern in patterns:
if pattern.search(text):
text = pattern.sub("\\n" + block + "\\n", text, count=1)
path.write_text(text)
raise SystemExit(0)
needle = "\\n location / {\\n proxy_pass http://127.0.0.1:"
index = text.rfind(needle)
if index == -1:
raise SystemExit("failed to locate sub.tksea.top root location block")
text = text[:index] + "\\n" + block + text[index:]
path.write_text(text)
EOF
ssh_remote "mkdir -p $(printf '%q' "$REMOTE_STAGE_DIR")"
scp_remote "$index_copy" "$REMOTE:$REMOTE_STAGE_DIR/index.html"
scp_remote "$patch_file" "$REMOTE:$REMOTE_STAGE_DIR/patch_tksea_portal_nginx.py"
ssh_remote "sudo install -d -m 755 $(printf '%q' "$REMOTE_PORTAL_DIR") && sudo cp $(printf '%q' "$REMOTE_STAGE_DIR/index.html") $(printf '%q' "$REMOTE_PORTAL_DIR/index.html") && sudo python3 $(printf '%q' "$REMOTE_STAGE_DIR/patch_tksea_portal_nginx.py") && sudo nginx -t && sudo systemctl reload nginx"
cat <<EOF
tksea portal deployed
remote: ${REMOTE}
portal url: https://sub.tksea.top/portal/
legacy url: https://sub.tksea.top/kimi-portal/
portal dir: ${REMOTE_PORTAL_DIR}
nginx site: ${REMOTE_NGINX_SITE}
EOF
}
main "$@"

View File

@@ -1,6 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
# remote43 patched stack 渲染 helper供部署脚本和测试脚本共享。
remote43_require_file() {
local path="$1"
local label="$2"

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
# shellcheck disable=SC1091
source "$ROOT_DIR/scripts/remote43_patched_stack_lib.sh"
source "$ROOT_DIR/scripts/deploy/remote43_patched_stack_lib.sh"
KEY="${KEY:-/home/long/下载/zjsea.pem}"
REMOTE="${REMOTE:-ubuntu@43.155.133.187}"
@@ -198,7 +198,7 @@ shared pack path: ${LOCAL_SHARED_PACK_DIR}
next:
1. 在另一终端运行: ${LOCAL_TUNNEL_SCRIPT}
2. 当前终端执行: set -a; source ${LOCAL_OPERATOR_ENV_FILE}; set +a
3. 再运行: bash ${ROOT_DIR}/scripts/import_remote43_provider.sh kimi-a7m kimi-k2.6 A7M_KIMI_API_KEY /path/to/keyfile
3. 再运行: bash ${ROOT_DIR}/scripts/acceptance/import_remote43_provider.sh kimi-a7m kimi-k2.6 A7M_KIMI_API_KEY /path/to/keyfile
EOF
}

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
fail() {
echo "FAIL: $*" >&2
@@ -26,7 +26,7 @@ assert_not_contains() {
run_test_build_subscription_access_prep_sql() {
# shellcheck disable=SC1091
source "$ROOT_DIR/scripts/host_access_prep_lib.sh"
source "$ROOT_DIR/scripts/acceptance/host_access_prep_lib.sh"
local sql
sql="$(build_subscription_access_prep_sql 42 'sk-test-123' 7 10 30 1 'hermes remote subscription validation')"
@@ -141,7 +141,7 @@ EOF
SUBSCRIPTION_USERS="42" \
SKIP_ROLLBACK="1" \
AFTER_IMPORT_HOOK_COMMAND='printf "%s\n" "$BATCH_ID:$BATCH_DETAIL_FILE:$ACCESS_MODE" > "$ARTIFACT_DIR/hook.txt"' \
"$ROOT_DIR/scripts/real_host_acceptance.sh" >"$stdout_file"
"$ROOT_DIR/scripts/acceptance/real_host_acceptance.sh" >"$stdout_file"
[[ -f "$hook_file" ]] || fail "after-import hook did not create $hook_file"
[[ -f "$guide_file" ]] || fail "artifact guide was not created"
@@ -244,7 +244,7 @@ EOF
UPSTREAM_BASE="https://upstream.example.com/v1" \
UPSTREAM_API_KEY="upstream-key" \
MODEL="deepseek-v4-flash" \
bash "$ROOT_DIR/scripts/check_deepseek_completion_split.sh" >"$stdout_file"
bash "$ROOT_DIR/scripts/acceptance/check_deepseek_completion_split.sh" >"$stdout_file"
[[ -f "$summary_file" ]] || fail "missing summary file: $summary_file"
local summary stdout_contents host_headers upstream_headers
@@ -501,7 +501,7 @@ EOF
SUBSCRIPTION_DAYS=30 \
MIN_BALANCE=10 \
SKIP_ROLLBACK=1 \
bash "$ROOT_DIR/scripts/import_remote43_provider.sh" deepseek gpt-4 UPSTREAM_KEY >/dev/null
bash "$ROOT_DIR/scripts/acceptance/import_remote43_provider.sh" deepseek gpt-4 UPSTREAM_KEY >/dev/null
[[ -f "$summary_file" ]] || fail "prep summary was not captured"
local prep_summary
@@ -618,7 +618,7 @@ EOF
{"access_closures":[{"DetailsJSON":"{\"requested_probe_api_key\":\"sk-raw-probe-20260523b\",\"subscription_users\":[\"crm-user\"]}"}]}
EOF
python3 "$ROOT_DIR/scripts/migrate_historical_artifacts.py" "$src_root" >/dev/null
python3 "$ROOT_DIR/scripts/acceptance/migrate_historical_artifacts.py" "$src_root" >/dev/null
local migrated_runtime migrated_key_source migrated_invalidation migrated_group_state headers_text summary_json semantic_json details_json
migrated_runtime="$(cat "$target_dir/01-runtime-context.json")"
@@ -654,7 +654,7 @@ EOF
run_test_remote43_patched_stack_renderers() {
# shellcheck disable=SC1091
source "$ROOT_DIR/scripts/remote43_patched_stack_lib.sh"
source "$ROOT_DIR/scripts/deploy/remote43_patched_stack_lib.sh"
local host_env crm_env bootstrap
host_env="$(render_remote43_host_env "stack-pg" "stack-redis" "db-pass" "sub2api" "admin@sub2api.local" "admin-pass" "jwt-secret" "totp-secret")"
@@ -733,7 +733,7 @@ run_test_setup_remote43_patched_stack_dry_run() {
LOCAL_TUNNEL_SCRIPT="$tunnel_script" \
REMOTE_ROOT="/home/ubuntu/test-stack" \
DRY_RUN=1 \
bash "$ROOT_DIR/scripts/setup_remote43_patched_stack.sh" >"$stdout_file"
bash "$ROOT_DIR/scripts/deploy/setup_remote43_patched_stack.sh" >"$stdout_file"
[[ -f "$operator_env" ]] || fail "operator env file was not created"
[[ -f "$tunnel_script" ]] || fail "tunnel script was not created"

View File

@@ -0,0 +1,54 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
HTML_FILE="$ROOT_DIR/deploy/tksea-portal/index.html"
NGINX_FILE="$ROOT_DIR/deploy/tksea-portal/nginx.sub.tksea.top.conf.example"
DEPLOY_SCRIPT="$ROOT_DIR/scripts/deploy/deploy_tksea_portal.sh"
fail() {
echo "FAIL: $*" >&2
exit 1
}
assert_contains_file() {
local file="$1"
local needle="$2"
if ! grep -Fq "$needle" "$file"; then
fail "expected [$needle] in $file"
fi
}
[[ -f "$HTML_FILE" ]] || fail "missing $HTML_FILE"
[[ -f "$NGINX_FILE" ]] || fail "missing $NGINX_FILE"
[[ -f "$DEPLOY_SCRIPT" ]] || fail "missing $DEPLOY_SCRIPT"
assert_contains_file "$HTML_FILE" "Sub2API 多模型接入中心"
assert_contains_file "$HTML_FILE" "https://sub.tksea.top/portal/"
assert_contains_file "$HTML_FILE" "/portal-proxy/api/v1"
assert_contains_file "$HTML_FILE" "localStorage.setItem"
assert_contains_file "$HTML_FILE" "/auth/me"
assert_contains_file "$HTML_FILE" "/groups/available"
assert_contains_file "$HTML_FILE" "/subscriptions"
assert_contains_file "$HTML_FILE" "/keys?page=1&page_size=20"
assert_contains_file "$HTML_FILE" "copy-existing-key-btn"
assert_contains_file "$HTML_FILE" "已有 Key"
assert_contains_file "$HTML_FILE" "showToast"
assert_contains_file "$HTML_FILE" "可立即使用"
assert_contains_file "$HTML_FILE" "需开通"
assert_contains_file "$HTML_FILE" "暂不推荐"
assert_contains_file "$HTML_FILE" "gpt-5.4"
assert_contains_file "$HTML_FILE" "MiniMax-M2.7-highspeed"
assert_contains_file "$HTML_FILE" "deepseek-chat"
assert_contains_file "$NGINX_FILE" "location = /portal"
assert_contains_file "$NGINX_FILE" "location = /kimi-portal"
assert_contains_file "$NGINX_FILE" "location /portal/"
assert_contains_file "$NGINX_FILE" "location /portal-proxy/"
assert_contains_file "$NGINX_FILE" "location /kimi-portal-proxy/"
assert_contains_file "$DEPLOY_SCRIPT" "portal url: https://sub.tksea.top/portal/"
assert_contains_file "$DEPLOY_SCRIPT" "REMOTE_PORTAL_DIR"
assert_contains_file "$DEPLOY_SCRIPT" "patch_tksea_portal_nginx.py"
echo "PASS: tksea portal assets look consistent"