Harden remote43 acceptance script
This commit is contained in:
@@ -15,10 +15,11 @@ REMOTE="${REMOTE:-ubuntu@43.155.133.187}"
|
||||
CRM_BASE="${CRM_BASE:-http://127.0.0.1:18088}"
|
||||
HOST_BASE="${HOST_BASE:-http://127.0.0.1:18087}"
|
||||
CRM_HOST_BASE="${CRM_HOST_BASE:-$HOST_BASE}"
|
||||
REMOTE_HOST_BASE="${REMOTE_HOST_BASE:-$CRM_HOST_BASE}"
|
||||
HOST_NAME="${HOST_NAME:-remote43-current-host}"
|
||||
REMOTE_HOST_ENV_FILE="${REMOTE_HOST_ENV_FILE:-/home/ubuntu/sub2api-host-validation-fresh-deepseek-20260519_115244/.env}"
|
||||
REMOTE_PG_CONTAINER="${REMOTE_PG_CONTAINER:-sub2api-relaymgr-pg}"
|
||||
REMOTE_REDIS_CONTAINER="${REMOTE_REDIS_CONTAINER:-sub2api-relaymgr-redis}"
|
||||
REMOTE_PG_CONTAINER="${REMOTE_PG_CONTAINER:-}"
|
||||
REMOTE_REDIS_CONTAINER="${REMOTE_REDIS_CONTAINER:-}"
|
||||
PACK_PATH="${PACK_PATH:-$ROOT_DIR/packs/openai-cn-pack}"
|
||||
ROOT="${ROOT:-$ROOT_DIR/artifacts/real-host-acceptance}"
|
||||
ART="${ART:-$ROOT/$(date +%Y%m%d_%H%M%S)_remote43_${provider_id}_key_import}"
|
||||
@@ -26,8 +27,6 @@ MIN_BALANCE="${MIN_BALANCE:-10}"
|
||||
SUBSCRIPTION_DAYS="${SUBSCRIPTION_DAYS:-30}"
|
||||
SUBSCRIPTION_NOTES="${SUBSCRIPTION_NOTES:-hermes remote subscription validation}"
|
||||
mkdir -p "$ART"
|
||||
REMOTE_PG_CONTAINER_Q="$(printf '%q' "$REMOTE_PG_CONTAINER")"
|
||||
REMOTE_REDIS_CONTAINER_Q="$(printf '%q' "$REMOTE_REDIS_CONTAINER")"
|
||||
|
||||
if [[ -n "$key_file" ]]; then
|
||||
upstream_key="$(tr -d '\r\n' < "$key_file")"
|
||||
@@ -60,6 +59,62 @@ ssh_cmd() {
|
||||
ssh -i "$KEY" -o StrictHostKeyChecking=no "$REMOTE" "$cmd"
|
||||
}
|
||||
|
||||
resolve_remote_host_runtime() {
|
||||
local remote_port host_containers
|
||||
remote_port="$(python3 - "$REMOTE_HOST_BASE" <<'PY'
|
||||
from urllib.parse import urlparse
|
||||
import sys
|
||||
|
||||
target = urlparse(sys.argv[1])
|
||||
if target.port is not None:
|
||||
print(target.port)
|
||||
elif target.scheme == 'https':
|
||||
print(443)
|
||||
else:
|
||||
print(80)
|
||||
PY
|
||||
)"
|
||||
host_containers="$(ssh_cmd "sudo -n docker ps --format '{{.Names}}\t{{.Ports}}'")"
|
||||
HOST_CONTAINERS="$host_containers" python3 - "$remote_port" <<'PY'
|
||||
import os
|
||||
import sys
|
||||
|
||||
port = sys.argv[1]
|
||||
rows = os.environ.get("HOST_CONTAINERS", "").splitlines()
|
||||
for row in rows:
|
||||
name, _, ports = row.partition('\t')
|
||||
if f":{port}->" not in ports:
|
||||
continue
|
||||
app = name.strip()
|
||||
if app.endswith("-app-1"):
|
||||
prefix = app[:-len("-app-1")]
|
||||
print(app)
|
||||
print(f"{prefix}-postgres-1")
|
||||
print(f"{prefix}-redis-1")
|
||||
raise SystemExit(0)
|
||||
if app.endswith("-app"):
|
||||
prefix = app[:-len("-app")]
|
||||
print(app)
|
||||
print(f"{prefix}-pg")
|
||||
print(f"{prefix}-redis")
|
||||
raise SystemExit(0)
|
||||
raise SystemExit(f"unable to derive target host containers from port {port}")
|
||||
PY
|
||||
}
|
||||
|
||||
if [[ -z "$REMOTE_PG_CONTAINER" || -z "$REMOTE_REDIS_CONTAINER" ]]; then
|
||||
mapfile -t resolved_remote_runtime < <(resolve_remote_host_runtime)
|
||||
if [[ ${#resolved_remote_runtime[@]} -lt 3 ]]; then
|
||||
echo "unable to resolve remote host runtime containers for $REMOTE_HOST_BASE" >&2
|
||||
exit 2
|
||||
fi
|
||||
REMOTE_PG_CONTAINER="${REMOTE_PG_CONTAINER:-${resolved_remote_runtime[1]}}"
|
||||
REMOTE_REDIS_CONTAINER="${REMOTE_REDIS_CONTAINER:-${resolved_remote_runtime[2]}}"
|
||||
fi
|
||||
|
||||
REMOTE_PG_CONTAINER_Q="$(printf '%q' "$REMOTE_PG_CONTAINER")"
|
||||
REMOTE_REDIS_CONTAINER_Q="$(printf '%q' "$REMOTE_REDIS_CONTAINER")"
|
||||
|
||||
build_managed_subscription_identity_json() {
|
||||
local selector="$1"
|
||||
local group_id="$2"
|
||||
@@ -130,7 +185,7 @@ from pathlib import Path
|
||||
import json, subprocess, sys
|
||||
|
||||
env_path = Path(${REMOTE_HOST_ENV_FILE@Q})
|
||||
host_base = ${HOST_BASE@Q}
|
||||
host_base = ${REMOTE_HOST_BASE@Q}
|
||||
vals = {}
|
||||
for line in env_path.read_text().splitlines():
|
||||
if '=' not in line:
|
||||
@@ -318,13 +373,14 @@ $(remote_pg_query "$create_user_sql")
|
||||
EOF
|
||||
fi
|
||||
|
||||
python3 - "$ART/01-runtime-context.json" "$CRM_BASE" "$HOST_BASE" "$CRM_HOST_BASE" "$provider_id" "$sub_uid" "$sub_key" <<'PY'
|
||||
python3 - "$ART/01-runtime-context.json" "$CRM_BASE" "$HOST_BASE" "$CRM_HOST_BASE" "$REMOTE_HOST_BASE" "$provider_id" "$sub_uid" "$sub_key" <<'PY'
|
||||
import json, sys, pathlib
|
||||
path, crm, host, crm_host, provider_id, sub_uid, sub_key = sys.argv[1:8]
|
||||
path, crm, host, crm_host, remote_host, provider_id, sub_uid, sub_key = sys.argv[1:9]
|
||||
pathlib.Path(path).write_text(json.dumps({
|
||||
'crm_base': crm,
|
||||
'host_base': host,
|
||||
'crm_host_base': crm_host,
|
||||
'remote_host_base': remote_host,
|
||||
'provider_id': provider_id,
|
||||
'subscription_user_id': sub_uid,
|
||||
'subscription_user_key_prefix': sub_key[:12],
|
||||
@@ -432,13 +488,14 @@ else
|
||||
remote_fetch_group_state "$subscription_group_id" "$sub_uid" "$sub_key" "$ART/08-subscription-group-state.json"
|
||||
fi
|
||||
|
||||
python3 - "$ART/01-runtime-context.json" "$CRM_BASE" "$HOST_BASE" "$CRM_HOST_BASE" "$provider_id" "$sub_uid" "$sub_key" "$subscription_group_id" "$admin_uid" "$managed_user_email" "$managed_probe_key" "$managed_user_id" <<'PY'
|
||||
python3 - "$ART/01-runtime-context.json" "$CRM_BASE" "$HOST_BASE" "$CRM_HOST_BASE" "$REMOTE_HOST_BASE" "$provider_id" "$sub_uid" "$sub_key" "$subscription_group_id" "$admin_uid" "$managed_user_email" "$managed_probe_key" "$managed_user_id" <<'PY'
|
||||
import json, sys, pathlib
|
||||
path, crm, host, crm_host, provider_id, sub_uid, sub_key, group_id, admin_uid, managed_user_email, managed_probe_key, managed_user_id = sys.argv[1:13]
|
||||
path, crm, host, crm_host, remote_host, provider_id, sub_uid, sub_key, group_id, admin_uid, managed_user_email, managed_probe_key, managed_user_id = sys.argv[1:14]
|
||||
pathlib.Path(path).write_text(json.dumps({
|
||||
'crm_base': crm,
|
||||
'host_base': host,
|
||||
'crm_host_base': crm_host,
|
||||
'remote_host_base': remote_host,
|
||||
'provider_id': provider_id,
|
||||
'subscription_user_id': sub_uid,
|
||||
'subscription_user_key_prefix': sub_key[:12],
|
||||
@@ -460,11 +517,11 @@ print(json.dumps({
|
||||
}, ensure_ascii=False))
|
||||
PY
|
||||
)"
|
||||
ssh_cmd "curl -sS -D /tmp/models_headers.txt -o /tmp/models_body.json -H 'Authorization: Bearer $managed_probe_key' $HOST_BASE/v1/models"
|
||||
ssh_cmd "curl -sS -D /tmp/models_headers.txt -o /tmp/models_body.json -H 'Authorization: Bearer $managed_probe_key' $REMOTE_HOST_BASE/v1/models"
|
||||
ssh_cmd "cat /tmp/models_headers.txt" > "$ART/09-models.headers.txt"
|
||||
ssh_cmd "cat /tmp/models_body.json" > "$ART/10-models.body.json"
|
||||
|
||||
ssh_cmd "curl -sS -D /tmp/chat_headers.txt -o /tmp/chat_body.json -H 'Authorization: Bearer $managed_probe_key' -H 'Content-Type: application/json' $HOST_BASE/v1/chat/completions -d $(printf %q "$probe_payload")"
|
||||
ssh_cmd "curl -sS -D /tmp/chat_headers.txt -o /tmp/chat_body.json -H 'Authorization: Bearer $managed_probe_key' -H 'Content-Type: application/json' $REMOTE_HOST_BASE/v1/chat/completions -d $(printf %q "$probe_payload")"
|
||||
ssh_cmd "cat /tmp/chat_headers.txt" > "$ART/11-chat.headers.txt"
|
||||
ssh_cmd "cat /tmp/chat_body.json" > "$ART/12-chat.body.json"
|
||||
|
||||
@@ -476,14 +533,21 @@ ssh_cmd "curl -sS -D /tmp/upstream_chat_headers.txt -o /tmp/upstream_chat_body.t
|
||||
ssh_cmd "cat /tmp/upstream_chat_headers.txt" > "$ART/19-upstream-chat.headers.txt"
|
||||
ssh_cmd "cat /tmp/upstream_chat_body.txt" > "$ART/20-upstream-chat.body.txt"
|
||||
|
||||
crm_curl_json GET "/api/providers/$provider_id/status" > "$ART/13-provider-status.json"
|
||||
crm_curl_json GET "/api/providers/$provider_id/access/status" > "$ART/14-access-status.json"
|
||||
provider_query_suffix="?host_id=$(python3 - "$HOST_NAME" <<'PY'
|
||||
import sys
|
||||
from urllib.parse import quote
|
||||
print(quote(sys.argv[1], safe=''))
|
||||
PY
|
||||
)"
|
||||
|
||||
crm_curl_json GET "/api/providers/$provider_id/status${provider_query_suffix}" > "$ART/13-provider-status.json"
|
||||
crm_curl_json GET "/api/providers/$provider_id/access/status${provider_query_suffix}" > "$ART/14-access-status.json"
|
||||
preview_payload="$(python3 - "$provider_id" <<'PY'
|
||||
import json, sys
|
||||
print(json.dumps({'provider_id': sys.argv[1], 'mode': 'subscription'}, ensure_ascii=False))
|
||||
PY
|
||||
)"
|
||||
crm_curl_json POST "/api/providers/$provider_id/access/preview" "$preview_payload" > "$ART/15-access-preview.json"
|
||||
crm_curl_json POST "/api/providers/$provider_id/access/preview${provider_query_suffix}" "$preview_payload" > "$ART/15-access-preview.json"
|
||||
crm_curl_json GET "/api/import-batches/$batch_id" > "$ART/16-batch-detail-final.json"
|
||||
|
||||
python3 - "$ART" "$provider_id" "$batch_id" "$subscription_group_id" "$model_name" <<'PY'
|
||||
|
||||
@@ -16,6 +16,14 @@ assert_contains() {
|
||||
fi
|
||||
}
|
||||
|
||||
assert_not_contains() {
|
||||
local haystack="$1"
|
||||
local needle="$2"
|
||||
if [[ "$haystack" == *"$needle"* ]]; then
|
||||
fail "expected to avoid [$needle] in [$haystack]"
|
||||
fi
|
||||
}
|
||||
|
||||
run_test_build_subscription_access_prep_sql() {
|
||||
# shellcheck disable=SC1091
|
||||
source "$ROOT_DIR/scripts/host_access_prep_lib.sh"
|
||||
@@ -314,13 +322,13 @@ case "$url" in
|
||||
*/api/import-batches/123)
|
||||
write_body '{"managed_resources":[{"ResourceType":"group","HostResourceID":"7","ResourceName":"DeepSeek 默认分组"}]}'
|
||||
;;
|
||||
*/api/providers/deepseek/status)
|
||||
*/api/providers/deepseek/status*)
|
||||
write_body '{"status":"ready"}'
|
||||
;;
|
||||
*/api/providers/deepseek/access/status)
|
||||
*/api/providers/deepseek/access/status*)
|
||||
write_body '{"latest_access_status":"subscription_ready"}'
|
||||
;;
|
||||
*/api/providers/deepseek/access/preview)
|
||||
*/api/providers/deepseek/access/preview*)
|
||||
write_body '{"available":true}'
|
||||
;;
|
||||
*)
|
||||
@@ -342,6 +350,9 @@ if [[ "$cmd" == *'***'* ]]; then
|
||||
exit 1
|
||||
fi
|
||||
case "$cmd" in
|
||||
"sudo -n docker ps --format '{{.Names}}\t{{.Ports}}'"*)
|
||||
printf '%s\n' 'sub2api-fresh-deepseek-20260519_115244-app-1 127.0.0.1:18093->8080/tcp'
|
||||
;;
|
||||
*"/api/v1/auth/login"*)
|
||||
printf '%s\n' 'host-bearer-token'
|
||||
;;
|
||||
@@ -425,10 +436,10 @@ fi
|
||||
*"/api/providers/deepseek/reconcile"*)
|
||||
printf '%s\n' '{"status":"in_sync"}'
|
||||
;;
|
||||
*"sudo -n docker exec -i fresh-pg psql -U sub2api -d sub2api -At -F ''"*)
|
||||
*"sudo -n docker exec -i sub2api-fresh-deepseek-20260519_115244-postgres-1 psql -U sub2api -d sub2api -At -F ''"*)
|
||||
printf '%s\n' '{"group_id":7,"subscription":{"status":"active"},"key":{"group_id":7}}'
|
||||
;;
|
||||
*"sudo -n docker exec -i fresh-pg psql -U sub2api -d sub2api"*)
|
||||
*"sudo -n docker exec -i sub2api-fresh-deepseek-20260519_115244-postgres-1 psql -U sub2api -d sub2api"*)
|
||||
CMD="$cmd" LOG_DIR="$log_dir" python3 - <<'PY'
|
||||
import base64, os, re, pathlib, sys
|
||||
cmd = os.environ['CMD']
|
||||
@@ -454,7 +465,7 @@ else:
|
||||
print('')
|
||||
PY
|
||||
;;
|
||||
*"sudo -n docker exec fresh-redis redis-cli DEL apikey:auth:"*" billing:balance:"*" billing:sub:"*":7"*)
|
||||
*"sudo -n docker exec sub2api-fresh-deepseek-20260519_115244-redis-1 redis-cli DEL apikey:auth:"*" billing:balance:"*" billing:sub:"*":7"*)
|
||||
printf '%s\n' '3'
|
||||
;;
|
||||
*)
|
||||
@@ -472,11 +483,10 @@ EOF
|
||||
CRM_BASE="http://127.0.0.1:18088" \
|
||||
HOST_BASE="http://127.0.0.1:18087" \
|
||||
CRM_HOST_BASE="http://127.0.0.1:18093" \
|
||||
REMOTE_HOST_BASE="http://127.0.0.1:18093" \
|
||||
ROOT="$artifact_dir/root" \
|
||||
ART="$artifact_dir/run" \
|
||||
PACK_PATH="$pack_dir" \
|
||||
REMOTE_PG_CONTAINER="fresh-pg" \
|
||||
REMOTE_REDIS_CONTAINER="fresh-redis" \
|
||||
UPSTREAM_KEY="upstream-test-key" \
|
||||
SUBSCRIPTION_DAYS=30 \
|
||||
MIN_BALANCE=10 \
|
||||
@@ -493,6 +503,7 @@ EOF
|
||||
local runtime_context invalidation_log
|
||||
runtime_context="$(cat "$artifact_dir/run/01-runtime-context.json")"
|
||||
assert_contains "$runtime_context" '"crm_host_base": "http://127.0.0.1:18093"'
|
||||
assert_contains "$runtime_context" '"remote_host_base": "http://127.0.0.1:18093"'
|
||||
invalidation_log="$(cat "$artifact_dir/run/07-redis-targeted-invalidation.txt")"
|
||||
assert_contains "$invalidation_log" "auth_cache_key=apikey:auth:"
|
||||
assert_contains "$invalidation_log" "balance_cache_key=billing:balance:84"
|
||||
@@ -513,6 +524,13 @@ EOF
|
||||
assert_contains "$summary_json" '"upstream_models_has_expected_model": true'
|
||||
assert_contains "$summary_json" '"completion_classification": "unknown"'
|
||||
[[ -s "$ssh_log" ]] || fail "ssh log was empty"
|
||||
local ssh_contents
|
||||
ssh_contents="$(cat "$ssh_log")"
|
||||
assert_contains "$ssh_contents" "sudo -n docker ps --format"
|
||||
assert_contains "$ssh_contents" "http://127.0.0.1:18093/v1/models"
|
||||
assert_contains "$ssh_contents" "http://127.0.0.1:18093/v1/chat/completions"
|
||||
assert_not_contains "$ssh_contents" "http://127.0.0.1:18087/v1/models"
|
||||
assert_not_contains "$ssh_contents" "http://127.0.0.1:18087/v1/chat/completions"
|
||||
}
|
||||
|
||||
run_test_build_subscription_access_prep_sql
|
||||
|
||||
Reference in New Issue
Block a user