--- # --- Каталоги и файлы ---------------------------------------------------- - name: Создать каталог развёртывания ansible.builtin.file: path: "{{ deploy_dir }}" state: directory owner: root group: root mode: "0750" tags: [env] - name: Скопировать docker-compose.yaml ansible.builtin.copy: src: "{{ deploy_src_root }}/docker-compose.yaml" dest: "{{ deploy_dir }}/docker-compose.yaml" mode: "0640" # nginx монтирует ./nginx/templates и ./nginx/certs — без шаблонов nginx не # стартует, поэтому копируем конфигурацию рядом с compose. # Изменение шаблона → handler пересоздаёт nginx (envsubst рендерит на старте). - name: Скопировать шаблоны nginx ansible.builtin.copy: src: "{{ deploy_src_root }}/nginx/templates/" dest: "{{ deploy_dir }}/nginx/templates/" mode: "0644" notify: Recreate nginx # backend монтирует ./backend/uwsgi.ini (http-socket + 4 воркера) и # ./backend/production.py (классическая JWT-аутентификация). # Изменение конфигов → handler пересоздаёт backend и celery. - name: Скопировать конфиги backend (uwsgi.ini, production.py) ansible.builtin.copy: src: "{{ deploy_src_root }}/backend/" dest: "{{ deploy_dir }}/backend/" mode: "0644" notify: Recreate backend and celery # k3s-манифесты DNS-моста (namespace processing + Service/Endpoints postgres/minio). # processing-k8s-init монтирует ./k3s/manifests/processing и применяет их kubectl'ом. - name: Скопировать k3s-манифесты (DNS-мост processing) ansible.builtin.copy: src: "{{ deploy_src_root }}/k3s/manifests/" dest: "{{ deploy_dir }}/k3s/manifests/" mode: "0644" # engine монтирует ./engine/yc-s3-service-account.json (доступ Job-подов к S3). - name: Скопировать конфиги engine (S3 service-account) ansible.builtin.copy: src: "{{ deploy_src_root }}/engine/" dest: "{{ deploy_dir }}/engine/" mode: "0644" # vault монтирует ./vault/config как :ro — без конфига сервер не стартует. - name: Скопировать конфигурацию Vault ansible.builtin.copy: src: "{{ deploy_src_root }}/vault/config/" dest: "{{ deploy_dir }}/vault/config/" mode: "0644" # Каталоги PVC-данных k3s — по одному на ноду. Создаём заранее: иначе docker # создаст их от root с umask демона, а local-path пишет туда от имени подов. - name: Создать каталоги PVC-хранилища k3s (по ноде) ansible.builtin.file: path: "{{ deploy_dir }}/k3s-storage/{{ item }}" state: directory mode: "0755" loop: "{{ aero_k3s_nodes }}" # --- Секреты: генерация с персистом на control-node (идемпотентно) -------- - name: Сгенерировать/загрузить пароли (persist в secrets_store, gitignored) ansible.builtin.set_fact: sarex_secrets: >- {{ sarex_secrets | default({}) | combine({ item: lookup('password', secrets_store ~ '/' ~ inventory_hostname ~ '/' ~ item ~ ' length=' ~ secret_length ~ ' chars=ascii_letters,digits') }) }} loop: "{{ sarex_secret_vars }}" no_log: true tags: [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({ 'PLATFORM_DOMAIN': platform_domain, 'ADMIN_DOMAIN': admin_domain, 'MINIO_DOMAIN': minio_domain, 'SAREX_MINIO_CONSOLE_URL': 'https://' ~ minio_domain, 'GITEA_ADMIN_USER': gitea_admin_user, 'GITEA_ADMIN_EMAIL': gitea_admin_email, 'GITEA_ORG': gitea_org, 'GITEA_REPO': gitea_repo, 'AERO_GIT_BRANCH': aero_git_branch, 'AERO_FLUX_PATH': aero_flux_path, '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] - name: Отрендерить .env из .env.example с переопределениями ansible.builtin.template: src: env.j2 dest: "{{ deploy_dir }}/.env" owner: root group: root mode: "0600" no_log: true tags: [env] # --- Самоподписанный TLS-сертификат для трёх доменов ---------------------- - name: Создать каталог для сертификатов ansible.builtin.file: path: "{{ cert_dir }}" state: directory mode: "0750" - name: Сгенерировать самоподписанный сертификат (SAN на все домены) ansible.builtin.shell: # umask 077 → приватный ключ создаётся сразу с правами 0600 cmd: >- umask 077 && openssl req -x509 -nodes -newkey rsa:2048 -days {{ cert_days }} -keyout {{ cert_dir }}/selfsigned.key -out {{ cert_dir }}/selfsigned.crt -subj "/CN={{ platform_domain }}" -addext "subjectAltName=DNS:{{ platform_domain }},DNS:{{ admin_domain }},DNS:{{ minio_domain }}" creates: "{{ cert_dir }}/selfsigned.crt" # --- Доступ к приватному реестру (docker на хосте + registries.yaml нод) --- # Обязательно ДО подъёма k3s: registries.yaml монтируется в ноды, и k3s читает # его только при старте. - name: Настроить доступ к приватному реестру ansible.builtin.include_tasks: registry.yml tags: [login, registry] # --- GitOps-подложка: k3s + gitea + vault + Flux -------------------------- # Отдельный тег и отдельный флаг: подложка поднимается публичными образами и # не зависит от docker login, поэтому её можно накатывать независимо от стека. - name: Поднять GitOps-подложку контура ansible.builtin.include_tasks: platform.yml when: aero_platform_up | bool tags: [platform] # --- Поднять сервисы приложения ------------------------------------------ - name: Поднять сервисы приложения (docker compose up -d) ansible.builtin.command: 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), # чтобы обычный deploy не дёргал backend. Логин/пароль персистятся в secrets_store # (переиспользуются при повторе — идемпотентно). - name: Сгенерировать логин суперпользователя ansible.builtin.set_fact: superuser_name: "{{ lookup('password', secrets_store ~ '/' ~ inventory_hostname ~ '/SAREX_SUPERUSER_NAME chars=ascii_lowercase,digits length=10') }}" when: sarex_create_superuser | default(false) | bool no_log: true tags: [superuser] - name: Сгенерировать пароль суперпользователя ansible.builtin.set_fact: superuser_password: "{{ lookup('password', secrets_store ~ '/' ~ inventory_hostname ~ '/SAREX_SUPERUSER_PASSWORD chars=ascii_letters,digits length=' ~ secret_length) }}" when: sarex_create_superuser | default(false) | bool no_log: true tags: [superuser] - name: Создать/обновить Django-суперпользователя в backend when: sarex_create_superuser | default(false) | bool ansible.builtin.command: argv: - "{{ compose_cmd.split()[0] }}" - exec - -e - "SU_NAME={{ superuser_name }}" - -e - "SU_PASS={{ superuser_password }}" - -e - "SU_EMAIL={{ superuser_name }}@{{ platform_domain }}" - sarex-backend - python - manage.py - shell - -c - >- import os; from django.contrib.auth import get_user_model; U = get_user_model(); u, c = U.objects.get_or_create(username=os.environ['SU_NAME'], defaults={'email': os.environ['SU_EMAIL']}); u.is_staff = True; u.is_superuser = True; u.set_password(os.environ['SU_PASS']); u.save(); print('created' if c else 'updated') register: superuser_result changed_when: true no_log: true tags: [superuser] - name: Показать учётные данные суперпользователя ansible.builtin.debug: msg: "Django superuser → login: {{ superuser_name }} password: {{ superuser_password }}" when: sarex_create_superuser | default(false) | bool tags: [superuser]