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

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

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

MAX: https://max.ru/bashdays

Курс: @tormozilla_bot
Блог: https://bashdays.ru
Download Telegram
Имена файлов удаляются с помощью системных вызовов 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
Забавная ситуёвина с ubuntu 24. После того, как человек поменял в файле /etc/ssh/sshd_config порт с 22 на 2222 и сделал systemctl restart ssh - ничего не произошло.

По-прежнему слушался порт 22. Хм... Хуйня какая-то.

Всё оказалось прозаичнее. В новых версиях дистрибутива, ssh демон стал использовать сокетовую модель для подключения клиентов. Как раз недавно мы разбирали systemd и его функционал, поищи по тегу #systemd

Ну так вот. Если подключиться к консоли дистрибутива (не по ssh), то обнаружим, что никакой ssh сервис не запущен. Однако...

Но если выполнить: ss -tln то увидим:

tcp LISTEN 0 4096 *:22 *:*


Да ёбтвою мать! Разбираемся.

Суть такая. Для ssh используется не ssh.service юнит, а ssh.socket. А как мы знаем юнит socket не держит процесс постоянно запущенным. Экономия ресурсов и т.п. А вот когда кто-то обращается к порту 22, запрос улетает на сокет systemd, который уже и запускает сервис ssh.service..

Пиздец конечно конструкция. Если посмотреть файл ssh.socket, в нём будет:

[Unit]
Description=OpenBSD Secure Shell server socket
Before=sockets.target ssh.service
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run

[Socket]
ListenStream=0.0.0.0:22
ListenStream=[::]:22
BindIPv6Only=ipv6-only
Accept=no
FreeBind=yes

[Install]
WantedBy=sockets.target
RequiredBy=ssh.service


Смотрим на ListenStream, вот тебе и 22 порт.

А чё блядь делать?

Снимать штаны и бегать. А если серьезно, то решается это просто. После правки конфига /etc/ssh/sshd_config делаем:

systemctl daemon-reload
systemctl restart ssh.socket


Перезапускаем именно юнит сокета. После этого порт изменится. А в файле ssh.socket будет такое:

[Socket]
ListenStream=0.0.0.0:2222
ListenStream=[::]:2222


Красота. Если тебя удручают эти нововведения, то можно вернуться к привычной схеме.

Делается это так:

systemctl disable --now ssh.socket
rm -f /etc/systemd/system/ssh.service.d/00-socket.conf
rm -f /etc/systemd/system/ssh.socket.d/addresses.conf
systemctl daemon-reload
systemctl enable --now ssh.service


Способ описан на официальном сайте бубунты.

Вот такие пироги.

Ну а чем отличается ssh_config от sshd_config я описывал в этом посте.


🛠 #linux

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
9121
Как-то давненько пришлось мне отлаживать большущую APIху, но под рукой была только консолька. Ни о каком GUI и речь не шла. А пулять запросы через curl я заебался.

Тем более хотелось что-то организованное и визуально приятное.

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

Выбор пал на Posting. Это TUI клиент со всем необходимым функционалом.

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

Ставится элементарно:

curl -LsSf https://astral.sh/uv/install.sh | sh
uv tool install --python 3.13 posting


uv — это современный инструмент для Python, который объединяет в себе функционал менеджера пакетов и виртуальных окружений, задумывается как замена pip + venv/virtualenv + pip-tools.


Для хакерменов прям мастхев. Забирай в копилку, годнота!

🛠 #linux #utilites

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
65
Вечный вопрос — как сохранить правила iptables?

Вот навтыкал ты всяких безумных правил и цепочек через командную строку, радуешься. А после ребута все твои поделки к хуям улетучиваются.

И каждый раз ты идешь гуглить, как это решить. Жиза. Каждый раз, как в первый раз.

А делается всё просто, двумя командами:

sudo iptables-save > /etc/iptables/rules.v4
sudo ip6tables-save > /etc/iptables/rules.v6


