nginx
Deploy Observability / Deploy Observability Stack (push) Successful in 36s
Deploy / Deploy to Data VPS (push) Successful in 42s

This commit is contained in:
2026-07-10 17:06:05 +07:00
parent 414667062c
commit 6e47498dc5
8 changed files with 285 additions and 51 deletions
+80
View File
@@ -0,0 +1,80 @@
name: Deploy Nginx
# Единственный публичный вход (443/80) на VPS с данными — system nginx
# (НЕ Docker, чтобы просто использовать certbot и его автопродление, без
# возни с сертификатами внутри контейнера). Только ручной запуск: nginx
# на этом сервере меняется редко, а сертификат/.htpasswd — одноразовые
# secrets-на-сервере, трогать их каждым push'ом смысла нет (тот же принцип,
# что и генерация сертификата Postgres в deploy.yml — один раз, не на
# каждый прогон).
#
# Порядок первого запуска:
# 1. Запустить эту джобу (поднимет http-конфиг + certbot + сам nginx).
# 2. Один раз руками на сервере:
# sudo apt-get install -y certbot
# sudo certbot certonly --webroot -w /var/www/certbot \
# -d grafana.netrunner-vpn.com -d vault.netrunner-vpn.com
# sudo htpasswd -c /etc/nginx/.htpasswd <username> # спросит пароль
# (htpasswd — из пакета apache2-utils, если его нет: apt-get install -y apache2-utils)
# 3. Запустить эту джобу ещё раз — увидит, что сертификат появился,
# включит netrunner-ssl.conf и перезагрузит nginx.
#
# Обновление одного из паролей позже — та же команда htpasswd без -c
# (создать) добавит/перезапишет юзера, не трогая остальных; -D <user> удаляет.
on:
workflow_dispatch:
jobs:
deploy:
name: Deploy nginx config to Data VPS
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Sync nginx configs to VPS
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ vars.DATA_VPS_IP }}
username: root
key: ${{ secrets.DATA_SSH_PRIVATE_KEY }}
source: "nginx/netrunner-http.conf,nginx/netrunner-ssl.conf"
target: "/root/netrunner-data"
strip_components: 0
- name: Install/enable nginx via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ vars.DATA_VPS_IP }}
username: root
key: ${{ secrets.DATA_SSH_PRIVATE_KEY }}
script: |
set -e
if ! command -v nginx >/dev/null 2>&1; then
echo "📦 Устанавливаю nginx..."
apt-get update
apt-get install -y nginx
fi
mkdir -p /var/www/certbot
rm -f /etc/nginx/sites-enabled/default
cp /root/netrunner-data/nginx/netrunner-http.conf /etc/nginx/sites-available/netrunner-http.conf
cp /root/netrunner-data/nginx/netrunner-ssl.conf /etc/nginx/sites-available/netrunner-ssl.conf
ln -sf /etc/nginx/sites-available/netrunner-http.conf /etc/nginx/sites-enabled/netrunner-http.conf
CERT=/etc/letsencrypt/live/grafana.netrunner-vpn.com/fullchain.pem
if [ -f "$CERT" ]; then
ln -sf /etc/nginx/sites-available/netrunner-ssl.conf /etc/nginx/sites-enabled/netrunner-ssl.conf
echo "🔒 Сертификат найден — HTTPS-конфиг включён."
else
rm -f /etc/nginx/sites-enabled/netrunner-ssl.conf
echo "⚠️ Сертификата ещё нет ($CERT) — HTTPS-конфиг НЕ включён."
echo " Выпустите его руками (см. заголовок deploy-nginx.yml) и перезапустите эту джобу."
fi
nginx -t
systemctl enable --now nginx
systemctl reload nginx
echo "✅ nginx synchronized."
+6
View File
@@ -54,6 +54,12 @@ jobs:
echo "🔑 Генерирую самоподписанный TLS-сертификат для Postgres (первый деплой)..."
openssl req -new -x509 -days 3650 -nodes -subj "/CN=netrunner-db" \
-out postgres/certs/server.crt -keyout postgres/certs/server.key
# postgres:16-alpine работает под UID/GID 70 (не root) — файл,
# созданный openssl от root с правами 600, контейнеру не прочитать
# ("Permission denied" при старте). Владельца нужно менять именно
# на 70:70 (проверено: `docker run --rm postgres:16-alpine id postgres`),
# не 999 — это UID из Debian-образов, у alpine он другой.
chown 70:70 postgres/certs/server.key
chmod 600 postgres/certs/server.key
fi
+80
View File
@@ -0,0 +1,80 @@
name: Deploy Nginx
# Единственный публичный вход (443/80) на VPS с данными — system nginx
# (НЕ Docker, чтобы просто использовать certbot и его автопродление, без
# возни с сертификатами внутри контейнера). Только ручной запуск: nginx
# на этом сервере меняется редко, а сертификат/.htpasswd — одноразовые
# secrets-на-сервере, трогать их каждым push'ом смысла нет (тот же принцип,
# что и генерация сертификата Postgres в deploy.yml — один раз, не на
# каждый прогон).
#
# Порядок первого запуска:
# 1. Запустить эту джобу (поднимет http-конфиг + certbot + сам nginx).
# 2. Один раз руками на сервере:
# sudo apt-get install -y certbot
# sudo certbot certonly --webroot -w /var/www/certbot \
# -d grafana.netrunner-vpn.com -d vault.netrunner-vpn.com
# sudo htpasswd -c /etc/nginx/.htpasswd <username> # спросит пароль
# (htpasswd — из пакета apache2-utils, если его нет: apt-get install -y apache2-utils)
# 3. Запустить эту джобу ещё раз — увидит, что сертификат появился,
# включит netrunner-ssl.conf и перезагрузит nginx.
#
# Обновление одного из паролей позже — та же команда htpasswd без -c
# (создать) добавит/перезапишет юзера, не трогая остальных; -D <user> удаляет.
on:
workflow_dispatch:
jobs:
deploy:
name: Deploy nginx config to Data VPS
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Sync nginx configs to VPS
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ vars.DATA_VPS_IP }}
username: root
key: ${{ secrets.DATA_SSH_PRIVATE_KEY }}
source: "nginx/netrunner-http.conf,nginx/netrunner-ssl.conf"
target: "/root/netrunner-data"
strip_components: 0
- name: Install/enable nginx via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ vars.DATA_VPS_IP }}
username: root
key: ${{ secrets.DATA_SSH_PRIVATE_KEY }}
script: |
set -e
if ! command -v nginx >/dev/null 2>&1; then
echo "📦 Устанавливаю nginx..."
apt-get update
apt-get install -y nginx
fi
mkdir -p /var/www/certbot
rm -f /etc/nginx/sites-enabled/default
cp /root/netrunner-data/nginx/netrunner-http.conf /etc/nginx/sites-available/netrunner-http.conf
cp /root/netrunner-data/nginx/netrunner-ssl.conf /etc/nginx/sites-available/netrunner-ssl.conf
ln -sf /etc/nginx/sites-available/netrunner-http.conf /etc/nginx/sites-enabled/netrunner-http.conf
CERT=/etc/letsencrypt/live/grafana.netrunner-vpn.com/fullchain.pem
if [ -f "$CERT" ]; then
ln -sf /etc/nginx/sites-available/netrunner-ssl.conf /etc/nginx/sites-enabled/netrunner-ssl.conf
echo "🔒 Сертификат найден — HTTPS-конфиг включён."
else
rm -f /etc/nginx/sites-enabled/netrunner-ssl.conf
echo "⚠️ Сертификата ещё нет ($CERT) — HTTPS-конфиг НЕ включён."
echo " Выпустите его руками (см. заголовок deploy-nginx.yml) и перезапустите эту джобу."
fi
nginx -t
systemctl enable --now nginx
systemctl reload nginx
echo "✅ nginx synchronized."
+6
View File
@@ -54,6 +54,12 @@ jobs:
echo "🔑 Генерирую самоподписанный TLS-сертификат для Postgres (первый деплой)..."
openssl req -new -x509 -days 3650 -nodes -subj "/CN=netrunner-db" \
-out postgres/certs/server.crt -keyout postgres/certs/server.key
# postgres:16-alpine работает под UID/GID 70 (не root) — файл,
# созданный openssl от root с правами 600, контейнеру не прочитать
# ("Permission denied" при старте). Владельца нужно менять именно
# на 70:70 (проверено: `docker run --rm postgres:16-alpine id postgres`),
# не 999 — это UID из Debian-образов, у alpine он другой.
chown 70:70 postgres/certs/server.key
chmod 600 postgres/certs/server.key
fi
+4 -23
View File
@@ -15,8 +15,10 @@
# LOKI_ALLOWED_CIDR ниже, заглушка 0.0.0.0/0 — заменить по мере аренды
# серверов, тот же паттерн что postgres/pg_hba.conf в соседнем стеке).
# - Prometheus/Grafana UI НЕ публикуются напрямую (127.0.0.1-only) — доступ
# к Grafana только через Caddy с auth-гейтом (см. фазу 7 плана
# мониторинга), Prometheus UI вообще не нужен наружу.
# к Grafana только через system nginx на этом же VPS (см. ../nginx/,
# TLS + HTTP Basic Auth) — единственный публичный вход, отдельный от
# этого compose-стека (nginx не в Docker, деплоится отдельным workflow
# deploy-nginx.yml). Prometheus UI вообще не нужен наружу.
#
# Список нод для scrape (observability/targets/nodes.json) генерится и
# заливается сюда отдельным шагом деплоя netrunner-proxy — см.
@@ -92,25 +94,6 @@ services:
networks:
- netrunner_observability
# Единственный публичный вход в Grafana — гейтит доступ через существующую
# cookie-сессию netrunner-backend (Owner/Moderator), см. observability/Caddyfile.
# Порт Grafana выше (3030) остаётся 127.0.0.1-only — снаружи только через это.
caddy:
image: caddy:2-alpine
container_name: netrunner-observability-caddy
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./observability/Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- grafana
networks:
- netrunner_observability
networks:
netrunner_observability:
driver: bridge
@@ -119,5 +102,3 @@ volumes:
prometheus_data:
loki_data:
grafana_data:
caddy_data:
caddy_config:
+18
View File
@@ -0,0 +1,18 @@
# Часть 1/2 — включается всегда, с самого первого деплоя (см.
# deploy-nginx.yml). Обслуживает ACME-challenge для certbot и редиректит
# всё остальное на HTTPS. netrunner-ssl.conf (сами сервисы) деплой
# включает отдельно, только когда сертификат уже существует — иначе nginx
# откажется стартовать (ssl_certificate на несуществующий файл).
server {
listen 80;
listen [::]:80;
server_name grafana.netrunner-vpn.com vault.netrunner-vpn.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
+91
View File
@@ -0,0 +1,91 @@
# Часть 2/2 — деплой (deploy-nginx.yml) включает этот файл ТОЛЬКО когда на
# сервере уже есть сертификат /etc/letsencrypt/live/grafana.netrunner-vpn.com/
# (иначе nginx -t упадёт: ssl_certificate на несуществующий файл). До первого
# `certbot certonly` этот файл лежит на диске, но не подключён — только
# netrunner-http.conf, обслуживающий ACME-challenge.
#
# Обе площадки закрыты HTTP Basic Auth (доп. пароль ДО того, как запрос
# вообще дойдёт до Grafana/Vaultwarden) — см. deploy-nginx.yml про
# .htpasswd. Для Vaultwarden basic auth намеренно НЕ накрывает /api,
# /identity, /notifications — мобильные и десктоп-клиенты Bitwarden, а
# также браузерное расширение, не умеют проходить Basic Auth на этих
# путях (только сама веб-морда на "/" людьми открывается руками),
# закрыть их означало бы сломать синхронизацию всем клиентам.
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name grafana.netrunner-vpn.com;
# Сертификат лежит в папке ПЕРВОГО домена из -d при выпуске (grafana...),
# даже несмотря на то что SAN-сертификат покрывает оба домена — так у
# certbot всегда, это не опечатка.
ssl_certificate /etc/letsencrypt/live/grafana.netrunner-vpn.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/grafana.netrunner-vpn.com/privkey.pem;
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
location / {
proxy_pass http://127.0.0.1:3030;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Grafana Live (websocket) — используется для live-обновления панелей.
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name vault.netrunner-vpn.com;
ssl_certificate /etc/letsencrypt/live/grafana.netrunner-vpn.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/grafana.netrunner-vpn.com/privkey.pem;
client_max_body_size 525M; # вложения Vaultwarden
# Веб-морда ("/") — единственное место, которое реально открывают руками
# в браузере, поэтому единственное место с доп. паролем.
location / {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://127.0.0.1:8081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# API/логин/синхронизация — сюда ходят мобильные приложения, десктоп-клиент
# и браузерное расширение напрямую, без Basic Auth (клиенты Bitwarden его
# на этих путях не поддерживают — добавить сюда auth_basic значило бы
# сломать синхронизацию всем, кроме веб-морды).
location ~ ^/(api|identity)/ {
proxy_pass http://127.0.0.1:8081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Websocket-уведомления о синхронизации между устройствами — тоже без
# Basic Auth (по той же причине) и с апгрейдом соединения.
location /notifications/hub {
proxy_pass http://127.0.0.1:8081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
-28
View File
@@ -1,28 +0,0 @@
# Гейтит доступ к Grafana через уже существующую cookie-сессию
# netrunner-backend (владелец/модератор) — Grafana сама наружу НЕ торчит
# (см. docker-compose.observability.yml, порт 3030 только на 127.0.0.1),
# только через этот Caddy.
#
# Как это работает: cookie сессии backend'а (nrxp_token) ставится с
# Domain=.netrunner-vpn.com (см. COOKIE_DOMAIN в netrunner-backend/.env) —
# значит браузер сам приложит её и к запросам на grafana.netrunner-vpn.com
# (тот же родительский домен, другой сабдомен — это same-site, не
# cross-site, для куки без атрибута Partitioned браузер это разрешает даже
# из iframe). forward_auth здесь пересылает эту cookie на реальный бэкенд
# (уже на другом VPS) и либо пускает дальше в Grafana (200), либо блокирует
# (401) — сам Caddy решения не принимает, только проксирует запрос-проверку.
#
# ЗАМЕНИТЬ ПЕРЕД ПЕРВЫМ ЗАПУСКОМ:
# - CHANGE_ME_GRAFANA_DOMAIN — сабдомен для Grafana (например
# grafana.netrunner-vpn.com), DNS A-запись на IP этого VPS.
# - CHANGE_ME_BACKEND_PUBLIC_URL — публичный URL бэкенда (например
# https://account.netrunner-vpn.com), КОГДА backend будет арендован и
# задеплоен — до этого auth-гейт нерабочий (пускать некому проверять).
CHANGE_ME_GRAFANA_DOMAIN {
forward_auth CHANGE_ME_BACKEND_PUBLIC_URL {
uri /api/v1/admin/observability/check
copy_headers Cookie
}
reverse_proxy grafana:3000
}