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
Как безопасно тестировать юниты

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

Но не знаем как всё это дело тестировать. А для тестирования тебе пригодится ключик --runtime.

Ключ --runtime — создаёт временное переопределение, которое исчезнет после перезагрузки.


Идеально подходит, если ты тестишь, не уверен или не хочешь ничего портить.

А вот и сама команда:

SYSTEMD_EDITOR=vim systemctl edit --runtime nginx


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


Всё! Теперь правим юнит, тестируем. Временный файл с override будет создан тут /run/systemd/.

И самое главное — все изменения сохранятся только до следующей перезагрузки системы.

А какая в этом польза?

1. Для временного изменения конфигурации systemd без затрагивания оригинального файла.

2. Для тестов или отладки (например, сменить ExecStart для nginx только на время текущей сессии).

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

Пример с nginx

Хочу временно запустить nginx с другим конфигом! Создаю рантайм.

[Service]
ExecStart=
ExecStart=/usr/sbin/nginx -c /home/user/nginx-bashdays.conf


Перезапускаю nginx и радуюсь результату, теперь nginx запущен с другим конфигом.

А почему ExecStart идет дважды?

Хе брат, это очередная приколюха systemd. Оно очищает предыдущее значение ExecStart из основного юнита. А следующая строка задаёт новое значение.

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

Думаю на этом можно закончить. Я неочевидную базу выдал, а ты стал еще сильнее. Изучай и ничего не бойся!

🛠 #linux #tricks #debug #systemd

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
156
Как разгребать гавно в юнитах

Ладно, не гавно, а override файлы, которых может быть нихуя не одна штука. Причем несколько для одного юнита.

Не знаю нахуя так делают, но временами такое встречается. Особенно на каких-то легаси серверах, которые работают со времен фидонета.

Короче, все что нам нужно это команда:

sudo systemctl revert nginx


Эта команда отменяет все локальные изменения юнита nginx и возвращает его к состоянию по умолчанию, заданному в оригинальных unit-файлах, поставляемых системой или пакетом.

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

Команда удаляем override-файлы в:

/etc/systemd/system/nginx.service.d/
/etc/systemd/system/nginx.service


И следом будет использован оригинальный unit-файл, который обычно расположен в /lib/systemd/system/nginx.service.

Когда это полезно?

Если ты вносил изменения через systemctl edit, вручную правил nginx.service, или добавлял drop-in'ы — и хочешь откатиться к дефолтной конфигурации, revert делает это безопасно.

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

Ну а ты носи в это в голове и никогда не бойся экспериментировать. Любой факап — твой опыт.

Предыдущие посты на тему systemd ищи по тегу #systemd


🛠 #linux #tricks #debug #systemd

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
463
Почему в юнитах не работает Environment.

Сразу пример:

[Service]
Environment="FOO=bar"
ExecStart=/bin/bash /usr/local/sbin/bashdays.sh


Сделал юнит, но в скрипт bashdays.sh не передаётся переменная FOO. Хотя логически всё правильно.

Тут снова приколы.

➡️ 1. Скрипт запускается не напрямую, а через интерпретатор.

