--- # --- Каталоги и файлы ---------------------------------------------------- - 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" # --- Секреты: генерация с персистом на 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, '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" # --- Логин в приватный реестр образов (json_key) -------------------------- - name: Проверить, выполнен ли вход в реестр ansible.builtin.command: grep -q "{{ registry_host }}" /root/.docker/config.json register: registry_login_state changed_when: false failed_when: false tags: [login] - name: Скопировать ключ реестра на хост (временно) ansible.builtin.copy: src: "{{ registry_key_src }}" dest: /root/.registry-key.json mode: "0600" when: registry_login_state.rc != 0 no_log: true tags: [login] - name: docker login в приватный реестр ansible.builtin.shell: >- set -o pipefail && cat /root/.registry-key.json | docker login --username {{ registry_username }} --password-stdin {{ registry_host }} args: executable: /bin/bash when: registry_login_state.rc != 0 register: registry_login_result changed_when: "'Login Succeeded' in registry_login_result.stdout" no_log: true tags: [login] - name: Удалить временный ключ реестра с хоста ansible.builtin.file: path: /root/.registry-key.json state: absent tags: [login] # --- Поднять сервисы приложения ------------------------------------------ - 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]