Community生產力與協作github.com

Cem-Tas96/claude-skills

個人 Claude Code 技能集,在 macOS 與 Windows 之間同步,存放於 ~/.claude/skills/。

claude-skills 是什麼?

這項技能(Gameboy-Gate)是一個強制性的部署關卡,適用於部署在名為「Gameboy」的 Hetzner CAX11 ARM 伺服器上的任何專案。它定義了一個完整的 8 階段流程,從伺服器資源快照、基於專案類型的 AI 驅動記憶體限制計算,到在 Coolify 資料庫中持久化設定、環境變數配置、Traefik 速率限制、檔案描述符限制、壓力測試,以及最終的 PASS/FAIL 判斷。此技能支援 Claude Code、Cursor 和 Codex 等平台,並透過讀取本機的 `gameboy.local.md` 檔案來保護敏感的連線資料。其核心價值在於防止因記憶體超額分配導致的容器 OOM 錯誤、部署回歸問題,並確保伺服器在 4GB RAM 的資源限制下能穩定運行。它同時也是資源管理的最佳實踐範例,可用於其他具備類似限制的邊緣伺服器場景。

相容平台✓Claude Code~Codex CLI~Cursor
npx skills add Cem-Tas96/claude-skills

Installed? Explore more 生產力與協作 skills: steipete/gemini, steipete/gh-issues, steipete/skill-creator · View all 6 →

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

🎮 Gameboy-Gate — 每個專案的入學考試

強制要求: 任何部署在 Gameboy(<GAMEBOY_IP>)上的專案都必須通過此關卡 — 無論誰使用 Claude,無論哪個專案。沒有 PASS 就沒有發佈。

🔒 連線資料在本機: <GAMEBOY_IP>、<GAMEBOY_SSH>、<COOLIFY_URL>、<WATCHDOG_*> 的真實值儲存在 gitignored 的 gameboy-gate/gameboy.local.md 檔案中(不在公開 repo 中)。每次執行關卡前,請先讀取此檔案並替換掉這些佔位符。

背景:Gameboy 伺服器

伺服器:Hetzner CAX11 (ARM)
IP:     <GAMEBOY_IP>          # 真實值:請見 gameboy.local.md
SSH:    <GAMEBOY_SSH>         # 真實值:請見 gameboy.local.md
RAM:    4GB 總計
CPU:    2x vCPU (ARM Neoverse-N1 @ 2.0GHz)
磁碟:   40GB SSD
OS:     Ubuntu (Docker + Coolify)
Coolify UI:<COOLIFY_URL>     # 真實值:請見 gameboy.local.md

固定開銷保留(絕不低於此值):

OS + 核心:              400MB
Coolify(含 DB、Redis、Proxy、Sentinel):900MB
安全緩衝區:            200MB
──────────────────────────────
可用於專案:           ~2500MB(總計 4096MB)

正在運行的專案(截至 2026年6月26日 — 真實名稱請見 gameboy.local.md):

  • app-workflow (Activepieces):1536MB 限制 / 768MB 保留
  • app-billing (Next.js):512MB 限制 / 256MB 保留
  • app-landing (靜態):256MB 限制 / 128MB 保留
  • 已佔用: 2304MB 限制 / 1152MB 保留

關卡流程(依此順序執行,不可跳過)

階段 G0:取得伺服器快照

總是先做這一步 — 這是伺服器當前狀態的真實反映:

<GAMEBOY_SSH> "
free -h &&
docker stats --no-stream --format 'table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.CPUPerc}}' &&
df -h / &&
docker inspect \$(docker ps -q) --format '{{.Name}}: mem={{.HostConfig.Memory}}' 2>/dev/null | sort
"

從中推導出:

  • TOTAL_RAM_MB(幾乎總是 3840MB = 3.7GB 以 bytes 計)
  • COOLIFY_OVERHEAD_MB = 所有 Coolify 自身容器目前總和(coolify、coolify-db、coolify-redis、coolify-sentinel、coolify-proxy)
  • RUNNING_PROJECTS_MB = 所有運行中專案容器的 mem_limit 總和
  • AVAILABLE_MB = TOTAL_RAM_MB - 400 (OS) - COOLIFY_OVERHEAD_MB - RUNNING_PROJECTS_MB - 200 (安全緩衝)

階段 G1:限制計算(AI 驅動,無硬編碼)

根據專案類型,AI 計算出正確的限制:

