Поиск забытых .ssh директорий
После чистки пользователей в системе нередко остаются их .ssh-каталоги. Это мусор + потенциальная дыра: старые ключи могут лежать годами.
▪️ Быстрый поиск по системе
Найдет все .ssh, включая:
нестандартные каталоги
▪️ Проверяем, существует ли владелец
Если владелец:
UNKNOWN
или пользователь отсутствует в
то каталог подозрительный.
▪️ Поиск .ssh без пользователя
⚠️ Перед удалением лучше сделать архив
BashTex📱 #bash #utils
После чистки пользователей в системе нередко остаются их .ssh-каталоги. Это мусор + потенциальная дыра: старые ключи могут лежать годами.
find / -type d -name .ssh 2>/dev/null
Найдет все .ssh, включая:
/home/*/.ssh
/root/.sshнестандартные каталоги
find / -type d -name .ssh -exec stat -c '%U %n' {} \;
Если владелец:
UNKNOWN
или пользователь отсутствует в
/etc/passwdто каталог подозрительный.
while read user path; do
id "$user" &>/dev/null || echo "Лишний: $path"
done < <(find / -type d -name .ssh -exec stat -c '%U %n' {} \;)
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Циклы while vs for - где что правильно
В bash оба цикла нужны, но для разных задач. Путаница между ними - источник проблем.
▪️ for - перебор готового списка. Используй, когда список уже есть.
файлы;
аргументы "$@";
элементы массива.
⚠️ Пример ошибок:
▪️ while - поток данных. Идеален для чтения ввода.
строки с пробелами;
вывод команд;
большие файлы.
⚠️ Частая ошибка:
(цикл в subshell!)
BashTex📱 #bash
В bash оба цикла нужны, но для разных задач. Путаница между ними - источник проблем.
for f in *.log; do
echo "$f"
done
файлы;
аргументы "$@";
элементы массива.
for f in $(ls *.log); do # ломается на пробелах
while IFS= read -r line; do
echo "$line"
done < file.txt
строки с пробелами;
вывод команд;
большие файлы.
cat file | while read line; do
count=$((count+1)) # переменная пропадёт
done
(цикл в subshell!)
for не является универсальным, а while безопаснее для данных. Перед созданием цикла нужно задавать себе вопрос: список или поток.BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Контроль свободных inode на дисках
Можно иметь 50% свободного места и при этом… не создать ни одного файла. Причина: закончились inode.
▪️ Проверка inode на дисках
▪️ Когда это становится проблемой
миллионы мелких файлов (cache, maildir, tmp)
Docker overlay
Типичная ошибка:
…при пустом диске.
▪️ Поиск пожирателей inode
Или по каталогам:
▪️ Что делать
чистить cache и tmp
пересоздать FS с большим inode ratio (если хронически)
BashTex📱 #bash
Можно иметь 50% свободного места и при этом… не создать ни одного файла. Причина: закончились inode.
df -i
Ключевые колонки:Inodes- всегоIUsed- использованоIFree- свободноIUse%- процент
миллионы мелких файлов (cache, maildir, tmp)
Docker overlay
/var/spool, /tmp, /var/libТипичная ошибка:
No space left on device
…при пустом диске.
find /var -xdev -type f | wc -l
Или по каталогам:
for d in /var/*; do
echo "$(find "$d" -type f | wc -l) $d"
done
чистить cache и tmp
пересоздать FS с большим inode ratio (если хронически)
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🔥2
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5😁1🗿1
Контроль роста каталогов во времени
Проблема знакомая: диск внезапно кончается, а кто именно его съел - непонятно. Решение простое: снимать размеры каталогов и сравнивать во времени.
1️⃣ Делаем снимок. Сохраняем размеры верхнеуровневых директорий:
сортировка сразу по размеру
2️⃣ Сравниваем со вчера. Если есть предыдущий снимок:
Или удобнее, показать только рост:
На выходе список каталогов, которые реально выросли, с дельтой.
▪️ Зачем это нужно
быстро найти runaway-логи;
поймать тихий рост кешей;
понять, кто ест диск, а не гадать
BashTex📱 #bash #utils
Проблема знакомая: диск внезапно кончается, а кто именно его съел - непонятно. Решение простое: снимать размеры каталогов и сравнивать во времени.
du -x --max-depth=1 /var 2>/dev/null | sort -n > /tmp/var.size.today
-x - не уходим на другие FS--max-depth=1 - только первый уровеньсортировка сразу по размеру
diff -u /tmp/var.size.yesterday /tmp/var.size.today
Или удобнее, показать только рост:
join -1 2 -2 2 \
<(sort -k2 /tmp/var.size.yesterday) \
<(sort -k2 /tmp/var.size.today) \
| awk '{delta=$3-$1; if (delta>0) printf "%+dK %s\n", delta, $2}' \
| sort -n
На выходе список каталогов, которые реально выросли, с дельтой.
быстро найти runaway-логи;
поймать тихий рост кешей;
понять, кто ест диск, а не гадать
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
Работа с временными метками файлов
В linux у каждого файла есть не только размер, но и время. И на этом можно строить полезную автоматику.
▪️ Какие метки бывают
⚠️ ctime - не дата создания
▪️ Получаем время в виде числа. Для сравнений удобнее epoch:
Текущее время:
▪️ Пример: файл старше N минут
▪️ Сравнение двух файлов
Полезно для: автопересборки конфигов или выбора актуального бэкапа
▪️ Практика: очистка старых файлов
BashTex📱 #bash #utils
В linux у каждого файла есть не только размер, но и время. И на этом можно строить полезную автоматику.
stat file.txt
Главные поля:
Modify (mtime) - когда менялось содержимое
Access (atime) - когда читали
Change (ctime) - менялись права / владелец
stat -c %Y file.txt # mtime в секундах
Текущее время:
now=$(date +%s)
mtime=$(stat -c %Y file.log)
(( now - mtime > 600 )) && echo "Файл старше 10 минут"
f1=$(stat -c %Y a.conf)
f2=$(stat -c %Y b.conf)
(( f1 > f2 )) && echo "a.conf новее"
Полезно для: автопересборки конфигов или выбора актуального бэкапа
for f in /tmp/*; do
(( now - $(stat -c %Y "$f") > 86400 )) && rm -f "$f"
done
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
Автоперезапуск сервиса только если он упал больше N раз
Иногда сервис падает один раз - это нормально. Но если он падает 10 раз за минуту, бесконечный Restart=always только усугубляет проблему.
Задача: перезапускать сервис только если он упал больше N раз за период.
1️⃣ Считаем падения через systemd. У systemd есть счетчик рестартов:
Вывод:
2️⃣ Простая логика в bash
▪️ Учитываем период времени
Счетчик накапливается с момента запуска systemd.
Чтобы учитывать последний час, можно смотреть journal:
Или искать Main process exited.
BashTex📱 #bash #utils
Иногда сервис падает один раз - это нормально. Но если он падает 10 раз за минуту, бесконечный Restart=always только усугубляет проблему.
Задача: перезапускать сервис только если он упал больше N раз за период.
systemctl show nginx -p NRestarts
Вывод:
NRestarts=3
SERVICE=nginx
LIMIT=5
restarts=$(systemctl show "$SERVICE" -p NRestarts --value)
if (( restarts >= LIMIT )); then
echo "Сервис падал $restarts раз — перезапуск"
systemctl restart "$SERVICE"
else
echo "Падений мало ($restarts) — не трогаем"
fi
Счетчик накапливается с момента запуска systemd.
Чтобы учитывать последний час, можно смотреть journal:
journalctl -u nginx --since "1 hour ago" | grep -c "Started"
Или искать Main process exited.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
Please open Telegram to view this post
VIEW IN TELEGRAM
😁8
Маунт есть, а доступа нет
Представим ситуацию, mount показывает, что файловая система подключена, но при заходе в каталог: Permission denied.
1️⃣ Проверяем права на каталог
Важно:
владелец
группа
права (rwx)
Даже если диск смонтирован, права на точку монтирования остаются критичны.
Исправление:
2️⃣ Проверяем UID/GID (часто с NFS). На NFS или при миграции: пользователь есть, но UID отличается
Проверка:
Если UID не совпадает, то и доступ будет запрещен.
Решение:
синхронизировать UID
или использовать anonuid/anongid (для NFS)
3️⃣ Смотрим опции монтирования
Частые виновники:
ro - read-only
noexec
nosuid
nodev
Перемонтировать:
4️⃣ SELinux (тихий убийца доступа). Если права нормальные, но доступ запрещен:
Проверка контекста:
Исправление:
5️⃣ Root_squash (NFS-специфика). На NFS root может стать nobody. Проверка экспорта:
Если включен
6️⃣ ACL могут перекрывать права
ACL могут запретить доступ даже при chmod 777.
BashTex📱 #bash
Представим ситуацию, mount показывает, что файловая система подключена, но при заходе в каталог: Permission denied.
ls -ld /mnt/data
Важно:
владелец
группа
права (rwx)
Даже если диск смонтирован, права на точку монтирования остаются критичны.
Исправление:
chown user:group /mnt/data
chmod 755 /mnt/data
Проверка:
id user
ls -ln /mnt/data
Если UID не совпадает, то и доступ будет запрещен.
Решение:
синхронизировать UID
или использовать anonuid/anongid (для NFS)
mount | grep /mnt/data
Частые виновники:
ro - read-only
noexec
nosuid
nodev
Перемонтировать:
mount -o remount,rw /mnt/data
getenforce
Проверка контекста:
ls -Z /mnt/data
Исправление:
restorecon -Rv /mnt/data
exportfs -v
Если включен
root_squash, root не всесилен.
getfacl /mnt/data
ACL могут запретить доступ даже при chmod 777.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9🔥1
Поиск сервисов без логов
Сервис запущен, порт слушает, CPU есть, а в логах тишина и это может быть опасно т.к: нет аудита, нет диагностики, нельзя понять, что происходит. Будем пробовать искать их.
1️⃣ Проверяем, куда вообще пишет сервис
Смотрим:
Loaded
Active
путь к unit-файлу
Далее:
Если:
StandardOutput=null
StandardError=null
это означает, что логирование отключено.
2️⃣ Проверяем journal
Если пусто, а сервис активен:
3️⃣ Проверяем, есть ли вообще лог-файлы. Иногда сервис пишет в файл, а не в journal:
Или:
4️⃣ Проверяем stdout/stderr процесса
Если дескрипторы 1 и 2 указывают в /dev/null - сервис глухой.
5️⃣ Смотрим конфиг самого приложения. Часто в конфиге:
6️⃣ Мини-детектор тихих сервисов. Проверяем активные сервисы без событий в journal за час:
BashTex📱 #bash #utils
Сервис запущен, порт слушает, CPU есть, а в логах тишина и это может быть опасно т.к: нет аудита, нет диагностики, нельзя понять, что происходит. Будем пробовать искать их.
systemctl status myservice
Смотрим:
Loaded
Active
путь к unit-файлу
Далее:
systemctl show myservice -p StandardOutput -p StandardError
Если:
StandardOutput=null
StandardError=null
это означает, что логирование отключено.
journalctl -u myservice --since "1 hour ago"
Если пусто, а сервис активен:
systemctl is-active myservice
lsof -p $(pgrep -x myservice) | grep log
Или:
ls -l /var/log | grep myservice
ls -l /proc/$(pgrep -x myservice)/fd
Если дескрипторы 1 и 2 указывают в /dev/null - сервис глухой.
log_level = none
logging = false
daemon = true
for s in $(systemctl list-units --type=service --state=running --no-legend | awk '{print $1}'); do
count=$(journalctl -u "$s" --since "1 hour ago" | wc -l)
(( count == 0 )) && echo "Тихий сервис: $s"
done
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9
Please open Telegram to view this post
VIEW IN TELEGRAM
😁5
Почему сервис не стартует?
1️⃣ Быстрая проверка статуса
2️⃣ Смотрим реальные ошибки из journal
3️⃣ Проверяем unit-файл
Проверка синтаксиса:
4️⃣ Типовые причины, которые встречаются чаще всего
🤩 Неверный путь к бинарнику:
Файл не существует и сервис не стартует.
🤩 Порт уже занят
🤩 Неправильные права
Нужно проверить владельца и execute-бит.
🤩 Ошибка в конфиге приложения. Попробуй запустить вручную:
🛠 Небольшой скрипт диагностики
Запуск:
BashTex📱 #bash #utils
systemctl status myservice -n 20 --no-pager
Смотрим:
Loaded
Active
ExecStart
последние строки логов
journalctl -u myservice -xe --no-pager
Частые сообщения:
Permission denied
No such file or directory
Address already in use
Failed to bind
Unit entered failed state
systemctl cat myservice
Ошибки часто здесь:
неправильный путь в ExecStart
забыли daemon-reload
лишний User=
отсутствующая рабочая директория
Проверка синтаксиса:
systemd-analyze verify /etc/systemd/system/myservice.service
ExecStart=/usr/bin/appФайл не существует и сервис не стартует.
ss -lntp | grep 8080
ls -l /path/to/app
Нужно проверить владельца и execute-бит.
sudo -u serviceuser /usr/bin/app
#!/bin/bash
SERVICE="$1"
echo "=== STATUS ==="
systemctl status "$SERVICE" -n 10 --no-pager
echo
echo "=== LAST ERRORS ==="
journalctl -u "$SERVICE" -n 20 --no-pager
echo
echo "=== UNIT FILE ==="
systemctl cat "$SERVICE"
echo
echo "=== PORT CHECK ==="
ss -lntp | grep "$(systemctl show "$SERVICE" -p ExecStart --value | awk '{print $1}')" 2>/dev/null
Запуск:
./diag.sh nginx
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10
Чеклист для ситуации, когда сервер под нагрузкой
Представим ситуацию, сервер начинает тормозить, мониторинг краснеет, а пользователи уже бегут к телефону или генерируют инциденты. На этот случай и написан чеклист, что проверять в первую очередь.
1️⃣ CPU: кто жрет процессор
Если load > количества ядер - это означает, что проблема есть.
Детализация:
Если высокий wa, то проблема в IO.
2️⃣ IO: не уперлись ли в диск
Если %util ≈ 100% - диск забит.
Быстрое упрощение:
Кто активно пишет/читает.
3️⃣ Память: нет ли OOM
И сразу:
Если есть OOM Killer, то причина найдена.
4️⃣ Сеть: не уперлись ли в соединения
И:
Кто льет трафик.
5️⃣ Процессы: нет ли runaway
6️⃣ Быстрый общий снимок
Будет полезно для фиксации состояния.
BashTex📱 #bash #utils
Представим ситуацию, сервер начинает тормозить, мониторинг краснеет, а пользователи уже бегут к телефону или генерируют инциденты. На этот случай и написан чеклист, что проверять в первую очередь.
uptime
Смотрим:
Load Average
сравниваем с количеством CPU (nproc)
Если load > количества ядер - это означает, что проблема есть.
Детализация:
top
Смотрим:
%CPU
us / sy / wa
процессы в топе
Если высокий wa, то проблема в IO.
iostat -xz 1 3
Смотрим:
%util
await
svctm
Если %util ≈ 100% - диск забит.
Быстрое упрощение:
iotop
Кто активно пишет/читает.
free -h
Проверяем:
свободная память
swap
И сразу:
dmesg | grep -i oom
Если есть OOM Killer, то причина найдена.
ss -s
Смотрим:
ESTAB
TIME-WAIT
orphaned sockets
И:
iftop
Кто льет трафик.
ps aux --sort=-%cpu | head
ps aux --sort=-%mem | head
Смотрим:
неожиданные процессы
бесконечные воркеры
top -b -n1 | head -20
Будет полезно для фиксации состояния.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9🔥4
Управление выводом
В консоли все выглядит одинаково. Но для системы это два разных потока:
stdout - нормальный вывод (FD 1)
stderr - ошибки (FD 2)
И если их не различать - автоматизация начинает ломаться.
▪️ Что такое stdout и stderr. Пример:
Первое уйдет в stdout, второе в stderr.
Проверим:
Теперь:
out.log - нормальный результат
err.log - ошибки
⚠️ Почему это важно
1️⃣ CI/CD пайплайны. В системах вроде GitHub Actions или GitLab CI:
stdout - это лог выполнения
stderr - это ошибка шага
Если приложение пишет все в stdout, то и пайплайн может считать шаг успешным.
2️⃣ Парсинг вывода в скриптах. Типичная ошибка:
Если some_command пишет ошибку в stdout - переменная будет содержать текст ошибки.
Автоматизация съест ее как валидные данные.
3️⃣ Логирование в systemd. В systemd:
stdout - это StandardOutput
stderr - это StandardError
Если сервис пишет ошибки в stdout, мониторинг может их не выделить.
🤩 Типичный анти-паттерн
Если curl вернет HTTP 500, сообщение об ошибке может уйти в stdout.
🤩 Правильно:
▪️ Управление потоками.
Раздельная запись
Объединить
Игнорировать ошибки
Поменять местами
BashTex📱 #bash #utils
В консоли все выглядит одинаково. Но для системы это два разных потока:
stdout - нормальный вывод (FD 1)
stderr - ошибки (FD 2)
И если их не различать - автоматизация начинает ломаться.
echo "OK"
echo "ERROR" >&2
Первое уйдет в stdout, второе в stderr.
Проверим:
./script.sh >out.log 2>err.log
Теперь:
out.log - нормальный результат
err.log - ошибки
stdout - это лог выполнения
stderr - это ошибка шага
Если приложение пишет все в stdout, то и пайплайн может считать шаг успешным.
result=$(some_command)
Если some_command пишет ошибку в stdout - переменная будет содержать текст ошибки.
Автоматизация съест ее как валидные данные.
stdout - это StandardOutput
stderr - это StandardError
Если сервис пишет ошибки в stdout, мониторинг может их не выделить.
if curl bashtex.com; then
echo "OK"
fi
Если curl вернет HTTP 500, сообщение об ошибке может уйти в stdout.
if curl -fsS bashtex.com; then
echo "OK"
else
echo "FAIL" >&2
fi
Раздельная запись
command >out.log 2>err.log
Объединить
command >all.log 2>&1
Игнорировать ошибки
command 2>/dev/null
Поменять местами
command 3>&1 1>&2 2>&3
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
Please open Telegram to view this post
VIEW IN TELEGRAM
😁13🔥1
Сравнение конфигураций сервиса между серверами
Классическая ситуация: на одном сервере все работает. На другом - нет. При этом версии одинаковые, ОС одинаковая, но потом спустя время выясняется: конфиг отличается на одну строку.
1️⃣ Самый простой способ - diff через SSH
Но это неудобно и мусорно.
Лучше сразу:
Чисто. Без временных файлов.
2️⃣ Игнорировать комментарии и пустые строки. Очень часто различия только в комментариях.
Теперь сравнивается только реальная логика.
3️⃣ Если это nginx - сравнивать весь effective config. У nginx часто include-файлы.
Правильнее:
Так мы сравниваем итоговую конфигурацию.
4️⃣ Проверка sshd. У ssh есть удобная команда:
Она выводит итоговую конфигурацию с учетом defaults. Сравниваем так же через process substitution.
5️⃣ Если серверов много. Массовая проверка:
Сортировка помогает убрать разницу порядка строк.
BashTex📱 #bash #utils
Классическая ситуация: на одном сервере все работает. На другом - нет. При этом версии одинаковые, ОС одинаковая, но потом спустя время выясняется: конфиг отличается на одну строку.
😁 Задача
Сравнить конфигурации одного сервиса между серверами:
nginx.conf
sshd_config
app.conf
Быстро. Чисто. Без копипасты глазами.
ssh server1 "cat /etc/nginx/nginx.conf" > s1.conf
ssh server2 "cat /etc/nginx/nginx.conf" > s2.conf
diff -u s1.conf s2.conf
Но это неудобно и мусорно.
Лучше сразу:
diff -u \
<(ssh server1 "cat /etc/nginx/nginx.conf") \
<(ssh server2 "cat /etc/nginx/nginx.conf")
Чисто. Без временных файлов.
diff -u \
<(ssh s1 "grep -vE '^\s*#|^\s*$' /etc/ssh/sshd_config") \
<(ssh s2 "grep -vE '^\s*#|^\s*$' /etc/ssh/sshd_config")
Теперь сравнивается только реальная логика.
Правильнее:
ssh s1 "nginx -T" > s1.nginx
ssh s2 "nginx -T" > s2.nginx
diff -u s1.nginx s2.nginx
Так мы сравниваем итоговую конфигурацию.
sshd -T
Она выводит итоговую конфигурацию с учетом defaults. Сравниваем так же через process substitution.
for host in s1 s2 s3; do
ssh "$host" "sshd -T" | sort > "$host.conf"
done
diff -u s1.conf s2.conf
Сортировка помогает убрать разницу порядка строк.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Отладка bash-скриптов
В bash есть встроенная трассировка, и она может быть эффективнее любого printf-дебага.
1️⃣ Включаем трассировку
Теперь каждая команда будет выводиться перед выполнением:
Отключить:
Полезно включать только на проблемном участке.
2️⃣ Отладка всего скрипта. Можно запустить сразу так:
Или добавить в начало:
3️⃣ Сделать трассировку читаемой. По умолчанию вывод начинается с +. Это скучно. Можно добавить больше контекста:
Теперь вывод будет выглядеть так:
Здесь видно: время, файл и номер строки Для сложных скриптов - это крайне полезно.
4️⃣ Отдельный лог отладки. Чтобы не засорять stdout:
Теперь трассировка уйдет в
5️⃣ Отладка переменных. Полезные режимы:
Продвинутый вариант:
BashTex📱 #bash #utils
В bash есть встроенная трассировка, и она может быть эффективнее любого printf-дебага.
set -x
Теперь каждая команда будет выводиться перед выполнением:
+ var=10
+ echo 10
Отключить:
set +x
Полезно включать только на проблемном участке.
bash -x script.sh
Или добавить в начало:
#!/bin/bash
set -x
export PS4='+ $(date "+%H:%M:%S") ${BASH_SOURCE}:${LINENO}: '
Теперь вывод будет выглядеть так:
+ 14:32:10 script.sh:12: var=10
Здесь видно: время, файл и номер строки Для сложных скриптов - это крайне полезно.
exec 3>/tmp/debug.log
BASH_XTRACEFD=3
set -x
Теперь трассировка уйдет в
/tmp/debug.log, а основной вывод останется чистым. Это особенно важно для автоматизации и CI.
set -u # ошибка при использовании unset переменной
set -e # выход при ошибке
set -o pipefail # ловим ошибки в пайпах
Продвинутый вариант:
set -euxo pipefail
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🔥3
Генерация временных файлов
Практически каждый, кому необходимы были временные файлы писал так:
И вроде все ок, но на деле это один из самых старых и самых недооцененных багов в bash.
▪️ Почему /tmp/file.$RANDOM - это плохая идея
1️⃣ Коллизии.
2️⃣ Race condition. Ты:
А злоумышленник заранее создал симлинк с таким именем и твой скрипт перезапишет чужой файл. Это классическая symlink attack.
3️⃣ Неправильные права. Файл создается с текущим umask. Это может быть не то, что ты ожидал.
▪️ Правильный способ - mktemp
Результат:
▪️ Создание временной директории
▪️ Правильная очистка. Всегда следует добавлять cleanup:
Теперь файл удалится даже при ошибке или Ctrl+C.
▪️ Когда нужно имя с префиксом
Или:
BashTex📱 #bash #utils
Практически каждый, кому необходимы были временные файлы писал так:
TMP="/tmp/script.$RANDOM"
И вроде все ок, но на деле это один из самых старых и самых недооцененных багов в bash.
$RANDOM - это число от 0 до 32767. При параллельном запуске скриптов совпадения реальны.
echo "data" > /tmp/file.$RANDOM
А злоумышленник заранее создал симлинк с таким именем и твой скрипт перезапишет чужой файл. Это классическая symlink attack.
TMP=$(mktemp)
Результат:
/tmp/tmp.ABCd93kL
Что делает mktemp:
генерирует криптографически безопасное имя
создаёт файл атомарно
выставляет права 600
исключает race condition
TMP_DIR=$(mktemp -d)
TMP=$(mktemp)
cleanup() {
rm -f "$TMP"
}
trap cleanup EXIT
Теперь файл удалится даже при ошибке или Ctrl+C.
mktemp /tmp/myscript.XXXXXX
Или:
mktemp -d /tmp/myscript.XXXXXX
XXXXXX обязательно - это шаблон.BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
Почему sleep - это костыль?
Очень часто используют такую конструкцию:
Или ещё хуже: в цикле с фиксированной паузой.
Это неуправляемый retry, который: создает лишнюю нагрузку, не учитывает тип ошибки, не масштабируется и может устроить небольшой DDoS при массовом падении сервиса.
Правильный подход - retry с exponential backoff.
▪️ Почему sleep 5 - это плохая стратегия.
Представим: API лежит 2 минуты. 100 серверов начинают делать: запрос -> sleep 5 -> запрос -> sleep 5 → …
Ты получаешь синхронную атаку на уже мертвый сервис.
Exponential backoff делает паузы все длиннее: 1s -> 2s -> 4s -> 8s -> 16s -> ...
Это: снижает нагрузку, дает сервису восстановиться, уменьшает каскадные падения
🛠 Базовая retry-обертка
▪️ Использование:
▪️ Что здесь происходит
выходной код управляет retry;
задержка удваивается;
максимум попыток ограничен.
▪️ Когда retry вреден
при логических ошибках (401, 403);
если операция не идемпотентна;
если сервис возвращает fatal error.
Retry должен применяться к временным ошибкам, а не ко всем подряд.
BashTex📱 #bash #utils
Очень часто используют такую конструкцию:
curl https://api.bashtex.com || sleep 5 && curl https://api.bashtex.com
Или ещё хуже: в цикле с фиксированной паузой.
Это неуправляемый retry, который: создает лишнюю нагрузку, не учитывает тип ошибки, не масштабируется и может устроить небольшой DDoS при массовом падении сервиса.
Правильный подход - retry с exponential backoff.
Представим: API лежит 2 минуты. 100 серверов начинают делать: запрос -> sleep 5 -> запрос -> sleep 5 → …
Ты получаешь синхронную атаку на уже мертвый сервис.
Exponential backoff делает паузы все длиннее: 1s -> 2s -> 4s -> 8s -> 16s -> ...
Это: снижает нагрузку, дает сервису восстановиться, уменьшает каскадные падения
retry() {
local max_attempts=$1
shift
local attempt=1
local delay=1
while true; do
"$@" && return 0
if (( attempt >= max_attempts )); then
echo "Команда не удалась после $attempt попыток"
return 1
fi
echo "Попытка $attempt неудачна. Повтор через $delay сек."
sleep "$delay"
attempt=$(( attempt + 1 ))
delay=$(( delay * 2 ))
done
}
retry 5 curl -fsS https://api.bashtex.com
"$@" - передаем любую команду;выходной код управляет retry;
задержка удваивается;
максимум попыток ограничен.
при логических ошибках (401, 403);
если операция не идемпотентна;
если сервис возвращает fatal error.
Retry должен применяться к временным ошибкам, а не ко всем подряд.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Профилирование bash-скрипта
Большинство bash-скриптов медленные не потому что bash плохой, а потому что внутри: лишние grep | awk | sed, десятки subshell, сетевые вызовы без таймаутов и циклы по 100k строк
И пока не измерил - не оптимизируешь.
1️⃣ Базовый уровень - time. Самое простое:
Вывод:
Если real > user + sys - это значит, что скрипт ждет (сеть, диск, sleep).
Если user большой - это значит много вычислений.
2️⃣ Профилирование построчно через DEBUG trap. Настоящая магия начинается здесь.
Но это просто трассировка. Чтобы измерять время между строками:
Теперь перед каждой командой будет вывод:
И сразу видно: где реальная задержка, какой вызов тормозит и сколько стоит subshell
⚠️ Важно
На больших скриптах лог будет огромный.
▪️ Мини-профилировщик функции. Если нужно измерить только конкретный блок:
Использование:
BashTex📱 #bash #utils
Большинство bash-скриптов медленные не потому что bash плохой, а потому что внутри: лишние grep | awk | sed, десятки subshell, сетевые вызовы без таймаутов и циклы по 100k строк
И пока не измерил - не оптимизируешь.
time ./script.sh
Вывод:
real 0m3.421s
user 0m0.212s
sys 0m0.084s
Что важно:real- общее время (включая ожидание сети, IO)user- CPU в user spacesys- системные вызовы
Если real > user + sys - это значит, что скрипт ждет (сеть, диск, sleep).
Если user большой - это значит много вычислений.
#!/usr/bin/env bash
PS4='+ $(date "+%s.%N") '
exec 3>&2
BASH_XTRACEFD=3
set -x
Но это просто трассировка. Чтобы измерять время между строками:
#!/usr/bin/env bash
last_time=$(date +%s%N)
trap '
now=$(date +%s%N)
diff=$(( (now - last_time) / 1000000 ))
echo "${diff} ms → $BASH_COMMAND"
last_time=$now
' DEBUG
Теперь перед каждой командой будет вывод:
2 ms → var=$(cat file)
153 ms → curl https://api
1 ms → echo done
И сразу видно: где реальная задержка, какой вызов тормозит и сколько стоит subshell
trap DEBUG вызывается перед каждой командой, включая: внутренние, подстановки и циклы.На больших скриптах лог будет огромный.
measure() {
local start=$(date +%s%N)
"$@"
local end=$(date +%s%N)
echo "Функция заняла $(( (end - start)/1000000 )) ms"
}
Использование:
measure heavy_function
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
Автоматический failover при недоступности mount-точки
Неприятная ситуация когда mount вроде есть, но по факту хранилище уже умерло. NFS отвалился. iSCSI завис. Ceph не отвечает. Сервис продолжает писать… в никуда.
Решение - автоматическая логика: Проверка -> попытка remount -> переключение на fallback.
▪️ Логика failover
Проверяем, что mount существует;
Проверяем доступность записи;
Если ошибка, то пробуем remount;
Если не помогло - переключаемся на fallback.
🛠 Пример скрипта
▪️ Что здесь важно
1.
2.
3.
▪️ Как запускать правильно
Лучше всего: systemd timer или healthcheck внутри сервиса или отдельный watchdog unit
⚠️ Нюансы
Если сервис пишет активно, то нужно ставить его на паузу перед failover;
fallback должен быть заранее подготовлен;
важно логировать переключения;
нужен механизм возврата обратно.
BashTex📱 #bash #utils
Неприятная ситуация когда mount вроде есть, но по факту хранилище уже умерло. NFS отвалился. iSCSI завис. Ceph не отвечает. Сервис продолжает писать… в никуда.
Решение - автоматическая логика: Проверка -> попытка remount -> переключение на fallback.
Проверяем, что mount существует;
Проверяем доступность записи;
Если ошибка, то пробуем remount;
Если не помогло - переключаемся на fallback.
#!/usr/bin/env bash
MOUNT_POINT="/data"
FALLBACK="/data_local"
TEST_FILE="$MOUNT_POINT/.healthcheck"
log() {
echo "$(date '+%F %T') | $1"
}
check_mount() {
mountpoint -q "$MOUNT_POINT" || return 1
timeout 3 touch "$TEST_FILE" 2>/dev/null || return 1
rm -f "$TEST_FILE"
}
remount() {
log "Попытка remount..."
mount -o remount "$MOUNT_POINT"
}
switch_to_fallback() {
log "Переключение на fallback $FALLBACK"
umount -l "$MOUNT_POINT"
mount --bind "$FALLBACK" "$MOUNT_POINT"
}
main() {
if check_mount; then
log "Mount работает нормально"
exit 0
fi
log "Mount недоступен"
remount && sleep 2
if check_mount; then
log "Remount помог"
exit 0
fi
switch_to_fallback
}
main
1.
timeout - если NFS завис, без timeout скрипт повиснет навсегда.2.
mountpoint -q - Проверяет именно mount, а не просто директорию.3.
umount -l - lazy umount - полезно, если есть залипшие процессы.Лучше всего: systemd timer или healthcheck внутри сервиса или отдельный watchdog unit
Если сервис пишет активно, то нужно ставить его на паузу перед failover;
fallback должен быть заранее подготовлен;
важно логировать переключения;
нужен механизм возврата обратно.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8