- PVSM.RU - https://www.pvsm.ru -

Возникла передо мной задача — автоматически выполнять действие при вставке какого-нибудь storage device в Дебиане. К примеру, просто автоматически монтировать его. А может, монтировать и синхронизировать данные, если устройство известно. А может, проверять его clamav на всякую фигню и запускать на нём что-нибудь типа USB-вакцины. Может, и включить сирену, если владельца рядом с компьютером нет =)
К примеру, есть у меня флешки, штук этак 5-7. На каждой записано что-то своё, одна — загрузочная, одна с документами, одна с программами, одна с музыкой и с фотками… И стоит дома сервер, на котором все-все копии того, что на флешках, есть. Хотелось бы мне, чтобы с одной флешкой синхронизировались документы, с другой — музыка и фотки, с третьей — загрузочные образы, и так далее. Только вот нужно это дело как-то автоматически запускать, потому как не годится каждый раз через SSH на сервер лезть и ручками всё править. Поэтому нужно что-то, где можно было бы флешки прописать и действия, нужные при подключении, задать. А там один раз скрипт синхронизации написал — и готово.
Только вот ничего готового и полностью подходящего в Гугле не нашлось. Часть решений тянут за собой кучу зависимостей и не рассчитаны на headless-установку, часть устарели, часть не имеют всего необходимого функционала, а часть позволяют это сделать, только вот слишком муторно, настраивать трудно, и вообще, NIH ;-) Поэтому написал я свой демон автоматического монтирования, ну и выкладываю его сюда, может, пригодится кому-то ещё, может, такому же владельцу домашнего сервера, который точно так же хочет синхронизировать флешки с домашним NAS. Да и хочется, чтобы кто-нибудь код покритиковал, на ошибки указал и решения некоторых проблем подсказал — смотреть в конце топика.
Был у меня как-то iPod. Иногда возникала необходимость его заряжать от компьютера с Линуксом. Но как только подключаешь его к компьютеру, он сразу включает режим синхронизации. Для того, чтобы он прекратил заряжаться, требуется либо хардварное решение (переходник папа-мама USB с по-особому замкнутыми контактами), либо выполнить eject /dev/sdx1, что выключает режим синхронизации и переводит в режим зарядки. Соответственно, нужно выполнять эту команду каждый раз — паять переходник ведь так лень… тогда и родился такой скрипт — umipod.sh: (UnMount-iPOD)
#!/bin/bash
#Intended to run in background and eject device specified by UUID if plugged
# in.Check is performed every N seconds where N variable is set in script
# header part.
N=10 #Yes, this is delay between checks, best recommended is 10 seconds
# which is just N=10.
UUID="4C25-4DC2"
BEACON="/usr/local/bin/umipod/a.flg"
if [[ $1 = "-a" ]]
then
touch $BEACON
echo "Mounting temporarily allowed"
exit 0
fi
if [[ $1 = "-d" ]]
then
rm -f $BEACON
echo "Device will be ejected "
exit 0
fi
while [ true ]
do
if [[ ! -e $BEACON && -e /dev/disk/by-uuid/$UUID ]]
then
DEVPART=`blkid -U $UUID`
eject $DEVPART
echo "Device ejected"
fi
sleep $N
done
Прошу сильно не плеваться на качество кода, это было написано два года назад, да и дальше в статье будет ещё много кода — берегите слюну =) Суть проста — в цикле проверяем, подключено ли устройство с конкретным UUID, если да, то находим, какое блочное устройство eject'ить и делаем это. Подключил плеер к компьютеру, через пару секунд он автоматически отключается, но продолжает заряжаться — можно дальше слушать музыку. Было достаточно удобно… Пока не потерял плеер =( С тех пор надобность в таком устройстве практически отпала.
А сейчас столкнулся с некоторыми задачами, для которых требуются автоматические действия при подключении носителя. Вот и решил это исправить, только теперь и близко не подходя к Bash =) Язык программирования долго выбирать не пришлось — да и не из чего, честно говоря, я только Python в последнее время и занимаюсь. Дальше пришлось подумать — писать ли демон, который в цикле проверяет, не появилось ли новых ещё необработанных разделов, или цепляться через udev, чтобы программа вызывалась как callback при подключении любой флешки. Второй вариант вроде потребляет меньше ресурсов — но первый не зависит от udev и проще =) Поэтому решил писать демона, которого можно спокойно сунуть в автозапуск.
Собственно, вот он [1]. Об устройстве и возможностях легче всего будет рассказать, описывая то, как его настраивать. Но всё же вкратце опишу принцип работы:
Дальше — уже требует описания конфига =) Конфиг обычно состоит из четырёх секций, любую из которых можно безболезненно пропустить.
{
"globals": {
"interval":3,
"debug":false,
"noexecute":true,
"comment":"Everything that is in this section will be exported as a global variable in the script, replacing if there's something to replace."
},
"exceptions": [
{"uuid":"ceb62844-7cc8-4dcc-8127-105253a081fc", "comment":"System boot partition"},
{"uuid":"6d1a8448-10c2-4d42-b8f6-ee790a849228", "comment":"System root partition"},
{"uuid":"9b0bb1fc-8720-4793-ab35-8a028a475d1e", "comment":"System swap partition"}
],
"rules": [
{"uuid":"E02C8F0E2C8EDEC2", "mount":{"mountpoint":"/media/16G-DT100G2"}},
{"uuid":"7F22-AD64", "mount":{"mountpoint":"/media/16G-DT100G3"}},
{"uuid":"406C9EEE6C9EDE4A", "mount":{"mountpoint":"/media/80G-Music"}},
{"uuid":"52663FC01BD35EA4", "mount":{"mountpoint":"/media/32G-Data"}}
],
"default": {
"mount":true,
"comment":"Configuration section for the actions that are taken if drive isn't in either exception or rule list."
}
}
Тут всё просто — любая переменная в этой секции экспортируется в global namespace демона такой незатейливой функцией:
def export_globals():
log("Exporting globals from config file")
for variable in config["globals"].keys():
if debug:
log("Exporting variable "+variable+" from config")
globals()[variable] = config["globals"][variable]
Просто и быстро, не нужно составлять всякие списки переменных к экспорту или запихивать все переменные в словарь.
Полезные переменные:
main_mount_dir — основная директория для монтирования, когда путь для монтирования неизвестен либо указан в виде относительного пути. По умолчанию — "/media".
default_mount_option — опции при монтировании по умолчанию, когда другие не указаны. Я оставляю там «rw», хоть и не уверен, не прописано ли это уже по умолчанию в драйверах для файловых систем =)
logfile — путь к лог-файлу. Если надо поменять, лучше указывать абсолютный — чёрт его знает, куда попадут логи, если указать относительный =) Скорее всего — в .
interval — интервал между проверками на предмет появления новых разделов. Как по мне — даже интервал в одну секунду не загружает процессор сколь-либо значительно, даже на одноядерном Celeron 900MHz =)
Не столь полезные переменные:
debug — тут всё понятно. Значительно убыстряет увеличение размера лог-файла, в реальной жизни вряд ли понадобится =)
super_debug — убыстряет расширение лог-файла ещё больше. Вероятность того, что понадобится, ещё меньше.
noexecute — опция, которая включена по умолчанию и при которой демон только имитирует вызовы внешних команд — таким образом, с момента первого запуска и до тех пор, пока не будет убрана опция, ничего монтироваться/выполняться не будет. Сделана для того, чтобы до настройки демон не монтировал разделы, которые уже монтируются, используя fstab.
Любой раздел, чей UUID/Label указан в этой секции, будет жестоко проигнорирован. Собственно, основной use case — разделы, которые автоматически монтируются при загрузке системы из fstab. То есть обычные записи в этой секции будут выглядеть так:
"exceptions": [
{"uuid":"ceb62844-7cc8-4dcc-8127-105253a081fc", "comment":"System boot partition"},
{"uuid":"6d1a8448-10c2-4d42-b8f6-ee790a849228", "comment":"System root partition"},
{"uuid":"9b0bb1fc-8720-4793-ab35-8a028a475d1e", "comment":"System swap partition"}
]
Кстати — всякие там ключи типа «comment», естественно, игнорируются. Если не считать трюк с повторным использованием ключей в словаре, это единственная возможность комментировать конфиг-файл в формате JSON =)
Тут описываются правила для особых случаев. К примеру, здесь можно указать правило типа:
{"uuid":"E02C8F0E2C8EDEC2", "mount":{"mountpoint":"/media/16G-DT100G2", "options":"rw,uid=1002,gid=1002"}},
При наличии такого правила раздел с UUID E02C8F0E2C8EDEC2 будет всегда монтироваться по пути "/media/16G-DT100G2", используя опции «rw,uid=1002,gid=1002».
Тут всё просто — если раздел не соответствует записям в двух предыдущих секциях, то именно эта секция отвечает за действие по умолчанию. Обычно содержит просто «mount»:true, ну или «mount»:false.
Как можно видеть, во последних трёх секциях в правилах есть два типа переменных.
Первый тип — переменные для идентификации какого-то конкретного раздела. Их пока три типа — «uuid», «label» и «label_regex».
Второй тип — переменные для обозначения действия. Их тоже три:
Естественно, при обработке секции «exceptions» никакие действия, даже если будут указаны, приняты не будут, а в секции «default» не будут обрабатываться переменные идентификации — смысла нет =)
Добавлю пару примеров правил в конфиге:
{
"globals": {
"interval":1,
"debug":false,
"default_mount_option":"noexec,noatime,rw"
},
"exceptions": [
{"label":"Root", "comment":"System boot partition"},
{"uuid":"6d1a8448-10c2-4d42-b8f6-ee790a849228", "comment":"System root partition"},
{"uuid":"9b0bb1fc-8720-4793-ab35-8a028a475d1e", "comment":"System swap partition"}
],
"rules": [
{"label":"MULTISYSTEM", "mount":{"mountpoint":"/media/MULTISYSTEM", "comment":"Будет конфликтовать, если будет два раздела с одинаковой меткой =( "}},
{"uuid":"7F22-AD64", "mount":{"mountpoint":"/media/16G-DT100G3"}, "command":"/usr/local/bin/sync_dt100g3.sh"},
{"uuid":"406C9EEE6C9EDE4A", "mount":{"mountpoint":"/media/80G-Music"}, "command":"mocp --server; mocp -P", "comment":"Автоматическое проигрывание музыки при "},
{"label_regex":"*iPhone*", "comment":"За правильность регулярки вообще не ручаюсь", "mount":true, "script":"/usr/loca/bin/iphone_factory_restore.sh", "comment":"Учимся делать бекапы ;-) "}
],
"default": {
"mount":false,
"comment":"Ты куда монтируешься, а ну пропуск покажи"
}
}
В итоге — что позволяет этот демон? Кроме того, что он помогает автоматически монтировать разделы на подключаемых устройствах, он может быть использован для:
Сейчас, когда всё написано и полностью равботает, но до конца ещё не отполировано, у меня есть несколько вопросов по улучшению безопасности и соответствию каким-нибудь там выработавшимся за многие годы традициям в написании демонов.
GitHub [1]
Кстати, эту статью можно считать продолжением мной обещанного, но на уже почти год как остановленного курса статей по настройке своего переносного сервера, используя Debian. [2] Собирался сделать что-то такое в рамках реализации своего файлового сервера, только вот тогда не смог найти готового решения… А сейчас написал своё =) В ближайшее время думаю написать ещё пару статей, которые подойдут под тематику этого курса.
Автор: CRImier
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/news/60336
Ссылки в тексте:
[1] вот он: https://github.com/CRImier/pautomount
[2] курса статей по настройке своего переносного сервера, используя Debian.: http://habrahabr.ru/post/188108/
[3] Источник: http://habrahabr.ru/post/220301/
Нажмите здесь для печати.