專案類型識別:
- Node.js / Next.js 應用程式     → BASE_MB = 350,每個 Worker 額外 50
- Python / Django / FastAPI       → BASE_MB = 200,每個 Worker 額外 30
- Go / Rust 服務                  → BASE_MB = 80,  每個 Worker 額外 20
- 靜態網站 (nginx/caddy)          → BASE_MB = 64,  每個 Worker 額外 0
- Postgres                         → BASE_MB = 128, 每個連線額外 10
- Redis                            → BASE_MB = 64,  每個 Worker 額外 0
- 背景 Worker (佇列)              → BASE_MB = 200, 每個 Worker 額外 50
- AI/LLM 服務(本機)             → BASE_MB = 800+ (若模型在本機,至少 2GB)

限制公式:

MEM_LIMIT_MB    = min(AVAILABLE_MB * 0.8, BASE_MB * 3)   -- 可用量的 80%,最多 3 倍 Base
MEM_RESERVATION = MEM_LIMIT_MB * 0.5                      -- 50% 作為保證最小值
MEM_SWAP_MB     = MEM_LIMIT_MB * 1.4                      -- 允許 40% 的 Swap 開銷
CPU_LIMIT       = round(2.0 * (MEM_LIMIT_MB / AVAILABLE_MB), 1)  -- 與 RAM 佔比成比例

NODE_OPTIONS_MB = floor(MEM_LIMIT_MB * 0.70)              -- V8 堆積 = 容器限制的 70%
UV_THREADPOOL_SIZE = min(8, cpu_count * 2)                -- libuv 執行緒數

在設定之前進行驗證檢查:

  • MEM_LIMIT_MB >= 128(啟動所需的最小值)
  • AVAILABLE_MB - MEM_LIMIT_MB >= 200(安全緩衝區仍存在)
  • 所有 mem_limit 總和 ≤ 3500MB(不超過實體 RAM 的 85%)
  • 如果驗證失敗 → 封鎖,通知 Cem

階段 G2:在 Coolify DB 中永久設定限制

這是防止部署回歸的關鍵修復。 Coolify 每次部署時都會從自己的資料庫重新產生 docker-compose.yaml。只有當限制存在於資料庫中時,它們才能在部署後持續存在。

如果知道應用程式的 UUID:

<GAMEBOY_SSH> "
docker exec coolify-db psql -U coolify -d coolify -c \"
UPDATE applications SET
  limits_memory = '<MEM_LIMIT_MB>m',
  limits_memory_swap = '<MEM_SWAP_MB>m',
  limits_memory_swappiness = 10,
  limits_memory_reservation = '<MEM_RESERVATION>m',
  limits_cpus = '<CPU_LIMIT>',
  limits_cpu_shares = 1024
WHERE uuid = '<APP_UUID>';
SELECT uuid, name, limits_memory, limits_cpus FROM applications WHERE uuid='<APP_UUID>';
\"
"

從 Coolify URL 取得 UUID:<COOLIFY_URL>/project/.../application/<UUID>/...

如果 UUID 未知 → 透過資料庫搜尋:

docker exec coolify-db psql -U coolify -d coolify -c "SELECT uuid, name FROM applications ORDER BY created_at DESC LIMIT 10;"

階段 G3:設定環境變數

在專案的 .env 中設定(透過 echo >> .env 或 Coolify UI):

# 對於 Node.js/Next.js:
NODE_OPTIONS=--max-old-space-size=<NODE_OPTIONS_MB>
UV_THREADPOOL_SIZE=<UV_THREADPOOL_SIZE>

# 對於工作流程應用程式(Activepieces 等):
AP_MAX_CONCURRENT_JOBS=5     # 防止沙箱爆炸
AP_TRIGGER_DEFAULT_POLL_INTERVAL=15  # 減少資料庫基準負載

# 對於所有應用程式:
# (生產環境中不要使用 DEBUG 日誌!)
NODE_ENV=production

重要:不要有換行錯誤! 檢查每個變數是否在自己的行上:

grep -c '^[A-Z_].*=' .env  # 應等於變數數量
# 除了值中,任何行都不能包含兩個 '='
awk -F= 'NF>2 && $1 ~ /^[A-Z_]/' .env && echo "錯誤:行中包含雙重 =!"

階段 G4:啟用 Traefik 速率限制

對於每個新的公開服務:

<GAMEBOY_SSH> "
cat > /data/coolify/proxy/dynamic/rate-limit-global.yaml << 'EOF'
http:
  middlewares:
    rate-limit-strict:
      rateLimit:
        average: 60
        burst: 120
        period: 1s
        sourceCriterion:
          ipStrategy:
            depth: 1
    rate-limit-api:
      rateLimit:
        average: 30
        burst: 60
        period: 1s
        sourceCriterion:
          ipStrategy:
            depth: 1
EOF
echo '速率限制配置已寫入'
"

Traefik 會自動拾取此配置(watch=true)— 無需重啟。

然後將 Middleware 加入應用程式的路由器(在應用程式特定的 Traefik 配置中):

middlewares:
  - rate-limit-strict

