Work version

This commit is contained in:
Magnus Root 2026-02-24 11:30:35 +03:00
commit dac9a1e3f4
197 changed files with 14953 additions and 0 deletions

0
.ansible/.lock Normal file
View file

View file

@ -0,0 +1,26 @@
---
name: YAML Lint
on: [push]
jobs:
yamllint:
runs-on: ubuntu-yamllint
steps:
- name: Checkout linting
uses: actions/checkout@v4
- name: Run yamllint with auto-detected config
run: |
yamllint -c .yamllint .
gitleaks:
runs-on: ubuntu-yamllint
steps:
- name: Checkout passwords leaks
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run gitleaks
run: gitleaks detect --source . --verbose
...

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.vscode/

7
.gitleaks.toml Normal file
View file

@ -0,0 +1,7 @@
[extend]
useDefault = true
[allowlist]
paths = [
# '''roles/logs_settings_on_ptaf/templates/filebeat.yml.j2''',
]

11
.yamllint Normal file
View file

@ -0,0 +1,11 @@
---
extends: default
ignore: |
roles/filebeat_install/files/fields.yml
.gitea/workflows/yamllint.yml
rules:
line-length:
max: 180
...

40
README.md Executable file
View file

@ -0,0 +1,40 @@
# waf_playbooks
Репозиторий для всех задач решаемых на WAF с помощью ansible.
Все изменения должны делаться в новых ветках и потом мерджиться в master.
При пуше коммитов в гит происходит проверка кода на соответствие правилам оформления кода с помощью `yamllint`, а также проверка на утечки с помощью `gitleaks`. Если проверка не будет пройдена, то не будет возможности смёрджить изменения в основную ветку. Так что если ваш коммит н еможет пройти какую-либо проверку, то можно запустить проверку этими же инструментами локально и найти в чём проблема.
## Dockerfile для yamllint и gitleaks
```bash
FROM docker.gitea.com/runner-images:ubuntu-latest
RUN apt-get update && \
apt-get install -y yamllint && \
apt-get install -y wget && \
wget https://github.com/gitleaks/gitleaks/releases/download/v8.18.4/gitleaks_8.18.4_linux_x64.tar.gz && \
tar -xzf gitleaks_8.18.4_linux_x64.tar.gz && \
mv gitleaks /usr/local/bin/ && \
rm gitleaks_8.18.4_linux_x64.tar.gz && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
```
## Docker-compose.yml для yamllint и gitleaks
```bash
version: '3'
services:
act_runner:
image: gitea/act_runner:latest
privileged: true
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./runner_data:/data
environment:
- GITEA_INSTANCE_URL=https://svc-git.cirex.ru
- GITEA_RUNNER_REGISTRATION_TOKEN=EQcod
- GITEA_RUNNER_NAME=my-docker-runner
- GITEA_RUNNER_LABELS=ubuntu-yamllint:docker://ubuntu-yamllint:latest,ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest,ubuntu-22.04:docker://docker.gitea.com/runner-images:ubuntu-22.04
restart: unless-stopped
```

30
ansible.cfg Executable file
View file

@ -0,0 +1,30 @@
[defaults]
inventory = ./inventory/hosts.yml
host_key_checking = False
pipelining = True
forks = 10
strategy = free
stdout_callback = default
callback_whitelist = default
callback_default_result_format = yaml
remote_user = ansible
python_interpreter = /usr/bin/python3
gathering = smart
fact_caching_timeoout = 86400
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_fact_cache
[privilege_escalation]
become = True
become_method = sudo
become_user = root
[persistent_connections]
pipelining = True
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
retries = 3
[logging]
log_path = /var/log/ansible.log

4
inventory/group_vars/AZ1.yml Executable file
View file

@ -0,0 +1,4 @@
---
gray_gateway:
white_gateway:
...

4
inventory/group_vars/AZ2.yml Executable file
View file

@ -0,0 +1,4 @@
---
gray_gateway:
white_gateway:
...

View file

@ -0,0 +1,4 @@
---
gray_gateway:
white_gateway:
...

View file

@ -0,0 +1,4 @@
---
gray_gateway:
white_gateway:
...

159
inventory/group_vars/prod.yml Executable file
View file

@ -0,0 +1,159 @@
---
api_sp_token:
db_user:
db_password:
telegram_bot_token:
git_token:
telegram_chat_id:
grafana_api_token:
db_name: test_info
username_on_new_hosts: user
install_ssh_pass:
timezone: "Europe/Moscow"
ntp_servers:
-
-
dns_servers:
-
-
need_support_soft:
- xrdp
- tree
- mc
- wget
- gpg
- python3
- ansible
- git
- apt-transport-https
- ca-certificates
- curl
- apt-utils
- gnupg2
xrdp_pass:
primary_db_host: '10.10.10.8'
secondary_db_host: '10.10.10.5'
arch_version: amd64
ubuntu_codename: noble
zabbix_version: "7.0"
zabbix_server: "10.10.20.5"
zabbix_port: "10050"
zabbix_hostname: system.hostname
ubuntu_version: "24.04"
compose_version: 2.39.3
iptables_version: 1.8.10-3ubuntu2
docker_agent_version: '4.1.6.409465'
docker_image: ptaf-core-nginx-agent:release-{{ docker_agent_version }}
local_directory_docker_image: /home/install/ptaf-core-nginx-agent_release-{{ docker_agent_version }}.tar
magos_bin: "/usr/local/bin/magos"
number_of_worker_processes: 4
number_of_worker_connections: 64000
sysctl_params:
net.core.netdev_max_backlog: 10000
net.core.somaxconn: 262144
net.ipv4.tcp_syncookies: 1
net.ipv4.tcp_max_syn_backlog: 262144
net.ipv4.tcp_max_tw_buckets: 720000
net.ipv4.tcp_timestamps: 1
net.ipv4.tcp_tw_reuse: 1
net.ipv4.tcp_fin_timeout: 30
net.ipv4.tcp_keepalive_time: 1800
net.ipv4.tcp_keepalive_probes: 7
net.ipv4.tcp_keepalive_intvl: 30
net.core.wmem_max: 33554432
net.core.rmem_max: 33554432
net.core.rmem_default: 8388608
net.core.wmem_default: 4194394
net.ipv4.tcp_rmem: "4096 8388608 16777216"
net.ipv4.tcp_wmem: "4096 4194394 16777216"
net.ipv4.ip_local_port_range: "1024 65535"
net.netfilter.nf_conntrack_max: 1048576
antibot_networks:
-
-
waf_networks:
-
-
need_soft:
- tree
- mc
- ncdu
- chrony
- sqlite3
- python3-pip
- postgresql-client-common
- postgresql-client-16
- mtr
- traceroute
- angie-module-njs
user_list:
- username:
home_dir:
key_file:
- username:
home_dir:
key_file:
need_install_firewall: true
app_config:
Test_service:
# По умолчанию пусто. Если надо добавить, то добалять адрес с : на конце
LISTEN_IP:
TENANT_NAME: "Test_service"
CONTAINER_NUMBERS: 1
# Есть ли настройки без кастомов
USUAL_SETTINGS: true
# Здесь и далее указывается только то у чего нет кастомных настроек.
# Ни апстирмов, ни портов, ни server.
SID_NUMBERS:
- 11111
- 11111
- 11111
# Приложения в тенанте. Подставляются в шаблоны в апстримы и имена логов
APP_IN_TENANT_NAMES:
- "test_1_ru"
- "test_2_ru"
- "test_3_ru"
# Приложения в тенанте. Подставляются в server_name.
# Если их несколько для одного приложения, то пишутся в одну строку
SERVER_NAMES_FOR_APPS:
- "test.1.ru"
- "test.2.ru"
- "test.3.ru"
# Адреса ориджинов. Подставляются в апстримы
UPSTREAM_IPS:
- 91.206.126.155
- 91.206.126.36
- 91.206.126.162
# Группировка ориджинов на случай, если в апстриме больше одного адреса
UPSTREAM_GROUPS:
- ips: "{{ app_config[app].UPSTREAM_IPS[:1] }}"
# Приложения в тенанте. Подставляются в шаблоны в апстримы и имена логов
suffix: "test_1_ru"
- ips: "{{ app_config[app].UPSTREAM_IPS[1:2] }}"
suffix: "test_2_ru"
- ips: "{{ app_config[app].UPSTREAM_IPS[2:3] }}"
suffix: "test_3_ru"
# Есть ли кастомные правила для PT AF Nginx или кастомные locations
CUSTOM_CONFIG_PTAF_NGINX_NEED: false
# Есть ли кастомные изменения в upstream PT AF Nginx
CUSTOM_CONFIG_PTAF_NGINX_UPSTREAM_NEED: false
# Есть ли кастомные правила для Angie или кастомные locations
CUSTOM_CONFIG_ANGIE_NEED: false
# Есть ли кастомные изменения в upstream Angie
CUSTOM_CONFIG_ANGIE_UPSTREAM_NEED: false
# Есть ли кастомные порты
CUSTOM_PORTS_NEED: false
# Есть ли кастомные настройки апстримов Angie для кастомных портов
CUSTOM_CONFIG_ANGIE_UPSTREAM_NEED_WITH_CUSTOM_PORTS: false
# Есть ли кастомные настройки апстримов PT AF Nginx для кастомных портов
CUSTOM_CONFIG_PTAF_NGINX_UPSTREAM_NEED_WITH_CUSTOM_PORTS: false
# Есть ли кастомные правила Angie
CUSTOM_CONFIG_ANGIE_NEED_WITH_CUSTOM_PORTS: false
# Есть ли кастомные правила PT AF Nginx
CUSTOM_CONFIG_PTAF_NGINX_NEED_WITH_CUSTOM_PORTS: false
# Есть ли кастом в location. Не может быть без CUSTOM_CONFIG_ANGIE_NEED:true
CUSTOM_LOCATIONS_NEED: false
# Нужна ли отгрузка логов заказчику
FLUENT_BIT_NEED: false
CONNECTION_STRING:
...

20
inventory/hosts.yml Executable file
View file

@ -0,0 +1,20 @@
---
all:
children:
prod:
children:
PTAF:
children:
AZ1:
hosts:
WAF--AZ1-test01:
ansible_host: 10.10.10.4
WAF--AZ1-test02:
ansible_host: 10.10.10.5
AZ2:
hosts:
WAF-AZ2-test01:
ansible_host: 10.10.11.4
WAF-AZ2-test02:
ansible_host: 10.10.11.5
...

View file

@ -0,0 +1,3 @@
---
auspex_bin: "/usr/local/bin/auspex"
...

View file