Ладно, спиздел, нужно еще доставить:

sudo apt install iptables-persistent


Эта хуёвина автоматом создает файлы /etc/iptables/rules.v4, /etc/iptables/rules.v6 и будет применять их при загрузке.

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

😀😃😄😁😅😂🤣😊
😇🙂🙃😉😌😍🥰😘
😗😙😚😋😛😝😜🤪
🤨🧐🤓😎🤩🥳😏😒

Просто открываешь напрямую файл rules.v4/rules.v6 и прописываешь туда все свои хотелки.

После этого не забываем дёрнуть слона за хобот:

systemctl restart netfilter-persistent


Либо по старинке, добавляешь правила через терминал и затем делаешь:

sudo netfilter-persistent save


Но в этом случае файлы rules.v4/rules.v6 будут перезатерты, имей это ввиду если лез в них своими руками.

Саммари

1. Ставим iptables-persistent
2. Пиздярим правила в терминале
3. Применяем netfilter-persistent save

Как делаю я? У меня писюн длинный, поэтому сразу вношу все необходимое руками в rules.v4/rules.v6, хотя это не есть бест-практика, не делай так.

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

🛠 #linux #security #iptables

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
166
Вчера в посте я упомянул конфиги iptables лишь для части дистрибутивов, Alex эту ситуацию в комментариях подметил верно.

Давай посмотрим что в других дистрибутивах происходит.

Debian / Ubuntu

Основной пакет → iptables-persistent / netfilter-persistent

/etc/iptables/rules.v4
/etc/iptables/rules.v6


RHEL / CentOS / Rocky / Alma (iptables-services)

/etc/sysconfig/iptables
/etc/sysconfig/ip6tables


Fedora (новые версии)

По умолчанию использует firewalld (поверх nftables). Если ставишь iptables-services:

/etc/sysconfig/iptables
/etc/sysconfig/ip6tables


Arch Linux / Manjaro

Из коробки iptables не сохраняет правила, обычно юзеры делают сами:

/etc/iptables/iptables.rules
/etc/iptables/ip6tables.rules


OpenSUSE / SLES

По умолчанию тоже firewalld, если ставить пакет iptables, правила обычно хранят в:

/etc/sysconfig/iptables


Astra Linux (Смоленск, Орёл и др. редакции)

Астра базируется на Debian, поэтому у неё схема как у Debian/Ubuntu:

/etc/iptables/rules.v4
/etc/iptables/rules.v6


РЕД ОС (RedOS)

RedOS базируется на RHEL/CentOS, поэтому там всё по «редхэту»:

/etc/sysconfig/iptables
/etc/sysconfig/ip6tables


Вроде основное осветил, если что-то проебал, забыл, затроил — пиши в комменты, поправим.

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

🛠 #linux #security #iptables

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
35
Мне прилетела интересная задача.

🔤🔤🔥🔤🔤🔤🔤

ТЗ: Сервер без GUI, к нему подключают usb-диск с NTFS и начинается автоматическое архивирование. Логин не требуется, сообщения выдаются на tty1.

sudo apt install ntfs-3g # для монтирования ntfs


Использовать будем штатные средства udev + systemd .

1. Подключаем диск, и запоминаем UUID.

lsblk -f


sdc                                                                         
└─sdc1
ntfs FLASH
1234567890ABCDEF

здесь FLASH - метка диска 1234567890ABCDEF - UUID

создадим каталог для монтирования:

mkdir -p /media/usb_ntfs; chmod 777 /media/usb_ntfs


Дальше создаем 4 файла. Чтобы было проще, в каждом файле в начале коммент с названием файла.

После создания:

sudo systemctl daemon-reload


Как это работает:

При подключении диска usb-диска срабатывает UDEV правило 99-usb-mount.rules и пытается запустить службу autousbbackup.service

autousbbackup.service пытается запустить media-usb_ntfs.mount, поскольку жестко он нее зависит.

