📜 ⬆️ ⬇️

Pautomount is a daemon for automatic mounting, running scripts and all such other things.

image

There was a task in front of me - to automatically perform an action when inserting some storage device in Debian. For example, just automatically mount it. Or maybe mount and sync data if the device is known. Or maybe check his clamav for all garbage and run something like a USB vaccine on it. Maybe turn on the siren, if the owner is not near the computer =)

For example, I have flash drives, pieces that way 5-7. Each one has something different, one boot, one with documents, one with programs, one with music and photos ... And there is a server at home, on which there are all-all copies of what is on flash drives. I would like the documents to be synchronized with one flash drive, the other with music and photos, the third with boot images, and so on. Only now you need to automatically start this business somehow, because it doesn’t work every time through SSH to climb the server and edit everything with handles. Therefore, you need something, where it would be possible to register flash drives and specify the actions needed when connecting. And there once the synchronization script was written - and it’s done.
')
Only here nothing ready and completely suitable in Google was found. Some solutions pull a bunch of dependencies behind them and are not designed for a headless installation, some are outdated, some do not have all the necessary functionality, and some allow you to do this, only now it’s too dreary, it’s difficult to tune, and in general, NIH ;-) the auto-mount daemon, well, I post it here, maybe it will be useful to someone else, maybe the same home server owner, who just wants to sync flash drives with a home NAS. Yes, and I want someone to criticize the code, pointed out the errors and suggested solutions to some problems - look at the end of the topic.


I had an iPod somehow. Sometimes it was necessary to charge it from a computer with Linux. But as soon as you connect it to the computer, it immediately turns on the synchronization mode. In order for it to stop charging, you need either a hardware solution (a USB daddy adapter with specially closed contacts), or to perform eject / dev / sdx1, which turns off synchronization mode and places it in charge mode. Accordingly, you need to execute this command every time - soldering the adapter is so lazy ... then such a script was born - umipod.sh: (UnMount-iPOD)

umipod.sh
#!/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 


I strongly ask you not to spit on the quality of the code, it was written two years ago, and further there will be a lot of code in the article - save saliva =) The essence is simple - we check in the loop whether the device is connected with a specific UUID, if yes, then find out which block device eject'it and do it. I connected the player to the computer, after a couple of seconds it automatically turns off, but continues to charge - you can continue listening to music. It was quite convenient ... Until I lost the player = (Since then, the need for such a device has almost disappeared.

And now I ran into some tasks that require automatic actions when connecting media. So I decided to fix it, but now I don’t even come close to Bash =) I didn’t have to choose a programming language for a long time - and, frankly, I’m only in Python lately. Then I had to think whether to write a daemon, which in the loop checks if new, still raw partitions appeared, or to cling via udev, so that the program could be called as a callback when connecting any USB flash drive. The second option seems to consume less resources - but the first does not depend on udev and is simpler =) Therefore, I decided to write a demon that can be easily put into autorun.

Actually, here it is . It will be easiest to tell about the device and its capabilities, describing how to configure it. But still briefly describe the principle of operation:

  1. The daemon checks / dev / disk / by-uuid every n seconds, then compiles a list of available partitions from there
  2. This list is compared with a list made n seconds before that, sections are defined that were not there before
  3. If there are newly connected sections, it is determined by the entries from the config file which section to ignore, and for which section to perform some action.
  4. GOTO 1


Next - already requires a description of the config =) The config usually consists of four sections, any of which can be skipped painlessly.

 { "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." } } 


1) Section "globals"


Everything is simple here - any variable in this section is exported to the global namespace of the daemon with such a simple function:

def export_globals ():
 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] 

Simply and quickly, you do not need to make all sorts of lists of variables to export or push all the variables into the dictionary.

Useful variables:
main_mount_dir - the main mount directory, when the mount path is unknown or specified as a relative path. The default is "/ media".
default_mount_option - options when mounting by default when others are not specified. I leave “rw” there, although I’m not sure if this isn’t the default for drivers for file systems =)
logfile - path to the log file. If you need to change, it is better to specify the absolute - the devil knows where the logs will go, if you specify the relative =) Most likely - in \.
interval - the interval between checks for new sections. As for me, even an interval of one second does not load the processor in any way significantly, even on a single-core Celeron 900MHz =)

