hyapp-deploly/deploy/tencent_tat/hyapp_admin_server_deploy.py
2026-07-08 22:33:12 +08:00

231 lines
8.0 KiB
Python

#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
import os
import shlex
import sys
from pathlib import Path
from typing import Any
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from deploy.tencent_tat.hyapp_tat_deploy import build_tat_client, load_env_file, load_inventory, run_tat_shell
DEFAULT_REPO_URL = "git@gitea.haiyihy.com:hy/hyapp-server.git"
DEFAULT_SOURCE_DIR = "/opt/deploy/sources/hyapp-server"
DEFAULT_BRANCH = "main"
DEFAULT_ADMIN_REGION = "ap-guangzhou"
DEFAULT_ADMIN_INSTANCE_ID = "ins-0x5mjwmc"
DEFAULT_ADMIN_HOST = "134.175.160.86"
DEFAULT_ADMIN_BIND_ADDR = "172.16.0.6"
DEFAULT_ADMIN_PORT = 13100
def build_remote_script(args: argparse.Namespace) -> str:
dry_run = "1" if args.dry_run else "0"
return f"""#!/usr/bin/env bash
set -euo pipefail
REPO_URL={shlex.quote(args.repo_url)}
SOURCE_DIR={shlex.quote(args.source_dir)}
BRANCH={shlex.quote(args.branch)}
DRY_RUN={dry_run}
ADMIN_BIND_ADDR={shlex.quote(args.admin_bind_addr)}
ADMIN_PORT={shlex.quote(str(args.admin_port))}
export GIT_SSH_COMMAND="ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15"
export GOPROXY="${{GOPROXY:-https://goproxy.cn|https://proxy.golang.org|direct}}"
export GOSUMDB="${{GOSUMDB:-sum.golang.org}}"
echo "admin_host=$(hostname)"
echo "repo=$REPO_URL"
echo "source_dir=$SOURCE_DIR"
echo "branch=$BRANCH"
if [ "$DRY_RUN" = "1" ]; then
echo "git=$(git --version 2>/dev/null || true)"
echo "go=$(go version 2>/dev/null || true)"
target_commit="$(git ls-remote "$REPO_URL" "refs/heads/$BRANCH" | awk '{{print $1}}')"
if [ -z "$target_commit" ]; then
echo "cannot resolve origin/$BRANCH" >&2
exit 12
fi
echo "target_commit=$target_commit"
echo "admin_active=$(systemctl is-active hyapp-admin-server || true)"
ss -lntp | grep "$ADMIN_PORT" || true
curl -fsS -m 3 "http://$ADMIN_BIND_ADDR:$ADMIN_PORT/healthz" || true
echo
exit 0
fi
if ! command -v git >/dev/null 2>&1 || ! command -v go >/dev/null 2>&1; then
if command -v dnf >/dev/null 2>&1; then
dnf install -y git golang
elif command -v yum >/dev/null 2>&1; then
yum install -y git golang
else
echo "git/go missing and no supported package manager found" >&2
exit 13
fi
fi
if [ -d "$SOURCE_DIR/.git" ]; then
cd "$SOURCE_DIR"
git remote set-url origin "$REPO_URL"
else
mkdir -p "$(dirname "$SOURCE_DIR")"
git clone "$REPO_URL" "$SOURCE_DIR"
cd "$SOURCE_DIR"
fi
git fetch --prune origin "$BRANCH"
git checkout -B "$BRANCH" "origin/$BRANCH"
git reset --hard "origin/$BRANCH"
git clean -fd
commit="$(git rev-parse HEAD)"
short_commit="$(git rev-parse --short HEAD)"
build_binary="/tmp/admin-server-$short_commit"
echo "commit=$commit"
echo "go=$(go version)"
cd "$SOURCE_DIR/server/admin"
GOWORK=off go mod download
GOWORK=off CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o "$build_binary" ./cmd/server
chmod 0755 "$build_binary"
binary_sha="$(sha256sum "$build_binary" | awk '{{print $1}}')"
binary_size="$(stat -c %s "$build_binary")"
echo "binary_sha=$binary_sha"
echo "binary_size=$binary_size"
BIN=/opt/hyapp/admin-server/admin-server
BACKUP="$BIN.bak.$(date +%Y%m%d%H%M%S)"
cp "$BIN" "$BACKUP"
install -o root -g root -m 0755 "$build_binary" "$BIN.new"
mv "$BIN.new" "$BIN"
systemctl restart hyapp-admin-server
ready=0
for _ in $(seq 1 40); do
active="$(systemctl is-active hyapp-admin-server || true)"
if [ "$active" = "active" ] && ss -lntp | grep -q "$ADMIN_BIND_ADDR:$ADMIN_PORT"; then
if curl -fsS -m 3 "http://$ADMIN_BIND_ADDR:$ADMIN_PORT/healthz" >/tmp/hyapp-admin-healthz.out &&
curl -fsS -m 3 "http://$ADMIN_BIND_ADDR:$ADMIN_PORT/readyz" >/tmp/hyapp-admin-readyz.out; then
ready=1
break
fi
fi
sleep 2
done
if [ "$ready" != "1" ]; then
echo "admin-server verification failed; rollback to $BACKUP" >&2
cp "$BACKUP" "$BIN"
systemctl restart hyapp-admin-server || true
exit 22
fi
echo "backup=$BACKUP"
echo "active=$(systemctl is-active hyapp-admin-server || true)"
echo "installed_sha=$(sha256sum "$BIN" | awk '{{print $1}}')"
echo "listener=$(ss -lntp | grep "$ADMIN_BIND_ADDR:$ADMIN_PORT" || true)"
echo "healthz=$(cat /tmp/hyapp-admin-healthz.out)"
echo "readyz=$(cat /tmp/hyapp-admin-readyz.out)"
"""
def run_deploy(args: argparse.Namespace) -> dict[str, Any]:
inventory = load_inventory(args.inventory)
admin_inventory = {
"region": args.admin_region,
"deploy_server": {"instance_id": args.admin_instance_id},
"hosts": {"admin-server": {"instance_id": args.admin_instance_id, "private_ip": args.admin_bind_addr}},
"services": {
"admin-server": {
"unit": "hyapp-admin-server",
"container": "hyapp-admin-server",
"env_file": "/etc/hyapp/admin-server/config.yaml",
"target_port": args.admin_port,
"hosts": ["admin-server"],
}
},
}
previous_region = None
region_was_set = "TENCENTCLOUD_REGION" in os.environ
if region_was_set:
previous_region = os.environ["TENCENTCLOUD_REGION"]
os.environ["TENCENTCLOUD_REGION"] = args.admin_region
try:
client = build_tat_client(admin_inventory)
finally:
if region_was_set and previous_region is not None:
os.environ["TENCENTCLOUD_REGION"] = previous_region
elif not region_was_set:
os.environ.pop("TENCENTCLOUD_REGION", None)
script = build_remote_script(args)
result = run_tat_shell(
client,
instance_id=args.admin_instance_id,
script=script,
command_name="hyapp-admin-server-deploy",
timeout_seconds=args.timeout_seconds,
poll_seconds=int(inventory.get("tat_poll_seconds") or 2),
)
output = str(result.get("output") or "")
return {
"adminHost": args.admin_host,
"adminInstanceId": args.admin_instance_id,
"adminRegion": args.admin_region,
"dryRun": args.dry_run,
"ok": result.get("status") == "SUCCESS",
"output": output[-12000:],
"sourceDir": args.source_dir,
"status": result.get("status"),
"target": admin_inventory.get("deploy_server") or {},
}
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Deploy production admin-server by pulling Gitea origin/main on the Guangzhou admin host.")
parser.add_argument("action", choices=["deploy"])
parser.add_argument("--env-file", default="")
parser.add_argument("--inventory", default="deploy/tencent_tat/hyapp-services.inventory.prod.example.json")
parser.add_argument("--repo-url", default=DEFAULT_REPO_URL)
parser.add_argument("--source-dir", default=DEFAULT_SOURCE_DIR)
parser.add_argument("--branch", default=DEFAULT_BRANCH)
parser.add_argument("--admin-region", default=DEFAULT_ADMIN_REGION)
parser.add_argument("--admin-instance-id", default=DEFAULT_ADMIN_INSTANCE_ID)
parser.add_argument("--admin-host", default=DEFAULT_ADMIN_HOST)
parser.add_argument("--admin-bind-addr", default=DEFAULT_ADMIN_BIND_ADDR)
parser.add_argument("--admin-port", type=int, default=DEFAULT_ADMIN_PORT)
parser.add_argument("--timeout-seconds", type=int, default=1800)
parser.add_argument("--dry-run", action="store_true")
parser.add_argument("--yes", action="store_true")
return parser
def main() -> int:
parser = build_parser()
args = parser.parse_args()
try:
load_env_file(args.env_file)
if args.branch != DEFAULT_BRANCH:
raise RuntimeError("admin-server production deploy only supports origin/main")
if not args.dry_run and not args.yes:
raise RuntimeError("real execution requires --yes")
payload = run_deploy(args)
print(json.dumps(payload, ensure_ascii=False, indent=2))
return 0 if payload.get("ok") else 1
except Exception as exc: # noqa: BLE001
print(json.dumps({"error": str(exc), "ok": False}, ensure_ascii=False, indent=2))
return 1
if __name__ == "__main__":
raise SystemExit(main())