fix(backup): ссылка на архив переживает F5 и повторное скачивание

HEAD-проба в downloadBackup() сжигала одноразовый тикет: Express 4
прогоняет HEAD через GET-хендлер /api/backup/:token, который удалял
тикет и файл до отдачи архива, поэтому настоящий GET всегда получал
404 «Ссылка на бэкап устарела».

- server.js: GET /api/backup/:token больше не удаляет тикет и файл —
  ссылка живёт BACKUP_TTL_MS (30 мин), Range-докачка работает
- вынес dropBackupTicket(), fs.rmSync обёрнут в try/catch
- BACKUP_TICKETS_MAX = 3: лишние тикеты вычищаются по возрасту
- sweepBackupStorage() режет с запасом 5 минут сверх TTL
- settings.js: убрана HEAD-проба, в #backupStatus рендерится реальная
  кликабельная ссылка вместо невидимого синтетического <a>
- AGENTS.md: раздел 8 — новое поведение + предупреждение про HEAD
This commit is contained in:
dev
2026-10-04 09:07:16 +03:00
parent 76d36e5c35
commit 19b2365b4c
3 changed files with 34 additions and 25 deletions
+2 -1
View File
@@ -155,7 +155,8 @@ This document defines how AI agents should work with the WhatIDo codebase. Follo
- Cert: app generates self-signed cert at build (`certs/cert.pem`), mounted into tailscale container
### 8. Backup / Restore
- **Admin UI**: `POST /api/backup` → тикет + `GET /api/backup/:token` (разовая ссылка, 30 мин), `POST /api/restore` (upload `.tar.gz`)
- **Admin UI**: `POST /api/backup` → тикет + `GET /api/backup/:token` (ссылка живёт `BACKUP_TTL_MS`, 30 мин, **скачивание можно повторять** — в том числе после обрыва связи и F5; не «сжигать» тикет `HEAD`-пробой, Express 4 отдаёт HEAD через GET-хендлер), `POST /api/restore` (upload `.tar.gz`)
- **Хранение тикетов**: in-memory `Map` (`server.js`), поэтому после рестарта приложения ссылка даёт 404 — это ожидаемо. Одновременно живых тикетов не больше `BACKUP_TICKETS_MAX`, лишние и истёкшие вычищаются `pruneBackupTickets()`; файлы доживают `sweepBackupStorage()` с запасом в 5 минут сверх TTL
- **Scripts**: `scripts/backup.sh`, `scripts/restore.sh` (host-level)
- Формат архива: `tar.gz` с `data.json` + `uploads/`. Версия формата — `BACKUP_FORMAT_VERSION` в `backup-restore.js` (сейчас `2`), принимаются версии `1..2`; версия пишется в `data.json.version` и возвращается в ответе `POST /api/backup` и `POST /api/restore`
- `data.json` содержит `version`, `created_at`, `app` (версия/коммит), `counts` (строки по таблицам + `files`) и сами данные. Таблицы перечислены в `BACKUP_TABLES` — **при добавлении таблицы править её и в `buildBackupArchive`, и здесь**