--- - name: Полная миграция ботов на новый сервер hosts: new_server become: yes vars: # Основная директория проекта project_root: "/home/prod" # Пользователь и группа deploy_user: "deploy" uid: "1001" gid: "1001" # Старый сервер для копирования данных old_server: "root@77.223.98.129" # Опция: пересоздавать папку /home/prod (по умолчанию — нет) recreate_project: false # Grafana настройки grafana_admin_user: "{{ lookup('env', 'GRAFANA_ADMIN_USER') | default('admin') }}" grafana_admin_password: "{{ lookup('env', 'GRAFANA_ADMIN_PASSWORD') | default('admin') }}" tasks: # ======================================== # ЭТАП 1: ПОДГОТОВКА СИСТЕМЫ (ROOT) # ======================================== - name: "[1/10] Обновить SSH host key для избежания ошибок при переустановке" known_hosts: path: ~/.ssh/known_hosts name: "{{ ansible_host }}" key: "{{ lookup('pipe', 'ssh-keyscan -t rsa,ecdsa,ed25519 ' + ansible_host) }}" state: present delegate_to: localhost run_once: true ignore_errors: yes - name: "[1/10] Обновить кэш пакетов" apt: update_cache: yes - name: "[1/10] Установить необходимые пакеты" apt: name: - docker.io - docker-compose - make - git - python3-pip - curl - sshpass - rsync - vim - zsh - ufw - htop - iotop - traceroute - ncdu - prometheus-node-exporter - fail2ban - tzdata - nginx - openssl - apache2-utils - certbot - python3-certbot-nginx state: present - name: "[1/10] Установить Python библиотеки для Ansible" pip: name: - passlib - bcrypt state: present - name: "[1/10] Установить часовой пояс Europe/Moscow" timezone: name: Europe/Moscow # ======================================== # ЭТАП 2: НАСТРОЙКА СИСТЕМЫ (ROOT) # ======================================== - name: "[2/10] Проверить существование swap-файла" stat: path: /swapfile register: swap_file_stat - name: "[2/10] Создать swap-файл (2GB)" command: fallocate -l 2G /swapfile when: not swap_file_stat.stat.exists - name: "[2/10] Установить правильные права на swap-файл" file: path: /swapfile mode: '0600' owner: root group: root - name: "[2/10] Настроить swap-файл" command: mkswap /swapfile when: not swap_file_stat.stat.exists - name: "[2/10] Включить swap-файл" command: swapon /swapfile when: not swap_file_stat.stat.exists - name: "[2/10] Настроить swappiness = 10 (временно)" sysctl: name: vm.swappiness value: '10' state: present reload: yes - name: "[2/10] Настроить swappiness = 10 (постоянно)" lineinfile: path: /etc/sysctl.conf regexp: '^vm\.swappiness\s*=' line: 'vm.swappiness = 10' state: present - name: "[2/10] Добавить swap-файл в /etc/fstab для автоматического монтирования" lineinfile: path: /etc/fstab line: '/swapfile none swap sw 0 0' state: present create: yes - name: "[2/10] Проверить статус swap" command: swapon --show register: swap_status changed_when: false - name: "[2/10] Показать информацию о swap" debug: var: swap_status.stdout_lines # Настройка параметров безопасности ядра - name: "[2/10] Настроить параметры безопасности ядра" sysctl: name: "{{ item.name }}" value: "{{ item.value }}" state: present reload: yes loop: # Защита от DDoS - { name: "net.ipv4.tcp_syn_retries", value: "2" } - { name: "net.ipv4.tcp_synack_retries", value: "2" } - { name: "net.ipv4.tcp_max_syn_backlog", value: "2048" } - { name: "net.ipv4.tcp_fin_timeout", value: "15" } - { name: "net.ipv4.tcp_keepalive_time", value: "1200" } - { name: "net.ipv4.tcp_keepalive_intvl", value: "15" } - { name: "net.ipv4.tcp_keepalive_probes", value: "5" } - { name: "net.core.netdev_max_backlog", value: "1000" } - { name: "net.core.somaxconn", value: "65535" } # Защита от IP спуфинга - { name: "net.ipv4.conf.all.accept_source_route", value: "0" } - { name: "net.ipv4.conf.default.accept_source_route", value: "0" } - { name: "net.ipv6.conf.all.accept_source_route", value: "0" } - { name: "net.ipv6.conf.default.accept_source_route", value: "0" } # Защита от фрагментации - { name: "net.ipv4.conf.all.log_martians", value: "1" } - { name: "net.ipv4.conf.default.log_martians", value: "1" } - { name: "net.ipv4.icmp_echo_ignore_broadcasts", value: "1" } - { name: "net.ipv4.icmp_ignore_bogus_error_responses", value: "1" } - { name: "net.ipv4.tcp_syncookies", value: "1" } - { name: "net.ipv4.conf.all.rp_filter", value: "1" } - { name: "net.ipv4.conf.default.rp_filter", value: "1" } # Для Docker - { name: "kernel.pid_max", value: "65536" } - { name: "kernel.threads-max", value: "4096" } - { name: "vm.max_map_count", value: "262144" } - name: "[2/10] Сохранить параметры безопасности в /etc/sysctl.conf" lineinfile: path: /etc/sysctl.conf regexp: "^{{ item.name }}\\s*=" line: "{{ item.name }} = {{ item.value }}" state: present loop: # Защита от DDoS - { name: "net.ipv4.tcp_syn_retries", value: "2" } - { name: "net.ipv4.tcp_synack_retries", value: "2" } - { name: "net.ipv4.tcp_max_syn_backlog", value: "2048" } - { name: "net.ipv4.tcp_fin_timeout", value: "15" } - { name: "net.ipv4.tcp_keepalive_time", value: "1200" } - { name: "net.ipv4.tcp_keepalive_intvl", value: "15" } - { name: "net.ipv4.tcp_keepalive_probes", value: "5" } - { name: "net.core.netdev_max_backlog", value: "1000" } - { name: "net.core.somaxconn", value: "65535" } # Защита от IP спуфинга - { name: "net.ipv4.conf.all.accept_source_route", value: "0" } - { name: "net.ipv4.conf.default.accept_source_route", value: "0" } - { name: "net.ipv6.conf.all.accept_source_route", value: "0" } - { name: "net.ipv6.conf.default.accept_source_route", value: "0" } # Защита от фрагментации - { name: "net.ipv4.conf.all.log_martians", value: "1" } - { name: "net.ipv4.conf.default.log_martians", value: "1" } - { name: "net.ipv4.icmp_echo_ignore_broadcasts", value: "1" } - { name: "net.ipv4.icmp_ignore_bogus_error_responses", value: "1" } - { name: "net.ipv4.tcp_syncookies", value: "1" } - { name: "net.ipv4.conf.all.rp_filter", value: "1" } - { name: "net.ipv4.conf.default.rp_filter", value: "1" } # Для Docker - { name: "kernel.pid_max", value: "65536" } - { name: "kernel.threads-max", value: "4096" } - { name: "vm.max_map_count", value: "262144" } # ======================================== # ЭТАП 3: СИСТЕМНЫЕ СЕРВИСЫ (ROOT) # ======================================== - name: "[3/10] Включить и запустить prometheus-node-exporter" systemd: name: prometheus-node-exporter enabled: yes state: started - name: "[3/10] Проверить статус prometheus-node-exporter" command: systemctl status prometheus-node-exporter register: node_exporter_status changed_when: false - name: "[3/10] Показать статус prometheus-node-exporter" debug: var: node_exporter_status.stdout_lines - name: "[3/10] Проверить, что node_exporter слушает на порту 9100" command: netstat -tulpn | grep 9100 register: node_exporter_port changed_when: false - name: "[3/10] Показать информацию о порте 9100" debug: var: node_exporter_port.stdout_lines - name: "[3/10] Обновить Docker Compose до последней версии" get_url: url: "https://github.com/docker/compose/releases/latest/download/docker-compose-{{ ansible_system }}-{{ ansible_architecture }}" dest: /usr/local/bin/docker-compose mode: '0755' - name: "[3/10] Включить и запустить Docker" systemd: name: docker enabled: yes state: started # ======================================== # ЭТАП 4: ПОЛЬЗОВАТЕЛЬ DEPLOY (ROOT) # ======================================== - name: "[4/10] Проверить существование пользователя deploy" getent: database: passwd key: "{{ deploy_user }}" register: user_exists failed_when: false - name: "[4/10] Создать группу deploy с GID 1001" group: name: "{{ deploy_user }}" gid: "{{ gid }}" when: user_exists.ansible_facts.getent_passwd is not defined - name: "[4/10] Создать пользователя deploy с UID 1001 (если не существует)" user: name: "{{ deploy_user }}" uid: "{{ uid }}" group: "{{ gid }}" shell: /bin/zsh create_home: yes system: no groups: docker append: yes when: user_exists.ansible_facts.getent_passwd is not defined - name: "[4/10] Установить zsh как оболочку по умолчанию для существующего пользователя deploy" user: name: "{{ deploy_user }}" shell: /bin/zsh when: user_exists.ansible_facts.getent_passwd is defined - name: "[4/10] Скопировать SSH ключ с локальной машины для пользователя deploy" authorized_key: user: "{{ deploy_user }}" key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" state: present - name: "[4/10] Настроить sudo для deploy (только Docker команды)" lineinfile: path: /etc/sudoers.d/deploy line: "{{ deploy_user }} ALL=(ALL) NOPASSWD: /usr/bin/docker, /usr/bin/docker-compose, /usr/bin/make" create: yes mode: '0440' validate: 'visudo -cf %s' - name: "[4/10] Удалить /home/prod, если требуется (чистое развертывание)" file: path: "{{ project_root }}" state: absent when: recreate_project | bool - name: "[4/10] Создать директорию проекта /home/prod" file: path: "{{ project_root }}" state: directory owner: "{{ deploy_user }}" group: "{{ deploy_user }}" mode: '0755' - name: "[4/10] Скопировать приватный SSH ключ для Git" copy: src: "~/.ssh/id_rsa" dest: "/home/{{ deploy_user }}/.ssh/id_rsa" owner: "{{ deploy_user }}" group: "{{ deploy_user }}" mode: '0600' remote_src: no - name: "[4/10] Настроить SSH config для GitHub" lineinfile: path: "/home/{{ deploy_user }}/.ssh/config" line: "Host github.com\n StrictHostKeyChecking no\n UserKnownHostsFile /dev/null" create: yes owner: "{{ deploy_user }}" group: "{{ deploy_user }}" mode: '0600' # ======================================== # ЭТАП 5: КЛОНИРОВАНИЕ РЕПОЗИТОРИЕВ (DEPLOY) # ======================================== - name: "[5/10] Исправить права на директорию проекта перед клонированием" file: path: "{{ project_root }}" owner: "{{ deploy_user }}" group: "{{ deploy_user }}" mode: '0755' recurse: yes become: yes - name: "[5/10] Клонировать основной репозиторий prod" git: repo: git@github.com:KerradKerridi/prod.git dest: "{{ project_root }}" update: yes force: yes become: yes become_user: "{{ deploy_user }}" - name: "[5/10] Клонировать AnonBot" git: repo: git@github.com:KerradKerridi/AnonBot.git dest: "{{ project_root }}/bots/AnonBot" update: yes force: yes become: yes become_user: "{{ deploy_user }}" - name: "[5/10] Клонировать telegram-helper-bot" git: repo: git@github.com:KerradKerridi/telegram-helper-bot.git dest: "{{ project_root }}/bots/telegram-helper-bot" version: dev-9 update: yes force: yes become: yes become_user: "{{ deploy_user }}" - name: "[5/10] Исправить права на все файлы после клонирования" file: path: "{{ project_root }}" owner: "{{ deploy_user }}" group: "{{ deploy_user }}" mode: '0755' recurse: yes become: yes # ======================================== # ЭТАП 6: КОПИРОВАНИЕ КОНФИГУРАЦИЙ (ROOT) # ======================================== - name: "[6/10] Скопировать конфигурацию Alertmanager" copy: src: "{{ project_root }}/infra/alertmanager/alertmanager.yml" dest: "{{ project_root }}/infra/alertmanager/alertmanager.yml" owner: "{{ deploy_user }}" group: "{{ deploy_user }}" mode: '0644' backup: yes remote_src: yes - name: "[6/10] Скопировать правила алертов Prometheus" copy: src: "{{ project_root }}/infra/prometheus/alert_rules.yml" dest: "{{ project_root }}/infra/prometheus/alert_rules.yml" owner: "{{ deploy_user }}" group: "{{ deploy_user }}" mode: '0644' backup: yes remote_src: yes - name: "[6/10] Скопировать дашборды Grafana" copy: src: "{{ project_root }}/infra/grafana/dashboards/" dest: "{{ project_root }}/infra/grafana/dashboards/" owner: "{{ deploy_user }}" group: "{{ deploy_user }}" mode: '0644' backup: yes remote_src: yes - name: "[6/10] Скопировать скрипт настройки SSL" copy: src: "{{ project_root }}/scripts/setup-ssl.sh" dest: /usr/local/bin/setup-ssl.sh owner: root group: root mode: '0755' backup: yes remote_src: yes - name: "[6/10] Установить правильные права на дашборд Node Exporter Full" file: path: "{{ project_root }}/infra/grafana/provisioning/dashboards/node-exporter-full-dashboard.json" owner: "{{ deploy_user }}" group: "{{ deploy_user }}" mode: '0644' # ======================================== # ЭТАП 7: КОПИРОВАНИЕ ДАННЫХ СО СТАРОГО СЕРВЕРА (ROOT) # ======================================== - name: "[7/10] Скопировать SSH ключ на старый сервер для копирования файлов" authorized_key: user: root key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" state: present delegate_to: "{{ old_server }}" - name: "[7/10] Копировать .env для telegram-helper-bot со старого сервера" fetch: src: "/home/prod/bots/telegram-helper-bot/.env" dest: "/tmp/telegram-helper-bot.env" flat: yes delegate_to: "{{ old_server }}" - name: "[7/10] Переместить .env для telegram-helper-bot на новое место" copy: src: "/tmp/telegram-helper-bot.env" dest: "{{ project_root }}/bots/telegram-helper-bot/.env" owner: "{{ deploy_user }}" group: "{{ deploy_user }}" mode: '0644' - name: "[7/10] Проверить размер БД для telegram-helper-bot" stat: path: "/home/prod/bots/telegram-helper-bot/database/tg-bot-database.db" delegate_to: "{{ old_server }}" register: db_size - name: "[7/10] Показать размер БД для telegram-helper-bot" debug: msg: "Размер БД: {{ (db_size.stat.size / 1024 / 1024) | round(2) }} MB" - name: "[7/10] Копировать БД для telegram-helper-bot" fetch: src: "/home/prod/bots/telegram-helper-bot/database/tg-bot-database.db" dest: "/tmp/tg-bot-database.db" flat: yes delegate_to: "{{ old_server }}" - name: "[7/10] Переместить БД для telegram-helper-bot на новое место" copy: src: "/tmp/tg-bot-database.db" dest: "{{ project_root }}/bots/telegram-helper-bot/database/tg-bot-database.db" owner: "{{ deploy_user }}" group: "{{ deploy_user }}" mode: '0644' - name: "[7/10] Создать папку voice_users на новом сервере" file: path: "{{ project_root }}/bots/telegram-helper-bot/voice_users" state: directory owner: "{{ deploy_user }}" group: "{{ deploy_user }}" mode: '0755' - name: "[7/10] Создать временную папку для voice_users на локальной машине" file: path: "/tmp/voice_users_migration" state: directory mode: '0755' delegate_to: localhost become: no - name: "[7/10] Копировать voice_users со старого сервера на локальную машину" command: > rsync -avz --progress --stats --partial --verbose root@77.223.98.129:/home/prod/bots/telegram-helper-bot/voice_users/ /tmp/voice_users_migration/ delegate_to: localhost become: no - name: "[7/10] Копировать voice_users с локальной машины на новый сервер" synchronize: src: "/tmp/voice_users_migration/" dest: "{{ project_root }}/bots/telegram-helper-bot/voice_users/" mode: push rsync_opts: "--progress --stats --partial --verbose" - name: "[7/10] Очистить временную папку на локальной машине" file: path: "/tmp/voice_users_migration" state: absent delegate_to: localhost become: no - name: "[7/10] Копировать корневой .env файл" fetch: src: "/home/prod/.env" dest: "/tmp/root.env" flat: yes delegate_to: "{{ old_server }}" - name: "[7/10] Переместить корневой .env файл на новое место" copy: src: "/tmp/root.env" dest: "{{ project_root }}/.env" owner: "{{ deploy_user }}" group: "{{ deploy_user }}" mode: '0644' - name: "[7/10] Копировать .env для AnonBot" fetch: src: "/home/prod/bots/AnonBot/.env" dest: "/tmp/anonbot.env" flat: yes delegate_to: "{{ old_server }}" - name: "[7/10] Переместить .env для AnonBot на новое место" copy: src: "/tmp/anonbot.env" dest: "{{ project_root }}/bots/AnonBot/.env" owner: "{{ deploy_user }}" group: "{{ deploy_user }}" mode: '0644' - name: "[7/10] Проверить размер БД для AnonBot" stat: path: "/home/prod/bots/AnonBot/database/anon_qna.db" delegate_to: "{{ old_server }}" register: anon_db_size - name: "[7/10] Показать размер БД для AnonBot" debug: msg: "Размер БД AnonBot: {{ (anon_db_size.stat.size / 1024 / 1024) | round(2) }} MB" - name: "[7/10] Копировать БД для AnonBot" fetch: src: "/home/prod/bots/AnonBot/database/anon_qna.db" dest: "/tmp/anon_qna.db" flat: yes delegate_to: "{{ old_server }}" - name: "[7/10] Переместить БД для AnonBot на новое место" copy: src: "/tmp/anon_qna.db" dest: "{{ project_root }}/bots/AnonBot/database/anon_qna.db" owner: "{{ deploy_user }}" group: "{{ deploy_user }}" mode: '0644' - name: "[7/10] Установить права на скопированные файлы" file: path: "{{ item }}" owner: "{{ deploy_user }}" group: "{{ deploy_user }}" mode: '0644' loop: - "{{ project_root }}/bots/telegram-helper-bot/.env" - "{{ project_root }}/bots/telegram-helper-bot/database/tg-bot-database.db" - "{{ project_root }}/bots/AnonBot/.env" - "{{ project_root }}/bots/AnonBot/database/anon_qna.db" - name: "[7/10] Исправить права доступа для voice_users (рекурсивно)" file: path: "{{ project_root }}/bots/telegram-helper-bot/voice_users" owner: "{{ deploy_user }}" group: "{{ deploy_user }}" mode: '0755' recurse: yes # ======================================== # ЭТАП 8: NGINX КОНФИГУРАЦИЯ (ROOT) # ======================================== - name: "[8/10] Остановить nginx (если запущен)" systemd: name: nginx state: stopped ignore_errors: yes - name: "[8/10] Создать директории для nginx конфигураций" file: path: "{{ item }}" state: directory owner: root group: root mode: '0755' loop: - "{{ project_root }}/infra/nginx" - "{{ project_root }}/infra/nginx/ssl" - "{{ project_root }}/infra/nginx/conf.d" - "{{ project_root }}/infra/uptime-kuma" - "{{ project_root }}/infra/alertmanager" - "{{ project_root }}/infra/grafana/dashboards" - "{{ project_root }}/scripts" - name: "[8/10] Сгенерировать самоподписанный SSL сертификат (fallback)" command: > openssl req -x509 -newkey rsa:4096 -keyout {{ project_root }}/infra/nginx/ssl/key.pem -out {{ project_root }}/infra/nginx/ssl/cert.pem -days 365 -nodes -subj "/CN={{ ansible_host }}/O=Monitoring/C=RU" args: creates: "{{ project_root }}/infra/nginx/ssl/cert.pem" when: not use_letsencrypt | default(false) - name: "[8/10] Создать директории для Let's Encrypt" file: path: "{{ item }}" state: directory owner: root group: root mode: '0755' loop: - /etc/letsencrypt - /etc/letsencrypt/live - /etc/letsencrypt/archive - /etc/letsencrypt/renewal when: use_letsencrypt | default(false) - name: "[8/10] Настроить cron для автоматического обновления SSL сертификатов" cron: name: "SSL Certificate Renewal" job: "0 2 * * 1 /usr/local/bin/ssl-renewal.sh" user: root when: use_letsencrypt | default(false) - name: "[8/10] Установить права на SSL сертификаты" file: path: "{{ item }}" owner: root group: root mode: '0600' loop: - "{{ project_root }}/infra/nginx/ssl/cert.pem" - "{{ project_root }}/infra/nginx/ssl/key.pem" - name: "[8/10] Создать htpasswd файл для status page" htpasswd: path: "{{ project_root }}/infra/nginx/.htpasswd" name: "admin" password: "{{ lookup('env', 'STATUS_PAGE_PASSWORD') | default('admin123') }}" owner: root group: root mode: '0644' - name: "[8/10] Удалить старую конфигурацию nginx" file: path: /etc/nginx/nginx.conf state: absent - name: "[8/10] Скопировать основную конфигурацию nginx" template: src: "/Users/andrejkatyhin/PycharmProjects/prod/infra/nginx/nginx.conf" dest: /etc/nginx/nginx.conf owner: root group: root mode: '0644' backup: yes vars: SERVER_IP: "{{ ansible_host }}" - name: "[8/10] Скопировать конфигурации nginx для сервисов" copy: src: "/Users/andrejkatyhin/PycharmProjects/prod/infra/nginx/conf.d/" dest: /etc/nginx/conf.d/ owner: root group: root mode: '0644' backup: yes - name: "[8/10] Создать директорию для SSL сертификатов" file: path: /etc/nginx/ssl state: directory owner: root group: root mode: '0755' - name: "[8/10] Сгенерировать самоподписанный SSL сертификат" command: > openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/ssl/privkey.pem -out /etc/nginx/ssl/fullchain.pem -subj "/C=RU/ST=Moscow/L=Moscow/O=Bot Infrastructure/OU=IT Department/CN={{ ansible_host }}" args: creates: /etc/nginx/ssl/fullchain.pem - name: "[8/10] Установить права на SSL сертификаты" file: path: "{{ item }}" owner: root group: root mode: '0600' loop: - /etc/nginx/ssl/privkey.pem - /etc/nginx/ssl/fullchain.pem - name: "[8/10] Скопировать htpasswd файл" copy: src: "{{ project_root }}/infra/nginx/.htpasswd" dest: /etc/nginx/.htpasswd owner: root group: root mode: '0644' backup: yes remote_src: yes - name: "[8/10] Включить nginx (запустим позже после контейнеров)" systemd: name: nginx enabled: yes state: stopped # ======================================== # ЭТАП 9: БЕЗОПАСНОСТЬ И ФАЙРВОЛ (ROOT) # ======================================== - name: "[9/10] Разрешить SSH (порт 22) перед включением UFW" ufw: rule: allow port: "22" proto: tcp - name: "[9/10] Разрешить новый SSH порт (15722) перед включением UFW" ufw: rule: allow port: "15722" proto: tcp - name: "[9/10] Настроить политику UFW по умолчанию" ufw: policy: deny direction: incoming - name: "[9/10] Включить UFW (файрвол)" ufw: state: enabled - name: "[9/10] Открыть порты для сервисов" ufw: rule: allow port: "{{ item }}" proto: tcp loop: - "8080" # Telegram Bot - "8081" # AnonBot - "9090" # Prometheus - "3000" # Grafana - "9100" # Node Exporter - "80" # HTTP - "443" # HTTPS - name: "[9/10] Настроить безопасный SSH" lineinfile: path: /etc/ssh/sshd_config regexp: "^{{ item.regexp }}" line: "{{ item.line }}" backup: yes loop: - { regexp: "Port", line: "Port 15722" } - { regexp: "PermitRootLogin", line: "PermitRootLogin no" } - { regexp: "PasswordAuthentication", line: "PasswordAuthentication no" } - { regexp: "PubkeyAuthentication", line: "PubkeyAuthentication yes" } - { regexp: "AllowUsers", line: "AllowUsers {{ deploy_user }}" } notify: reload ssh - name: "[9/10] Перезагрузить SSH сервис для применения настроек" systemd: name: ssh state: reloaded - name: "[9/10] Создать конфигурацию Fail2ban для SSH" copy: content: | [sshd] enabled = true port = 15722 filter = sshd logpath = /var/log/auth.log maxretry = 3 bantime = 3600 findtime = 600 dest: /etc/fail2ban/jail.d/sshd.local owner: root group: root mode: '0644' - name: "[9/10] Создать конфигурацию Fail2ban для Nginx" copy: content: | [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/error.log maxretry = 3 bantime = 3600 findtime = 600 [nginx-limit-req] enabled = true port = http,https filter = nginx-limit-req logpath = /var/log/nginx/error.log maxretry = 3 bantime = 3600 findtime = 600 dest: /etc/fail2ban/jail.d/nginx.local owner: root group: root mode: '0644' - name: "[9/10] Создать конфигурацию Fail2ban для Docker" copy: content: | [docker] enabled = true port = 2375,2376 filter = docker logpath = /var/log/syslog maxretry = 3 bantime = 3600 findtime = 600 dest: /etc/fail2ban/jail.d/docker.local owner: root group: root mode: '0644' - name: "[9/10] Включить и запустить Fail2ban" systemd: name: fail2ban enabled: yes state: started - name: "[9/10] Проверить статус Fail2ban" command: fail2ban-client status register: fail2ban_status changed_when: false - name: "[9/10] Показать статус Fail2ban" debug: var: fail2ban_status.stdout_lines # ======================================== # ЭТАП 10: ЗАПУСК ПРИЛОЖЕНИЙ И ПРОВЕРКИ (DEPLOY + ROOT) # ======================================== - name: "[10/10] Запустить ботов через make up" command: make up args: chdir: "{{ project_root }}" become: yes become_user: "{{ deploy_user }}" - name: "[10/10] Пауза на 45 секунд — дать контейнерам запуститься" pause: seconds: 45 - name: "[10/10] Проверить конфигурацию nginx (после запуска контейнеров)" command: nginx -t register: nginx_config_test changed_when: false - name: "[10/10] Показать результат проверки nginx" debug: var: nginx_config_test.stdout_lines - name: "[10/10] Запустить nginx (после запуска контейнеров)" systemd: name: nginx state: started - name: "[10/10] Проверить статус nginx" command: systemctl status nginx register: nginx_status changed_when: false - name: "[10/10] Показать статус nginx" debug: var: nginx_status.stdout_lines - name: "[10/10] Проверить, что порт 8080 (Telegram Bot) открыт" wait_for: port: 8080 host: "{{ ansible_host }}" timeout: 30 state: started - name: "[10/10] Проверить, что порт 8081 (AnonBot) открыт" wait_for: port: 8081 host: "{{ ansible_host }}" timeout: 30 state: started - name: "[10/10] Проверить, что порт 9090 (Prometheus) открыт" wait_for: port: 9090 host: "{{ ansible_host }}" timeout: 30 state: started - name: "[10/10] Проверить, что порт 3000 (Grafana) открыт" wait_for: port: 3000 host: "{{ ansible_host }}" timeout: 30 state: started - name: "[10/10] Проверить, что порт 9100 (Node Exporter) открыт" wait_for: port: 9100 host: "{{ ansible_host }}" timeout: 30 state: started - name: "[10/10] Проверить доступность Node Exporter метрик" uri: url: "http://{{ ansible_host }}:9100/metrics" method: GET status_code: 200 validate_certs: no register: node_exporter_metrics retries: 3 delay: 5 - name: "[10/10] Проверить, что порт 80 (Nginx HTTP) открыт" wait_for: port: 80 host: "{{ ansible_host }}" timeout: 30 state: started - name: "[10/10] Проверить, что порт 443 (Nginx HTTPS) открыт" wait_for: port: 443 host: "{{ ansible_host }}" timeout: 30 state: started - name: "[10/10] Проверить, что порт 3001 (Uptime Kuma) открыт" wait_for: port: 3001 host: "{{ ansible_host }}" timeout: 30 state: started # - name: "[10/10] Проверить, что порт 9093 (Alertmanager) открыт" # wait_for: # port: 9093 # host: "{{ ansible_host }}" # timeout: 30 # state: started # # Пропускаем проверку Alertmanager, так как есть проблемы с конфигурацией - name: "[10/10] Проверить доступность Nginx" uri: url: "http://{{ ansible_host }}/nginx-health" method: GET status_code: 200 validate_certs: no register: nginx_health retries: 5 delay: 10 - name: "[10/10] Проверить доступность Grafana через Nginx" uri: url: "https://{{ ansible_host }}/grafana/api/health" method: GET status_code: 200 validate_certs: no register: grafana_nginx_health retries: 5 delay: 10 - name: "[10/10] Проверить доступность Prometheus через Nginx" uri: url: "https://{{ ansible_host }}/prometheus/-/healthy" method: GET status_code: 200 validate_certs: no register: prometheus_nginx_health retries: 5 delay: 10 - name: "[10/10] Проверить доступность Grafana API" uri: url: "http://{{ ansible_host }}:3000/api/health" method: GET status_code: 200 validate_certs: no register: grafana_health retries: 5 delay: 10 - name: "[10/10] Проверить доступность Uptime Kuma через Nginx" uri: url: "https://{{ ansible_host }}/status" method: GET status_code: 200 validate_certs: no register: uptime_kuma_nginx_health retries: 5 delay: 10 - name: "[10/10] Проверить доступность Alertmanager через Nginx" uri: url: "https://{{ ansible_host }}/alertmanager/" method: GET status_code: 200 validate_certs: no register: alertmanager_nginx_health retries: 5 delay: 10 - name: "[10/10] Переподключиться по новому SSH порту" meta: reset_connection - name: "[10/10] Закрыть старый SSH порт 22 в UFW (финальный шаг)" ufw: rule: deny port: "22" proto: tcp - name: "[10/10] Проверка запуска ботов завершена — всё работает 🟢" debug: msg: "Все сервисы запущены и слушают нужные порты. SSH настроен на порт 15722, Fail2ban активен, параметры безопасности ядра применены. Порт 22 закрыт для безопасности. Добавлены: Uptime Kuma (статусная страница), Alertmanager (мониторинг), Let's Encrypt SSL, Grafana дашборды." # handlers для перезагрузки сервисов handlers: - name: reload ssh systemd: name: ssh state: reloaded - name: restart ufw ufw: state: reloaded