ServerAdmin.ru
28.4K subscribers
263 photos
33 videos
12 files
2.59K links
Авторская информация о системном администрировании.

Информация о рекламе: @srv_admin_reklama_bot
Автор: @zeroxzed

Второй канал: @srv_admin_live
Сайт: serveradmin.ru
Download Telegram
​​Если вам необходимо кому-то передать свой bash скрипт, но при этом вы не хотите, чтобы этот кто-то видел его содержимое, то есть простое решение. С помощью утилиты shc его можно транслировать в язык C и скомпилировать. На выходе будет обычный бинарник, который будет успешно работать практически на любой ОС Linux.

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

Shc живёт в базовых репозиториях Debian или Ubuntu, возможно и в других дистрибутивах. Для сборки также понадобится пакет gcc.

# apt install shc gcc

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

#!/bin/bash
v=$1
echo "Simple BASH script. Entered VARIABLE: $v"

Запускаем:

# ./script.sh 111
Simple BASH script. Entered VARIABLE: 111

Теперь компилируем его в бинарник:

# shc -f -r script.sh

На выходе получаем два файла:
- script.sh.x - бинарник
- script.sh.x.c - исходный код

Запускаем бинарь:

# ./script.sh.x 123
Simple BASH script. Entered VARIABLE: 123

Отработал точно так же, как и bash скрипт. С помощью shc можно указать дату, после которой скрипт запускаться не будет. Выглядит это примерно так:

# shc -e 31/12/2023 -m "Извини, но ты опоздал!" -f -r script.sh
# ./script.sh.x
./script.sh.x: has expired!
Извини, но ты опоздал!

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

Быстро поискал и нашёл готовое решение по расшифровке таких файлов:
https://github.com/yanncam/UnSHc
Так что имейте ввиду, что это в основном защита от дурака.

#bash #script
​​Вчера заметка про простенький http сервер на python породила интересное обсуждение на тему передачи файлов. Конечно, способов существует уйма, и каждый использует то, что ему привычнее, удобнее, быстрее.

Лично у меня прижились следующие способы передачи.

🟢 Если надо перекинуть один файл между серверами, я использую scp:
# scp -P 22777 user@10.1.4.4:/mnt/data/BackUp/onlyoffice.tar.gz /backup
Сразу привёл пример с нестандартным портом, там как тут используется заглавная -P. Я долго не мог запомнить это, используя маленькую -p, как в ssh.

🟢 Если файлов много, использую rsync:
# rsync -avz -e "ssh -p 1234 -i /root/.ssh/id_rsa" user@10.1.4.22:/data/mail /data
Тоже такой универсальный пример, где сразу и порт, и ключ указан, если аутентификация не по паролю.

🟢 А вот если надо скопировать что-то разово на мой рабочий ноут или какой-то виндовый комп, то я запускаю веб сервер на python и просто скачиваю. Для текстовых логов актуально, чтобы сразу забрать все, что нужны:
# cd /var/log
# python3 -m http.server 8000

🔴 А вот простой трюк, когда надо перекинуть файл с одного сервера на другой, но при этом подключение между серверами не настроено, но я со своего ноута или jump сервера могу подключиться к обоим. Тогда можно сделать вот так:
# ssh user01@10.1.4.4 'cat /home/user01/file.tar.gz' | ssh user02@10.1.5.10 'cat > /home/user02/file.tar.gz'

#bash
Небольшая шпаргалка с командами, которая пригодится, когда у вас закончилось свободное место на дисках на сервере под управлением Linux.

🟢 Смотрим, что у нас вообще по занимаемому месту:

# df -h

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

# du -h -d 1 / | sort -hr
# du -hs /* | sort -hr
Ограничиваем вывод:
# du -h -d 1 / | sort -hr | head -10
Смотрим топ 20 самых больших директорий:
# du -hcx --max-depth=6 / | sort -rh | head -n 20
Смотрим топ 20 самых больших файлов:
# find / -mount -ignore_readdir_race -type f -exec du -h "{}" + 2>&1 \
> | sort -rh | head -n 20

🟢 На всякий случай проверяем inodes. Иногда свободное место заканчивается из-за них:

# df -ih

