Compare commits

..

No commits in common. "5ef655f1bc1b36514fb7414cc58b1f36a3c079eb" and "0f9969ddad80061e298715e50713c41f69ccc1af" have entirely different histories.

7 changed files with 29 additions and 231 deletions

View File

@ -15,10 +15,6 @@ SAREX_DJANGO_DB_USER=django
SAREX_DJANGO_DB_PASSWORD=django-secret SAREX_DJANGO_DB_PASSWORD=django-secret
# Расширения PostgreSQL для базы django (создаёт postgres-init суперпользователем) # Расширения PostgreSQL для базы django (создаёт postgres-init суперпользователем)
SAREX_DJANGO_DB_EXTENSIONS=ltree 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_USER=processing
SAREX_PROCESSING_DB_PASSWORD=processing-secret SAREX_PROCESSING_DB_PASSWORD=processing-secret
SAREX_BIM_DB_USER=bim SAREX_BIM_DB_USER=bim

View File

@ -41,37 +41,16 @@ chmod 600 ~/.ssh/id_ed25519
## Запуск ## Запуск
Из WSL, в каталоге проекта (`/mnt/c/.../iac/aero`). Из WSL, в каталоге проекта (`/mnt/c/.../iac/aero`):
### Установка с чистого листа — одной командой
```bash ```bash
uv run poe install uv sync # окружение (uv сам поставит нужный Python)
uv run poe ping # проверка SSH + sudo
uv run poe check # dry-run (--check --diff), ничего не меняет
uv run poe play # боевой прогон роли
``` ```
Последовательность: `sync``galaxy`**`provision`** (базовые пакеты + docker Одной командой всё сразу: `uv run poe up` (`sync` → `galaxy``play`).
и запуск службы) → **`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:` > `ANSIBLE_CONFIG` и `UV_LINK_MODE` заданы в `[tool.poe.env]` — на диске `C:`
> (через `/mnt/c`) права `0777`, и ansible игнорирует `ansible.cfg` в > (через `/mnt/c`) права `0777`, и ansible игнорирует `ansible.cfg` в
@ -79,12 +58,10 @@ uv run poe install
### Развёртывание sarex-стека (роль `sarex_stack`) ### Развёртывание sarex-стека (роль `sarex_stack`)
Копирует `docker-compose.yaml`, `nginx/templates` и `backend/uwsgi.ini` в Копирует `docker-compose.yaml`, `nginx/` и `postgres/initdb` в `/root/sarex`,
`/root/sarex`, рендерит `.env` из `.env.example` со **сгенерированными паролями** рендерит `.env` из `.env.example` со **сгенерированными паролями** и доменами
и доменами `*.sarex.local.lonsdaleites.ru`, генерирует самоподписанный `*.sarex.local.lonsdaleites.ru`, генерирует самоподписанный TLS-сертификат на
TLS-сертификат на все три домена (`nginx/certs/selfsigned.{crt,key}`), логинится все три домена (`nginx/certs/selfsigned.{crt,key}`).
в реестр и (при `sarex_compose_up=true`) поднимает сервисы приложения.
Роли/базы и MinIO-бакеты создаются одноразовыми `postgres-init`/`minio-init`.
```bash ```bash
uv run ansible-playbook deploy.yml --check --diff # dry-run uv run ansible-playbook deploy.yml --check --diff # dry-run

View File

