Files
sub2api-cn-relay-manager/scripts/deploy/remote43_patched_stack_lib.sh
2026-05-27 09:39:05 +08:00

252 lines
6.9 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
set -euo pipefail
# remote43 patched stack 渲染 helper供部署脚本和测试脚本共享。
remote43_require_file() {
local path="$1"
local label="$2"
[[ -f "$path" ]] || {
echo "missing $label: $path" >&2
return 1
}
}
remote43_random_hex() {
local bytes="${1:?bytes required}"
python3 - "$bytes" <<'PY'
import secrets
import sys
print(secrets.token_hex(int(sys.argv[1])))
PY
}
remote43_write_env_file() {
local path="$1"
shift
: > "$path"
while [[ $# -gt 0 ]]; do
local key="$1"
local value="$2"
shift 2
case "$value" in
*$'\n'*)
echo "env value for $key must not contain newlines" >&2
return 1
;;
esac
printf '%s=%s\n' "$key" "$value" >> "$path"
done
}
render_remote43_host_env() {
local pg_container="$1"
local redis_container="$2"
local db_password="$3"
local db_name="$4"
local admin_email="$5"
local admin_password="$6"
local jwt_secret="$7"
local totp_key="$8"
cat <<EOF
AUTO_SETUP=true
DATABASE_HOST=$pg_container
DATABASE_PORT=5432
DATABASE_USER=sub2api
DATABASE_PASSWORD=$db_password
DATABASE_DBNAME=$db_name
REDIS_HOST=$redis_container
REDIS_PORT=6379
ADMIN_EMAIL=$admin_email
ADMIN_PASSWORD=$admin_password
JWT_SECRET=$jwt_secret
TOTP_ENCRYPTION_KEY=$totp_key
SECURITY_URL_ALLOWLIST_ENABLED=false
SECURITY_URL_ALLOWLIST_MODE=disabled
EOF
}
render_remote43_crm_env() {
local crm_port="$1"
local sqlite_dsn="$2"
local admin_token="$3"
local sqlite_dsn_q admin_token_q
printf -v sqlite_dsn_q '%q' "$sqlite_dsn"
printf -v admin_token_q '%q' "$admin_token"
cat <<EOF
SUB2API_CRM_LISTEN_ADDR=127.0.0.1:$crm_port
SUB2API_CRM_SQLITE_DSN=$sqlite_dsn_q
SUB2API_CRM_ADMIN_TOKEN=$admin_token_q
SUB2API_CRM_RECONCILE_WORKER_ENABLED=false
EOF
}
render_remote43_bootstrap_script() {
local remote_root="$1"
local host_env_file="$2"
local crm_env_file="$3"
local host_binary_name="$4"
local crm_binary_name="$5"
local data_dir="$6"
local crm_db_file="$7"
local crm_pid_file="$8"
local crm_log_file="$9"
local app_container="${10}"
local pg_container="${11}"
local redis_container="${12}"
local network_name="${13}"
local host_image="${14}"
local pg_image="${15}"
local redis_image="${16}"
local db_password="${17}"
local db_name="${18}"
local host_port="${19}"
local crm_port="${20}"
local host_container_port="${21}"
local remote_root_q host_env_q crm_env_q host_binary_q crm_binary_q
local data_dir_q crm_db_q crm_pid_q crm_log_q app_q pg_q redis_q
local network_q host_image_q pg_image_q redis_image_q db_password_q db_name_q
local host_port_q crm_port_q host_container_port_q
remote_root_q="$(printf '%q' "$remote_root")"
host_env_q="$(printf '%q' "$host_env_file")"
crm_env_q="$(printf '%q' "$crm_env_file")"
host_binary_q="$(printf '%q' "$host_binary_name")"
crm_binary_q="$(printf '%q' "$crm_binary_name")"
data_dir_q="$(printf '%q' "$data_dir")"
crm_db_q="$(printf '%q' "$crm_db_file")"
crm_pid_q="$(printf '%q' "$crm_pid_file")"
crm_log_q="$(printf '%q' "$crm_log_file")"
app_q="$(printf '%q' "$app_container")"
pg_q="$(printf '%q' "$pg_container")"
redis_q="$(printf '%q' "$redis_container")"
network_q="$(printf '%q' "$network_name")"
host_image_q="$(printf '%q' "$host_image")"
pg_image_q="$(printf '%q' "$pg_image")"
redis_image_q="$(printf '%q' "$redis_image")"
db_password_q="$(printf '%q' "$db_password")"
db_name_q="$(printf '%q' "$db_name")"
host_port_q="$(printf '%q' "$host_port")"
crm_port_q="$(printf '%q' "$crm_port")"
host_container_port_q="$(printf '%q' "$host_container_port")"
cat <<EOF
#!/usr/bin/env bash
set -euo pipefail
REMOTE_ROOT=$remote_root_q
HOST_ENV_FILE=$host_env_q
CRM_ENV_FILE=$crm_env_q
HOST_BINARY="\$REMOTE_ROOT/$host_binary_q"
CRM_BINARY="\$REMOTE_ROOT/$crm_binary_q"
DATA_DIR=$data_dir_q
CRM_DB_FILE=$crm_db_q
CRM_PID_FILE=$crm_pid_q
CRM_LOG_FILE=$crm_log_q
APP_CONTAINER=$app_q
PG_CONTAINER=$pg_q
REDIS_CONTAINER=$redis_q
NETWORK_NAME=$network_q
HOST_IMAGE=$host_image_q
PG_IMAGE=$pg_image_q
REDIS_IMAGE=$redis_image_q
DB_PASSWORD=$db_password_q
DB_NAME=$db_name_q
HOST_PORT=$host_port_q
CRM_PORT=$crm_port_q
HOST_CONTAINER_PORT=$host_container_port_q
mkdir -p "\$REMOTE_ROOT" "\$DATA_DIR"
chmod 755 "\$HOST_BINARY" "\$CRM_BINARY"
rm -f "\$DATA_DIR/install.lock" "\$DATA_DIR/config.yaml" "\$DATA_DIR/.installed"
rm -f "\$CRM_DB_FILE"
if [[ -f "\$CRM_PID_FILE" ]]; then
kill "\$(cat "\$CRM_PID_FILE")" >/dev/null 2>&1 || true
rm -f "\$CRM_PID_FILE"
fi
rm -f "\$CRM_LOG_FILE"
sudo -n docker rm -f "\$APP_CONTAINER" "\$PG_CONTAINER" "\$REDIS_CONTAINER" >/dev/null 2>&1 || true
sudo -n docker network inspect "\$NETWORK_NAME" >/dev/null 2>&1 || sudo -n docker network create "\$NETWORK_NAME" >/dev/null
sudo -n docker run -d --name "\$PG_CONTAINER" --network "\$NETWORK_NAME" \\
-e POSTGRES_USER=sub2api \\
-e POSTGRES_PASSWORD="\$DB_PASSWORD" \\
-e POSTGRES_DB="\$DB_NAME" \\
"\$PG_IMAGE" >/dev/null
sudo -n docker run -d --name "\$REDIS_CONTAINER" --network "\$NETWORK_NAME" \\
"\$REDIS_IMAGE" >/dev/null
sleep 10
sudo -n docker run -d --name "\$APP_CONTAINER" --network "\$NETWORK_NAME" \\
-p "127.0.0.1:\$HOST_PORT:\$HOST_CONTAINER_PORT" \\
--env-file "\$HOST_ENV_FILE" \\
-v "\$DATA_DIR:/app/data" \\
-v "\$HOST_BINARY:/app/sub2api:ro" \\
"\$HOST_IMAGE" /app/sub2api >/dev/null
python3 - "\$HOST_ENV_FILE" "\$HOST_PORT" <<'PY'
import json
import pathlib
import subprocess
import sys
import time
env_path = pathlib.Path(sys.argv[1])
host_port = sys.argv[2]
values = {}
for line in env_path.read_text(encoding='utf-8').splitlines():
if '=' not in line:
continue
key, value = line.split('=', 1)
values[key] = value
payload = json.dumps({
'email': values['ADMIN_EMAIL'],
'password': values['ADMIN_PASSWORD'],
'turnstile_token': '',
}, ensure_ascii=False)
url = f"http://127.0.0.1:{host_port}/api/v1/auth/login"
for _ in range(60):
result = subprocess.run(
['curl', '-fsS', '-H', 'Content-Type: application/json', '-X', 'POST', url, '-d', payload],
text=True,
capture_output=True,
)
if result.returncode == 0 and 'access_token' in result.stdout:
raise SystemExit(0)
time.sleep(2)
raise SystemExit(f'host login did not become ready on {url}')
PY
nohup bash -lc 'set -a; source "\$1"; set +a; exec "\$2"' _ "\$CRM_ENV_FILE" "\$CRM_BINARY" >"\$CRM_LOG_FILE" 2>&1 &
echo \$! > "\$CRM_PID_FILE"
python3 - "\$CRM_PORT" <<'PY'
import subprocess
import sys
import time
url = f"http://127.0.0.1:{sys.argv[1]}/healthz"
for _ in range(30):
result = subprocess.run(['curl', '-fsS', url], text=True, capture_output=True)
if result.returncode == 0 and result.stdout.strip() == 'ok':
raise SystemExit(0)
time.sleep(1)
raise SystemExit(f'crm healthz did not become ready on {url}')
PY
printf 'host_base=http://127.0.0.1:%s\n' "\$HOST_PORT"
printf 'crm_base=http://127.0.0.1:%s\n' "\$CRM_PORT"
printf 'remote_host_env=%s\n' "\$HOST_ENV_FILE"
printf 'crm_log=%s\n' "\$CRM_LOG_FILE"
EOF
}