Если в ExecStart указан скрипт с shebang’ом (#!/bin/bash), systemd запускает его как отдельный процесс, и переменные окружения передаются.

Но если ты укажешь в ExecStart сам интерпретатор, вот так:

ExecStart=/bin/bash /usr/local/sbin/bashdays.sh


То переменная FOO, заданная через Environment=, не попадёт в подскрипт, потому что ExecStart запускает bash, а уже bash запускает — скрипт, и переменные окружения само собой нихуя не передаются.

Как правильно?

Вот так:

[Service]
Environment="FOO=bar"
ExecStart=/usr/local/sbin/bashdays.sh


А сам скрипт:

#!/bin/bash

echo "FOO is $FOO"


Всё! Теперь переменная из юнита будет корректно передаваться в скрипт.

➡️ 2. Использовать EnvironmentFile=

Если у тебя дохуя переменных, то выносим их в отдельный файл, например сюда /etc/bashdays-service.env.

FOO=bar
BAZ=qux


А в самом юните прописываем:

[Service]
EnvironmentFile=/etc/bashdays-service.env
ExecStart=/usr/local/sbin/bashdays.sh


Всё! Теперь переменные считываются из файла и скрипт их видит.

➡️ 3. Передавать переменные прямо в ExecStart

[Service]
ExecStart=/bin/bash -c 'FOO=bar exec /usr/local/sbin/bashdays.sh'


Тут особо и комментировать нечего, всё очевидно.

Как отладить и задебажить?

А вот так:

systemctl show nginx


Вместо nginx подставляешь свой сервис и наблюдаешь все переменные которые передались. В наших примерах увидишь Environment=FOO=bar.

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

Как вариант, можешь добавить в скрипт такую хуйню:

env > /tmp/env.log


И смотришь, какие переменные реально передаются в твой скрипт.

Еще одна база выдана, вроде очевидные вещи, но так глубоко в это никто не лезет. Пользуйся.

🛠 #linux #tricks #debug #systemd #bash

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
786
Три скрытых героя systemd

А вот и сами герои: timer, path и socket

Это невидимые юниты systemd, которые могут заменить cron, inotify`и даже `xinetd.

➡️ 1. Timer

Про timer ты наверняка слышал. Это альтернатива crontab. Позволяет запускать сервис по расписанию. Вместо того чтобы писать крон-джобы, ты создаёшь .service и .timer.

Пример:

/etc/systemd/system/backup.service

[Unit]
Description=Backup job

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/backup.sh


/etc/systemd/system/backup.timer

[Unit]
Description=Run backup daily

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target


Активируем:

sudo systemctl enable --now backup.timer


Persistent=true гарантирует запуск пропущенной задачи, если система была выключена.

Всё! Никаких тебе легаси кронтабов, все работает на юнитах, запускается бэкапилка, тикает таймер.

➡️ 2. Path

Работает как inotifywait или File Watcher: следит за файлами/папками и запускает сервис при изменении.

Пример:

/etc/systemd/system/upload.path

[Unit]
Description=Watch upload folder

[Path]
PathModified=/var/www/bashdays/upload
Unit=process-upload.service

[Install]
WantedBy=multi-user.target


/etc/systemd/system/process-upload.service

[Unit]
Description=Process uploaded files

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/process-upload.sh


Теперь каждый раз когда в папке /var/www/bashdays/upload что-то меняется, автоматически запускается скрипт process-upload.sh.

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


➡️ 3. Socket

Заменяет ручной systemctl start, активируя сервис при первом подключении к сокету. Аналог inetd/xinetd

Нихуя не понятно, но на примере сейчас всё прояснится.

Пример:

/etc/systemd/system/echo.socket

[Unit]
Description=Echo socket

[Socket]
ListenStream=12345
Accept=yes

[Install]
WantedBy=sockets.target


/etc/systemd/system/echo@.service

[Unit]
Description=Echo service

[Service]
ExecStart=/usr/bin/nc -l -p 12345 -e /bin/cat
StandardInput=socket


В примере, сервис НЕ работает постоянно, он стартует, только когда кто-то подключается к порту 12345.

Когда ты используешь .socket с Accept=yes, systemd открывает сокет сам и ждёт подключения. А когда подключение приходит — создаёт новый экземпляр сервиса, подставляя туда данные об этом соединении. После завершения — сервис умирает. Всё очень экономно и прозрачно.

Как понять, какой экземпляр стартует?

systemd запускает echo@<ID>.service, где <ID> — уникальный идентификатор подключения (например, PID или номер сокета).

journalctl -u echo@*


Зачем нужны скрытые юниты?

1. Не нужен cron, всё централизовано в systemd.
2. Не нужен inotify-tools.
3. Сервисы не висят без дела, запускаются только когда нужно
4. Журналирование через journalctl

Вот такие пироги. В повседневной работе я применяю timer и path, с сокетами как-то сильно не приходилось париться.

Ну а ты попробуй хотя бы перетащить свои кронджобы в timer и порадоваться бест-практикам.

🛠 #linux #tricks #debug #systemd #bash

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
9106
Продолжаем ковырять systemd

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

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

systemctl --user enable bashdays.timer


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

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

Эта штука создаёт символическую ссылку в ~/.config/systemd/user/ в директории default.target.wants/

Если тебе нужно разово запустить такой юнит, то enable меняем на start. Вот и вся наука.

А как отлаживать?

➡️ 1. Сначала нужно убедиться что таймер активен:

systemctl --user status bashdays.timer


Если всё ок, то увидишь Active: active (waiting).

➡️ 2. Смотрим расписание запуска

systemctl --user list-timers


- NEXT — когда следующий запуск
- LEFT — сколько осталось времени
- LAST — когда запускался в прошлый раз
- UNIT — имя таймера

➡️ 3. Проверяем выполнялся ли сервис

journalctl --user-unit bashdays.service --since "1 hour ago"


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

journalctl --user-unit bashdays.service --since today


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

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

🛠 #linux #tricks #debug #systemd #bash

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
362
Продолжаем разбирать systemd на кирпичики. Сегодня про кандишены (условия/ситуации).

Директива ConditionPathExists используется в юнит-файлах systemd и позволяет задать условие для запуска юнита: он будет запущен только если указанный файл или директория существует.

Пример:

[Unit]
Description=Special Service
ConditionPathExists=/etc/bashdays-config

[Service]
ExecStart=/usr/local/bin/bashdays-handler


Этот юнит не запустится, если файл /etc/bashdays-config не существует. В статусе сервиса ты увидишь:

ConditionPathExists=/etc/bashdays-config was not met


Если путь НЕ существует, systemd не будет запускать юнит. Вместо этого он будет считаться пропущенным (skipped), а не проваленным.

Нахуя это надо?

1. Например, если bashdays-config существует, запускается сервис с особым поведением.

2. Можно создавать один юнит, который активируется только при наличии определённого модуля или плагина.

3. Иногда это используют в early boot-юнитах, чтобы запускать их только если что-то доступно (например, том LUKS).

Список основных Condition's (нажми на спойлер)

ConditionPathExists — файл или каталог существует
ConditionPathIsDirectory — путь существует и это каталог
ConditionPathIsMountPoint — путь является точкой монтирования
ConditionFileIsExecutable — файл существует и он исполняемый
ConditionKernelCommandLine — есть ли параметр ядра с указанным значением
ConditionPathExistsGlob — совпадает ли хотя бы один путь по glob-шаблону
ConditionPathIsSymbolicLink — является ли путь символической ссылкой
ConditionFileNotEmpty — существует ли файл и не пуст ли он
ConditionEnvironment — установлена ли переменная окружения
ConditionArchitecture — архитектура CPU (например, x86_64, aarch64)
ConditionVirtualization — nип виртуализации (например, kvm, docker)
ConditionHost — имя хоста
ConditionMachineID — cовпадает ли machine-id
ConditionControlGroupController — есть ли указанный cgroup controller (например, cpu, memory)
ConditionNeedsUpdate — нуждается ли в обновлении (/usr или /etc)
ConditionFirstBoot — Первый ли это запуск после установки
ConditionACPower — Подключено ли питание от сети (для ноутбуков)
ConditionSecurity — Активен ли определённый LSM (например, selinux)
ConditionUser — Запускается ли от указанного пользователя
ConditionGroup — Запускается ли от указанной группы
ConditionCapability — Имеет ли процесс определённую capability
ConditionNetwork — Есть ли сеть (online, configured)
ConditionMemory — Есть ли минимум указанного объёма памяти


Дополнительно

Если заменить Condition на Assert, условие не выполнено — юнит считается проваленным, а не пропущенным.

То есть берем к примеру директиву ConditionPathExists и меняем её на AssertPathExists.

AssertPathExists=/etc/bashdays.conf


И получается:

ConditionPathExists — юнит пропускается (не считается ошибкой)

AssertPathExists — юнит падает (считается ошибкой)

Assert полезен, когда ты строго требуешь, чтобы ресурс (например, внешний диск, NFS, или другой том) был смонтирован перед запуском сервиса. Если его нет — это ошибка, а не «ну и похуй».

[Unit]
Description=Start backup script only if /mnt/backup is mounted
AssertPathIsMountPoint=/mnt/backup

[Service]
ExecStart=/usr/local/bin/backup.sh


Если /mnt/backup не смонтирован, systemd выдаст ошибку, и сервис не запустится.

Статус будет такой:

systemd[1]: Starting backup.service...
systemd[1]: backup.service: Failed with result 'assert'.


Ну и все это дело можно комбинировать:

ConditionPathExists=/mnt/backup
AssertPathIsMountPoint=/mnt/backup


Если /mnt/backup не существует — юнит пропускается.

Если существует, но не смонтирован — юнит заваливается.

Короче systemd не такой уж простой, как кажется с первого взгляда. На нём можно прям заебись логику построить и получить желаемое. Так что недооценивать его явно не стоит, это прям заебись комбайн.

🛠 #linux #tricks #debug #systemd #bash

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
673
This media is not supported in the widget
VIEW IN TELEGRAM
2886
Всем привет. Опять решил поделиться опытом своих провалов.

🔤🔤🔥🔤🔤🔤🔤

Вобщем, как-то раз я решил написать самоучитель по 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
Хочется двухфакторную аутентификацию, но внутренний параноик запрещает пользоваться сторонними сервисами аутентификации.

🔤🔤🔥🔤🔤🔤🔤

Хочу рассмотреть концепт и выслушать замечания от уважаемых людей.

Итак, идея проста.

1. Прописываем shell-скрипт пользователю в качестве shell. Или группе в ForceCommand в sshd_config.

2. После первого уровня (пароль или ключ) запускается скрипт.

3. Скрипт выводит на экран код и отправляет на почту/телегу админу сообщение о попытке аутентификации (ведь первый уровень аутентификации пройден) пользователя с UID (чтобы не светить логины на сторонних ресурсах.)

4. Сам скрипт никаких дополнительных действий от пользователя не требует, и завершается через некоторое время (3-15 минут) если от пользователя нет кода.

5. Пользователь отправляет код по почте и/или боту в телегу. Причем пользователь заранее должен знать, по какому адресу отправлять код.

6. Скрипт, периодически проверяя почту/телегу, при получении нужного кода передает управление bash. УРА, Я В ДОМИКЕ.

7. Если код не пришел, в лог записывается инфа, timestamp username. Если кто-то попытается досить - другой скрипт (нужны права) может банить.

8. В качестве дополнительной ограничительной меры в /etc/passwd в поле 5 GECOS можно прописать мыло или номер tg-чата, с которого ожидается код.

9. Скрипт завершается при любой ошибке.

Ну и конечно, Пользователь отправляет код с телефона, а не того компа, с которого подключается по ssh :-)