@ -28,26 +28,22 @@ UV_LINK_MODE = "copy"
sync = "uv sync" sync = "uv sync"
galaxy = "ansible-galaxy collection install -r requirements.yml" galaxy = "ansible-galaxy collection install -r requirements.yml"
ping = "ansible docker_hosts -m ping" ping = "ansible docker_hosts -m ping"
# Провижининг хоста: базовые пакеты + docker/containerd + запуск службы play = "ansible-playbook site.yml"
provision = "ansible-playbook site.yml"
check = "ansible-playbook site.yml --check --diff" check = "ansible-playbook site.yml --check --diff"
# Деплой конфигурации: файлы, .env с паролями, сертификаты, docker login
deploy = "ansible-playbook deploy.yml" deploy = "ansible-playbook deploy.yml"
deploy-check = "ansible-playbook deploy.yml --check --diff" deploy-check = "ansible-playbook deploy.yml --check --diff"
# Деплой + поднять стек приложения (docker compose up -d)
stack = "ansible-playbook deploy.yml -e sarex_compose_up=true"
# Только генерация паролей и рендер .env на хосте (без копирования/сертификатов) # Только генерация паролей и рендер .env на хосте (без копирования/сертификатов)
gen-env = "ansible-playbook deploy.yml --tags env" gen-env = "ansible-playbook deploy.yml --tags env"
# docker login в приватный реестр по ключу json_key # docker login в приватный реестр по ключу json_key
login = "ansible-playbook deploy.yml --tags login" login = "ansible-playbook deploy.yml --tags login"
# Сгенерировать логин+пароль и создать Django-суперпользователя в backend # сгенерировать логин+пароль и создать Django-суперпользователя в backend
superuser = "ansible-playbook deploy.yml -e sarex_create_superuser=true --tags superuser" superuser = "ansible-playbook deploy.yml --tags superuser"
lint = "ansible-lint" lint = "ansible-lint"
# Подготовка окружения: uv sync + установка коллекций # Полная подготовка окружения: uv sync + установка коллекций
[tool.poe.tasks.setup] [tool.poe.tasks.setup]
sequence = ["sync", "galaxy"] sequence = ["sync", "galaxy"]
# Установка с чистого листа: окружение → docker → деплой+стек → суперпользователь # Всё сразу: подготовка окружения и прогон плейбука
[tool.poe.tasks.install] [tool.poe.tasks.up]
sequence = ["sync", "galaxy", "provision", "stack", "superuser"] sequence = ["sync", "galaxy", "play"]

View File

@ -31,10 +31,6 @@ secret_length: 24
# Хранилище секретов на control-node (persist + переиспользование). Gitignored. # Хранилище секретов на control-node (persist + переиспользование). Gitignored.
secrets_store: "{{ playbook_dir }}/.secrets" 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) # Приватный реестр образов и ключ для входа (json_key)
registry_host: cr.yandex registry_host: cr.yandex
registry_username: json_key registry_username: json_key
@ -45,18 +41,3 @@ registry_key_src: /mnt/c/Users/user/.ssh/local/authorized_key.json
# docker login в cr.yandex — по умолчанию выключено. # docker login в cr.yandex — по умолчанию выключено.
sarex_compose_up: false sarex_compose_up: false
compose_cmd: "docker compose" 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

View File