階段 G5:設定檔案描述符限制

Docker daemon 必須知道這些限制。每個 Gameboy 設定只需執行一次,之後就會持久存在:

<GAMEBOY_SSH> "
# 檢查是否已設定
grep -q 'nofile' /etc/docker/daemon.json && echo 'already set' || {
  # 備份並更新
  cp /etc/docker/daemon.json /etc/docker/daemon.json.bak.\$(date +%Y%m%d)
  python3 -c \"
import json
with open('/etc/docker/daemon.json') as f: d = json.load(f)
d['default-ulimits'] = {'nofile': {'Name': 'nofile', 'Hard': 65536, 'Soft': 65536}}
with open('/etc/docker/daemon.json', 'w') as f: json.dump(d, f, indent=2)
print('daemon.json updated')
  \"
  # 注意:systemctl restart docker 會重新啟動所有容器!
  # 僅在無活躍流量時進行 → 與 Cem 協調
  echo '需要:systemctl restart docker(短暫停機!)'
}
"

階段 G6:壓力測試(真正的入學考試)

50 個並發使用者,持續 60 秒:

# 測試 1:基本可達性
curl -o /dev/null -s -w "HTTP=%{http_code} %{time_total}s\n" https://<APP_URL>/

# 測試 2:50 個並發 HTTP 請求(30 秒)
/usr/sbin/ab -n 3000 -c 50 -t 30 -r -k https://<APP_URL>/ 2>&1 | grep -E "Requests|Failed|Time per|50%|90%|99%"

# 測試 3:在負載期間監控記憶體
<GAMEBOY_SSH> "for i in \$(seq 1 6); do sleep 10; docker stats --no-stream --format '{{.Name}}: {{.MemUsage}} {{.MemPerc}} CPU={{.CPUPerc}}' | grep -v NAMES; echo '---'; done" &
MONITOR_PID=\$!
/usr/sbin/ab -n 5000 -c 50 -t 55 -r https://<APP_URL>/ 2>/dev/null
wait \$MONITOR_PID

階段 G7:PASS/FAIL 標準

PASS(必須滿足所有條件):

標準閾值
HTTP 狀態2xx 或 3xx(無 502/503/504)
p99 延遲< 3000ms
記憶體增長穩定(在 t=0 和 t=60s 之間 < +20%)
記憶體低於限制< 容器限制的 90%
無失敗請求< 0.1%
測試後伺服器可達所有 URL 返回 HTTP 200/302
測試後 CPU< 80% 持續

FAIL(只要一個就封鎖):

  • 測試後有任何容器宕機或重啟
  • 記憶體達到限制(> 95%)
  • 失敗請求 > 1%
  • p99 > 5000ms
  • Watchdog 在測試期間介入(檢查:<WATCHDOG_LOG>)

階段 G8:關卡結果

═══════════════════════════════════════════════════════
🎮 GAMEBOY-GATE 結果
═══════════════════════════════════════════════════════
專案:     [名稱]
App-UUID:    [Coolify UUID]
測試時間:   [日期/時間]

限制計算:
  伺服器 RAM:              3840MB
  Coolify 開銷:        [n]MB(已測量)
  其他專案:         [n]MB(限制總和)
  可用:               [n]MB
  已分配:              MEM=[n]m,CPU=[n],NODE_OPTIONS=[n]MB

檢查:
  ✅/❌ Coolify DB 限制已持久設定
  ✅/❌ NODE_OPTIONS 正確(無換行錯誤)
  ✅/❌ 速率限制已啟用
  ✅/❌ 檔案描述符限制 65536
  ✅/❌ Redis maxmemory 策略已設定(如果是 Redis)
  ✅/❌ 壓力測試 50c/30s 通過

壓力測試:
  請求/秒:    [n]     (PASS 當 > 10)
  p50:      [n]ms
  p99:      [n]ms   (PASS 當 < 3000ms)
  失敗:   [n]%    (PASS 當 < 0.1%)
  記憶體峰值: [n]MB   (PASS 當 < 限制的 90%)

═══════════════════════════════════════════════════════
判決: ✅ PASS — 允許發佈
       ❌ FAIL — 封鎖發佈([原因])
═══════════════════════════════════════════════════════

常見 FAIL 原因與修復

FAIL:NODE_OPTIONS 未設定

# 檢查:
docker exec <container> printenv NODE_OPTIONS
# 修復:
grep -n 'NODE_OPTIONS' .env  # 檢查換行錯誤
# 錯誤:HOST=0.0.0.0NODE_OPTIONS=... → 修復:
sed -i 's/HOST=0.0.0.0NODE_OPTIONS=/HOST=0.0.0.0\nNODE_OPTIONS=/' .env

FAIL:測試後容器 OOM

