在 NAS 中固定硬盘挂载点的实践教程(udev + systemd + bind mount)
本文方法为原创,使用 ChatGPT 整理润色发布。阅读本文需要一定的技术基础。数据无价,谨慎操作。
问题背景
在 NAS 或多硬盘系统中,常遇到 硬盘挂载点随重启或热拔插变化 的问题:
- 系统的自动挂载器(如
mountmgr或 udisks)会给硬盘分配/vol00/硬盘名路径,但这些路径不是固定的。 - 这对于像 qBittorrent 这种依赖固定路径的应用造成困扰,因为种子路径是固定的,但挂载点每次重启时有几率改变。
因此,为解决上述问题,需要寻求一个办法,将特定的硬盘分区挂载到固定的挂载点。
方案选择
笔者在前期探索时,发现了有以下几种解决方案。
方案 1:使用 fstab + uuid 直接挂载
fstab 基本语法为 :
1 | /dev/sdaX /mnt/path ext4 defaults 0 0 |
优点:配置最为简单,直接。
缺点:
与系统自带的挂载冲突:
闭源 NAS系统通常有自己的 mountmgr 或后台守护进程,比如飞牛自动把非存储空间的硬盘挂到
/vol00/...。如果在/etc/fstab里再写一份挂载(比如/mnt/path),就可能冲突(提示“设备已挂载”或直接失败)灵活性差,安全性低:
若使用诸如
/dev/sda1这样的设备路径,在硬件顺序变化时,可能会导致挂载失效。即便可以使用
UUID=的方式指定设备,也需要自己指定文件系统类型等挂载选项,不便于使用。此外,错误的配置容易引起系统盘挂载的问题,造成无法开机等后果。
方案 2:udev + 脚本进行 bind
优点:
- 使用
bind mount不会引起挂载冲突,是由内核提供的“多入口”方案。 - 使用 RUN 调用 shell 脚本,简单直观,脚本直接处理挂载。
- 能够直接传递
DEVNAME,ID_FS_UUID等环境变量,脚本里面可直接取用。
- 使用
缺点:受到 udev sandbox 限制,无法可靠挂载。
udev 官方文档说明:
This can only be used for very short-running foreground tasks. Running an event process for a long period of time may block all further events for this or a dependent device.
Note that running programs that access the network or mount/unmount filesystems is not allowed inside of udev rules, due to the default sandbox that is enforced on
systemd-udevd.service.Starting daemons or other long-running processes is not allowed; the forked processes, detached or not, will be unconditionally killed after the event handling has finished. In order to activate long-running processes from udev rules, provide a service unit and pull it in from a udev device using the
SYSTEMD_WANTSdevice property. See systemd.device(5) for details.这意味着,使用 udev 的
RUN+=中只能执行短任务,禁止访问网络、挂载文件系统、启动守护进程。实际测试中,即使是空脚本,长时间的任务或访问挂载会被 sandbox 杀死,无法成功执行指令。
方案 3:udev + systemd service + 脚本进行 bind
- 优点:
- 使用 systemd 执行脚本,绕过 udev sandbox 限制
- 支持等待系统自动挂载完成
- 缺点:
- systemd service 模板需要处理 实例名限制(连字符、特殊字符)
- 环境变量无法传递,需要在脚本中额外再去获取设备名等参数。
由于方案1、方案2受到种种限制,经测试无法完成目标,因此采用方案3的方法。
该方法与系统的自动挂载不冲突,且灵活性好。一个简单的对比图如下:
flowchart LR
subgraph FSTAB[方案一:传统 fstab 挂载方式]
A1[系统启动] --> B1[fstab 尝试挂载]
B1 --> C1[可能冲突: 已由 mountmgr 挂载]
B1 --> D1[路径未知: 无法提前写死]
B1 --> E1[失败: 设备 busy 或挂载点错误]
end
subgraph UDEV[方案三:udev + systemd + 脚本方案]
A[硬盘插入或重启] --> B[内核检测到块设备]
B --> C[udev 触发规则]
C -->|匹配 UUID| D[设置 SYSTEMD_WANTS=disk-bind@<UUID>.service]
D --> E[systemd 启动 disk-bind@<UUID>.service]
E --> F[执行脚本 disk-bind.sh]
F --> G[查 UUID → 别名配置表]
G --> H[等待系统自动挂载完成]
H --> I[bind mount 到 /mnt/mymount/<Alias>]
I --> J[日志记录 → 稳定别名可用]
end
技术要点概览
UUID 固定唯一
/dev/disk/by-uuid/提供了硬盘唯一标识。- 不随重启或热拔插变化。
Alias 配置
使用简单的配置文件
/etc/disk-alias.conf:1
2
31234-ABCD=movies
5678-EFGH="Backup Disk"
AAAA-BBBB=music支持:
- Alias 带空格或特殊字符(用引号包裹)
- 自动去掉引号,空格直接保留
Device 单元命名规则
- systemd 的
.device单元会把/dev/disk/by-uuid/<UUID>转换成dev-disk-by\x2duuid-<UUID>.device - 连字符
-必须转义为\x2d,否则依赖解析失败
- systemd 的
systemd Template + Environment 方式
- UUID 通过 Environment 变量传给脚本
- 脚本根据 UUID 在配置表中查别名,创建固定挂载点,并执行 bind mount
- 避免
%i带连字符直接引用.device造成依赖失败
实施步骤
注意,如果在 Windows 环境编辑,请确保使用 UTF-8 编码,以及 Unix 行尾(LF) !
1. 查询磁盘信息
SSH 登录系统,执行如下命令查询 UUID 并做记录。
1 | sudo blkid |
2. 创建 UUID->别名 配置文件
文件路径:/etc/disk-alias.conf
1 | 1234-ABCD=movies |
等号前的是 UUID,由第一步中获得。等号后的是 alias(挂载点)。
- UUID 不要加引号
- Alias 可以带引号(会自动去掉外层引号),但尽量还是使用不包含空格的路径。
- 另外注意避免在 Alias 中使用
/或其他特殊路径字符
3. 编写挂载脚本
路径:/usr/local/bin/disk-bind.sh,注意添加
chmod +x 的可执行权限。
1 |
|
3. 编写 systemd 模板
路径:/etc/systemd/system/disk-bind@.service
1 | [Unit] |
4. 编写 udev 规则
路径:/etc/udev/rules.d/90-disk-bind.rules
1 | # 示例:文件盘 |
注意修改每个条目的 UUID,与你的分区对应。
完成配置后,可实现:
- 自动触发 systemd service
- 保证硬盘热插拔或启动时绑定别名挂载点
5. 测试与调试
重新加载 systemd 单元与 udev 规则:
1 | sudo systemctl daemon-reload |
手动触发 udev add 事件:
使用如下命令触发一次事件,进行测试:
1 | sudo udevadm trigger -s block -c add |
如需测试单一分区,可以追加参数,例如
-v /sys/class/block/sda3
查看服务状态:
1 | journalctl -t disk-bind -f |
如果触发成功,这里会有相关的日志输出
验证挂载点:
1 | mount | grep /mnt/mymount |
如果不出问题,即可看到磁盘挂载点被成功绑定。此后,在系统启动后,对应的硬盘也会被自动绑定到这些固定的挂载点上。
附录
DEBUG
Debug 过程中可能会需要使用到的命令:
1 | sudo tail -f /var/log/syslog | grep disk-bind |
1 | systemctl list-units | grep disk-bind |
运行未按预期时,当然也可以手动启动服务,检查单次运行的情况:
1 | sudo systemctl start disk-bind@ABCD-1234.service |
其中,ABCD-1234 是
UUID,需要替换为真实值。这样做可以测试问题是出在 udev,还是 systemd 和
脚本。
qBittorrent Docker Compose
附赠一个实用的 docker 配置,可以在稳定挂载后启动容器:
1 | version: "2.4" |