Bash Days | Linux | DevOps
Авторский блог от действующего девопса Самобытно про разработку, devops, linux, скрипты, сисадминство, техдирство и за айтишную жизу. Автор: Роман Шубин Реклама: @maxgrue MAX: https://max.ru/bashdays Курс: @tormozilla_bot Блог: https://bashdays.ru
显示更多📈 Telegram 频道 Bash Days | Linux | DevOps 的分析概览
频道 Bash Days | Linux | DevOps (@bashdays) 俄语 语言赛道中的 是活跃参与者。目前社区聚集了 23 806 名订阅者,在 技术与应用 类别中位列第 5 710,并在 俄罗斯 地区排名第 28 118 位。
📊 受众指标与增长动态
自 невідомо 创建以来,项目保持高速增长,吸引了 23 806 名订阅者。
根据 15 六月, 2026 的最新数据,频道保持稳定运转。过去 30 天订阅人数变化为 -195,过去 24 小时变化为 -10,整体触达仍然可观。
- 认证状态: 未认证
- 互动率 (ER): 平均受众互动率为 23.79%。内容发布后 24 小时内通常能获得 11.52% 的反应,占订阅者总量。
- 帖子覆盖: 每篇帖子平均可获得 5 664 次浏览,首日通常累积 2 744 次浏览。
- 互动与反馈: 受众积极参与,单帖平均反应数为 25。
- 主题关注点: 内容集中在 bashdays, linux, bash, docker, скрипт 等核心主题上。
📝 描述与内容策略
作者将该频道定位为表达主观观点的平台:
“Авторский блог от действующего девопса
Самобытно про разработку, devops, linux, скрипты, сисадминство, техдирство и за айтишную жизу.
Автор: Роман Шубин
Реклама: @maxgrue
MAX: https://max.ru/bashdays
Курс: @tormozilla_bot
Блог: https://bashdays.r...”
凭借高频更新(最新数据采集于 16 六月, 2026),频道始终保持新鲜度与高覆盖。分析显示受众积极互动,使其成为 技术与应用 类别中的关键影响点。
У меня есть такая херня, что я забираю посылки и могу их потом только спустя месяц распаковать. Потому что уже ничего не удивляет. То блядь набор для варки шоколада пришлют, то машинку для интимной стрижки. Хуй знает чем вообще работодатели руководствуются. Всякую шляпу на ДР вечно дарят. Я ебал шоколад варить! Вот до сих пор лежат посылки с алихи для самохостинга нераспечатанные. Всё собираюсь распаковать и пост для тебя накидать, но пока никак руки не дойдут. После тайги обещаю распаковать и поделиться сборкой.В этот раз что-то пошло не так и я распаковал «контейнер», любопытство взяло своё. А там — Селектел и что-то про балансировку. Ну думаю — сбылась мечта идиота, теперь у меня есть Ти-Рекс в полный рост! Сяду на него и буду балансировать. Ну либо как минимум бумажная документация по управлению облаками и провайдерами терраформа. По факту удивили. Сначала я нихуя не понял что это, какая-то доска и бочка, в бочке явно не пиво… гуси какие-то на картинках… Но сын прояснил — батя, это баланс-борд! Но тебе на нём нельзя, у тебя ипотека, тебе её еще 300 лет выплачивать. Лана чё, колесики приделаю и буду как в молодости на скейте гонять, яж сука девопс-инженер. Ну или зимой сёрфить с горки как на сноуборде. Сюрприз удался! Во-первых — неожиданно, во-вторых это не набор для варки шоколада и т.п. Это ебать — борд! Вот бы мне такой в 16 лет!
Спасибо всем причастным к этому сюрпризу, отдельное спасибо Ксении, Анне, Марии. Ну и девочкам из саппорта, которым пришлось пережить все мои безумные тикеты касаемо SelectOS.Фотосы прикладываю, там еще приглашение на Day-Off халявный, но я увы не в Питере, но в онлайне схожу позырю, мож чё нового расскажут. Можешь тоже сгонять, фоном попырить. Короче все запечатлел, поделился. Всем спасибо и хороших предстоящих выходных, берегите себя и своих близких! 🛠 #рабочиебудни — ✅ @bashdays ✅ @linuxfactory ✅ @blog
Часто раннеры пихают прям на продакшен сервера, где крутятся все бизнес процессы. В этом случае продакшен превращается в помойку, нагрузка постоянно скачет при сборках/деплое и т.п. Это хуёвая практика, на продакшене такого быть не должно. Лишь поэтому мы и поднимаем отдельный сервер под раннеры.Подняли сервер, нарегали нужных раннеров. В идеале это процесс автоматизировать, ансибл-хуянсибл ну или чё там у тебя есть. Раннеры можно зарегать на один токен: Сервер 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 ✅ @blogwith-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 ✅ @blogdomain-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 ✅ @blogcron нихуя не умеет определять рабочий сегодня день или нет. И 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 и порой это пиздец хуёва. Отвалилась АПИха, скрипт не получил данные. Поэтому рекомендую сделать себе собственный микросервис с АПИхой и ни от кого не зависеть. Тем более если сервер в оффлайне, запрос на внешнюю АПИху он сделать не сможет.Если знаешь еще какие-то способы, велком в комментарии. 🛠 #bash #linux — ✅ @bashdays ✅ @linuxfactory ✅ @blog
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
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 / @blogkill и т.п. а будет все тот же 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
现已上线!2025 年 Telegram 研究 — 年度关键洞察 
