Deploy MkDocs to VPS / deploy (push) Successful in 13s
- Зачем: - runbook требовал незафиксированного контекста для первого деплоя и восстановления сайта на чистой VPS. - Что: - добавлены prerequisites, установка пакетов, первый workflow, UFW и проверенный атомарный откат. - спецификация и PRD обновлены по факту завершённого переноса на VPS. - локальные команды Snap uv заменены на persistent Python venv. - Проверка: - mkdocs build --strict выполнен через persistent venv пользователя gitea-runner. - последовательность rollback проверена на временном дереве releases и symlink.
117 lines
15 KiB
Markdown
117 lines
15 KiB
Markdown
# Публикация сайта через 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 перезаписывает расходящиеся изменения.
|