# CF7 Spam → Telegram — Self-Hosted Updates from Gitea (1.2.0) — DRAFT **Статус:** DRAFT — дизайн обсуждён в брейнсторме, пользователь подтвердил разделы 1 и 2. Не финализировано (нет финального spec-документа без суффикса DRAFT, нет implementation plan, нет beads-подзадач). **Что нужно сделать в новой сессии:** 1. Перечитать этот документ. 2. Спросить пользователя: «По дизайну self-hosted updates (1.2.0) всё ок? Если да — финализирую spec (без DRAFT), пробегусь по self-review, попрошу глянуть, потом writing-plans». 3. Если ок — `git mv ...DRAFT.md` в финальное имя `2026-04-21-cf7stg-self-hosted-updates-design.md`, повторить self-review, попросить пользователя глянуть финал, потом invoke `superpowers:writing-plans`. --- ## Контекст **Trigger:** только что выпустили v1.1.0 (silent drop). Деплой 1.1.0 на домработница.рус сделан вручную через `rsync`. Этот же плагин будет деплоиться на другие сайты (washanyanya.ru, потенциально другие). Хочется, чтобы WP-сайты сами получали обновления через стандартный «Плагины → Обновления», без `rsync` и без знания внутренностей. **Решено в брейнсторме:** - Gitea репо `bryzgalov/cf7-spam-to-telegram` сделан **публичным** (пользователь подтвердил). В коде нет секретов. - Используем **Plugin Update Checker v5** от YahnisElsts, vendored в `includes/lib/plugin-update-checker/`. - В режиме «Generic JSON server»: PUC ходит в статичный `wp-plugin-info.json` в корне репо (через Gitea raw URL). - Zip-релизы хранятся как Gitea Release assets (создаются вручную через скрипт `release.sh`, чистые благодаря `.gitattributes export-ignore`). ## Scope **В scope:** - Vendored PUC v5 в `includes/lib/plugin-update-checker/`. - Файл `wp-plugin-info.json` в корне репо (Generic JSON для PUC). - Инициализация PUC в `Plugin::boot()` через `register_update_checker()`. - `.gitattributes` с `export-ignore` для всех dev-файлов. - Bash-скрипт `release.sh` (bump version → commit → tag → push → build clean zip → Gitea Release + asset upload). - Минимальные тесты на инвариант (PUC файл существует, JSON валидный, version в JSON совпадает с константой плагина). - Версия 1.2.0 + changelog. **Вне scope:** - Кастомный `GiteaApi extends Api` (не нужен — публичный JSON). - Update-сервер / прокси (не нужен — статичный JSON через raw URL). - CI/Gitea Actions для авто-сборки релизов (вручную через release.sh достаточно). - License key management. - Авто-rollback на старую версию. - Уведомления о доступном обновлении куда-либо кроме стандартной WP-админки. ## Архитектура ``` Локальная разработка ↓ ./release.sh 1.2.0 git push origin main --tags git archive --worktree-attributes → cf7-spam-to-telegram-1.2.0.zip POST Gitea API /releases → создаёт Release объект POST Gitea API /releases/{id}/assets → uploads zip WP-сайт (плагин 1.2.x уже установлен) ↓ Plugin::boot() → register_update_checker() ↓ PUC раз в 12 часов (стандартный WP transient) GET https://git.netranking.ru/bryzgalov/cf7-spam-to-telegram/raw/branch/main/wp-plugin-info.json ↓ парсит, сравнивает version с CF7STG_VERSION ↓ если новее → WP «Плагины → Обновления» показывает кнопку «Обновить» WP качает zip по download_url (Gitea release asset, public, без auth) ↓ распаковывает в wp-content/plugins/cf7-spam-to-telegram/ Плагин обновлён. ``` ## Изменяемые файлы | Файл | Что | |---|---| | `includes/lib/plugin-update-checker/` (новая папка, ~50 файлов) | Распакованный PUC v5 release с https://github.com/YahnisElsts/plugin-update-checker. Закоммичен целиком. | | `wp-plugin-info.json` (новый, корень репо) | Статичная metadata, формат Generic JSON для PUC. | | `includes/class-plugin.php` | В `boot()` добавляется `self::register_update_checker()`. Новый private method. | | `.gitattributes` (изменяется) | `export-ignore` правила для всех dev-файлов. | | `release.sh` (новый, корень, `chmod +x`) | Bash-скрипт релиза. | | `cf7-spam-to-telegram.php` | Bump до `1.2.0` (header `Version:` + константа `CF7STG_VERSION`). | | `readme.txt` | Bump `Stable tag: 1.2.0` + Changelog `= 1.2.0 =`. | | `.gitignore` | Добавляется `dist/`. | ## `wp-plugin-info.json` — структура ```json { "name": "CF7 Spam → Telegram", "slug": "cf7-spam-to-telegram", "version": "1.2.0", "download_url": "https://git.netranking.ru/bryzgalov/cf7-spam-to-telegram/releases/download/v1.2.0/cf7-spam-to-telegram-1.2.0.zip", "homepage": "https://git.netranking.ru/bryzgalov/cf7-spam-to-telegram", "requires": "6.0", "tested": "6.9", "requires_php": "7.4", "last_updated": "2026-04-21 17:00:00", "author": "Vladimir Bryzgalov", "sections": { "description": "Forwards CF7 spam submissions to Telegram with heuristic classification (client/unclear/spam/drop labels).", "changelog": "<извлечённый из readme.txt блок == Changelog == ==>" } } ``` ## `Plugin::boot()` изменение ```php public function boot(): void { Settings::register_classifier_filters(); Dispatcher::register_hooks(); (new CF7Source())->register(); self::register_update_checker(); // ← новый if (is_admin()) { (new Settings_Page())->register(); (new Dashboard_Widget())->register(); } } private static function register_update_checker(): void { $loader = CF7STG_PLUGIN_DIR . 'includes/lib/plugin-update-checker/plugin-update-checker.php'; if (!is_file($loader)) return; // не падаем, если папка отсутствует require_once $loader; if (!class_exists('YahnisElsts\\PluginUpdateChecker\\v5\\PucFactory')) return; \YahnisElsts\PluginUpdateChecker\v5\PucFactory::buildUpdateChecker( 'https://git.netranking.ru/bryzgalov/cf7-spam-to-telegram/raw/branch/main/wp-plugin-info.json', CF7STG_PLUGIN_FILE, 'cf7-spam-to-telegram' ); } ``` ## `release.sh` (главные шаги) ``` release.sh 1.2.0 → 1. validate (semver, clean tree, на main, есть ~/.gitea-token) 2. bump version в cf7-spam-to-telegram.php (header + define) и readme.txt (Stable tag) 3. update wp-plugin-info.json (version, download_url, last_updated, sections.changelog ← из readme.txt) 4. git add + git commit -m "release: 1.2.0" + git tag v1.2.0 + git push origin main --tags 5. mkdir -p dist git archive --worktree-attributes --format=zip --prefix=cf7-spam-to-telegram/ -o dist/cf7-spam-to-telegram-1.2.0.zip v1.2.0 6. POST /api/v1/repos/.../releases (создать Release объект) 7. POST /api/v1/repos/.../releases/{id}/assets (загрузить zip как asset) 8. echo URLs (release page, asset URL, PUC JSON URL) ``` Token хранится в `~/.gitea-token` (chmod 600), не в репо. Создание токена — единожды, в Gitea UI: `user/settings/applications` → scope `write:repository`. ## `.gitattributes` правила ``` # Исключаем dev-файлы из git archive (release zip) tests/ export-ignore docs/ export-ignore composer.json export-ignore composer.lock export-ignore phpunit.xml export-ignore .phpunit.result.cache export-ignore .beads/ export-ignore .claude/ export-ignore CLAUDE.md export-ignore AGENTS.md export-ignore release.sh export-ignore dist/ export-ignore .gitignore export-ignore .gitattributes export-ignore .DS_Store export-ignore ``` **ВАЖНО:** `includes/lib/plugin-update-checker/` НЕ исключаем — она нужна в zip! ## Тесты `tests/UpdateCheckerIntegrationTest.php` (extends `Cf7stgTestCase`): - `test_puc_loader_file_exists()` — `includes/lib/plugin-update-checker/plugin-update-checker.php` существует. - `test_puc_factory_class_loadable_after_require()` — после `require_once` лоадера класс `YahnisElsts\PluginUpdateChecker\v5\PucFactory` доступен. - `test_wp_plugin_info_json_is_valid()` — `wp-plugin-info.json` парсится, содержит обязательные ключи `name, slug, version, download_url, sections`. - `test_wp_plugin_info_json_version_matches_plugin_constant()` — `wp-plugin-info.json::version` совпадает с `CF7STG_VERSION` из `cf7-spam-to-telegram.php` (regex extract). Защита: release.sh забыл обновить JSON — тест поймает. - `test_wp_plugin_info_json_download_url_matches_version()` — `download_url` содержит `/releases/download/v{version}/`. `tests/ZipArchiveBuildTest.php` (опционально, требует git в `$PATH`): - `test_git_archive_includes_runtime_dirs()` — `git archive HEAD --worktree-attributes -o /tmp/test.zip` распаковывается, содержит `cf7-spam-to-telegram.php`, `includes/`, `admin/`, `includes/lib/plugin-update-checker/plugin-update-checker.php`. - `test_git_archive_excludes_dev_dirs()` — НЕТ `tests/`, `docs/`, `composer.json`, `release.sh`. ## Error handling - Отсутствие PUC папки на сайте: `register_update_checker` делает `is_file` + `class_exists` → плагин не падает, обновлений просто нет (как до 1.2.0). - Недоступность Gitea (network/down/4xx): PUC сам логирует через `error_log`, ничего не показывает в админке. Следующая итерация (12ч) повторит. Юзер не видит «обновлений» — всё. - Bad JSON в `wp-plugin-info.json`: PUC скипает, error_log. - Версия в JSON совпадает с локальной → no update notice. Норм. ## Rollout 1. Локально: создать токен в Gitea (один раз), сохранить в `~/.gitea-token`. 2. Локально: подготовить v1.2.0 (vendor PUC, добавить файлы, написать release.sh, тесты). 3. PHPUnit зелёный → `./release.sh 1.2.0` → tag + push + Release + asset. 4. На домработница.рус (где сейчас 1.1.0) — один последний раз через `rsync` обновить до 1.2.0. Активировать плагин (auto-seed уже идемпотентный). PUC начинает работать. 5. Все будущие версии — `./release.sh 1.x.y`, и через 12ч (или после ручного «Проверить обновления») WP-сайты сами обновятся. ## Версионирование - `1.1.0` → `1.2.0` (semver minor: новая фича, обратная совместимость). - Changelog в `readme.txt`: ``` = 1.2.0 = * New: Self-hosted plugin updates from Gitea via plugin-update-checker (Yahnis Elsts v5). WP «Плагины → Обновления» теперь показывает обновления автоматически (раз в 12ч), скачивая чистый zip из Gitea Releases. Источник: https://git.netranking.ru/bryzgalov/cf7-spam-to-telegram/raw/branch/main/wp-plugin-info.json. * Dev: release.sh — однострочный релиз (bump version → commit → tag → push → build clean zip via git archive --worktree-attributes → Gitea Release + asset upload). * Dev: .gitattributes export-ignore для tests/, docs/, composer.*, phpunit.xml, .beads/, .claude/, CLAUDE.md, AGENTS.md, release.sh — git archive собирает чистый zip без dev-файлов. ``` ## Открытые вопросы для следующей сессии Эти моменты НЕ обсуждены с пользователем явно — могут потребовать одного-двух уточнений перед finalizing spec: 1. **Какую точно версию PUC v5 vendor'им?** Скачать последний release с https://github.com/YahnisElsts/plugin-update-checker/releases (на 2026-04-21 это видимо `5.x.x`). В spec зафиксировать конкретную версию для воспроизводимости. 2. **Уважает ли Gitea raw URL HTTPS на git.netranking.ru cache headers?** PUC ставит `If-Modified-Since`, важно чтобы сервер не ломал. Проверить вручную через `curl -I https://git.netranking.ru/.../raw/branch/main/wp-plugin-info.json` — если есть `Last-Modified` / `ETag` — отлично. 3. **Минификация PUC?** Vendored PUC ~50 файлов / ~300KB. Не критично, но можно опционально удалить `examples/`, `tests/`, `docs/` из vendored папки. Рассмотреть как nit. 4. **Поддержка Gitea Actions** — пользователь не запрашивал, но если есть желание авто-релизов на push tag — это +1 эпик в будущем.