# Публикация сайта через Gitea Actions и VPS Статус: реализовано 2026-08-05; механизм синхронизации GitHub-резерва требует отдельного решения после восстановления доступа к GitHub. Инструкции по восстановлению и эксплуатации находятся в [`project/ops/gitea-vps-site/README.md`](../ops/gitea-vps-site/README.md). ## Проблема Сайт `de.dementev.space` публиковался через GitHub Actions и GitHub Pages. После блокировки учётной записи GitHub основной домен стал недоступен, хотя исходный Markdown и конфигурация MkDocs сохранились, а основной Git-репозиторий уже размещён в собственной Gitea. Публикация сайта не должна зависеть от доступности учётной записи GitHub. При этом рабочий процесс из исходной архитектуры сохраняется: изменение попадает в `main`, сайт собирается и обновляется автоматически без отдельного ручного деплоя. ## Цели - Публиковать `de.dementev.space` с существующей VPS. - Запускать сборку и публикацию автоматически после push в `main` в Gitea. - Не заменять работающую версию сайта, если получение исходников или строгая сборка MkDocs завершились ошибкой. - Ограничить права runner каталогами, необходимыми для сборки и публикации сайта. - Сохранить GitHub Pages как заранее собираемый резерв на случай проблем с VPS. - Сохранить минимальные накладные расходы и нулевые дополнительные расходы на хостинг. ## Не цели - Автоматическое переключение между VPS и GitHub Pages. - Жёсткие требования к времени восстановления или высокой доступности. - Перенос еженедельной проверки внешних ссылок с GitHub Actions в Gitea Actions. - Изменение существующих файлов в `.github/workflows/`. - Добавление динамического приложения, авторизации или серверной базы данных. ## Исходное состояние на 2026-08-04 - MkDocs Material собирает статический каталог `site/`; `site_url` уже равен `https://de.dementev.space/`. - Публичный репозиторий `ddmitry/de-roadmap` в Gitea является текущим `origin`. - Gitea Actions для репозитория включены, но подходящего runner пока нет. - Gitea видит существующие GitHub workflows, однако они зависят от GitHub Pages, GitHub-токенов и сторонних actions. - На VPS, выбранной для сайта, пока нет HTTP-сервера; публично доступен только SSH. ## Выбранное решение ### Основной контур публикации Gitea становится источником событий для основной публикации. На VPS с сайтом работает repository-scoped Gitea runner, зарегистрированный только для `de-roadmap`. Runner использует host mode и отдельную метку, предназначенную только для этого workflow. Workflow хранится отдельно в `.gitea/workflows/deploy-site.yml`. Gitea выбирает `.gitea/workflows` раньше `.github/workflows`, поэтому GitHub-specific workflows сохраняются в репозитории, но не исполняются Gitea. Workflow запускается после push в `main` и вручную. Он получает конкретный commit из публичного Gitea-репозитория обычным Git, создаёт изолированное Python-окружение, устанавливает закреплённые версии зависимостей и выполняет строгую сборку MkDocs. Сторонние `uses:` не применяются, поэтому сборка не зависит от GitHub Actions Marketplace. ### Изоляция runner Runner работает как отдельный непривилегированный системный пользователь. У него нет `sudo`, членства в группе `docker` и доступа на запись к конфигурации nginx, сертификатам или другим сервисам VPS. Пользователь runner может записывать только в собственный рабочий каталог и каталог релизов сайта. Workflow не запускается для pull request из недоверенных веток. Потребление памяти, CPU и количество процессов ограничиваются средствами менеджера сервисов операционной системы. ### Публикация и восстановление после ошибки Каждая успешная сборка создаёт отдельную версию статического сайта. Новая версия становится активной только после завершения всех проверок. Переключение между версиями выполняется атомарно; частично собранный каталог никогда не становится корнем сайта. Ошибка получения исходников, установки зависимостей или сборки оставляет активной предыдущую версию. Как минимум одна предыдущая успешная версия сохраняется для быстрого ручного отката. ### HTTP и HTTPS Статические файлы обслуживает штатный nginx из репозитория Ubuntu. Nginx только читает активную версию сайта и не требует перезагрузки при обычной публикации контента. TLS-сертификат для `de.dementev.space` получает и продлевает Certbot с интеграцией nginx. Перед переключением проверяются локальная сборка и конфигурация nginx, а DNS TTL заранее уменьшается. Затем DNS направляется на VPS, проверяется публичная доступность по HTTP и выпускается сертификат. Короткий интервал между переключением DNS и готовностью HTTPS допустим, поскольку жёсткого требования к непрерывной доступности нет. ### Резерв на GitHub Pages После восстановления доступа к GitHub существующий GitHub workflow продолжает собирать сайт из GitHub-репозитория. Чтобы резерв оставался актуальным, изменения из основной Gitea должны автоматически синхронизироваться с GitHub. Предпочтительный кандидат — встроенный Gitea push mirror; окончательный механизм и его права будут согласованы отдельно после восстановления учётной записи. Резерв доступен по стандартному адресу GitHub Pages, но основной домен направлен на VPS. GitHub Pages считается тёплым резервом контента и холодным резервом домена. При отказе VPS владелец вручную переключает DNS и при необходимости повторно активирует custom domain в настройках GitHub Pages. Допустимы задержка распространения DNS и ожидание выпуска TLS-сертификата; автоматический failover не требуется. ## Отклонённые варианты - **Периодический pull с VPS:** проще инфраструктурно, но не даёт опыта работы с Gitea runner и менее наглядно связывает push с результатом сборки. - **Runner рядом с Gitea:** усложняет доставку результата на VPS и позволяет сборкам влиять на ресурсы Git-сервера. - **Jobs в Docker:** дают лучшую изоляцию, но требуют доступа runner к Docker и отдельного механизма передачи результата в каталог nginx. Для одного доверенного репозитория это лишняя сложность. - **Caddy вместо nginx:** упрощает автоматический TLS, но штатный nginx лучше соответствует предпочтению владельца и доступен с обновлениями безопасности из основного репозитория Ubuntu. Certbot закрывает задачу TLS отдельно. - **Cloudflare Pages, GitLab Pages или другой внешний Pages-сервис:** уменьшают нагрузку на VPS, но добавляют новую внешнюю учётную запись и зависимость, от которой как раз уходим. - **Ожидание разблокировки GitHub:** сохраняет старую архитектуру, но оставляет сайт недоступным на неопределённый срок. ## Риски и меры | Риск | Мера | |------|------| | Workflow исполняет команды непосредственно на VPS | Repository-scoped runner, отдельный пользователь без `sudo` и Docker, узкие права на запись, запуск только из `main`, системные лимиты ресурсов | | Ошибка сборки ломает опубликованный сайт | Сборка в отдельной версии и атомарное переключение только после успеха | | Сторонний action выполняет неожиданный код | Workflow состоит из собственных команд и не использует `uses:` | | Gitea временно недоступна | Уже опубликованный сайт обслуживается независимо от Gitea | | VPS недоступна или потеряна | Исходники остаются в Gitea; GitHub Pages служит ручным резервом после восстановления GitHub | | После переключения DNS HTTPS ещё не готов | DNS TTL уменьшается заранее; Certbot запускается сразу после подтверждения публичного HTTP; короткий перерыв принят как допустимый | | Сертификат GitHub Pages не готов во время аварии | Допускается задержка; на время восстановления используется стандартный адрес GitHub Pages | | GitHub-резерв отстаёт от Gitea | После восстановления GitHub настраивается автоматическая синхронизация; до выбора механизма резерв не считается тёплым | ## Проверка реализации - Runner после перезагрузки VPS автоматически подключается к Gitea и принимает только jobs с выделенной меткой. - Push тестового изменения в `main` запускает ровно один Gitea workflow и публикует соответствующий commit. - Gitea не запускает workflows из `.github/workflows/`, а сами файлы остаются без изменений. - Workflow не обращается к GitHub за actions и не требует GitHub-токенов. - Ошибка `mkdocs build --strict` завершает workflow с ошибкой и не изменяет публичную версию сайта. - Успешная сборка переключает сайт целиком, без периода частично обновлённого содержимого. - Пользователь runner не может использовать `sudo`, Docker или изменять конфигурацию nginx. - `https://de.dementev.space/` отдаёт собранный сайт с действующим сертификатом после переключения DNS. - Nginx и runner восстанавливаются после перезагрузки VPS без ручного запуска. - После восстановления GitHub и настройки синхронизации резервная сборка получает тот же commit и остаётся доступна по стандартному адресу GitHub Pages. ## Влияние на документацию Эта спецификация заменяет решения о публикации и custom domain из `project/ADR.md`. Решения того документа о MkDocs Material, структуре файлов и dual-compatible links остаются актуальными. После реализации нужно обновить `AGENTS.md`, `project/PRD.md` и `project/TODO.md`, чтобы они описывали фактический основной контур публикации. Ссылку на исходный репозиторий в `mkdocs.yml` следует направить на Gitea; ссылку на GitHub можно сохранить как дополнительную после восстановления доступа. ## Открытый вопрос После восстановления GitHub нужно окончательно выбрать способ автоматической синхронизации резервного репозитория. Базовый кандидат — Gitea push mirror с синхронизацией при каждом push; GitHub при этом становится read-only зеркалом, поскольку mirror перезаписывает расходящиеся изменения.