Files
prod/infra/ansible/playbook.yml
Andrey 1db579797d refactor: update Nginx configuration and Docker setup
- Change user directive in Nginx configuration from 'nginx' to 'www-data'.
- Update upstream server configurations in Nginx to use 'localhost' instead of service names.
- Modify Nginx server block to redirect HTTP to a status page instead of Grafana.
- Rename Alertmanager location from '/alertmanager/' to '/alerts/' for consistency.
- Remove deprecated status page configuration and related files.
- Adjust Prometheus configuration to reflect the new Docker network settings.
2025-09-18 21:21:23 +03:00

1027 lines
36 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
- 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] Клонировать основной репозиторий 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 }}"
# ========================================
# ЭТАП 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] Создать конфигурацию 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] Проверить, что порт 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
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
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