diff --git a/sync_letsencrypt_certs_from_storm.yml b/sync_letsencrypt_certs_from_storm.yml index 1e9ed59..39b2f44 100644 --- a/sync_letsencrypt_certs_from_storm.yml +++ b/sync_letsencrypt_certs_from_storm.yml @@ -1,60 +1,83 @@ --- -- name: Универсальная динамическая синхронизация всех SSL-сертификатов Let's Encrypt +# ============================================================================== +# ЭТАП 1: Сбор сертификатов с хоста storm.bit-it.ru на ваш ПК +# ============================================================================== +- name: Безопасный сбор всех SSL-сертификатов с эталонного хоста + hosts: storm.bit-it.ru + become: true + vars: + local_tmp_dir: "/tmp/ansible_certs" + # По умолчанию пустой список означает: обрабатываем ВСЕ найденные домены + target_certs: [] + tasks: + - name: Динамический поиск папок доменов + ansible.builtin.find: + paths: "/etc/letsencrypt/live" + file_type: directory + recurse: false + excludes: "all,README" + register: found_live_dirs + + - name: Формирование списка доменов с учетом фильтрации + ansible.builtin.set_fact: + dynamic_domains: >- + {% if target_certs | length > 0 %} + {{ found_live_dirs.files | map(attribute='path') | map('basename') | intersect(target_certs) | list }} + {% else %} + {{ found_live_dirs.files | map(attribute='path') | map('basename') | list }} + {% endif %} + cacheable: true + + - name: Проверка, что список доменов для переноса не пуст + ansible.builtin.meta: end_play + when: dynamic_domains | length == 0 + comment: "Скрипт остановится, если переданных доменов нет на сервере-источнике" + + - name: Вывод списка доменов, выбранных для синхронизации + ansible.builtin.debug: + msg: "Будут синхронизированы следующие сертификаты: {{ dynamic_domains }}" + + - name: Создание изолированной папки на вашем ПК + ansible.builtin.file: + path: "{{ local_tmp_dir }}/{{ item }}" + state: directory + mode: '0700' + delegate_to: localhost + loop: [ 'archive', 'live', 'renewal' ] + + - name: Скачивание папок archive на ваш ПК + ansible.builtin.fetch: + src: "/etc/letsencrypt/archive/{{ item }}/" + dest: "{{ local_tmp_dir }}/archive/{{ item }}/" + flat: false + loop: "{{ dynamic_domains }}" + + - name: Скачивание конфигураций обновления renewal на ваш ПК + ansible.builtin.fetch: + src: "/etc/letsencrypt/renewal/{{ item }}.conf" + dest: "{{ local_tmp_dir }}/renewal/{{ item }}.conf" + flat: true + loop: "{{ dynamic_domains }}" + + - name: Скачивание папок live на ваш ПК + ansible.builtin.fetch: + src: "/etc/letsencrypt/live/{{ item.0 }}/{{ item.1 }}.pem" + dest: "{{ local_tmp_dir }}/live/{{ item.0 }}/{{ item.1 }}.pem" + flat: true + loop: "{{ dynamic_domains | product(['cert', 'chain', 'fullchain', 'privkey']) | list }}" + +# ============================================================================== +# ЭТАП 2: Раскатка сертификатов с вашего ПК и ПОЛНАЯ ЗАЧИСТКА ХВОСТОВ +# ============================================================================== +- name: Раскатка SSL-сертификатов с ПК на целевые хосты и зачистка hosts: mail_servers become: true vars: local_tmp_dir: "/tmp/ansible_certs" - # Имя вашего старого сервера, как оно записано в файле inventory Ansible - source_host_inventory_name: "old_server" + source_host_inventory_name: "storm.bit-it.ru" tasks: - - name: БЛОК АВТОМАТИЧЕСКОГО СБОРА (Выполняется на сервере-источнике) - block: - - name: Динамический поиск только папок доменов (игнорируем README и файлы) - ansible.builtin.find: - paths: "/etc/letsencrypt/live" - file_type: directory - recurse: false - excludes: "all,README" - register: found_live_dirs - - - name: Формирование чистого списка доменов - ansible.builtin.set_fact: - dynamic_domains: "{{ found_live_dirs.files | map(attribute='path') | map('basename') | list }}" - - - name: Создание локальной структуры папок на управляющей машине (Ansible Host) - ansible.builtin.file: - path: "{{ local_tmp_dir }}/{{ item }}" - state: directory - mode: '0755' - delegate_to: localhost - run_once: true - loop: [ 'archive', 'live', 'renewal' ] - - - name: Скачивание папок archive (сохраняем оригиналы файлов) - ansible.builtin.fetch: - src: "/etc/letsencrypt/archive/{{ item }}/" - dest: "{{ local_tmp_dir }}/archive/{{ item }}/" - flat: false - loop: "{{ dynamic_domains }}" - - - name: Скачивание конфигураций обновления renewal - ansible.builtin.fetch: - src: "/etc/letsencrypt/renewal/{{ item }}.conf" - dest: "{{ local_tmp_dir }}/renewal/{{ item }}.conf" - flat: true - loop: "{{ dynamic_domains }}" - - - name: Скачивание папок live (копируем исходное содержимое) - ansible.builtin.fetch: - src: "/etc/letsencrypt/live/{{ item.0 }}/{{ item.1 }}.pem" - dest: "{{ local_tmp_dir }}/live/{{ item.0 }}/{{ item.1 }}.pem" - flat: true - loop: "{{ dynamic_domains | product(['cert', 'chain', 'fullchain', 'privkey']) | list }}" - delegate_to: "{{ source_host_inventory_name }}" - run_once: true - - - name: БЛОК НАКАТЫВАНИЯ И АДАПТИВНОГО ПЕРЕЗАПУСКА (Выполняется на удаленных хостах) + - name: Основной блок выполнения раскатки block: - name: Сбор информации о запущенных сервисах на целевом хосте ansible.builtin.service_facts: @@ -103,7 +126,7 @@ force: true loop: "{{ hostvars[source_host_inventory_name]['dynamic_domains'] | product(['cert', 'chain', 'fullchain', 'privkey']) | list }}" - - name: Автоматический перезапуск активных веб- и почтовых служб (только если они запущены) + - name: Автоматический перезапуск активных веб- и почтовых служб ansible.builtin.systemd: name: "{{ item }}" state: restarted @@ -114,4 +137,12 @@ - httpd when: - ansible_facts.services[item + '.service'] is defined - - ansible_facts.services[item + '.service'].state == 'running' \ No newline at end of file + - ansible_facts.services[item + '.service'].state == 'running' + + always: + - name: Полное удаление секретных ключей с ПК администратора + ansible.builtin.file: + path: "{{ local_tmp_dir }}" + state: absent + delegate_to: localhost + run_once: true \ No newline at end of file