# Публикация сайта через Gitea Actions и VPS Статус: публикация на VPS реализована 2026-08-05. После восстановления доступа к GitHub выбран встроенный push mirror Gitea для синхронизации репозиториев; рабочий origin остается в Gitea. Инструкции по восстановлению и эксплуатации находятся в [`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 Изменения из основной Gitea передаются на GitHub встроенным push mirror. Ссылки для учащихся ведут на GitHub-зеркала, основной домен остается на VPS. Workflow в `.github/workflows/deploy-site.yml` сохранен для резервной публикации. Перед использованием резерва нужно проверить настройки Pages и успешный деплой: наличие зеркала репозитория само по себе этого не подтверждает. 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 служит ручным резервом после проверки резервной публикации | | После переключения DNS HTTPS ещё не готов | DNS TTL уменьшается заранее; Certbot запускается сразу после подтверждения публичного HTTP; короткий перерыв принят как допустимый | | Сертификат GitHub Pages не готов во время аварии | Допускается задержка; на время восстановления используется стандартный адрес GitHub Pages | | GitHub-резерв отстает от Gitea | Проверяем результат push mirror, совпадение коммитов и резервную сборку; до успешной проверки резерв не считается готовым | ## Проверка реализации - 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 остаются актуальными. Документация проекта описывает Gitea Actions и VPS как основной контур публикации. Ссылки репозитория в `mkdocs.yml` направлены на GitHub для менти; рабочий origin остается в Gitea. ## Синхронизация GitHub Способ синхронизации выбран: встроенный Gitea push mirror. Рабочие изменения вносятся в Gitea, GitHub используется как зеркало для чтения и создания учебных форков. Нужно отдельно проверить работу зеркал всех стендов и готовность резервной публикации Pages.