Bash Days | Linux | DevOps
23.3K subscribers
152 photos
25 videos
664 links
Авторский канал от действующего девопса

Самобытно про разработку, devops, linux, скрипты, сисадминство, техдирство и за айтишную жизу.

Автор: Роман Шубин
Реклама: @maxgrue

MAX: https://max.ru/bashdays

Курс: @tormozilla_bot
Блог: https://bashdays.ru
Download Telegram
Всем привет. Опять решил поделиться опытом своих провалов.

🔤🔤🔥🔤🔤🔤🔤

Вобщем, как-то раз я решил написать самоучитель по bash.

По традиции все учебники либо в html, либо в pdf. Я решил остановиться на pdf. Я начал писать в libreoffice, там есть экспорт в pdf. Пока писал - познакомился с markdown. И вдруг подумалось, markdown - такой классный, напрямик можно в гитхаб запихнуть. И в pdf конвертнуть с помощью pandoc можно. Вобщем, решил верстать в markdown.

Для начала познакомился с редакторами, которые умеют подсвечивать markdown (pluma, mousepad, geany) Вобщем, жить можно, но подсвечивается не всё.

После этого попробовал Apostrophe. Он уже умеет дополнительно показывать результат. Но работает медленно. Особенно на больших документах.

В общем - перепопробовал, - не нравится. Решил писать в mousepad, а потом конвертить с помощью pandoc.

Написал скрипт для удобства пользования - меняется дата файла - автоматом резервная копия версии и конвертация в фоне.

Начал работать и тут вскрылась куча недостатков markdown;

1. Нет нормального форматирования (заголовок нельзя центрировать)
2. Нет нормальных таблиц, один ущерб какой-то.
3. Проблема с кирилицей (решаемая), но с помощью дополнительного tex-файла настроек.
\usepackage{longtable}\setlength{\LTleft}{2em}
\setmainfont{Liberation Serif}
\setsansfont{Liberation Sans}
\setmonofont{Liberation Mono}
\newfontfamily\cyrillicfont{Liberation Sans}
\defaultfontfeatures{Scale=MatchLowercase, Mapping=tex-text}
\usepackage{polyglossia}
\setmainlanguage{russian}
\setotherlanguage{english}

4. Нет возможности печатать некоторые символы UTF.
5. некоторые символы приходится маскировать, чтобы они не понимались, как управляющие.

Единственный плюс - подсветка синтаксиса bash.

В общем, после 19 главы я понял, что моего терпения не хватит закончить работу. Вернулся к libreoffice. За два дня освоил работу со стилями, и все пошло как по маслу. Кстати, экспорт в pdf тоже быстрее, на мой взгляд.

Может и не все получилось, сами знаете - первый блин комом.

Замечания и комментарии приветствуются.

С результатом можно ознакомиться здесь 👇

🅰️🅰️
➡️ https://github.com/tagd-tagd/self-instruction
🅰️🅰️

🛠 #bash #linux #utilites #markdown

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
395
Сегодня будем убивать неугодные сервисы. Нет, тут будет не про kill и т.п. а будет все тот же systemd.

Короче в юните можно указать параметр RuntimeMaxSec, в нем задаём время жизни сервиса. Если сервис работает дольше указанного времени, то ему песда. Systemd принудительно завершит его.

Удобно применять для сервисов, которые не должны жить вечно. Нет ничего вечного! Например, временные задачи, вспомогательные демоны, скрипты и т.п.

[Service]
RuntimeMaxSec=30s


0s, 5min, 1h, 2d — интервалы
infinity — отключение лимита

А что будет если время вышло?

Как и написал выше — будет песда! Systemd пошлет SIGTERM. А если сервис сука живучий и не завершился, то в ход пойдет тяжелая артиллерия через TimeoutStopSec, тут уже будет послан SIGKILL. Но его нужно предварительно прописать.

TimeoutStopSec= — это время ожидания корректного завершения сервиса после того, как systemd послал ему сигнал SIGTERM.

[Service]
ExecStart=/usr/bin/python3 /opt/scripts/bashdays-task.py
RuntimeMaxSec=60


Этот сервис будет убит через 60 секунд после запуска — даже если скрипт ещё не завершился.

[Service]
ExecStart=/usr/bin/bashdays-daemon
RuntimeMaxSec=60
TimeoutStopSec=10


Если bashdays-daemon работает дольше 60 секунд → SIGTERM
Ждём до 10 секунд → если не завершился → SIGKILL

Частый паттерн

RuntimeMaxSec=300
TimeoutStopSec=5
Restart=on-failure


Сервис работает максимум 5 минут, и если завис — systemd подождёт 5 секунд на аккуратное завершение, а потом прибьёт его нахуй.

А нахуя тут Restart=on-failure?

Оно говорит — «если сервис завершился аварийно — перезапусти его» А завершение по SIGKILL из-за превышения времени жизни это и есть failure.

Если не указать TimeoutStopSec, будет использоваться значение по умолчанию: 90 секунд — порой это дохуя, поэтому предпочтительнее задать его руками.

Важный нюанс!

Если в юните используется Restart=always, то после убийства, сервис будет перезапущен и возможно сразу «помрёт» если не изменит своё поведение.

Такие дела, изучай!

🛠 #linux #tricks #debug #systemd #bash

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
661
reexec VS reload

Порой народ путает команды systemctl daemon-reload и systemctl daemon-reexec.

С виду вроде похожие, но нет. Спросил тут на досуге товарища — а ты знаешь чем они отличаются?

Да, ответил товариш, reexec это старая версия перечитывания сервисов и юнитов. Я обычно делаю так:

systemctl daemon-reexec
systemctl daemon-reload
systemctl enable node_exporter
systemctl start node_exporter


Неее… так не нужно! Это хуйня! По крайней мере первая команда тебе тут не нужна для перезапуска и обновления сервисов.

Команда systemctl daemon-reexec перезапускает сам systemd, это нужно например при обновлении бинарников systemd, но никак не для перезапуска юнитов и сервисов.

После редактирования *.service / *.timer / *.mount файлов, достаточно сделать daemon-reload, эта команда перечитает unit-файлы.

Обычно проходится по каталогам:

/etc/systemd/system/
/lib/systemd/system/
/usr/lib/systemd/system/
/run/systemd/system/


То есть она перезагружает только конфигурацию юнитов, без перезапуска сервисов.

Так что не путай, в большинстве случаев тебе достаточно daemon-reload.

🛠 #linux #tricks #debug #systemd

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
870
Имена файлов удаляются с помощью системных вызовов unlink, unlinkat.

Ну дак вот. Когда счетчик ссылок (имен) становится равен нулю — файл удаляется.

Физическое удаление (блоки маркируются свободными) происходит когда больше никто не удерживает файл. То есть процесс закрыл последний дескриптор ассоциированный с файлом.

Давай попробуем поймать (открыть файл) до того, как запустится вызов unlink.

Создаем искусственный временный файл:

printf '%s\n' {a..z} > /tmp/sh-thd-bashdays


Создаем и запускаем скрипт:

#!/bin/bash

file=/tmp/sh-thd-bashdays
exec 3< "$file"
rm -vf "$file" # Имени уже нет
read -N 4096 -ru 3 # Но мы читаем
printf '%s' $REPLY # Выводим
exec 3>&-
exit


Что тут происходит?

exec 3< - файл открывается на чтение, привязывается к дескриптору 3 и с этого момента файл начинает жить в «памяти» в open file table ядра, даже если мы его ёбнем удалим. Дескриптор 3 теперь указывает на открытый файл, независимо от имени.

rm -vf - файл удаляется из файловой системы, имени больше нет, НО, его содержимое еще доступно, потому что он открыт.

read -N 4096 - читаем до 4096 байт из открытого файла через дескриптор 3.

exec 3>& - закрываем дескриптор 3 и после этого файл окончательно исчезнет, если его больше никто не держит, а ядро высвобождает ресурсы.

Итоги:

Эта фишка демонстрирует, что можно удалить файл, но продолжить его использовать если он был открыт на момент удаления.

Это классическая Unix-модель: имя файла — не сам файл, а просто ссылка.

Если держишь дескриптор — файл всё ещё существует.


А тут как-то рассказывал, как восстанавливать удаленные файлы, в том числе и запущенные.


Чтиво почитать:

man 2 unlink
man 2 unlinkat

🛠 #linux #bash

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
38
CRON в первый рабочий день месяца

Да, ключевой момент тут именно в «рабочий день».

Задачка вроде не тривиальная, но как оказалось cron нихуя не умеет определять рабочий сегодня день или нет. И systemd timer тоже не умеет. Никто ничо блядь не умеет.

Мы с тобой не пальцем деланы, изобретем свой велосипед!

Чтобы реализовать желаемое, я изобрел такую кишку:

0 8 1-3 * * [ "$(date +\%u)" -lt 6 ] && [ "$(curl -s https://isdayoff.ru/$(date +\%Y\%m\%d))" = "0" ] && /usr/local/sbin/bashdays.sh


Выглядит монструозно, но блядь... Можно в сам скрипт проверку забить, но это еще один костыль.

Что тут происходит?

Короче…

0 8 1-3 * * — Запуск 1–3 числа месяца в 08:00. Потому что первый рабочий день месяца может выпасть на 1-е число, если это будний день. 2-е число, если 1-е — выходной. 3-е число, если 1-е и 2-е — выходные (например, сб + вс).

[ "$(date +%u)" -lt 6 ] — Проверяем, что сегодня будний день (1–5 = Пн–Пт). Пусть тебя здесь 6ка не смущает. Она означает: день меньше 6, т.е. 1, 2, 3, 4, 5 — это будни (Пн – Пт).

curl ... = "0" — Проверка, что сегодня рабочий день по календарю РФ, выполняется через АПИху производственного календаря.

&& /opt/scripts/first-workday.sh — Выполняем только если сошлись оба условия.

То есть АПИха возвращает 0 если сегодня будни. Если выходной, то оно вернет что-то другое.

Тут еще бы для curl и date указать что-то вроде CURL=$(which curl), а то крон частенько залупается и не знает откуда запускать эти команды.

Всегда старайся указывать полные пути до программ и утилит. Это распространенный кейс, крон вроде отрабатывает, но ничего не работает. А оно просто не знает откуда запускать, то что ты ему прописал.

Да, есть зависимость от внешнего API и порой это пиздец хуёва. Отвалилась АПИха, скрипт не получил данные. Поэтому рекомендую сделать себе собственный микросервис с АПИхой и ни от кого не зависеть. Тем более если сервер в оффлайне, запрос на внешнюю АПИху он сделать не сможет.


Если знаешь еще какие-то способы, велком в комментарии.

UPD: более корректные алгоритмы решения этой задачи смотрим в комментах, всем спасибо!

🛠 #bash #linux

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
660
Слыхал про with-lock-ex, flock, lckdo?

Если коротко, это штуки, которые не дают запустить второй экземпляр скрипта/программы, если первый ещё работает.

Всё это и многое другое входит в пакет moreutils, в убунтах из коробки я их не нашел, пришлось ставить. Но это детали. В этот пакет входит еще дюжина полезных хуёвин.

Например (нажми на спойлер):

sudo apt install moreutils

chronic — показывает вывод команды, только если она завершилась с ошибкой.

combine — логические операции над двумя файлами (как `sort` + `comm`, но проще).

errno — выводит описание кода ошибки (EACCES 13 Permission denied).

ifdata — dыводит информацию о сетевых интерфейсах в скриптабельном виде.

ifne — выполняет команду только если предыдущая команда что-то вывела (if-not-empty).

isutf8 — проверяет, является ли файл валидным UTF-8.

и так далее…


Вернемся к lckdo (Lock and Do). Это обёртка вокруг flock. Как и написал выше, оно не позволяет запуститься второму экземпляру программы/скрипты.

Допустим у тебя что-то запустилось по крону, не успело завершиться и через промежуток снова запускается. Наплодилось 100500 процессов, сервак встал раком, а потом и тебя поставили раком.

lckdo — cтавит блокировку на файл, если блокировка прошла — выполняет указанную команду, если не удалось — завершает выполнение (по умолчанию).

Давай на практике!

В первом терминале запускаем:

lckdo /tmp/bashdays.lock bash -c 'echo start; sleep 60; echo done'


Во втором терминале запускаем:

lckdo /tmp/bashdays.lock echo "Hello bashdays"


И получаем: lckdo: lockfile /tmp/bashdays.lock' is already locked

По умолчанию lckdo ничего не ждет, если нужно, чтобы он ждал до освобождения блокировки, добавляем -w.

lckdo -w /tmp/bashdays.lock echo "I'm free"


Теперь когда файл /tmp/bashdays.lock будет отпущен, отработает команда и выведет на экран I'm free.

Как lckdo работает с lock-файлом.

1. Файл /tmp/bashdays.lock сам по себе ничего не блокирует.

2. Блокировку держит процесс, который открыл файл и вызвал flock/lckdo на него.

3. Когда процесс завершается — блокировка автоматически снимается.

Даже если файл /tmp/bashdays.lock остаётся на диске — он больше не заблокирован, и lckdo снова сможет его использовать.

Всё логично, вот еще одни пример с кроном:

* * * * * lckdo /tmp/bashdays.lock /usr/local/bin/bashdays.sh


Скрипт не будет запущен, если предыдущий запуск не завершился.

Такие дела. Бери на вооружение, возможно где-то в хозяйстве сгодится.

🛠 #linux

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
55
Сопроцессы. Практика. Часть Вторая.

🔤🔤🔥🔤🔤🔤🔤

coproc хорошо подходит для общения в клиент-серверном режиме. Для примера попробуем подключиться к POP3 серверу с шифрованием ssl прямо из bash-скрипта.

Сам ssl несколько сложноват для bash, поэтому в качестве посредника будем использовать openssl s_client.

Протокол и команды POP3 лучше посмотреть на википедии.

1. Cоздим сопроцесс. Для этого запустим openssl в режиме s_client. При этом из дескриптора POP3_CONN[0] можно читать данные от сопроцесса.

В дескриптор POP3_CONN[1] можно писать для сопроцесса.