@ -23,12 +23,11 @@
dest: "{{ deploy_dir }}/nginx/templates/" dest: "{{ deploy_dir }}/nginx/templates/"
mode: "0644" mode: "0644"
# backend монтирует ./backend/uwsgi.ini (http-socket + 4 воркера) и # backend монтирует ./backend/uwsgi.ini (http-socket + 4 воркера)
# ./backend/production.py (классическая JWT-аутентификация) - name: Скопировать конфиг uwsgi для backend
- name: Скопировать конфиги backend (uwsgi.ini, production.py)
ansible.builtin.copy: ansible.builtin.copy:
src: "{{ deploy_src_root }}/backend/" src: "{{ deploy_src_root }}/backend/uwsgi.ini"
dest: "{{ deploy_dir }}/backend/" dest: "{{ deploy_dir }}/backend/uwsgi.ini"
mode: "0644" mode: "0644"
# --- Секреты: генерация с персистом на control-node (идемпотентно) -------- # --- Секреты: генерация с персистом на control-node (идемпотентно) --------
@ -44,24 +43,7 @@
no_log: true no_log: true
tags: [env] tags: [env]
# RSA-ключи JWT (RS512): генерируем на control-node, persist в secrets_store. - name: Собрать словарь переопределений для .env (пароли + домены)
- 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: ansible.builtin.set_fact:
env_overrides: >- env_overrides: >-
{{ sarex_secrets | combine({ {{ sarex_secrets | combine({
@ -69,8 +51,6 @@
'ADMIN_DOMAIN': admin_domain, 'ADMIN_DOMAIN': admin_domain,
'MINIO_DOMAIN': minio_domain, 'MINIO_DOMAIN': minio_domain,
'SAREX_MINIO_CONSOLE_URL': 'https://' ~ 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 no_log: true
tags: [env] tags: [env]
@ -138,14 +118,13 @@
state: absent state: absent
tags: [login] tags: [login]
# --- Поднять сервисы приложения ------------------------------------------ # --- Опционально: поднять стек -------------------------------------------
- name: Поднять сервисы приложения (docker compose up -d) - name: Поднять стек (docker compose up -d)
ansible.builtin.command: ansible.builtin.command:
cmd: "{{ compose_cmd }} up -d {{ sarex_services | join(' ') }}" cmd: "{{ compose_cmd }} up -d"
chdir: "{{ deploy_dir }}" chdir: "{{ deploy_dir }}"
when: sarex_compose_up | bool when: sarex_compose_up | bool
changed_when: true changed_when: true
tags: [up]
# --- Django-суперпользователь: генерация логина+пароля, создание ----------- # --- Django-суперпользователь: генерация логина+пароля, создание -----------
# Только по явной команде (uv run poe superuser передаёт sarex_create_superuser=true), # Только по явной команде (uv run poe superuser передаёт sarex_create_superuser=true),

View File

@ -1,125 +0,0 @@
from .base import *
from logging.handlers import SysLogHandler
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS')
FILE_UPLOAD_PERMISSIONS = 0o644
DEBUG = False
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
SECURE_SSL_REDIRECT = False
# STATIC_ROOT = env('STATIC_ROOT')
# MEDIA_ROOT = env('MEDIA_ROOT')
# SECRET_KEY = env('SECRET_KEY')
SECRET_KEY = 't2=9+($2f%7ptsdy4!rby$)mcfl1l%o2e@vs^d(g&(wwi&%k1v'
# DATABASE CONFIGURATION START
# ------------------------------------------------------------------------------
POSTGRES_DATABASE = env('DJANGO_POSTGRES_DATABASE')
POSTGRES_USER = env('DJANGO_POSTGRES_USER')
POSTGRES_PASSWORD = env('DJANGO_POSTGRES_PASSWORD')
POSTGRES_HOST = env('DJANGO_POSTGRES_HOST')
POSTGRES_PORTS = env('DJANGO_POSTGRES_PORTS', default="5432")
DATABASES = {
'default': {
'ENGINE': 'django_prometheus.db.backends.postgresql',
'NAME': POSTGRES_DATABASE,
'USER': POSTGRES_USER,
'PASSWORD': POSTGRES_PASSWORD,
'HOST': POSTGRES_HOST,
'PORT': POSTGRES_PORTS,
}
}
# DATABASE CONFIGURATION END
# ------------------------------------------------------------------------------
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse'
}
},
'formatters': {
'verbose': {
'format': '[contactor] %(levelname)s %(asctime)s %(message)s'
},
},
'handlers': {
# Send all messages to console
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
},
# critical errors are logged to sentry
'sentry': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'logging.StreamHandler',
},
},
'loggers': {
# This is the "catch all" logger
'': {
'handlers': ['console', 'sentry'],
'level': 'DEBUG',
'propagate': False,
},
}
}
COMPARATOR_SECTION = env("COMPARATOR_SECTION", default="sarex-production-storage")
SIMPLE_JWT = {
# 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
'ACCESS_TOKEN_LIFETIME': timedelta(hours=1),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': False,
'UPDATE_LAST_LOGIN': False,
'ALGORITHM': 'RS512',
'SIGNING_KEY': env.str("JWT_PRIVATE_KEY").replace("\\n", "\n"),
'VERIFYING_KEY': env.str("JWT_PUBLIC_KEY").replace("\\n", "\n"),
'AUDIENCE': None,
'ISSUER': SIMPLE_JWT_ISSUER,
'AUTH_HEADER_TYPES': ('Bearer',),
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
'TOKEN_TYPE_CLAIM': 'token_type',
'JTI_CLAIM': 'jti',
'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
# 'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
'SLIDING_TOKEN_LIFETIME': timedelta(hours=1),
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
}
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
COMPARISON_API_URL = f"{WORKFLOWSSETTINGS.host}/comparisons"
DOCUMENTATION_API_URL = f"{WORKFLOWSSETTINGS.host}/documentations"
PDM_FILES_API_URL = f"{WORKFLOWSSETTINGS.host}/files"
# --- Override для self-hosted (auth_type=original) --------------------------
# В base.py классическая SimpleJWT-аутентификация закомментирована (образ собран
# под Zitadel), поэтому Bearer-токен от /api/token/ отвергается защищёнными
# эндпоинтами. Возвращаем её первым классом; Session — для админки.
REST_FRAMEWORK["DEFAULT_AUTHENTICATION_CLASSES"] = [
"rest_framework_simplejwt.authentication.JWTAuthentication",
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.BasicAuthentication",
]

View File

@ -181,9 +181,7 @@ services:
image: ${SAREX_REDIS_IMAGE:-cr.yandex/crp3ccidau046kdj8g9q/redis:7.0.10} image: ${SAREX_REDIS_IMAGE:-cr.yandex/crp3ccidau046kdj8g9q/redis:7.0.10}
container_name: redis container_name: redis
restart: unless-stopped restart: unless-stopped
# protected-mode no: redis только во внутренней docker-сети (порт наружу не command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"]
# публикуется), иначе бэкенд/celery из других контейнеров отбиваются "DENIED"
command: ["redis-server", "--save", "60", "1", "--loglevel", "warning", "--protected-mode", "no", "--stop-writes-on-bgsave-error", "no"]
volumes: volumes:
- sarex-redis-data:/data - sarex-redis-data:/data
healthcheck: healthcheck:
@ -275,8 +273,8 @@ services:
environment: &sarex-backend-env environment: &sarex-backend-env
ALLOWED_HOSTS: "*" ALLOWED_HOSTS: "*"
DJANGO_SETTINGS_MODULE: config.settings.production DJANGO_SETTINGS_MODULE: config.settings.production
JWT_PRIVATE_KEY: ${SAREX_JWT_PRIVATE_KEY:-JWT} JWT_PRIVATE_KEY: "JWT"
JWT_PUBLIC_KEY: ${SAREX_JWT_PUBLIC_KEY:-JPK} JWT_PUBLIC_KEY: "JPK"
DJANGO_POSTGRES_HOST: postgres DJANGO_POSTGRES_HOST: postgres
DJANGO_POSTGRES_DATABASE: ${SAREX_POSTGRES_DB:-sarex_db} DJANGO_POSTGRES_DATABASE: ${SAREX_POSTGRES_DB:-sarex_db}
DJANGO_POSTGRES_USER: ${SAREX_DJANGO_DB_USER:-django} DJANGO_POSTGRES_USER: ${SAREX_DJANGO_DB_USER:-django}
@ -302,8 +300,6 @@ services:
volumes: volumes:
# свой uwsgi.ini поверх образного: http-socket + 4 воркера (:z — SELinux/RedOS) # свой uwsgi.ini поверх образного: http-socket + 4 воркера (:z — SELinux/RedOS)
- ./backend/uwsgi.ini:/opt/sarex/uwsgi.ini:ro,z - ./backend/uwsgi.ini:/opt/sarex/uwsgi.ini:ro,z
# production.py с включённой классической JWT-аутентификацией
- ./backend/production.py:/opt/sarex/config/settings/production.py:ro,z
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy
@ -323,8 +319,6 @@ services:
entrypoint: celery -A config worker -l info -E -Q default -n default_worker.%h --concurrency=1 -B entrypoint: celery -A config worker -l info -E -Q default -n default_worker.%h --concurrency=1 -B
environment: environment:
<<: *sarex-backend-env <<: *sarex-backend-env
volumes:
- ./backend/production.py:/opt/sarex/config/settings/production.py:ro,z
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy
@ -368,7 +362,7 @@ services:
POSTGRES_SSL_USE: "0" POSTGRES_SSL_USE: "0"
ENABLE_SQL_QUERY: "0" ENABLE_SQL_QUERY: "0"
DJANGO_HOST: http://backend:8000 # django-backend в этом же compose DJANGO_HOST: http://backend:8000 # django-backend в этом же compose
PUBLIC_KEY: ${SAREX_JWT_PUBLIC_KEY:-JPK} # JWT public key (общий с backend) PUBLIC_KEY: "JPK" # JWT public key (заглушка, как у backend)
ports: ports:
- "8081:8080" - "8081:8080"
depends_on: depends_on: