Найти в Дзене
Ivan-Yurievich

Bash-скрипты для сисадмина: автоматизируй или страдай

Есть такое правило в сисадминстве — если ты делаешь что-то руками второй раз, стоит задуматься. Если третий раз — точно пора писать скрипт. Bash-скрипты пугают новичков. Синтаксис странный, кавычки важны, пробелы важны, ошибки невнятные. Но когда привыкаешь — это один из самых мощных инструментов в арсенале. Скрипт написал один раз, запускаешь сколько угодно, передаёшь коллеге, вешаешь на cron — и забыл. В этой статье — реальные скрипты которые я использую. Не абстрактные примеры из учебника, а то что реально лежит на моих серверах и работает. Прежде чем скрипты — несколько вещей без которых никуда. Шебанг — первая строка любого скрипта: bash #!/bin/bash Говорит системе чем выполнять файл. Всегда первая строка, всегда. Сделать скрипт исполняемым: bash chmod +x myscript.sh
./myscript.sh Запускать с отладкой: bash bash -x myscript.sh Флаг -x выводит каждую команду перед выполнением. Незаменимо когда скрипт делает не то что ожидаешь. Важные настройки в начале скрипта: bash #!/bin/bash
set
Оглавление

Есть такое правило в сисадминстве — если ты делаешь что-то руками второй раз, стоит задуматься. Если третий раз — точно пора писать скрипт.

Bash-скрипты пугают новичков. Синтаксис странный, кавычки важны, пробелы важны, ошибки невнятные. Но когда привыкаешь — это один из самых мощных инструментов в арсенале. Скрипт написал один раз, запускаешь сколько угодно, передаёшь коллеге, вешаешь на cron — и забыл.

В этой статье — реальные скрипты которые я использую. Не абстрактные примеры из учебника, а то что реально лежит на моих серверах и работает.

Основы которые нужно знать

Прежде чем скрипты — несколько вещей без которых никуда.

Шебанг — первая строка любого скрипта:

bash

#!/bin/bash

Говорит системе чем выполнять файл. Всегда первая строка, всегда.

Сделать скрипт исполняемым:

bash

chmod +x myscript.sh
./myscript.sh

Запускать с отладкой:

bash

bash -x myscript.sh

Флаг -x выводит каждую команду перед выполнением. Незаменимо когда скрипт делает не то что ожидаешь.

Важные настройки в начале скрипта:

bash

#!/bin/bash
set -e # остановить скрипт при любой ошибке
set -u # ошибка при использовании неопределённой переменной
set -o pipefail # ошибка если упала любая команда в пайпе

Эти три строки я добавляю в каждый серьёзный скрипт. Без них скрипт может молча продолжать работу после ошибки — и натворить дел.

Переменные, условия, циклы — быстрый курс

Переменные:

bash

NAME="Ivan"
echo "Hello, $NAME"

# Результат команды в переменную
CURRENT_DATE=$(date +%Y-%m-%d)
echo "Today: $CURRENT_DATE"

# Арифметика
COUNT=5
TOTAL=$((COUNT * 2))
echo $TOTAL # 10

Важно: никаких пробелов вокруг = при присваивании. NAME = "Ivan" — ошибка. NAME="Ivan" — правильно.

Условия:

bash

if [ -f "/etc/nginx/nginx.conf" ]; then
echo "Nginx config exists"
else
echo "Config not found"
fi

Частые проверки:

  • -f file — файл существует
  • -d dir — директория существует
  • -z "$VAR" — переменная пустая
  • -n "$VAR" — переменная не пустая
  • $A -eq $B — числа равны
  • "$A" = "$B" — строки равны

Циклы:

bash

# По списку
for server in web1 web2 web3; do
echo "Checking $server..."
ssh $server "uptime"
done