Сама media-usb_ntfs.mount запустится только в том случае, если UUID диска будет 1234567890ABCDEF.

Если все условия совпадают, autousbbackup.service запустит скрипт autousbbackup.sh, внутри которого Вы напишите копирование или синхронизацию данных в каталог /media/usb_ntfs.

Если используется архивирование с чередованием дисков - просто сделайте у дисков одинаковые метки:

sudo umount /dev/sdXN # где /dev/sdXN  имя вашего NTFS-раздела.
sudo ntfslabel --new-serial=1234567890ABCDEF /dev/sdXN #задайте UUID


👆 Если в mount не указать опцию nofail система будет тормозить при загрузке.
👆 Запустить скрипт через UDEV даже в фоне не получится, поскольку система вырубит его через 5 сек.

#/etc/udev/rules.d/99-usb-mount.rules

SUBSYSTEM=="block", KERNEL=="sd*", ACTION=="add", TAG+="systemd", ENV{SYSTEMD_WANTS}="autousbbackup.service"


#/etc/systemd/system/media-usb_ntfs.mount

[Unit]
Description=Mount USB NTFS by UUID

[Mount]
What=/dev/disk/by-uuid/1234567890ABCDEF
Where=/media/usb_ntfs
Type=ntfs-3g
Options=defaults,big_writes,nofail,uid=1000,gid=1000

[Install]
WantedBy=multi-user.target


#/etc/systemd/system/autousbbackup.service

[Unit]
Description=Simple service autousbbackup
Requires=media-usb_ntfs.mount
After=media-usb_ntfs.mount

[Service]
Type=simple
ExecStart=/root/work/autousbbackup/autousbbackup.sh

[Install]
WantedBy=multi-user.target


#!/bin/bash
#/root/work/autousbbackup/autousbbackup.sh

declare -x PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
declare MOUNT_POINT=/media/usb_ntfs
declare TTY=/dev/tty1
declare DF='%(%Y%m%d-%H%M%S)T' #date format
declare LOG="$0.log"
exec &>"$LOG" # only one backup log
#exec &>>"$LOG" # all backups log
printf "$DF %s\n" -1 "START BACKUP"|tee "$TTY"
##############################################

sleep 3 # backup emulation

##############################################linux
printf "$DF %s\n" -1 "END BACKUP"|tee "$TTY"

#suicide, because service require mount point
systemd-mount --umount "$MOUNT_POINT"


🛠 #linux #bash

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
677
Как оказалось многие не знают, как нативным cron (без systemd timers) запускать скрипты с интервалом в 30 секунд, без модификации самого скрипта.

Все довольно просто и очевидно. Нужно сделать бутерброд.

Cron исторически работает только с минутной точностью. В crontab нельзя написать «каждые 10 секунд» или «раз в 30 секунд». Для этого обычно использую systemd timers или отдельный демонический скрипт с while true; sleep ...


В crontab строка запускается раз в минуту. Но внутри можно поставить sleep — задержку перед запуском. Таким образом мы получим несколько запусков в рамках одной минуты.

* * * * * /usr/local/sbin/bashdays.sh
* * * * * sleep 30; /usr/local/sbin/bashdays.sh


1. Первая строка запускает скрипт в начале минуты (00:00, 00:01, 00:02…)

2. Вторая строка — ждёт 30 секунд и запускает скрипт (00:00:30, 00:01:30, 00:02:30…).

Тут и получаем шаг в 30 секунд, именно через 2 вызова.

Костыльно? Ага! Но порой не хочется ебаться с таймерами и сделать все по-быстрому. Как вариант, вполне годный. Аналогично можно городить и другие интервалы.

Минусы подхода

Нет гарантии точности. Если первый запуск скрипта будет работать дольше, чем пауза (sleep), запуски могут наложиться.
Мусор в crontab. Для мелкого интервала надо плодить много строк.
Нет гибкой логики.

Где это полезно