@ -0,0 +1,82 @@
---
- name: Copy auspex from cloud
ansible.builtin.get_url:
url: ""
dest: "{{ auspex_bin }}"
owner: root
group: root
mode: '0744'
timeout: 60
force: true
retries: 3
delay: 10
- name: Create cron task for auspex cert checker
ansible.builtin.cron:
name: "Run auspex cert checker"
minute: "0"
hour: "9"
day: "*"
month: "*"
weekday: "*"
job: "{{ auspex_bin }} -c"
user: root
- name: Create cron task for auspex port checker
ansible.builtin.cron:
name: "Run auspex port checker"
minute: "*/16"
hour: "*"
day: "*"
month: "*"
weekday: "*"
job: "{{ auspex_bin }} -p"
user: root
- name: Create cron task for auspex match checker
ansible.builtin.cron:
name: "Run auspex match checker"
minute: "*/11"
hour: "*"
day: "*"
month: "*"
weekday: "*"
job: "{{ auspex_bin }} -m"
user: root
- name: Create cron task for auspex logs checker
ansible.builtin.cron:
name: "Run auspex logs checker"
minute: "*/30"
hour: "*"
day: "*"
month: "*"
weekday: "*"
job: "{{ auspex_bin }} -l"
user: root
- name: Copy auspex on and off scripts
ansible.builtin.template:
src: "{{ item }}"
dest: "/usr/local/bin/{{ (item | basename).removesuffix('.j2') }}"
owner: root
group: root
mode: '0744'
loop: "{{ query('fileglob', 'templates/*.sh.j2') }}"
- name: Create directory /etc/auspex
ansible.builtin.file:
path: /etc/auspex
state: directory
owner: root
group: root
mode: '0755'
- name: Create .env
ansible.builtin.template:
src: auspex.env.j2
dest: /etc/auspex/auspex.env
owner: root
group: root
mode: '0600'
...

View file

@ -0,0 +1,22 @@
TELEGRAM_BOT_TOKEN={{ telegram_bot_token }}
TELEGRAM_CHAT_ID={{ telegram_chat_id }}
DB_USER={{ db_user }}
DB_PASSWORD={{ db_password }}
DB_NAME=waf_info
DB_PORT=5432
PRIMARY_DB_HOST=10.100.10.8
SECONDARY_DB_HOST=10.100.13.5
PTAF_BASE_DIR=/home/install/ptaf
ANGIE_CONF_DIR=/etc/angie/http.d
SSL_DIR=/etc/ssl
NGINX_CONFIG_GLOB=/home/install/conf/ptaf-nginx/*/*/*/*.conf
STATE_FILE_PATH=/tmp/port_checker_state.json
LOG_DIR=/var/log/ptaf_nginx
CERT_CRITICAL_DAYS=3
CERT_WARNING_DAYS=15
CERT_INFO_DAYS=30
LOG_STALE_MINUTES=61
CHECK_TIMEOUT_SEC=10
MAX_RETRIES=3
RETRY_DELAY_SEC=2
MAX_WORKERS=10

View file

@ -0,0 +1,34 @@
#!/bin/bash
# Скрипт для комментирования строки в root crontab
# Строка, которую нужно найти (без комментария)
TARGET_LINE="*/30 * * * * {{ auspex_bin }} -l"
# Проверяем, запущен ли скрипт от root
if [ "$EUID" -ne 0 ]; then
echo "Ошибка: скрипт должен быть запущен от имени root"
exit 1
fi
# Получаем текущий crontab
CURRENT_CRON=$(crontab -l 2>/dev/null)
# Экранируем специальные символы для использования в regex
ESCAPED_LINE=$(echo "$TARGET_LINE" | sed 's/[.*[\^$]/\\&/g')
# Проверяем, существует ли строка в незакомментированном виде
if echo "$CURRENT_CRON" | grep -qE "^${ESCAPED_LINE}"; then
# Строка найдена и не закомментирована - комментируем её
echo "$CURRENT_CRON" | sed "s|^\(${ESCAPED_LINE}\)|#\1|" | crontab -
echo "Строка закомментирована успешно"
exit 0
elif echo "$CURRENT_CRON" | grep -qE "^#${ESCAPED_LINE}"; then
# Строка уже закомментирована
echo "Строка уже закомментирована, ничего не делаем"
exit 0
else
# Строка не найдена вообще
echo "Предупреждение: строка не найдена в crontab"
exit 2
fi

View file

@ -0,0 +1,34 @@
#!/bin/bash
# Скрипт для раскомментирования строки в root crontab
# Строка, которую нужно найти (без комментария)
TARGET_LINE="*/30 * * * * {{ auspex_bin }} -l"
# Проверяем, запущен ли скрипт от root
if [ "$EUID" -ne 0 ]; then
echo "Ошибка: скрипт должен быть запущен от имени root"
exit 1
fi
# Получаем текущий crontab
CURRENT_CRON=$(crontab -l 2>/dev/null)
# Экранируем специальные символы для использования в regex
ESCAPED_LINE=$(echo "$TARGET_LINE" | sed 's/[.*[\^$]/\\&/g')
# Проверяем, существует ли строка в закомментированном виде
if echo "$CURRENT_CRON" | grep -qE "^#${ESCAPED_LINE}"; then
# Строка найдена и закомментирована - раскомментируем её
echo "$CURRENT_CRON" | sed "s|^#\(${ESCAPED_LINE}\)|\1|" | crontab -
echo "Строка раскомментирована успешно"
exit 0
elif echo "$CURRENT_CRON" | grep -qE "^${ESCAPED_LINE}"; then
# Строка уже раскомментирована
echo "Строка уже активна, ничего не делаем"
exit 0
else
# Строка не найдена вообще
echo "Предупреждение: строка не найдена в crontab"
exit 2
fi

View file

@ -0,0 +1,34 @@
#!/bin/bash
# Скрипт для комментирования строки в root crontab
# Строка, которую нужно найти (без комментария)
TARGET_LINE="*/11 * * * * {{ auspex_bin }} -m"
# Проверяем, запущен ли скрипт от root
if [ "$EUID" -ne 0 ]; then
echo "Ошибка: скрипт должен быть запущен от имени root"
exit 1
fi
# Получаем текущий crontab
CURRENT_CRON=$(crontab -l 2>/dev/null)
# Экранируем специальные символы для использования в regex
ESCAPED_LINE=$(echo "$TARGET_LINE" | sed 's/[.*[\^$]/\\&/g')
# Проверяем, существует ли строка в незакомментированном виде
if echo "$CURRENT_CRON" | grep -qE "^${ESCAPED_LINE}"; then
# Строка найдена и не закомментирована - комментируем её
echo "$CURRENT_CRON" | sed "s|^\(${ESCAPED_LINE}\)|#\1|" | crontab -
echo "Строка закомментирована успешно"
exit 0
elif echo "$CURRENT_CRON" | grep -qE "^#${ESCAPED_LINE}"; then
# Строка уже закомментирована
echo "Строка уже закомментирована, ничего не делаем"
exit 0
else
# Строка не найдена вообще
echo "Предупреждение: строка не найдена в crontab"
exit 2
fi

View file

@ -0,0 +1,34 @@
#!/bin/bash
# Скрипт для раскомментирования строки в root crontab
# Строка, которую нужно найти (без комментария)
TARGET_LINE="*/11 * * * * {{ auspex_bin }} -m"
# Проверяем, запущен ли скрипт от root
if [ "$EUID" -ne 0 ]; then
echo "Ошибка: скрипт должен быть запущен от имени root"
exit 1
fi
# Получаем текущий crontab
CURRENT_CRON=$(crontab -l 2>/dev/null)
# Экранируем специальные символы для использования в regex
ESCAPED_LINE=$(echo "$TARGET_LINE" | sed 's/[.*[\^$]/\\&/g')
# Проверяем, существует ли строка в закомментированном виде
if echo "$CURRENT_CRON" | grep -qE "^#${ESCAPED_LINE}"; then
# Строка найдена и закомментирована - раскомментируем её
echo "$CURRENT_CRON" | sed "s|^#\(${ESCAPED_LINE}\)|\1|" | crontab -
echo "Строка раскомментирована успешно"
exit 0
elif echo "$CURRENT_CRON" | grep -qE "^${ESCAPED_LINE}"; then
# Строка уже раскомментирована
echo "Строка уже активна, ничего не делаем"
exit 0
else
# Строка не найдена вообще
echo "Предупреждение: строка не найдена в crontab"
exit 2
fi

View file

@ -0,0 +1,34 @@
#!/bin/bash
# Скрипт для комментирования строки в root crontab
# Строка, которую нужно найти (без комментария)
TARGET_LINE="*/11 * * * * {{ auspex_bin }} -p"
# Проверяем, запущен ли скрипт от root
if [ "$EUID" -ne 0 ]; then
echo "Ошибка: скрипт должен быть запущен от имени root"
exit 1
fi
# Получаем текущий crontab
CURRENT_CRON=$(crontab -l 2>/dev/null)
# Экранируем специальные символы для использования в regex
ESCAPED_LINE=$(echo "$TARGET_LINE" | sed 's/[.*[\^$]/\\&/g')
# Проверяем, существует ли строка в незакомментированном виде
if echo "$CURRENT_CRON" | grep -qE "^${ESCAPED_LINE}"; then
# Строка найдена и не закомментирована - комментируем её
echo "$CURRENT_CRON" | sed "s|^\(${ESCAPED_LINE}\)|#\1|" | crontab -
echo "Строка закомментирована успешно"
exit 0
elif echo "$CURRENT_CRON" | grep -qE "^#${ESCAPED_LINE}"; then
# Строка уже закомментирована
echo "Строка уже закомментирована, ничего не делаем"
exit 0
else
# Строка не найдена вообще
echo "Предупреждение: строка не найдена в crontab"
exit 2
fi

View file

@ -0,0 +1,34 @@
#!/bin/bash
# Скрипт для раскомментирования строки в root crontab
# Строка, которую нужно найти (без комментария)
TARGET_LINE="*/11 * * * * {{ auspex_bin }} -p"
# Проверяем, запущен ли скрипт от root
if [ "$EUID" -ne 0 ]; then
echo "Ошибка: скрипт должен быть запущен от имени root"
exit 1
fi
# Получаем текущий crontab
CURRENT_CRON=$(crontab -l 2>/dev/null)
# Экранируем специальные символы для использования в regex
ESCAPED_LINE=$(echo "$TARGET_LINE" | sed 's/[.*[\^$]/\\&/g')
# Проверяем, существует ли строка в закомментированном виде
if echo "$CURRENT_CRON" | grep -qE "^#${ESCAPED_LINE}"; then
# Строка найдена и закомментирована - раскомментируем её
echo "$CURRENT_CRON" | sed "s|^#\(${ESCAPED_LINE}\)|\1|" | crontab -
echo "Строка раскомментирована успешно"
exit 0
elif echo "$CURRENT_CRON" | grep -qE "^${ESCAPED_LINE}"; then
# Строка уже раскомментирована
echo "Строка уже активна, ничего не делаем"
exit 0
else
# Строка не найдена вообще
echo "Предупреждение: строка не найдена в crontab"
exit 2
fi

View file

@ -0,0 +1,3 @@
# Auspex logs off
Отключение проверки свежести логов

View file

@ -0,0 +1,16 @@
---
- name: Check utils
ansible.builtin.stat:
path: /usr/local/bin/auspex
register: utils_stat
- name: Auspex logs check off
ansible.builtin.command: /usr/local/bin/auspex_l_off.sh
register: result
when: utils_stat.stat.exists and utils_stat.stat.executable
- name: Show result
ansible.builtin.debug:
var: result.stdout
when: utils_stat.stat.exists
...

View file

@ -0,0 +1,3 @@
# Auspex logs on
Включение проверки свежести логов

View file

@ -0,0 +1,16 @@
---
- name: Check utils
ansible.builtin.stat:
path: /usr/local/bin/auspex
register: utils_stat
- name: Auspex logs check on
ansible.builtin.command: /usr/local/bin/auspex_l_on.sh
register: result
when: utils_stat.stat.exists and utils_stat.stat.executable
- name: Show result
ansible.builtin.debug:
var: result.stdout
when: utils_stat.stat.exists
...

View file

@ -0,0 +1,3 @@
# Auspex match off
Отключение проверки связности Angie и Docker

View file

