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

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

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

MAX: https://max.ru/bashdays

Курс: @tormozilla_bot
Блог: https://bashdays.ru
Download Telegram
Вчера в посте я упомянул конфиги 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
74
Фиксим кривой 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
567
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