Compare commits
2 Commits
0f9969ddad
...
5ef655f1bc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ef655f1bc | ||
|
|
e4953042c0 |
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"]
|
||||
|
||||
@ -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
|
||||
|
||||
@ -23,11 +23,12 @@
|
||||
dest: "{{ deploy_dir }}/nginx/templates/"
|
||||
mode: "0644"
|
||||
|
||||
# backend монтирует ./backend/uwsgi.ini (http-socket + 4 воркера)
|
||||
- name: Скопировать конфиг uwsgi для backend
|
||||
# backend монтирует ./backend/uwsgi.ini (http-socket + 4 воркера) и
|
||||
# ./backend/production.py (классическая JWT-аутентификация)
|
||||
- name: Скопировать конфиги backend (uwsgi.ini, production.py)
|
||||
ansible.builtin.copy:
|
||||
src: "{{ deploy_src_root }}/backend/uwsgi.ini"
|
||||
dest: "{{ deploy_dir }}/backend/uwsgi.ini"
|
||||
src: "{{ deploy_src_root }}/backend/"
|
||||
dest: "{{ deploy_dir }}/backend/"
|
||||
mode: "0644"
|
||||
|
||||
# --- Секреты: генерация с персистом на control-node (идемпотентно) --------
|
||||
@ -43,7 +44,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 +69,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 +138,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),
|
||||
|
||||
125
backend/production.py
Normal file
125
backend/production.py
Normal file
@ -0,0 +1,125 @@
|
||||
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",
|
||||
]
|
||||
@ -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}
|
||||
@ -300,6 +302,8 @@ services:
|
||||
volumes:
|
||||
# свой uwsgi.ini поверх образного: http-socket + 4 воркера (:z — SELinux/RedOS)
|
||||
- ./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:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
@ -319,6 +323,8 @@ services:
|
||||
entrypoint: celery -A config worker -l info -E -Q default -n default_worker.%h --concurrency=1 -B
|
||||
environment:
|
||||
<<: *sarex-backend-env
|
||||
volumes:
|
||||
- ./backend/production.py:/opt/sarex/config/settings/production.py:ro,z
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
@ -362,7 +368,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:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user