Files
cayce-ops/deploy/usr-local-bin/cayce-auto-merge
T

47 lines
2.9 KiB
Bash
Executable File

#!/bin/bash
# Auto-merge (ronda-9): SOLO PRs con todos los checks en success y que NO
# toquen paths protegidos. Nunca btcpay-events. Nunca credenciales del operador.
# r19: el token persiste en /etc/cayce/ci-bot.token (600). Regenerar con nombre
# ya usado da ERROR y dejaba BOT vacio (bug historico: el script no hacia nada).
# r31 (S1): lock compartido con cayce-autodeploy — nunca compiten sobre los
# mismos arboles/branches (incidente 02:06:29Z del 2026-11-03).
LOCK=/run/lock/cayce-git-ops.lock
exec 9>"$LOCK" || { echo 'FATAL: no se puede abrir lockfile'; exit 1; }
if ! flock -w 240 9; then
echo "[auto-merge] autodeploy mantiene el lock >240s — paso de este ciclo (el proximo mergea)"
exit 0
fi
BOT=$(cat /etc/cayce/ci-bot.token 2>/dev/null | tr -d '[:space:]')
if [ -z "$BOT" ]; then
BOT=$(sudo -u git /usr/local/bin/gitea --config /etc/gitea/app.ini admin user generate-access-token -u ci-bot -t "auto-merge-$(date +%s)" --scopes write:repository,write:issue 2>/dev/null | grep -aoE "[0-9a-f]{40}" | head -1)
[ -n "$BOT" ] && printf '%s' "$BOT" > /etc/cayce/ci-bot.token && chmod 600 /etc/cayce/ci-bot.token
fi
[ -z "$BOT" ] && { echo 'FATAL: sin token ci-bot'; exit 1; }
G="http://127.0.0.1:3100/api/v1"
# paths protegidos (grep -E contra la lista de ficheros modificados del PR)
PROTECTED="SPEC.md|MANDATE.md|prompts/cayce-identity.md|guard[^/]*\.|relay-watcher|outbox\.mjs|guard-blocklist|check-single-outbox|\.gitea/workflows|\.github/workflows|cayce-auto-merge|act_runner|docker|cayce-autodeploy|cayce-urgent-watch|cayce-daily-report|approvals\.mjs|approvals\.test\.mjs|approval-watcher\.mjs|cayce-approval-watcher|cayce-promo\.(service|timer)"
for REPO in cayce-ops entity bitcoinhotels; do
curl -s -H "Authorization: token $BOT" "$G/repos/cayce/$REPO/pulls?state=open&limit=20" | python3 -c "
import json,sys
for p in json.load(sys.stdin):
if p.get('title','').upper().startswith('WIP'): continue
print(p['number'], p['head']['sha'])" 2>/dev/null | while read N SHA; do
FILES=$(curl -s -H "Authorization: token $BOT" "$G/repos/cayce/$REPO/pulls/$N/files?limit=100" | python3 -c "
import json,sys
for f in json.load(sys.stdin): print(f.get('filename',''))" 2>/dev/null)
if echo "$FILES" | grep -qE "$PROTECTED"; then
echo "$REPO #$N: TOCA PATH PROTEGIDO — no se auto-mergea"
continue
fi
# r19: /status (combinado) en vez de /statuses — la lista incluye los
# 'pending' historicos y hacia que un PR verde pareciera no verde
OK=$(curl -s -H "Authorization: token $BOT" "$G/repos/cayce/$REPO/commits/$SHA/status" | python3 -c "
import json,sys
d = json.load(sys.stdin)
print('yes' if isinstance(d, dict) and d.get('state') == 'success' else 'no')" 2>/dev/null)
if [ "$OK" = "yes" ]; then
curl -s -X POST -H "Authorization: token $BOT" -H "Content-Type: application/json" -d '{"Do":"merge"}' "$G/repos/cayce/$REPO/pulls/$N/merge" -o /dev/null -w "$REPO #$N (green, sin paths protegidos) merge: %{http_code}\n"
fi
done
done