nginx
This commit is contained in:
@@ -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."
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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."
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user