@ -0,0 +1,16 @@
---
- name: Check utils
ansible.builtin.stat:
path: /usr/local/bin/auspex
register: utils_stat
- name: Auspex match off
ansible.builtin.command: /usr/local/bin/auspex_m_off.sh
register: result
when: utils_stat.stat.exists and utils_stat.stat.executable
- name: Show result
ansible.builtin.debug:
var: result.stdout
when: utils_stat.stat.exists
...

View file

@ -0,0 +1,3 @@
# Auspex logs on
Включение проверки связности Angie и Docker

View file

@ -0,0 +1,16 @@
---
- name: Check utils
ansible.builtin.stat:
path: /usr/local/bin/auspex
register: utils_stat
- name: Auspex match on
ansible.builtin.command: /usr/local/bin/auspex_m_on.sh
register: result
when: utils_stat.stat.exists and utils_stat.stat.executable
- name: Show result
ansible.builtin.debug:
var: result.stdout
when: utils_stat.stat.exists
...

View file

@ -0,0 +1,3 @@
# Auspex ports off
Отключение проверки доступности портов

View file

@ -0,0 +1,16 @@
---
- name: Check utils
ansible.builtin.stat:
path: /usr/local/bin/auspex
register: utils_stat
- name: Port checker off
ansible.builtin.command: /usr/local/bin/auspex_p_off.sh
register: result
when: utils_stat.stat.exists and utils_stat.stat.executable
- name: Show result
ansible.builtin.debug:
var: result.stdout
when: utils_stat.stat.exists
...

View file

@ -0,0 +1,3 @@
# Auspex ports on
Включение проверки доступности портов

View file

@ -0,0 +1,16 @@
---
- name: Check utils
ansible.builtin.stat:
path: /usr/local/bin/auspex
register: utils_stat
- name: Port checker on
ansible.builtin.command: /usr/local/bin/auspex_p_on.sh
register: result
when: utils_stat.stat.exists and utils_stat.stat.executable
- name: Show result
ansible.builtin.debug:
var: result.stdout
when: utils_stat.stat.exists
...

View file

@ -0,0 +1,9 @@
# Certs_settings
Роль для раскатки SSL-сертификатов.
Если нужно добавить сертификаты для стороннего АнтиДДОС, то можно просто положить их в папку files рядом с сертами SP.
Если нужно добавить сертификаты для mtls, то в директории *files* есть директория mtls, туда и нужно положить сертификаты.
НЕ ЗАБЫВАЙТЕ называть сертификаты так, чтобы было понятно к чему они принадлежат, а также шифровать их через ansible-vault.

View file

@ -0,0 +1,6 @@
---
- name: Reload angie
service:
name: "angie"
state: reloaded
...

View file

@ -0,0 +1,41 @@
---
- name: Copy antiddos cert
ansible.builtin.copy:
src: "{{ item }}"
dest: /etc/ssl/certs/
owner: root
group: root
mode: '0644'
loop: "{{ lookup('fileglob', 'files/*.crt', 'files/*.cer', 'files/*.pem', wantlist=True) }}"
notify: Reload angie
- name: Copy antiddos key
ansible.builtin.copy:
src: "{{ item }}"
dest: /etc/ssl/private/
owner: root
group: root
mode: '0644'
loop: "{{ lookup('fileglob', 'files/*.key', wantlist=True) }}"
notify: Reload angie
- name: Copy mtls certs
ansible.builtin.copy:
src: "{{ item }}"
dest: /etc/ssl/mtls/
owner: root
group: root
mode: '0644'
loop: "{{ lookup('fileglob', 'files/mtls/*.crt', 'files/mtls/*.cer', 'files/mtls/*.pem', wantlist=True) }}"
notify: Reload angie
- name: Copy mtls keys
ansible.builtin.copy:
src: "{{ item }}"
dest: /etc/ssl/mtls/
owner: root
group: root
mode: '0644'
loop: "{{ lookup('fileglob', 'files/mtls/*.key', wantlist=True) }}"
notify: Reload angie
...

4
roles/chrony_install/README.md Executable file
View file

@ -0,0 +1,4 @@
# Роль для установки и настройки Chrony для синхрониации времени
В `defaults/main` нужно указать нужную временную зону и адреса NTP-серверов.
Далее роль проверит и при необходимости установит Chrony и внесёт настройки в файл `/etc/chrony/chrony.conf`

View file

@ -0,0 +1,25 @@
---
- name: Chrony install
ansible.builtin.apt:
name: chrony
state: present
- name: Timezone settings
ansible.builtin.timezone:
name: "{{ timezone }}"
- name: Chrony settings
ansible.builtin.template:
src: chrony.conf.j2
dest: /etc/chrony/chrony.conf
owner: root
group: root
mode: '0644'
- name: Chrony enable
ansible.builtin.service:
name: chronyd
state: restarted
enabled: true
ignore_errors: "{{ ansible_check_mode }}"
...

View file

@ -0,0 +1,3 @@
{% for server in ntp_servers %}
server {{ server }} iburst
{% endfor %}

View file

@ -0,0 +1 @@
# Роль для установки различного необходимого софта

View file