🟢 Если вывод du показывает, что места свободного нет, но сумма всех директорий явно меньше занимаемого места, то надо проверить удалённые файлы, которые всё ещё держит какой-то процесс. Они не видны напрямую, посмотреть их можно через lsof:

# lsof | grep '(deleted)'
# lsof +L1

Если увидите там файлы большого размера, то посмотрите, какая служба их держит открытыми и перезапустите её. Обычно это помогает. Если служба зависла, то грохните её принудительно через kill -9 <pid>.

🟢 Ещё один важный момент. Может получиться так, что du оказывает, что всё место занято. В файловой системе напрямую вы не можете найти, чем оно занято, так как сумма всех файлов и директорий явно меньше, чем занято места. Среди удалённых файлов тоже ничего нет. Тогда проверяйте свои точки монтирования. Бывает так, что у вас, к примеру, скриптом монтируется сетевой диск в /mnt/backup и туда копируются бэкапы. А потом диск отключается. Если по какой-то причине в момент бэкапа диск не подмонтируется, а данные туда скопируются, то они все лягут на корневую систему, хоть и в папку /mnt/backup. Если после этого туда смонтировать сетевой диск, то ранее скопированные файлы будут не видны, но они будут занимать место. Ситуация хоть и кажется довольно редкой, но на самом деле она иногда случается и я сам был свидетелем такого. И читатели писали такие же истории. Вот одна из них. Поэтому если скриптом подключаете диски, то всегда проверяйте, успешно ли они подключились, прежде чем копировать туда файлы. Вот примеры, как это можно сделать.

#bash #terminal
​​Расскажу про необычную комбинацию клавиш в консоли bash, о которой наверняка многие не знают, а она иногда бывает полезной. Это из серии советов, когда те, кто про него знают, думаю, что тут такого, зачем об этом писать. А кто-то вообще об этом никогда не слышал.

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

Нажимаем комбинацию Alt+Shif+3. После этого к команде автоматически добавляется в самое начало # и команда как-бы выполняется, но на самом деле не выполняется, потому что стоит #. При этом команда улетает вместе с # в history. После этого её можно там посмотреть, либо быстро вызвать через поиск по Ctrl+R. Если искать по #, то она первая и выскочит.

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

#bash
​​Прошлая заметка про комбинацию в терминале Alt+Shift+3 получила какой-то невероятный отклик в виде лайков, хотя не делает чего-то особенного. Не ожидал такой реакции. Раз это так востребовано, расскажу ещё про одну комбинацию клавиш в bash, про которую наверняка многие не знают, но она куда более полезная на практике по сравнению с упомянутой выше.

Для тех, кто часто работает в bash, наверняка будет знакома история, когда вы что-то запустили в терминале и постоянно появляются новые строки. Вы хотите прочитать что-то, что уже ушло вверх с экрана, но из-за того, что постоянно появляются новые строки, терминал неизменно проматывается в самый низ. Бесячая ситуация, когда несколько раз пытаешься промотать вверх и не получается.

Такое возникает, например, когда ты смотришь логи в режиме реального времени:

# tail -f /var/log/nginx/access.log

C tail ситуация вымышленная, просто для демонстрации того, о чём идёт речь. Нет никакой проблемы прервать tail, прекратить появление новых строк, чтобы можно было спокойно промотать терминал.

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

И тут тебе поможет комбинация Ctrl + s. Она поставит вывод в терминал на паузу. Новые строки не будут появляться, а ты спокойно сможешь проскролить экран до ошибки и прочитать её. Когда закончишь, нажмёшь Ctrl + q, и терминал продолжит работу в обычном режиме.

То же самое происходит, когда в терминале собирает Dockerfile, запускается большой docker-compose и т.д. Невозможно проскролить терминал наверх, пока процесс не завершится. Но если сделать Ctrl + s, то всё получится.

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

#bash
Возникла небольшая прикладная задача. Нужно было периодически с одного mysql сервера перекидывать дамп одной таблицы из базы на другой сервер в такую же базу. Решений этой задачи может быть много. Я взял и решил в лоб набором простых команд на bash. Делюсь с вами итоговым скриптом. Даже если он вам не нужен в рамках этой задачи, то можете взять какие-то моменты для использования в другой.

