ci(site): добавлена публикация через Gitea Actions

- Зачем:
  - основной сайт не должен зависеть от заблокированной учётной записи GitHub.
- Что:
  - добавлены строгая сборка и атомарная публикация через repository-scoped runner.
  - добавлены воспроизводимые конфигурации systemd, nginx и эксплуатационный runbook.
  - проектная документация и ссылки на репозиторий обновлены для Gitea.
- Проверка:
  - выполнены mkdocs build --strict, Bash/YAML-проверки и локальный HTTP smoke-check.
  - runner зарегистрирован, ограничен средствами systemd и виден в Gitea как online.
This commit is contained in:
2026-08-04 15:16:33 -04:00
parent 8584fc225b
commit 1edda33f87
10 changed files with 408 additions and 11 deletions
+157
View File
@@ -0,0 +1,157 @@
# Эксплуатация публикации `de.dementev.space`
Этот runbook реализует спецификацию
[`2026-08-04-gitea-vps-site-publishing.md`](../../specs/2026-08-04-gitea-vps-site-publishing.md).
Команды рассчитаны на Ubuntu 26.04 и Gitea 1.27.
## Зафиксированные параметры
- Gitea Runner: `2.3.0`, Linux amd64.
- SHA256: `1e9fb1bea022fdf40993ecbc1a13e87db1bfd3d7f42666e37f15f71d840d53b3`.
- Runner: repository-scoped, метка `de-roadmap-host:host`.
- Пользователь сервиса: `gitea-runner`, без `sudo` и Docker.
- Корень публикации: `/srv/de-roadmap`.
- Хранение: текущий релиз и две предыдущие версии.
## Подготовка VPS
Установить HTTP-сервер и Certbot:
```bash
sudo apt-get update
sudo apt-get install nginx certbot python3-certbot-nginx
```
Создать пользователя и каталоги:
```bash
sudo useradd \
--system \
--home-dir /var/lib/gitea-runner \
--create-home \
--shell /usr/sbin/nologin \
gitea-runner
sudo install -d -o gitea-runner -g gitea-runner -m 0755 \
/var/lib/gitea-runner/workspaces \
/srv/de-roadmap \
/srv/de-roadmap/releases
sudo install -d -o root -g root -m 0755 /etc/gitea-runner
```
Скачать runner во временный каталог, сверить checksum и установить root-owned
бинарник в `/usr/local/bin/gitea-runner`:
```bash
runner_tmp_dir=$(mktemp -d /tmp/de-roadmap-runner.XXXXXX)
curl -fsSLo "${runner_tmp_dir}/gitea-runner" \
https://dl.gitea.com/gitea-runner/2.3.0/gitea-runner-2.3.0-linux-amd64
printf '%s %s\n' \
'1e9fb1bea022fdf40993ecbc1a13e87db1bfd3d7f42666e37f15f71d840d53b3' \
"${runner_tmp_dir}/gitea-runner" \
| sha256sum --check
sudo install -o root -g root -m 0755 \
"${runner_tmp_dir}/gitea-runner" /usr/local/bin/gitea-runner
rm "${runner_tmp_dir}/gitea-runner"
rmdir "$runner_tmp_dir"
```
Из корня репозитория установить конфигурацию и unit:
```bash
sudo install -o root -g root -m 0644 \
project/ops/gitea-vps-site/gitea-runner.yaml \
/etc/gitea-runner/config.yaml
sudo install -o root -g root -m 0644 \
project/ops/gitea-vps-site/gitea-runner.service \
/etc/systemd/system/gitea-runner.service
sudo systemd-analyze verify /etc/systemd/system/gitea-runner.service
```
## Регистрация runner
Repository registration token получают в Gitea:
`ddmitry/de-roadmap` → Settings → Actions → Runners. Токен не сохраняют в Git
или shell history. Временный файл с токеном создают с владельцем
`gitea-runner:gitea-runner` и режимом `0600`. После регистрации файл
`/var/lib/gitea-runner/.runner` должен принадлежать тому же пользователю и
иметь режим `0600`.
Token-file должен содержать ровно 40 символов без завершающего перевода строки.
При извлечении JSON-ответа через `jq` использовать `jq --join-output '.token'`,
а не `jq --raw-output`, который добавляет newline.
```bash
sudo -u gitea-runner \
/usr/local/bin/gitea-runner \
--config /etc/gitea-runner/config.yaml \
register \
--no-interactive \
--instance https://git.dementev.space \
--token-file /var/lib/gitea-runner/.registration-token \
--name de-roadmap-vps
sudo rm /var/lib/gitea-runner/.registration-token
sudo chmod 0600 /var/lib/gitea-runner/.runner
sudo systemctl daemon-reload
sudo systemctl enable --now gitea-runner
```
Временный файл с токеном удаляют сразу после успешной регистрации.
## Nginx и первичная публикация
Из корня репозитория установить virtual host, создать ссылку и только затем
перечитать проверенную конфигурацию:
```bash
sudo install -o root -g root -m 0644 \
project/ops/gitea-vps-site/nginx.conf \
/etc/nginx/sites-available/de-roadmap
sudo ln -s \
/etc/nginx/sites-available/de-roadmap \
/etc/nginx/sites-enabled/de-roadmap
sudo nginx -t
sudo systemctl reload nginx
```
Default-сайт можно отключить только после успешной проверки нового virtual
host.
До первого workflow можно собрать сайт вручную и опубликовать его тем же
скриптом с тестовым release id. Проверка до переключения DNS:
```bash
curl --header 'Host: de.dementev.space' http://127.0.0.1/
```
После локальной проверки разрешить профили `Nginx Full` в UFW. До этого
публичные порты `80/tcp` и `443/tcp` должны оставаться закрытыми.
## DNS и TLS
1. Уменьшить TTL записи `de.dementev.space`.
2. Направить `A` на VPS; удалить или корректно направить `AAAA`.
3. Убедиться, что сайт доступен извне по HTTP.
4. Выпустить сертификат:
```bash
sudo certbot --nginx -d de.dementev.space
```
5. Проверить HTTPS и `systemctl status certbot.timer`.
6. Вернуть обычный DNS TTL.
## Проверка и откат
Активная версия определяется ссылкой `/srv/de-roadmap/current`. Для ручного
отката создать временную ссылку на нужный каталог в `releases/` и атомарно
заменить `current` через `mv -Tf`. Перед удалением релиза всегда проверять
результат `readlink -f /srv/de-roadmap/current`.
Диагностика:
```bash
systemctl status gitea-runner
journalctl -u gitea-runner
nginx -t
curl --header 'Host: de.dementev.space' http://127.0.0.1/
```
@@ -0,0 +1,47 @@
[Unit]
Description=Gitea Actions runner for de-roadmap
Documentation=https://docs.gitea.com/usage/actions
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=gitea-runner
Group=gitea-runner
WorkingDirectory=/var/lib/gitea-runner
ExecStart=/usr/local/bin/gitea-runner daemon --config /etc/gitea-runner/config.yaml
Restart=always
RestartSec=10s
TimeoutStopSec=45s
KillMode=mixed
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin"
Environment="UV_CACHE_DIR=/var/lib/gitea-runner/.cache/uv"
Environment="UV_PYTHON_DOWNLOADS=never"
UMask=0022
NoNewPrivileges=true
PrivateDevices=true
PrivateTmp=true
ProtectClock=true
ProtectControlGroups=true
ProtectHome=true
ProtectHostname=true
ProtectKernelLogs=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectSystem=strict
ReadWritePaths=/var/lib/gitea-runner /srv/de-roadmap
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
RestrictRealtime=true
RestrictSUIDSGID=true
LockPersonality=true
CapabilityBoundingSet=
SystemCallArchitectures=native
MemoryMax=1G
CPUQuota=100%
TasksMax=128
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,37 @@
log:
level: info
runner:
file: /var/lib/gitea-runner/.runner
capacity: 1
timeout: 15m
shutdown_timeout: 30s
insecure: false
fetch_timeout: 5s
fetch_interval: 2s
fetch_interval_max: 10s
workdir_cleanup_age: 24h
idle_cleanup_interval: 10m
labels:
- "de-roadmap-host:host"
allocate_pty: false
cache:
enabled: false
container:
valid_volumes: []
docker_host: "-"
require_docker: false
host:
workdir_parent: /var/lib/gitea-runner/workspaces
health_check:
enabled: true
min_free_disk_space_mb: 1024
interval: 30s
timeout: 10s
metrics:
enabled: false
+21
View File
@@ -0,0 +1,21 @@
server {
listen 80;
listen [::]:80;
server_name de.dementev.space;
root /srv/de-roadmap/current;
index index.html;
charset utf-8;
server_tokens off;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location / {
try_files $uri $uri/ =404;
}
location ~ /\. {
deny all;
}
}