🛠 #вопросвзал

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
239
Имена файлов удаляются с помощью системных вызовов 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
Напозырить и ужаснуться

50 GNU Commands VS 50 PowerShell Commands

🛠 #bash #powershell

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
238
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
This media is not supported in your browser
VIEW IN TELEGRAM
Порой заёбываешь подбирать свободный домен для собственных нужд, но на любую хотелку уже есть утилита.

Сегодня тыкаем domain-check. Как раз вышла новая версия v0.6.0. Проект молодой, но достаточно перспективный, а самое главное с отрытым исходным кодом.

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

curl https://sh.rustup.rs -sSf | sh
. "$HOME/.cargo/env"
cargo install domain-check


Либо качаем отсюда бинарник под свою ОС.

Запускаем проверку доменов:

domain-check linuxfactory -t ru,com,io,dev


Опа, опа, нихуя се!

linuxfactory.dev AVAILABLE
linuxfactory.ru TAKEN
linuxfactory.com TAKEN
linuxfactory.io AVAILABLE

Summary: 2 available, 2 taken, 0 unknown (processed in 0.9s)


Выбираем свободный и по желанию регаем. У domain-check дохера ключей и методов подбора/проверки.

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

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

🛠 #utilites

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
52
Слыхал про 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
Довольно частый вопрос с LFКак жить с gitlab раннерами?

