name: "๐Ÿ”ง ้“ธๆธŠ ยท PM2 ๆœๅŠก่ฏŠๆ–ญไธŽๅฅๅบทๆฃ€ๆŸฅ" # โ”โ”โ” PM2 ๆœๅŠก่ฏŠๆ–ญ + ๅ…จ็ณป็ปŸๅฅๅบทๆฃ€ๆŸฅ + Notion ๆŠฅๅ‘Š โ”โ”โ” # ๆ‰‹ๅŠจ่งฆๅ‘๏ผšๅฏนๆœๅŠกๅ™จ PM2 ่ฟ›็จ‹่ฟ›่กŒๅ…จ้ขๆŽ’ๆŸฅ๏ผŒ # ่‡ชๅŠจๅค„็† errored ่ฟ›็จ‹๏ผŒๅนถๅฐ†่ฏŠๆ–ญๆŠฅๅ‘Šๅ†™ๅ…ฅ Notion ๅทฅๅ•้˜Ÿๅˆ—ใ€‚ # # ๅฟ…้œ€ GitHub Secrets: # DEPLOY_HOST โ€” ๆœๅŠกๅ™จ IP # DEPLOY_USER โ€” SSH ็”จๆˆทๅ # DEPLOY_KEY โ€” SSH ็ง้’ฅ๏ผˆๅฎŒๆ•ด PEM ๅ†…ๅฎน๏ผ‰ # DEPLOY_PATH โ€” ็ฝ‘็ซ™ๆ น็›ฎๅฝ•๏ผˆๅฆ‚ /var/www/guanghulab๏ผ‰ # # ๅฏ้€‰ Secrets๏ผˆๆœ‰ๅˆ™่‡ชๅŠจๅ†™ Notion ๅทฅๅ•๏ผ‰: # NOTION_API_TOKEN โ€” Notion ้›†ๆˆ token # NOTION_TICKET_DB_ID โ€” ๅทฅๅ•้˜Ÿๅˆ—ๆ•ฐๆฎๅบ“ ID๏ผˆ้ป˜่ฎค 84edd0640bf146a9a5a7840107013e8c๏ผ‰ on: workflow_dispatch: inputs: auto_cleanup: description: 'ๆ˜ฏๅฆ่‡ชๅŠจๆธ…็† errored ่ฟ›็จ‹๏ผˆๅˆ ้™คๅฏๅŠจ่ทฏๅพ„ไธๅญ˜ๅœจ็š„่ฟ›็จ‹๏ผ‰' required: false default: 'true' type: choice options: - 'true' - 'false' permissions: contents: read jobs: diagnose: name: "๐Ÿ” PM2 ่ฏŠๆ–ญ + ็ณป็ปŸๅฅๅบทๆฃ€ๆŸฅ" runs-on: ubuntu-latest steps: - name: ๐Ÿ“ฅ ๆฃ€ๅ‡บไปฃ็  uses: actions/checkout@v4 - name: ๐ŸŸข ้…็ฝฎ Node.js uses: actions/setup-node@v4 with: node-version: "20" - name: ๐Ÿ”‘ ้…็ฝฎ SSH ่ฟžๆŽฅ run: | mkdir -p ~/.ssh echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/deploy_key chmod 600 ~/.ssh/deploy_key ssh-keyscan -H "${{ secrets.DEPLOY_HOST }}" >> ~/.ssh/known_hosts 2>/dev/null || { echo "โš ๏ธ ssh-keyscan ๅคฑ่ดฅ๏ผŒไฝฟ็”จ StrictHostKeyChecking=no ๅŽๅค‡" } # โ”โ”โ” ้˜ถๆฎตไธ€๏ผšPM2 ๆœๅŠก่ฏŠๆ–ญ โ”โ”โ” - name: "๐Ÿ”ง ้˜ถๆฎตไธ€ ยท PM2 errored ๆœๅŠกๆŽ’ๆŸฅ" id: pm2_diagnose run: | echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" echo " ้˜ถๆฎตไธ€ ยท PM2 errored ๆœๅŠกๆŽ’ๆŸฅ" echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" PM2_REPORT=$(ssh -i ~/.ssh/deploy_key \ -o StrictHostKeyChecking=no \ "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" ' echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" echo " PM2 ่ฟ›็จ‹็Šถๆ€ๆฆ‚่งˆ" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" pm2 status 2>/dev/null || echo "โŒ pm2 ๆœชๅฎ‰่ฃ…ๆˆ–ไธๅฏ็”จ" echo "" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" echo " errored ่ฟ›็จ‹่ฏฆ็ป†ๆ—ฅๅฟ—" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" # ้ๅކๆ‰€ๆœ‰ errored ่ฟ›็จ‹ ERRORED_LIST=$(pm2 jlist 2>/dev/null | python3 -c " import sys, json try: procs = json.load(sys.stdin) for p in procs: if p.get(\"pm2_env\", {}).get(\"status\") == \"errored\": name = p.get(\"name\", \"unknown\") pm_id = p.get(\"pm_id\", \"?\") script = p.get(\"pm2_env\", {}).get(\"pm_exec_path\", \"unknown\") restarts = p.get(\"pm2_env\", {}).get(\"restart_time\", 0) print(f\"{pm_id}|{name}|{script}|{restarts}\") except: pass " 2>/dev/null) if [ -z "$ERRORED_LIST" ]; then echo "โœ… ๆฒกๆœ‰ errored ่ฟ›็จ‹" else echo "$ERRORED_LIST" | while IFS="|" read -r pm_id name script restarts; do echo "" echo "โ”โ”โ” ่ฟ›็จ‹: $name (PM2 ID: $pm_id) โ”โ”โ”" echo " ๅฏๅŠจ่„šๆœฌ: $script" echo " ้‡ๅฏๆฌกๆ•ฐ: $restarts" # ๆฃ€ๆŸฅ่„šๆœฌๆ–‡ไปถๆ˜ฏๅฆๅญ˜ๅœจ if [ -f "$script" ]; then echo " ่„šๆœฌๆ–‡ไปถ: โœ… ๅญ˜ๅœจ" else echo " ่„šๆœฌๆ–‡ไปถ: โŒ ไธๅญ˜ๅœจ" fi # ๆŸฅ็œ‹ๆœ€่ฟ‘ๆ—ฅๅฟ— echo " ๆœ€่ฟ‘้”™่ฏฏๆ—ฅๅฟ— (ๆœ€ๅŽ 20 ่กŒ):" pm2 logs "$name" --lines 20 --nostream --err 2>/dev/null || echo " (ๆ— ๆณ•่ฏปๅ–ๆ—ฅๅฟ—)" echo "" done fi echo "" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" echo " ๆ‰€ๆœ‰่ฟ›็จ‹่ฏฆ็ป†ไฟกๆฏ" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" pm2 jlist 2>/dev/null | python3 -c " import sys, json try: procs = json.load(sys.stdin) for p in procs: env = p.get(\"pm2_env\", {}) print(f\" [{p.get(\"pm_id\")}] {p.get(\"name\")} | status={env.get(\"status\")} | script={env.get(\"pm_exec_path\",\"?\")} | restarts={env.get(\"restart_time\",0)} | memory={p.get(\"monit\",{}).get(\"memory\",0)}\") except Exception as e: print(f\" ่งฃๆžๅคฑ่ดฅ: {e}\") " 2>/dev/null echo "" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" echo " ็ซฏๅฃๅ ็”จๆƒ…ๅ†ต" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" netstat -tlnp 2>/dev/null | grep -E "3000|3001|3002|3721|8080" || \ ss -tlnp 2>/dev/null | grep -E "3000|3001|3002|3721|8080" || \ echo " (netstat/ss ไธๅฏ็”จ)" ') echo "$PM2_REPORT" # ไฟๅญ˜ๅˆฐๆ–‡ไปถไพ›ๅŽ็ปญๆญฅ้ชคไฝฟ็”จ echo "$PM2_REPORT" > /tmp/pm2-report.txt # ่ฎพ็ฝฎ่พ“ๅ‡บ { echo "pm2_report<> "$GITHUB_OUTPUT" # โ”โ”โ” ้˜ถๆฎตไธ€ ยท ่‡ชๅŠจๆธ…็† โ”โ”โ” - name: "๐Ÿงน ้˜ถๆฎตไธ€ ยท ่‡ชๅŠจๆธ…็† errored ่ฟ›็จ‹" if: ${{ inputs.auto_cleanup == 'true' }} id: pm2_cleanup run: | echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" echo " ่‡ชๅŠจๆธ…็† errored ่ฟ›็จ‹" echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" CLEANUP_REPORT=$(ssh -i ~/.ssh/deploy_key \ -o StrictHostKeyChecking=no \ "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" ' ERRORED_LIST=$(pm2 jlist 2>/dev/null | python3 -c " import sys, json try: procs = json.load(sys.stdin) for p in procs: if p.get(\"pm2_env\", {}).get(\"status\") == \"errored\": name = p.get(\"name\", \"unknown\") script = p.get(\"pm2_env\", {}).get(\"pm_exec_path\", \"unknown\") print(f\"{name}|{script}\") except: pass " 2>/dev/null) CLEANED="" SKIPPED="" if [ -z "$ERRORED_LIST" ]; then echo "โœ… ๆฒกๆœ‰้œ€่ฆๆธ…็†็š„ errored ่ฟ›็จ‹" else echo "$ERRORED_LIST" | while IFS="|" read -r name script; do if [ ! -f "$script" ]; then echo "๐Ÿ—‘๏ธ ๅˆ ้™ค $name๏ผˆ่„šๆœฌ $script ไธๅญ˜ๅœจ๏ผ‰" pm2 delete "$name" 2>/dev/null && echo " โœ… ๅทฒๅˆ ้™ค" || echo " โŒ ๅˆ ้™คๅคฑ่ดฅ" else echo "โš ๏ธ ไฟ็•™ $name๏ผˆ่„šๆœฌ $script ๅญ˜ๅœจ๏ผŒๅฏ่ƒฝๆ˜ฏ่ฟ่กŒๆ—ถ้”™่ฏฏ๏ผ‰" echo " ๅฐ่ฏ•้‡ๅฏ..." pm2 restart "$name" 2>/dev/null && echo " โœ… ้‡ๅฏๆˆๅŠŸ" || echo " โŒ ้‡ๅฏๅคฑ่ดฅ" fi done echo "" echo "๐Ÿ“Š ๆธ…็†ๅŽ PM2 ็Šถๆ€:" pm2 status 2>/dev/null pm2 save 2>/dev/null || echo "โš ๏ธ pm2 save ๅคฑ่ดฅ" fi ') echo "$CLEANUP_REPORT" echo "$CLEANUP_REPORT" > /tmp/cleanup-report.txt { echo "cleanup_report<> "$GITHUB_OUTPUT" # โ”โ”โ” ้˜ถๆฎตไบŒ๏ผšๅ…จ็ณป็ปŸๅฅๅบทๆฃ€ๆŸฅ โ”โ”โ” - name: "๐Ÿฅ ้˜ถๆฎตไบŒ ยท ๅ…จ็ณป็ปŸๅฅๅบทๆฃ€ๆŸฅ" id: health_check run: | echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" echo " ้˜ถๆฎตไบŒ ยท ๅ…จ็ณป็ปŸๅฅๅบทๆฃ€ๆŸฅ" echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" HEALTH_REPORT=$(ssh -i ~/.ssh/deploy_key \ -o StrictHostKeyChecking=no \ "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" ' echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" echo " Nginx ็Šถๆ€" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" nginx -t 2>&1 || echo "โŒ Nginx ้…็ฝฎๆฃ€ๆต‹ๅคฑ่ดฅ" systemctl status nginx --no-pager -l 2>/dev/null | head -15 || echo "โš ๏ธ systemctl ไธๅฏ็”จ" echo "" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" echo " SSL ่ฏไนฆๆœ‰ๆ•ˆๆœŸ" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" if [ -f "/etc/letsencrypt/live/guanghulab.com/fullchain.pem" ]; then openssl x509 -in /etc/letsencrypt/live/guanghulab.com/fullchain.pem -noout -dates 2>/dev/null || echo "โš ๏ธ ๆ— ๆณ•่ฏปๅ– SSL ่ฏไนฆ" else echo "โš ๏ธ SSL ่ฏไนฆๆ–‡ไปถไธๅญ˜ๅœจไบŽ้ป˜่ฎค่ทฏๅพ„" # ๅฐ่ฏ•ๆŸฅๆ‰พๅ…ถไป–่ฏไนฆ่ทฏๅพ„ find /etc/letsencrypt/live/ -name "fullchain.pem" 2>/dev/null | head -3 || echo " ๆœชๆ‰พๅˆฐ Let'\''s Encrypt ่ฏไนฆ" find /etc/nginx/ -name "*.pem" -o -name "*.crt" 2>/dev/null | head -3 || echo " ๆœชๆ‰พๅˆฐ Nginx SSL ๆ–‡ไปถ" fi echo "" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" echo " ็ฃ็›˜็ฉบ้—ด" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" df -h 2>/dev/null || echo "โŒ df ๅ‘ฝไปคไธๅฏ็”จ" echo "" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" echo " ๅ†…ๅญ˜ไฝฟ็”จ" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" free -h 2>/dev/null || echo "โŒ free ๅ‘ฝไปคไธๅฏ็”จ" echo "" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" echo " PM2 ๆœ€็ปˆ็Šถๆ€" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" pm2 status 2>/dev/null || echo "โŒ pm2 ไธๅฏ็”จ" echo "" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" echo " Node.js ็‰ˆๆœฌ" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" node -v 2>/dev/null || echo "โŒ Node.js ๆœชๅฎ‰่ฃ…" npm -v 2>/dev/null || echo "โŒ npm ๆœชๅฎ‰่ฃ…" echo "" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" echo " ็ฝ‘็ซ™ๅฏ่ฎฟ้—ฎๆ€งๆฃ€ๆŸฅ" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" curl -sI https://guanghulab.com 2>/dev/null | head -5 || echo "โš ๏ธ HTTPS ่ฎฟ้—ฎๅคฑ่ดฅ" echo "---" curl -s http://127.0.0.1:3000/health 2>/dev/null | head -c 200 || echo "โš ๏ธ ็ซฏๅฃ 3000 ๆ— ๅ“ๅบ”" echo "" echo "---" curl -s http://127.0.0.1:3001/api/v1/ 2>/dev/null | head -c 200 || echo "โš ๏ธ ็ซฏๅฃ 3001 ๆ— ๅ“ๅบ”" echo "" echo "---" curl -s http://127.0.0.1:3002/api/ps/ 2>/dev/null | head -c 200 || echo "โš ๏ธ ็ซฏๅฃ 3002 ๆ— ๅ“ๅบ”" echo "" echo "" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" echo " ็ณป็ปŸ่ฟ่กŒๆ—ถ้—ด" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" uptime 2>/dev/null || echo "โŒ uptime ไธๅฏ็”จ" ') echo "$HEALTH_REPORT" echo "$HEALTH_REPORT" > /tmp/health-report.txt { echo "health_report<> "$GITHUB_OUTPUT" # โ”โ”โ” ้˜ถๆฎตไธ‰๏ผš็”Ÿๆˆ Notion ่ฏŠๆ–ญๆŠฅๅ‘Š โ”โ”โ” - name: "๐Ÿ“‹ ้˜ถๆฎตไธ‰ ยท ๅ†™ๅ…ฅ Notion ๅทฅๅ•" env: NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }} NOTION_TICKET_DB_ID: ${{ secrets.NOTION_TICKET_DB_ID || '84edd0640bf146a9a5a7840107013e8c' }} PM2_REPORT: ${{ steps.pm2_diagnose.outputs.pm2_report }} CLEANUP_REPORT: ${{ steps.pm2_cleanup.outputs.cleanup_report }} HEALTH_REPORT: ${{ steps.health_check.outputs.health_report }} run: | if [ -z "$NOTION_TOKEN" ]; then echo "โš ๏ธ Notion credentials ๆœช้…็ฝฎ๏ผŒ่ทณ่ฟ‡ๅทฅๅ•ๅˆ›ๅปบ" echo "่ฏŠๆ–ญๆŠฅๅ‘Šไป…่พ“ๅ‡บๅˆฐ Actions ๆ—ฅๅฟ—" exit 0 fi node scripts/server-diagnose-report.js - name: "โœ… ่ฏŠๆ–ญๅฎŒๆˆ" run: | echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" echo " โœ… PM2 ่ฏŠๆ–ญๅฎŒๆฏ• ยท $(date '+%Y-%m-%d %H:%M:%S')" echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”"