使用systemd自动挂载USB驱动器


27

我们正在将服务器从非常过时的发行版更新为基于Debian Jessie的现代系统,包括lightdm / xfce,当然还有systemd(和udisks2)。一大难题是自动安装USB驱动器。我们曾经使用一些udev规则来完成此任务。旧的规则几乎仍然有效-创建了挂载点并且驱动器已很好地挂载,但是几秒钟后systemd正在做一些事情来中断挂载,因此后续的访问尝试将导致“传输端点未连接”错误。

通过命令行手动安装驱动器可以正常工作。文件管理器(thunar和thunar-volman,它们依次使用udisks2)也是如此。但是这些都不是可行的选择-这些系统通常无头运行,因此thunar通常无法运行。我们需要能够插入磁盘驱动器以进行基于cron的无人值守备份。

我以为修改udev脚本以产生在执行挂载之前等待几秒钟的分离作业可能会解决问题,但是systemd似乎竭尽全力防止这种情况-它仍然以某种方式等待分离作业完成之前继续。

也许让udev脚本使udisks2发痒是正确的方法?我很茫然,所以任何建议都将不胜感激。


1
仅与切向相关,但是...您要将xfce放在服务器上?
Parthian Shot

嗯,我相当宽松地使用了“服务器”一词...所有与系统的用户交互都是通过Web应用程序进行的,通常是通过网络上的浏览器进行访问。但是有些客户更喜欢非网络解决方案,因此我们以一种自助服务终端模式在控制台上运行Chrome。(这对于调试网络配置问题也很方便,您可以插入监视器/鼠标/键盘并访问Web应用程序中的基本诊断工具,而无需Linux登录凭据)。可能有比lightdm / xfce更轻的解决方案,但这是最简单的设置...
Mike Blackwell

对于任何想要直接运行脚本的systemd-udevd规则的人:它工作了一段时间,但如果udevd已自动启动,则有时会停止运行脚本。从命令行停止并重新启动,就可以了。最重要的是,它不能与NTFS + FUSE一起很好地工作,因为udev检测到它具有运行时间很长的子进程(ntfs-3g),并在60s之后将其杀死。底线:udev规则直接运行脚本是浪费时间。如答案中所述,使用udev规则和systemd服务代替。然后,您也不必处理名称空间(MountFlags = slave)。
标记

我也遇到过类似的问题,因为udev无法建立网络连接而启动了脚本。下面使用systemd的解决方案也适用于此-谢谢!
昆汀·斯塔福德·弗雷泽

Answers:


28

在几次错误的开始之后,我发现了这一点。关键是在udev和安装脚本之间添加systemd单元服务。

(根据记录,我无法udisksctl mount -b /dev/sdb1直接使用udev规则或systemd单元文件中的udisks2(通过诸如此类)使它正常工作。似乎存在争用情况,并且设备节点尚未准备就绪,导致Error looking up object for device /dev/sdb1。不幸的是,因为udisks2可以处理所有挂载点的混乱情况...)

繁重的工作由Shell脚本完成,该脚本负责创建和删除安装点,以及安装和卸载驱动器。

/usr/local/bin/usb-mount.sh

#!/bin/bash

# This script is called from our systemd unit file to mount or unmount
# a USB drive.

usage()
{
    echo "Usage: $0 {add|remove} device_name (e.g. sdb1)"
    exit 1
}

