Lumen / .github /workflows /state-backup.yml
SilverElixir
Telegram alert on state-backup failure instead of Actions summary only (audit M11)
2d6a16c
Raw History Blame Contribute Delete
5.93 kB
name: State backup
# Ежедневный автоматический бэкап /export_state (см. README, раздел
# "Диагностика") — раньше это был ручной curl владельца без расписания.
# Требует секреты в настройках репозитория:
# SPACE_HOST — хост HF Space (например, silverelixir-lumen.hf.space)
# ADMIN_PANEL_KEY — см. curl -H "Authorization: Bearer <BOT_TOKEN>" .../admin_keys
# STATE_BACKUP_REPO — приватный репозиторий для бэкапов, например S1lverElixir/Lumen-state
# STATE_BACKUP_TOKEN — fine-grained PAT с Contents: read+write только на STATE_BACKUP_REPO
# Опционально (алерт о провале, аудит M11):
# STATE_ALERT_TOKEN — токен бота-сканера (lumen_smoke_bot), без прав админа
# STATE_ALERT_CHAT — личный chat_id владельца с этим ботом
# Без них провал виден только в сводке Actions.
#
# ПРИВАТНОСТЬ (инцидент, сентябрь 2026): раньше файл заливался артефактом
# Actions, а артефакты публичного репозитория может скачать любой. Теперь
# бэкап пушится git-коммитом в отдельный ПРИВАТНЫЙ репозиторий — каждый день
# отдельный коммит, история и есть глубина хранения. Ничего чувствительного
# в артефакты больше не кладётся.
on:
schedule:
- cron: "0 3 * * *" # 03:00 UTC ежедневно
workflow_dispatch: # + ручной запуск по требованию
permissions:
contents: read
# Два одновременных запуска (шедул + ручной) толкались бы в один бранч backup-репо.
concurrency:
group: state-backup
cancel-in-progress: false
jobs:
backup:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Fetch /export_state
run: |
curl -sf --retry 3 --max-time 120 \
-H "Authorization: Bearer ${{ secrets.ADMIN_PANEL_KEY }}" \
"https://${{ secrets.SPACE_HOST }}/export_state" \
-o export_state.json
- name: Push to private backup repo
env:
STATE_BACKUP_TOKEN: ${{ secrets.STATE_BACKUP_TOKEN }}
STATE_BACKUP_REPO: ${{ secrets.STATE_BACKUP_REPO }}
run: |
set -euo pipefail
# Приватность: бэкап только в приватный репозиторий, иначе стоп.
visibility="$(GH_TOKEN="${STATE_BACKUP_TOKEN}" gh repo view "${STATE_BACKUP_REPO}" --json visibility -q .visibility)"
if [ "${visibility}" != "PRIVATE" ]; then
echo "Refusing backup: ${STATE_BACKUP_REPO} is not PRIVATE (got: ${visibility})" >&2
exit 1
fi
git config --global user.name "lumen-backup"
git config --global user.email "lumen-backup@local"
# PAT не в URL клона, а в extraheader: иначе светится в remote -v и логах.
git -c "http.https://github.com/.extraheader=AUTHORIZATION: basic $(printf 'x-access-token:%s' "${STATE_BACKUP_TOKEN}" | base64 -w0)" \
clone "https://github.com/${STATE_BACKUP_REPO}.git" backup-repo
cp export_state.json backup-repo/export_state.json
cd backup-repo
git add export_state.json
git diff --cached --quiet || git commit -m "Backup $(date -u +%Y-%m-%d)"
# extraheader только на эту команду: в конфиге клона секрета нет.
git -c "http.https://github.com/.extraheader=AUTHORIZATION: basic $(printf 'x-access-token:%s' "${STATE_BACKUP_TOKEN}" | base64 -w0)" \
push origin HEAD:main
- name: Alert backup failure to Telegram
# Бэкап — единственный резерв истории чатов, и раньше его провал жил только
# в логах Actions (аудит M11, 10.2026). Состояние выводится из секретов:
# без них шаг тихо пропускается и падение всё равно уходит в сводку ниже.
# STATE_ALERT_TOKEN — токен бота-сканера (лumen_smoke_bot), без прав админа
# STATE_ALERT_CHAT — личный chat_id владельца с этим ботом
# Токен не печатаем и не кладём в URL: он в extraheader-подобном Bearer.
if: failure()
env:
STATE_ALERT_TOKEN: ${{ secrets.STATE_ALERT_TOKEN }}
STATE_ALERT_CHAT: ${{ secrets.STATE_ALERT_CHAT }}
run: |
if [ -z "${STATE_ALERT_TOKEN}" ] || [ -z "${STATE_ALERT_CHAT}" ]; then
echo "alert secrets not set — skipping Telegram alert" >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
curl -sf --retry 2 --max-time 20 -X POST \
"https://api.telegram.org/bot${STATE_ALERT_TOKEN}/sendMessage" \
-H "Content-Type: application/json" \
-d "$(jq -nc --arg c "$STATE_ALERT_CHAT" \
--arg t "Lumen: ежедневный бэкап состояния ПРОВАЛИЛСЯ. Проверь вкладку Actions." \
'{chat_id:$c, text:$t}')" \
|| echo "Telegram alert failed" >> "$GITHUB_STEP_SUMMARY"
- name: Report backup failure
if: failure()
run: |
echo "## State backup FAILED — проверьте вкладку Actions" >> "$GITHUB_STEP_SUMMARY"
exit 1