При записи используем перенаправление >&${POP3_CONN[1] . При чтении тоже можно использовать перенаправление, но поскольку у команды read есть ключ -u красивее воспользоваться им.

2. Аутентифицируемся

3. Закроем сессию и дескрипторы.

# Функция для отправки команд серверу
function SEND_CMD() {
sleep 0.3
echo "$@" >&${POP3_CONN[1]}
sleep 0.3
}

# аутентификация. Обычный логин
function POP3_LOGIN() {
declare REC
declare -a AREC
# проверка соединения
read -ert 2 -u ${POP3_CONN[0]} REC
read -ra AREC <<<${REC//$'\r'/}
if [[ "${AREC[0]}" == "+OK" ]];then
# Отправляем логин
SEND_CMD "USER $USER"
read -ert 2 -u ${POP3_CONN[0]} REC
read -ra AREC <<<${REC//$'\r'/}
if [[ "${AREC[0]}" == "+OK" ]];then
# Отправляем пароль
SEND_CMD "PASS $PASS"
read -ert 2 -u "${POP3_CONN[0]}" REC
read -ra AREC <<<${REC//$'\r'/}
if [[ "${AREC[0]}" == "+OK" ]];then
return 0 # аутентификация успешна
else
return 3 # не правильный пароль
fi
else
return 2 #не правильный login
fi
else
return 1 # ошибка соединения с сервером
fi
}

#Выход и закрытие дескрипторов.
function POP3_QUIT(){
SEND_CMD "QUIT"
# Закрываем coproc
exec ${POP3_CONN[0]}<&-
exec ${POP3_CONN[1]}>&-
}


Задержки 0.3 секунды при отправке нужны для того, чтобы сервер успел сформировать ответ.

Ошибки -ERR не обрабатывал. В случае чего команда read завершится по таймауту в 2 сек. (-t 2)

${REC//$'\r'/} конструкция удаляет cr, потому что POP3 сервер отвечает c lfcr.


#!/bin/bash

SERVER="server"
PORT=995
USER="user@server"
PASS="StrongPass"

# создаем сопроцесс и соединяемся с сервером pop3
coproc POP3_CONN { openssl s_client -connect "${SERVER}:${PORT}" -quiet 2>/dev/null;}
POP3_LOGIN
POP3_QUIT


help coproc
help read
man openssl
вики POP3

🛠 #bash #linux

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
221
Сопроцессы. Практика. Часть Третья.

🔤🔤🔥🔤🔤🔤🔤

Это уже больше не сопроцессы, а про то, как принять почту в скрипте bash.

Соединение с POP3 сервером есть. Аутентификация тоже. Осталось написать что-нибудь полезное.

# возвращает число писем в ящике
function POP3_STAT(){
declare -a AREC
declare REC
SEND_CMD "STAT"
read -ert 2 -u ${POP3_CONN[0]} REC
read -ra AREC <<<${REC//$'\r'/}
if [[ ${AREC[0]} == "+OK" ]];then
echo ${AREC[1]} # число сообщений
return 0
else
echo 0
return 1
fi
}
#Помечает к удалению указанное письмо
function POP3_DELE(){
declare -i MSG_NUM=${1:-1} # по умолчанию первое
declare -a AREC
declare REC
SEND_CMD "DELE $MSG_NUM" #удаляем указанное сообщение
read -ert 2 -u ${POP3_CONN[0]} REC
read -ra AREC <<<${REC//$'\r'/}
if [[ ${AREC[0]} == "+OK" ]];then
return 0
else
return 1
fi
}
# читает письмо с заголовками
function POP3_RETR(){
declare -i MSG_NUM=${1:-1} # по умолчанию первое
declare -a AREC
declare REC
SEND_CMD "RETR $MSG_NUM" #читаем указанное сообщение
read -ert 2 -u ${POP3_CONN[0]} REC
read -ra AREC <<<${REC//$'\r'/}
if [[ ${AREC[0]} == "+OK" ]];then
while read -r -t 2 -u ${POP3_CONN[0]} REC ; do
REC=${REC//$'\r'/}
echo "$REC"
if [[ "$REC" == "." ]];then
return 0 # msg end
fi
done
else
return 1
fi
}
# читает указанное число строк письма
function POP3_TOP(){
declare -i MSG_NUM=${1:-1} # по умолчанию первое
declare -i STR_NUM=${2:-1} # по умолчанию одна строка
declare -a AREC
declare REC
#читаем указанное сообщение
SEND_CMD "TOP $MSG_NUM $STR_NUM"
read -ert 2 -u ${POP3_CONN[0]} REC
read -ra AREC <<<${REC//$'\r'/}
if [[ ${AREC[0]} == "+OK" ]];then
while read -ert 2 -u ${POP3_CONN[0]} REC ; do
REC=${REC//$'\r'/}
echo "$REC"
if [[ "$REC" == "." ]];then
return 0
fi
done
else
return 1
fi
}

Финальный код
#!/bin/bash

SERVER="server"
PORT=995
USER="user@server"
PASS="StrongPass"

coproc POP3_CONN { openssl s_client -connect "${SERVER}:${PORT}" -quiet 2>/dev/null;}

POP3_LOGIN && echo POP3_LOGIN OK
MSG_NUM=$(POP3_STAT)
#цикл перебора сообщений
while ((MSG_NUM));do
POP3_TOP $MSG_NUM 1 # Заголовки + 1 строку сообщения
# POP3_RETR $MSG_NUM # сообщения целиком
# POP3_DELE $MSG_NUM # помечаем к удалению.
((--MSG_NUM))
done

POP3_QUIT


help coproc
help read
man openssl

🛠 #bash #linux

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
20
Хочешь устроить себе челлендж на знание командной строки? Да пожалуйста, лови тренажер по Linux-терминалу. Правда он на английском, но мы с тобой тоже не пальцем деланные.

Тренажер содержит 77 вопросов. Вполне достаточно чтобы заебаться.


Я даж успешно прошел первую задачку, правда по-олдскульному и затронул все бед-практики, которые только существуют.

Установка простая:

cd /tmp
python3 -m venv textual_apps
cd textual_apps
source bin/activate
pip install cliexercises
cliexercises


Здесь я использовал tmp папку и venv, чтобы систему всяким гавно не засорять, а чисто поиграться.

Что прикольно, если совсем тупой, то можно получить подсказку, будет показано несколько решений. Подсказка открывается по CTRL+S.

Сможешь выбить все 77 вопросов без подсказок?

Исходники этого тренажера лежат тут, а видосик с работой можешь посмотреть тут.

🛠 #linux #bash

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
364
Ремонтировал внешний диск, в моменте отвалился от малины и я получил RAW вместо ext3. Печалька. Бекапы естественно я никакие не делал, хотя 5 лет собирался этим заняться.

Как говорится - пока гром не грянет мужик не перекрестится.


Велосипед я изобретать не стал, а так же не стал брать сразу какие-то продвинутые инструменты для восстановления. Решил воспользоваться нативным fsck, чем чёрт не шутит.

Пошел погуглил синтаксис, не каждый же день этой хернёй пользуешься. А там блядь:

fsck -y /dev/sdb
fsck.ext3 -y /dev/sdb


Хмм... помимо fsck.ext3 есть еще и fsck.ext4 и еще несколько штук. Ёбтвою мать. Отправляемся ресерчить, чем эта поебота отличается от обычного fsck.

TL:DR: НИЧЕМ!

Ща, расскажу. Короче в чистом виде fsck это обёртка, универсальная оболочка, которая автоматом определяет тип файловой системы и затем уже запускает условно fsck.ext3, ну или какая там у тебя на дисках.

Автоопределение это заебись, но порой fsck не хочет ничего проверять. Поэтому самостоятельно определяем тип своей файловой системы и в зависимости от результата запускаем fsck.ext3 и т.п.

Чтобы узнать, что запустит fsck делаем так:

fsck -N /dev/sda1


В результате получишь:

[/usr/sbin/fsck.ext4 (1) -- fsck.ext4 /dev/sda1


А еще у fsck нет ключа - [-E extended-options]. В нее можно передать:

-E discard - Включает TRIM (удаление неиспользуемых блоков на SSD) во время проверки. Аналог fstrim, но в процессе fsck.

-E journal_only - Проверяет только журнал ext3/ext4, не сканируя всю ФС. Быстро, но полезно только в определённых сценариях.

-E frag - Проводит анализ фрагментации. Полезно, если интересует дефрагментация ext4.

-E bmap2extent - Преобразует старые "indirect" блоки в extent-формат (для старых ext4).

-E test_fs - Включает особое поведение для тестирования (не используется в продакшене).

Пример:

fsck.ext4 -f -E discard /dev/sda1


Принудительная проверка + удаление "мусорных" блоков на SSD.

# Как fsck определяет тип файловой системы

Порядок определения:

1. Смотрит в /etc/fstab и выгребает третий столбец.
2. Если в fstab хуй, то оно запускает blkid /dev/sda1
3. Если определить не получилось, пиздует в /etc/filesystems, но в большинстве случаев такого файла в современных дистрибутивах нет. Этот файл опционален.

Вот и вся наука. В кишки лезть уже не будет, этой информации вполне достаточно.

Ну и чтобы на каждую ошибку не вводить y, пропиши автофикс:

fsck -y /dev/sda1


Оно там само пошуршит, все исправит и будет тебе счастье. Изучай.

🛠 #linux

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
463