if [[ $# -ne 2 ]]; then
    usage
fi

ACTION=$1
DEVBASE=$2
DEVICE="/dev/${DEVBASE}"

# See if this drive is already mounted, and if so where
MOUNT_POINT=$(/bin/mount | /bin/grep ${DEVICE} | /usr/bin/awk '{ print $3 }')

do_mount()
{
    if [[ -n ${MOUNT_POINT} ]]; then
        echo "Warning: ${DEVICE} is already mounted at ${MOUNT_POINT}"
        exit 1
    fi

    # Get info for this drive: $ID_FS_LABEL, $ID_FS_UUID, and $ID_FS_TYPE
    eval $(/sbin/blkid -o udev ${DEVICE})

    # Figure out a mount point to use
    LABEL=${ID_FS_LABEL}
    if [[ -z "${LABEL}" ]]; then
        LABEL=${DEVBASE}
    elif /bin/grep -q " /media/${LABEL} " /etc/mtab; then
        # Already in use, make a unique one
        LABEL+="-${DEVBASE}"
    fi
    MOUNT_POINT="/media/${LABEL}"

    echo "Mount point: ${MOUNT_POINT}"

    /bin/mkdir -p ${MOUNT_POINT}

    # Global mount options
    OPTS="rw,relatime"

    # File system type specific mount options
    if [[ ${ID_FS_TYPE} == "vfat" ]]; then
        OPTS+=",users,gid=100,umask=000,shortname=mixed,utf8=1,flush"
    fi

    if ! /bin/mount -o ${OPTS} ${DEVICE} ${MOUNT_POINT}; then
        echo "Error mounting ${DEVICE} (status = $?)"
        /bin/rmdir ${MOUNT_POINT}
        exit 1
    fi

    echo "**** Mounted ${DEVICE} at ${MOUNT_POINT} ****"
}

do_unmount()
{
    if [[ -z ${MOUNT_POINT} ]]; then
        echo "Warning: ${DEVICE} is not mounted"
    else
        /bin/umount -l ${DEVICE}
        echo "**** Unmounted ${DEVICE}"
    fi

    # Delete all empty dirs in /media that aren't being used as mount
    # points. This is kind of overkill, but if the drive was unmounted
    # prior to removal we no longer know its mount point, and we don't
    # want to leave it orphaned...
    for f in /media/* ; do
        if [[ -n $(/usr/bin/find "$f" -maxdepth 0 -type d -empty) ]]; then
            if ! /bin/grep -q " $f " /etc/mtab; then
                echo "**** Removing mount point $f"
                /bin/rmdir "$f"
            fi
        fi
    done
}

case "${ACTION}" in
    add)
        do_mount
        ;;
    remove)
        do_unmount
        ;;
    *)
        usage
        ;;
esac

该脚本又由systemd单位文件调用。我们使用“ @”文件名语法,因此我们可以将设备名称作为参数传递。

/etc/systemd/system/usb-mount@.service

[Unit]
Description=Mount USB Drive on %i
[Service]
Type=oneshot
RemainAfterExit=true
ExecStart=/usr/local/bin/usb-mount.sh add %i
ExecStop=/usr/local/bin/usb-mount.sh remove %i

最后,一些udev规则在热插拔上启动和停止systemd单元服务:

/etc/udev/rules.d/99-local.rules

KERNEL=="sd[a-z][0-9]", SUBSYSTEMS=="usb", ACTION=="add", RUN+="/bin/systemctl start usb-mount@%k.service"

KERNEL=="sd[a-z][0-9]", SUBSYSTEMS=="usb", ACTION=="remove", RUN+="/bin/systemctl stop usb-mount@%k.service"

这似乎可以解决问题!几个有用的命令,用于调试如下内容:

  • udevadm control -l debug打开的详细日志记录, /var/log/syslog以便您查看正在发生的情况。
  • udevadm control --reload-rules 在rules.d目录中修改文件后(可能没有必要,但不会造成伤害...)。
  • systemctl daemon-reload 修改系统单位文件后。

4
哇。这太棒了。希望我能多次投票!我唯一需要修改的是在我的系统上,blkid似乎没有提取an ID_FS_LABEL,因此我只是使用DEVBASE而不是LABEL构造了MOUNT_POINT
Travis Griggs

可以修改此设置以与ATA / SCSI设备一起使用吗?请参阅:serverfault.com/q/825779/297059
user339676

@Travis-您可以使用udevadm代替blkid。它提供了更多详细信息以及其他信息。(例如,udevadm info --query=property --name=sda1
user339676

如果已经连接了USB设备,这在启动时将无法正常工作。有任何想法吗?
米歇尔·阿塔佐夫

如果未设置nullglob,则在卸载时,清理会生成类似的错误/usr/bin/find: '/media/*': No such file or directory。清理可以像if [ "$f" != "/media/*" ]; then运行之前一样使用其他检查find
Pro Backup

12

可以使用一个新的简洁systemd自动安装选项,fstab它使您可以使用所有标准化的安装许可选项,如下所示:

  x-systemd.automount

fstab一行的示例:

  /dev/sdd1   /mnt/hitachi-one     auto     noauto,x-systemd.automount     0 2

noauto选项将意味着它不会尝试像旧版软件那样在启动时进行挂载autofs

在添加新x-systemd.automount行之后,fstab您需要运行:

  sudo systemctl daemon-reload

然后是以下两个或两个:

  sudo systemctl restart remote-fs.target
  sudo systemctl restart local-fs.target

有关更多信息:

https://wiki.archlinux.org/index.php/Fstab#Automount_with_systemd


sudo systemctl restart local-fs.target帮了我
大忙

2

我已将脚本从@MikeBlackwell修改为:

  • 认识到跨越多个字符,而不仅仅是设备名称/dev/sd[a-z],但是/dev/sd[a-z]*,带有大量锭子的服务器通常是这种情况。
  • 在以下位置跟踪自动安装驱动器的列表: /var/log/usb-mount.track
  • /var/log/messages使用标签usb-mount.sh将操作记录到
  • 与挂载点到那些尚未被分配了标记与驱动器出现问题无法运行该设备标签前缀的设备名(空): /media/sdd2_usbtest/media/sdd2_
  • 随附的包装器脚本可适当放置文件,并在需要时撤消

由于@MikeBlackwell已经完成了大部分繁重的工作,因此我选择不重写它;刚刚进行了必要的更改。我已经知道他的工作,因为他看到了他的名字和原始答案的URI。

https://github.com/raamsri/automount-usb上找到它


2

使用pmount,systemd和Mike Blackwell的方法,您可以简化整个过程:

/etc/systemd/system/usb-mount@.service

[Unit]
Description=Mount USB Drive on %i
[Service]
Type=oneshot
RemainAfterExit=true
ExecStart=/usr/bin/pmount --umask 000 /dev/%i /media/%i
ExecStop=/usr/bin/pumount /dev/%i

/etc/udev/rules.d/99-usb-mount.rules

ACTION=="add",KERNEL=="sd[a-z][0-9]*",SUBSYSTEMS=="usb",RUN+="/bin/systemctl start usb-mount@%k.service"
ACTION=="remove",KERNEL=="sd[a-z][0-9]*",SUBSYSTEMS=="usb",RUN+="/bin/systemctl stop usb-mount@%k.service"

谢谢,迈克。


0

我会同意沃伦·杨(Warren Young)的回答,但我对

我添加了一些空间保护,因为它使驱动器的环境评估出错。

我在chmod usb磁盘上添加了一个部分,以便所有用户都可以完全访问非ntfs或vfat磁盘。

/usr/local/bin/usb-mount.sh

#!/bin/bash

# This script is called from our systemd unit file to mount or unmount
# a USB drive.

usage()
{
    echo "Usage: $0 {add|remove} device_name (e.g. sdb1)"
    exit 1
}

if [[ $# -ne 2 ]]; then
    usage
fi

ACTION="$1"
DEVBASE="$2"
DEVICE="/dev/${DEVBASE}"

# See if this drive is already mounted, and if so where
MOUNT_POINT=$(/bin/mount | /bin/grep ${DEVICE} | /usr/bin/awk '{ print $3 }')

do_mount()
{
    if [[ -n "${MOUNT_POINT}" ]]; then
        echo "Warning: ${DEVICE} is already mounted at ${MOUNT_POINT}"
        exit 1
    fi

    # Get info for this drive: $ID_FS_LABEL, $ID_FS_UUID, and $ID_FS_TYPE
    # added some sed's to avoid space issues
    eval $(/sbin/blkid -o udev ${DEVICE}|sed 's/=/="/'|sed 's/$/"/')

    # Figure out a mount point to use
    LABEL="${ID_FS_LABEL}"
    if [[ -z "${LABEL}" ]]; then
        LABEL="${DEVBASE}"
    elif /bin/grep -q " /media/${LABEL} " /etc/mtab; then
        # Already in use, make a unique one
        LABEL+="-${DEVBASE}"
    fi
    MOUNT_POINT="/media/${LABEL}"

    echo "Mount point: ${MOUNT_POINT}"

    /bin/mkdir -p "${MOUNT_POINT}"

    # Global mount options
    OPTS="rw,relatime"
    #added a chmod checker for file systems that don't 
    #understand allow all to read write
    CHMOD=no
    # File system type specific mount options
    if [[ ${ID_FS_TYPE} == "vfat" ]]; then
        OPTS+=",users,gid=100,umask=000,shortname=mixed,utf8=1,flush"
    #added options I wanted on ntfs
    elif [[ ${ID_FS_TYPE} == "ntfs" ]]; then
        OPTS+=",user,users,umask=000,allow_other"
    else
       CHMOD=yes
    fi

    if ! /bin/mount -o "${OPTS}" ${DEVICE} "${MOUNT_POINT}"; then
        echo "Error mounting ${DEVICE} (status = $?)"
        /bin/rmdir "${MOUNT_POINT}"
        exit 1
    fi


    echo "**** Mounted ${DEVICE} at ${MOUNT_POINT} ****"
    if [ "${CHMOD}" = "yes" ];then
        /usr/bin/find "${MOUNT_POINT}" -type f -exec chmod 0666 {} \;
        /usr/bin/find "${MOUNT_POINT}" -type d -exec chmod 0777 {} \;
    fi
}

do_unmount()
{
    if [[ -z ${MOUNT_POINT} ]]; then
        echo "Warning: ${DEVICE} is not mounted"
    else
        /bin/umount -l ${DEVICE}
        echo "**** Unmounted ${DEVICE}"
    fi

    # Delete all empty dirs in /media that aren't being used as mount
    # points. This is kind of overkill, but if the drive was unmounted
    # prior to removal we no longer know its mount point, and we don't
    # want to leave it orphaned...
    for f in /media/* ; do
        if [[ -n $(/usr/bin/find "$f" -maxdepth 0 -type d -empty) ]]; then
            if ! /bin/grep -q " $f " /etc/mtab; then
                echo "**** Removing mount point $f"
                /bin/rmdir "$f"
            fi
        fi
    done
}

case "${ACTION}" in
    add)
        do_mount
        ;;
    remove)
        do_unmount
        ;;
    *)
        usage
        ;;
 esac

您可能需要用几句话来描述原始答案与您的答案之间的区别,以使其更有用。PS:沃伦·杨没有回答。也许您是说Mike Blackwell的答案已被编辑?
阿米尔
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.