- scripts/deploy/deploy_crm_only.sh: 单进程部署 sub2api-cn-relay-manager
CRM 控制面到 remote43,不依赖 sub2api host / PG / Redis 容器。
复用 scripts/deploy/remote43_patched_stack_lib.sh 的 env 渲染
(render_remote43_crm_env),render_crm_only_bootstrap 用 $\{VAR\} 占位符 +
sed 注入避开 set -u + unquoted-heredoc 边缘问题。
部署前先 kill 老进程 (再 scp 二进制) 避免 ELF overwrite 失败。
- docs/DEPLOYMENT.md: 加 '在线部署节点' 段,记录 stack / 端口 / 入口 / 验证。
- docs/EXECUTION_BOARD.md: 顶部加 'Latest Online Stack' 段。
- artifacts/online-deploy-20260602/: 本次真实部署的证据
- 01-local-build.txt: 本地 server 二进制 md5 + git head
- 02-remote-inspect.txt: 远端 process / port / db tables
- 03-crm-api-checks.txt: /healthz /api/packs /api/hosts /metrics 真实响应
- 04-portal-public.txt: sub.tksea.top 公共入口
- 05-quality-gates.txt: gofmt / vet / test -race / integration
- manifest.json: 结构化汇总
验证(2026-06-02 21:32-21:43):
- /healthz: HTTP 200 'ok'
- /api/packs (Bearer): HTTP 200 '{"packs":[]}'
- /api/hosts (Bearer): HTTP 200 '{"hosts":[]}'
- /api/packs (no auth): HTTP 401
- /metrics (Prometheus): HTTP 200,含 active_hosts/active_providers/
db_connections_active + Go runtime
- sub.tksea.top/portal/: HTTP 200
- sub.tksea.top/portal-admin-api/healthz: HTTP 200 'ok'(反代到 CRM)
- go test -race ./internal/... ./tests/integration/...: PASS
- gofmt / go vet: 干净
221 lines
7.9 KiB
Bash
221 lines
7.9 KiB
Bash
#!/usr/bin/env bash
|
||
# deploy_crm_only.sh — 把 sub2api-cn-relay-manager(CRM 控制面)单进程部署到
|
||
# remote43。不起 sub2api host / PG / Redis 容器。
|
||
#
|
||
# 复用 scripts/deploy/remote43_patched_stack_lib.sh 的 env 渲染 helper。
|
||
|
||
set -euo pipefail
|
||
|
||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||
# shellcheck disable=SC1091
|
||
source "$ROOT_DIR/scripts/deploy/remote43_patched_stack_lib.sh"
|
||
|
||
KEY="${KEY:-/home/long/下载/zjsea.pem}"
|
||
REMOTE="${REMOTE:-ubuntu@43.155.133.187}"
|
||
STACK_NAME="${STACK_NAME:-crm-only-$(date +%Y%m%d)}"
|
||
CRM_PORT="${CRM_PORT:-18190}"
|
||
CRM_BINARY="${CRM_BINARY:-$ROOT_DIR/server}"
|
||
LOCAL_REPO_BUNDLE="${LOCAL_REPO_BUNDLE:-/tmp/${STACK_NAME}-repo.bundle}"
|
||
LOCAL_OPERATOR_ENV_FILE="${LOCAL_OPERATOR_ENV_FILE:-/tmp/${STACK_NAME}.env}"
|
||
LOCAL_TUNNEL_SCRIPT="${LOCAL_TUNNEL_SCRIPT:-/tmp/${STACK_NAME}.tunnel.sh}"
|
||
LOCAL_DEPLOY_DIR="${LOCAL_DEPLOY_DIR:-/tmp/${STACK_NAME}-stage}"
|
||
|
||
REMOTE_ROOT="${REMOTE_ROOT:-/home/ubuntu/${STACK_NAME}_${CRM_PORT}}"
|
||
REMOTE_REPO_ROOT="${REMOTE_REPO_ROOT:-/home/ubuntu/sub2api-cn-relay-manager-git-current}"
|
||
REMOTE_REPO_BUNDLE="$REMOTE_ROOT/sub2api-cn-relay-manager.bundle"
|
||
REMOTE_CRM_ENV_FILE="$REMOTE_ROOT/.env.crm"
|
||
REMOTE_BOOTSTRAP_FILE="$REMOTE_ROOT/bootstrap.sh"
|
||
REMOTE_CRM_BINARY="$REMOTE_ROOT/sub2api-cn-relay-manager-server"
|
||
REMOTE_CRM_DB_FILE="$REMOTE_ROOT/sub2api-cn-relay-manager.db"
|
||
REMOTE_CRM_PID_FILE="$REMOTE_ROOT/crm.pid"
|
||
REMOTE_CRM_LOG_FILE="$REMOTE_ROOT/crm.log"
|
||
|
||
crm_admin_token="${crm_admin_token:-$(remote43_random_hex 24)}"
|
||
crm_admin_username="${crm_admin_username:-admin}"
|
||
crm_admin_password="${crm_admin_password:-$crm_admin_token}"
|
||
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 "$@"; }
|
||
|
||
write_local_tunnel_script() {
|
||
cat > "$LOCAL_TUNNEL_SCRIPT" <<EOF
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
exec ssh -N \\
|
||
-L ${CRM_PORT}:127.0.0.1:${CRM_PORT} \\
|
||
-i $(printf "%q" "$KEY") \\
|
||
-o StrictHostKeyChecking=no \\
|
||
$(printf "%q" "$REMOTE")
|
||
EOF
|
||
chmod +x "$LOCAL_TUNNEL_SCRIPT"
|
||
}
|
||
|
||
write_operator_env() {
|
||
remote43_write_env_file "$LOCAL_OPERATOR_ENV_FILE" \
|
||
CRM_BASE "http://127.0.0.1:${CRM_PORT}" \
|
||
REMOTE_CRM_BASE "http://127.0.0.1:${CRM_PORT}" \
|
||
REMOTE_ROOT "$REMOTE_ROOT" \
|
||
REMOTE_CRM_ENV_FILE "$REMOTE_CRM_ENV_FILE" \
|
||
REMOTE_REPO_ROOT "$REMOTE_REPO_ROOT" \
|
||
KEY "$KEY" \
|
||
REMOTE "$REMOTE" \
|
||
crm_admin_token "$crm_admin_token" \
|
||
crm_admin_username "$crm_admin_username" \
|
||
crm_admin_password "$crm_admin_password" \
|
||
CRM_PORT "$CRM_PORT"
|
||
chmod 600 "$LOCAL_OPERATOR_ENV_FILE"
|
||
}
|
||
|
||
render_crm_only_bootstrap() {
|
||
local crm_env_q remote_root_q remote_repo_bundle_q
|
||
local crm_binary_q crm_db_q crm_pid_q crm_log_q remote_repo_root_q crm_port_q
|
||
printf -v crm_env_q "%q" "$REMOTE_CRM_ENV_FILE"
|
||
printf -v remote_root_q "%q" "$REMOTE_ROOT"
|
||
printf -v remote_repo_bundle_q "%q" "$REMOTE_REPO_BUNDLE"
|
||
printf -v crm_binary_q "%q" "$REMOTE_CRM_BINARY"
|
||
printf -v crm_db_q "%q" "$REMOTE_CRM_DB_FILE"
|
||
printf -v crm_pid_q "%q" "$REMOTE_CRM_PID_FILE"
|
||
printf -v crm_log_q "%q" "$REMOTE_CRM_LOG_FILE"
|
||
printf -v remote_repo_root_q "%q" "$REMOTE_REPO_ROOT"
|
||
printf -v crm_port_q "%q" "$CRM_PORT"
|
||
|
||
local tmp_bootstrap
|
||
tmp_bootstrap="$(mktemp)"
|
||
cat > "$tmp_bootstrap" <<'BOOTSTRAP_EOF'
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
export REMOTE_ROOT=__REMOTE_ROOT__
|
||
export CRM_ENV_FILE=__CRM_ENV_FILE__
|
||
export CRM_BINARY=__CRM_BINARY__
|
||
export CRM_DB_FILE=__CRM_DB_FILE__
|
||
export CRM_PID_FILE=__CRM_PID_FILE__
|
||
export CRM_LOG_FILE=__CRM_LOG_FILE__
|
||
export REMOTE_REPO_ROOT=__REMOTE_REPO_ROOT__
|
||
export REMOTE_REPO_BUNDLE=__REMOTE_REPO_BUNDLE__
|
||
export CRM_PORT=__CRM_PORT__
|
||
|
||
mkdir -p "$REMOTE_ROOT" "$(dirname "$REMOTE_REPO_ROOT")"
|
||
chmod 755 "$CRM_BINARY"
|
||
|
||
if [[ -f "$REMOTE_REPO_BUNDLE" ]]; then
|
||
if [[ -d "$REMOTE_REPO_ROOT/.git" ]]; then
|
||
git -C "$REMOTE_REPO_ROOT" fetch "$REMOTE_REPO_BUNDLE" main
|
||
git -C "$REMOTE_REPO_ROOT" reset --hard FETCH_HEAD
|
||
else
|
||
rm -rf "$REMOTE_REPO_ROOT"
|
||
git clone "$REMOTE_REPO_BUNDLE" "$REMOTE_REPO_ROOT"
|
||
git -C "$REMOTE_REPO_ROOT" checkout main
|
||
fi
|
||
git -C "$REMOTE_REPO_ROOT" config user.name "Remote43 CRM"
|
||
git -C "$REMOTE_REPO_ROOT" config user.email "remote43-crm@tksea.top"
|
||
fi
|
||
|
||
if [[ -f "$CRM_PID_FILE" ]]; then
|
||
OLD_PID="$(cat "$CRM_PID_FILE")"
|
||
if kill "$OLD_PID" >/dev/null 2>&1; then
|
||
sleep 1
|
||
fi
|
||
rm -f "$CRM_PID_FILE"
|
||
fi
|
||
rm -f "$CRM_DB_FILE" "$CRM_LOG_FILE"
|
||
|
||
nohup bash -lc 'set -a; source "$CRM_ENV_FILE"; set +a; exec "$CRM_BINARY"' >"$CRM_LOG_FILE" 2>&1 &
|
||
echo $! > "$CRM_PID_FILE"
|
||
|
||
python3 - "$CRM_PORT" <<'PY'
|
||
import subprocess, sys, time
|
||
url = f"http://127.0.0.1:{sys.argv[1]}/healthz"
|
||
for _ in range(30):
|
||
r = subprocess.run(["curl", "-fsS", url], text=True, capture_output=True)
|
||
if r.returncode == 0 and r.stdout.strip() == "ok":
|
||
raise SystemExit(0)
|
||
time.sleep(1)
|
||
raise SystemExit(f"crm healthz did not become ready on {url}")
|
||
PY
|
||
|
||
printf "crm_base=http://127.0.0.1:%s\n" "$CRM_PORT"
|
||
printf "crm_pid_file=%s\n" "$CRM_PID_FILE"
|
||
printf "crm_log=%s\n" "$CRM_LOG_FILE"
|
||
printf "remote_repo_root=%s\n" "$REMOTE_REPO_ROOT"
|
||
BOOTSTRAP_EOF
|
||
sed -i -e "s|__REMOTE_ROOT__|$remote_root_q|g" -e "s|__CRM_ENV_FILE__|$crm_env_q|g" -e "s|__CRM_BINARY__|$crm_binary_q|g" -e "s|__CRM_DB_FILE__|$crm_db_q|g" -e "s|__CRM_PID_FILE__|$crm_pid_q|g" -e "s|__CRM_LOG_FILE__|$crm_log_q|g" -e "s|__REMOTE_REPO_ROOT__|$remote_repo_root_q|g" -e "s|__REMOTE_REPO_BUNDLE__|$remote_repo_bundle_q|g" -e "s|__CRM_PORT__|$crm_port_q|g" "$tmp_bootstrap"
|
||
cat "$tmp_bootstrap"
|
||
rm -f "$tmp_bootstrap"
|
||
}
|
||
|
||
|
||
|
||
main() {
|
||
require_cmd bash curl git python3 ssh scp
|
||
remote43_require_file "$KEY" "ssh key"
|
||
remote43_require_file "$CRM_BINARY" "crm server binary"
|
||
|
||
rm -f "$LOCAL_REPO_BUNDLE"
|
||
git -C "$ROOT_DIR" bundle create "$LOCAL_REPO_BUNDLE" main
|
||
|
||
write_local_tunnel_script
|
||
write_operator_env
|
||
|
||
local crm_env_file bootstrap_file
|
||
crm_env_file="$(mktemp)"
|
||
bootstrap_file="$(mktemp)"
|
||
trap "rm -f \"$crm_env_file\" \"$bootstrap_file\"" EXIT
|
||
|
||
render_remote43_crm_env \
|
||
"$CRM_PORT" \
|
||
"file:${REMOTE_CRM_DB_FILE}?_foreign_keys=on&_busy_timeout=5000" \
|
||
"$crm_admin_token" \
|
||
"$REMOTE_REPO_ROOT" \
|
||
"$crm_admin_username" \
|
||
"$crm_admin_password" > "$crm_env_file"
|
||
|
||
render_crm_only_bootstrap > "$bootstrap_file"
|
||
chmod +x "$bootstrap_file"
|
||
|
||
mkdir -p "$LOCAL_DEPLOY_DIR"
|
||
cp "$crm_env_file" "$LOCAL_DEPLOY_DIR/.env.crm"
|
||
cp "$bootstrap_file" "$LOCAL_DEPLOY_DIR/bootstrap.sh"
|
||
|
||
ssh_remote "mkdir -p $(printf "%q" "$REMOTE_ROOT")
|
||
if [[ -f $(printf "%q" "$REMOTE_CRM_PID_FILE") ]]; then
|
||
OLDPID=\$(cat $(printf "%q" "$REMOTE_CRM_PID_FILE"))
|
||
kill \$OLDPID 2>/dev/null || true
|
||
sleep 1
|
||
fi
|
||
rm -f $(printf "%q" "$REMOTE_CRM_PID_FILE") $(printf "%q" "$REMOTE_CRM_DB_FILE") $(printf "%q" "$REMOTE_CRM_LOG_FILE") $(printf "%q" "$REMOTE_CRM_BINARY")"
|
||
scp_remote "$CRM_BINARY" "$REMOTE:$REMOTE_CRM_BINARY"
|
||
scp_remote "$LOCAL_REPO_BUNDLE" "$REMOTE:$REMOTE_REPO_BUNDLE"
|
||
scp_remote "$crm_env_file" "$REMOTE:$REMOTE_CRM_ENV_FILE"
|
||
scp_remote "$bootstrap_file" "$REMOTE:$REMOTE_BOOTSTRAP_FILE"
|
||
ssh_remote "bash $(printf "%q" "$REMOTE_BOOTSTRAP_FILE")"
|
||
|
||
cat <<EOF
|
||
crm-only stack prepared
|
||
remote crm base: http://127.0.0.1:${CRM_PORT}
|
||
remote crm env file: ${REMOTE_CRM_ENV_FILE}
|
||
remote crm log: ${REMOTE_CRM_LOG_FILE}
|
||
remote repo root: ${REMOTE_REPO_ROOT}
|
||
local operator env file: ${LOCAL_OPERATOR_ENV_FILE}
|
||
local tunnel script: ${LOCAL_TUNNEL_SCRIPT}
|
||
local deploy dir: ${LOCAL_DEPLOY_DIR}
|
||
|
||
next:
|
||
1. 在另一终端运行: ${LOCAL_TUNNEL_SCRIPT}
|
||
2. 当前终端执行: set -a; source ${LOCAL_OPERATOR_ENV_FILE}; set +a
|
||
3. 验证: curl -fsS http://127.0.0.1:${CRM_PORT}/healthz
|
||
curl -fsS -H "Authorization: Bearer \$crm_admin_token" http://127.0.0.1:${CRM_PORT}/api/packs
|
||
EOF
|
||
}
|
||
|
||
main "$@"
|