NFS - Automate attaching / detaching of shares
From Blue-IT.org Wiki
Contents
- 1 History
- 2 Other Links
- 3 Prerequisites
- 4 Prepare your workstation / server
- 5 The mount / umount script
- 6 Run the script
- 7 On server: Using udev to mount/umount drives
- 8 Test everything
- 9 Discussion
- 10 Troubleshooting
History
- --Apos 21:51, 30 August 2007 (CEST) Changed username udevmount to netmount.
- --Apos 00:55, 27 August 2007 (CEST) All parts working. Verified on my working pc (ubuntu feisty), server (ubuntu feisty) and laptop (ubuntu feisty).
Other Links
- Ubuntuforums: Automatically mount and unmount network shares
- Automatically mounting and unmounting Samba/Windows shares with CIFS
- Symlinks for umountnfs / sendsigs wrong: hang on shutdown / reboot
- n-m should not tear down interfaces during shutdown
- Debian Bug report logs - #367944 initscript: /etc/rc(0|6).d/S20sendsigs kills wpa daemon before umounting nfs mounts
Prerequisites
Prerequisites
- Administrative privileges to the system
- Profound knowledge of
- user administration,
- bash editing,
- nfs admnistration
- The working directory for scripts is
/root/bin
- Everything was implemented and tested on ubuntu systems, a network with three pc's.
- In principle scalable to any system. Each nfs client is first pinged and then handles mount/umount commands itself via ssh.
- Requires a new unpriviledged user on each client. This user is in the sudoers list, allowed to do the mount and unmount command. This user cannot (!) be logged in, it has no password. Only key-autentification works, thereby permitting passwordless ssh connections -on and only those - from server to client.
- This solution requires that both, nfs client and server are permanently connected through the network.
The intention for this HowTo was the problem, that in case you are removing an exported - and mounted - nfs file system from a network this will have severe side effects on the clients:
- All applications depending on the file system will fail / hang
- The desktop manager which will be left in an unusable state for minutes
- Drives will not be synced, data loss can occur
Solution: Publisher / Subscriber Pattern
If a nfs exported filesystem is removed from the nfs server, it must also be removed on all computers that are connected to the nfs server.
The server therefore has to have a list of these computers. In speach of the publisher/subscriber pattern he is the publisher.
The nfs clients are the subscribers. They subscribe to the server. The administrator is doing this in our approach by writing them into the servers clientlist.
To provide mechanisms to add or remove files on the clients, the server must be able to connect somehow to the clients - and that's the problem of most of the current nfs servers implementations: Mostly, just the clients connect in certain time intervals to the nfs servers in the network and then wait fore a certain amount of time for the drive to reappears.
The publisher/subscriber pattern however wants the subscribers (clients) to be reported immediately, when the system state changed, e.g. a drive is unmounted.
And remember the following: any PC can be publisher and/or subscriber. So a mixed setup with more than one nfs server is no problem. Each server cares for it's own clients, no matter what other servers do.
On my home's setup, my server PC exports shares covering a backup drive via USB and a home directory. The second one is a laptop and exports his desktop. The third one is the working horse and exports music and video data. Each server pc cares for his clients, he takes care for unmounting his exported shares on its addicted clients.
Prepare your workstation / server
First open an editor
Login as root open the following files with your favorite editor.
gedit /etc/fstab \ /etc/exports \ /root/bin/nfs_clientlist.sh \ /root/bin/mount_nfs_exports_on_attached_clients \ /root/bin/mount_nfs_on_client \ /root/bin/nfs_udev_mount.sh
These are the files we will alter/write. The last two have to be made executable via chmod 755.
/etc/exports
The /etc/exports delivers every information we need.
- nfs share's mountpoints
- possibly connected clients
It is easy to extract all exported shares of the nfs server with a little bash and awk charm and store it in a bash variable called EXPORTS (we will integrate this charm into a bigger script later):
EXPORTS=$(cat /etc/exports | grep -v ^# | grep " " \ | cut -f 1 | awk '{system("echo -n " $1 "----")}' \ | sed s/----/" "/g)
It filters out all commentary lines, all empty lines, and writes the extracted directory names - devided by spaces - into the string $EXPORTS.
On server: Create a clientlist
It could be possible, to extract information out of the /etc/exports with a little bash magic. Feel free to do this. It also might be possible to write all this in perl ;) Sice now I found no time to do this. Drop me a mail, if you have an idea..
In this solution I hold the variables of the nfs clients in a simple configuration file, called nfs_clientlist.sh.
There is also a $HOSTNAME line. If you just have one network card, configured correctly your /etc/hosts and this hostname is used for your nfs server network wide, then you can leave this alone.
In my case, I use 2 network cards with different names, so I need this line.
cd /root/bin vim nfs_clientlist.sh
############################################################ ### PLEASE EDIT ############################################ ############################################################ # All CLIENTS that use the nfs server on this machine CLIENTLIST="client1 client2 client3" # The hostname of this machine. # Might be different with more than on network card. #THISHOST=myServerName THISHOST=$HOSTNAME ############################################################
For the nfs servers machine to be able to trigger the mount/unmount on connected clients there might be two way's:
- Writing a client/server program that communicates on a special port.
- Issuing a bash mount/umount via ssh on the remote machine
The first approach might be possible in e.g. perl/pyhton, but for my purposes connection/reconnecting via ssh fits my needs.
Prepare the clients in the network
To be able to trigger the mount/unmount on each client we need a user has the following abilities:
- sudo for mount/umount
- passwordless login via ssh from nfs server
Some of you could fear if they hear passwordless login. But this is only for a special unpriviledged user that has no password. So ssh logins are only possible via su -c (root) and if the client has the appropriate encrypted ssh-key from the server. No one, even not on localhost can login as this user.
On client: Create an unpriviledged user with ssh
Log in as root and create an unpriviledged user netmount with a unique user id (in my case 130) and give it the password netmount. Because we enable passwordless login via ssh a secure password makes no sin. Afterwards we create the ssh keys. Please press just ENTER until the key generation is finished. We do not use a ssh password!
useradd -g 0 -m -u 130 -s /bin/bash netmount \ passwd netmount
Next we need a standard unix password. Later we will remove it. We login
su netmount -c /bin/bash
As root again, add the user to the sudoers file:
visudo
Add the two lines
netmount ALL=(ALL) NOPASSWD: /bin/mount netmount ALL=(ALL) NOPASSWD: /bin/umount
In the next step, we create the ssh keys. You can skip this, the script of the next section automatically checks, if an corresponding key exists.
If you like to create a new on, press Enter two times without creating a password for the ssh-key when asked so:
ssh-keygen -t dsa exit
Prepare the server
On server: Export the ssh keys to each client
On each nfs server you have to attach the generated public key to the authorized_keys2 file of the client you want to connect to.
- Switch on all nfs client pc's.
- Don't forget to edit your nfs_clientlist.sh in advance. The following script uses this file to connect to all clients.
vim /root/bin/nfs_clientlist.sh
- On server NOW login as user netmount:
su netmount -c /bin/bash
Use a little helper script to create and distribute the ssh keys.
cd /root/bin vim ssh_helper.sh
The script will perform all tasks for you:
- Connection to the client via ssh
- Checking, if a previous id_dsa file on the client exists
- Checking, if the connection fails due to changed keys
If something goes wrong and you only have a few computers than the best is, to remove every /home/netmount/.ssh directory on every pc and start over from scratch.
#!/bin/bash # cat <<EOF --------------------------------------------------------------------- This scripts exports THIS computers public ssh key to a clients's ~/.ssh/authorized_keys2 file. This will enable passworless login from THIS pc to the client. You need to specify at least the IP/alias of the other pc! You optionally can specify another username than the actual one. Usage: ssh_export ip_of_remote_client [alternate_username] --------------------------------------------------------------------- EOF client="$1" NAME="$2" THIS_HOSTNAME="$HOSTNAME" [ "$NAME" ] || NAME="${USER}" [ "$client" ] || echo "ERROR: You have to specify at least a client IP or alias." [ "$client" ] || exit 1 cd ~/.ssh echo -n "* Check connection to client ... " if ping -c 1 $client > /dev/null then echo OK. else echo Please check the connection. echo - Aborting here. exit 1 fi echo "* Create ssh dir on client, if it not already exists ... " ssh ${NAME}@$client "mkdir -p ~/.ssh; chmod 700 ~/.ssh" echo "* Check if a local pub key exist and/or create one ... " PUB_KEY="id_dsa.pub" if test -e $PUB_KEY then echo " - A file named $PUB_KEY exists." else echo " - Creating a new one." ssh-keygen -t dsa fi echo "* Check, if an older key was exported already in former time to the client." if ssh ${client} "if (cat ~/.ssh/authorized_keys2 | grep ${NAME}@${THIS_HOSTNAME});\ then echo - WARNING: An older key was exported before to $client. \ \n - Please fix manually.; exit 1; \ fi"; then echo " - Authorized keys file is clean." else echo " - Aborting here." exit 1 fi echo "* Copy the public key of THIS pc to the client ... " scp id_dsa.pub ${NAME}@$client:~/.ssh/id_dsa.pub_${HOSTNAME} echo "* We make an entry into the authorized_keys file on the client ... " ssh ${NAME}@$client "cat ~/.ssh/id_dsa.pub_${HOSTNAME} >> ~/.ssh/authorized_keys2; \ rm ~/.ssh/id_dsa.pub_${HOSTNAME};" echo "* Secure the local public key ... " chmod 600 id_dsa.pub echo ; echo "* LET'S TEST IT:" echo " Now we test with running the following terminal command." echo " You should NOT be prompted by a password." echo ; if ssh ${NAME}@$client "echo - This is a message on $client." then echo " - Congratulation: If you was NOT promptd for a password," echo " you can login passwordless to your client $client." else echo " - WARNING: There was an error with the passwordless login to $client." exit 1 fi echo ; echo ; echo "** Program ended." echo ; echo If you like to remove the automatic login, you have to echo remove the public key in the file /home/${NAME}/.ssh/authorized_keys2 echo on your clients - $client - computer
Execute the script:
sh ssh_helper.sh
Test it
If you now try to connect to your client via ssh you should not be prompted for a password.
If not, then something went wrong. In this case please first repeat all steps within the ssh-key chain and assure this will work!
Back on prepared clients
On client: Remove the password of user netmount
Since we have passwordless ssh connection from the server to each client, we don't need and don't want a login for the user netmount on a local client.
So on every client do (as root)
sudo passwd -d netmount
This assures, that only servers, that are authenticated via the ssh-keychain can contact the client.
If you need to recover the ssh authentication you have to add a pasword (sudo passwd netmount again and proceed as before.
The mount / umount script
Naming conventions for mountpoints
On the clients the /etc/fstab should look like this
# on client vim /etc/fstab
[...] nfs_server:/media/MySpecialHarddisk /media/MySpecialHarddisk nfs defaults 0 0
On the server the /etc/fstab should mount the device the same way accordingly:
# on nfs server vim /etc/fstab /dev/disk/MySpecialHarddisk /media/MySpecialHarddisk ext3 \ rw,noauto,sync,noexec,users 0 0
Remark the mountpoint /media/MySpecialHarddisk that has to be identical on all pc's in the network for this device.
In the next section you'll find a script, that helps you easily configure your system according to this needs.
Create the fstab, exports, udev entries
- You can use the following script, but you should first read the whole article. Then you will be able to understand, what this script provides you with - and why.
The naming convention for equal names in fstab, exports and - later - udev.rules could lead to less flexible mountpoint names. Dealing with symlinks could fix the problem.
On the other side this could lead to a network wide unification, which isn't such bad thing.
The next script will help integrate a new drive in your network. Run it in a terminal, it gives you templates to copy and paste into the appropriate configuration files.
- This script will change NO files.
cd /root/bin vim add_nfs_device.sh
#! /bin/sh # # add_nfs_device.sh # # Add a new udev device on the nfs server # that matches the naming conventions ################################################# #### EDIT TO FIT YOUR NEEDS ##################### ################################################# # Used for devicename, symlink, mountpoint, export NAME_OF_DEVICE="Tevion_1_240MB" # Udev params UDEV_SUBSYSTEMS="usb" UDEV_KERNEL="sd?a" ATTRS_SERIAL="200501CAF55" ENV_PRODUCT="67b/3507/1" FILE_UDEV=/etc/udev/rules.d/10-local.rules # local fstab FILE_FSTAB=/etc/fstab FSTAB_MOUNT_DIR=/media FSTAB_FILESYSTEM="ext3" #FSTAB_MOUNT_OPTS="rw,noauto,sync,dirsync,noexec,nodev,noatime,users" FSTAB_MOUNT_OPTS="rw,noauto,rsize=8192,wsize=8192,timeo=14,intr" FSTAB_BOOT_OPTS="0 0" # nfs exports FILE_EXPORTS=/etc/exports NFS_OPTIONS="rw,sync,no_root_squash,no_subtree_check" CLIENTLIST="ibmr31" # Overwritten, when /root/bin/nfs_clientlist.sh exists ################################################# #### Lets go #################################### ################################################# echo "#-----------------------------------------" echo "# Writing the udev rule" echo "#-----------------------------------------" cat - <<EOF # added by `pwd`/$0 # External ${UDEV_SUBSYSTEMS} device - ${NAME_OF_DEVICE} ACTION=="add", SUBSYSTEMS=="${UDEV_SUBSYSTEMS}", KERNEL=="${UDEV_KERNEL}", ATTRS{serial}=="${ATTRS_SERIAL}", NAME="${NAME_OF_DEVICE}", SYMLINK:="disk/${NAME_OF_DEVICE}", GROUP="users", MODE="660", RUN+="/root/bin/udev_mount.sh $env{ACTION} '${NAME_OF_DEVICE}' &" ACTION=="remove", ENV{PRODUCT}=="${ENV_PRODUCT}", RUN+="/root/bin/udev_mount.sh $env{ACTION} '${NAME_OF_DEVICE}' &", OPTIONS=="ignore_remove" EOF echo "#-----------------------------------------" echo "# fstab" echo "#-----------------------------------------" cat - <<EOF # added by `pwd`/$0 # External ${UDEV_SUBSYSTEMS} device - ${NAME_OF_DEVICE} /dev/disk/${NAME_OF_DEVICE} ${FSTAB_MOUNT_DIR}/${NAME_OF_DEVICE} ${FSTAB_FILESYSTEM} ${FSTAB_MOUNT_OPTS} ${FSTAB_BOOT_OPTS} EOF echo "#-----------------------------------------" echo "# exports" echo "#-----------------------------------------" # source CLIENTLIST if exit if test -e /root/bin/nfs_clientlist.sh then . /root/bin/nfs_clientlist.sh fi NFS_STRING=${FSTAB_MOUNT_DIR}/${NAME_OF_DEVICE} for client in $CLIENTLIST do NFS_STRING="${NFS_STRING} ${client}(${NFS_OPTIONS})" done # write the file cat - <<EOF # added by `pwd`/$0 # External ${UDEV_SUBSYSTEMS} device - ${NAME_OF_DEVICE} ${NFS_STRING} EOF
Edit header of the script to your needs and run it in a terminal.
sh add_nfs_device.sh
the output will provide you with the necessary config file entries. What remains now is to add the device into the fstab on the attached nfs client.
On client: Write the essential mount / umount script
Two scripts will trigger the mount/umount on each client.
They are called with the parameter [mount|umount]. The callers are e.g. an init script (etc/init.d/...), udev (rules file) or it will be called manually on login/logout (e.g. of your desktop manager)or whatever else you can imagine. We will look at this piont later.
First
For now let's write the first script down. It is just a wrapper for calling the real worker, that is doing the mount/umount work on each client:
cd /root/bin vim mount_nfs_exports_on_attached_clients
#! /bin/bash # # /root/bin/mount_nfs_exports_on_attached_clients. # Umount/mount all exported nfs mount on attached clients. # Do an appropriate fstab entry on these clients. # # The used mount directories AND client names MUST # match the udev and fstab entries # on thes machines - which should be the same - exactly. export HOME=/root export USER=root export PATH="$PATH:/usr/local/bin:/sbin/:/usr/bin:/usr/sbin/:/bin" . /root/bin/nfs_clientlist.sh # Info (-t) for logger ME="mount_nfs" # check the command line parameter MOUNT="$1" if [[ ${MOUNT} =~ [mount|umount] ]] then logger -t $ME "STARTING: $THISHOST" logger -t $ME "CLIENTLIST: $CLIENTLIST" else logger -t $ME "ERROR: only mount or umount as parameter are allowed." \ exit 1 fi # force remount on network CLIENTLIST for client in $CLIENTLIST do /bin/bash /root/bin/mount_nfs_on_client ${MOUNT} ${client} & done
Second
The second script does the real work:
vim mount_nfs_on_client
#! /bin/bash # # /root/bin/mount_nfs_on_client # Umount/mount all exported nfs mounts on the attached clients. # Do an appropriate fstab entry on these clients. # # The used mount directories AND client names MUST # match the udev and fstab entries # on thes machines - which should be the same - exactly. export PATH="$PATH:/usr/local/bin:/sbin/:/usr/bin:/usr/sbin/:/bin" . /root/bin/nfs_clientlist.sh # Next line grabs all nfs exports # We have to take care for the mount order. # "sort" is doing that for us. EXPORTS="" EXPORTS_MOUNT="$(cat /etc/exports | grep -v ^# | grep " " | \ cut -f 1 | sort | awk '{system("echo -n " $1 "----")}' | \ sed s/----/" "/g)" EXPORTS_UMOUNT="$(cat /etc/exports | grep -v ^# | grep " " | \ cut -f 1 | sort | sort -r | awk '{system("echo -n " $1 "----")}' | \ sed s/----/" "/g)" # Info (-t) for logger ME="mount_nfs_client" # check the command line parameter MOUNT="$1" if [[ ${MOUNT} ]] then logger -t $ME "STARTING: $THISHOST" else logger -t $ME "ERROR: mount parmam not defined." \ exit 1 fi CLIENT="$2" if [[ ${CLIENT} ]] then logger -t $ME "CLIENT: $CLIENT" else logger -t $ME "ERROR: a client has to be attached." \ exit 1 fi # Manage exports if [ "${MOUNT}" = "mount" ] then EXPORTS=" $EXPORTS_MOUNT " else EXPORTS=" $EXPORTS_UMOUNT " fi if [ "${MOUNT}" = "umount" ] then EXPORTS=" $EXPORTS_UMOUNT " fi logger -t $ME "EXPORTS: $EXPORTS"
sync # force remount on network CLIENT if ping -c2 $CLIENT >> /dev/null then for export in $EXPORTS do if [ "${MOUNT}" = "mount" ] then logger -t $ME "Trying mount..." # Check, if subdirectories exist (nfs can also mount subdirs!) if test -d "${export}" then logger -t $ME "Trying mount _${export}_ on _${CLIENT}_..." # For removable devices check, if they are # not already mounted on the client su netmount -c "/usr/bin/ssh netmount@${CLIENT} \ '/bin/mount | cut -d \" \" -f1 | grep -x ${THISHOST}:${export} \ || sudo /bin/mount ${THISHOST}:${export} \ && echo ${THISHOST}:${export} already mounted;'" \ | logger -t $ME fi fi if [ "${MOUNT}" = "umount" ] then logger -t $ME "Trying umount..." su netmount -c "/usr/bin/ssh netmount@${CLIENT} \ 'sync;sudo /bin/umount ${THISHOST}:${export}'" \ | logger -t $ME fi done fi
Make both scripts executable by everyone, but editable only by root:
chmod 755 mount_nfs_* chmod root:root mount_nfs_*
Run the script
Test the script
Login as root. By simply running (as root)
/bin/bash /root/bin/mount_nfs_exports_on_attached_clients [u]mount
in a terminal on the server, you can test, if everything works. On your clients, the nfs shares should be mounted/unmounted. Prove on the client with e.g.
watch -n2 df -h
and syslog
tail -f /var/log/syslog
On server: Call the script on boot
To trigger all attached clients of the system, you should call the script in your systems shutdown or boot scripts. On my ubuntu system this works like this:
/etc/init.d/rmnologin
E.g. call the script in the file /etc/init.d/rmnologin. It is the last script called on the system before the login prompt appears and therefore a good choice for every runlevel:
vim /etc/init.d/rmnologin
Add the line
# Mount nfs shares on connected pcs /bin/bash /root/bin/mount_nfs_exports_on_attached_clients mount
- Pay attention that you are calling the script with /bin/bash, the borne again shell and no other one. I encountered trouble e.g. with ubuntu's dash (symlinked to /bin/sh) bacause I am using some special bash features like [[ ]].
- Pay attention to the parameter mount to call the script. This mounts all shares on this PC according to fstab.
/etc/rc.local
Running the script via /etc/rc.local didn't work for me, because network connections where not aware when running this script.
On server: Call the script on shutdown
Edit a shutdown runlevel script
Unfortunatly there is not runlevel script for shutdown on ubuntu.
So I devired the rc.local accordingly:
vim /etc/init.d/rc.shutdown
#! /bin/sh PATH=/sbin:/bin:/usr/sbin:/usr/bin [ -f /etc/default/rcS ] && . /etc/default/rcS . /lib/lsb/init-functions do_stop() { if [ -x /etc/rc.shutdown ]; then log_begin_msg "Running local shutdown scripts (/etc/rc.shutdown)" /etc/rc.shutdown log_end_msg $? fi } case "$1" in start) # DO nothing ;; restart|reload|force-reload) echo "Error: argument '$1' not supported" >&2 exit 3 ;; stop) do_stop ;; *) echo "Usage: $0 stop" >&2 exit 3 ;; esac
Now edit the script file, which will be called from the above init script when shutdown occurs:
vim /etc/rc.shutdown
#!/bin/sh -e # # rc.local # # This script is executed when machine is shutting down or entering an runlevel # without network support. # # Make sure that the script will "exit 0" on success or any other # value on error. # # In order to enable or disable this script just change the execution # bits. # # By default this script does nothing. # Unmount nfs shares on attached clients /bin/bash /root/bin/mount_nfs_exports_on_attached_clients umount exit 0
Update the runlevels
And add it to the runlevels (debian way)
update-rc.d -f rc.shutdown defaults
A more restrictive way to do this is
update-rc.d rc.shutdown start 01 2 3 4 5 . stop 01 0 1 6 .
The last approach assures that the script starts before all others.
This works out of the box. If not see section troubleshooting at the end of this article.
You can try, if your script works with
/etc/init.d/rc.shutdown stop
On server: gdm configuration
My laptop is shutting down too fast, so the network connection is lost, before the nfs clients could be contacted. The problem also occurs, when using NetworkManager for network connections (e.g. wireless card).
gdm shutdown / reboot / suspend / hibernate
In general the file
/etc/gdm/gdm.conf
is responsible for the reboot and halt commands:
[...] RebootCommand=/sbin/shutdown -r now "Rebooted via gdm." HaltCommand=/sbin/shutdown -h now "Shut Down via gdm." SuspendCommand=/usr/sbin/pm-suspend HibernateCommand=/usr/sbin/pm-hibernate [...]
You can write a simple wrapper script and call the commands mentioned in the section before to unmount nfs shares before shutting down the machine.
The same you can do for suspend and hibernation.
gdm logout/login
You can then try to trigger the mount/umount script via the gnome login/logout scripts which you find in the following directories
# Run something ... # ...before the desktop mangager session starts: /etc/gdm/PreSession/Default # ... after the login: /etc/gdm/PostLogin/Default # ... after the gnome session ends # e.g. pressing the logout button /etc/gdm/PostSession/Default
Put the following command at the beginning of these files and change use mount and umount for whatever is needed:
/bin/bash /root/bin/mount_nfs_exports_on_attached_clients mount /bin/bash /root/bin/mount_nfs_exports_on_attached_clients umount
On server: Using udev to mount/umount drives
Purpose:
- You want to share removable devices
If you don't use removable devices, like an USB backup drive or hot swappable drives, than you can skip the next paragraphs.
Therefore we have to consider additional prerequisites:
- Editing special udev rules for the actions add/remove
- Unique naming conventions for udev-rules and fstab
Udev Rule for an external USB drive
The udev rule we intend to write has to take care of
- Giving the device to be mounted a unique name
- Triggering our mount_nfs_shares... script according to a remove or adding of the device
- Rejecting a buggy udev behaviour, that unpredictably fires on remove actions
The following udev rule suffices this:
vim /etc/udev/rules.d/10-local.rules
Unfortunately there is no variable handling in udev rules. So we have to specify the name of our device two or three times. This name must exactly match the entry in the /etc/fstab !!! Use the script I provided earlier in this article to produce convenient templates (section Create the fstab, exports, udev entries):
# LOCAL UDEV-RULES # # You can get infos about devices with # # e.g. udevinfo -a -p $(udevinfo -q path -n /dev/sda) # udevmonitor --env | grep PRODUCT # External USB 3,5 MySpecialHarddisk ACTION=="add", SUBSYSTEMS=="usb", KERNEL=="sd?1", \ ATTRS{serial}=="200501CAF55", NAME="MySpecialHarddisk", \ SYMLINK:="disk/MySpecialHarddisk", GROUP="users", MODE="660", \ RUN+="/bin/bash /root/bin/nfs_udev_mount.sh $env{ACTION} 'MySpecialHarddisk' &" ACTION=="remove", ENV{PRODUCT}=="67b/3507/1", \ RUN+="/bin/bash /root/bin/nfs_udev_mount.sh $env{ACTION} 'MySpecialHarddisk' &", \ OPTIONS=="ignore_remove"
Problems with ACTION remove
The add action was no problem. On remove action however, the ATTRS{} key for the device is not avaiable anymore, when the device was removed. As an effect, the remove action of the udev rule is never called.
It took me over a year to figure a trick out, to handle this problem: instead of ATTRS{} use the product id of the device, which will be available all the time.
With
udevmonitor --env | grep PRODUCT
you will get the PRODUCT_ID that matches the disconnected device. I is used with "ENV{PRODUCT}" in the udev rule.
Reload the rules
udevcontrol reload_rules
Write the mount / umount script for udev
As you can see, this latter udev rule triggers a script called nfs_udev_mount.sh. It is responsible for
- mount local devices
- calling the main nfs_mount script
- takes care or the buggy udev when removing devices
Since until now (August 2007) udev/hal fires unpredictable on removing a device. this means, that an attached script will be executed several times.
Therefore i blocked the script execution for 15 seconds (alter BLOCK_FOR_SECONDS to change this behavior). This should be enough on any system but is not a save solution for the problem. I hope further versions of udev/hal will fix this.
This script takes two parameters, that are triggerd within the udev rule:
- the action that occurs: remove->umount, add->mount
- the name of the device: mount/umount according to /etc/fstab
#! /bin/bash # # /root/bin/nfs_udev_mount.sh # mount tevion according to /etc/udev/rules.d/10-local.rules # Do an appropriate fstab entry, so mount -a # will do the rest export PATH="$PATH:/usr/local/bin:/sbin/:/usr/bin:/usr/sbin/:/bin" ACTION=$1 NAME=$2 ME=udev_nfs_mount LOCKFILE=/tmp/udev_mount.lock TIMESTAMP=`cat $LOCKFILE` myTime=`date +%s` BLOCK_FOR_SECONDS=15 MOUNT_DIR=/media MOUNTPOINT=${MOUNT_DIR}/${NAME} NFS_MOUNT_SCRIPT=/root/bin/mount_nfs_exports_on_attached_clients # Troubleshooting of fireing hal - udev events test -f $LOCKFILE || echo $myTime > $LOCKFILE if [ $myTime -lt $TIMESTAMP ] then #logger -t udev_mount.sh "exiting ..." exit 1 else #logger -t udev_mount.sh "setting new timestamp ..." expr $myTime + $BLOCK_FOR_SECONDS > $LOCKFILE fi ################################################### # NOW LETS go logger -t $ME "${NAME} ${ACTION} OK" # -------------------------------------------------- # mount local devices if [ "$ACTION" == "add" ] then if mount | grep ${MOUNTPOINT} then echo already mounted ... else mkdir -p ${MOUNTPOINT} mount ${MOUNTPOINT} # force remount on network clients /bin/bash ${NFS_MOUNT_SCRIPT} mount fi fi if [ "$ACTION" == "remove" ] then # force umount on network clients /bin/bash ${NFS_MOUNT_SCRIPT} umount #The following is a bad idea, cause udev handles this #umount -f /media/${NAME} fi logger -t $ME "$ACTION of $NAME finished."
For security reasons do
chmod 755 nfs_udev_mount.sh chmod root:root nfs_udev_mount.sh
Test everything
Add or remove (hotswap) a device on the server, and look what your clients are doing. Shutdown or reboot your nfs server. On all your clients should seamlessly appear/disappear the devices.
Open a terminal on the client and watch what happens:
watch -n2 df -h
What says syslog?
tail -f /var/log/syslog
What says udev?
udevmonitor --env
No desktop manager should hang anymore when shutting down your server or adding / removing your backup device ;)
Discussion
Dataloss
This approach will not hinder dataloss. If you opened a document, and shut down the nfs server, the document will be lost. Also the application might hang and the desktop manager. So this scenario is not handled in any way, and cannot be - ever - because the nfs server never ever will be aware, which recourse might use it's file system.
I tested the worst case with openoffice 2.2, with recovered the crashed document read-only after the next boot of the nfs server - that is better than a lost document.
And: i did not have to remount my nfs share by hand - that is a lot of comfort ;)
Clientlist
There is just one nfs clientlist for each server. This issue might be solved, if I find the time to extract each client out of the exports file.
Time critical
The scripts are a little bit time critical. They assume, that all clients/servers are running and the network connections are o.k. There are no further checks.
Each client is pinged two times before the ssh command will be executed. It takes a few seconds to mount / remount the devices, but it is done for each client in an a separate process.
The whole procedure takes about a second for each share. I messure about 3-5 seconds for 5 shares each of my pc's.
Ping security risc
One is the fact, that ping is used to detect running clients. This might be a problem in secure networks, where ping is not enabled, or firewalls are blocking it.
But consider: waiting for an unsuccessful ssh command to response is more time consuming.
User netmount security risc
Another is, that the netmount user could be a security whole. The only way to decouple this, is to write a real wrapper or client/server application, that runs's as root and tunnels this aspect.
But consider
- netmount has no password, only ssh authenticated servers can connect to an authenticated client. To install the ssh authentication you need root access on the client/server and install a password.
- You cannot login with the user netmount, not even from localhost.
- All scripts are possessed by root:root and can only be written by root:root
Not handy for big networks
For the system administrator it might be a little bit complicated to handle all the files, but with just one nfs server it boiles down to
- Add a custom udev rule
- Unify the fstab mountpoints and udev names for the devices
- edit the nfs_clients.sh with clientlist and local hostname.
But a good system administrator can handle all this with a little bash magic ;)
Troubleshooting
System hangs nevertheless
Probably you disconnected the network. A permannt network connection is one of the prerequisites for a nfs network (see intro of this article).
First reconnect or restart the nfs server that causes the problem. Then wait a little bit - the lost share should reappear, the hung system work.
Then shutdown the system normally and your scripts should work.
Path
On some systems, your environment will not match the requirements.
- Is your shell /bin/bash, is it installed?
- Is your mount command in /bin/mount (same for umount)?
General
Open all this files in one editor on your nfs server as root:
gedit /etc/udev/rules.d/10-local.rules \ /etc/fstab \ /etc/exports \ /etc/hosts \ /root/bin/nfs_clientlist.sh \ /root/bin/mount_nfs_exports_on_attached_clients \ /root/bin/nfs_udev_mount.sh
On your nfs client open
gedit /etc/fstab
- The mountpoints under /media, the device names for /etc/udev/rules.d/10-local.rules and /etc/fstab are unique
- Are all nfs impors named correct in /etc/fstab according to the naming conventions on the server. Same unique device name, same mountpoint.
- When you are using more than on network card (think about the wlan card on your laptop), check if you are using the correct IP/name mapping.
Alter the runlevels
Here another way for initialisation of the rc.shutdown script:
Remove the old links:
$ update-rc.d -f rc.shutdown remove Removing any system startup links for /etc/init.d/rc.shutdown ... /etc/rc0.d/K20rc.shutdown /etc/rc1.d/K20rc.shutdown /etc/rc2.d/S20rc.shutdown /etc/rc3.d/S20rc.shutdown /etc/rc4.d/S20rc.shutdown /etc/rc5.d/S20rc.shutdown /etc/rc6.d/K20rc.shutdown
Add rc.shutdown to be started at first:
$ update-rc.d rc.shutdown start 01 2 3 4 5 . stop 01 0 1 6 . Adding system startup for /etc/init.d/rc.shutdown ... /etc/rc0.d/K01rc.shutdown -> ../init.d/rc.shutdown /etc/rc1.d/K01rc.shutdown -> ../init.d/rc.shutdown /etc/rc6.d/K01rc.shutdown -> ../init.d/rc.shutdown /etc/rc2.d/S01rc.shutdown -> ../init.d/rc.shutdown /etc/rc3.d/S01rc.shutdown -> ../init.d/rc.shutdown /etc/rc4.d/S01rc.shutdown -> ../init.d/rc.shutdown /etc/rc5.d/S01rc.shutdown -> ../init.d/rc.shutdown