name: '๐Ÿ”„ ๅˆทๆ–ฐๆŽจ็†็ซฏ็‚น' # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• # AutoDL ๆŽจ็†ๆœบ็ซฏๅฃๅˆทๆ–ฐ ยท refresh-autodl-endpoint # ็ผ–ๅท: ZY-WF-REFRESH-AUTODL-ENDPOINT # ๅฎˆๆŠค: ้“ธๆธŠ ยท ICE-GL-ZY001 # ็‰ˆๆƒ: ๅ›ฝไฝœ็™ปๅญ—-2026-A-00037559 # # ่งฆๅ‘ๅœบๆ™ฏ: # AutoDL ๅฎžไพ‹ๆฏๆฌกๅผ€ๆœบ host:port ้ƒฝๆผ‚็งป. ๅ†ฐๆœ”/Awen ๅœจ AutoDL ๅผ€ # ๆœบๅŽ่ท‘ setup-inference.sh, ็„ถๅŽๆฅ่ฟ™้‡Œๅกซๆ–ฐ็š„ host:port + ่ฏฏ่งฆ # ้”ๅฃไปคใ€Œๅˆทๆ–ฐๆŽจ็†็ซฏ็‚นใ€, ๅทฅไฝœๆต่‡ชๅŠจ: # 1. check-secrets ๆ ก้ชŒ # 2. curl /v1/health ๆŽขๆดป (ไธ้€šๅฐฑไธๅ†™, ้ฟๅ…ๆฑกๆŸ“ 2C2G) # 3. SSH ๅˆฐ ZY-SVR-CN01 ๅ†™ inference-endpoint.json # 4. pm2 reload portal (็ƒญๅŠ ่ฝฝ) # 5. ไธญๆ–‡ๅ›žๆ‰ง # # ่ฏฏ่งฆ้”: confirm_phrase = ใ€Œๅˆทๆ–ฐๆŽจ็†็ซฏ็‚นใ€ # ๅ› ๆžœ้“พ: cc-003 (ๅŠจๆ€้€‚้…) + cc-004 (ไธญๆ–‡ไธ€ๆฌกๆ€ง) # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• on: workflow_dispatch: inputs: autodl_host: description: 'AutoDL ๆŽจ็†ๆœๅŠกๅ…ฌ็ฝ‘ host (ไพ‹: connect.westa.seetacloud.com ๆˆ– 8.131.xx.xx)' required: true type: string autodl_port: description: 'AutoDL ๆŽจ็†ๆœๅŠกๅ…ฌ็ฝ‘็ซฏๅฃ (ๆŽจ็†ๆœๅŠก HTTPS ๆšด้œฒ็ซฏๅฃ, ไธๆ˜ฏ SSH ็ซฏๅฃ)' required: true type: string use_https: description: 'ๅ่ฎฎ: https (ๆŽจ่) / http' required: true default: 'https' type: choice options: - 'https' - 'http' confirm_phrase: description: '่ฏฏ่งฆ้” ยท ๅฟ…้กป่พ“ๅ…ฅใ€Œๅˆทๆ–ฐๆŽจ็†็ซฏ็‚นใ€ๆ‰ไผš็œŸๅ†™, ๅ…ถไป–ๅ€ผ้™็บง dry-run' required: false default: '' type: string dry_run: description: 'ๅชๆ‰“ๅฐ่ฎกๅˆ’, ไธ็œŸๅ†™ๅŸŸๅๆœบ (true/false)' required: false default: 'false' type: choice options: - 'false' - 'true' permissions: contents: read concurrency: group: refresh-autodl-endpoint cancel-in-progress: false jobs: preflight: name: ๅฏๅŠจๅ‰้ข„ๆฃ€ ยท ๅฏ†้’ฅ + ่ฏฏ่งฆ้” + ๆŽขๆดป runs-on: ubuntu-latest timeout-minutes: 5 outputs: effective_dry_run: ${{ steps.misclick.outputs.effective_dry_run }} health_ok: ${{ steps.health.outputs.health_ok }} gpu_name: ${{ steps.health.outputs.gpu_name }} gpu_mem_gb: ${{ steps.health.outputs.gpu_mem_gb }} active_model: ${{ steps.health.outputs.active_model }} tier: ${{ steps.health.outputs.tier }} quant: ${{ steps.health.outputs.quant }} endpoint_url: ${{ steps.endpoint.outputs.endpoint_url }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' - name: ๅฏๅŠจๅ‰ๆœ€ๅฐๅฏ†้’ฅๆ ก้ชŒ (cn-domain-deploy) env: ZY_CN_SERVER_HOST: ${{ secrets.ZY_CN_SERVER_HOST }} ZY_CN_SERVER_USER: ${{ secrets.ZY_CN_SERVER_USER }} ZY_CN_SERVER_KEY: ${{ secrets.ZY_CN_SERVER_KEY }} ZY_CN_SERVER_PATH: ${{ secrets.ZY_CN_SERVER_PATH }} ZY_CN_SSH_PORT: ${{ secrets.ZY_CN_SSH_PORT }} run: | # ่ฟ™้‡Œๅชๆ ก้ชŒ ZY_CN_SERVER_*, AutoDL ็š„ host:port ๆ˜ฏ input ไธๆ˜ฏ secret node scripts/preflight/check-secrets.js \ --workflow cn-domain-deploy \ --stage update \ --target main - name: ่ฏฏ่งฆ้” ยท ๅฟ…้กป่พ“ๅ…ฅใ€Œๅˆทๆ–ฐๆŽจ็†็ซฏ็‚นใ€ id: misclick env: PHRASE: ${{ inputs.confirm_phrase }} DRY: ${{ inputs.dry_run }} run: | set -e EFFECTIVE_DRY="$DRY" REQUIRED="ๅˆทๆ–ฐๆŽจ็†็ซฏ็‚น" if [ "$PHRASE" != "$REQUIRED" ]; then echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" echo " โš ๏ธ ่ฏฏ่งฆไฟๆŠค" echo " ่ฆ็œŸๅ†™ 2C2G ๅŸŸๅๆœบ, ๅฟ…้กปๅœจ confirm_phrase" echo " ๅญ—ๆฎต็ฒพ็กฎ่พ“ๅ…ฅ: ใ€Œ$REQUIREDใ€" echo " ๅฝ“ๅ‰ๅ€ผ=ใ€Œ$PHRASEใ€ โ†’ ่‡ชๅŠจๅˆ‡ๅˆฐ dry-run." echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" EFFECTIVE_DRY="true" else echo "โœ… ๅทฒๆ˜พๅผ็กฎ่ฎค (phrase=ใ€Œ$PHRASEใ€), ๅณๅฐ†็œŸๅ†™" fi echo "effective_dry_run=$EFFECTIVE_DRY" >> "$GITHUB_OUTPUT" - name: ็ป„่ฃ…็ซฏ็‚น URL id: endpoint env: HOST: ${{ inputs.autodl_host }} PORT: ${{ inputs.autodl_port }} PROTO: ${{ inputs.use_https }} run: | set -euo pipefail # ็ฎ€ๆ˜“ๆ ผๅผๆ ก้ชŒ โ€” ้˜ฒๆญข Awen ่ฏฏ็ฒ˜ ssh ๅ‘ฝไปค่ฟ›ๆฅ if echo "$HOST" | grep -qE '[[:space:]]'; then echo "โŒ host ๅซ็ฉบ็™ฝๅญ—็ฌฆ, ่ฏทๅชๅกซ่ฃธ host (ไธ่ฆๅธฆ ssh -p ...)" >&2; exit 1 fi if ! echo "$PORT" | grep -qE '^[0-9]+$'; then echo "โŒ port ๅฟ…้กปๆ˜ฏ็บฏๆ•ฐๅญ—, ๅฝ“ๅ‰=ใ€Œ$PORTใ€" >&2; exit 1 fi if [ "$PORT" -lt 1 ] || [ "$PORT" -gt 65535 ]; then echo "โŒ port ่ถŠ็•Œ (1..65535), ๅฝ“ๅ‰=$PORT" >&2; exit 1 fi URL="${PROTO}://${HOST}:${PORT}" echo "endpoint_url=$URL" >> "$GITHUB_OUTPUT" echo "โœ… ็›ฎๆ ‡็ซฏ็‚น: $URL" - name: ๆŽขๆดป /v1/health (ไธ้€šๅฐฑไธๅพ€ไธ‹่ตฐ) id: health env: URL: ${{ steps.endpoint.outputs.endpoint_url }} run: | set -uo pipefail echo "[health] curl $URL/v1/health (้‡่ฏ• 3 ๆฌก, ๆฏๆฌก 10s ่ถ…ๆ—ถ)" OK="false" BODY="" for i in 1 2 3; do BODY="$(curl -fsS -m 10 "$URL/v1/health" 2>/dev/null || true)" if [ -n "$BODY" ] && echo "$BODY" | grep -q '"status"'; then OK="true" break fi echo " ็ฌฌ $i ๆฌกๅคฑ่ดฅ, 2s ๅŽ้‡่ฏ•..." sleep 2 done if [ "$OK" != "true" ]; then echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" echo " โŒ ๆŽจ็†ๆœๅŠกไธๅฏ่พพ: $URL/v1/health" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" echo " ๅธธ่งๅŽŸๅ› :" echo " 1. AutoDL ๅฎžไพ‹ๆฒกๅผ€ๆœบ / ่‡ชๅฎšไน‰ๆœๅŠก็ซฏๅฃๆฒกๆšด้œฒ" echo " 2. host:port ๆŠ„้”™ไบ† (ๆณจๆ„ๆ˜ฏๆŽจ็†ๆœๅŠก็ซฏๅฃ, ไธๆ˜ฏ SSH ็ซฏๅฃ)" echo " 3. setup-inference.sh ่ฟ˜ๆฒก่ท‘ๅฎŒ (ๅคงๆจกๅž‹ๅŠ ่ฝฝ้œ€่ฆ 60-120s)" echo " 4. ๅ่ฎฎ้”™: ๅฎž้™…ๅชๆœ‰ http ไฝ†ๅกซไบ† https (ๆˆ–ๅ่ฟ‡ๆฅ)" echo "" echo " ไธ‹ไธ€ๆญฅ: ๅ…ˆๅœจ AutoDL ไธŠ curl ๆœฌๆœบ http://127.0.0.1:8000/v1/health" echo " ็กฎ่ฎคๆœๅŠกๅœจ่ท‘, ๅ†ๆฅ้‡่ฏ•ๆœฌๅทฅไฝœๆต." echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" echo "health_ok=false" >> "$GITHUB_OUTPUT" exit 1 fi echo "$BODY" > /tmp/health.json GPU_NAME="$(echo "$BODY" | jq -r '.gpu.name // "unknown"')" GPU_MEM_GB="$(echo "$BODY" | jq -r '.gpu.memory_gb // "0"')" ACTIVE_MODEL="$(echo "$BODY" | jq -r '.model.name // "unknown"')" TIER="$(echo "$BODY" | jq -r '.tier.size_tier // "unknown"')" QUANT="$(echo "$BODY" | jq -r '.tier.quantization // "unknown"')" { echo "health_ok=true" echo "gpu_name=$GPU_NAME" echo "gpu_mem_gb=$GPU_MEM_GB" echo "active_model=$ACTIVE_MODEL" echo "tier=$TIER" echo "quant=$QUANT" } >> "$GITHUB_OUTPUT" echo "โœ… ๆŽจ็†ๆœๅŠกๅœจ่ท‘ ยท GPU=$GPU_NAME ยท ๅฝ“ๅ‰ๆจกๅž‹=$ACTIVE_MODEL ยท ๆกฃไฝ=$TIER/$QUANT" write-endpoint: name: ๅ†™ inference-endpoint.json + ้‡่ฝฝ portal needs: preflight if: needs.preflight.outputs.health_ok == 'true' runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v4 - name: ่ฃ…่ฝฝ SSH ๅ‡ญๆฎ (ๅ†™ๅˆฐ 2C2G ๅŸŸๅๆœบ) id: target env: HOST: ${{ secrets.ZY_CN_SERVER_HOST }} USER: ${{ secrets.ZY_CN_SERVER_USER }} KEY: ${{ secrets.ZY_CN_SERVER_KEY }} PORT: ${{ secrets.ZY_CN_SSH_PORT }} DEPLOY_PATH: ${{ secrets.ZY_CN_SERVER_PATH }} run: | set -euo pipefail if [ -z "$HOST" ] || [ -z "$KEY" ] || [ -z "$USER" ]; then echo "โŒ ZY_CN_SERVER_{HOST,USER,KEY} ไปปไธ€ไธบ็ฉบ" >&2; exit 1 fi PORT="${PORT:-22}" DEPLOY_PATH="${DEPLOY_PATH:-/data/guanghulab}" echo "user=$USER" >> "$GITHUB_OUTPUT" echo "port=$PORT" >> "$GITHUB_OUTPUT" echo "deploy_path=$DEPLOY_PATH" >> "$GITHUB_OUTPUT" umask 077 mkdir -p ~/.ssh printf '%s\n' "$KEY" > ~/.ssh/refresh_key chmod 600 ~/.ssh/refresh_key ssh-keyscan -p "$PORT" -H "$HOST" >> ~/.ssh/known_hosts 2>/dev/null || true printf '%s' "$HOST" > ~/.ssh/refresh_host chmod 600 ~/.ssh/refresh_host - name: ็ป„่ฃ… inference-endpoint.json env: URL: ${{ needs.preflight.outputs.endpoint_url }} GPU_NAME: ${{ needs.preflight.outputs.gpu_name }} GPU_MEM_GB: ${{ needs.preflight.outputs.gpu_mem_gb }} ACTIVE_MODEL: ${{ needs.preflight.outputs.active_model }} TIER: ${{ needs.preflight.outputs.tier }} QUANT: ${{ needs.preflight.outputs.quant }} RUN_ID: ${{ github.run_id }} run: | set -euo pipefail REFRESHED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)" # jq ๅฎ‰ๅ…จ็ป„่ฃ…, ้ฟๅ… shell ๆณจๅ…ฅ jq -n \ --arg url "$URL" \ --arg gpu_name "$GPU_NAME" \ --arg gpu_mem_gb "$GPU_MEM_GB" \ --arg active_model "$ACTIVE_MODEL" \ --arg tier "$TIER" \ --arg quant "$QUANT" \ --arg refreshed_at "$REFRESHED_AT" \ --arg run_id "$RUN_ID" \ '{ _sovereign: "TCS-0002โˆž ยท ๅ›ฝไฝœ็™ปๅญ—-2026-A-00037559", _ๅฎˆๆŠค: "้“ธๆธŠ ยท ICE-GL-ZY001", endpoint_url: $url, refreshed_at: $refreshed_at, refreshed_by_run_id: $run_id, gpu: { name: $gpu_name, memory_gb: $gpu_mem_gb }, tier: { size_tier: $tier, quantization: $quant }, active_model: $active_model }' > inference-endpoint.json echo "็ป„่ฃ…ๅฎŒๆˆ:" cat inference-endpoint.json - name: Dry-run ยท ๅชๆ‰“ๅฐ่ฎกๅˆ’ if: needs.preflight.outputs.effective_dry_run == 'true' env: USER: ${{ steps.target.outputs.user }} PORT: ${{ steps.target.outputs.port }} DEPLOY_PATH: ${{ steps.target.outputs.deploy_path }} run: | echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• DRY RUN โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" echo "would scp inference-endpoint.json โ†’ $USER@:$DEPLOY_PATH/portal/data/" echo "would ssh $USER@ 'sudo -u $USER mkdir -p $DEPLOY_PATH/portal/data && pm2 reload portal || true'" echo "(real host masked)" - name: ็œŸ่ท‘ ยท scp ๅ†™ๅ…ฅ + pm2 reload if: needs.preflight.outputs.effective_dry_run != 'true' env: USER: ${{ steps.target.outputs.user }} PORT: ${{ steps.target.outputs.port }} DEPLOY_PATH: ${{ steps.target.outputs.deploy_path }} run: | set -euo pipefail HOST="$(cat ~/.ssh/refresh_host)" # 1. ็กฎไฟ็›ฎๅฝ•ๅญ˜ๅœจ (PR-4 portal ไผš็”จ่ฟ™ไธช่ทฏๅพ„; PR-4 ไน‹ๅ‰ไนŸๅ…ˆๅปบๅฅฝ) ssh -i ~/.ssh/refresh_key -p "$PORT" \ -o StrictHostKeyChecking=accept-new \ "$USER@$HOST" \ "sudo mkdir -p $DEPLOY_PATH/portal/data && sudo chown -R $USER:$USER $DEPLOY_PATH/portal" # 2. scp ไธŠๅŽป scp -i ~/.ssh/refresh_key -P "$PORT" \ -o StrictHostKeyChecking=accept-new \ inference-endpoint.json \ "$USER@$HOST:$DEPLOY_PATH/portal/data/inference-endpoint.json" # 3. pm2 reload portal (PR-4 ไน‹ๅ‰ portal ไธๅญ˜ๅœจ, ๅ‘ฝไปคๅคฑ่ดฅไนŸ็ฎ—ๆˆๅŠŸ) ssh -i ~/.ssh/refresh_key -p "$PORT" \ -o StrictHostKeyChecking=accept-new \ "$USER@$HOST" \ "if pm2 describe portal >/dev/null 2>&1; then \ pm2 reload portal && echo 'โœ… pm2 reload portal ๆˆๅŠŸ'; \ else \ echo 'โ„น๏ธ portal ่ฟ˜ๆฒก่ตท (PR-4 ไน‹ๅ‰ๆญฃๅธธ), ๅทฒๅ†™ๅ…ฅ endpoint.json ็ญ‰ portal ไธŠ็บฟๅŽ่‡ชๅŠจ่ฏป'; \ fi" - name: ไธญๆ–‡ๅ›žๆ‰งๅˆฐ GitHub Actions Summary if: always() env: URL: ${{ needs.preflight.outputs.endpoint_url }} GPU_NAME: ${{ needs.preflight.outputs.gpu_name }} GPU_MEM_GB: ${{ needs.preflight.outputs.gpu_mem_gb }} ACTIVE_MODEL: ${{ needs.preflight.outputs.active_model }} TIER: ${{ needs.preflight.outputs.tier }} QUANT: ${{ needs.preflight.outputs.quant }} DRY: ${{ needs.preflight.outputs.effective_dry_run }} run: | { echo "# ๐Ÿ”„ ๆŽจ็†็ซฏ็‚นๅˆทๆ–ฐๅ›žๆ‰ง" echo "" echo "> ็ป™ๅ†ฐๆœ” / ้œœ็ š / Awen ็œ‹็š„ไธญๆ–‡ๅ›žๆ‰ง" echo "" if [ "$DRY" = "true" ]; then echo "## โš ๏ธ DRY RUN (ๆฒก็œŸๅ†™)" echo "" echo "่ฏฏ่งฆ้”ๅฃไปคๆฒกๅกซๅฏน, ๅทฅไฝœๆตๅชๆ‰“ๅฐ่ฎกๅˆ’, ๆฒก็œŸๅ†™ 2C2G ๅŸŸๅๆœบ." echo "" else echo "## โœ… ๅทฒๅˆ‡ๅˆฐๆ–ฐ็ซฏ็‚น" echo "" fi echo "| ้กน | ๅ€ผ |" echo "|---|---|" echo "| ๆŽจ็†็ซฏ็‚น | \`$URL\` |" echo "| GPU | $GPU_NAME ($GPU_MEM_GB GB) |" echo "| ๅฝ“ๅ‰ๆจกๅž‹ | $ACTIVE_MODEL |" echo "| ๆกฃไฝ | $TIER / $QUANT |" echo "| ๅˆทๆ–ฐๆ—ถ้—ด | $(date -u +%Y-%m-%dT%H:%M:%SZ) |" echo "" echo "ไธ‹ไธ€ๆญฅ: ๅ†ฐๆœ”ๆ‰“ๅผ€ \`https://guanghulab.com/portal\` ่ฏ•ไธ€ไธ‹, ็œ‹ๆจกๅž‹ๆœ‰ๆฒกๆœ‰ๅ“ๅบ”." echo "ๅฆ‚ๆžœ่ฟ˜ๆ–ญ, ็œ‹ \`/v1/health\`: \`$URL/v1/health\`" } >> "$GITHUB_STEP_SUMMARY" - name: ๆธ…็† SSH ็ง้’ฅ if: always() run: | rm -f ~/.ssh/refresh_key ~/.ssh/refresh_host