@ -0,0 +1,73 @@
user angie;
worker_processes auto;
worker_rlimit_nofile 65536;
error_log /var/log/angie/error.log notice;
pid /run/angie.pid;
load_module modules/ngx_http_js_module.so;
events {
worker_connections 65536;
}
http {
server_names_hash_bucket_size 128;
include /etc/angie/mime.types;
default_type application/octet-stream;
js_path /etc/angie/njs/;
js_import main from host_transform.js;
log_format waf escape=json
'{'
'"SID":$http_x_sid,'
'"server_name":"$server_name",'
'"app_name":"$host",'
'"proxyed_to":"$upstream_addr",'
'"upstream_status":"$upstream_status",'
'"upstream_response_length":"$upstream_response_length",'
'"upstream_response_time":"$upstream_response_time",'
'"upstream_bytes_sent":"$upstream_bytes_sent",'
'"server_port":$server_port,'
'"server_addr":"$server_addr",'
'"server_protocol":"$server_protocol",'
'"remote_addr":"$remote_addr",'
'"response_status_code":$status,'
'"response_body_bytes_sent":$body_bytes_sent,'
'"http_x_forwarded_for":"$http_x_forwarded_for",'
'"uri":"$uri",'
'"http_user_agent":"$http_user_agent",'
'"query_string":"$query_string",'
'"request_method":"$request_method",'
'"request_time":$request_time,'
'"@timestamp":"$time_iso8601"'
'}';
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
log_format extended '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" rt="$request_time" '
'"$http_user_agent" "$http_x_forwarded_for" '
'h="$host" sn="$server_name" ru="$request_uri" u="$uri" '
'ucs="$upstream_cache_status" ua="$upstream_addr" us="$upstream_status" '
'uct="$upstream_connect_time" urt="$upstream_response_time"';
access_log /var/log/angie/access.log waf;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/angie/http.d/*.conf;
}
#stream {
# include /etc/angie/stream.d/*.conf;
#}

View file

@ -0,0 +1,22 @@
# Default server для порта 80 - отбрасывает неизвестные запросы
server {
listen 80 default_server;
server_name _;
# Возвращаем 444 (закрываем соединение без ответа)
return 444;
# Или можно вернуть 403/404:
# return 403 "Access denied";
}
# Default server для порта 443
server {
listen 443 ssl;
server_name _;
ssl_certificate /etc/ssl/certs/sp.crt;
ssl_certificate_key /etc/ssl/private/sp.key;
return 444;
}

View file

@ -0,0 +1,13 @@
{
"bip": "172.17.0.1/24",
"default-address-pools": [
{
"base": "172.18.0.0/16",
"size": 24
},
{
"base": "172.19.0.0/16",
"size": 24
}
]
}

View file

@ -0,0 +1,6 @@
function sanitizeHost(r) {
var host = r.headersIn.Host || '';
return host.replace(/_/g, '-');
}
export default { sanitizeHost };

View file

@ -0,0 +1,20 @@
---
services:
portainer:
container_name: portainer
image: portainer/portainer-ce:lts
restart: always
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /home/install/portainer/portainer_data:/data
ports:
- 29443:9443
volumes:
portainer_data:
name: portainer_data
networks:
default:
name: portainer_network
...

View file

@ -0,0 +1,63 @@
---
- name: Download GPG Angie key
ansible.builtin.get_url:
url: https://angie.software/keys/angie-signing.gpg
dest: /etc/apt/trusted.gpg.d/angie-signing.gpg
mode: '0644'
- name: Create sources.list for Angie
ansible.builtin.template:
src: "templates/angie.list.j2"
dest: "/etc/apt/sources.list.d/angie.list"
register: angie_gpg_result
- name: Update cache
ansible.builtin.apt:
update_cache: true
when: angie_gpg_result.changed
- name: Install Angie
ansible.builtin.apt:
name: angie
state: present
ignore_errors: "{{ ansible_check_mode }}"
- name: Change angie.conf
ansible.builtin.copy:
src: files/angie.conf
dest: /etc/angie/angie.conf
mode: '0644'
owner: root
group: root
- name: Create njs dir
ansible.builtin.file:
path: /etc/angie/njs/
state: directory
mode: '0755'
owner: root
group: root
- name: Angie njs copy
ansible.builtin.copy:
src: files/host_transform.js
dest: /etc/angie/njs/host_transform.js
mode: '0644'
owner: root
group: root
- name: Angie default route copy
ansible.builtin.copy:
src: files/angie_00-default.conf
dest: /etc/angie/http.d/00-default.conf
mode: '0644'
owner: root
group: root
- name: Start and enable Angie service
ansible.builtin.systemd:
name: angie
state: started
enabled: true
ignore_errors: "{{ ansible_check_mode }}"
...

View file

@ -0,0 +1,13 @@
---
- name: Copy lazydocker from cloud
ansible.builtin.get_url:
url: ""
dest: "/usr/local/bin/lazydocker"
owner: root
group: root
mode: '0744'
timeout: 60
retries: 3
delay: 10
when: inventory_hostname is match("^PTAF")
...

View file

@ -0,0 +1,25 @@
---
- name: Soft from Ubuntu repo
ansible.builtin.include_tasks: soft_from_ubuntu_repo.yml
- name: Angie install
ansible.builtin.include_tasks: angie_install.yml
when: inventory_hostname is match("^PTAF")
- name: Need soft install
ansible.builtin.include_tasks: need_soft_install.yml
- name: Portainer run
ansible.builtin.include_tasks: portainer_install.yml
when: inventory_hostname is match("^PTAF")
- name: Zabbix install
ansible.builtin.include_tasks: zabbix_install.yml
- name: Lazydocker install
ansible.builtin.include_tasks: lazydocker_install.yml
- name: Logrotate settings
ansible.builtin.include_role:
name: logrotate_settings
...

View file

@ -0,0 +1,96 @@
---
- name: Install prerequisites
ansible.builtin.apt:
name:
- ca-certificates
- curl
- software-properties-common
- gnupg
state: present
- name: Install iptables
ansible.builtin.apt:
name: iptables={{ iptables_version }}
state: present
when: inventory_hostname is match("^PTAF")
- name: Docker install
block:
- name: Download GPG Docker key
ansible.builtin.get_url:
url: https://download.docker.com/linux/ubuntu/gpg
dest: /etc/apt/keyrings/docker.asc
mode: '0644'
- name: Create sources.list for Docker
ansible.builtin.template:
src: "templates/docker.list.j2"
dest: "/etc/apt/sources.list.d/docker.list"
register: docker_gpg_result
- name: Update cache
ansible.builtin.apt:
update_cache: true
when: docker_gpg_result.changed
- name: Install Docker
ansible.builtin.apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
- docker-compose-plugin
state: present
register: docker_install_result
ignore_errors: "{{ ansible_check_mode }}"
- name: Install Docker Compose
ansible.builtin.get_url:
# yamllint disable-line rule:line-length
url: https://github.com/docker/compose/releases/download/v{{ compose_version }}/docker-compose-Linux-x86_64
dest: /usr/local/bin/docker-compose
mode: '0755'
register: compose_install_result
- name: Copy docker network settings
ansible.builtin.copy:
src: files/docker_network.json
dest: /etc/docker/daemon.json
owner: root
group: root
mode: '0644'
register: docker_install_result
- name: Add system users to docker group
ansible.builtin.user:
name: "{{ item }}"
groups: docker
append: true
with_items:
- zabbix
- install
ignore_errors: "{{ ansible_check_mode }}"
- name: Add TB users to docker group
ansible.builtin.user:
name: "{{ item.username }}"
groups: docker
append: true
with_items: "{{ user_list }}"
ignore_errors: "{{ ansible_check_mode }}"
when: inventory_hostname is match("^PTAF")
- name: Check need restart docker
ansible.builtin.set_fact:
# yamllint disable-line rule:line-length
need_restart: "{{ docker_install_result is changed or compose_install_result is changed }}"
when: inventory_hostname is match("^PTAF")
- name: Restart Docker service
ansible.builtin.systemd:
name: docker
state: restarted
when: need_restart
ignore_errors: "{{ ansible_check_mode }}"
...

View file

@ -0,0 +1,25 @@
---
- name: Create need dir
ansible.builtin.file:
path: /home/install/portainer
state: directory
mode: '0755'
owner: root
group: root
- name: Download portainer docker compose file
ansible.builtin.copy:
src: files/portainer_docker_compose.yml
dest: /home/install/portainer/docker-compose.yml
mode: '0644'
owner: root
group: root
ignore_errors: "{{ ansible_check_mode }}"
- name: Run Docker compose
community.docker.docker_compose_v2:
project_src: /home/install/portainer/
files:
- docker-compose.yml
ignore_errors: true
...

View file

@ -0,0 +1,8 @@
---
- name: Install different soft
ansible.builtin.apt:
name: "{{ item }}"
state: present
with_items: "{{ need_soft }}"
ignore_errors: true
...

View file

@ -0,0 +1,15 @@
---
- name: Pull and run Zabbix Agent container
community.docker.docker_container:
name: zabbix-agent-{{ zabbix_version }}
image: zabbix/zabbix-agent:{{ zabbix_version }}-ubuntu-latest
state: started
restart_policy: always
env:
ZBX_SERVER_HOST: "{{ zabbix_server }}"
ZBX_LISTENPORT: "{{ zabbix_port }}"
ZBX_HOSTNAMEITEM: "{{ zabbix_hostname }}"
ports:
- "10050:10050"
ignore_errors: true
...

View file

@ -0,0 +1 @@
deb https://download.angie.software/angie/ubuntu/{{ ubuntu_version }} {{ ubuntu_codename }} main

View file

@ -0,0 +1 @@
deb [arch={{ arch_version }} signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu {{ ubuntu_codename }} stable

4
roles/dns_settings/README.md Executable file
View file

@ -0,0 +1,4 @@
# Роль для настройки DNS-серверов
В `defaults/main` указываются dns-серверы на которые будет обращаться хост.
Далее роль подкидывает на хост заполненный шаблон по пути `/etc/resolv.conf` и перезагружает службу.

View file

@ -0,0 +1,14 @@
---
- name: New resolv conf upload
ansible.builtin.template:
src: resolv.conf.j2
dest: /etc/resolv.conf
owner: root
group: root
mode: '0644'
- name: Restart systemd-resolved
ansible.builtin.systemd:
name: systemd-resolved
state: restarted
...

View file

@ -0,0 +1,5 @@
{% for server in dns_servers %}
nameserver {{ server }}
{% endfor %}
options edns0 trust-ad
search .

View file

@ -0,0 +1,8 @@
# Роль для настройки access логов nginx и angie по одному шаблону
Во время работы роли происходит следующее:
1. Проверяется какой веб-сервер используется;
2. На основе полученных данных в нужные файлы подкидывается форма `access`-логов;
3. В конфигурационные файлы добавляется указание использовать нужную форму;
4. Устанавливается и запускается `filebeat`.

View file

@ -0,0 +1,3 @@
---
filebeat_install_filebeat_password:
...

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,46 @@
---
- name: Check if filebeat is installed
ansible.builtin.command: dpkg -s filebeat
register: filebeat_check
changed_when: false
failed_when: false
- name: Install filebeat
ansible.builtin.apt:
# yamllint disable-line rule:line-length
deb: ""
when: filebeat_check.rc != 0
- name: Make backups
ansible.builtin.copy:
src: "/etc/filebeat/{{ item }}"
dest: "/etc/filebeat/{{ item | basename }}.bak"
remote_src: true
with_items:
- filebeat.yml
- fields.yml
ignore_errors: "{{ ansible_check_mode }}"
- name: Add new filebeat conf
ansible.builtin.template:
src: templates/filebeat.yml.j2
dest: /etc/filebeat/filebeat.yml
owner: root
group: root
mode: '0644'
- name: Add new fields conf
ansible.builtin.template:
src: files/fields.yml
dest: /etc/filebeat/fields.yml
owner: root
group: root
mode: '0644'
- name: Enable filebeat
ansible.builtin.service:
name: filebeat
state: restarted
enabled: true
ignore_errors: "{{ ansible_check_mode }}"
...

View file

@ -0,0 +1,472 @@
filebeat.inputs:
- type: log
enabled: true
id: nginx-access
paths:
- /var/log/ptaf_nginx/*/*/*access.log
- /var/log/ptaf_nginx/*/*/*access.log.1
json:
keys_under_root: true
overwrite_keys: true
add_error_key: true
fields:
log_type: access
fields_under_root: true
- type: filestream
id: nginx-error
enabled: true
paths:
- /var/log/ptaf_nginx/*/*/*error.log
- /var/log/ptaf_nginx/*/*/*error.log.1
fields:
log_type: "error"
fields_under_root: true
- type: filestream
id: angie-error
enabled: true
paths:
- /var/log/angie/*error.log
- /var/log/angie/*error.log.1
fields:
log_type: "angie-error"
fields_under_root: true
setup.ilm.enabled: false
setup.template.enabled: false
setup.template.name: "ptaf"
setup.template.pattern: "ptaf-*"
# Отключаем автоматическое создание индексов
setup.dashboards.enabled: false
output.elasticsearch:
enabled: true
# Array of hosts to connect to.
hosts: ["https://10.10.10.23:9200", "https://10.10.10.24:9200"]
username: "vector"
password: {{ filebeat_install_filebeat_password }}
#Вывод в консоль
#output.console:
# codec.json:
# pretty: true
#SSL configuration for HTTPS
ssl:
verification_mode: none # Отключаем проверку сертификата
# Индексы для разных типов логов
indices:
- index: "ptaf-access-%{+yyyy.MM.dd}"
when:
equals:
log_type: "access"
- index: "ptaf-error-%{+yyyy.MM.dd}"
when:
equals:
log_type: "error"
- index: "ptaf-angie-error-%{+yyyy.MM.dd}"
when:
equals:
log_type: "angie-error"
seccomp:
enabled: false
processors:
- script:
lang: javascript
source: |
function process(event) {
// Обработка upstream_response_time
var responseTime = event.Get("upstream_response_time");
if (responseTime && typeof responseTime === 'string') {
var values = responseTime.split(",");
if (values.length > 0) {
var lastValue = values[values.length - 1].trim();
var numValue = parseFloat(lastValue);
if (!isNaN(numValue)) {
event.Put("upstream_response_time_digital", numValue);
}
}
}
// Обработка upstream_status
var upstreamStatus = event.Get("upstream_status");
if (upstreamStatus && typeof upstreamStatus === 'string') {
var statusValues = upstreamStatus.split(",");
if (statusValues.length > 0) {
var lastStatus = statusValues[statusValues.length - 1].trim();
var intStatus = parseInt(lastStatus, 10);
if (!isNaN(intStatus)) {
event.Put("upstream_status_digital", intStatus);
}
}
}
// Обработка upstream_response_length
var responseLength = event.Get("upstream_response_length");
if (responseLength && typeof responseLength === 'string') {
var lengthValues = responseLength.split(",");
if (lengthValues.length > 0) {
var lastLength = lengthValues[lengthValues.length - 1].trim();
var intLength = parseInt(lastLength, 10);
if (!isNaN(intLength)) {
event.Put("upstream_response_length_digital", intLength);
}
}
}
// Обработка upstream_bytes_sent (ДОБАВЛЕНО)
var bytesSent = event.Get("upstream_bytes_sent");
if (bytesSent && typeof bytesSent === 'string') {
var bytesValues = bytesSent.split(",");
if (bytesValues.length > 0) {
var lastBytes = bytesValues[bytesValues.length - 1].trim();
var intBytes = parseInt(lastBytes, 10);
if (!isNaN(intBytes)) {
event.Put("upstream_bytes_sent_digital", intBytes);
}
}
}
}
- drop_fields:
fields: ["agent","ecs.version","log.offset","input.type","type"]
ignore_missing: true
- dissect:
tokenizer: "/var/log/ptaf_nginx/%{TENANT}/%{}"
field: "log.file.path"
target_prefix: ""
- rename:
fields:
- from: "host.name"
to: "node_name"
ignore_missing: true
fail_on_error: false
# Процессор для составной переменной agent_name
- script:
lang: javascript
source: |
function process(event) {
var tenant = event.Get("TENANT");
var nodeName = event.Get("node_name");
var logPath = event.Get("log.file.path");
if (!tenant || !nodeName || !logPath) return;
// Извлекаем AZ часть
var azMatch = nodeName.match(/(AZ\d+)/);
var azPart = azMatch ? azMatch[1] : "AZ0";
// Извлекаем имя хоста (последняя часть после дефиса)
var parts = nodeName.split('-');
var nodePart = parts[parts.length - 1];
// Извлекаем agent (ищем "ptaf-agent001")
var agentName = "agent000";
var agentMatch = logPath.match(/ptaf-(agent\d+)/);
if (agentMatch) {
agentName = agentMatch[1];
}
// Преобразуем nodePart: test01 → t01
var shortNodePart = "t00";
if (nodePart) {
// Ищем первую букву и цифры
var nodeMatch = nodePart.match(/^([a-zA-Z])[a-zA-Z]*(\d+)$/);
if (nodeMatch) {
var letter = nodeMatch[1]; // первая буква
var digits = nodeMatch[2]; // цифры
shortNodePart = letter.toLowerCase() + digits;
} else {
// Если не нашли буквы, берем только цифры
var digitsOnly = nodePart.match(/(\d+)/);
if (digitsOnly) {
shortNodePart = "t" + digitsOnly[1];
}
}
}
// Преобразуем agentName: agent001 → a001
var shortAgentName = "a000";
if (agentName) {
// Ищем первую букву и цифры
var agentMatch = agentName.match(/^([a-zA-Z])[a-zA-Z]*(\d+)$/);
if (agentMatch) {
var letter = agentMatch[1]; // первая буква
var digits = agentMatch[2]; // цифры
shortAgentName = letter.toLowerCase() + digits;
} else {
// Если не нашли буквы, берем только цифры
var digitsOnly = agentName.match(/(\d+)/);
if (digitsOnly) {
shortAgentName = "a" + digitsOnly[1];
}
}
}
// Создаем composite_id в новом формате
event.Put("agent_name", tenant + "_" + azPart + "_" + shortNodePart + "_" + shortAgentName);
}
ignore_failure: true
# Процессор для nginx-error: парсинг timestamp из сообщения
- dissect:
tokenizer: "%{error_timestamp} [%{log_level}] %{rest}"
field: "message"
target_prefix: ""
ignore_failure: true
when:
equals:
log_type: "error"
# Преобразование timestamp для nginx-error
- timestamp:
field: "error_timestamp"
layouts:
- "2006-01-02 15:04:05.000"
timezone: "Europe/Moscow"
target_field: "@timestamp"
ignore_failure: true
when:
equals:
log_type: "error"
# Удаление временных полей для nginx-error
- drop_fields:
fields: ["error_timestamp", "log_level", "rest"]
ignore_missing: true
when:
equals:
log_type: "error"
- drop_fields:
fields: ["agent","ecs.version","log.offset","input.type","type"]
ignore_missing: true
- dissect:
field: "log.file.path"
tokenizer: '/var/log/angie/%{extracted_sid}_%{rest}'
target_prefix: ""
overwrite_keys: false # Не перезаписывать существующие поля
ignore_failure: true
# 2. Копируем extracted_sid в SID, если SID не существует
- script:
lang: javascript
source: |
function process(event) {
var extractedSid = event.Get("extracted_sid");
var existingSid = event.Get("SID");
// Если SID уже существует, оставляем его
if (existingSid !== null && existingSid !== undefined && existingSid !== "") {
// Удаляем временное поле extracted_sid
event.Delete("extracted_sid");
return;
}
// Если извлекли SID из пути и SID еще не существует
if (extractedSid && !existingSid) {
event.Put("SID", extractedSid);
// Удаляем временное поле
event.Delete("extracted_sid");
}
}
ignore_failure: true
# 3. Удаляем временное поле rest
- drop_fields:
fields: ["rest"]
ignore_missing: true
# Процессор для angie-error: парсинг timestamp из сообщения
# 1. Первичный разбор error-лога с помощью dissect
- dissect:
when:
and:
- equals:
log_type: "angie-error"
- contains:
message: "client:"
field: "message"
tokenizer: '%{error_date} %{error_time} [%{error_level}] %{pid_worker}: *%{connection_id} %{rest}'
target_prefix: "error"
overwrite_keys: true
ignore_failure: true
# 2. Расширенная обработка оставшейся части в script
- script:
when:
and:
- equals:
log_type: "angie-error"
- has_fields: ['error.rest']
lang: javascript
source: |
function process(event) {
var rest = event.Get("error.rest");
if (!rest) return;
// Разделяем текст ошибки и дополнительные поля
// Ищем первое вхождение ", client:" чтобы отделить текст ошибки
var clientIndex = rest.indexOf(", client:");
if (clientIndex === -1) {
// Если нет client:, значит это простое сообщение
event.Put("error.message_text", rest);
return;
}
// Извлекаем текст ошибки (все до ", client:")
var errorMessage = rest.substring(0, clientIndex);
event.Put("error.message_text", errorMessage.trim());
// Извлекаем ключ-значение пары
var kvString = rest.substring(clientIndex + 2); // +2 чтобы пропустить ", "
// Разбираем пары ключ:значение
parseKeyValuePairs(kvString, event);
// Дополнительно разбираем PID#worker
var pidWorker = event.Get("error.pid_worker");
if (pidWorker && pidWorker.includes("#")) {
var parts = pidWorker.split("#");
event.Put("error.pid", parts[0]);
event.Put("error.worker", parts[1]);
}
}
// Функция для разбора строки с key:value парами
function parseKeyValuePairs(kvString, event) {
var regex = /(\w+):\s*(?:"([^"]*)"|([^,]*))/g;
var match;
var fields = {};
while ((match = regex.exec(kvString)) !== null) {
var key = match[1];
var value = match[2] || match[3];
// Очищаем значение от лишних пробелов
if (value) {
value = value.trim();
}
// Сохраняем поле с соответствующим префиксом
var fieldName = "error." + key;
event.Put(fieldName, value);
}
}
ignore_failure: true
# 3. Для notice логов (без полей client, server и т.д.)
- dissect:
when:
and:
- equals:
log_type: "angie-error"
- not:
contains:
message: "client:"
field: "message"
tokenizer: '%{error_date} %{error_time} [%{error_level}] %{pid_worker}: %{error_message}'
target_prefix: "error"
overwrite_keys: true
ignore_failure: true
# 4. Создание единого поля datetime
- script:
when:
equals:
log_type: "angie-error"
lang: javascript
source: |
function process(event) {
var date = event.Get("error.error_date");
var time = event.Get("error.error_time");
if (date && time) {
// Преобразуем дату из 2026/01/29 в 2026-01-29
var isoDate = date.replace(/\//g, "-");
// Создаем строку в формате ISO 8601
var dateTime = isoDate + "T" + time;
event.Put("error.datetime", dateTime);
}
}
ignore_failure: true
# 5. Установка timestamp из лога
- timestamp:
when:
equals:
log_type: "angie-error"
field: "error.datetime"
layouts:
- "2006-01-02T15:04:05"
timezone: "Europe/Moscow"
ignore_failure: true
# 6. Дополнительная обработка специальных полей
- script:
when:
and:
- equals:
log_type: "angie-error"
- has_fields: ['error.request']
lang: javascript
source: |
function process(event) {
// Разбираем поле request на метод, путь и версию
var request = event.Get("error.request");
if (request) {
var parts = request.split(" ");
if (parts.length >= 3) {
event.Put("error.http_method", parts[0]);
event.Put("error.http_path", parts[1]);
event.Put("error.http_version", parts[2].replace("HTTP/", ""));
}
}
// Разбираем upstream URL
var upstream = event.Get("error.upstream");
if (upstream && upstream.startsWith("http")) {
try {
var url = upstream.match(/^(\w+):\/\/([^:/]+)(?::(\d+))?(\/.*)?$/);
if (url) {
event.Put("error.upstream_protocol", url[1]);
event.Put("error.upstream_host", url[2]);
event.Put("error.upstream_port", url[3] || "80");
event.Put("error.upstream_path", url[4] || "/");
}
} catch(e) {
// Игнорируем ошибки парсинга
}
}
}
ignore_failure: true
# 7. Удаление промежуточных полей
- drop_fields:
when:
equals:
log_type: "angie-error"
fields: [
"error_date", "error_time", "error.error_date", "error.error_time",
"error.datetime", "error.rest", "error.pid_worker",
"error.request", "error.message", "tags"
]
ignore_missing: true
# Общий drop_fields для всех логов
- drop_fields:
fields: ["agent","ecs.version","log.offset","input.type","type","log.flags"]
ignore_missing: true
...

View file

@ -0,0 +1 @@
# Роль для установки и настройке Fluent-bit для отгрузки логов PT AF заказчикам

View file

@ -0,0 +1,6 @@
[SERVICE]
flush 1
storage.path /var/log/flb-storage/
parsers_file parsers.conf
plugins_file plugins.conf
@INCLUDE ptaf-*.conf

View file

@ -0,0 +1,31 @@
[INPUT]
Name syslog
Tag test
Parser syslog-rfc5424
Mode tcp
Listen 172.17.0.1
Port 5141
Buffer_Max_Size 128KB
Buffer_Chunk_Size 15MB
storage.type filesystem
[OUTPUT]
Name syslog
syslog_format rfc5424
Match test
host 10.8.0.24
port 5684
mode tcp
tls on
tls.verify off
tls.debug 1
syslog_severity_preset 6
syslog_facility_preset 16
syslog_hostname_key host
syslog_appname_key ident
syslog_procid_key pid
syslog_msgid_key msgid
syslog_message_key message
storage.total_limit_size 3000MB
Retry_Limit False
syslog_maxsize 51200

View file

@ -0,0 +1,34 @@
---
- name: Check if fluent-bit is installed
ansible.builtin.command: dpkg -l fluent-bit
register: fluent_bit_check
ignore_errors: true
changed_when: false
- name: Fluent-bit install
ansible.builtin.shell: "curl https://raw.githubusercontent.com/fluent/fluent-bit/master/install.sh | sh"
when: fluent_bit_check.rc != 0
- name: Copy fluent-bit conf
ansible.builtin.copy:
src: files/fluent-bit.conf
dest: /etc/fluent-bit/fluent-bit.conf
owner: root
group: root
mode: '0644'
- name: Copy input output conf
ansible.builtin.template:
src: "{{ item }}"
dest: /etc/fluent-bit/
owner: root
group: root
mode: '0644'
loop: "{{ lookup('fileglob', 'files/ptaf-*', wantlist=True) }}"
- name: Ensure fluent-bit service is running and enabled
ansible.builtin.service:
name: fluent-bit
state: restarted
enabled: true
...

View file

@ -0,0 +1,3 @@
---
grafana_gen_bin: "/usr/local/bin/grafana_gen"
...

View file

@ -0,0 +1,34 @@
#!/bin/bash
# Скрипт для комментирования строки в root crontab
# Строка, которую нужно найти (без комментария)
TARGET_LINE="*/10 * * * * /usr/local/bin/grafana_gen"
# Проверяем, запущен ли скрипт от root
if [ "$EUID" -ne 0 ]; then
echo "Ошибка: скрипт должен быть запущен от имени root"
exit 1
fi
# Получаем текущий crontab
CURRENT_CRON=$(crontab -l 2>/dev/null)
# Экранируем специальные символы для использования в regex
ESCAPED_LINE=$(echo "$TARGET_LINE" | sed 's/[.*[\^$]/\\&/g')
# Проверяем, существует ли строка в незакомментированном виде
if echo "$CURRENT_CRON" | grep -qE "^${ESCAPED_LINE}"; then
# Строка найдена и не закомментирована - комментируем её
echo "$CURRENT_CRON" | sed "s|^\(${ESCAPED_LINE}\)|#\1|" | crontab -
echo "Строка закомментирована успешно"
exit 0
elif echo "$CURRENT_CRON" | grep -qE "^#${ESCAPED_LINE}"; then
# Строка уже закомментирована
echo "Строка уже закомментирована, ничего не делаем"
exit 0
else
# Строка не найдена вообще
echo "Предупреждение: строка не найдена в crontab"
exit 2
fi

View file

@ -0,0 +1,34 @@
#!/bin/bash
# Скрипт для раскомментирования строки в root crontab
# Строка, которую нужно найти (без комментария)
TARGET_LINE="*/10 * * * * /usr/local/bin/grafana_gen"
# Проверяем, запущен ли скрипт от root
if [ "$EUID" -ne 0 ]; then
echo "Ошибка: скрипт должен быть запущен от имени root"
exit 1
fi
# Получаем текущий crontab
CURRENT_CRON=$(crontab -l 2>/dev/null)
# Экранируем специальные символы для использования в regex
ESCAPED_LINE=$(echo "$TARGET_LINE" | sed 's/[.*[\^$]/\\&/g')
# Проверяем, существует ли строка в закомментированном виде
if echo "$CURRENT_CRON" | grep -qE "^#${ESCAPED_LINE}"; then
# Строка найдена и закомментирована - раскомментируем её
echo "$CURRENT_CRON" | sed "s|^#\(${ESCAPED_LINE}\)|\1|" | crontab -
echo "Строка раскомментирована успешно"
exit 0
elif echo "$CURRENT_CRON" | grep -qE "^${ESCAPED_LINE}"; then
# Строка уже раскомментирована
echo "Строка уже активна, ничего не делаем"
exit 0
else
# Строка не найдена вообще
echo "Предупреждение: строка не найдена в crontab"
exit 2
fi

View file

@ -0,0 +1,51 @@
---
- name: Copy grafana gen from cloud
ansible.builtin.get_url:
url: ""
dest: "{{ grafana_gen_bin }}"
owner: root
group: root
mode: '0744'
timeout: 60
force: true
retries: 3
delay: 10
register: download_result
- name: Copy grafana gen on and off scripts
ansible.builtin.copy:
src: "{{ item }}"
dest: /usr/local/bin/
owner: root
group: root
mode: '0744'
loop: "{{ lookup('fileglob', 'files/*.sh', wantlist=True) }}"
- name: Create cron task for update
ansible.builtin.cron:
name: "Run grafana_gen every 10 minutes"
minute: "*/10"
hour: "*"
day: "*"
month: "*"
weekday: "*"
job: "{{ grafana_gen_bin }}"
user: root
when: download_result.changed
- name: Create directory /etc/grafana_gen
ansible.builtin.file:
path: /etc/grafana_gen
state: directory
owner: root
group: root
mode: '0755'
- name: Create .env
ansible.builtin.template:
src: grafana_gen.env.j2
dest: /etc/grafana_gen/grafana_gen.env
owner: root
group: root
mode: '0600'
...

View file

@ -0,0 +1,7 @@
DB_USER={{ db_user }}
DB_PASSWORD={{ db_password }}
GIT_TOKEN={{ git_token }}
GRAFANA_API_KEY={{ grafana_api_token }}
GRAFANA_URL=https://metrics.cloudstack.ru
GRAFANA_FOLDER=WAF - PTAF
DASHBOARD_TITLE=PT AF Requests

View file

@ -0,0 +1,4 @@
---
- name: Logrotate run
ansible.builtin.command: "logrotate -f /etc/logrotate.conf"
...

View file

@ -0,0 +1,11 @@
[Unit]
Description=Daily rotation of log files
Documentation=man:logrotate(8) man:logrotate.conf(5)
[Timer]
OnCalendar=*:0/10
AccuracySec=1m
Persistent=true
[Install]
WantedBy=timers.target

View file

@ -0,0 +1,15 @@
/var/log/angie/*.log {
hourly
missingok
rotate 2
compress
maxage 15
notifempty
create 644 angie angie
sharedscripts
postrotate
if [ -f /run/angie.pid ]; then
kill -HUP $(cat /run/angie.pid)
fi
endscript
}

View file

@ -0,0 +1,20 @@
/var/log/ptaf_nginx/*/*/*.log {
daily
maxsize 200M
missingok
rotate 3
maxage 15
compress
notifempty
create 777 root root
su root root
sharedscripts
postrotate
CONTAINER_IDS=$(docker ps --filter "status=running" --filter "name=ptaf*" --format='{{.ID}}')
for CONTAINER_ID in $CONTAINER_IDS; do
docker exec "$CONTAINER_ID" /opt/ptaf/bin/ptaf-nginx -s reopen
done
find /var/log/ptaf_nginx -type f -name "*.log" -size 0 -mmin +60 -delete 2>/dev/null || true
endscript
}

View file

@ -0,0 +1,15 @@
/var/log/solidwall/*.log {
daily
maxsize 200M
missingok
rotate 3
maxage 15
compress
notifempty
create 777 root adm
su root adm
sharedscripts
postrotate
systemctl reload solidwall-nginx > /dev/null 2>&1 || true
endscript
}

View file

@ -0,0 +1,26 @@
/var/log/syslog
/var/log/mail.info
/var/log/mail.warn
/var/log/mail.err
/var/log/mail.log
/var/log/daemon.log
/var/log/kern.log
/var/log/auth.log
/var/log/user.log
/var/log/lpr.log
/var/log/cron.log
/var/log/debug
/var/log/messages
{
rotate 2
daily
maxsize 1G
missingok
notifempty
compress
su root root
sharedscripts
postrotate
/usr/lib/rsyslog/rsyslog-rotate
endscript
}

View file

@ -0,0 +1,64 @@
---
- name: Check logrotate dir
ansible.builtin.file:
path: /etc/logrotate.d/
mode: '0644'
owner: root
group: root
- name: Logrotate angie settings
ansible.builtin.copy:
src: logrotate_angie
dest: /etc/logrotate.d/angie
mode: '0644'
owner: root
group: root
when: inventory_hostname is match("^PTAF")
ignore_errors: "{{ ansible_check_mode }}"
- name: Logrotate syslog settings
ansible.builtin.copy:
src: files/logrotate_syslog
dest: /etc/logrotate.d/rsyslog
mode: '0644'
owner: root
group: root
ignore_errors: "{{ ansible_check_mode }}"
- name: Logrotate PT AF settings
ansible.builtin.copy:
src: files/logrotate_ptaf
dest: /etc/logrotate.d/ptaf
mode: '0644'
owner: root
group: root
when: inventory_hostname is match("^PTAF")
ignore_errors: "{{ ansible_check_mode }}"
- name: Logrotate SolidWall settings
ansible.builtin.copy:
src: files/logrotate_solidwall
dest: /etc/logrotate.d/solidwall
mode: '0644'
owner: root
group: root
when: inventory_hostname is match("^ce-swall")
ignore_errors: "{{ ansible_check_mode }}"
- name: Logrotate timer
ansible.builtin.copy:
src: files/logrotate.timer
dest: /lib/systemd/system/logrotate.timer
mode: '0644'
owner: root
group: root
ignore_errors: "{{ ansible_check_mode }}"
- name: Start and enable logrotate timer
ansible.builtin.systemd:
name: logrotate.timer
enabled: true
state: started
daemon_reload: true
ignore_errors: "{{ ansible_check_mode }}"
...

4
roles/magos/README.md Normal file
View file

@ -0,0 +1,4 @@
# Роль magos
Роль раскатывает на хосты с docker PT AF утилиту magos, которая запускается каждые пять минут по крону и ходит в БД и API ServicePipe и актуализирует данные по upstream, weight и server_name.
Логи выполненных операций пишутся в файл и каждые день отправляются в git. Конфиги Angie и PT AF Nginx также отправляются в git.

View file

@ -0,0 +1,7 @@
---
magos_upload_logs_script_path: "/usr/local/bin/upload_logs.sh"
magos_upload_configs_script_path: "/usr/local/bin/upload_configs.sh"
magos_metrics_script_path: "/usr/local/bin/magos_metrics.sh"
magos_log_dir: "/var/log/update_apps_configs_logs"
magos_log_file: "/var/log/update_apps_configs_logs/update_apps_configs.log"
...

View file

@ -0,0 +1,34 @@
#!/bin/bash
# Скрипт для комментирования строки в root crontab
# Строка, которую нужно найти (без комментария)
TARGET_LINE="*/5 * * * * /usr/local/bin/magos_metrics.sh"
# Проверяем, запущен ли скрипт от root
if [ "$EUID" -ne 0 ]; then
echo "Ошибка: скрипт должен быть запущен от имени root"
exit 1
fi
# Получаем текущий crontab
CURRENT_CRON=$(crontab -l 2>/dev/null)
# Экранируем специальные символы для использования в regex
ESCAPED_LINE=$(echo "$TARGET_LINE" | sed 's/[.*[\^$]/\\&/g')
# Проверяем, существует ли строка в незакомментированном виде
if echo "$CURRENT_CRON" | grep -qE "^${ESCAPED_LINE}"; then
# Строка найдена и не закомментирована - комментируем её
echo "$CURRENT_CRON" | sed "s|^\(${ESCAPED_LINE}\)|#\1|" | crontab -
echo "Строка закомментирована успешно"
exit 0
elif echo "$CURRENT_CRON" | grep -qE "^#${ESCAPED_LINE}"; then
# Строка уже закомментирована
echo "Строка уже закомментирована, ничего не делаем"
exit 0
else
# Строка не найдена вообще
echo "Предупреждение: строка не найдена в crontab"
exit 2
fi

View file

@ -0,0 +1,34 @@
#!/bin/bash
# Скрипт для раскомментирования строки в root crontab
# Строка, которую нужно найти (без комментария)
TARGET_LINE="*/5 * * * * /usr/local/bin/magos_metrics.sh"
# Проверяем, запущен ли скрипт от root
if [ "$EUID" -ne 0 ]; then
echo "Ошибка: скрипт должен быть запущен от имени root"
exit 1
fi
# Получаем текущий crontab
CURRENT_CRON=$(crontab -l 2>/dev/null)
# Экранируем специальные символы для использования в regex
ESCAPED_LINE=$(echo "$TARGET_LINE" | sed 's/[.*[\^$]/\\&/g')
# Проверяем, существует ли строка в закомментированном виде
if echo "$CURRENT_CRON" | grep -qE "^#${ESCAPED_LINE}"; then
# Строка найдена и закомментирована - раскомментируем её
echo "$CURRENT_CRON" | sed "s|^#\(${ESCAPED_LINE}\)|\1|" | crontab -
echo "Строка раскомментирована успешно"
exit 0
elif echo "$CURRENT_CRON" | grep -qE "^${ESCAPED_LINE}"; then
# Строка уже раскомментирована
echo "Строка уже активна, ничего не делаем"
exit 0
else
# Строка не найдена вообще
echo "Предупреждение: строка не найдена в crontab"
exit 2
fi

View file

@ -0,0 +1,90 @@
---
- name: Tuning core
ansible.builtin.include_tasks: tuning_core.yml
- name: Create directoies
ansible.builtin.file:
path: /var/log/{{ item }}
state: directory
owner: root
group: root
mode: '0755'
loop:
- update_apps_configs_logs
- ptaf_configs
- name: DNS conf
ansible.builtin.include_role:
name: dns_settings
- name: Copy certs
ansible.builtin.include_role:
name: certs_settings
- name: Logrotate settings
ansible.builtin.include_role:
name: logrotate_settings
- name: Node_exporter install
ansible.builtin.include_role:
name: node_exporter_install
- name: Copy magos from cloud
ansible.builtin.include_role:
name: update_magos
register: magos_download_result
- name: Copy upload log script to host
ansible.builtin.template:
src: templates/upload_logs.sh.j2
dest: "{{ magos_upload_logs_script_path }}"
owner: root
group: root
mode: '0744'
- name: Copy upload configs script to host
ansible.builtin.template:
src: templates/upload_configs.sh.j2
dest: "{{ magos_upload_configs_script_path }}"
owner: root
group: root
mode: '0744'
- name: Copy magos metrics script to host
ansible.builtin.template:
src: templates/magos_metrics.sh.j2
dest: "{{ magos_metrics_script_path }}"
owner: root
group: root
mode: '0744'
- name: Start soft
ansible.builtin.shell: "{{ magos_metrics_script_path }}"
when: magos_download_result.changed
ignore_errors: true
- name: Create cron task for update
ansible.builtin.cron:
name: "Run update_apps_configs every 5 minutes"
minute: "*/5"
hour: "*"
day: "*"
month: "*"
weekday: "*"
job: "{{ magos_metrics_script_path }}"
user: root
- name: Copy magos on and off scripts
ansible.builtin.copy:
src: "{{ item }}"
dest: /usr/local/bin/
owner: root
group: root
mode: '0744'
loop: "{{ lookup('fileglob', 'files/*.sh', wantlist=True) }}"
- name: Auspex install
ansible.builtin.include_role:
name: auspex
ignore_errors: true
...

View file

@ -0,0 +1,19 @@
---
- name: Backup sysctl
ansible.builtin.copy:
src: /etc/sysctl.conf
dest: /etc/sysctl.conf.backup
remote_src: true
- name: Load conntrack module
ansible.builtin.modprobe:
name: nf_conntrack
state: present
- name: Tuning core
ansible.builtin.sysctl:
name: "{{ item.key }}"
value: "{{ item.value }}"
loop: "{{ sysctl_params | dict2items }}"
notify: Reload sysctl
...

View file

@ -0,0 +1,60 @@
#!/bin/bash
# Конфигурация
METRIC_FILE="/var/lib/node_exporter/textfile_collector/cron_magos.prom"
TIMEOUT_SECONDS=120 # 2 минуты максимум на выполнение
mkdir -p "$(dirname ${METRIC_FILE})"
# Закрываем stdin чтобы скрипты не ждали ввода
exec </dev/null
START=$(date +%s)
STATUS=0
# Функция для безопасного выполнения команды с timeout
safe_run() {
local cmd="$1"
local description="$2"
# Запускаем с timeout и закрытым stdin
timeout ${TIMEOUT_SECONDS} bash -c "${cmd}" </dev/null
local exit_code=$?
if [ ${exit_code} -eq 124 ]; then
echo "[$(date)] ERROR: ${description} timed out after ${TIMEOUT_SECONDS}s" >> /var/log/cron_monitor_errors.log
return 1
elif [ ${exit_code} -ne 0 ]; then
echo "[$(date)] ERROR: ${description} failed with exit code ${exit_code}" >> /var/log/cron_monitor_errors.log
return 1
fi
return 0
}
# Выполняем команды последовательно
if safe_run "{{ magos_bin }} > {{ magos_log_file }} 2>&1" "magos"; then
if safe_run "{{ magos_upload_logs_script_path }}" "upload_logs.sh"; then
if safe_run "{{ magos_upload_configs_script_path }}" "upload_configs.sh"; then
STATUS=1
fi
fi
fi
DURATION=$(($(date +%s) - START))
# Записываем метрики
cat > "${METRIC_FILE}" << EOF
# HELP cron_magos_status Cron job execution status (1=success, 0=failure)
# TYPE cron_magos_status gauge
cron_magos_status ${STATUS}
# HELP cron_magos_last_run Last execution timestamp
# TYPE cron_magos_last_run gauge
cron_magos_last_run $(date +%s)
# HELP cron_magos_duration_seconds Execution duration
# TYPE cron_magos_duration_seconds gauge
cron_magos_duration_seconds ${DURATION}
EOF
exit $((1 - STATUS))

View file

@ -0,0 +1,163 @@
#!/bin/bash
set -euo pipefail
BRANCH=$(hostname)
ANGIE_CONF_DIR="/etc/angie/http.d/"
PTAF_CONF_DIR="/home/install/conf/ptaf-nginx/"
REPO_URL="https://{{ git_token }}@svc-git.cirex.ru/cloudstack/ptaf_config_logs.git"
# Функция для обработки одной директории
process_directory() {
local dir="$1"
local branch_suffix="$2"
local full_branch="${BRANCH}-${branch_suffix}"
echo "$(date '+%Y-%m-%d %H:%M:%S') ======================================"
echo "$(date '+%Y-%m-%d %H:%M:%S') Processing directory: $dir"
echo "$(date '+%Y-%m-%d %H:%M:%S') Branch: $full_branch"
cd "$dir"
# Если это не git-репозиторий — инициализируем новый
if [ ! -d ".git" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') Инициализируем новый локальный git-репозиторий"
git init
git config user.name "ptaf-sync-script"
git config user.email "ptaf-sync-script@$(hostname)"
fi
# Настраиваем пользователя
git config user.name "ptaf-sync-script" 2>/dev/null || true
git config user.email "ptaf-sync-script@$(hostname)" 2>/dev/null || true
# Настраиваем стратегию разрешения конфликтов: всегда предпочитать локальную версию
git config pull.rebase true
git config rebase.autoStash true
# Добавляем/проверяем remote origin
if ! git remote | grep -q '^origin$'; then
echo "$(date '+%Y-%m-%d %H:%M:%S') Добавляем remote origin"
git remote add origin "$REPO_URL"
else
git remote set-url origin "$REPO_URL"
fi
# Очищаем возможные битые ссылки
echo "$(date '+%Y-%m-%d %H:%M:%S') Очищаем старые ссылки"
git remote prune origin 2>/dev/null || true
# КЛЮЧЕВОЕ ИЗМЕНЕНИЕ: Получаем только нашу ветку
echo "$(date '+%Y-%m-%d %H:%M:%S') Проверяем существование ветки '$full_branch' на сервере"
if git ls-remote --heads origin "$full_branch" | grep -q "$full_branch"; then
echo "$(date '+%Y-%m-%d %H:%M:%S') Ветка существует на сервере, получаем её"
git fetch origin "$full_branch:refs/remotes/origin/$full_branch" --force 2>/dev/null || {
echo "$(date '+%Y-%m-%d %H:%M:%S') Ошибка при fetch, очищаем и повторяем"
rm -f .git/refs/remotes/origin/"$full_branch"
git fetch origin "$full_branch:refs/remotes/origin/$full_branch" --force 2>/dev/null || true
}
BRANCH_EXISTS_REMOTE=true
else
echo "$(date '+%Y-%m-%d %H:%M:%S') Ветка '$full_branch' ещё не существует на сервере"
BRANCH_EXISTS_REMOTE=false
fi
# Проверяем, существует ли ветка локально
if ! git show-ref --verify --quiet refs/heads/"$full_branch"; then
echo "$(date '+%Y-%m-%d %H:%M:%S') Создаём локальную ветку '$full_branch'"
if [ "$BRANCH_EXISTS_REMOTE" = true ]; then
# Создаём ветку на основе удалённой
git checkout -b "$full_branch" "origin/$full_branch"
else
# Создаём новую orphan ветку
git checkout --orphan "$full_branch" 2>/dev/null || git checkout -b "$full_branch"
git rm -rf . 2>/dev/null || true
fi
else
git checkout "$full_branch" 2>/dev/null || {
echo "$(date '+%Y-%m-%d %H:%M:%S') Ошибка переключения, принудительно переключаемся"
git checkout -f "$full_branch"
}
fi
# Добавляем все файлы
echo "$(date '+%Y-%m-%d %H:%M:%S') Добавляем все файлы"
git add -A .
# Проверяем наличие изменений и коммитим ИХ СНАЧАЛА
if ! git diff-index --quiet HEAD -- 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then
COMMIT_MSG="Config update $(date '+%Y-%m-%d %H:%M:%S') [host: $(hostname -s)]"
echo "$(date '+%Y-%m-%d %H:%M:%S') Обнаружены локальные изменения — коммитим их ПЕРЕД pull"
git commit -m "$COMMIT_MSG"
fi
# Теперь делаем pull/rebase если ветка существует на сервере
if [ "$BRANCH_EXISTS_REMOTE" = true ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') Синхронизация с удалённой веткой origin/$full_branch"
# Вариант 1: Пробуем обычный rebase
if git pull --rebase origin "$full_branch" 2>/dev/null; then
echo "$(date '+%Y-%m-%d %H:%M:%S') Rebase успешен"
else
echo "$(date '+%Y-%m-%d %H:%M:%S') Конфликт при rebase, разрешаем автоматически"
# Прерываем rebase
git rebase --abort 2>/dev/null || true
# Вариант 2: Используем стратегию merge с предпочтением локальных изменений
echo "$(date '+%Y-%m-%d %H:%M:%S') Используем merge стратегию с предпочтением локальных изменений"
if git pull --no-rebase --strategy=recursive --strategy-option=ours origin "$full_branch" 2>/dev/null; then
echo "$(date '+%Y-%m-%d %H:%M:%S') Merge успешен с предпочтением локальных изменений"
else
echo "$(date '+%Y-%m-%d %H:%M:%S') Merge также не удался, используем force push"
# Вариант 3: Если и merge не сработал - будем пушить с force
# (это безопасно, так как мы уже закоммитили все локальные изменения)
fi
fi
fi
# Создаём keepalive коммит, если изменений больше нет
if git diff-index --quiet HEAD -- 2>/dev/null && git diff --cached --quiet 2>/dev/null; then
COMMIT_MSG="No changes keepalive commit $(date '+%Y-%m-%d %H:%M:%S') [host: $(hostname -s)]"
echo "$(date '+%Y-%m-%d %H:%M:%S') Нет новых изменений — создаём пустой коммит"
git commit --allow-empty -m "$COMMIT_MSG"
fi
# Пушим с fallback вариантами
echo "$(date '+%Y-%m-%d %H:%M:%S') Пушим ветку $full_branch"
if git push origin "$full_branch" --set-upstream 2>/dev/null; then
echo "$(date '+%Y-%m-%d %H:%M:%S') Push успешен"
elif git push origin "$full_branch" --force-with-lease 2>/dev/null; then
echo "$(date '+%Y-%m-%d %H:%M:%S') Push успешен с --force-with-lease"
else
echo "$(date '+%Y-%m-%d %H:%M:%S') Используем force push (локальные изменения приоритетнее)"
git push origin "$full_branch" --force || {
echo "$(date '+%Y-%m-%d %H:%M:%S') ОШИБКА: Не удалось запушить ветку $full_branch"
return 1
}
fi
echo "$(date '+%Y-%m-%d %H:%M:%S') Успешно завершено: $dir"
return 0
}
# Обрабатываем первую директорию
if [ -d "$ANGIE_CONF_DIR" ]; then
process_directory "$ANGIE_CONF_DIR" "angie-conf" || {
echo "$(date '+%Y-%m-%d %H:%M:%S') ОШИБКА при обработке $ANGIE_CONF_DIR"
}
else
echo "$(date '+%Y-%m-%d %H:%M:%S') ПРЕДУПРЕЖДЕНИЕ: Директория $ANGIE_CONF_DIR не найдена"
fi
# Обрабатываем вторую директорию
if [ -d "$PTAF_CONF_DIR" ]; then
process_directory "$PTAF_CONF_DIR" "ptaf-conf" || {
echo "$(date '+%Y-%m-%d %H:%M:%S') ОШИБКА при обработке $PTAF_CONF_DIR"
}
else
echo "$(date '+%Y-%m-%d %H:%M:%S') ПРЕДУПРЕЖДЕНИЕ: Директория $PTAF_CONF_DIR не найдена"
fi
echo "$(date '+%Y-%m-%d %H:%M:%S') ======================================"
echo "$(date '+%Y-%m-%d %H:%M:%S') All backups completed."

View file

@ -0,0 +1,132 @@
#!/bin/bash
set -euo pipefail
BRANCH=$(hostname)-logs
LOGS_DIR="{{ magos_log_dir }}"
REPO_URL="https://{{ git_token }}@svc-git.cirex.ru/cloudstack/ptaf_config_logs.git"
echo "$(date '+%Y-%m-%d %H:%M:%S') Начинаем синхронизацию логов в ветку '$BRANCH'"
cd "$LOGS_DIR"
# Если это не git-репозиторий — инициализируем новый
if [ ! -d ".git" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') Инициализируем новый локальный git-репозиторий"
git init
git config user.name "ptaf-sync-script"
git config user.email "ptaf-sync-script@$(hostname)"
fi
# Настраиваем пользователя
git config user.name "ptaf-sync-script" 2>/dev/null || true
git config user.email "ptaf-sync-script@$(hostname)" 2>/dev/null || true
# Настраиваем стратегию разрешения конфликтов
git config pull.rebase true
git config rebase.autoStash true
# Добавляем/проверяем remote origin
if ! git remote | grep -q '^origin$'; then
echo "$(date '+%Y-%m-%d %H:%M:%S') Добавляем remote origin: $REPO_URL"
git remote add origin "$REPO_URL"
else
# Обновляем URL на случай изменения токена
git remote set-url origin "$REPO_URL"
fi
# Очищаем возможные битые ссылки перед fetch
echo "$(date '+%Y-%m-%d %H:%M:%S') Очищаем старые ссылки"
git remote prune origin 2>/dev/null || true
# КЛЮЧЕВОЕ ИЗМЕНЕНИЕ: Получаем только нашу ветку, а не все ветки
echo "$(date '+%Y-%m-%d %H:%M:%S') Получаем информацию только о ветке '$BRANCH'"
if git ls-remote --heads origin "$BRANCH" | grep -q "$BRANCH"; then
# Ветка существует на сервере - получаем только её
git fetch origin "$BRANCH:refs/remotes/origin/$BRANCH" --force 2>/dev/null || {
echo "$(date '+%Y-%m-%d %H:%M:%S') Ошибка при fetch, пытаемся очистить и повторить"
rm -f .git/refs/remotes/origin/"$BRANCH"
git fetch origin "$BRANCH:refs/remotes/origin/$BRANCH" --force 2>/dev/null || true
}
BRANCH_EXISTS_REMOTE=true
else
echo "$(date '+%Y-%m-%d %H:%M:%S') Ветка '$BRANCH' ещё не существует на сервере"
BRANCH_EXISTS_REMOTE=false
fi
# Проверяем, существует ли ветка локально
if ! git show-ref --verify --quiet refs/heads/"$BRANCH"; then
echo "$(date '+%Y-%m-%d %H:%M:%S') Создаём локальную ветку '$BRANCH'"
if [ "$BRANCH_EXISTS_REMOTE" = true ]; then
# Создаём ветку на основе удалённой
git checkout -b "$BRANCH" "origin/$BRANCH"
else
# Создаём новую ветку (может быть пустой или на основе текущего HEAD)
git checkout --orphan "$BRANCH" 2>/dev/null || git checkout -b "$BRANCH"
# Очищаем индекс для orphan ветки
git rm -rf . 2>/dev/null || true
fi
else
git checkout "$BRANCH" 2>/dev/null || {
echo "$(date '+%Y-%m-%d %H:%M:%S') Ошибка переключения на ветку, принудительно переключаемся"
git checkout -f "$BRANCH"
}
fi
# Добавляем все изменения
echo "$(date '+%Y-%m-%d %H:%M:%S') Добавляем все изменения"
git add -A
# Проверяем, есть ли что коммитить и коммитим СНАЧАЛА
if ! git diff-index --quiet HEAD -- 2>/dev/null; then
COMMIT_MSG="Auto commit $(date '+%Y-%m-%d %H:%M:%S') [host: $(hostname)]"
echo "$(date '+%Y-%m-%d %H:%M:%S') Коммитим локальные изменения ПЕРЕД pull: $COMMIT_MSG"
git commit -m "$COMMIT_MSG"
fi
# Теперь делаем pull/rebase если ветка существует на сервере
if [ "$BRANCH_EXISTS_REMOTE" = true ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') Синхронизация с удалённой веткой origin/$BRANCH"
# Вариант 1: Пробуем обычный rebase
if git pull --rebase origin "$BRANCH" 2>/dev/null; then
echo "$(date '+%Y-%m-%d %H:%M:%S') Rebase успешен"
else
echo "$(date '+%Y-%m-%d %H:%M:%S') Конфликт при rebase, разрешаем автоматически"
# Прерываем rebase
git rebase --abort 2>/dev/null || true
# Вариант 2: Используем стратегию merge с предпочтением локальных изменений
echo "$(date '+%Y-%m-%d %H:%M:%S') Используем merge стратегию с предпочтением локальных изменений"
if git pull --no-rebase --strategy=recursive --strategy-option=ours origin "$BRANCH" 2>/dev/null; then
echo "$(date '+%Y-%m-%d %H:%M:%S') Merge успешен с предпочтением локальных изменений"
else
echo "$(date '+%Y-%m-%d %H:%M:%S') Merge также не удался, используем force push"
# Вариант 3: Если и merge не сработал - будем пушить с force
fi
fi
fi
# Проверяем снова, есть ли что коммитить после синхронизации
if ! git diff-index --quiet HEAD -- 2>/dev/null; then
echo "$(date '+%Y-%m-%d %H:%M:%S') Есть изменения после синхронизации, коммитим"
git commit -m "Post-sync changes $(date '+%Y-%m-%d %H:%M:%S')"
fi
echo "$(date '+%Y-%m-%d %H:%M:%S') Пушим в origin/$BRANCH"
# Пушим с fallback вариантами
if git push origin "$BRANCH" --set-upstream 2>/dev/null; then
echo "$(date '+%Y-%m-%d %H:%M:%S') Push успешен"
elif git push origin "$BRANCH" --force-with-lease 2>/dev/null; then
echo "$(date '+%Y-%m-%d %H:%M:%S') Push успешен с --force-with-lease"
else
echo "$(date '+%Y-%m-%d %H:%M:%S') Используем force push (локальные изменения приоритетнее)"
git push origin "$BRANCH" --force
fi
echo "$(date '+%Y-%m-%d %H:%M:%S') Успешно закоммичено и запушено"
# Запускаем следующий скрипт
{{ magos_upload_configs_script_path }} &
exit 0

View file

@ -0,0 +1,3 @@
# Magos off
Отключение автоматизации настройки WAF

View file

@ -0,0 +1,16 @@
---
- name: Check script
ansible.builtin.stat:
path: /usr/local/bin/magos_off.sh
register: script_stat
- name: Waf automata off
ansible.builtin.command: /usr/local/bin/magos_off.sh
register: result
when: script_stat.stat.exists and script_stat.stat.executable
- name: Show result
ansible.builtin.debug:
var: result.stdout
when: script_stat.stat.exists
...

3
roles/magos_on/README.md Normal file
View file

@ -0,0 +1,3 @@
# Magos on
Включение автоматизации настройки WAF

View file

@ -0,0 +1,16 @@
---
- name: Check script
ansible.builtin.stat:
path: /usr/local/bin/magos_on.sh
register: script_stat
- name: Waf automata on
ansible.builtin.command: /usr/local/bin/magos_on.sh
register: result
when: script_stat.stat.exists and script_stat.stat.executable
- name: Show result
ansible.builtin.debug:
var: result.stdout
when: script_stat.stat.exists
...

3
roles/netplan_update/README.md Executable file
View file

@ -0,0 +1,3 @@
# Роль для обновления Netplan
Автоматически смотрит какие адреса назначены на интерфейсы и согласно этому заполняет конфиг Netplan. Сети до которых нужен доступ прописываются в `defaults/main.yml`.

View file

@ -0,0 +1,5 @@
---
netplan_update_gray_additional_routes:
- 10.11.2.5/32 # Zabbix
- 10.10.3.0/24 # Серверы AD DNS NTP
...

View file

@ -0,0 +1,36 @@
---
- name: Set gray interface (10.100.x.x)
ansible.builtin.set_fact:
interface_1: "{{ item.key }}"
interface_ip_1_with_mask: "{{ item.value.ipv4.address }}/{{ item.value.ipv4.prefix }}"
loop: "{{ ansible_facts | dict2items }}"
when:
- item.value.ipv4 is defined
- item.value.ipv4.address is defined
- item.value.ipv4.address.startswith('10.100')
- name: Set white interface
ansible.builtin.set_fact:
interface_2: "{{ item.key }}"
interface_ip_2_with_mask: "{{ item.value.ipv4.address }}/{{ item.value.ipv4.prefix }}"
loop: "{{ ansible_facts | dict2items }}"
when:
- item.value.ipv4 is defined
- item.value.ipv4.address is defined
- not item.value.ipv4.address.startswith('10.100')
- not item.value.ipv4.address.startswith('127.')
- name: Create backup
ansible.builtin.command: cp /etc/netplan/99-netcfg-vmware.yaml /etc/netplan/99-netcfg-vmware.yaml.bak
- name: Copy new netplan config
ansible.builtin.template:
src: templates/99-netcfg-vmware.yaml.j2
dest: /etc/netplan/99-netcfg-vmware.yaml
owner: root
group: root
mode: '0600'
- name: Apply new netplan
ansible.builtin.command: sudo netplan apply
...

View file

@ -0,0 +1,24 @@
# Changed from Ansible.
network:
version: 2
renderer: networkd
ethernets:
{{ interface_1 }}:
dhcp4: no
dhcp6: no
addresses:
- {{ interface_ip_1_with_mask }}
routes:
{% for route in netplan_update_gray_additional_routes %}
- to: {{ route }}
via: {{ gray_gateway }}
{% endfor %}
{{ interface_2 }}:
dhcp4: no
dhcp6: no
addresses:
- {{ interface_ip_2_with_mask }}
routes:
- to: default
via: {{ white_gateway }}

View file

@ -0,0 +1,9 @@
# Роль для первичной настройки хоста после разворачивания
Данная роль выпоняет следующие действия:
1. Устанавливает `Angie` и логротейт для него;
2. Устанавливает `Zabbix-agent` через `Docker`;
3. Ставит различный дополнительный софт из репозиториев, который может понадобиться;
4. Конфигурирует `Chrony` и `DNS`;
5. Настривает `SSH` и подкидывает ключи;
6. Меняет пароль для пользователя `install`.

View file

View file

@ -0,0 +1,8 @@
---
- name: Change password for install user
ansible.builtin.user:
name: "{{ username_on_new_hosts }}"
password: "{{ install_ssh_pass | password_hash('sha512') }}"
update_password: always
state: present
...

View file

@ -0,0 +1,49 @@
---
- name: DNS conf
ansible.builtin.include_role:
name: dns_settings
- name: Netplan update
ansible.builtin.include_role:
name: netplan_update
- name: Restart connection
ansible.builtin.meta: reset_connection
- name: APT cache update
ansible.builtin.apt:
update_cache: true
ignore_errors: true
- name: Different need soft install
ansible.builtin.include_role:
name: different_need_soft_install
- name: SolidWall repo install
ansible.builtin.include_role:
name: solidwall_repo_add
when: inventory_hostname is match("^ce-swall")
- name: Node exporter install
ansible.builtin.include_role:
name: node_exporter_install
- name: SSH conf
ansible.builtin.include_role:
name: ssh_settings
- name: Logrotate settings
ansible.builtin.include_role:
name: logrotate_settings
- name: Chrony conf
ansible.builtin.include_role:
name: chrony_install
- name: Update firewall
ansible.builtin.include_role:
name: update_firewall
- name: Change password for user
ansible.builtin.include_tasks: change_password_for_user.yml
...

Some files were not shown because too many files have changed in this diff Show more