Лёгкие скрипты мониторинга (ping, проверка статуса).
Хаотизация нагрузки (например, sleep $((RANDOM % 60)) для рассинхрона).
Если systemd timers или другие планировщики недоступны (например, в ограниченных окружениях или старых системах).

А как работать с таймерами ищи по тегу #systemd, много про это писал.

🛠 #linux #cron #systemd

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
51
Как не стремись к автоматизации, всегда найдется какой-нибудь легаси сервис, который требует ручного обслуживания.

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

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

Обычно это чинилось очень легко, через chown -R. Все это знали и никого это не смущало. Короче костыль ебаный.

Казалось бы, есть масса способов предотвратить такие ошибки: правильные права на файлы, ACL’ы, SELinux.

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

Спойлер: Я залез в кроличью нору и знатно так хлебнул гавна.

Попытка намбер 1

В Linux есть API под названием fanotify, с его помощью можно получать события о действиях с файлами в юзерспейс.

Всё просто: инициализируем fanotify_init, указываем каталоги через fanotify_mark и читаем события из дескриптора.

Но тут же вылез огромный хуй:

- нельзя отслеживать каталоги рекурсивно (только целый маунт)
- anotify даёт только PID процесса, который что-то сделал. А чтобы узнать UID/GID — нужно лезть в /proc/<pid>/status. То есть на каждое событие приходится открывать и парсить файлы в /proc.

Решение вполне рабочее, но громоздкое. Я такие не люблю, этож думать надо, вайбкодингом не решается.

Попытка намбер 2

Вспоминаем что есть eBPF. Это штука позволяет запускать программы прямо в ядре Linux. Они компилируются в байткод, проходят проверку, а потом гоняются через JIT почти с нативной скоростью.

Что такое eBPF можешь почитать тут и тут.


В eBPF заебись то, что можно цепляться за разные функции ядра. Например, можно подцепиться к vfs_mkdir или vfs_create — это общий слой для работы с файлами.

То есть единая точка входа. Там можно отлавливать события и фильтровать их, не гоняя лишние переключения контекста.

Но и тут вылез хуй:

- kprobes на функции VFS нестабильны, в новых ядрах сигнатуры могут меняться или функции вообще исчезнуть.

- фильтрацию приходится писать прямо в eBPF, а там свои ограничения. Нет бесконечных циклов, стек всего ~512 байт.

Да блядь…

Как я победил рекурсивный обход

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

Но так как в eBPF нельзя сделать «бесконечный» цикл, я ограничил глубину с помощью MAX_DEPTH.

На практике этого вполне достаточно. Глубина каталогов мне известна. Ну и конечно, пришлось аккуратно работать с RCU-локами, чтобы дерево не поменялось в момент обхода.

➡️ Кусок кода в первом комментарии, сюда не влез.


Как можно улучшить

В идеале использовать не VFS-хуки, а LSM hooks (Linux Security Module).

Они стабильнее, понятнее и позволяют сразу работать с путями. Там можно красиво получить path и сразу преобразовать его в строку, а потом делать поиск подстроки.

Но в моём ядре этих хуков не было, хуй знает почему, видимо дистрибутив слишком древний. Надо попробовать на новых, чем черт не шутит.

Итоги

Эта поделка как и предполагалась, погрузила меня в печаль, душевные страдания, НО стала отличным тренажером для прокачки:

- Внутренностей Linux ядра
- Работы с eBPF
- И кучу другого с kernel-space

eBPF — мощнейший инструмент, но очень тонкий. Ошибёшься — будешь выебан в жопу.


Информации по нему много, но вся она разбросана. Собрать всё это в кучу было отдельным квестом.

Мораль?

Иногда самое простое решение — это chown -R. Но куда интереснее — написать свой велосипед и заглянуть в кроличью нору Linux ядра.

🛠 #linux #debug #dev

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
947
Настройка core dump в Docker

Цель этого поста — дать тебе общее руководство по включению и сбору core dumps для приложений, работающих в docker контейнере.

Настраиваем хост для сохранения дампов

