fix docs/devops-deploy — чёткий порядок, без дыр

- порядок шагов исправлен (сначала код, потом таблицы)
- sudo согласован
- PostgreSQL: установка → код → таблицы
- все <домен> заменены на white.nubes.ru как пример
- таблица .env с колонкой 'Что писать'
This commit is contained in:
2026-06-03 13:02:42 +03:00
parent 006741a5fa
commit 2a77b163db
+204 -170
View File
@@ -1,140 +1,137 @@
# Деплой ipwhitelist — PRODUCTION
> Репозиторий: `https://gitea.services.ngcloud.ru/Nail/ipwhitelist-app.git` (публичный)
> Стек: Node.js 20+ / Express 4 / EJS / PostgreSQL 16+
> Требования: доступ к Keycloak Nubes (realm `cloud`), зарегистрированный OIDC-клиент
Репозиторий: `https://gitea.services.ngcloud.ru/Nail/ipwhitelist-app.git` (публичный)
ОС: Ubuntu 22.04 / 24.04 (для других ОС команды могут отличаться)
---
## 1. Подготовка сервера
## 0. Что должно быть готово до начала
| Что | Откуда берётся |
|---|---|
| Сервер (ВМ) с Ubuntu | Выделяет DevOps |
| Домен (например `white.nubes.ru`) | DNS запись на сервер |
| Доступ к Keycloak Nubes (realm `cloud`) | Существующий Keycloak в инфраструктуре Nubes |
| Зарегистрированный OIDC-клиент в Keycloak | Создаётся в Keycloak (шаг 6) |
| `KC_CLIENT_ID` и `KC_CLIENT_SECRET` | Выдаются после регистрации клиента |
**Что будет установлено на сервер:** Node.js 20, PostgreSQL 16, nginx, certbot, PM2.
---
## 1. PostgreSQL — установка
**Где:** на этой же ВМ, рядом с приложением.
**Версия:** 16 (или любая 15+).
**Безопасность:** слушает только localhost (наружу не торчит).
```bash
# Node.js 20+
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt install -y nodejs git nginx certbot python3-certbot-nginx postgresql
# 1.1 Установить PostgreSQL
sudo apt update
sudo apt install -y postgresql
# Код
git clone https://gitea.services.ngcloud.ru/Nail/ipwhitelist-app.git /opt/ipwhitelist
# 1.2 Проверить что запустился и слушает localhost
sudo systemctl status postgresql --no-pager
ss -tlnp | grep 5432
# Должно быть: 127.0.0.1:5432 (НЕ 0.0.0.0:5432)
# 1.3 Сгенерировать пароль для БД — он понадобится в .env
DB_PASS=$(openssl rand -base64 24)
echo "Пароль БД: $DB_PASS" # сохранить — вписать в .env
# 1.4 Создать пользователя и базу данных
sudo -u postgres psql -c "CREATE USER ipwhitelist WITH PASSWORD '${DB_PASS}';"
sudo -u postgres psql -c "CREATE DATABASE ipwhitelist OWNER ipwhitelist;"
```
**Таблицы создадим позже (шаг 2.4), после клонирования кода — схема лежит в репозитории.**
---
## 2. Node.js + код приложения + таблицы БД
```bash
# 2.1 Установить Node.js 20
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo bash -
sudo apt install -y nodejs
# 2.2 Скачать код и установить зависимости
sudo git clone https://gitea.services.ngcloud.ru/Nail/ipwhitelist-app.git /opt/ipwhitelist
sudo chown -R $(whoami):$(whoami) /opt/ipwhitelist # чтобы npm install не просил sudo
cd /opt/ipwhitelist
npm install --production
# 2.4 Теперь код есть — можно создать таблицы (schema.sql в репозитории)
sudo -u postgres psql -d ipwhitelist -f /opt/ipwhitelist/sql/schema.sql
# 2.5 Проверить что таблицы создались
sudo -u postgres psql -d ipwhitelist -c "\dt"
# Результат: companies, whitelist_entries, audit_log, session
```
---
## 2. PostgreSQL
## 3. .env — настройка приложения
```bash
# Создать пользователя и БД
sudo -u postgres psql -c "CREATE USER ipwhitelist WITH PASSWORD '${PASSWORD}';"
sudo -u postgres psql -c "CREATE DATABASE ipwhitelist OWNER ipwhitelist;"
# Применить схему
PGPASSWORD="${PASSWORD}" psql -h localhost -U ipwhitelist -d ipwhitelist -f /opt/ipwhitelist/sql/schema.sql
# Настройка: слушать только localhost (безопасность)
grep -c "^listen_addresses = 'localhost'" /etc/postgresql/16/main/postgresql.conf \
|| echo "listen_addresses = 'localhost'" >> /etc/postgresql/16/main/postgresql.conf
systemctl restart postgresql
# 3.1 Скопировать шаблон
cd /opt/ipwhitelist
sudo cp .env.example .env
sudo chmod 600 .env # чтобы никто не прочитал пароли
```
**3.2 Открыть .env и заполнить значения:**
```bash
sudo nano /opt/ipwhitelist/.env
```
**Что и куда писать:**
| Строка в .env | Что вписать |
|---|---|
| `DB_PASS=` | Пароль, сгенерированный на шаге 1.3 |
| `DEV_MODE=false` | **ОБЯЗАТЕЛЬНО false** иначе будет mock-логин |
| `ADMIN_CLIENT_ID=WZ01112` | clientId администратора (получить у заказчика) |
| `KC_CLIENT_ID=` | ID клиента из Keycloak (шаг 6) |
| `KC_CLIENT_SECRET=` | Secret клиента из Keycloak (шаг 6) |
| `APP_URL=https://white.nubes.ru` | **Реальный домен** без слеша на конце |
| `SESSION_SECRET=` | Сгенерировать: `openssl rand -base64 32` |
| `CSRF_SECRET=` | Сгенерировать: `openssl rand -base64 32` |
**Остальные строки не трогать** — значения по умолчанию подходят.
---
## 3. .env (PRODUCTION)
## 4. nginx — HTTPS и прокси
**Зачем:** браузер не пускает на HTTPS-сайты с localhost. Нужен nginx как прослойка.
```bash
cp /opt/ipwhitelist/.env.example /opt/ipwhitelist/.env
chmod 600 /opt/ipwhitelist/.env
# 4.1 Установить nginx и certbot
sudo apt install -y nginx certbot python3-certbot-nginx
```
```ini
# ── База данных ──
DB_HOST=localhost
DB_PORT=5432
DB_NAME=ipwhitelist
DB_USER=ipwhitelist
DB_PASS=<сгенерировать: openssl rand -base64 24>
DB_SSLMODE=disable # localhost — шифрование не нужно
**4.2 Создать конфиг nginx.** Заменить `white.nubes.ru` на свой домен:
# ── Аутентификация ──
DEV_MODE=false # ОБЯЗАТЕЛЬНО false в production
ADMIN_CLIENT_ID=WZ01112 # clientId пользователя-администратора
# ── Keycloak / OIDC (данные от DevOps) ──
KC_CLIENT_ID=<client_id> # зарегистрированный OIDC-клиент
KC_CLIENT_SECRET=<secret> # секрет клиента
KC_BASE_URL=https://keycloak.nubes.ru/realms/cloud
APP_URL=https://<домен> # публичный URL приложения (без слеша)
# ── Сессия ──
SESSION_SECRET=<openssl rand -base64 32>
CSRF_SECRET=<openssl rand -base64 32>
# ── Лимит записей по умолчанию ──
DEFAULT_LIMIT=15
# ── Порт (для nginx upstream) ──
PORT=3001
# ── API_BASE — куда UI-слой ходит за API (обычно localhost) ──
API_BASE=http://localhost:3001
```
**Генерация секретов:**
```bash
openssl rand -base64 32 # для SESSION_SECRET
openssl rand -base64 32 # для CSRF_SECRET
openssl rand -base64 24 # для DB_PASS
sudo nano /etc/nginx/sites-available/ipwhitelist
```
---
## 4. Keycloak — регистрация OIDC-клиента
**Что сделать в админке Keycloak (realm `cloud`):**
1. Создать клиента:
- Client ID: `<домен>` (например `white.nubes.ru`)
- Client authentication: ON
- Standard flow: ON
- Valid redirect URIs: `https://<домен>/callback`
- Web origins: `https://<домен>` (или `+`)
2. Забрать `Client secret` → в .env как `KC_CLIENT_SECRET`
3. **Mapper'ы** (создать на клиенте, тип `User Attribute → Token Claim`):
| Claim name | User attribute | Назначение |
|---|---|---|
| `ClientID` | `client_id` | ID компании (может быть через запятую) |
| `company_id` | `company_id` | ID компании в БД |
| `company_name` | `company_name` | Название компании |
4. **Пользователи** — создать в Keycloak с заполненными атрибутами `client_id`, `company_id`, `company_name`.
**Формат multi-company:** `client_id = "WZ11125,WZ03816"` — первое значение до запятой = активная компания. Приложение парсит автоматически.
---
## 5. nginx + HTTPS
Вставить:
```nginx
server {
listen 80;
server_name <домен>;
server_name white.nubes.ru; # ← ЗАМЕНИТЬ на свой домен
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name <домен>;
server_name white.nubes.ru; # ← ЗАМЕНИТЬ на свой домен
ssl_certificate /etc/letsencrypt/live/<домен>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<домен>/privkey.pem;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
ssl_certificate /etc/letsencrypt/live/white.nubes.ru/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/white.nubes.ru/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3001;
@@ -142,109 +139,146 @@ server {
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_set_header X-Forwarded-Host $host;
}
}
```
```bash
# Получить сертификат
certbot --nginx -d <домен>
# 4.3 Включить сайт
sudo ln -s /etc/nginx/sites-available/ipwhitelist /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
# Проверить авто-обновление
certbot renew --dry-run
# 4.4 Получить SSL-сертификат (certbot сам подправит конфиг)
sudo certbot --nginx -d white.nubes.ru # ← ЗАМЕНИТЬ на свой домен
# 4.5 Проверить что nginx работает
sudo nginx -t
sudo systemctl reload nginx
```
---
## 6. PM2 — автозапуск
## 5. PM2 — автозапуск приложения
**Зачем:** чтобы приложение само стартовало после перезагрузки сервера.
```bash
npm i -g pm2
# 5.1 Установить PM2
sudo npm i -g pm2
# Запуск
pm2 start /opt/ipwhitelist/server.js --name ipwhitelist --update-env
# 5.2 Запустить приложение
cd /opt/ipwhitelist
sudo pm2 start server.js --name ipwhitelist --update-env
# Автостарт при перезагрузке
pm2 startup systemd
pm2 save
# 5.3 Настроить автозапуск при перезагрузке
sudo pm2 startup systemd
sudo pm2 save
# Проверка
pm2 status
pm2 logs ipwhitelist --lines 5
# Должно быть: [auth] OIDC — Keycloak
# Если [auth] MOCK — значит KC_CLIENT_ID не задан
# 5.4 Проверить что запустилось
sudo pm2 status
# Должен быть статус: online
```
---
## 7. Порядок ввода в эксплуатацию
## 6. Keycloak — регистрация OIDC-клиента
```bash
# 1. Убедиться что DEV_MODE=false и заданы KC_*
grep -E "DEV_MODE|KC_CLIENT_ID|KC_CLIENT_SECRET" /opt/ipwhitelist/.env
**Где:** в админке Keycloak Nubes (`https://keycloak.nubes.ru/admin`, realm `cloud`).
**Зачем:** чтобы Keycloak знал наше приложение и пускал пользователей обратно после входа.
# 2. Перезапустить
pm2 restart ipwhitelist --update-env
**6.1 Создать клиента:**
# 3. Проверить логи — должен быть OIDC-режим
pm2 logs ipwhitelist --lines 3 --nostream
# 4. Проверить health
curl -s https://<домен>/healthz
# → OK
# 5. Проверить SSO: открыть https://<домен>/login в браузере
# → редирект на Keycloak
# → после входа → редирект обратно на главную
```
---
## 8. Проверка
| URL | Ожидаемый результат |
| Поле | Значение |
|---|---|
| `https://<домен>/healthz` | `OK` |
| `https://<домен>/` | Редирект на Keycloak login (если нет сессии) |
| `https://<домен>/login` | Редирект на Keycloak (если `DEV_MODE=false`) |
| `https://<домен>/export` | CIDR-список (text/plain) |
| `https://<домен>/api/v1/entries` | 401 (нужен Bearer) |
| `https://<домен>/callback?code=...` | Обмен code → сессия → редирект на `/` |
| Client ID | `white.nubes.ru` (или свой домен) |
| Client authentication | ON (включено) |
| Standard flow | ON (включено) |
| Valid redirect URIs | `https://white.nubes.ru/callback` |
| Web origins | `https://white.nubes.ru` |
**6.2 Забрать секрет:** после создания клиента открыть вкладка `Credentials``Client secret` → скопировать.
**6.3 Создать mapper'ы** (вкладка `Client scopes``ipwhitelist-app-dedicated``Add mapper``By configuration``User Attribute`):
Создать три mapper'а:
| Имя | User Attribute | Claim name | Тип |
|---|---|---|---|
| `ClientID` | `client_id` | `ClientID` | String |
| `company_id` | `company_id` | `company_id` | String |
| `company_name` | `company_name` | `company_name` | String |
**6.4 Создать пользователей** (вкладка `Users``Add user`):
| Логин | Пароль | Атрибуты |
|---|---|---|
| `admin` | задать вкладка Credentials | `client_id=WZ01112`, `company_id=1`, `company_name=Nubes Admin` |
| `client` | задать | `client_id=WZ88888`, `company_id=2`, `company_name=Альфа` |
**Как задать атрибуты:** вкладка `Attributes` → добавить по одному → `Save`.
**Формат multi-company:** `client_id = "WZ11125,WZ03816"` — первое значение до запятой = активная компания. Приложение само разберёт.
---
## 9. Обновление
## 7. Финальная проверка
```bash
# 7.1 Убедиться что в .env всё заполнено
grep -E "DEV_MODE|KC_CLIENT_ID|KC_CLIENT_SECRET|APP_URL|DB_PASS" /opt/ipwhitelist/.env
# Проверить: DEV_MODE=false, KC_CLIENT_ID и KC_CLIENT_SECRET не пустые
# 7.2 Перезапустить приложение
cd /opt/ipwhitelist
sudo pm2 restart ipwhitelist --update-env
# 7.3 Проверить что включился OIDC-режим
sudo pm2 logs ipwhitelist --lines 3 --nostream
# В логе должно быть: [auth] OIDC — Keycloak
# Если [auth] MOCK — значит KC_CLIENT_ID/KC_CLIENT_SECRET не заданы
```
**Открыть в браузере:**
| URL | Что должно произойти |
|---|---|
| `https://white.nubes.ru/healthz` | Покажет `OK` |
| `https://white.nubes.ru/` | Редирект на страницу логина Keycloak |
| Ввести логин/пароль пользователя из Keycloak | Редирект обратно на главную с белым списком |
| `https://white.nubes.ru/export` | Список CIDR (текст) |
| `https://white.nubes.ru/logout` | Выход из системы |
---
## 8. Обновление приложения
```bash
cd /opt/ipwhitelist
git pull
npm install --production
pm2 restart ipwhitelist --update-env
sudo git pull
sudo npm install --production
sudo pm2 restart ipwhitelist --update-env
```
---
## 10. Переменные окружения (справочно)
## 9. Переменные .env — полный список
| Переменная | Обязательна | По умолчанию | Описание |
| Переменная | Когда нужна | По умолчанию | Что писать |
|---|---|---|---|
| `DB_HOST` | да | — | Хост PostgreSQL |
| `DB_PORT` | нет | 5432 | Порт PostgreSQL |
| `DB_NAME` | да | — | Имя БД |
| `DB_USER` | да | — | Пользователь БД |
| `DB_PASS` | да | — | Пароль БД |
| `DB_SSLMODE` | нет | disable | `require` для внешнего подключения |
| `DEV_MODE` | нет | true | `false` в production (ОБЯЗАТЕЛЬНО) |
| `ADMIN_CLIENT_ID` | нет | WZ01112 | clientId администратора |
| `KC_CLIENT_ID` | да* | — | OIDC client ID (обязателен для SSO) |
| `KC_CLIENT_SECRET` | да* | — | OIDC client secret |
| `KC_BASE_URL` | нет | `https://keycloak.nubes.ru/realms/cloud` | Keycloak realm URL |
| `APP_URL` | да* | — | Публичный URL приложения (для OIDC redirect) |
| `SESSION_SECRET` | да | — | Подпись сессионных cookie |
| `CSRF_SECRET` | нет | — | CSRF-токены |
| `PORT` | нет | 3000 | Порт Express |
| `DEFAULT_LIMIT` | нет | 15 | Лимит записей по умолчанию |
| `API_BASE` | нет | `http://localhost:PORT` | URL для UI→API запросов |
\* — обязательны при `DEV_MODE=false`
| `DB_HOST` | всегда | — | `localhost` |
| `DB_PORT` | всегда | 5432 | |
| `DB_NAME` | всегда | — | `ipwhitelist` |
| `DB_USER` | всегда | — | `ipwhitelist` |
| `DB_PASS` | всегда | — | пароль из шага 1.3 |
| `DB_SSLMODE` | никогда | disable | не трогать |
| `DEV_MODE` | всегда | true | **false** в production |
| `ADMIN_CLIENT_ID` | всегда | WZ01112 | clientId админа |
| `KC_CLIENT_ID` | для SSO | — | из Keycloak (шаг 6) |
| `KC_CLIENT_SECRET` | для SSO | — | из Keycloak (шаг 6) |
| `KC_BASE_URL` | для SSO | `https://keycloak.nubes.ru/realms/cloud` | не трогать |
| `APP_URL` | для SSO | — | `https://white.nubes.ru` |
| `SESSION_SECRET` | всегда | — | `openssl rand -base64 32` |
| `CSRF_SECRET` | опционально | — | `openssl rand -base64 32` |
| `PORT` | всегда | 3000 | `3001` |
| `DEFAULT_LIMIT` | опционально | 15 | не трогать |
| `API_BASE` | всегда | `http://localhost:PORT` | не трогать |