fix(ci): guard gitea workflow secret surfaces
Some checks failed
Code Review / ai-code-review (push) Failing after 10s
Some checks failed
Code Review / ai-code-review (push) Failing after 10s
This commit is contained in:
@@ -87,11 +87,18 @@ jobs:
|
||||
echo "✅ API 測試通過"
|
||||
|
||||
- name: Login to Harbor
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.HARBOR }}
|
||||
username: ${{ secrets.HARBOR_USERNAME }}
|
||||
password: ${{ secrets.HARBOR_PASSWORD }}
|
||||
run: |
|
||||
HARBOR_USERNAME="$(cat <<'AWOOOI_SECRET_HARBOR_USERNAME'
|
||||
${{ secrets.HARBOR_USERNAME }}
|
||||
AWOOOI_SECRET_HARBOR_USERNAME
|
||||
)"
|
||||
HARBOR_PASSWORD="$(cat <<'AWOOOI_SECRET_HARBOR_PASSWORD'
|
||||
${{ secrets.HARBOR_PASSWORD }}
|
||||
AWOOOI_SECRET_HARBOR_PASSWORD
|
||||
)"
|
||||
printf '%s' "$HARBOR_PASSWORD" | docker login "${{ env.HARBOR }}" \
|
||||
-u "$HARBOR_USERNAME" \
|
||||
--password-stdin
|
||||
|
||||
# Dev API 鏡像:強制重建,不用 cache(確保 models.json 等配置文件更新)
|
||||
- name: Build and Push API (Dev)
|
||||
@@ -107,16 +114,37 @@ jobs:
|
||||
|
||||
# 注入 Dev K8s Secrets
|
||||
- name: Inject Dev K8s Secrets
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||
TG_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
TG_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
|
||||
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
||||
run: |
|
||||
secret_b64() {
|
||||
python3 -c 'import base64, sys; data=sys.stdin.buffer.read(); data=data[:-1] if data.endswith(b"\n") else data; sys.stdout.write(base64.b64encode(data).decode())'
|
||||
}
|
||||
write_deploy_key() {
|
||||
mkdir -p ~/.ssh
|
||||
umask 077
|
||||
cat > ~/.ssh/deploy_key <<'AWOOOI_DEPLOY_KEY'
|
||||
${{ secrets.DEPLOY_SSH_KEY }}
|
||||
AWOOOI_DEPLOY_KEY
|
||||
chmod 600 ~/.ssh/deploy_key
|
||||
}
|
||||
TG_BOT_TOKEN_B64="$(secret_b64 <<'AWOOOI_SECRET_TG_BOT_TOKEN'
|
||||
${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
AWOOOI_SECRET_TG_BOT_TOKEN
|
||||
)"
|
||||
TG_CHAT_ID_B64="$(secret_b64 <<'AWOOOI_SECRET_TG_CHAT_ID'
|
||||
${{ secrets.TELEGRAM_CHAT_ID }}
|
||||
AWOOOI_SECRET_TG_CHAT_ID
|
||||
)"
|
||||
NVIDIA_API_KEY_B64="$(secret_b64 <<'AWOOOI_SECRET_NVIDIA_API_KEY'
|
||||
${{ secrets.NVIDIA_API_KEY }}
|
||||
AWOOOI_SECRET_NVIDIA_API_KEY
|
||||
)"
|
||||
GEMINI_API_KEY_B64="$(secret_b64 <<'AWOOOI_SECRET_GEMINI_API_KEY'
|
||||
${{ secrets.GEMINI_API_KEY }}
|
||||
AWOOOI_SECRET_GEMINI_API_KEY
|
||||
)"
|
||||
|
||||
mkdir -p ~/.ssh
|
||||
echo "$SSH_PRIVATE_KEY" > ~/.ssh/deploy_key
|
||||
chmod 600 ~/.ssh/deploy_key
|
||||
write_deploy_key
|
||||
# 2026-05-05 Codex: kubectl runs on 120 control-plane. 121 is a
|
||||
# worker and its local kubeconfig points at 127.0.0.1:6443.
|
||||
ssh -o StrictHostKeyChecking=no -i ~/.ssh/deploy_key wooo@192.168.0.120 << SECRETS
|
||||
@@ -124,19 +152,19 @@ jobs:
|
||||
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
|
||||
|
||||
sudo kubectl patch secret awoooi-secrets -n awoooi-dev --type='json' -p='[
|
||||
{"op":"replace","path":"/data/OPENCLAW_TG_BOT_TOKEN","value":"'"$(echo -n "${TG_BOT_TOKEN}" | base64 -w 0)"'"},
|
||||
{"op":"replace","path":"/data/OPENCLAW_TG_CHAT_ID","value":"'"$(echo -n "${TG_CHAT_ID}" | base64 -w 0)"'"}
|
||||
{"op":"replace","path":"/data/OPENCLAW_TG_BOT_TOKEN","value":"${TG_BOT_TOKEN_B64}"},
|
||||
{"op":"replace","path":"/data/OPENCLAW_TG_CHAT_ID","value":"${TG_CHAT_ID_B64}"}
|
||||
]' || echo "⚠️ Telegram Secrets patch 跳過"
|
||||
|
||||
if [ -n "${NVIDIA_API_KEY}" ]; then
|
||||
if [ -n "${NVIDIA_API_KEY_B64}" ]; then
|
||||
sudo kubectl patch secret awoooi-secrets -n awoooi-dev --type='json' -p='[
|
||||
{"op":"replace","path":"/data/NVIDIA_API_KEY","value":"'"$(echo -n "${NVIDIA_API_KEY}" | base64 -w 0)"'"}
|
||||
{"op":"replace","path":"/data/NVIDIA_API_KEY","value":"${NVIDIA_API_KEY_B64}"}
|
||||
]' && echo "✅ NVIDIA_API_KEY 已注入 dev"
|
||||
fi
|
||||
|
||||
if [ -n "${GEMINI_API_KEY}" ]; then
|
||||
if [ -n "${GEMINI_API_KEY_B64}" ]; then
|
||||
sudo kubectl patch secret awoooi-secrets -n awoooi-dev --type='json' -p='[
|
||||
{"op":"replace","path":"/data/GEMINI_API_KEY","value":"'"$(echo -n "${GEMINI_API_KEY}" | base64 -w 0)"'"}
|
||||
{"op":"replace","path":"/data/GEMINI_API_KEY","value":"${GEMINI_API_KEY_B64}"}
|
||||
]' && echo "✅ GEMINI_API_KEY 已注入 dev"
|
||||
fi
|
||||
|
||||
@@ -145,8 +173,6 @@ jobs:
|
||||
|
||||
# 部署到 awoooi-dev
|
||||
- name: Deploy to Dev K8s
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||
run: |
|
||||
cat k8s/awoooi-dev/02-configmap.yaml | \
|
||||
ssh -o StrictHostKeyChecking=no -i ~/.ssh/deploy_key wooo@192.168.0.120 \
|
||||
|
||||
@@ -76,6 +76,9 @@ jobs:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Guard Workflow Secret Surfaces
|
||||
run: ruby scripts/ci/check-gitea-step-env-secrets.rb
|
||||
|
||||
# 2026-03-31 ogt: 優化告警格式 - 提高可讀性
|
||||
- name: Get Commit Info
|
||||
id: commit
|
||||
@@ -434,44 +437,121 @@ jobs:
|
||||
# 2026-03-31 ogt: P0-1 Secrets 自動注入 (ADR-035 強制)
|
||||
# 2026-03-31 ogt: 加入 AI API Keys (修復 mock_fallback 問題)
|
||||
- name: Inject K8s Secrets
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||
TG_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
TG_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
|
||||
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
||||
# 2026-04-01 Claude Code: Langfuse LLMOps keys (Phase 15.1 補齊 CD 注入)
|
||||
LANGFUSE_PUBLIC_KEY: ${{ secrets.LANGFUSE_PUBLIC_KEY }}
|
||||
LANGFUSE_SECRET_KEY: ${{ secrets.LANGFUSE_SECRET_KEY }}
|
||||
# 2026-04-02 Claude Code: Telegram 白名單 (授權簽核用)
|
||||
TG_USER_WHITELIST: ${{ secrets.OPENCLAW_TG_USER_WHITELIST }}
|
||||
# Phase O-4.1 2026-04-02: Sentry API Token (Wave A.1 ADR-037)
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
# ADR-059 2026-04-05: Gitea Webhook Secret (GITEA_ 前綴為保留字,改用 AWOOOI_ 前綴)
|
||||
GITEA_WEBHOOK_SECRET: ${{ secrets.AWOOOI_GITEA_WEBHOOK_SECRET }}
|
||||
# MCP Phase 3: ArgoCD API Token (2026-04-11 Claude Sonnet 4.6)
|
||||
ARGOCD_API_TOKEN: ${{ secrets.ARGOCD_API_TOKEN }}
|
||||
# 2026-04-18 ogt + Claude Opus 4.7: ADR-090-B L3-only 升級 L2(永久連線串 + 應用 secret)
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
MIGRATION_DATABASE_URL: ${{ secrets.MIGRATION_DATABASE_URL }}
|
||||
REDIS_URL: ${{ secrets.REDIS_URL }}
|
||||
JWT_SECRET: ${{ secrets.JWT_SECRET }}
|
||||
JWT_ALGORITHM: ${{ secrets.JWT_ALGORITHM }}
|
||||
WEBHOOK_HMAC_SECRET: ${{ secrets.WEBHOOK_HMAC_SECRET }}
|
||||
AWOOOP_OPERATOR_API_KEY: ${{ secrets.AWOOOP_OPERATOR_API_KEY }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
CLAUDE_API_KEY: ${{ secrets.CLAUDE_API_KEY }}
|
||||
# AWOOOI_ 前綴避開 Gitea 保留字(同 AWOOOI_GITEA_WEBHOOK_SECRET 模式)
|
||||
GITEA_API_TOKEN: ${{ secrets.AWOOOI_GITEA_API_TOKEN }}
|
||||
NEMOTRON_BOT_TOKEN: ${{ secrets.NEMOTRON_BOT_TOKEN }}
|
||||
OPENCLAW_BOT_TOKEN: ${{ secrets.OPENCLAW_BOT_TOKEN }}
|
||||
SMTP_HOST: ${{ secrets.SMTP_HOST }}
|
||||
SRE_GROUP_CHAT_ID: ${{ secrets.SRE_GROUP_CHAT_ID }}
|
||||
run: |
|
||||
# 2026-05-18 Codex: 不把 secrets 放進 step-level env。
|
||||
# Gitea/act_runner 的 job log 可能展開 env;這裡只在 shell 內短暫轉
|
||||
# base64,並避免輸出原值。
|
||||
secret_b64() {
|
||||
python3 -c 'import base64, sys; data=sys.stdin.buffer.read(); data=data[:-1] if data.endswith(b"\n") else data; sys.stdout.write(base64.b64encode(data).decode())'
|
||||
}
|
||||
write_deploy_key() {
|
||||
mkdir -p "${HOME}/.ssh"
|
||||
umask 077
|
||||
cat > "${HOME}/.ssh/deploy_key" <<'AWOOOI_DEPLOY_KEY'
|
||||
${{ secrets.DEPLOY_SSH_KEY }}
|
||||
AWOOOI_DEPLOY_KEY
|
||||
chmod 600 "${HOME}/.ssh/deploy_key"
|
||||
}
|
||||
|
||||
TG_BOT_TOKEN_B64="$(secret_b64 <<'AWOOOI_SECRET_TG_BOT_TOKEN'
|
||||
${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
AWOOOI_SECRET_TG_BOT_TOKEN
|
||||
)"
|
||||
TG_CHAT_ID_B64="$(secret_b64 <<'AWOOOI_SECRET_TG_CHAT_ID'
|
||||
${{ secrets.TELEGRAM_CHAT_ID }}
|
||||
AWOOOI_SECRET_TG_CHAT_ID
|
||||
)"
|
||||
NVIDIA_API_KEY_B64="$(secret_b64 <<'AWOOOI_SECRET_NVIDIA_API_KEY'
|
||||
${{ secrets.NVIDIA_API_KEY }}
|
||||
AWOOOI_SECRET_NVIDIA_API_KEY
|
||||
)"
|
||||
GEMINI_API_KEY_B64="$(secret_b64 <<'AWOOOI_SECRET_GEMINI_API_KEY'
|
||||
${{ secrets.GEMINI_API_KEY }}
|
||||
AWOOOI_SECRET_GEMINI_API_KEY
|
||||
)"
|
||||
LANGFUSE_PUBLIC_KEY_B64="$(secret_b64 <<'AWOOOI_SECRET_LANGFUSE_PUBLIC_KEY'
|
||||
${{ secrets.LANGFUSE_PUBLIC_KEY }}
|
||||
AWOOOI_SECRET_LANGFUSE_PUBLIC_KEY
|
||||
)"
|
||||
LANGFUSE_SECRET_KEY_B64="$(secret_b64 <<'AWOOOI_SECRET_LANGFUSE_SECRET_KEY'
|
||||
${{ secrets.LANGFUSE_SECRET_KEY }}
|
||||
AWOOOI_SECRET_LANGFUSE_SECRET_KEY
|
||||
)"
|
||||
TG_USER_WHITELIST_B64="$(secret_b64 <<'AWOOOI_SECRET_TG_USER_WHITELIST'
|
||||
${{ secrets.OPENCLAW_TG_USER_WHITELIST }}
|
||||
AWOOOI_SECRET_TG_USER_WHITELIST
|
||||
)"
|
||||
SENTRY_AUTH_TOKEN_B64="$(secret_b64 <<'AWOOOI_SECRET_SENTRY_AUTH_TOKEN'
|
||||
${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
AWOOOI_SECRET_SENTRY_AUTH_TOKEN
|
||||
)"
|
||||
GITEA_WEBHOOK_SECRET_B64="$(secret_b64 <<'AWOOOI_SECRET_GITEA_WEBHOOK_SECRET'
|
||||
${{ secrets.AWOOOI_GITEA_WEBHOOK_SECRET }}
|
||||
AWOOOI_SECRET_GITEA_WEBHOOK_SECRET
|
||||
)"
|
||||
ARGOCD_API_TOKEN_B64="$(secret_b64 <<'AWOOOI_SECRET_ARGOCD_API_TOKEN'
|
||||
${{ secrets.ARGOCD_API_TOKEN }}
|
||||
AWOOOI_SECRET_ARGOCD_API_TOKEN
|
||||
)"
|
||||
DATABASE_URL_B64="$(secret_b64 <<'AWOOOI_SECRET_DATABASE_URL'
|
||||
${{ secrets.DATABASE_URL }}
|
||||
AWOOOI_SECRET_DATABASE_URL
|
||||
)"
|
||||
MIGRATION_DATABASE_URL_B64="$(secret_b64 <<'AWOOOI_SECRET_MIGRATION_DATABASE_URL'
|
||||
${{ secrets.MIGRATION_DATABASE_URL }}
|
||||
AWOOOI_SECRET_MIGRATION_DATABASE_URL
|
||||
)"
|
||||
REDIS_URL_B64="$(secret_b64 <<'AWOOOI_SECRET_REDIS_URL'
|
||||
${{ secrets.REDIS_URL }}
|
||||
AWOOOI_SECRET_REDIS_URL
|
||||
)"
|
||||
JWT_SECRET_B64="$(secret_b64 <<'AWOOOI_SECRET_JWT_SECRET'
|
||||
${{ secrets.JWT_SECRET }}
|
||||
AWOOOI_SECRET_JWT_SECRET
|
||||
)"
|
||||
JWT_ALGORITHM_B64="$(secret_b64 <<'AWOOOI_SECRET_JWT_ALGORITHM'
|
||||
${{ secrets.JWT_ALGORITHM }}
|
||||
AWOOOI_SECRET_JWT_ALGORITHM
|
||||
)"
|
||||
WEBHOOK_HMAC_SECRET_B64="$(secret_b64 <<'AWOOOI_SECRET_WEBHOOK_HMAC_SECRET'
|
||||
${{ secrets.WEBHOOK_HMAC_SECRET }}
|
||||
AWOOOI_SECRET_WEBHOOK_HMAC_SECRET
|
||||
)"
|
||||
AWOOOP_OPERATOR_API_KEY_B64="$(secret_b64 <<'AWOOOI_SECRET_AWOOOP_OPERATOR_API_KEY'
|
||||
${{ secrets.AWOOOP_OPERATOR_API_KEY }}
|
||||
AWOOOI_SECRET_AWOOOP_OPERATOR_API_KEY
|
||||
)"
|
||||
SENTRY_DSN_B64="$(secret_b64 <<'AWOOOI_SECRET_SENTRY_DSN'
|
||||
${{ secrets.SENTRY_DSN }}
|
||||
AWOOOI_SECRET_SENTRY_DSN
|
||||
)"
|
||||
CLAUDE_API_KEY_B64="$(secret_b64 <<'AWOOOI_SECRET_CLAUDE_API_KEY'
|
||||
${{ secrets.CLAUDE_API_KEY }}
|
||||
AWOOOI_SECRET_CLAUDE_API_KEY
|
||||
)"
|
||||
GITEA_API_TOKEN_B64="$(secret_b64 <<'AWOOOI_SECRET_GITEA_API_TOKEN'
|
||||
${{ secrets.AWOOOI_GITEA_API_TOKEN }}
|
||||
AWOOOI_SECRET_GITEA_API_TOKEN
|
||||
)"
|
||||
NEMOTRON_BOT_TOKEN_B64="$(secret_b64 <<'AWOOOI_SECRET_NEMOTRON_BOT_TOKEN'
|
||||
${{ secrets.NEMOTRON_BOT_TOKEN }}
|
||||
AWOOOI_SECRET_NEMOTRON_BOT_TOKEN
|
||||
)"
|
||||
OPENCLAW_BOT_TOKEN_B64="$(secret_b64 <<'AWOOOI_SECRET_OPENCLAW_BOT_TOKEN'
|
||||
${{ secrets.OPENCLAW_BOT_TOKEN }}
|
||||
AWOOOI_SECRET_OPENCLAW_BOT_TOKEN
|
||||
)"
|
||||
SMTP_HOST_B64="$(secret_b64 <<'AWOOOI_SECRET_SMTP_HOST'
|
||||
${{ secrets.SMTP_HOST }}
|
||||
AWOOOI_SECRET_SMTP_HOST
|
||||
)"
|
||||
SRE_GROUP_CHAT_ID_B64="$(secret_b64 <<'AWOOOI_SECRET_SRE_GROUP_CHAT_ID'
|
||||
${{ secrets.SRE_GROUP_CHAT_ID }}
|
||||
AWOOOI_SECRET_SRE_GROUP_CHAT_ID
|
||||
)"
|
||||
|
||||
# S1/S2: 統一命名 deploy_key,改用 ssh-keyscan(比 StrictHostKeyChecking=no 更安全)
|
||||
mkdir -p ~/.ssh
|
||||
echo "$SSH_PRIVATE_KEY" > "${HOME}/.ssh/deploy_key"
|
||||
chmod 600 "${HOME}/.ssh/deploy_key"
|
||||
write_deploy_key
|
||||
# 2026-05-13 Codex: keyscan must include ED25519 explicitly. Some
|
||||
# OpenSSH builds otherwise record only RSA/ECDSA, then strict deploy
|
||||
# SSH fails with "No ED25519 host key is known" after image push.
|
||||
@@ -485,69 +565,69 @@ jobs:
|
||||
|
||||
# 注入 Telegram Secrets (ADR-035 鐵律)
|
||||
\$KUBECTL patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[
|
||||
{"op":"add","path":"/data/OPENCLAW_TG_BOT_TOKEN","value":"'$(echo -n "${TG_BOT_TOKEN}" | base64 -w 0)'"},
|
||||
{"op":"add","path":"/data/OPENCLAW_TG_CHAT_ID","value":"'$(echo -n "${TG_CHAT_ID}" | base64 -w 0)'"}
|
||||
{"op":"add","path":"/data/OPENCLAW_TG_BOT_TOKEN","value":"${TG_BOT_TOKEN_B64}"},
|
||||
{"op":"add","path":"/data/OPENCLAW_TG_CHAT_ID","value":"${TG_CHAT_ID_B64}"}
|
||||
]' || { echo "❌ Telegram Secrets patch 失敗 — ADR-035 鐵律"; exit 1; }
|
||||
|
||||
# 2026-03-31 ogt: 注入 AI API Keys (修復 NVIDIA/Gemini mock_fallback)
|
||||
# 2026-04-01 Claude Code: base64 -w 0 防止長 key 換行破壞 JSON
|
||||
# NVIDIA NIM (免費 tier)
|
||||
if [ -n "${NVIDIA_API_KEY}" ] && [ "${NVIDIA_API_KEY}" != "" ]; then
|
||||
if [ -n "${NVIDIA_API_KEY_B64}" ]; then
|
||||
\$KUBECTL patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[
|
||||
{"op":"add","path":"/data/NVIDIA_API_KEY","value":"'$(echo -n "${NVIDIA_API_KEY}" | base64 -w 0)'"}
|
||||
{"op":"add","path":"/data/NVIDIA_API_KEY","value":"${NVIDIA_API_KEY_B64}"}
|
||||
]' && echo "✅ NVIDIA_API_KEY 已注入" || echo "⚠️ NVIDIA_API_KEY patch 失敗"
|
||||
else
|
||||
echo "⚠️ NVIDIA_API_KEY 未設定,跳過"
|
||||
fi
|
||||
|
||||
# Gemini (備援)
|
||||
if [ -n "${GEMINI_API_KEY}" ] && [ "${GEMINI_API_KEY}" != "" ]; then
|
||||
if [ -n "${GEMINI_API_KEY_B64}" ]; then
|
||||
\$KUBECTL patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[
|
||||
{"op":"add","path":"/data/GEMINI_API_KEY","value":"'$(echo -n "${GEMINI_API_KEY}" | base64 -w 0)'"}
|
||||
{"op":"add","path":"/data/GEMINI_API_KEY","value":"${GEMINI_API_KEY_B64}"}
|
||||
]' && echo "✅ GEMINI_API_KEY 已注入" || echo "⚠️ GEMINI_API_KEY patch 失敗"
|
||||
else
|
||||
echo "⚠️ GEMINI_API_KEY 未設定,跳過"
|
||||
fi
|
||||
|
||||
# 2026-04-01 Claude Code: Langfuse LLMOps keys (補齊 CD 注入,之前只有手動設定)
|
||||
if [ -n "${LANGFUSE_PUBLIC_KEY}" ] && [ -n "${LANGFUSE_SECRET_KEY}" ]; then
|
||||
if [ -n "${LANGFUSE_PUBLIC_KEY_B64}" ] && [ -n "${LANGFUSE_SECRET_KEY_B64}" ]; then
|
||||
\$KUBECTL patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[
|
||||
{"op":"add","path":"/data/LANGFUSE_PUBLIC_KEY","value":"'$(echo -n "${LANGFUSE_PUBLIC_KEY}" | base64 -w 0)'"},
|
||||
{"op":"add","path":"/data/LANGFUSE_SECRET_KEY","value":"'$(echo -n "${LANGFUSE_SECRET_KEY}" | base64 -w 0)'"}
|
||||
{"op":"add","path":"/data/LANGFUSE_PUBLIC_KEY","value":"${LANGFUSE_PUBLIC_KEY_B64}"},
|
||||
{"op":"add","path":"/data/LANGFUSE_SECRET_KEY","value":"${LANGFUSE_SECRET_KEY_B64}"}
|
||||
]' && echo "✅ LANGFUSE keys 已注入" || echo "⚠️ LANGFUSE keys patch 失敗"
|
||||
else
|
||||
echo "⚠️ LANGFUSE_PUBLIC_KEY/SECRET_KEY 未設定,跳過 (現有 K8s secret 值維持不變)"
|
||||
fi
|
||||
|
||||
# 2026-04-02 Claude Code: Telegram Whitelist (授權簽核用戶 ID)
|
||||
if [ -n "${TG_USER_WHITELIST}" ]; then
|
||||
if [ -n "${TG_USER_WHITELIST_B64}" ]; then
|
||||
\$KUBECTL patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[
|
||||
{"op":"add","path":"/data/OPENCLAW_TG_USER_WHITELIST","value":"'$(echo -n "${TG_USER_WHITELIST}" | base64 -w 0)'"}
|
||||
{"op":"add","path":"/data/OPENCLAW_TG_USER_WHITELIST","value":"${TG_USER_WHITELIST_B64}"}
|
||||
]' && echo "✅ TG_USER_WHITELIST 已注入" || echo "⚠️ TG_USER_WHITELIST patch 失敗"
|
||||
fi
|
||||
|
||||
# Phase O-4.1 2026-04-02: Sentry Auth Token (Wave A.1 ADR-037)
|
||||
if [ -n "${SENTRY_AUTH_TOKEN}" ]; then
|
||||
if [ -n "${SENTRY_AUTH_TOKEN_B64}" ]; then
|
||||
\$KUBECTL patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[
|
||||
{"op":"add","path":"/data/SENTRY_AUTH_TOKEN","value":"'$(echo -n "${SENTRY_AUTH_TOKEN}" | base64 -w 0)'"}
|
||||
{"op":"add","path":"/data/SENTRY_AUTH_TOKEN","value":"${SENTRY_AUTH_TOKEN_B64}"}
|
||||
]' && echo "✅ SENTRY_AUTH_TOKEN 已注入" || echo "⚠️ SENTRY_AUTH_TOKEN patch 失敗"
|
||||
else
|
||||
echo "⚠️ SENTRY_AUTH_TOKEN 未設定,Sentry Comment API 將跳過"
|
||||
fi
|
||||
|
||||
# ADR-059 2026-04-05 Claude Code: Gitea Webhook Secret
|
||||
if [ -n "${GITEA_WEBHOOK_SECRET}" ]; then
|
||||
if [ -n "${GITEA_WEBHOOK_SECRET_B64}" ]; then
|
||||
\$KUBECTL patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[
|
||||
{"op":"add","path":"/data/GITEA_WEBHOOK_SECRET","value":"'$(echo -n "${GITEA_WEBHOOK_SECRET}" | base64 -w 0)'"}
|
||||
{"op":"add","path":"/data/GITEA_WEBHOOK_SECRET","value":"${GITEA_WEBHOOK_SECRET_B64}"}
|
||||
]' && echo "✅ GITEA_WEBHOOK_SECRET 已注入" || echo "⚠️ GITEA_WEBHOOK_SECRET patch 失敗"
|
||||
else
|
||||
echo "⚠️ GITEA_WEBHOOK_SECRET 未設定,Gitea Webhook 簽章驗證將在 prod 失效"
|
||||
fi
|
||||
|
||||
# MCP Phase 3: ArgoCD API Token (2026-04-11 Claude Sonnet 4.6)
|
||||
if [ -n "${ARGOCD_API_TOKEN}" ]; then
|
||||
if [ -n "${ARGOCD_API_TOKEN_B64}" ]; then
|
||||
\$KUBECTL patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[
|
||||
{"op":"add","path":"/data/ARGOCD_API_TOKEN","value":"'$(echo -n "${ARGOCD_API_TOKEN}" | base64 -w 0)'"}
|
||||
{"op":"add","path":"/data/ARGOCD_API_TOKEN","value":"${ARGOCD_API_TOKEN_B64}"}
|
||||
]' && echo "✅ ARGOCD_API_TOKEN 已注入" || echo "⚠️ ARGOCD_API_TOKEN patch 失敗"
|
||||
else
|
||||
echo "⚠️ ARGOCD_API_TOKEN 未設定,ArgoCD MCP 將使用空 token"
|
||||
@@ -560,98 +640,98 @@ jobs:
|
||||
# 注意: 每個 block 與上方維持相同結構(if guard + base64 -w 0 + json patch)
|
||||
|
||||
# DATABASE_URL — PG 應用連線串(2026-04-18 輪替)
|
||||
if [ -n "${DATABASE_URL}" ]; then
|
||||
if [ -n "${DATABASE_URL_B64}" ]; then
|
||||
\$KUBECTL patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[
|
||||
{"op":"add","path":"/data/DATABASE_URL","value":"'$(echo -n "${DATABASE_URL}" | base64 -w 0)'"}
|
||||
{"op":"add","path":"/data/DATABASE_URL","value":"${DATABASE_URL_B64}"}
|
||||
]' && echo "✅ DATABASE_URL 已注入" || echo "⚠️ DATABASE_URL patch 失敗"
|
||||
else
|
||||
echo "⚠️ DATABASE_URL 未設定,awoooi-api 將無法連 PG"
|
||||
fi
|
||||
|
||||
# MIGRATION_DATABASE_URL — CI migration 用 awoooi_migrator 限權帳號(ADR-090-B)
|
||||
if [ -n "${MIGRATION_DATABASE_URL}" ]; then
|
||||
if [ -n "${MIGRATION_DATABASE_URL_B64}" ]; then
|
||||
\$KUBECTL patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[
|
||||
{"op":"add","path":"/data/MIGRATION_DATABASE_URL","value":"'$(echo -n "${MIGRATION_DATABASE_URL}" | base64 -w 0)'"}
|
||||
{"op":"add","path":"/data/MIGRATION_DATABASE_URL","value":"${MIGRATION_DATABASE_URL_B64}"}
|
||||
]' && echo "✅ MIGRATION_DATABASE_URL 已注入" || echo "⚠️ MIGRATION_DATABASE_URL patch 失敗"
|
||||
fi
|
||||
|
||||
# REDIS_URL — Redis 連線(6380 on 188)
|
||||
if [ -n "${REDIS_URL}" ]; then
|
||||
if [ -n "${REDIS_URL_B64}" ]; then
|
||||
\$KUBECTL patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[
|
||||
{"op":"add","path":"/data/REDIS_URL","value":"'$(echo -n "${REDIS_URL}" | base64 -w 0)'"}
|
||||
{"op":"add","path":"/data/REDIS_URL","value":"${REDIS_URL_B64}"}
|
||||
]' && echo "✅ REDIS_URL 已注入" || echo "⚠️ REDIS_URL patch 失敗"
|
||||
else
|
||||
echo "⚠️ REDIS_URL 未設定"
|
||||
fi
|
||||
|
||||
# JWT_SECRET / JWT_ALGORITHM — API 認證
|
||||
if [ -n "${JWT_SECRET}" ]; then
|
||||
if [ -n "${JWT_SECRET_B64}" ]; then
|
||||
\$KUBECTL patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[
|
||||
{"op":"add","path":"/data/JWT_SECRET","value":"'$(echo -n "${JWT_SECRET}" | base64 -w 0)'"}
|
||||
{"op":"add","path":"/data/JWT_SECRET","value":"${JWT_SECRET_B64}"}
|
||||
]' && echo "✅ JWT_SECRET 已注入" || echo "⚠️ JWT_SECRET patch 失敗"
|
||||
fi
|
||||
if [ -n "${JWT_ALGORITHM}" ]; then
|
||||
if [ -n "${JWT_ALGORITHM_B64}" ]; then
|
||||
\$KUBECTL patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[
|
||||
{"op":"add","path":"/data/JWT_ALGORITHM","value":"'$(echo -n "${JWT_ALGORITHM}" | base64 -w 0)'"}
|
||||
{"op":"add","path":"/data/JWT_ALGORITHM","value":"${JWT_ALGORITHM_B64}"}
|
||||
]' && echo "✅ JWT_ALGORITHM 已注入" || echo "⚠️ JWT_ALGORITHM patch 失敗"
|
||||
fi
|
||||
|
||||
# WEBHOOK_HMAC_SECRET — Alertmanager webhook HMAC 簽章
|
||||
if [ -n "${WEBHOOK_HMAC_SECRET}" ]; then
|
||||
if [ -n "${WEBHOOK_HMAC_SECRET_B64}" ]; then
|
||||
\$KUBECTL patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[
|
||||
{"op":"add","path":"/data/WEBHOOK_HMAC_SECRET","value":"'$(echo -n "${WEBHOOK_HMAC_SECRET}" | base64 -w 0)'"}
|
||||
{"op":"add","path":"/data/WEBHOOK_HMAC_SECRET","value":"${WEBHOOK_HMAC_SECRET_B64}"}
|
||||
]' && echo "✅ WEBHOOK_HMAC_SECRET 已注入" || echo "⚠️ WEBHOOK_HMAC_SECRET patch 失敗"
|
||||
fi
|
||||
|
||||
# AWOOOP_OPERATOR_API_KEY — AwoooP Operator mutation endpoints
|
||||
if [ -n "${AWOOOP_OPERATOR_API_KEY}" ]; then
|
||||
if [ -n "${AWOOOP_OPERATOR_API_KEY_B64}" ]; then
|
||||
\$KUBECTL patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[
|
||||
{"op":"add","path":"/data/AWOOOP_OPERATOR_API_KEY","value":"'$(echo -n "${AWOOOP_OPERATOR_API_KEY}" | base64 -w 0)'"}
|
||||
{"op":"add","path":"/data/AWOOOP_OPERATOR_API_KEY","value":"${AWOOOP_OPERATOR_API_KEY_B64}"}
|
||||
]' && echo "✅ AWOOOP_OPERATOR_API_KEY 已注入" || echo "⚠️ AWOOOP_OPERATOR_API_KEY patch 失敗"
|
||||
fi
|
||||
|
||||
# SENTRY_DSN — Sentry 錯誤追蹤(不是 auth token)
|
||||
if [ -n "${SENTRY_DSN}" ]; then
|
||||
if [ -n "${SENTRY_DSN_B64}" ]; then
|
||||
\$KUBECTL patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[
|
||||
{"op":"add","path":"/data/SENTRY_DSN","value":"'$(echo -n "${SENTRY_DSN}" | base64 -w 0)'"}
|
||||
{"op":"add","path":"/data/SENTRY_DSN","value":"${SENTRY_DSN_B64}"}
|
||||
]' && echo "✅ SENTRY_DSN 已注入" || echo "⚠️ SENTRY_DSN patch 失敗"
|
||||
fi
|
||||
|
||||
# CLAUDE_API_KEY — Claude 備援 LLM
|
||||
if [ -n "${CLAUDE_API_KEY}" ]; then
|
||||
if [ -n "${CLAUDE_API_KEY_B64}" ]; then
|
||||
\$KUBECTL patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[
|
||||
{"op":"add","path":"/data/CLAUDE_API_KEY","value":"'$(echo -n "${CLAUDE_API_KEY}" | base64 -w 0)'"}
|
||||
{"op":"add","path":"/data/CLAUDE_API_KEY","value":"${CLAUDE_API_KEY_B64}"}
|
||||
]' && echo "✅ CLAUDE_API_KEY 已注入" || echo "⚠️ CLAUDE_API_KEY patch 失敗"
|
||||
fi
|
||||
|
||||
# GITEA_API_TOKEN — Gitea API Token(從 AWOOOI_GITEA_API_TOKEN 映射)
|
||||
if [ -n "${GITEA_API_TOKEN}" ]; then
|
||||
if [ -n "${GITEA_API_TOKEN_B64}" ]; then
|
||||
\$KUBECTL patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[
|
||||
{"op":"add","path":"/data/GITEA_API_TOKEN","value":"'$(echo -n "${GITEA_API_TOKEN}" | base64 -w 0)'"}
|
||||
{"op":"add","path":"/data/GITEA_API_TOKEN","value":"${GITEA_API_TOKEN_B64}"}
|
||||
]' && echo "✅ GITEA_API_TOKEN 已注入" || echo "⚠️ GITEA_API_TOKEN patch 失敗"
|
||||
fi
|
||||
|
||||
# NEMOTRON_BOT_TOKEN / OPENCLAW_BOT_TOKEN — 多 Bot 架構
|
||||
if [ -n "${NEMOTRON_BOT_TOKEN}" ]; then
|
||||
if [ -n "${NEMOTRON_BOT_TOKEN_B64}" ]; then
|
||||
\$KUBECTL patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[
|
||||
{"op":"add","path":"/data/NEMOTRON_BOT_TOKEN","value":"'$(echo -n "${NEMOTRON_BOT_TOKEN}" | base64 -w 0)'"}
|
||||
{"op":"add","path":"/data/NEMOTRON_BOT_TOKEN","value":"${NEMOTRON_BOT_TOKEN_B64}"}
|
||||
]' && echo "✅ NEMOTRON_BOT_TOKEN 已注入" || echo "⚠️ NEMOTRON_BOT_TOKEN patch 失敗"
|
||||
fi
|
||||
if [ -n "${OPENCLAW_BOT_TOKEN}" ]; then
|
||||
if [ -n "${OPENCLAW_BOT_TOKEN_B64}" ]; then
|
||||
\$KUBECTL patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[
|
||||
{"op":"add","path":"/data/OPENCLAW_BOT_TOKEN","value":"'$(echo -n "${OPENCLAW_BOT_TOKEN}" | base64 -w 0)'"}
|
||||
{"op":"add","path":"/data/OPENCLAW_BOT_TOKEN","value":"${OPENCLAW_BOT_TOKEN_B64}"}
|
||||
]' && echo "✅ OPENCLAW_BOT_TOKEN 已注入" || echo "⚠️ OPENCLAW_BOT_TOKEN patch 失敗"
|
||||
fi
|
||||
|
||||
# SMTP_HOST / SRE_GROUP_CHAT_ID
|
||||
if [ -n "${SMTP_HOST}" ]; then
|
||||
if [ -n "${SMTP_HOST_B64}" ]; then
|
||||
\$KUBECTL patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[
|
||||
{"op":"add","path":"/data/SMTP_HOST","value":"'$(echo -n "${SMTP_HOST}" | base64 -w 0)'"}
|
||||
{"op":"add","path":"/data/SMTP_HOST","value":"${SMTP_HOST_B64}"}
|
||||
]' && echo "✅ SMTP_HOST 已注入" || echo "⚠️ SMTP_HOST patch 失敗"
|
||||
fi
|
||||
if [ -n "${SRE_GROUP_CHAT_ID}" ]; then
|
||||
if [ -n "${SRE_GROUP_CHAT_ID_B64}" ]; then
|
||||
\$KUBECTL patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[
|
||||
{"op":"add","path":"/data/SRE_GROUP_CHAT_ID","value":"'$(echo -n "${SRE_GROUP_CHAT_ID}" | base64 -w 0)'"}
|
||||
{"op":"add","path":"/data/SRE_GROUP_CHAT_ID","value":"${SRE_GROUP_CHAT_ID_B64}"}
|
||||
]' && echo "✅ SRE_GROUP_CHAT_ID 已注入" || echo "⚠️ SRE_GROUP_CHAT_ID patch 失敗"
|
||||
fi
|
||||
|
||||
@@ -707,13 +787,18 @@ jobs:
|
||||
# 4. 等待 ArgoCD sync + rollout 完成
|
||||
# 5. Health Check
|
||||
- name: Deploy to K8s (ArgoCD GitOps)
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||
GITEA_TOKEN: ${{ secrets.CD_PUSH_TOKEN }}
|
||||
run: |
|
||||
write_deploy_key() {
|
||||
mkdir -p "${HOME}/.ssh"
|
||||
umask 077
|
||||
cat > "${HOME}/.ssh/deploy_key" <<'AWOOOI_DEPLOY_KEY'
|
||||
${{ secrets.DEPLOY_SSH_KEY }}
|
||||
AWOOOI_DEPLOY_KEY
|
||||
chmod 600 "${HOME}/.ssh/deploy_key"
|
||||
}
|
||||
|
||||
mkdir -p ~/.ssh
|
||||
echo "$SSH_PRIVATE_KEY" > "${HOME}/.ssh/deploy_key"
|
||||
chmod 600 "${HOME}/.ssh/deploy_key"
|
||||
write_deploy_key
|
||||
# 2026-05-13 Codex: mirror Inject K8s Secrets host-key handling so the
|
||||
# deploy job never reaches SSH with a known_hosts file missing ED25519.
|
||||
ssh-keyscan -T 5 -t ed25519,rsa,ecdsa "${K8S_SSH_HOST}" > "${HOME}/.ssh/known_hosts" 2>/dev/null
|
||||
@@ -761,7 +846,7 @@ jobs:
|
||||
git commit -m "chore(cd): deploy ${IMAGE_TAG::7} [skip ci]"
|
||||
# 用 token 推送(避免 SSH key 需要額外設定 push 權限)
|
||||
git remote remove gitea 2>/dev/null || true
|
||||
git remote add gitea http://wooo:${GITEA_TOKEN}@192.168.0.110:3001/wooo/awoooi.git
|
||||
git remote add gitea "http://wooo:${{ secrets.CD_PUSH_TOKEN }}@192.168.0.110:3001/wooo/awoooi.git"
|
||||
# 先 rebase 避免 non-fast-forward (其他 commit 在 CI 期間已推入)
|
||||
# 2026-04-17 ogt: -X theirs — kustomization.yaml 衝突時採用當次部署的 image tag
|
||||
git fetch gitea main
|
||||
|
||||
@@ -30,6 +30,9 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 50
|
||||
|
||||
- name: Guard Workflow Secret Surfaces
|
||||
run: ruby scripts/ci/check-gitea-step-env-secrets.rb
|
||||
|
||||
- name: Skip Stale Main Push
|
||||
id: stale
|
||||
run: |
|
||||
@@ -102,7 +105,6 @@ jobs:
|
||||
- name: Notify Code Review Start
|
||||
if: steps.stale.outputs.skip != 'true'
|
||||
env:
|
||||
TG_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
TG_CHAT_ID: ${{ env.TELEGRAM_ALERT_CHAT_ID }}
|
||||
SHORT_SHA: ${{ steps.ctx.outputs.short_sha }}
|
||||
BRANCH: ${{ steps.ctx.outputs.branch }}
|
||||
@@ -110,6 +112,10 @@ jobs:
|
||||
FILES_DISPLAY: ${{ steps.ctx.outputs.files_display }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TG_BOT_TOKEN="$(cat <<'AWOOOI_SECRET_TG_BOT_TOKEN'
|
||||
${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
AWOOOI_SECRET_TG_BOT_TOKEN
|
||||
)"
|
||||
html_escape() { sed 's/&/\&/g; s/</\</g; s/>/\>/g'; }
|
||||
COMMIT_ESC="$(printf '%s' "$COMMIT_MSG" | html_escape)"
|
||||
FILES_ESC="$(printf '%s\n' "$FILES_DISPLAY" | html_escape)"
|
||||
@@ -150,11 +156,14 @@ jobs:
|
||||
- name: Notify Code Review Completion
|
||||
if: always() && steps.stale.outputs.skip != 'true'
|
||||
env:
|
||||
TG_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
TG_CHAT_ID: ${{ env.TELEGRAM_ALERT_CHAT_ID }}
|
||||
SHORT_SHA: ${{ steps.ctx.outputs.short_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TG_BOT_TOKEN="$(cat <<'AWOOOI_SECRET_TG_BOT_TOKEN'
|
||||
${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
AWOOOI_SECRET_TG_BOT_TOKEN
|
||||
)"
|
||||
REPORT=/tmp/code-review-report.json
|
||||
if [ ! -s "$REPORT" ]; then
|
||||
cat > "$REPORT" <<'JSON'
|
||||
|
||||
@@ -37,7 +37,10 @@ jobs:
|
||||
- name: Setup SSH key
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_ed25519
|
||||
umask 077
|
||||
cat > ~/.ssh/id_ed25519 <<'AWOOOI_DEPLOY_KEY'
|
||||
${{ secrets.DEPLOY_SSH_KEY }}
|
||||
AWOOOI_DEPLOY_KEY
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
ssh-keyscan 192.168.0.110 >> ~/.ssh/known_hosts
|
||||
|
||||
|
||||
@@ -72,14 +72,19 @@ jobs:
|
||||
|
||||
- name: Apply new migrations
|
||||
if: steps.diff.outputs.new_files != ''
|
||||
env:
|
||||
# 從 Gitea secrets 取,不直接明碼輸出。
|
||||
# MIGRATION_DATABASE_URL 是限權帳號;DATABASE_URL 只在 PostgreSQL
|
||||
# 明確回報「必須是 table owner」時作為受控 fallback。
|
||||
PGURL: ${{ secrets.MIGRATION_DATABASE_URL }}
|
||||
OWNER_PGURL: ${{ secrets.DATABASE_URL }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# 從 Gitea secrets 取,不放 step-level env,避免 runner log 展開。
|
||||
# MIGRATION_DATABASE_URL 是限權帳號;DATABASE_URL 只在 PostgreSQL
|
||||
# 明確回報「必須是 table owner」時作為受控 fallback。
|
||||
PGURL="$(cat <<'AWOOOI_SECRET_MIGRATION_DATABASE_URL'
|
||||
${{ secrets.MIGRATION_DATABASE_URL }}
|
||||
AWOOOI_SECRET_MIGRATION_DATABASE_URL
|
||||
)"
|
||||
OWNER_PGURL="$(cat <<'AWOOOI_SECRET_DATABASE_URL'
|
||||
${{ secrets.DATABASE_URL }}
|
||||
AWOOOI_SECRET_DATABASE_URL
|
||||
)"
|
||||
if [ -z "$PGURL" ]; then
|
||||
echo "::error::MIGRATION_DATABASE_URL secret not set in Gitea"
|
||||
exit 1
|
||||
@@ -121,11 +126,16 @@ jobs:
|
||||
|
||||
- name: Seed asset_discovery_run (audit)
|
||||
if: steps.diff.outputs.new_files != ''
|
||||
env:
|
||||
PGURL: ${{ secrets.MIGRATION_DATABASE_URL }}
|
||||
OWNER_PGURL: ${{ secrets.DATABASE_URL }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
PGURL="$(cat <<'AWOOOI_SECRET_MIGRATION_DATABASE_URL'
|
||||
${{ secrets.MIGRATION_DATABASE_URL }}
|
||||
AWOOOI_SECRET_MIGRATION_DATABASE_URL
|
||||
)"
|
||||
OWNER_PGURL="$(cat <<'AWOOOI_SECRET_DATABASE_URL'
|
||||
${{ secrets.DATABASE_URL }}
|
||||
AWOOOI_SECRET_DATABASE_URL
|
||||
)"
|
||||
if [ -z "$PGURL" ]; then
|
||||
echo "::error::MIGRATION_DATABASE_URL secret not set in Gitea"
|
||||
exit 1
|
||||
@@ -179,9 +189,12 @@ jobs:
|
||||
- name: Notify Telegram (if configured)
|
||||
if: always()
|
||||
env:
|
||||
TG_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
TG_CHAT: ${{ env.TELEGRAM_ALERT_CHAT_ID }}
|
||||
run: |
|
||||
TG_TOKEN="$(cat <<'AWOOOI_SECRET_TG_TOKEN'
|
||||
${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
AWOOOI_SECRET_TG_TOKEN
|
||||
)"
|
||||
STATUS="${{ job.status }}"
|
||||
CICD_STATUS="success"
|
||||
[ "$STATUS" != "success" ] && CICD_STATUS="failed"
|
||||
|
||||
@@ -1,3 +1,51 @@
|
||||
## 2026-05-18 | T44 Gitea workflow secret env 泄漏面收斂
|
||||
|
||||
**背景**:T43 production verification 時,Gitea job log 顯示 step-level env 有 secret 展開風險。這會讓部署 SSH key、DB URL、Telegram token、AI provider key 等敏感值在 CI log 中留下痕跡,會直接破壞 token hygiene,也會污染後續 AwoooP / SignOz / log matching 的可信度。
|
||||
|
||||
**修正**:
|
||||
- `.gitea/workflows/cd.yaml`
|
||||
- `Inject K8s Secrets` 不再把 secrets 掛在 step-level `env:`。
|
||||
- 改用 shell-local heredoc 讀取 secret,短暫轉 base64 後送入 K8s secret patch。
|
||||
- `Deploy to K8s (ArgoCD GitOps)` 不再以 step env 傳 `DEPLOY_SSH_KEY` / `CD_PUSH_TOKEN`。
|
||||
- CD tests job 新增 `Guard Workflow Secret Surfaces`。
|
||||
- `.gitea/workflows/cd-dev.yaml`
|
||||
- Dev secret injection 移除 `DEPLOY_SSH_KEY` / Telegram / NVIDIA / Gemini step env。
|
||||
- Harbor login 改為 shell-local heredoc,不再把 username/password 放 action `with:`。
|
||||
- `.gitea/workflows/code-review.yaml`
|
||||
- Telegram token 不再放 step env。
|
||||
- workflow 開頭新增 secret surface guard。
|
||||
- `.gitea/workflows/run-migration.yml`
|
||||
- `MIGRATION_DATABASE_URL` / `DATABASE_URL` / Telegram token 不再放 step env。
|
||||
- `Seed asset_discovery_run (audit)` 維持目前 JSON SQL 寫法,避免舊版 `:'commit_sha'` psql bind syntax 錯誤復發。
|
||||
- `.gitea/workflows/deploy-alerts.yaml`
|
||||
- deploy key 改用 heredoc 寫入,不再用 `echo "${{ secrets.DEPLOY_SSH_KEY }}"`。
|
||||
- 新增 `scripts/ci/check-gitea-step-env-secrets.rb`:
|
||||
- 掃描 `.gitea/workflows/*.{yml,yaml}`。
|
||||
- 若任何 step `env:` 或 action `with:` 出現 `${{ secrets.* }}`,直接 fail。
|
||||
|
||||
**verification**:
|
||||
- `ruby scripts/ci/check-gitea-step-env-secrets.rb`:no Gitea step env/with secrets。
|
||||
- `ruby -e 'require "yaml"; Dir[".gitea/workflows/*.{yml,yaml}"].each { |p| YAML.load_file(p) }; puts "workflow yaml ok"'`:pass。
|
||||
- workflow `run:` block 經 `${{ ... }}` dummy 替換後 `bash -n`:pass。
|
||||
- `rg -n ': \$\{\{ secrets\.' .gitea/workflows`:0 matches。
|
||||
- `git diff --check`:pass。
|
||||
|
||||
**風險與後續**:
|
||||
- 這一輪修掉 repo 可控的 step env/action input 泄漏面,但已出現在歷史 job log 的 secret 不能靠 commit 消失;需要在 Gitea/runner 端輪換相關 key,並清理或縮短敏感 log retention。
|
||||
- workflow run script 內仍會在執行時讀取 Gitea secrets,這是 CI 必要行為;下一波要補的是 runner-level masking policy 與 secret rotation checklist,而不是把 secret 再搬回 env。
|
||||
|
||||
**目前整體進度**:
|
||||
- Alertmanager 低風險自動修復主線:約 98%。
|
||||
- 完整 AI 自動化管理產品化:約 99%。
|
||||
- 告警詳情/歷史/主卡/前端 deep-link 可追溯:約 99%。
|
||||
- Telegram approval / reject callback 閉環:約 96%。
|
||||
- Truth-chain 對「自動修復成功但驗證降級」的判讀:約 99%。
|
||||
- AwoooP MCP 使用可見性:約 90%。
|
||||
- 188 OpenClaw runtime hygiene:約 90%。
|
||||
- Token hygiene:約 65%。
|
||||
- CI/CD secret masking hygiene:約 60%(repo 可控 env/with 泄漏面已擋;仍需外部輪換與 runner log retention)。
|
||||
- Gitea infra-lint 可執行性:100%。
|
||||
|
||||
## 2026-05-18 | T43 AwoooP Run Detail 顯示 legacy/self-built MCP 證據
|
||||
|
||||
**背景**:Telegram 截圖與前端 Run detail 雖已能顯示 AwoooP Gateway / channel dossier / remediation dry-run,但仍有一個可見性缺口:許多既有自建 MCP / legacy MCP 呼叫寫在 `mcp_audit_log`,Run detail 只讀 `awooop_mcp_gateway_audit`。結果是 operator 容易誤判「沒有真的用 MCP」,即使 truth-chain API 其實能查到 legacy MCP audit。
|
||||
|
||||
32
scripts/ci/check-gitea-step-env-secrets.rb
Executable file
32
scripts/ci/check-gitea-step-env-secrets.rb
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env ruby
|
||||
# Guard against putting secrets in Gitea step env/with blocks.
|
||||
# Gitea/act_runner logs may render those blocks before masking is effective.
|
||||
|
||||
require "yaml"
|
||||
|
||||
workflow_glob = File.expand_path("../../.gitea/workflows/*.{yml,yaml}", __dir__)
|
||||
violations = []
|
||||
|
||||
Dir[workflow_glob].sort.each do |path|
|
||||
doc = YAML.load_file(path)
|
||||
jobs = doc.fetch("jobs", {})
|
||||
jobs.each do |job_name, job|
|
||||
Array(job["steps"]).each_with_index do |step, index|
|
||||
%w[env with].each do |section|
|
||||
Hash(step[section]).each do |key, value|
|
||||
next unless value.to_s.include?("${{ secrets.")
|
||||
|
||||
violations << "#{path}:#{job_name}:step#{index + 1}:#{section}.#{key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if violations.any?
|
||||
warn "Gitea workflow exposes secrets through step env/with:"
|
||||
violations.each { |violation| warn " - #{violation}" }
|
||||
exit 1
|
||||
end
|
||||
|
||||
puts "no Gitea step env/with secrets"
|
||||
Reference in New Issue
Block a user