Для начала надо сконфигурировать хостовую машину, чтобы сохранять такие дампы в нужное место. Нет, не в жопу.

Универсальный способ — задать шаблон core pattern. Шаблон определяет путь и информацию о процессе который наебнулся.

echo '/tmp/core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern


Кратенько:

%e — имя процесса
%p — pid процесса

Более подробно о конфигурации core pattern можешь почитать в man-странице ядра Linux.


Как вариант, можно настроить host изнутри контейнера через CMD или ENTRYPOINT. Но контейнер в этом случае должен запускаться в privileged mode, что хуева с точки зрения безопасности.

Пример хуёвого приложения

#include <cstdlib>
void foo() {
std::abort();
}

int main() {
foo();
return 0;
}


После компиляции и запуска, приложение наебнется с ошибкой.

Пишем Dockerfile для этого приложения

FROM ubuntu:22.04

# Install tools
RUN apt update \
&& apt -y install \
build-essential \
gdb \
&& rm -rf /var/lib/apt/lists/*

# Build the application
COPY ./ /src/
WORKDIR /src/
RUN g++ main.cpp -o app

CMD ["/src/app"]


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


Запускаем контейнер с нужными опциями:

docker run \
--init \
--ulimit core=-1 \
--mount type=bind,source=/tmp/,target=/tmp/ \
application:latest


Разбираем опции:

--init — гарантирует корректную обработку сигналов внутри контейнера

--ulimit — устанавливает неограниченный размер core dump для процессов внутри контейнера

--mount — монтирует /tmp из хоста в контейнер, чтобы дампы, создаваемые внутри контейнера, были доступны после его остановки или удаления

Здесь важно: путь source на хосте должен совпадать с тем, который задан в шаблоне core pattern.

После того как приложение наебнется, core dump будет сохранён на хостовой машине в директории /tmp.

ls /tmp/core*
# /tmp/core.app.5


Анализируем дамп с помощью gdb

Такс, мы получили core dump и он у нас лежит на хостовой машине, но рекомендуется открыть его внутри контейнера. Контейнер должен быть из того же образа, в котором компилилось приложение.

Это поможет убедиться, что все зависимости (библиотеки и прочая хуитень) доступны.

docker run \
-it \
--mount type=bind,source=/tmp/,target=/tmp/ \
application:latest \
bash


Если в образе нет исходного кода, можно допом смаунтить исходники:

docker run \
-it \
--mount type=bind,source=/tmp/,target=/tmp/ \
--mount type=bind,source=<путь_к_исходникам_на_хосте>,target=/src/ \
application:latest \
bash


Теперь внутри контейнера запускаем:

gdb app /tmp/core.app.5


После загрузки дампа можно выполнить команду bt (backtrace), чтобы увидеть трассировку стека:

(gdb) bt
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51
#1 0x00007f263f378921 in __GI_abort () at abort.c:79
#2 0x000055f9a9d16653 in foo() ()
#3 0x000055f9a9d1665c in main ()


Команда поможет определить, где именно произошел факап.

Давай быстренько разберем ошибку.

#0 и #1 показывают, что процесс получил сигнал 6 (SIGABRT) и завершился через abort()

#2 — вызов произошёл внутри функции foo()

#3main() вызвал foo()

В исходнике было:

void foo() {
std::abort();
}


То есть ошибка здесь не баг компиляции или рантайма, а намеренно вставленный вызов std::abort(), который и приводит к аварийному завершению и генерации core dump.

Если у тебя docker-compose, то все флаги (--init, --ulimit, --mount и т.д.) применимы и для него. То есть отладку можно легко адаптировать.

Хуй знает чё еще написать, завтра тему дебага продолжим, чет в конце года много траблшутинга навалило разнообразного.

🛠 #linux #debug #dev

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
545
Здесь: я как-то поднимал проблему с торможением 1c на postgres.

🔤🔤🔥🔤🔤🔤🔤

Благодаря нашему коллеге @ovchinnikovmy, дело сдвинулось с мертвой точки. Спасибо ему большое за консультации и рекомендации по PG.

Мы начали попытки оптимизировать работу postgres для нашей задачи. И сразу столкнулись с проблемой. Ну, оптимизировали. А насколько?

Улучшение есть, а кто был виноват в тормозах PG или 1С?

Все может прекрасно работать в тестах, и становится колом, когда идет интенсивная работа в нескольких базах одновременно. Где горлышко бутылки - число ядер, частота или скорость диска, или может пора памяти добавить?

Там маленькая конторка. Фактически один сервак. Не будешь же zabbix ради этого ставить.

Онлайн можно посмотреть через nmon, top/htop. nmon даже позволяет записывать данные в лог, и есть программа, которая позволяет генерить html с отчетами, но там все интегрально. По системе. А хочется по процессам.

Остановился на пакете sysstat. Это такой консольный zabbix. Он позволяет собирать статистику по процессам. Анализировать можно память, проц, диск, стэк. Причем по каждому PID в отдельности и прямо в консоли. В общем, все, что нужно. Для большего удобства я набросал скрипт.

#!/bin/bash

# 20251005
# apt install sysstat gawk
# работа с 9 до 18, запись с 8:30 до 18:30
# запуск через cron
# 30 8 * * * /root/work/stat/stat.sh &

declare -x PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
declare -i INTERVAL_SEC=10
declare -i COUNT=3600 # итого 10 часов
declare -i WEEK_DAY;printf -v WEEK_DAY "%(%-u)T"
declare LOG="$0_${WEEK_DAY}.csv"

pidstat -r -l -d -H -h -U -u $INTERVAL_SEC $COUNT |
gawk 'NR<4;$2=="usr1cv83"||$2=="postgres"{$1=strftime("%Y%m%d_%H%M%S",$1);print}'>"$LOG"


Он собирает статистику каждые 10 сек по двум пользователям postgres (PG) и usr1cv83 (1С) в csv-лог (разделитель пробел, но это можно исправить).

Поскольку лог текстовый, дальше его можно вертеть с помощью awk/sort или просто в LibreOffice Calc.

pidstat ключи:

-r - память
-l - командная строка процесса
-d - диск
-h - табличный режим
-H - время unix
-U - username
-u - проц

gawk ключи:

NR<4 - заголовок (легенда) из трех строк
$2=="usr1cv83"||$2=="postgres" - фильтрация по username
$1=strftime("%Y%m%d_%H%M%S",$1) - удобный формат времени.

LOG="$0_${WEEK_DAY}.csv" - Недельная ротация. По одному на день.

🛠 #debug #linux

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
39
Сколько линуксоида не корми, он все равно на винду поглядывает.

Накопал я тут тебе WinBoat.

Эта штука запускает Windows-приложения прямо в Linux с максимально «нативной» интеграцией: окна приложений — как обычные X/Wayland-окна, общий доступ к файлам и даже полноценный Windows-десктоп.

Что в коробке

- Запустит любое приложение, которое работает в Windows — в том числе коммерческие программы.

Ради прикола запустил Excel, ну что сказать, минимум приседаний и танцев с бубном. Работает прям отлично.

- Окна приложений интегрируются в рабочий стол Linux, не просто RDP в едином окне, а RemoteApp-композитинг.

- Автоматические инсталляции через GUI — выбираешь параметры, остальное делает WinBoat.

- Файловая интеграция, домашний каталог монтируется в винду.

- Полный Windows-десктоп по запросу (если нужно работать внутри полноценной виртуальной машины).

Что под капотом

Это Electron-приложение + сопутствующий «guest server». Windows запускается как VM внутри контейнера Docker. А между хостом и гостем общаются через WinBoat Guest Server.

Затем для прорисовки отдельных окон используется FreeRDP + Windows RemoteApp — это и даёт ложное ощущение нативности.

Штука интересная возможно бы я ее использовал, если бы плотно сидел на линуксе.

Так что приглядись, глядишь установишь в хозяйстве приживется.

🛠 #утилиты #utilites #linux

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
73
Фиксим кривой Exit Code в docker

Во время работы с docker контейнером наткнулся на неочевидный код выхода (exit code).

Про exit codes (коды выхода) писал тут


Суть проблемы: Когда программа внутри контейнера падает с abort(), Docker возвращает неправильный код выхода. Вместо ожидаемого 134 (128 + SIGABRT), контейнер отдаёт 139 (128 + SIGSEGV).

То есть контейнер маскирует реальную причину падения приложения. Соответственно дальнейший дебаг не имеет смысла, потому что код выхода не соответствует действительности.

Давай проверим:

#include <cstdlib>

int main() {
std::abort();
return 0;
}


Пишем Dockerfile:

FROM ubuntu:22.04

RUN apt-get update \
&& apt-get -y install \
build-essential \
&& rm -rf /var/lib/apt/lists/*

COPY ./ /src/
WORKDIR /src/
RUN g++ main.cpp -o app

WORKDIR /
CMD ["/src/app"]


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

docker build -f ./Dockerfile -t sigabort_test:latest .
docker run --name test sigabort_test:latest ; echo $?


А на выходе у нас код: 139.

В примере выше код выхода — 139 = 128 + 11, где 11 соответствует SIGSEGV (ошибка сегментации), а не 134 = 128 + 6, что был бы SIGABRT (аварийное завершение).

Чтобы это пофиксить, нужно захерачить хак:

CMD ["bash", "-c", "/src/app ; exit $(echo $?)"]


docker run --name test sigabort_test:latest ; echo $?
bash: line 1: 6 Aborted /src/app
134


После этого контейнер будет возвращать корректный код 134.

Вариант рабочий, но костыльный. Правильнее использовать ключ --init.

Если запустить контейнер с флагом --init, используя исходную команду CMD ["/src/app"], мы получим ожидаемый 134 код. Что нам и нужно.

docker run --init --name test sigabort_test:latest ; echo $?

134


Почему init все починил?

Давай копнём глубже. В Linux процесс с PID 1 (init) имеет нестандартную семантику сигналов:

- Если у PID 1 для сигнала стоит действие «по умолчанию» (никакого обработчика), то сигналы с действием terminate игнорируются ядром. Это сделано, чтобы случайным SIGTERM/SIGINT нельзя было «уронить» init.

- PID 1 должен забирать зомби-процессы (делать wait() за умершими детьми). Если этого не делать, накопятся зомби.

- PID 1 обычно пробрасывает сигналы дальше — тому «настоящему» приложению, которое оно запускает.

Когда мы запускаем контейнер без --init, приложение становится PID 1.

Большинство обычных приложений (на C/C++/Go/Node/Java и т.д.) не написаны как «инит-системы», они не настраивают обработку всех сигналов, не занимаются «реапингом» детей и не пробрасывают сигналы. В результате вылазиют баги.


Наш сценарий с abort() (который поднимает SIGABRT) упирается именно в правила для PID 1. abort() внутри процесса поднимает SIGABRT.

Для обычного процесса с PID ≠ 1 это приводит к завершению с кодом 128 + 6 = 134. Но если процесс — PID 1, ядро игнорирует «терминирующие» сигналы при действии по умолчанию. В результате стандартные ожидания вокруг SIGABRT ломаются.

Ну а дальше вступают в силу детали реализации рантайма/сишной библиотеки, как именно контейнерный рантайм считывает статус.

На практике это может приводить к тому, что ты видишь 139 (SIGSEGV) вместо ожидаемого 134 (SIGABRT).

И тут проблема не в docker, а в том, что приложение неожиданно оказалось в роли init-процесса и попало под его особые правила.

Вот и вся наука. Изучай.

🛠 #docker #devops #linux #debug

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
566
Fedora Post Install

Набрёл тут на Bash поделку CRIMS0NH4T.

В двух словах — делает за тебя необходимую рутину после чистой установки Fedora.

— Настройка DNF
— Установка RMP Fusion
— Установка кодеков
— GPU драйвера (Intel, Nvidia, AMD)
— Тюнинг производительности
— Оптимизация Gnome

Как я понял, версия прям ранняя альфа-альфа и активно допиливается автором.

Я бы минимально запатчил так:

- set -u
+ set -euo pipefail
+ IFS=$'\n\t'

- readonly LOG_FILE="/tmp/crimsonhat_$(date +%Y%m%d_%H%M%S).log"
+ LOG_FILE="$(mktemp /tmp/crimsonhat.XXXXXX.log)"
+ readonly LOG_FILE

- run_with_progress() { local message="$1" shift log INFO "$message" if "$@" >/dev/null 2>&1; then return 0 else return 1 fi }
+ run_with_progress() {
+ local message="$1"; shift
+ log INFO "$message"
+ if "$@" >>"$LOG_FILE" 2>&1; then
+ return 0
+ else
+ return 1
+ fi
+}

- disk_type=$(lsblk -d -o name,rota | awk 'NR==2 {print $2}')
- primary_disk=$(lsblk -d -o name,rota | awk 'NR==2 {print $1}')
+ read -r primary_disk disk_type < <(lsblk -dn -o NAME,ROTA | head -n1)
...
- current_scheduler=$(cmd < "$scheduler_path" 2>/dev/null | grep -o '\[.*\]' | tr -d '[]')
+ current_scheduler=$(tr -d '\n' < "$scheduler_path" | sed -n 's/.*\[\(.*\)\].*/\1/p')


