Files
prod/infra/ansible/playbook.yml
Andrey 2661b3865e fix: update Dockerfile reference in docker-compose and add versioning to Ansible playbook
- Change Dockerfile reference in docker-compose.yml from Dockerfile.bot to Dockerfile
- Add versioning comment for the telegram-helper-bot repository in playbook.yml
2025-09-16 18:51:05 +03:00

720 lines
24 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:
- name: Обновить 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: Обновить кэш пакетов
apt:
update_cache: yes
- name: Установить необходимые пакеты
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
state: present
- name: Установить часовой пояс Europe/Moscow
timezone:
name: Europe/Moscow
- name: Проверить существование swap-файла
stat:
path: /swapfile
register: swap_file_stat
- name: Создать swap-файл (2GB)
command: fallocate -l 2G /swapfile
when: not swap_file_stat.stat.exists
- name: Установить правильные права на swap-файл
file:
path: /swapfile
mode: '0600'
owner: root
group: root
- name: Настроить swap-файл
command: mkswap /swapfile
when: not swap_file_stat.stat.exists
- name: Включить swap-файл
command: swapon /swapfile
when: not swap_file_stat.stat.exists
- name: Настроить swappiness = 10 (временно)
sysctl:
name: vm.swappiness
value: '10'
state: present
reload: yes
- name: Настроить swappiness = 10 (постоянно)
lineinfile:
path: /etc/sysctl.conf
regexp: '^vm\.swappiness\s*='
line: 'vm.swappiness = 10'
state: present
- name: Добавить swap-файл в /etc/fstab для автоматического монтирования
lineinfile:
path: /etc/fstab
line: '/swapfile none swap sw 0 0'
state: present
create: yes
# --- НАСТРОЙКА БЕЗОПАСНОСТИ ЯДРА ---
- name: Настроить параметры безопасности ядра
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: Сохранить параметры безопасности в /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" }
- name: Проверить статус swap
command: swapon --show
register: swap_status
changed_when: false
- name: Показать информацию о swap
debug:
var: swap_status.stdout_lines
- name: Включить и запустить prometheus-node-exporter
systemd:
name: prometheus-node-exporter
enabled: yes
state: started
- name: Проверить статус prometheus-node-exporter
command: systemctl status prometheus-node-exporter
register: node_exporter_status
changed_when: false
- name: Показать статус prometheus-node-exporter
debug:
var: node_exporter_status.stdout_lines
- name: Проверить, что node_exporter слушает на порту 9100
command: netstat -tulpn | grep 9100
register: node_exporter_port
changed_when: false
- name: Показать информацию о порте 9100
debug:
var: node_exporter_port.stdout_lines
- name: Обновить 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'
become: yes
- name: Включить и запустить Docker
systemd:
name: docker
enabled: yes
state: started
- name: Разрешить SSH (порт 22) перед включением UFW
ufw:
rule: allow
port: "22"
proto: tcp
- name: Разрешить новый SSH порт (15722) перед включением UFW
ufw:
rule: allow
port: "15722"
proto: tcp
- name: Настроить политику UFW по умолчанию
ufw:
policy: deny
direction: incoming
- name: Включить UFW (файрвол)
ufw:
state: enabled
- name: Открыть порты для сервисов
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: Проверить существование пользователя deploy
getent:
database: passwd
key: "{{ deploy_user }}"
register: user_exists
failed_when: false
- name: Создать группу deploy с GID 1001
group:
name: "{{ deploy_user }}"
gid: "{{ gid }}"
when: user_exists.ansible_facts.getent_passwd is not defined
- name: Создать пользователя 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: Установить zsh как оболочку по умолчанию для существующего пользователя deploy
user:
name: "{{ deploy_user }}"
shell: /bin/zsh
when: user_exists.ansible_facts.getent_passwd is defined
- name: Скопировать SSH ключ с локальной машины для пользователя deploy
authorized_key:
user: "{{ deploy_user }}"
key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
state: present
- name: Настроить безопасный 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: Переподключиться по новому SSH порту
meta: reset_connection
- name: Удалить /home/prod, если требуется (чистое развертывание)
file:
path: "{{ project_root }}"
state: absent
when: recreate_project | bool
- name: Создать директорию проекта /home/prod
file:
path: "{{ project_root }}"
state: directory
owner: "{{ deploy_user }}"
group: "{{ deploy_user }}"
mode: '0755'
- name: Настроить SSH ключи для GitHub
authorized_key:
user: "{{ deploy_user }}"
key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
state: present
- name: Скопировать приватный 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: Настроить 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'
- name: Переключиться на пользователя deploy
meta: reset_connection
- name: Клонировать основной репозиторий prod
git:
repo: git@github.com:KerradKerridi/prod.git
dest: "{{ project_root }}"
clone: yes
update: yes
become: yes
become_user: "{{ deploy_user }}"
- name: Клонировать AnonBot
git:
repo: git@github.com:KerradKerridi/AnonBot.git
dest: "{{ project_root }}/bots/AnonBot"
clone: yes
update: yes
become: yes
become_user: "{{ deploy_user }}"
- name: Клонировать telegram-helper-bot
git:
repo: git@github.com:KerradKerridi/telegram-helper-bot.git
dest: "{{ project_root }}/bots/telegram-helper-bot"
# TODO: Убрать после тестирования
version: dev-9
clone: yes
update: yes
become: yes
become_user: "{{ deploy_user }}"
- name: Установить правильные права на дашборд Node Exporter Full
file:
path: "{{ project_root }}/infra/grafana/provisioning/dashboards/node-exporter-full-dashboard.json"
owner: "{{ deploy_user }}"
group: "{{ deploy_user }}"
mode: '0644'
become: yes
become_user: "{{ deploy_user }}"
- name: Скопировать SSH ключ на старый сервер для копирования файлов
authorized_key:
user: root
key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
state: present
delegate_to: "{{ old_server }}"
- name: Копировать .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: Переместить .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'
become: yes
become_user: "{{ deploy_user }}"
- name: Проверить размер БД для telegram-helper-bot
stat:
path: "/home/prod/bots/telegram-helper-bot/database/tg-bot-database.db"
delegate_to: "{{ old_server }}"
register: db_size
- name: Показать размер БД для telegram-helper-bot
debug:
msg: "Размер БД: {{ (db_size.stat.size / 1024 / 1024) | round(2) }} MB"
- name: Копировать БД для 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: Переместить БД для 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'
become: yes
become_user: "{{ deploy_user }}"
- name: Создать папку voice_users на новом сервере
file:
path: "{{ project_root }}/bots/telegram-helper-bot/voice_users"
state: directory
owner: "{{ deploy_user }}"
group: "{{ deploy_user }}"
mode: '0755'
become: yes
become_user: "{{ deploy_user }}"
- name: Создать временную папку для voice_users на локальной машине
file:
path: "/tmp/voice_users_migration"
state: directory
mode: '0755'
delegate_to: localhost
become: no
- name: Копировать 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: Копировать 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"
become: yes
become_user: "{{ deploy_user }}"
- name: Очистить временную папку на локальной машине
file:
path: "/tmp/voice_users_migration"
state: absent
delegate_to: localhost
become: no
- name: Копировать корневой .env файл
fetch:
src: "/home/prod/.env"
dest: "/tmp/root.env"
flat: yes
delegate_to: "{{ old_server }}"
- name: Переместить корневой .env файл на новое место
copy:
src: "/tmp/root.env"
dest: "{{ project_root }}/.env"
owner: "{{ deploy_user }}"
group: "{{ deploy_user }}"
mode: '0644'
become: yes
become_user: "{{ deploy_user }}"
- name: Копировать .env для AnonBot
fetch:
src: "/home/prod/bots/AnonBot/.env"
dest: "/tmp/anonbot.env"
flat: yes
delegate_to: "{{ old_server }}"
- name: Переместить .env для AnonBot на новое место
copy:
src: "/tmp/anonbot.env"
dest: "{{ project_root }}/bots/AnonBot/.env"
owner: "{{ deploy_user }}"
group: "{{ deploy_user }}"
mode: '0644'
become: yes
become_user: "{{ deploy_user }}"
- name: Проверить размер БД для AnonBot
stat:
path: "/home/prod/bots/AnonBot/database/anon_qna.db"
delegate_to: "{{ old_server }}"
register: anon_db_size
- name: Показать размер БД для AnonBot
debug:
msg: "Размер БД AnonBot: {{ (anon_db_size.stat.size / 1024 / 1024) | round(2) }} MB"
- name: Копировать БД для AnonBot
fetch:
src: "/home/prod/bots/AnonBot/database/anon_qna.db"
dest: "/tmp/anon_qna.db"
flat: yes
delegate_to: "{{ old_server }}"
- name: Переместить БД для AnonBot на новое место
copy:
src: "/tmp/anon_qna.db"
dest: "{{ project_root }}/bots/AnonBot/database/anon_qna.db"
owner: "{{ deploy_user }}"
group: "{{ deploy_user }}"
mode: '0644'
become: yes
become_user: "{{ deploy_user }}"
- name: Установить права на скопированные файлы
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"
become: yes
- name: Исправить права доступа для voice_users (рекурсивно)
file:
path: "{{ project_root }}/bots/telegram-helper-bot/voice_users"
owner: "{{ deploy_user }}"
group: "{{ deploy_user }}"
mode: '0755'
recurse: yes
become: yes
- name: Запустить ботов через make up
command: make up
args:
chdir: "{{ project_root }}"
become: yes
become_user: "{{ deploy_user }}"
tags: ["start_bots"]
# --- НАСТРОЙКА FAIL2BAN ---
- name: Создать конфигурацию 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: Создать конфигурацию 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: Создать конфигурацию 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: Включить и запустить Fail2ban
systemd:
name: fail2ban
enabled: yes
state: started
- name: Проверить статус Fail2ban
command: fail2ban-client status
register: fail2ban_status
changed_when: false
- name: Показать статус Fail2ban
debug:
var: fail2ban_status.stdout_lines
# --- НОВОЕ: Проверка портов ---
- name: Пауза на 30 секунд — дать контейнерам запуститься
pause:
seconds: 30
- name: Проверить, что порт 8080 (Telegram Bot) открыт
wait_for:
port: 8080
host: "{{ ansible_host }}"
timeout: 30
state: started
- name: Проверить, что порт 8081 (AnonBot) открыт
wait_for:
port: 8081
host: "{{ ansible_host }}"
timeout: 30
state: started
- name: Проверить, что порт 9090 (Prometheus) открыт
wait_for:
port: 9090
host: "{{ ansible_host }}"
timeout: 30
state: started
- name: Проверить, что порт 3000 (Grafana) открыт
wait_for:
port: 3000
host: "{{ ansible_host }}"
timeout: 30
state: started
- name: Проверить, что порт 9100 (Node Exporter) открыт
wait_for:
port: 9100
host: "{{ ansible_host }}"
timeout: 30
state: started
- name: Проверить доступность Grafana API
uri:
url: "http://{{ ansible_host }}:3000/api/health"
method: GET
status_code: 200
register: grafana_health
retries: 5
delay: 10
- name: Закрыть старый SSH порт 22 в UFW (финальный шаг)
ufw:
rule: deny
port: "22"
proto: tcp
- name: Проверка запуска ботов завершена — всё работает 🟢
debug:
msg: "Все сервисы запущены и слушают нужные порты. SSH настроен на порт 15722, Fail2ban активен, параметры безопасности ядра применены. Порт 22 закрыт для безопасности."
# handlers для перезагрузки сервисов
handlers:
- name: reload ssh
systemd:
name: ssh
state: reloaded
- name: restart ufw
ufw:
state: reloaded