# По файлам
for file in /var/log/*.log; do
echo "Processing: $file"
done

# While
COUNT=0
while [ $COUNT -lt 5 ]; do
echo "Count: $COUNT"
COUNT=$((COUNT + 1))
done

Аргументы скрипта:

bash

#!/bin/bash
# Вызов: ./script.sh myserver 8080

SERVER=$1 # первый аргумент
PORT=$2 # второй аргумент

echo "Connecting to $SERVER:$PORT"

$1, $2 и т.д. — позиционные аргументы. $0 — имя самого скрипта. $# — количество аргументов. $@ — все аргументы.

Скрипт 1 — бэкап с ротацией

Самый используемый скрипт на любом сервере. Делает бэкап директории, архивирует, удаляет старые бэкапы.

bash

#!/bin/bash
set -e

# Настройки
SOURCE_DIR="/var/www/mysite"
BACKUP_DIR="/var/backups/mysite"
DAYS_TO_KEEP=30
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/backup_$DATE.tar.gz"

# Создать директорию если нет
mkdir -p "$BACKUP_DIR"

echo "Starting backup: $SOURCE_DIR"
echo "Destination: $BACKUP_FILE"

# Создать архив
tar -czf "$BACKUP_FILE" -C "$(dirname $SOURCE_DIR)" "$(basename $SOURCE_DIR)"

# Проверить что архив создан
if [ -f "$BACKUP_FILE" ]; then
SIZE=$(du -sh "$BACKUP_FILE" | cut -f1)
echo "Backup created: $BACKUP_FILE ($SIZE)"
else
echo "ERROR: Backup failed!"
exit 1
fi

# Удалить бэкапы старше N дней
echo "Removing backups older than $DAYS_TO_KEEP days..."
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +$DAYS_TO_KEEP -delete

echo "Done! Current backups:"
ls -lh "$BACKUP_DIR"

Вешаем на cron — запускать каждую ночь в 3:00:

bash

crontab -e
# Добавляем:
0 3 * * * /usr/local/bin/backup-mysite.sh >> /var/log/backup-mysite.log
2>&1

Скрипт 2 — мониторинг диска с оповещением

Проверяет заполненность дисков. Если больше порога — шлёт письмо.

bash

#!/bin/bash

THRESHOLD=80
EMAIL="admin@example.com"
HOSTNAME=$(hostname)

# Проверяем все разделы
df -h | grep -vE '^Filesystem|tmpfs|cdrom' | while read line; do
USAGE=$(echo "$line" | awk '{print $5}' | sed 's/%//')
PARTITION=$(echo "$line" | awk '{print $6}')

if [ "$USAGE" -gt "$THRESHOLD" ]; then
MESSAGE="ALERT: $HOSTNAME — disk $PARTITION is ${USAGE}% full"
echo "$MESSAGE"
echo "$MESSAGE" | mail -s "Disk Alert: $HOSTNAME" "$EMAIL"
fi
done

Запускаем каждый час через cron:

bash

0 * * * * /usr/local/bin/check-disk.sh

Скрипт 3 — деплой приложения

Реальный скрипт деплоя. Тянет код из git, устанавливает зависимости, перезапускает сервис.

bash

#!/bin/bash
set -e

APP_DIR="/var/www/myapp"
BRANCH="${1:-main}"
LOG_FILE="/var/log/deploy.log"

log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

log "=== Starting deploy (branch: $BRANCH) ==="

cd "$APP_DIR"

# Сохранить текущий коммит для отката
PREV_COMMIT=$(git rev-parse HEAD)
log "Previous commit: $PREV_COMMIT"

# Получить новый код
log "Pulling from git..."
git fetch origin
git checkout "$BRANCH"
git pull origin "$BRANCH"

NEW_COMMIT=$(git rev-parse HEAD)
log "New commit: $NEW_COMMIT"

# Установить зависимости
log "Installing dependencies..."
npm ci --only=production

# Сборка
log "Building..."
npm run build

# Перезапустить сервис
log "Restarting service..."
systemctl restart myapp

# Проверить что сервис запустился
sleep 3
if systemctl is-active --quiet myapp; then
log "Deploy successful!"
else
log "ERROR: Service failed to start. Rolling back..."
git checkout "$PREV_COMMIT"
npm ci --only=production
npm run build
systemctl restart myapp
log "Rolled back to $PREV_COMMIT"
exit 1
fi

log "=== Deploy complete ==="

Запускать можно вручную или через CI/CD:

bash

./deploy.sh main # деплой main ветки
./deploy.sh feature/x # деплой конкретной ветки

Скрипт 4 — проверка доступности сайтов

Пингует список сайтов, если не отвечает — пишет в лог и отправляет алерт.

bash

#!/bin/bash

SITES=(
"https://example.com"
"https://myapp.ru"
"https://api.myservice.ru/health"
)

ALERT_EMAIL="admin@example.com"
LOG="/var/log/site-check.log"

check_site() {
local URL=$1
local HTTP_CODE

HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
--connect-timeout 10 \
--max-time 30 \
"$URL")

if [ "$HTTP_CODE" -eq 200 ]; then
echo "OK [$HTTP_CODE]: $URL"
return 0
else
echo "FAIL [$HTTP_CODE]: $URL"
return 1
fi
}

echo "=== Site check $(date) ===" >> "$LOG"

for SITE in "${SITES[@]}"; do
if ! check_site "$SITE" >> "$LOG"
2>&1; then
MESSAGE="ALERT: $SITE returned non-200 response"
echo "$MESSAGE" | mail -s "Site Down Alert" "$ALERT_EMAIL"
fi
done

Скрипт 5 — быстрая настройка нового сервера

Когда поднимаю новый VPS — первым делом запускаю этот скрипт. Обновляет систему, ставит нужные пакеты, настраивает базовую безопасность.

bash

#!/bin/bash
set -e

echo "=== Initial server setup ==="

# Обновить систему
apt update && apt upgrade -y

# Установить базовые пакеты
apt install -y \
curl wget git htop tmux \
ufw fail2ban \
nginx certbot python3-certbot-nginx \
docker.io docker-compose

# Настроить файрвол
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw allow http
ufw allow https
ufw --force enable

# Включить fail2ban
systemctl enable fail2ban
systemctl start fail2ban

# Добавить текущего пользователя в группу docker
usermod -aG docker $USER

echo "=== Setup complete! ==="
echo "Reboot recommended: sudo reboot"

Полезные однострочники

Иногда не нужен целый скрипт — достаточно однострочника.

Запустить команду на нескольких серверах:

bash

for s in web1 web2 web3; do ssh $s "systemctl status nginx"; done

Найти и заменить строку во всех файлах:

bash

find /etc/nginx -name "*.conf" -exec sed -i 's/old.domain.ru/new.domain.ru/g' {} \;

Архивировать и сразу передать на другой сервер:

bash

tar -czf - /var/www/mysite | ssh user@backup-server "cat > /backups/mysite.tar.gz"

Выполнить скрипт на удалённом сервере без копирования:

bash

ssh user@server 'bash -s' < local-script.sh

Мониторить появление строки в логе:

bash

tail -f /var/log/nginx/access.log | grep --line-buffered "500"

Где хранить скрипты

Системные скрипты кладу в /usr/local/bin/ — они доступны из любого места без указания пути. Права:

bash

chmod +x /usr/local/bin/myscript.sh

Все скрипты держу в git-репозитории. Называю его server-scripts или dotfiles. Когда поднимаю новый сервер — клонирую репозиторий и все мои скрипты уже на месте.

Итог

Bash-скрипты — это не высшая математика. Это инструмент который сэкономит тебе часы рутинной работы если начать его использовать.

Начни с малого: следующий раз когда поймаешь себя на повторяющемся действии — открой редактор и напиши скрипт. Даже если он получится кривой и с костылями — он будет работать. А со временем придёт чистота и элегантность.

Автоматизируй или страдай — выбор очевиден.