Хотя set -euo pipefail тут конечно спорный вариант, можно наступить на грабли.

pipefail заставляет конвейер возвращать ошибку, если любая команда в скрипте упала. Так что применив set -u автор обезопасил себя от неочевидных багов.


Как концепт, вполне можно форкнуть и подсмотреть какие-то штуки, возможно в своих поделках тебе что-то сгодится.

🛠 #linux #tweaks

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
523
Сегодня поделюсь историей, которая началась плохо, но закончилась хорошо.

🔤🔤🔤🔤🔤🔤🔤

В общем, у меня домашний шлюз - mini PC на debian. А поскольку мощность для шлюза избыточна, приходится использовать ее не по прямому назначению.

Поставил туда transmission-daemon, и рулю в консоли через transmission-remote.

Сами понимаете, что каждый раз в консоли набирать transmission-remote да еще с ключами — кнопки сотрутся, поэтому пользуюсь алиасами.

И вот сегодня я накосячил с алиасом настолько знатно, что bash упал и я покинул чат. И после этого не смог подключиться по ssh.

Если бы я правил sshd_config, я бы, конечно, подключился двумя сессиями, и все восстановил. Кто ж знал, что правка .bashrc тоже опасна.

Думал уже придется лезть на чердак, снимать миниписюк и править файлы локально, но все оказалось гораздо проще. Оказывается есть даже два способа, чтобы поправить ситуацию.

1. Выполнить ssh user@host -t /bin/sh

Вместо /bin/sh можно указать любую оболочку (их примерный список можно глянуть в /etc/shells). Понятно, что оболочка должна быть установлена на удаленной машине.

2. Подключиться по sftp, скачать .bashrc, локально отредактировать и залить обратно.

В общем, все сработало нормально. Алиас исправил. Все работает.

А подключился бы, как положено, при правке важных файлов двумя ssh-сессиями. И не было бы этой статьи.

Кстати, использование именно первого пункта позволяет обходить тривиальные защиты ssh (типа скриптовой двухфакторной аутентификации или использование sleepshell для пользователя, которому только разрешен проброс портов), основанные на простой замене оболочки пользователя в /etc/passwd.

При выполнении скрипта, хакер уже аутентифицирован в системе, и может просто заменить оболочку.

🛠 #linux #ssh #fix

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