根本原因幾乎總是以下之一:

  1. NODE_OPTIONS 未設定 → V8 完全佔用容器 RAM
  2. 沙箱進程(AP、程式碼執行)沒有併發限制
  3. 應用程式碼中的記憶體洩漏(較少見)
# 檢查是否發生重啟:
docker inspect <container> --format '{{.RestartCount}}'
# 檢查是否發生 OOM 殺死:
dmesg | grep -i 'oom\|killed' | tail -10

FAIL:p99 > 3000ms

  1. Heavy API 端點沒有快取 → 實作 Redis 快取
  2. PG 連線池耗盡 → 增加 MAX_CONCURRENT_REQUESTS 或池大小
  3. 每個請求的 Forward-Auth 開銷 → 加入 JWT 快取

FAIL:失敗請求 > 0.1%

  1. Traefik 超時短於應用程式回應 → 增加 Traefik 超時
  2. FD 限制耗盡 → 步驟 G5 (ulimits)
  3. PG max_connections 耗盡 → 在 PG 中設定 max_connections=200

部署彈性:部署存活協議

每次 Coolify 部署都必須保證以下幾點,以防止伺服器崩潰:

每次部署前檢查:

<GAMEBOY_SSH> "
# 可用 RAM 必須 > 800MB
FREE=\$(free -m | awk '/^Mem:/{print \$7}')
echo "可用:\${FREE}MB"
[ \$FREE -lt 800 ] && echo '警告:部署 RAM 不足!' || echo 'RAM 部署 OK'
# 磁碟必須 > 5GB 可用(拉取映像需要空間)
DISK=\$(df / | awk 'NR==2{print \$4}')
echo "磁碟可用:\$((DISK/1024))MB"
[ \$DISK -lt 5242880 ] && echo '警告:磁碟空間不足!' || echo '磁碟 OK'
"

Coolify 在部署時做了什麼(以及我們需要確保什麼):

  1. Coolify 讀取資料庫 → 產生 docker-compose.yaml 帶有在資料庫中設定的限制(G2 → 持久 ✅)
  2. 容器停止 → 短暫停機
  3. 新容器啟動 → 健康檢查等待應用程式就緒
  4. Traefik 取得新的後端端點

健康檢查配置(在 Coolify UI 或直接在資料庫中):

docker exec coolify-db psql -U coolify -d coolify -c "
UPDATE applications SET
  health_check_enabled = true,
  health_check_path = '/api/v1/flags',  -- 或 '/', '/health', '/ping'
  health_check_port = '80',
  health_check_interval = 15,
  health_check_timeout = 5,
  health_check_retries = 3,
  health_check_start_period = 90  -- 重型應用程式 90 秒啟動時間
WHERE uuid = '<APP_UUID>';
"

記憶體預算表(截至 2026年6月26日)

GAMEBOY 4GB RAM — 預算概覽
════════════════════════════════════════════════════════════
系統/OS:              400MB (固定)
Coolify 堆疊:          ~950MB (coolify+db+redis+sentinel+proxy)
────────────────────────────────────────────────────────────
可用於專案: ~2650MB
════════════════════════════════════════════════════════════
app-workflow:          1536MB 限制 / 768MB 保留
app-billing:            512MB 限制 / 256MB 保留
app-landing:            256MB 限制 / 128MB 保留
────────────────────────────────────────────────────────────
仍可用:             ~346MB 限制空間
════════════════════════════════════════════════════════════

警告:Gameboy 的限制容量已佔用約 91%。
超過 300MB 的新專案需要重新平衡(縮小現有專案
或最佳化 Coolify 堆疊)。

如果新專案需要超過 200MB → 先檢查是否可以將 landing(256MB)減少到 128MB(靜態網站,只需要很少的 RAM)。


每次部署的檢查清單(快速參考)

部署前:
  [ ] 可用 RAM > 800MB
  [ ] 磁碟 > 5GB 可用
  [ ] Coolify DB 限制已設定(非 0!)
  [ ] .env 中的 NODE_OPTIONS 正確(無換行錯誤)
  [ ] 已為公開端點配置速率限制

部署後:
  [ ] 容器正在執行(docker ps → Up X seconds (healthy))
  [ ] URL 可達(HTTP 2xx/3xx)
  [ ] NODE_OPTIONS 生效(docker exec <c> printenv NODE_OPTIONS)
  [ ] 記憶體穩定(docker stats → 未達限制)
  [ ] 無 OOM 殺死(dmesg | grep -i oom | tail -5)

設定檔與路徑

所有技能都位於:~/.claude/skills/ 每個技能一個目錄。載入順序:IPO(Letter),例如此技能來自 ~/.claude/skills/01-gameboy-gate/SKILL.md

相關技能