Деплой Hugo на VPS через GitHub Actions
Стек
- Hugo + Tailwind CSS + PostCSS
- GitHub Actions: build → rsync → VPS
- VPS: Ubuntu + nginx + certbot (Let’s Encrypt)
Порядок настройки
1. Node.js зависимости
Если тема использует Tailwind/PostCSS — обязателен postcss-cli:
1cd ru.blog
2npm install --save-dev postcss-cli
3git add package.json package-lock.json
4git commit -m "add postcss-cli"
Без postcss-cli Hugo не найдёт бинарник postcss через npx и билд упадёт.
2. Deploy-пользователь на VPS
1# Создать пользователя с ограниченным shell
2useradd -m -s /bin/rbash deploy
3
4# Директория для сайта
5mkdir -p /var/www/ru.blog.lis.im
6chown deploy:deploy /var/www/ru.blog.lis.im
7chmod 755 /var/www/ru.blog.lis.im
8
9# SSH-директория
10mkdir -p /home/deploy/.ssh
11chmod 700 /home/deploy/.ssh
12chown deploy:deploy /home/deploy/.ssh
Итоговые права:
drwxr-x--- deploy deploy /home/deploy/
drwx------ deploy deploy /home/deploy/.ssh/
drwxr-xr-x deploy deploy /var/www/ru.blog.lis.im/
nginx читает файлы сайта от своего пользователя (www-data), поэтому директория 755 — он сможет войти..ssh строго 700 — SSH демон отклонит ключ если права шире.
3. SSH-ключ
На локальной машине (не на VPS):
1ssh-keygen -t ed25519 -C "github-deploy-ru.blog" -f ~/.ssh/deploy_ru_blog
2# passphrase — оставить пустым, иначе GitHub Actions не сможет использовать ключ
Добавить публичный ключ на VPS:
1# Смотришь содержимое на локальной машине:
2cat ~/.ssh/deploy_ru_blog.pub
3
4# На VPS вставляешь в файл:
5nano /home/deploy/.ssh/authorized_keys
Формат строки в authorized_keys (без command= — он ломает rsync):
no-pty,no-agent-forwarding,no-port-forwarding,no-X11-forwarding ssh-ed25519 AAAA...ключ... github-deploy-ru.blog
Права на файл (строго, иначе SSH игнорирует):
1chmod 600 /home/deploy/.ssh/authorized_keys
2chown deploy:deploy /home/deploy/.ssh/authorized_keys
Проверить что всё правильно:
1ls -la /home/deploy/.ssh/
2# -rw------- deploy deploy authorized_keys ← должно быть именно так
4. sshd_config
Проверить, нет ли ограничений для не-root пользователей:
1sshd -T -C user=deploy | grep authenticationmethods
2# Должно вернуть: authenticationmethods publickey
Если возвращает keyboard-interactive — добавить блок перед Match User *,!root в /etc/ssh/sshd_config:
Match User deploy
AuthenticationMethods publickey
PasswordAuthentication no
KbdInteractiveAuthentication no
Применить:
1sshd -t && systemctl reload ssh
2# На Ubuntu сервис называется ssh, не sshd
5. GitHub Secrets
GitHub → репозиторий → Settings → Secrets and variables → Actions:
| Secret | Значение |
|---|---|
VPS_HOST | IP VPS |
VPS_USER | deploy |
VPS_SSH_KEY | содержимое ~/.ssh/deploy_ru_blog (приватный, с хедерами) |
VPS_SSH_PORT | порт SSH (узнать: ss -tlnp | grep sshd) |
VPS_DEPLOY_PATH | /var/www/ru.blog.lis.im/ |
VPS_SSH_KEY — полное содержимое файла включая строки:
-----BEGIN OPENSSH PRIVATE KEY-----
...
-----END OPENSSH PRIVATE KEY-----
6. nginx — конфиг до certbot
Создать файл:
1nano /etc/nginx/sites-available/ru.blog.lis.im
Только HTTP, без SSL — certbot упадёт если SSL-блок уже есть, но сертификата ещё нет:
1server {
2 listen 80;
3 listen [::]:80;
4 server_name ru.blog.lis.im;
5
6 root /var/www/ru.blog.lis.im;
7 index index.html;
8
9 location / {
10 try_files $uri $uri/ $uri.html =404;
11 }
12
13 error_page 404 /404.html;
14}
Активировать и проверить:
1ln -s /etc/nginx/sites-available/ru.blog.lis.im /etc/nginx/sites-enabled/
2nginx -t
3systemctl reload nginx
Убедиться что сайт открывается по HTTP перед запуском certbot:
1curl -I http://ru.blog.lis.im
2# HTTP/1.1 200 OK ← должно быть 200
7. SSL через certbot
1apt install certbot python3-certbot-nginx
2certbot --nginx -d ru.blog.lis.im
Certbot сам перепишет конфиг, добавит HTTPS-блок и редирект с 80 на 443.
После этого добавить в SSL-блок кеширование статики (найти блок server { listen 443 ...}):
1 location ~* \.(css|js|woff2?|png|jpg|ico|svg)$ {
2 expires 1y;
3 add_header Cache-Control "public, immutable";
4 }
1nginx -t && systemctl reload nginx
8. Итоговый конфиг nginx после certbot
Certbot генерирует примерно такое (плюс твои дополнения):
1server {
2 listen 80;
3 listen [::]:80;
4 server_name ru.blog.lis.im;
5 return 301 https://$host$request_uri;
6}
7
8server {
9 listen 443 ssl;
10 listen [::]:443 ssl;
11 server_name ru.blog.lis.im;
12
13 root /var/www/ru.blog.lis.im;
14 index index.html;
15
16 ssl_certificate /etc/letsencrypt/live/ru.blog.lis.im/fullchain.pem;
17 ssl_certificate_key /etc/letsencrypt/live/ru.blog.lis.im/privkey.pem;
18 include /etc/letsencrypt/options-ssl-nginx.conf;
19 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
20
21 location ~* \.(css|js|woff2?|png|jpg|ico|svg)$ {
22 expires 1y;
23 add_header Cache-Control "public, immutable";
24 }
25
26 location / {
27 try_files $uri $uri/ $uri.html =404;
28 }
29
30 error_page 404 /404.html;
31}
Типичные проблемы
postcss not found using npx
Нет postcss-cli в зависимостях. Сам postcss пакет не даёт CLI.
→ npm install --save-dev postcss-cli, закоммитить package.json и package-lock.json.
remote_path can not be empty
Секрет VPS_DEPLOY_PATH не добавлен в GitHub Secrets.
Connection refused на порту 22
SSH слушает другой порт.
→ ss -tlnp | grep sshd, добавить найденный порт в VPS_SSH_PORT.
Permission denied (keyboard-interactive)
1. Неправильный владелец или права на authorized_keys
Файл должен принадлежать deploy:deploy с правами 600, не root.
2. command= директива в authorized_keyscommand="rsync --server --daemon ." ломает GitHub Actions rsync.
Убрать command=..., оставить только no-pty,... ограничения.
3. sshd_config не разрешает publickey для deploy
Добавить Match User deploy блок с AuthenticationMethods publickey.
Проверить: sshd -T -C user=deploy | grep authenticationmethods
certbot: cannot load certificate
В nginx конфиге уже прописан SSL до получения сертификата.
→ Конфиг должен быть HTTP-only до запуска certbot. Убрать весь SSL, запустить certbot, он сам добавит.
sshd.service not found
На Ubuntu сервис называется ssh, не sshd.
→ systemctl reload ssh