diff --git a/.env.example b/.env.example index 4fef41a..144fbe5 100644 --- a/.env.example +++ b/.env.example @@ -15,6 +15,10 @@ SAREX_DJANGO_DB_USER=django SAREX_DJANGO_DB_PASSWORD=django-secret # Расширения PostgreSQL для базы django (создаёт postgres-init суперпользователем) SAREX_DJANGO_DB_EXTENSIONS=ltree +# JWT-ключи backend (RS512): PEM в одну строку с литеральными \n (backend разворачивает). +# Генерируются автоматически в .secrets ролью sarex_stack; здесь — плейсхолдеры. +SAREX_JWT_PRIVATE_KEY=changeme +SAREX_JWT_PUBLIC_KEY=changeme SAREX_PROCESSING_DB_USER=processing SAREX_PROCESSING_DB_PASSWORD=processing-secret SAREX_BIM_DB_USER=bim diff --git a/aero/README.md b/aero/README.md index 7ab8e97..1cb32e2 100644 --- a/aero/README.md +++ b/aero/README.md @@ -41,16 +41,37 @@ chmod 600 ~/.ssh/id_ed25519 ## Запуск -Из WSL, в каталоге проекта (`/mnt/c/.../iac/aero`): +Из WSL, в каталоге проекта (`/mnt/c/.../iac/aero`). + +### Установка с чистого листа — одной командой ```bash -uv sync # окружение (uv сам поставит нужный Python) -uv run poe ping # проверка SSH + sudo -uv run poe check # dry-run (--check --diff), ничего не меняет -uv run poe play # боевой прогон роли +uv run poe install ``` -Одной командой всё сразу: `uv run poe up` (`sync` → `galaxy` → `play`). +Последовательность: `sync` → `galaxy` → **`provision`** (базовые пакеты + docker +и запуск службы) → **`stack`** (деплой конфигурации + `docker compose up -d` +сервисов приложения) → **`superuser`** (генерация и создание Django-админа). +Перед запуском впишите хосты в [inventory.ini](inventory.ini) и убедитесь, что +на control-node есть SSH-ключ и `~/.ssh/local/authorized_key.json` для входа в +реестр. + +### Пошагово / отдельные команды + +| Команда | Действие | +| --- | --- | +| `uv run poe ping` | проверка SSH + sudo | +| `uv run poe provision` | базовые пакеты + docker/containerd + запуск службы | +| `uv run poe deploy` | деплой конфигурации: файлы, `.env`, сертификаты, `docker login` | +| `uv run poe stack` | деплой + поднять стек (`docker compose up -d`) | +| `uv run poe gen-env` | только сгенерировать пароли и `.env` | +| `uv run poe login` | только `docker login` в реестр по ключу | +| `uv run poe superuser` | сгенерировать логин+пароль и создать Django-суперпользователя | +| `uv run poe check` / `deploy-check` | dry-run (`--check --diff`) | + +Поднимаемые сервисы задаются в `sarex_services` (роль `sarex_stack`) — +`postgres(+init)`, `redis`, `rabbitmq`, `minio(+init)`, `measurements`, +`backend`, `celery`, `frontend`, `nginx` (k3s/gitea/processing не поднимаются). > `ANSIBLE_CONFIG` и `UV_LINK_MODE` заданы в `[tool.poe.env]` — на диске `C:` > (через `/mnt/c`) права `0777`, и ansible игнорирует `ansible.cfg` в @@ -58,10 +79,12 @@ uv run poe play # боевой прогон роли ### Развёртывание sarex-стека (роль `sarex_stack`) -Копирует `docker-compose.yaml`, `nginx/` и `postgres/initdb` в `/root/sarex`, -рендерит `.env` из `.env.example` со **сгенерированными паролями** и доменами -`*.sarex.local.lonsdaleites.ru`, генерирует самоподписанный TLS-сертификат на -все три домена (`nginx/certs/selfsigned.{crt,key}`). +Копирует `docker-compose.yaml`, `nginx/templates` и `backend/uwsgi.ini` в +`/root/sarex`, рендерит `.env` из `.env.example` со **сгенерированными паролями** +и доменами `*.sarex.local.lonsdaleites.ru`, генерирует самоподписанный +TLS-сертификат на все три домена (`nginx/certs/selfsigned.{crt,key}`), логинится +в реестр и (при `sarex_compose_up=true`) поднимает сервисы приложения. +Роли/базы и MinIO-бакеты создаются одноразовыми `postgres-init`/`minio-init`. ```bash uv run ansible-playbook deploy.yml --check --diff # dry-run diff --git a/aero/pyproject.toml b/aero/pyproject.toml index 47a489b..c674df3 100644 --- a/aero/pyproject.toml +++ b/aero/pyproject.toml @@ -28,22 +28,26 @@ UV_LINK_MODE = "copy" sync = "uv sync" galaxy = "ansible-galaxy collection install -r requirements.yml" ping = "ansible docker_hosts -m ping" -play = "ansible-playbook site.yml" +# Провижининг хоста: базовые пакеты + docker/containerd + запуск службы +provision = "ansible-playbook site.yml" check = "ansible-playbook site.yml --check --diff" +# Деплой конфигурации: файлы, .env с паролями, сертификаты, docker login deploy = "ansible-playbook deploy.yml" deploy-check = "ansible-playbook deploy.yml --check --diff" +# Деплой + поднять стек приложения (docker compose up -d) +stack = "ansible-playbook deploy.yml -e sarex_compose_up=true" # Только генерация паролей и рендер .env на хосте (без копирования/сертификатов) gen-env = "ansible-playbook deploy.yml --tags env" # docker login в приватный реестр по ключу json_key login = "ansible-playbook deploy.yml --tags login" -# сгенерировать логин+пароль и создать Django-суперпользователя в backend -superuser = "ansible-playbook deploy.yml --tags superuser" +# Сгенерировать логин+пароль и создать Django-суперпользователя в backend +superuser = "ansible-playbook deploy.yml -e sarex_create_superuser=true --tags superuser" lint = "ansible-lint" -# Полная подготовка окружения: uv sync + установка коллекций +# Подготовка окружения: uv sync + установка коллекций [tool.poe.tasks.setup] sequence = ["sync", "galaxy"] -# Всё сразу: подготовка окружения и прогон плейбука -[tool.poe.tasks.up] -sequence = ["sync", "galaxy", "play"] +# Установка с чистого листа: окружение → docker → деплой+стек → суперпользователь +[tool.poe.tasks.install] +sequence = ["sync", "galaxy", "provision", "stack", "superuser"] diff --git a/aero/roles/sarex_stack/defaults/main.yml b/aero/roles/sarex_stack/defaults/main.yml index f62468f..c7c8bdd 100644 --- a/aero/roles/sarex_stack/defaults/main.yml +++ b/aero/roles/sarex_stack/defaults/main.yml @@ -31,6 +31,10 @@ secret_length: 24 # Хранилище секретов на control-node (persist + переиспользование). Gitignored. secrets_store: "{{ playbook_dir }}/.secrets" +# RSA-ключи JWT backend (RS512). Генерируются на control-node, persist в secrets_store. +jwt_private_key_path: "{{ secrets_store }}/{{ inventory_hostname }}/jwt_private.pem" +jwt_public_key_path: "{{ secrets_store }}/{{ inventory_hostname }}/jwt_public.pem" + # Приватный реестр образов и ключ для входа (json_key) registry_host: cr.yandex registry_username: json_key @@ -41,3 +45,18 @@ registry_key_src: /mnt/c/Users/user/.ssh/local/authorized_key.json # docker login в cr.yandex — по умолчанию выключено. sarex_compose_up: false compose_cmd: "docker compose" + +# Сервисы приложения, поднимаемые при sarex_compose_up (depends_on тянет +# зависимости и порядок). k3s/gitea/processing намеренно не поднимаем. +sarex_services: + - postgres + - postgres-init + - redis + - rabbitmq + - minio + - minio-init + - measurements + - backend + - celery + - frontend + - nginx diff --git a/aero/roles/sarex_stack/tasks/main.yml b/aero/roles/sarex_stack/tasks/main.yml index 508b5ef..621bbdb 100644 --- a/aero/roles/sarex_stack/tasks/main.yml +++ b/aero/roles/sarex_stack/tasks/main.yml @@ -43,7 +43,24 @@ no_log: true tags: [env] -- name: Собрать словарь переопределений для .env (пароли + домены) +# RSA-ключи JWT (RS512): генерируем на control-node, persist в secrets_store. +- name: Сгенерировать приватный RSA-ключ JWT + ansible.builtin.command: "openssl genrsa -out {{ jwt_private_key_path }} 4096" + args: + creates: "{{ jwt_private_key_path }}" + delegate_to: localhost + become: false + tags: [env] + +- name: Извлечь публичный RSA-ключ JWT + ansible.builtin.command: "openssl rsa -in {{ jwt_private_key_path }} -pubout -out {{ jwt_public_key_path }}" + args: + creates: "{{ jwt_public_key_path }}" + delegate_to: localhost + become: false + tags: [env] + +- name: Собрать словарь переопределений для .env (пароли + домены + JWT) ansible.builtin.set_fact: env_overrides: >- {{ sarex_secrets | combine({ @@ -51,6 +68,8 @@ 'ADMIN_DOMAIN': admin_domain, 'MINIO_DOMAIN': minio_domain, 'SAREX_MINIO_CONSOLE_URL': 'https://' ~ minio_domain, + 'SAREX_JWT_PRIVATE_KEY': "'" ~ (lookup('file', jwt_private_key_path) | replace('\n', '\\n')) ~ "'", + 'SAREX_JWT_PUBLIC_KEY': "'" ~ (lookup('file', jwt_public_key_path) | replace('\n', '\\n')) ~ "'", }) }} no_log: true tags: [env] @@ -118,13 +137,14 @@ state: absent tags: [login] -# --- Опционально: поднять стек ------------------------------------------- -- name: Поднять стек (docker compose up -d) +# --- Поднять сервисы приложения ------------------------------------------ +- name: Поднять сервисы приложения (docker compose up -d) ansible.builtin.command: - cmd: "{{ compose_cmd }} up -d" + cmd: "{{ compose_cmd }} up -d {{ sarex_services | join(' ') }}" chdir: "{{ deploy_dir }}" when: sarex_compose_up | bool changed_when: true + tags: [up] # --- Django-суперпользователь: генерация логина+пароля, создание ----------- # Только по явной команде (uv run poe superuser передаёт sarex_create_superuser=true), diff --git a/docker-compose.yaml b/docker-compose.yaml index a3e9116..62fdd15 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -181,7 +181,9 @@ services: image: ${SAREX_REDIS_IMAGE:-cr.yandex/crp3ccidau046kdj8g9q/redis:7.0.10} container_name: redis restart: unless-stopped - command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"] + # protected-mode no: redis только во внутренней docker-сети (порт наружу не + # публикуется), иначе бэкенд/celery из других контейнеров отбиваются "DENIED" + command: ["redis-server", "--save", "60", "1", "--loglevel", "warning", "--protected-mode", "no", "--stop-writes-on-bgsave-error", "no"] volumes: - sarex-redis-data:/data healthcheck: @@ -273,8 +275,8 @@ services: environment: &sarex-backend-env ALLOWED_HOSTS: "*" DJANGO_SETTINGS_MODULE: config.settings.production - JWT_PRIVATE_KEY: "JWT" - JWT_PUBLIC_KEY: "JPK" + JWT_PRIVATE_KEY: ${SAREX_JWT_PRIVATE_KEY:-JWT} + JWT_PUBLIC_KEY: ${SAREX_JWT_PUBLIC_KEY:-JPK} DJANGO_POSTGRES_HOST: postgres DJANGO_POSTGRES_DATABASE: ${SAREX_POSTGRES_DB:-sarex_db} DJANGO_POSTGRES_USER: ${SAREX_DJANGO_DB_USER:-django} @@ -362,7 +364,7 @@ services: POSTGRES_SSL_USE: "0" ENABLE_SQL_QUERY: "0" DJANGO_HOST: http://backend:8000 # django-backend в этом же compose - PUBLIC_KEY: "JPK" # JWT public key (заглушка, как у backend) + PUBLIC_KEY: ${SAREX_JWT_PUBLIC_KEY:-JPK} # JWT public key (общий с backend) ports: - "8081:8080" depends_on: