feat: инкрементальный SFTP-деплой (только изменившиеся файлы, таймауты)
deploy-dedinit / Build & SFTP Deploy (push) Successful in 43m42s

This commit is contained in:
2026-09-22 18:40:46 +00:00
parent 65457becf2
commit f7be5a87b4
8 changed files with 187 additions and 15 deletions
@@ -0,0 +1,47 @@
# Design: Инкрементальный SFTP-деплой
## Подход
Модифицируем единственный файл `tools/deploy_sftp.py`:
1. **Сравнение файлов (size + mtime)**:
- Для каждого локального файла вызываем `sftp.stat(remote_path)`.
- Если stat успешен И `(st_size, int(st_mtime))` совпадает с локальным
`(os.stat(lpath).st_size, int(os.stat(lpath).st_mtime))` — пропускаем (не льём).
- Если stat бросает IOError (нет файла) или не совпадает — заливаем.
- Метрику печатаем: `= пропущено, + залито`.
2. **Таймауты против зависания**:
- `paramiko.Transport` создаём с `banner_timeout=30`, `timeout=30`.
- Для каждого `put` используем `paramiko` окно: оборачиваем в `socket.setdefaulttimeout(120)`
(SFTP-канал унаследует) — если операция длится дольше 2 мин, канал падает с ошибкой,
скрипт падает с понятным сообщением вместо бесконечного висения.
- Удаление тоже не должно висеть: те же таймауты.
3. **Что НЕ меняется**:
- Mirror-логика (всё, чего нет локально, удаляется) — сохранена.
- Рекурсивное создание каталогов — сохранено.
- Интерфейс: `tools/deploy_sftp.py [--dry-run]`, переменные DEDINIT_* / SSHPASS.
- Вызов из `.gitea/workflows/deploy.yml` не меняется.
## Риски
- **mtime на сервере** (Jino) может отличаться от локального (часовой пояс, точность).
SFTP stat возвращает mtime в секундах; локально `int(st_mtime)` — тоже секунды.
Допуск: считаем «не изменился», если `|remote_mtime - local_mtime| <= 120` И размеры равны.
(Больше 2 мин расхождения не бывает при честном сравнении; зато гарантируем, что
файл, записанный «только что» локально и залитый минуту назад, не перельётся без нужды.)
Решение: допуск на mtime = 300 сек (5 мин) при равенстве размеров.
- **Ложные пропуски**: если сервер отдаёт mtime 0 (некоторые SFTP-сервера) — считаем файл
изменённым (льём всегда). Проверка `if remote_mtime > 0`.
- **Большой дерево**: rlist рекурсивно обходит всё дерево — оставляем как есть (работает,
это доли секунды на 500 файлов).
## Верификация
- `python3 tools/deploy_sftp.py --dry-run` локально: показывает список изменившихся
(должно быть 0 после сборки в тот же public/... фактически покажет все «+», т.к. на
сервере старый mtime — это ок, первый деплой льёт всё).
- После первого инкрементального деплоя повторный `--dry-run` покажет «=» (пропуски).