Spaces:
Running
Running
SilverElixir
Telegram alert on state-backup failure instead of Actions summary only (audit M11)
2d6a16c Download .github/workflows/state-backup.yml from SilverElixir/Lumen: direct link, hf CLI and curl.
- Browser
- Download file 5.93 kB
-
https://huggingface.co/spaces/SilverElixir/Lumen/resolve/main/.github/workflows/state-backup.yml
- Command line
-
hf download hf://spaces/SilverElixir/Lumen/.github/workflows/state-backup.yml
-
curl -L -o state-backup.yml https://huggingface.co/spaces/SilverElixir/Lumen/resolve/main/.github/workflows/state-backup.yml
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 | |