# Netrunner Control Plane — разработка и деплой ## Локальная разработка (fully-dockerized, hot-reload) Весь стек поднимается одной командой — Postgres, Redis и приложение с автоперекомпиляцией (`cargo-watch`). Локальный хостовый Rust не нужен. ```bash make dev # db + redis + app (http://localhost:8080) make dev-logs # логи приложения make dev-down # остановить (тома/кеши сохраняются) make clean # снести стек вместе с БД и кешем компиляции ``` - Конфиг — `.env.dev` (бот выключен: `BOT_ENABLED=false`, реальный токен Telegram не нужен). - Миграции накатываются автоматически при старте `app` (`cargo run -- --migrate`). - Healthcheck: `GET /health` (liveness), `GET /health/ready` (readiness, пингует БД). - Первый запуск компилирует всё (несколько минут); дальше — инкрементально через тома `target_cache` / `cargo_registry`. Качество кода (то же, что гоняет CI): ```bash make fmt # форматирование make lint # clippy -D warnings make test # тесты (нужен Postgres + DATABASE_URL) ``` ## Пайплайн (CI/CD) Три воркфлоу в `.github/workflows/`: | Workflow | Триггер | Что делает | |----------|---------|------------| | `ci.yml` | PR, push в `main` | `fmt --check`, `clippy -D warnings`, тесты на эфемерном Postgres | | `build.yml` | push в `main`, тег `v*`, вручную | Собирает Docker-образ (cargo-chef + GHA-кеш слоёв) и пушит в GHCR | | `deploy.yml` → `deploy-dev` | **авто**, сразу после успешного `build.yml` на `main` | SSH на dev VPS: pull образа `latest` → миграции → перезапуск (`docker-compose.dev-remote.yml`) | | `deploy.yml` → `deploy-prod` | **только вручную** (Actions → Run workflow) | SSH на прод VPS: pull образа → миграции → перезапуск (`docker-compose.prod.yml`) | Компиляция Rust происходит **в GitHub Actions**, а не на машине разработчика и не на VPS. Сервер только тянет готовый образ — деплой занимает секунды. Сборка не требует доступа к БД (sqlx работает в offline-режиме). ### Ручной деплой (прод) GitHub → **Actions** → **Deploy** → **Run workflow** (можно указать тег образа, по умолчанию `latest`). Dev-окружение отдельного ручного шага не требует — обновляется само после каждого успешного билда `main`. ### Секреты GitHub (Settings → Secrets → Actions) - `VPS_IP` / `SSH_PRIVATE_KEY` — прод-сервер - `DEV_VPS_IP` / `DEV_SSH_PRIVATE_KEY` — dev-сервер (`45.76.161.137`, DNS `dev.netrunner-vpn.com`; на нём же по аналогии деплоится dev-версия `netrunner-landing`, см. её `DEPLOYMENT`/README). Домен, а не голый IP, используется во всех browser-facing URL (`WEB_APP_BASE_URL`, `CORS_ALLOWED_ORIGINS` и т.п.) — Telegram Login Widget не работает на IP (`/setdomain` в BotFather принимает только доменное имя). - `GHCR_TOKEN` — токен GHCR с правом `read:packages` (для pull на VPS, обоих) `GITHUB_TOKEN` для push в GHCR выдаётся автоматически. Отдельно на самом dev VPS (не GitHub-секрет, а переменная в `.env` рядом с `docker-compose.dev-remote.yml`) — `CF_API_TOKEN`: Cloudflare-токен для Caddy (DNS-01 challenge, см. ниже), шаблон "Edit zone DNS", ограниченный зоной `netrunner-vpn.com`. Не Global API Key. ### Что должно лежать на VPS (`/root/netrunner-core`) - `docker-compose.prod.yml` - `.env` (прод-секреты; `APP_ENV=production`, `BOT_ENABLED=true`, реальные токены) - `keys/netrunner_master_ed25519[.pub]` — мастер-ключ для провижининга нод - конфиги мониторинга: `prometheus.yml`, `loki-config.yml`, `promtail-config.yml`, `grafana-datasources.yml` Прод-стек (`docker-compose.prod.yml`) поднимает: `migrate` (one-shot миграции) → `app` (с healthcheck) + `db` + `redis` + Prometheus/Loki/Promtail/Grafana. ### Что должно лежать на dev VPS (`/root/netrunner-core`) - `docker-compose.dev-remote.yml`, `Dockerfile.caddy`, `Caddyfile.dev` (все три синкает CI при каждом `deploy-dev`, руками копировать не нужно) - `.env` (см. [`.env.dev-remote.example`](.env.dev-remote.example) — свои `JWT_SECRET`/`ARGON_SALT`/`DB_PASSWORD`, `CF_API_TOKEN`, `CORS_ALLOWED_ORIGINS`/`CSRF_ALLOWED_ORIGINS` включают origin dev-лендинга и Caddy-порта на этом же хосте) - `keys/netrunner_master_ed25519[.pub]` — свой мастер-ключ, отдельный от прода Dev-стек: `migrate` → `app` (порт `8080`, обычный HTTP, без TLS) + `db` + `redis` + `caddy` (порт `443`, реальный TLS через DNS-01/Cloudflare — см. [`Caddyfile.dev`](Caddyfile.dev)). Caddy здесь нужен по двум причинам: Telegram Web Apps (кнопки бота "Личный Кабинет"/"Тарифы"/"Синдикат") требуют `https://` URL, а Telegram Login Widget в обычном браузере требует именно порт 443 (его CSP `frame-ancestors` не учитывает порт и всегда подразумевает дефолтный порт схемы). Порт 80 на этом VPS закрыт — обычный HTTP-01 challenge не проходит, поэтому DNS-01. Все браузерные auth-флоу (бот, лендинг) должны идти через Caddy на 443, не напрямую в `:8080` — `COOKIE_SECURE=true` (как в проде), и Secure-cookie по голому HTTP на `:8080` браузер по-прежнему отбросит. ### Локальная сборка образа (фолбэк, обычно не нужно) ```bash make build-image # docker build -t $IMAGE . make push-image # docker push $IMAGE # или ./deploy.sh — собрать и запушить разом ```