#!/bin/bash

# Дамп базы с заменой общего комплексного параметра --opt, где используется ключ --lock-tables на набор отдельных ключей, где вместо lock-tables используется --single-transaction
/usr/bin/mysqldump --add-drop-database --add-locks --create-options --disable-keys --extended-insert --single-transaction --quick --set-charset --routines --events --triggers --comments --quote-names --order-by-primary --hex-blob --databases database01 -u'userdb' -p'password' > /mnt/backup/sql/"$(date +%Y-%m-%d)"-database01.sql

# Из общего дампа вырезаю дамп только данных таблицы table01. Общий дамп тоже оставляю, потому что он нужен для других задач
/usr/bin/cat /mnt/backup/sql/"$(date +%Y-%m-%d)"-database01.sql | /usr/bin/awk '/LOCK TABLES `table01`/,/UNLOCK TABLES/' > /mnt/backup/sql/"$(date +%Y-%m-%d)"-table01.sql

# Сжимаю оба дампа
/usr/bin/gzip /mnt/backup/sql/"$(date +%Y-%m-%d)"-database01.sql
/usr/bin/gzip /mnt/backup/sql/"$(date +%Y-%m-%d)"-table01.sql

# Копирую дамп таблицы на второй сервер, аутентификация по ключам
/usr/bin/scp /mnt/backup/sql/"$(date +%Y-%m-%d)"-table01.sql.gz sshuser@10.20.30.45:/tmp

# Выполняю на втором сервере ряд заданий в рамках ssh сессии: распаковываю дамп таблицы, очищаю таблицу на этом сервере, заливаю туда данные из дампа
/usr/bin/ssh sshuser@10.20.30.45 '/usr/bin/gunzip /tmp/"$(date +%Y-%m-%d)"-table01.sql.gz && /usr/bin/mysql -e "delete from database01.table01; use database01; source /tmp/"$(date +%Y-%m-%d)"-table01.sql;"'

# Удаляю дамп
/usr/bin/ssh sshuser@10.20.30.45 'rm /tmp/"$(date +%Y-%m-%d)"-table01.sql'


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

Отдельно отмечу для тех, кто не в курсе, что можно вот так запросто тут же после подключения по ssh выполнять какие-то команды в автоматическом режиме. Это удобно и часто пригождается.

#mysql #bash #script
Как быстро прибить приложение, которое слушает определённый порт?

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

# ss -tulnp | grep 8080
tcp LISTEN 0 5 0.0.0.0:8080 0.0.0.0:* users:(("python3",pid=5152,fd=3))
# kill 5152

Но быстрее и проще воспользоваться lsof:

# lsof -i:8080
COMMAND PID USER  FD  TYPE DEVICE SIZE/OFF NODE NAME
python3 5156 root  3u IPv4 41738   0t0 TCP *:http-alt (LISTEN)
# kill 5152

Или вообще в одно действие:

# lsof -i:8080 -t | xargs kill

Lsof могучая утилита. Я как-то особо не пользовался ей, кроме как для файлов, пока не подготовил заметку год назад с примерами использования. С тех порт и для сетевых соединений стал активно применять. Особенно вот в таком виде:

# lsof -i
# lsof -i TCP:25
# lsof -i TCP@1.2.3.4

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

# killport 8080

В стандартных репах её нет, придётся качать бинарник из репозитория. Если для Linux это не сильно надо, так как там много инструментов для подобных действий, что я продемонстрировал выше, то для Windows это будет более актуально. Killport есть и под винду (❗️) Использовать можно примерно так:

> killport 445 --dry-run
Would kill process 'System' listening on port 445

То есть сначала смотрим, что будет прибито, а потом только делаем.

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

#terminal #bash
Вчера в комментариях к заметке со скриптом речь зашла про шебанг (shebang). Слово какое-то непонятное. Я первые года и Unix админил, и скрипты писал, и слова такого не знал, пока случайно не услышал.

Шебанг - это то, с чего начинается практически любой скрипт:

#!/bin/bash

или так

#!/bin/sh