Да просто там всё. Если у тебя основная инфра в кубе, то в кубе и крутим.

Ну а если ламповые сервера, без всей этой кубовой хуйни, то вариантов не много.

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

Здесь важно раздуплить себе какой-нибудь гарбаж коллектор, либо башник в крону накидать.

Часто раннеры пихают прям на продакшен сервера, где крутятся все бизнес процессы. В этом случае продакшен превращается в помойку, нагрузка постоянно скачет при сборках/деплое и т.п.

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


Подняли сервер, нарегали нужных раннеров. В идеале это процесс автоматизировать, ансибл-хуянсибл ну или чё там у тебя есть.

Раннеры можно зарегать на один токен:

Сервер 1

gitlab-runner register \
--url https://gitlab.com/ \
--registration-token ABCDEFG123456 \
--executor docker \
--description "runner-1"


Сервер 2

gitlab-runner register \
--url https://gitlab.com/ \
--registration-token ABCDEFG123456 \
--executor docker \
--description "runner-2"


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

Раннер 1

--description "runner-fast"
--tag-list "docker"


Раннер 2

--description "runner-slow"
--tag-list "docker"


В .gitlab-ci.yml у нас так:

build:
script: echo "Building"
tags:
- docker


Задание пойдет на тот раннер, который раньше освободится. Если оба свободны — гитлаб рандомно сделает выбор, приоритетности нет.

