Files
de-roadmap/project/specs/2026-08-04-gitea-vps-site-publishing.md
T
ddadmin 4ad904efd0
Deploy MkDocs to VPS / deploy (push) Successful in 13s
docs(site): дополнены инструкции восстановления публикации
- Зачем:
  - runbook требовал незафиксированного контекста для первого деплоя и восстановления сайта на чистой VPS.
- Что:
  - добавлены prerequisites, установка пакетов, первый workflow, UFW и проверенный атомарный откат.
  - спецификация и PRD обновлены по факту завершённого переноса на VPS.
  - локальные команды Snap uv заменены на persistent Python venv.
- Проверка:
  - mkdocs build --strict выполнен через persistent venv пользователя gitea-runner.
  - последовательность rollback проверена на временном дереве releases и symlink.
2026-08-05 03:14:52 -04:00

15 KiB
Raw Blame History

Публикация сайта через Gitea Actions и VPS

Статус: реализовано 2026-08-05; механизм синхронизации GitHub-резерва требует отдельного решения после восстановления доступа к GitHub. Инструкции по восстановлению и эксплуатации находятся в project/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 перезаписывает расходящиеся изменения.