Not so useful variables:
debug - everything is clear. Significantly speeds up the increase in the size of the log file, in real life is unlikely to need =)
super_debug - speeds up the log file extension even more. The probability of what is needed is even less.
noexecute is an option that is enabled by default and in which the daemon only simulates calls to external commands — thus, from the moment of the first launch and until the option is removed, nothing will be mounted / executed. This is done to ensure that the daemon does not mount partitions that are already mounted using fstab prior to configuration.

2) Section "exceptions"


Any section whose UUID / Label is listed in this section will be severely ignored. Actually, the main use case are partitions that are automatically mounted when the system is loaded from fstab. That is, the usual entries in this section will look like this:
  "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"} ] 

By the way, all sorts of “comment” type keys are naturally ignored. Except for the key reuse trick in the dictionary, this is the only way to comment the config file in JSON format =)

3) Section "rules"


Here are the rules for special cases. For example, here you can specify a type rule:

  {"uuid":"E02C8F0E2C8EDEC2", "mount":{"mountpoint":"/media/16G-DT100G2", "options":"rw,uid=1002,gid=1002"}}, 


With such a rule, the partition with the UUID E02C8F0E2C8EDEC2 will always be mounted along the path "/ media / 16G-DT100G2" using the options "rw, uid = 1002, gid = 1002".

4) Section "default"


Everything is simple - if the section does not match the entries in the two previous sections, then this section is responsible for the default action. Usually it just contains “mount”: true, or “mount”: false.

rules


As you can see, in the last three sections of the rules there are two types of variables.
The first type is variables to identify a specific section. There are three types of them so far - “uuid”, “label” and “label_regex”.

The second type is variables to denote action. They are also three:

Naturally, when processing the “exceptions” section, no actions will be taken, even if specified, and identification variables will not be processed in the “default” section - there is no sense =)

I will add a couple of examples of rules in the config:

 { "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.*", "mount":true, "script":"/usr/loca/bin/iphone_factory_restore.sh", "comment":"   ;-) "} ], "default": { "mount":false, "comment":"  ,    " } } 


What for?


In the end - what allows this demon? In addition to helping to automatically mount partitions on connected devices, it can be used to:
  1. Synchronize files on removable media with, for example, a home server on Linux
  2. Peculiar usb-modeswitch, remember the same situation with the iPod
  3. Virus media checks
  4. Merging data from someone's iPhone / Android / flash drives / cameras quietly =)
  5. Samba-ball creations to access media over a network


Now, when everything is written and fully worked out, but not yet fully polished, I have a few questions on how to improve security and conformity to the traditions of writing demons that have developed over many years.

  1. Is it possible to constantly keep this demon under the root? The problem is that you need to have the privileges to run all commands - both mount and execute external scripts, among which may be the same rsync, for example. The security of the configuration file is from the same opera. If the script is launched under the root, and a command is written in the config that accidentally erases the MBR & MFT, it will be fun.
  2. To execute scripts and commands, definitely, in the near future, it will be necessary to make mandatory execution in the background, in a separate thread — otherwise, any command that will work longer than a couple of seconds will stop the whole demon. In principle, the same situation can occur when mounting. But there is a dilemma - if you mount in the background, then the command or script can be executed before the mount, such is race condition = (I think to separate the processing of each section into a separate thread, so there will be neither race condition nor hanging, if mount hangs from for the fact that you need to fix the FS.
  3. Does the daemon need to go into the background itself or is it optional? So far, it is arranged this way - the demon does not know how to leave for the background mode itself, for it it makes a start-stop-daemon in the init-script.
  4. How on the way to the block device (/ dev / sdxZ) the easiest and fastest way to check whether it is mounted? Preferably using standard Python modules, well, or at most - using external commands. Then you can get rid of the problems associated with double mounting of the same partition. Parse the mtab and compare the UUID with the paths to the block devices - the task is still that ;-)
  5. Should I generate an exception list from fstab before the first run, or can I rely on a user who handles it once?
  6. When launching external scripts, you should keep in mind possible special characters in the partition label, such as accidentally getting there "&& rm -rf /" (it will work when you run an external script) and "../../etc/" (can mount a section instead of "/ etc ") ;-) Question - what special characters need to be filtered to fully protect this hole? Immediately come to mind "&", "/" and ";", but there may be more.


Github
By the way, this article can be considered a continuation of what I promised, but for almost a year now, as a stopped course of articles on setting up your portable server, using Debian. I was going to do something in the framework of the implementation of my file server, but only then I could not find a ready-made solution ... And now I wrote my own =) In the near future I think to write a couple more articles that fit the subject of this course.

Source: https://habr.com/ru/post/220301/


All Articles