Интуитивно и так было понятно, для чего это. Конструкцию с sh я использовал, когда работал с FreeBSD. Перейдя на Linux, стал использовать bash. Причём именно в таком виде:

#!/bin/bash

Хотя указывать его можно не только так. Но абсолютно во всех дистрибутивах, с которыми я работал, по /bin/bash был именно bash либо в явном виде, либо символьной ссылкой на него. Так что если не хотите лишний раз забивать себе голову новой информацией, то пишите так же. Вероятность, что что-то пойдёт не так, очень мала. У меня ни разу за всю мою карьеру с таким шебангом проблем на Linux с bash скриптами не было. Исключение - контейнеры. Там отсутствие bash - обычное дело. Но там тогда и скрипты не будут нужны.

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

#!/usr/bin/env bash

Расположение bash будет взято из окружения, в котором будет запускаться скрипт. Так писать дольше, поэтому я такую конструкцию не использую.

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

# bash script.sh

А если не указать явно, то будет использована оболочка по умолчанию. Большая часть скриптов, с которыми я работаю, успешно отработает и в sh, и в bash. Именно эти оболочки наиболее распространены, остальное экзотика для серверов. Есть ещё dash, но, насколько я помню, она полностью совместима с sh.

В шебанге можно указать какие-то нюансы интерпретатора. В основном это актуально для python. Он как минимум может быть разных версий на одной и той же системе. Особенно это было актуально во времена перехода с python2 на python3. Когда был только python2 в шебангах могли указать python и скрипт выполнялся 2-й версией, так как именно она была в системе по умолчанию. Когда в системах стала появляться 3-я версия, для старых скриптов надо было отдельно ставить 2-ю и явно её указывать, либо редактируя шебанги в скриптах, либо запуская нужную версию явно:

# python2 script.py

Из экзотического применения шебангов можно привести примеры с awk или ansible:

#!/usr/bin/awk -f

#!/usr/bin/env ansible-playbook

И awk, и ansible-playbook являются интерпретаторами кода, так что для простого и быстрого запуска скриптов, написанных для них, можно использовать соответствующие шебанги, чтобы сразу запускать скрипт по аналогии с башем:

# ./my-playbook.yaml

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

Строки с шебангом могут содержать дополнительные аргументы, которые передаются интерпретатору (см. пример для awk -f выше). Однако, обработка аргументов может различаться, для переносимости лучше использовать только один аргумент без пробелов внутри. Теоретически это можно обойти в env, но на практике я не проверял. Увидел сам вчера в комментариях:

#!/usr/bin/env -S python3 -B -E -s

#bash
Пройдусь немного по важной теории по поводу работы в консоли bash. Я в разное время всё это затрагивал в заметках, но информация размазана через большие промежутки в публикациях. Имеет смысл повторить базу.

Покажу сразу очень наглядный пример:

# echo --version
--version

# /usr/bin/echo --version
echo (GNU coreutils) 9.1

В первом случае ключ не сработал, а во втором сработал, хотя запускал вроде бы одно и то же. А на самом деле нет. В первом примере я запустил echo из состава оболочки bash, а во втором случае - это бинарник в файловой системе. Это по факту разные программы. У них даже ключи разные.

Узнать, что конкретно мы запускаем, проще всего через type:

# type echo
echo is a shell builtin

# type /usr/bin/echo
/usr/bin/echo is /usr/bin/echo

В первом случае прямо указано, что echo - встроена в оболочку.

И ещё один очень характерный пример:

# type ls
ls is aliased to `ls --color=auto'

Очень часто ls это не утилита ls, а алиас с дополнительными параметрами.

Когда вы вводите команду в терминал, происходит следующее:

1️⃣ Проверяются алиасы. Если команда будет там найдена, то поиск остановится и она исполнится.

2️⃣ Далее команда проверяется, встроена в ли она в оболочку, или нет. Если встроена, то выполнится именно она.

3️⃣ Только теперь идёт поиск бинарника в директориях, определённых в $PATH.

У последнего варианта тоже есть свои нюансы. У вас может быть несколько исполняемых файлов в разных директориях с одинаковым именем. Ситуация нередкая. Можно столкнуться при наличии разных версий python или php в системе. Поиск бинарника ведётся по порядку следования этих директорий в переменной слева направо.

# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

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

И ещё важный нюанс по этой теме. Есть привычные команды из оболочки, которых нет в бинарниках. Например, cd.

# type cd
cd is a shell builtin

# which cd

В выводе последней команды пусто, то есть бинарника cd нет. Если вы будете использовать cd в скриптах, то проблем у вас не будет, так как там обычно в шебанге указан интерпретатор. Обычно это bash или sh, и там cd присутствует.

А вот если вы cd используете в юните systemd, то ничего не заработает:

ExecStart=cd /var/www/ ....... && .........

Вот так работать не будет. Systemd не будет запускать автоматом оболочку. Надо это явно указать:

ExecStart=/usr/bin/bash -с "cd /var/www/ ......."

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

❗️В связи с этим могу дать совет, который вам сэкономит кучу времени на отладке. Всегда и везде в скриптах, в кронах, в юнитах systemd и т.д. пишите полные пути до бинарников. Команда, в зависимости от пользователя, прав доступа и многих других условий, может быть запущена в разных оболочках и окружениях. Чтобы не было неожиданностей, пишите явно, что вы хотите запустить.

#bash #terminal
На всех серверах на базе Linux, а раньше и не только Linux, которые настраиваю и обслуживаю сам, изменяю параметры хранения истории команд, то есть history. Настройки обычно добавляю в файл ~/.bashrc. Постоянно обращаюсь к истории команд, поэтому стараюсь, чтобы они туда попадали и сохранялись. Покажу свои привычные настройки:

Для того, чтобы сразу же сохранять в историю введённую команду, использую переменную bash - PROMPT_COMMAND. Содержимое этой переменной выполняется как обычная команда Bash непосредственно перед тем, как Bash отобразит приглашение. Соответственно, команда history -a сохраняет историю команд этого сеанса в файл с историей.

PROMPT_COMMAND='history -a'

Сохранение метки времени вместе с самой командой и вывод её в определённом формате (2024-09-25 16:39:30):

export HISTTIMEFORMAT='%F %T '

Увеличение числа строк в файле с историей. По умолчанию хранится вроде бы только 500 последних команд, более старые удаляются. Я увеличиваю этот размер до 10000 строк. Ни разу не было, чтобы этого не хватило:

export HISTSIZE=10000

Некоторые команды не сохраняю в истории, так как они не представляют какой-то ценности, только отвлекают и занимают место. Вы можете свой список таких команд вести:

export HISTIGNORE="ls:history:w:htop:pwd:top:iftop"

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

export HISTCONTROL=ignorespace 

Для применения настроек можно выполнить:

# source ~/.bashrc

Посмотреть свои применённые параметры для истории можно вот так:

# export | grep -i hist

Если вы хотите, чтобы эти параметры применялись для всех пользователей сервера, то создайте отдельный файл с настройками, к примеру, history.sh и положите его в директорию /etc/profile.d. Скрипты из этой директории выполняются при запуске шелла пользователя.

❗️Для поиска по истории нажмите сочетание клавиш Ctrl+r и начните вводить команду. Для перебора множественных совпадений продолжайте нажимать Ctrl+r, пока не найдёте то, что ищите. Я лично привык просто грепать, а не искать в истории:

# history | grep 'apt install'

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

⚡️Расскажу про один нюанс, связанный HISTIGNORE, который не раз меня подставлял. У меня в исключениях, к примеру, есть команда htop. Она в историю не попадает. Бывало такое, что я перезагружал сервер командой reboot. Потом загружался и первым делом вводил команду htop, что-то смотрел, закрывал htop. Потом нужно было запустить htop снова. Я на автомате нажимаю стрелку вверх, чтобы запустить последнюю команду, там выскакивает reboot, потому что htop не сохранился, и я тут же жму enter. И сокрушаюсь, что на автомате выполнил не ту команду. Реально много раз на это попадался, правда без последствий. В основном это во время какой-то отладки бывает, там и так перезагрузки идут.

#linux #terminal #bash

🦖 Selectel — дешёвые и не очень дедики с аукционом!
Please open Telegram to view this post
VIEW IN TELEGRAM