Если важна приоритетность, можно поиграть в юните с параметром GITLAB_RUNNER_PRIORITY.

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


А как деплоить?

Тоже всё просто, ssh/rsync в помощь, раннер подключается к нужному серверу, например по ssh и выполняет все необходимые команды. Затаскивает новый докер образ, стопорит старый, запускает новый.

Как-то так:

deploy:
stage: deploy
script:
- |
ssh $SSH_USER@$SSH_HOST << EOF
docker pull $IMAGE_NAME
docker stop my_app || true
docker rm my_app || true
docker run -d -p 5000:5000 --name my_app $IMAGE_NAME
EOF


Подключаемся под юзером из переменной $SSH_USER к хосту из переменной $SSH_HOST. Ну и запускаем команды.

Важно!

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

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

🛠 #devops #linuxfactory #cicd

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
455
Сижу вчера втыкаю в домашки LF, звонок в домофон:

— Роман?
— Я не Роман, я Олег!
— Вам посылка!
— Хм… ну ок, заносите…

Вроде ничего не заказывал, думаю мож супруга покушать намутила — неа!

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

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

То блядь набор для варки шоколада пришлют, то машинку для интимной стрижки. Хуй знает чем вообще работодатели руководствуются. Всякую шляпу на ДР вечно дарят. Я ебал шоколад варить!

Вот до сих пор лежат посылки с алихи для самохостинга нераспечатанные. Всё собираюсь распаковать и пост для тебя накидать, но пока никак руки не дойдут. После тайги обещаю распаковать и поделиться сборкой.


В этот раз что-то пошло не так и я распаковал «контейнер», любопытство взяло своё. А там — Селектел и что-то про балансировку.

Ну думаю — сбылась мечта идиота, теперь у меня есть Ти-Рекс в полный рост!

Сяду на него и буду балансировать. Ну либо как минимум бумажная документация по управлению облаками и провайдерами терраформа.

По факту удивили. Сначала я нихуя не понял что это, какая-то доска и бочка, в бочке явно не пиво… гуси какие-то на картинках…

Но сын прояснил — батя, это баланс-борд! Но тебе на нём нельзя, у тебя ипотека, тебе её еще 300 лет выплачивать.

Лана чё, колесики приделаю и буду как в молодости на скейте гонять, яж сука девопс-инженер. Ну или зимой сёрфить с горки как на сноуборде.

Сюрприз удался! Во-первых — неожиданно, во-вторых это не набор для варки шоколада и т.п.

Это ебать — борд! Вот бы мне такой в 16 лет!

Спасибо всем причастным к этому сюрпризу, отдельное спасибо Ксении, Анне, Марии. Ну и девочкам из саппорта, которым пришлось пережить все мои безумные тикеты касаемо SelectOS.


Фотосы прикладываю, там еще приглашение на Day-Off халявный, но я увы не в Питере, но в онлайне схожу позырю, мож чё нового расскажут. Можешь тоже сгонять, фоном попырить.

Короче все запечатлел, поделился. Всем спасибо и хороших предстоящих выходных, берегите себя и своих близких!

🛠 #рабочиебудни

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