#!/usr/bin/env python3 from __future__ import annotations import base64 import json import os import time from typing import Callable, TypeVar from tencentcloud.common import credential from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException from tencentcloud.common.profile.client_profile import ClientProfile from tencentcloud.common.profile.http_profile import HttpProfile from tencentcloud.tat.v20201028 import models as tat_models, tat_client DEFAULT_INSTANCE_ID = os.environ.get("DEPLOY_INSTANCE_ID", "ins-ntmpcd3a") DEFAULT_REGION = os.environ.get("DEPLOY_REGION", "me-saudi-arabia") DEFAULT_REPO_URL = os.environ.get("MONITOR_REPO_URL", "git@gitea.haiyihy.com:hy/hy-app-monitor.git") DEFAULT_REPO_REF = os.environ.get("MONITOR_REPO_REF", "main") DEFAULT_REMOTE_DIR = os.environ.get("MONITOR_REMOTE_DIR", "/opt/hy-app-monitor") DEFAULT_APP_PORT = os.environ.get("APP_PORT", "6666") T = TypeVar("T") def env_required(name: str) -> str: value = os.environ.get(name, "").strip() if not value: raise SystemExit(f"missing env: {name}") return value def build_tat(secret_id: str, secret_key: str, region: str) -> tat_client.TatClient: cred = credential.Credential(secret_id, secret_key) return tat_client.TatClient( cred, region, ClientProfile(httpProfile=HttpProfile(endpoint="tat.tencentcloudapi.com")), ) def invoke(action: Callable[[], T], label: str, retries: int = 4, delay_seconds: int = 2) -> T: last_error: Exception | None = None for attempt in range(1, retries + 1): try: return action() except TencentCloudSDKException as exc: last_error = exc if attempt == retries: break time.sleep(delay_seconds * attempt) if last_error is not None: raise last_error raise RuntimeError(f"{label} failed without error") def remote_script(repo_url: str, repo_ref: str, remote_dir: str, app_port: str) -> str: return f"""set -euo pipefail REPO_URL={json.dumps(repo_url)} REPO_REF={json.dumps(repo_ref)} REMOTE_DIR={json.dumps(remote_dir)} APP_PORT={json.dumps(app_port)} export GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=accept-new' mkdir -p "$(dirname "$REMOTE_DIR")" if [ -d "$REMOTE_DIR/.git" ]; then git -C "$REMOTE_DIR" fetch --all --prune git -C "$REMOTE_DIR" checkout "$REPO_REF" git -C "$REMOTE_DIR" pull --ff-only origin "$REPO_REF" else rm -rf "$REMOTE_DIR" git clone --branch "$REPO_REF" "$REPO_URL" "$REMOTE_DIR" fi cd "$REMOTE_DIR" chmod +x scripts/*.sh APP_PORT="$APP_PORT" bash scripts/restart.sh sleep 1 ss -lntp | grep ":$APP_PORT" || true """ def run_tat_script(client: tat_client.TatClient, instance_id: str, script: str, timeout_seconds: int = 600) -> str: req = tat_models.RunCommandRequest() req.from_json_string( json.dumps( { "CommandName": "hy-app-monitor-deploy", "CommandType": "SHELL", "Content": base64.b64encode(script.encode("utf-8")).decode("ascii"), "InstanceIds": [instance_id], "Timeout": timeout_seconds, "WorkingDirectory": "/root", } ) ) resp = json.loads(invoke(lambda: client.RunCommand(req), "RunCommand").to_json_string()) invocation_id = resp["InvocationId"] deadline = time.time() + timeout_seconds + 120 while time.time() < deadline: time.sleep(3) query = tat_models.DescribeInvocationTasksRequest() query.from_json_string( json.dumps( { "HideOutput": False, "Limit": 20, "Filters": [{"Name": "invocation-id", "Values": [invocation_id]}], } ) ) data = json.loads(invoke(lambda: client.DescribeInvocationTasks(query), "DescribeInvocationTasks").to_json_string()) tasks = data.get("InvocationTaskSet") or [] if not tasks: continue task = tasks[0] status = task["TaskStatus"] if status in {"SUCCESS", "FAILED", "TIMEOUT", "CANCELLED", "START_FAILED"}: output = base64.b64decode(task.get("TaskResult", {}).get("Output", "")).decode("utf-8", errors="replace") if status != "SUCCESS": raise SystemExit(output or f"TAT task failed: {status}") return output raise SystemExit("TAT task timed out") def main() -> None: secret_id = env_required("TENCENT_SECRET_ID") secret_key = env_required("TENCENT_SECRET_KEY") instance_id = os.environ.get("DEPLOY_INSTANCE_ID", DEFAULT_INSTANCE_ID) region = os.environ.get("DEPLOY_REGION", DEFAULT_REGION) repo_url = os.environ.get("MONITOR_REPO_URL", DEFAULT_REPO_URL) repo_ref = os.environ.get("MONITOR_REPO_REF", DEFAULT_REPO_REF) remote_dir = os.environ.get("MONITOR_REMOTE_DIR", DEFAULT_REMOTE_DIR) app_port = os.environ.get("APP_PORT", DEFAULT_APP_PORT) tat = build_tat(secret_id, secret_key, region) output = run_tat_script(tat, instance_id, remote_script(repo_url, repo_ref, remote_dir, app_port)) print(output.strip()) if __name__ == "__main__": main()