BASH КАК ЛЕНЬ ПОМОГАЕТ РАБОТЕ
Вообще, у меня на работе есть свой отдельный кабинет, где меня никто не дергает, ахуенный комп с кучей мониторов, всякие игрушки, типа осциллографов и т. п. И тут же у меня под рукой... А скорее, где-то под ногой имеется сервак на базе Supermicro X11SSL-F. Ну, это, конечно, не НОД какой-нибудь Яндексовский, но все равно прикольно. Вот, балуюсь, отрабатываю на нем всякие навыки. Подробней здесь.
В общем, практически рай, особенно в силу того факта, что мне почти не приходится общаться с людьми. Все как-то больше с роботами.
Все бы хорошо, но даже в раю бывают приступы адской лени, особенно когда случаются какие-то однотипные задачи, провалы в памяти или еще какие-нибудь казусы. Зачастую, чтобы облегчить свою и без того простую жизнь, я использую скрипты. Решил покидать тут примерчики, которые можно использовать как основу для доработок нейросетками под разные задачи. Итак, поехали.
СТАТУС ПРОКСМОКС
Прописываем суть сводится к том, чтобы при подключении по ssh система сразу вывела статус всех контейнеров и виртуальных машин, а заодно напомнила команды, чтобы их создать/удалить/войти/выйти, заодно он подсказывает, где себя же и найти.
#!/bin/bash # Функция для проверки статуса сервиса check_service() { if systemctl is-active --quiet "$1"; then echo -e "\e[32m[RUNNING]\e[0m $1" # Зеленый - работает else echo -e "\e[31m[FAILED ]\e[0m $1" # Красный - не работает fi } # Функция для проверки статуса контейнера/ВМ LXC check_lxc() { local status status=$(pct status "$1" 2>/dev/null | awk '{print $2}') if [[ "$status" == "running" ]]; then echo -e "\e[32m[RUNNING]\e[0m LXC-$1 ($2)" else echo -e "\e[31m[STOPPED]\e[0m LXC-$1 ($2)" fi } # Функция для проверки статуса ВМ QEMU check_qemu() { local status status=$(qm status "$1" 2>/dev/null | awk '{print $2}') if [[ "$status" == "running" ]]; then echo -e "\e[32m[RUNNING]\e[0m VM-$1 ($2)" else echo -e "\e[33m[$(echo $status | tr 'a-z' 'A-Z') ]\e[0m VM-$1 ($2)" # Желтый для остановленных fi } echo "==========================================" echo " СИСТЕМНЫЙ ДАШБОРД ПРОКСМОКС-ХОСТА" echo " Скрипт: /usr/local/bin/status_dashboard.sh" echo "==========================================" # Информация о системе echo -e "\n--- \e[1mСистема\e[0m ---" echo "Хост: $(hostname)" echo "Время работы: $(uptime -p | sed 's/up //')" echo "Дата/время: $(date)" echo "IP: $(hostname -I | awk '{print $1}')" # Загрузка системы echo -e "\n--- \e[1mНагрузка\e[0m ---" echo "Загрузка CPU: $(cat /proc/loadavg | awk '{print $1", "$2", "$3}')" echo "Свободно RAM: $(free -h | awk '/^Mem:/ {print $4 " / " $2}')" echo "Диск: $(df -h / | awk 'NR==2 {print $4 " / " $2 " свободно"}')" # Статус ключевых сервисов echo -e "\n--- \e[1mСервисы\e[0m ---" check_service nginx check_service pveproxy check_service pvedaemon check_service pvestatd check_service pve-cluster # Сетевые службы echo -e "\n--- \e[1mСетевые службы\e[0m ---" if netstat -tulpn | grep -q ':80 .*nginx'; then echo -e "\e[32m[LISTEN ]\e[0m nginx на порту 80 (HTTP)" else echo -e "\e[31m[DOWN ]\e[0m nginx на порту 80 (HTTP)" fi if netstat -tulpn | grep -q ':443 .*nginx'; then echo -e "\e[32m[LISTEN ]\e[0m nginx на порту 443 (HTTPS)" else echo -e "\e[31m[DOWN ]\e[0m nginx на порту 443 (HTTPS)" fi if netstat -tulpn | grep -q ':8006 .*pveproxy'; then echo -e "\e[32m[LISTEN ]\e[0m Proxmox VE на порту 8006" else echo -e "\e[31m[DOWN ]\e[0m Proxmox VE на порту 8006" fi # Статус ВСЕХ контейнеров LXC echo -e "\n--- \e[1mВсе LXC Контейнеры\e[0m ---" # Получаем список всех контейнеров с их именами pct list | awk 'NR>1 {print $1, $3}' | while read id name; do if [ -n "$id" ] && [ "$id" != "VMID" ]; then check_lxc "$id" "$name" fi done # Статус ВСЕХ Виртуальных Машин echo -e "\n--- \e[1mВсе Виртуальные Машины (QEMU)\e[0m ---" # Получаем список всех ВМ с их именами qm list | awk 'NR>1 {print $1, $3}' | while read id name; do if [ -n "$id" ] && [ "$id" != "VMID" ]; then check_qemu "$id" "$name" fi done # Полезные команды echo -e "\n--- \e[1mБыстрые команды\e[0m ---" echo "Включить: pct start <ID> | qm start <ID>" echo "Выключить: pct shutdown <ID> | qm shutdown <ID>" echo "Принудительно: pct stop <ID> | qm stop <ID>" echo "Перезагрузить: pct reboot <ID> | qm reset <ID>" echo "Консоль: pct enter <ID> | qm terminal <ID>" echo "" echo "Проверить все: pct list | qm list" echo "Этот дашборд: status" echo "Экстренное восстановление: repair" # Статистика total_containers=$(pct list | awk 'NR>1' | wc -l) running_containers=$(pct list | grep running | wc -l) total_vms=$(qm list | awk 'NR>1' | wc -l) running_vms=$(qm list | grep running | wc -l) echo -e "\n--- \e[1mСтатистика\e[0m ---" echo "Контейнеры: $running_containers/$total_containers запущено" echo "Виртуальные машины: $running_vms/$total_vms запущено" echo "=========================================="
Чтобы добавить в скрипт терминал, пропишем путь к нашему скрипту в ~/.bashrc
nano ~/.bashrc
# ~/.bashrc: executed by bash(1) for non-login shells.
# Note: PS1 is set in /etc/profile, and the default umask is defined
# in /etc/login.defs. You should not need this unless you want different
# defaults for root.
# PS1='${debian_chroot:+($debian_chroot)}\h:\w\$ '
# umask 022
# You may uncomment the following lines if you want `ls' to be colorized:
# export LS_OPTIONS='--color=auto'
# eval "$(dircolors)"
# alias ls='ls $LS_OPTIONS'
# alias ll='ls $LS_OPTIONS -l'
# alias l='ls $LS_OPTIONS -lA'
#
# Some more alias to avoid making mistakes:
# alias rm='rm -i'
# alias cp='cp -i'
# alias mv='mv -i'
. "/root/.acme.sh/acme.sh.env"
# Вывод системного дашборда при SSH-подключении
if [ -n "$SSH_CONNECTION" ]; then
/usr/local/bin/status_dashboard.sh
fi
IPTABLES
Забавно, я заметил юзеры любят поумничать друг перед другом, когда заходит речь об ИТ-безопасности и все время как попугайчики повторяют слово "Файрволл". Почему-то им кажется, что это какая-то типа панацея, которая может спасти их от любых поползновений бабуинов из Нижнего Интернета. Еще они часто путают файрволл и антивирус. На самом деле, файрволл тупо закрывает или открывает какие-то порты и от особо-хитрожопого бабуина это не спасет. Так, например, мы недавно словили шифровальщика, который проник сначала на ПК одного из менеджеров через Ватсап, а уже оттуда на сервер и практически вывел его из строя. Ну и документам всем пизда настала, ясен пень. Тем не менее, файрволы нужны. А если ты не выпил утром кофе, не проснулся, то можешь запросто напутать последовательность команд, закрывающих порты и тогда наверняка... вдруг запляшут облака... И сам запляшешь! В итоге соединение сброится и тебе придется подняться жопу со стула! Что недопустимо... И потом, скорее всего, придется даже сжечь несколько килокалорий, чтобы физически подключать клаву/мышь к серверу. То есть, тебе надо будет дойти до него пешком при помощи ног! Затем, возможно, придется открыть дверь в специальную комнату... Проверить подключения, нажать кучу кнопок... Короче, это натурально геморрой. Хорошо еще, если сервер у тебя под рукой или ногой... А если он где-нибудь за КАД-ом?! Я обычно ленюсь писать иптаблесы руками и поэтому использую скрипты, сгенерированные ИИ, однако ИИ похеру - сможешь ли ты подключиться к своему серверу через ssh после закрытия порта 22 или нет. Поэтому у меня есть шаблон с автоотменой новых правил иптаблес. Любые иптаблесы у меня настраиваются через скрипты и в основе всегда этот шаблон:
#!/bin/bash
# Функция для вывода цветных сообщений
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} Настройка iptables для веб-сервера ${NC}"
echo -e "${BLUE}========================================${NC}"
echo -e "${YELLOW}[*] Запускаем аварийный сброс правил через 120 секунд в фоне...${NC}"
(
sleep 120
echo -e "${RED}[!] ВНИМАНИЕ: прошло 120 секунд. Сбрасываем iptables к разрешающему состоянию!${NC}"
iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT
iptables -F
iptables -X
iptables -t nat -F
echo -e "${RED}[!] Все правила iptables сброшены. Доступ должен быть восстановлен.${NC}"
) &
TIMER_PID=$!
echo -e "${YELLOW}[*] Таймер запущен в фоне (PID: $TIMER_PID)${NC}"
echo -e "${YELLOW}[*] Если всё работает — ОБЯЗАТЕЛЬНО убей таймер:${NC}"
echo -e " ${RED}kill $TIMER_PID${NC}"
echo ""
# ========== СБРОС ПРАВИЛ ==========
echo -e "${YELLOW}[*] Сбрасываем текущие правила iptables...${NC}"
iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT
iptables -F
iptables -X
iptables -t nat -F
echo -e "${GREEN}[+] Все правила сброшены${NC}"
# ========== ЗАГРУЗКА МОДУЛЕЙ (ИСПРАВЛЕННАЯ ВЕРСИЯ) ==========
echo -e "${YELLOW}[*] Инициализация модулей ядра...${NC}"
# Бесшумная загрузка модулей с обработкой ошибок
load_module() {
modprobe "$1" 2>/dev/null
if [ $? -eq 0 ]; then
echo -e "${GREEN}[+] Модуль $1 загружен${NC}"
return 0
else
echo -e "${YELLOW}[!] Модуль $1 не найден (может быть встроен в ядро)${NC}"
return 1
fi
}
# Пробуем загрузить модули для отслеживания соединений
load_module "nf_conntrack"
load_module "nf_conntrack_ipv4"
# Пробуем загрузить модули для FTP (разные варианты для разных ядер)
FTP_MODULES=("nf_conntrack_ftp" "nf_conntrack_ftp4" "xt_conntrack" "ip_conntrack_ftp")
FTP_LOADED=0
for module in "${FTP_MODULES[@]}"; do
if load_module "$module"; then
FTP_LOADED=1
break
fi
done
# Включаем helper для отслеживания FTP (если доступно)
if [ -f /proc/sys/net/netfilter/nf_conntrack_helper ]; then
echo 1 > /proc/sys/net/netfilter/nf_conntrack_helper 2>/dev/null && \
echo -e "${GREEN}[+] Включено отслеживание FTP соединений${NC}" || true
fi
if [ $FTP_LOADED -eq 0 ]; then
echo -e "${YELLOW}[!] Специальные модули для FTP не загружены${NC}"
echo -e "${YELLOW}[!] FTP будет работать, но могут быть проблемы с пассивным режимом${NC}"
fi
echo -e "${GREEN}[+] Инициализация модулей завершена${NC}"
# ========== ОСНОВНЫЕ ПРАВИЛА ==========
# 1. Loopback интерфейс (обязательно)
echo -e "${YELLOW}[*] Настраиваем правила loopback интерфейса...${NC}"
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
echo -e "${GREEN}[+] Loopback настроен${NC}"
# 2. Уже установленные и связанные соединения
echo -e "${YELLOW}[*] Разрешаем установленные соединения...${NC}"
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
echo -e "${GREEN}[+] Правило для установленных соединений добавлено${NC}"
# 3. ICMP (ping) для диагностики
echo -e "${YELLOW}[*] Разрешаем ICMP (ping) запросы...${NC}"
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
echo -e "${GREEN}[+] Ping разрешён${NC}"
# 4. SSH/SFTP - только для управления
echo -e "${YELLOW}[*] Открываем порт SSH/SFTP (22)...${NC}"
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
echo -e "${GREEN}[+] SSH/SFTP (22/tcp) открыт${NC}"
# 5. Веб-сервер (HTTP/HTTPS)
echo -e "${YELLOW}[*] Открываем веб-порты...${NC}"
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
echo -e "${GREEN}[+] Веб-порты открыты: 80/tcp, 443/tcp${NC}"
# 6. FTPS (FTP over SSL/TLS)
echo -e "${YELLOW}[*] Настраиваем правила для FTPS...${NC}"
iptables -A INPUT -p tcp --dport 21 -j ACCEPT # FTP контрольный порт
iptables -A INPUT -p tcp --dport 20 -j ACCEPT # FTP активный режим данных
echo -e "${GREEN}[+] FTPS базовые порты открыты: 20-21/tcp${NC}"
# 7. FTPS пассивный диапазон портов
echo -e "${YELLOW}[*] Открываем пассивный диапазон для FTPS...${NC}"
PASV_MIN=31010
PASV_MAX=31020
for port in $(seq $PASV_MIN $PASV_MAX); do
iptables -A INPUT -p tcp --dport $port -j ACCEPT
done
echo -e "${GREEN}[+] FTPS пассивный диапазон открыт: $PASV_MIN-$PASV_MAX/tcp${NC}"
# 8. Samba (файловый обмен)
echo -e "${YELLOW}[*] Настраиваем правила для Samba...${NC}"
iptables -A INPUT -p tcp --dport 139 -j ACCEPT # NetBIOS session
iptables -A INPUT -p tcp --dport 445 -j ACCEPT # SMB over TCP
iptables -A INPUT -p udp --dport 137 -j ACCEPT # NetBIOS name
iptables -A INPUT -p udp --dport 138 -j ACCEPT # NetBIOS datagram
echo -e "${GREEN}[+] Samba порты открыты: 139,445/tcp и 137-138/udp${NC}"
# 9. Разрешить трафик от локальной сети
echo -e "${YELLOW}[*] Разрешаем трафик от локальной сети...${NC}"
LOCAL_NET="192.168.88.0/24"
iptables -A INPUT -s $LOCAL_NET -j ACCEPT
echo -e "${GREEN}[+] Весь трафик от $LOCAL_NET разрешён${NC}"
# 10. Широковещательный трафик для Samba (опционально)
echo -e "${YELLOW}[*] Настраиваем широковещательный трафик для Samba...${NC}"
BROADCAST_ADDR="192.168.88.255"
iptables -A INPUT -d $BROADCAST_ADDR -p udp --dport 137 -j ACCEPT
iptables -A INPUT -d $BROADCAST_ADDR -p udp --dport 138 -j ACCEPT
echo -e "${GREEN}[+] Широковещательный трафик Samba настроен${NC}"
# ========== ПОЛИТИКИ ПО УМОЛЧАНИЮ ==========
echo -e "${YELLOW}[*] Устанавливаем политики по умолчанию...${NC}"
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
echo -e "${GREEN}[+] Политики установлены: INPUT DROP, FORWARD DROP, OUTPUT ACCEPT${NC}"
# ========== ФИНАЛЬНЫЙ ВЫВОД ==========
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${GREEN}✅ НАСТРОЙКА ЗАВЕРШЕНА УСПЕШНО!${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
echo -e "${GREEN}🔓 ОТКРЫТЫЕ ПОРТЫ:${NC}"
echo " SSH/SFTP : 22/tcp"
echo " Веб-сервер : 80/tcp, 443/tcp"
echo " FTPS : 20-21/tcp + $PASV_MIN-$PASV_MAX/tcp"
echo " Samba : 139,445/tcp, 137-138/udp"
echo " ICMP : ping разрешён"
echo ""
echo -e "${GREEN}🌐 ДОПОЛНИТЕЛЬНЫЕ РАЗРЕШЕНИЯ:${NC}"
echo " • Весь трафик от сети: $LOCAL_NET"
echo " • Широковещание Samba: $BROADCAST_ADDR"
echo " • Установленные соединения"
echo " • Loopback интерфейс"
echo ""
echo -e "${YELLOW}⚠️ ВАЖНЫЕ КОМАНДЫ:${NC}"
echo -e "1. ${RED}Обязательно останови таймер:${NC}"
echo -e " ${RED}kill $TIMER_PID${NC}"
echo ""
echo -e "2. ${YELLOW}Проверка правил:${NC}"
echo " iptables -L -n -v"
echo " iptables -L -n -v | grep -E '(22|80|443|21|139|445)'"
echo ""
echo -e "3. ${YELLOW}Проверка открытых портов:${NC}"
echo " ss -tulpn | grep -E '(22|80|443|21|139|445)'"
echo " netstat -tulpn 2>/dev/null | grep -E '(22|80|443|21|139|445)'"
echo ""
echo -e "4. ${YELLOW}Проверка подключений:${NC}"
echo " # Для SSH"
echo " tail -f /var/log/auth.log | grep sshd"
echo " # Для Samba"
echo " smbstatus"
echo " # Для FTP"
echo " tail -f /var/log/vsftpd.log"
echo ""
echo -e "5. ${YELLOW}Сохранение правил (после проверки):${NC}"
echo " # Для Debian/Proxmox:"
echo " apt-get install iptables-persistent"
echo " iptables-save > /etc/iptables/rules.v4"
echo " ip6tables-save > /etc/iptables/rules.v6"
echo ""
echo -e "${RED}⚠️ ВАЖНО ДЛЯ FTP:${NC}"
echo "Убедитесь, что в /etc/vsftpd.conf указано:"
echo " pasv_enable=YES"
echo " pasv_min_port=$PASV_MIN"
echo " pasv_max_port=$PASV_MAX"
echo " pasv_address=$(hostname -I | awk '{print $1}')"
echo " pasv_promiscuous=YES # если сервер за NAT"
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${GREEN}Скрипт выполнен в: $(date)${NC}"
echo -e "${BLUE}========================================${NC}"
КОМПИЛИРОВАНИЕ ПРОГРАММ
Казалось бы, а тут какие могут быть проблемы? Да еще как могут! Вот это: каждый раз нажимать кнопки, печатать многабукв, делать одно и тоже как робот после каждой добавленной в код программы запятой - да так можно и вспотеть! Или сойти с ума. Поэтому... Создадим скрипт компиляции с уже прописанными путями и добавим команду "comFic" в ~/.bashrc, чтобы было проще вызывать скрипт. Сам скрипт скомпилирует программу скопирует бинарник в /usr/local/bin/ficus32 и сам ее запустит, независимо от терминала, чтобы ты мог продолжить в нем работу! Здорово же, правда?
nano compile_ficus.sh
#!/bin/bash SRC="/webserver/ficus.ard-s.ru/main.cpp" OUT="$HOME/ficus32" BIN="/usr/local/bin/ficus32" echo "===> Останавливаем работающий ficus32..." pkill -f ficus32 || true sleep 0.5 echo "===> Компилируем новую версию..." g++ "$SRC" -std=c++17 -O2 -pthread -o "$OUT" RET=$? if [ $RET -ne 0 ]; then
echo "!!! Ошибка компиляции. Демон не обновлён."
exit 1
fi
echo "===> Копируем бинарник в системный каталог..."
cp "$OUT" "$BIN"
chmod +x "$BIN"
echo "===> Запускаем ficus32 отвязанно от терминала..." nohup "$BIN" > /dev/null 2>&1 & echo "Готово!"
nano ~/.bashrc
# ~/.bashrc: executed by bash(1) for non-login shells.
# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
# for examples
# If not running interactively, don't do anything
[ -z "$PS1" ] && return
# don't put duplicate lines in the history. See bash(1) for more options
# ... or force ignoredups and ignorespace
HISTCONTROL=ignoredups:ignorespace
# append to the history file, don't overwrite it
shopt -s histappend
# for setting history length see HISTSIZE and HISTFILESIZE in bash(1)
HISTSIZE=1000
HISTFILESIZE=2000
# check the window size after each command and, if necessary,
# update the values of LINES and COLUMNS.
shopt -s checkwinsize
# make less more friendly for non-text input files, see lesspipe(1)
[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)"
# set variable identifying the chroot you work in (used in the prompt below)
if [ -z "$debian_chroot" ] && [ -r /etc/debian_chroot ]; then
debian_chroot=$(cat /etc/debian_chroot)
fi
# set a fancy prompt (non-color, unless we know we "want" color)
case "$TERM" in
xterm-color) color_prompt=yes;;
esac
# uncomment for a colored prompt, if the terminal has the capability; turned
# off by default to not distract the user: the focus in a terminal window
# should be on the output of commands, not on the prompt
#force_color_prompt=yes
if [ -n "$force_color_prompt" ]; then
if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
# We have color support; assume it's compliant with Ecma-48
# (ISO/IEC-6429). (Lack of such support is extremely rare, and such
# a case would tend to support setf rather than setaf.)
color_prompt=yes
else
color_prompt=
fi
fi
if [ "$color_prompt" = yes ]; then
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
else
PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
fi
unset color_prompt force_color_prompt
# If this is an xterm set the title to user@host:dir
case "$TERM" in
xterm*|rxvt*)
PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1"
;;
*)
;;
esac
# enable color support of ls and also add handy aliases
if [ -x /usr/bin/dircolors ]; then
test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"
alias ls='ls --color=auto'
#alias dir='dir --color=auto'
#alias vdir='vdir --color=auto'
alias grep='grep --color=auto'
alias fgrep='fgrep --color=auto'
alias egrep='egrep --color=auto'
fi
# some more ls aliases
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'
# Alias definitions.
# You may want to put all your additions into a separate file like
# ~/.bash_aliases, instead of adding them here directly.
# See /usr/share/doc/bash-doc/examples in the bash-doc package.
if [ -f ~/.bash_aliases ]; then
. ~/.bash_aliases
fi
# enable programmable completion features (you don't need to enable
# this, if it's already enabled in /etc/bash.bashrc and /etc/profile
# sources /etc/bash.bashrc).
#if [ -f /etc/bash_completion ] && ! shopt -oq posix; then
# . /etc/bash_completion
#fi
alias comFic="$HOME/compile_ficus.sh"
ДАМП БД MYSQL
Случайно подтер на работе БД сайта... Хорошо, это была предрелизная бета-версия, которую еще даже толком не показывал начальству, но все равно обидно. Просто похерил все заявки, которые делал целых два часа. За то сразу смекнул, что надо делать юнит systemd и скрипт автоматизации создания бэкапов на такие случаи.
nano /usr/local/bin/ard-backup.sh
#!/bin/bash # Скрипт полного дампа БД ard # Использование: systemd service set -euo pipefail # ========== КОНФИГУРАЦИЯ ========== readonly DB_USER="ard" readonly DB_PASS="G7kP9sA2LxQd" readonly DB_NAME="ard" readonly BACKUP_DIR="$HOME/mysql_ard_backup" readonly LOG_DIR="/var/log/ard-backup" readonly RETENTION_DAYS=3 readonly COMPRESS_LEVEL=6 # 1-9, где 9 максимальное сжатие # ========== ФУНКЦИИ ========== log() { local level="$1" local message="$2" local timestamp timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo "[$timestamp] [$level] $message" | tee -a "$LOG_DIR/ard-backup.log" } backup_failed() { log "ERROR" "Бэкап провален: $1" exit 1 } create_backup() { local timestamp timestamp=$(date +%Y%m%d_%H%M%S) local backup_file backup_file="$BACKUP_DIR/${DB_NAME}_${timestamp}.sql.gz" log "INFO" "Начинаем бэкап БД: $DB_NAME" # Создаем дамп БД mysqldump --defaults-extra-file=<(echo -e "[client]\nuser=$DB_USER\npassword=$DB_PASS") \ --skip-column-statistics \ --routines \ --triggers \ --events \ --single-transaction \ --quick \ --hex-blob \ "$DB_NAME" | gzip -${COMPRESS_LEVEL} > "$backup_file" # Проверяем успешность if [[ $? -ne 0 ]]; then backup_failed "Не удалось создать дамп" fi if [[ ! -s "$backup_file" ]]; then backup_failed "Дамп создан, но файл пустой" fi local backup_size backup_size=$(du -h "$backup_file" | cut -f1) local tables_count tables_count=$(mysql --defaults-extra-file=<(echo -e "[client]\nuser=$DB_USER\npassword=$DB_PASS") \ -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '$DB_NAME'" 2>/dev/null || echo "N/A") log "INFO" "Бэкап успешен: $backup_file ($backup_size)" log "INFO" "Таблиц в БД: $tables_count" # Создаем файл с метаинформацией cat > "$BACKUP_DIR/${DB_NAME}_${timestamp}.info" << EOF Database: $DB_NAME Timestamp: $(date '+%Y-%m-%d %H:%M:%S') Backup file: $(basename "$backup_file") Size: $backup_size Tables: $tables_count Compression: gzip level $COMPRESS_LEVEL EOF echo "$backup_file" } clean_old_backups() { log "INFO" "Очистка старых бэкапов (старше $RETENTION_DAYS дней)..." local deleted_count deleted_count=0 # Удаляем старые файлы .sql.gz while IFS= read -r file; do if [[ -f "$file" ]]; then rm -f "$file" deleted_count=$((deleted_count + 1)) log "DEBUG" "Удален: $(basename "$file")" fi done < <(find "$BACKUP_DIR" -name "${DB_NAME}_*.sql.gz" -type f -mtime +$RETENTION_DAYS) # Удаляем старые .info файлы find "$BACKUP_DIR" -name "${DB_NAME}_*.info" -type f -mtime +$RETENTION_DAYS -delete log "INFO" "Удалено старых бэкапов: $deleted_count" } check_disk_space() { local available_space available_space=$(df -h "$BACKUP_DIR" | awk 'NR==2 {print $4}') local backup_dir_size backup_dir_size=$(du -sh "$BACKUP_DIR" 2>/dev/null | cut -f1 || echo "0") log "INFO" "Дисковое пространство: доступно $available_space, размер папки $backup_dir_size" # Если доступно меньше 1GB - предупреждение if [[ $(df "$BACKUP_DIR" | awk 'NR==2 {print $4}') -lt 1048576 ]]; then log "WARNING" "Мало свободного места на диске!" fi } # ========== ОСНОВНОЙ КОД ========== main() { # Создаем директории если нужно mkdir -p "$BACKUP_DIR" mkdir -p "$LOG_DIR" log "INFO" "=== Запуск бэкапа БД ard ===" # Проверяем доступность MySQL if ! mysql --defaults-extra-file=<(echo -e "[client]\nuser=$DB_USER\npassword=$DB_PASS") \ -e "SELECT 1" >/dev/null 2>&1; then backup_failed "Не удалось подключиться к MySQL" fi # Проверяем место на диске check_disk_space # Создаем бэкап local backup_file backup_file=$(create_backup) # Очищаем старые бэкапы clean_old_backups # Итоговая статистика local backup_count backup_count=$(find "$BACKUP_DIR" -name "${DB_NAME}_*.sql.gz" -type f | wc -l) local total_size total_size=$(du -sh "$BACKUP_DIR" | cut -f1) log "INFO" "Итого: $backup_count бэкапов, общий размер: $total_size" log "INFO" "=== Бэкап завершен успешно ===" # Возвращаем путь к созданному файлу для systemd echo "Backup created: $backup_file" } # Запуск main "$@"
nano /etc/systemd/system/ard-backup.service
[Unit] Description=ARD Database Backup Service Documentation=man:mysqldump(1) Wants=ard-backup.timer After=network.target mysql.service [Service] Type=oneshot User=root ExecStart=/usr/local/bin/ard-backup.sh Environment="HOME=/root" # Логирование в journald StandardOutput=journal StandardError=journal # Ограничения для безопасности ProtectSystem=strict ReadWritePaths=/root/mysql_ard_backup /var/log/ard-backup NoNewPrivileges=true PrivateTmp=true [Install] WantedBy=multi-user.target
nano /etc/systemd/system/ard-backup.timer
[Unit] Description=Backup ARD database twice daily Documentation=man:systemd.timer(5) Requires=ard-backup.service [Timer] # 2 раза в день: 2:00 и 13:00 OnCalendar=*-*-* 02:00:00 OnCalendar=*-*-* 13:00:00 # Если сервер был выключен, запустить при включении Persistent=true # Случайная задержка до 10 минут для распределения нагрузки RandomizedDelaySec=600 [Install] WantedBy=timers.target
Скрипт проверки:
nano /usr/local/bin/ard-backup-check.sh
#!/bin/bash # Проверка состояния бэкапов BACKUP_DIR="$HOME/mysql_ard_backup" LOG_DIR="/var/log/ard-backup" echo "=== СОСТОЯНИЕ БЭКАПОВ БД ard ===" echo "Время проверки: $(date)" echo "Директория бэкапов: $BACKUP_DIR" echo "" # Проверяем существование директории if [[ ! -d "$BACKUP_DIR" ]]; then echo "❌ Директория бэкапов не существует!" exit 1 fi # Список бэкапов echo "📦 Доступные бэкапы:" ls -lh "$BACKUP_DIR"/*.sql.gz 2>/dev/null | while read -r line; do echo " $line" done echo "" echo "📊 Статистика:" BACKUP_COUNT=$(find "$BACKUP_DIR" -name "*.sql.gz" -type f 2>/dev/null | wc -l) echo " Всего бэкапов: $BACKUP_COUNT" echo " Размер папки: $(du -sh "$BACKUP_DIR" 2>/dev/null | cut -f1 || echo "0")" echo " Старше 3 дней: $(find "$BACKUP_DIR" -name "*.sql.gz" -mtime +3 -type f 2>/dev/null | wc -l)" echo "" echo "⏰ Последние запуски systemd:" journalctl -u ard-backup.service --since "1 week ago" -n 5 --no-pager | grep -E "(Запуск|успешен|провален)" | tail -5 echo "" echo "💾 Проверка последнего бэкапа:" LAST_BACKUP=$(find "$BACKUP_DIR" -name "*.sql.gz" -type f 2>/dev/null | sort -r | head -1) if [[ -n "$LAST_BACKUP" ]]; then echo " Файл: $(basename "$LAST_BACKUP")" echo " Размер: $(du -h "$LAST_BACKUP" | cut -f1)" echo " Дата: $(stat -c %y "$LAST_BACKUP" 2>/dev/null || echo "N/A")" # Проверка целостности if gzip -t "$LAST_BACKUP" 2>/dev/null; then echo " ✅ Целостность: OK" else echo " ❌ Целостность: поврежден!" fi else echo " ❌ Бэкапов не найдено" fi echo "" echo "=== ПРОВЕРКА ЗАВЕРШЕНА ==="
Установка и настройка:
# 1. Создаем скрипт бэкапа sudo nano /usr/local/bin/ard-backup.sh # Вставить содержимое, сохранить sudo chmod +x /usr/local/bin/ard-backup.sh # 2. Создаем service файл sudo nano /etc/systemd/system/ard-backup.service # Вставить содержимое, сохранить # 3. Создаем timer файл sudo nano /etc/systemd/system/ard-backup.timer # Вставить содержимое, сохранить # 4. Создаем скрипт проверки sudo nano /usr/local/bin/ard-backup-check.sh sudo chmod +x /usr/local/bin/ard-backup-check.sh # 5. Создаем директории mkdir -p ~/mysql_ard_backup sudo mkdir -p /var/log/ard-backup # 6. Перезагружаем systemd и включаем sudo systemctl daemon-reload sudo systemctl enable ard-backup.timer sudo systemctl start ard-backup.timer # 7. Проверяем sudo systemctl status ard-backup.timer sudo systemctl list-timers | grep ard-backup # 8. Запускаем бэкап вручную для теста sudo systemctl start ard-backup.service # 9. Проверяем результат ~/mysql_ard_backup/ /usr/local/bin/ard-backup-check.sh # 10. Смотрим логи sudo journalctl -u ard-backup.service -f
Управление:
# Просмотр статуса таймера sudo systemctl status ard-backup.timer # Просмотр логов службы sudo journalctl -u ard-backup.service # Ручной запуск бэкапа sudo systemctl start ard-backup.service # Остановка таймера sudo systemctl stop ard-backup.timer # Запрет автозапуска sudo systemctl disable ard-backup.timer # Просмотр всех таймеров systemctl list-timers --all
