pATAq 发表于 2025-4-16 15:14:36

跟思兼学用Klipper(35) mergerfs助力热插拔自动扩容\脱机打印

本帖最后由 pATAq 于 2025-4-16 15:15 编辑

## 前言

原创文章,转载引用务必注明链接,水平有限,如有疏漏,欢迎指正交流。
文章如有更新请访问 (https://mc.dfrobot.com.cn/thread-323437-1-1.html?fromuid=725344)及 (https://www.cnblogs.com/sjqlwy/p/18607903/kbox_ext),前者内容较全,后者排版及阅读体验更佳。

> [思兼的学用Klipper系列文章合集](https://www.cnblogs.com/sjqlwy/p/18005866/klipper_blog_collection)

一晃才发现小半年没有发表新文章了,仿佛还在上个月。

这是一篇借助豆包,基于我的大纲与测试结果自动生成并经过我修改的文章,不知道效果如何。其实我本地很多存货,苦于没有时间精力整理成文,借助 AI 可能会是一个很好的方法。同时部分程序代码使用 AI 进行辅助,至于感受会在文末总结。

在 3D 打印的领域里,Klipper 固件凭借出色的性能,深受广大爱好者青睐。早期基于电视盒子打造的 Klipper 上位机,常见配置是 1G 内存+ 8G eMMC 存储。随着模型复杂度增加,上传的模型切片文件越来越大、数量越来越多,再加上延时摄影临时抓取的大量图片以及渲染生成的视频,这点存储空间就显得捉襟见肘了。

之前我写过文章,包括[跟着思兼学习Klipper(10)使能Klipper从U盘读取Gcode并打印](https://mc.dfrobot.com.cn/thread-312184-1-1.html?fromuid=725344)以及 [跟着思兼学习Klipper(23) 玩一玩必趣 BTT Pi 上位机](https://mc.dfrobot.com.cn/thread-316165-1-1.html?fromuid=725344),分别介绍热插拔自动挂载 U 盘或 SD 卡实现脱机打印,以及利用Alist+Rclone+网盘给上位机扩容。今天我们要更进一步,探索新的更完善的扩容方案。

软件环境:

* DietPi for x86_64 Legacy (based on Debian Bookworm)
* mergerfs v2.40.2

硬件环境:

* KlipperBoxPro 上位机
* 4GB microSD 和 64GB U盘,以下简称移动存储

本文涉及的内容:

* mergerfs 实现上位机热插拔移动存储实现脱机打印和自动扩容
* udev 规则故障排查
* AI 辅助文章写作的感受
* AI 辅助编程的感受

# 一、现状与目的

## 更完善的热插拔脱机打印

* 也就是马琳(Marlin)时代,在电脑上将切好的模型文件拷贝到 SD 卡或 U 盘后,插到打印机上进行打印。
* 随着 Klipper 的出现,网络打印流程更加方便。且特殊情况可以通过上位机创建热点来实现局域网打印。
* 由于 Klipper 不支持自动挂载磁盘,所以要额外的设置/程序实现自动挂载移动存储到 $HOME/printer_data/gcodes 目录下,从而被 Klipper 访问
* 考虑到 U 盘稳定性欠佳,接口松动可能导致打印失败,目前一种策略是在识别到移动存储插入后,会把其中的 gcode 文件复制一份到宿主机 gcodes 文件夹

![](https://s2.loli.net/2025/04/16/yISqY9JzgUWt75T.png)

!(https://s2.loli.net/2025/04/16/TEwQJz8jS5Fil3p.png)

## 长期挂载作为系统扩容磁盘

* 除了热插拔脱机打印功能之外,我认为更重要的是针对小存储上位机如电视盒子/ WiFi 棒子的扩容功能
* 把诸如 Gcodes 和延时摄影视频,或者其他文件存储到移动存储设备上,从而避免主磁盘空间满了
* 通过给移动存储创建特定标签,从而只把指定设备挂载为扩容盘。此处为 `KBOX_EXT_SD/KBOX_EXT_U1`

# 二、目前方案的缺点

这次的方案既支持脱机打印,又支持自动扩容。相比直接用 udev + mount 或者 fstab 挂载,优势明显:

**规避系统启动风险**:fstab 挂载一旦配置有误,比如设备路径写错、文件系统类型指定错误,极有可能导致系统启动失败。而 mergerfs 挂载能有效避免这类问题,确保系统顺利启动。

**灵活存储运用**:既能实现移动存储设备即插即打印,方便快捷地开启打印任务;又能常规插在上位机上作为扩容存储设备,不管是存放更大的 swapfile 来优化系统运行,还是存储延时摄影素材、大量的 gcode 文件,都不在话下。

**解决目录显示问题**:普通 mount 操作遇到同名目录时,会隐藏原目录,导致里面的文件无法访问。mergerfs 则不会出现这种状况,所有文件和目录都能正常显示,方便用户管理文件。

**便捷切片软件上传**:以往切片软件上传文件,常挂载到 gcodes 子目录,之后还得手动移动文件到合适位置。现在新方案支持切片软件直接上传到用于扩容的移动存储目录,大大简化了流程,提高了工作效率。

**降低板载存储损耗**:板载 eMMC 存储更换不便,且频繁读写易磨损。新方案把数据存储任务转移到可热插拔的移动存储设备,如 sd 卡。sd 卡用完可轻松更换,有效减少 eMMC 的磨损,延长其使用寿命。

接下来,我们来看看具体实现步骤。

# 三、结构设计

!(https://s2.loli.net/2025/04/16/dHFEWiXgvqYTtl9.png)

## 3.1 移动存储初始化

主要目的是选择合适的文件系统,对扫描到的移动存储设备格式化,设置分区标签为 KBOX_EXT_SD/KBOX_EXT_U1 等,方便后续正确挂载,并忽视其他存储设备。

### 3.1.1 文件系统选择

因为使用 microSD 或 U 盘这种闪存设备,且只存储简单文件无高级特性需求,考虑到 Windows/Linux 系统兼容性,存储寿命等因素,这里推荐如果需要 Windows 下也可以访问,推荐 exFAT 文件系统,否则可以尝试 f2fs 文件系统。其他如 ext4、btrfs 等也可以选择。

### 3.1.2 上位机安装必备软件包

```shell
# 安装 exFAT 或 f2fs 文件系统支持包
sudo apt install exfatprogs f2fs-tools util-linux attr
```

### 3.1.3 移动存储设备初始化脚本

使用步骤:

1. 将待格式化的磁盘插入上位机
2. 在上位机 ssh 终端中输入 `sudo bash prepare_storage.sh` 对设备进行初始化,根据提示选择正确的设备,如果不确定,可以在插拔移动存储前后,在终端输入 lsblk,对比后确认。

主要功能:

1. 自动扫描并列出所有可用磁盘,排除根目录所在磁盘,避免误格式化
2. 显示扫描到的移动存储,供用户选择【注意:磁盘会被清空,请务必选择正确的磁盘】
3. 允许用户选择 exFAT 还是 f2fs 文件系统。
4. 根据设备是 SD 卡还是 U 盘设置不同的分区标签
5. 在新分区上创建 gcodes 和其他目录

```shell
#!/bin/bash
# '''
# Author: sjqlwy sjqlwy@yeah.net
# Date: 2025年4月13日
# Description:初始化存储设备用于系统扩容以及脱机打印
# '''

# 获取根目录所在设备
root_device=$(df / | tail -1 | awk '{print $1}' | sed 's/*$//')

# 扫描可用的 SD 卡和 U 盘
devices=()
sizes=()
for device in /sys/block/*; do
    device_name=$(basename $device)
    if [[ $device_name == "mmcblk"* || $device_name == "sd"* ]]; then
      device_path="/dev/$device_name"
      if [ "$device_path" != "$root_device" ]; then
            devices+=("$device_path")
            size=$(blockdev --getsize64 "$device_path")
            # 将大小转换为GB并保留1位小数
            size_gb=$(printf "%.1f" $(echo "scale=1; $size / 1024 / 1024 / 1024" | bc))
            sizes+=("$size_gb GB")
      fi
    fi
done

# 显示可用设备供用户选择
if [ ${#devices[@]} -eq 0 ]; then
    echo "未找到可用的 SD 卡或 U 盘。"
    exit 1
fi

echo "可用的设备有:"
for i in "${!devices[@]}"; do
    echo "$((i + 1)). ${devices[$i]} (${sizes[$i]})"
done

# 获取用户选择
read -p "请输入你要选择的设备编号 (1 - ${#devices[@]}): " choice

if ! [[ $choice =~ ^+$ ]] || [ $choice -lt 1 ] || [ $choice -gt ${#devices[@]} ]; then
    echo "无效的选择。"
    exit 1
fi

selected_device=${devices[$((choice - 1))]}

# 卸载设备上的所有分区
for partition in $(lsblk -n -o NAME $selected_device | grep -E '^[^[:space:]]++$'); do
    umount "/dev/$partition" 2>/dev/null
done

# 删除原有分区表
parted -s "$selected_device" mklabel gpt
if [ $? -ne 0 ]; then
    echo "删除原有分区表失败,请检查设备状态。"
    exit 1
fi

# 创建新分区
parted -s "$selected_device" mkpart primary 0% 100%
if [ $? -ne 0 ]; then
    echo "创建新分区失败,请检查设备状态。"
    exit 1
fi

# 等待内核识别新分区
sleep 2

# 通知内核重新读取分区表
partprobe "$selected_device"

# 获取新分区的设备路径
if [[ $selected_device == *"mmcblk"* ]]; then
    new_partition="${selected_device}p1"
else
    new_partition="${selected_device}1"
fi

# 检查新分区是否存在
if [ ! -b "$new_partition" ]; then
    echo "新分区 $new_partition 未被识别,请检查设备状态。"
    exit 1
fi

# 生成合适的 LABEL
if [[ $selected_device == *"mmcblk"* ]]; then
    label="KBOX_EXT_SD"
else
    existing_labels=$(lsblk -o LABEL | grep '^KBOX_EXT_U')
    max_num=0
    for existing_label in $existing_labels; do
      num=$(echo $existing_label | grep -oE '+$' | sed 's/^0*//')
      if [ -n "$num" ] && [ $num -gt $max_num ]; then
            max_num=$num
      fi
    done
    new_num=$((max_num + 1))
    label="KBOX_EXT_U${new_num}"
fi

# 让用户选择文件系统格式
echo "请选择文件系统格式:"
echo "1. exfat(双系统访问,推荐)"
echo "2. f2fs"
read -p "请输入对应的数字 (1 - 2): " fs_choice

if [ "$fs_choice" != "1" ] && [ "$fs_choice" != "2" ]; then
    echo "无效的选择,请输入 1 或 2。"
    exit 1
fi

if [ "$fs_choice" == "1" ]; then
    fs_type="exfat"
    mkfs.exfat -n "$label" "$new_partition"
    # 给 exfat 分区添加 msftdata 标签
    partition_number=$(echo "$new_partition" | grep -oE '+$')
    parted -s "$selected_device" set "$partition_number" msftdata on
else
    fs_type="f2fs"
    mkfs.f2fs -l "$label" "$new_partition"
fi
if [ $? -ne 0 ]; then
    echo "格式化新分区为 $fs_type 格式失败,请检查设备状态。"
    exit 1
fi

# 修改 mount_point 为和 label 相同的格式
mount_point="/mnt/$label"
mkdir -p "$mount_point"
mount "$new_partition" "$mount_point"
if [ $? -ne 0 ]; then
    echo "挂载新分区 $new_partition 失败,请检查设备状态。"
    exit 1
fi

# 获取当前 sudo 执行用户
user=$(logname)

# 创建目录
mkdir -p "$mount_point/gcodes"
mkdir -p "$mount_point/timelapse"

# 根据文件系统格式决定是否修改目录所有者
if [ "$fs_type" == "f2fs" ]; then
    chown -R "$user" "$mount_point/gcodes" "$mount_point/timelapse"
fi

# 卸载新分区
umount "$mount_point"
if [ $? -ne 0 ]; then
    echo "卸载新分区 $new_partition 失败,请手动卸载。"
fi

echo "设备 $selected_device 已成功删除原有分区,创建了 $fs_type 分区,标签为 $label,并创建了 gcodes 和 timelapse 目录。"
```

**注意:**

1. 必须排除根目录所在磁盘避免误选择
2. 增加显示磁盘大小避免误选择
3. 一般 mSD 槽只有一个,U 盘可以有多个,所以预留了相关处理
4. exFAT 分区需要添加 msftdata 标签,否则在 Windows 下是隐藏分区不可见
5. 创建 f2fs 分区后,需要将 gcodes 目录所有者转到当前非 root 账户来获得读写权限
6. 由于 exFAT 不支持复杂权限管理,所以无法修改目录所有者,采用 mount 挂载的时候指定 UID GID 来实现。
7. 对于 exFAT 这种非 POSIX 标准文件系统,df -i 显示 inode 数为 0
8. 注意 exFAT 的标签长度有限制,所以这里设置为 `KBOX_EXT_Ux` 而不是 `KBOX_EXT_USBx`
9. 考虑到脱机打印,需要在 Windows 和 Linux 下都能访问移动存储,这里推荐 exFAT 文件系统
10. 如果仅做扩容盘使用,可以选择 f2fs 文件系统
11. 关于文件系统的兼容性问题,可以移步 https://trapexit.github.io/mergerfs/faq/compatibility_and_integration/

至此我们获得了一个标签为 KBOX_EXT_* 的移动存储,并可以在 Windows 下访问 (exFAT)。

## 3.2 上位机设置

### 3.2.1 安装必要软件包:

```shell
# 安装 mergerfs,此处使用了镜像源,且下载 Klipper Pro 系列上位机所使用的 x86_64(amd64) 架构软件包
wget https://cors.isteed.cc/https://github.com/trapexit/mergerfs/releases/download/2.40.2/mergerfs_2.40.2.debian-bookworm_amd64.deb
sudo dpkg -i mergerfs*.deb

# 创建目录 local_gcodes 用于存放从原 ~/printer_data/gcodes 目录迁移来的模型切片文件
cd && mkdir ~/printer_data/local_gcodes
mv ~/printer_data/gcodes/* ~/printer_data/local_gcodes/
```

**注意:**

* 由于系统自带的版本老旧(2.33.5),我们总是推荐从其项目主页下载手动安装最新版,以获得最新的特性与更好的兼容性。
* mount 后 `~/printer_data/local_gcodes` 目录中的文件不可见,所以我们提前移出

### 3.2.2 创建 udev 规则实现自动挂载

既往使用直接将移动存储设备挂载到 `~/printer_data/local_gcodes` 子目录下,存在诸多不便,所以本次使用 /mnt/目录下,然后使用 mergerfs 整合到虚拟目录。udev 规则如同智能管家,能识别标签为 KBOX\_EXT\_SD/USBx 设备插入,并按规则自动挂载到指定的 /mnt/ 目录。在 /etc/udev/rules.d/ 目录创建新规则文件,文件名可自定义,建议遵循规范便于管理,如创建名为 99-kboxext-mount.rules 的文件(数字表示优先级,越小越高,这里用 99)。

```shell
# 创建一个脚本,查找根目录所在磁盘用于排除,且输出符合 udev 规则。key:vaule
tee $HOME/kbox_scripts/get_rootfs.sh > /dev/null << 'EOF'
#!/bin/bash
device=$(/bin/findmnt -n -o SOURCE /)
echo "root_device=$device"
EOF

# 创建自动挂载规则,我们仍然参考 homeassistant 的脚本
# 注意这里使用 pi 用户
sudo tee /etc/udev/rules.d/99-kboxext-mount.rules > /dev/null << 'EOF'
# Get the root device
IMPORT{program}="/bin/bash /home/pi/get_rootfs.sh"
ENV{root_device}="%E{root_device}"

# Import the partition info into the environment variables
IMPORT{program}="/usr/sbin/blkid -o udev -p %N"

# Exit if partition is not a filesystem
ENV{ID_FS_USAGE}!="filesystem", GOTO="abort_rule"

# Exit if the label doesn't start with KBOX_EXT_
ENV{ID_FS_LABEL}!="KBOX_EXT_*", GOTO="abort_rule"

# Exit if it's the root device
ENV{DEVNAME}=="%E{root_device}", GOTO="abort_rule"

# Determine the mount point using the label
ENV{mount_point}="/mnt/%E{ID_FS_LABEL}"

# General mount options
ACTION=="add", ENV{mount_options}="relatime,sync,uid=pi,gid=pi"

# Filesystem-specific mount options
# ACTION=="add", ENV{ID_FS_TYPE}=="vfat|ntfs", ENV{mount_options}="{mount_options},utf8,gid=100,umask=002"
ACTION=="add", ENV{ID_FS_TYPE}=="f2fs", ENV{mount_options}="$env{mount_options}"
ACTION=="add", ENV{ID_FS_TYPE}=="exfat", ENV{mount_options}="$env{mount_options},utf8"

# Create the mount point directory if it doesn't exist and mount the device
ACTION=="add", RUN{program}+="/usr/bin/mkdir -p %E{mount_point}", RUN{program}+="/usr/bin/systemd-mount --no-block --automount=no --collect -o %E{mount_options} $devnode %E{mount_point}"

# Umount the device on 'remove' action (a.k.a unplug or eject the USB drive)
ACTION=="remove", RUN{program}+="/usr/bin/systemd-umount %E{mount_point}"

# Exit
LABEL="abort_rule"
EOF
# 增加可执行权限,并设置生效
sudo chmod +x /etc/udev/rules.d/80-kboxext-mount.rules
sudo udevadm control --reload-rules
sudo udevadm trigger
# sudo systemctl restart udev
```

**注意:**

1. 这里统一使用 pi 账户挂载,使得上传 gcode 时有正确的读写权限
2. 这里创建一个独立的脚本,用于获得符合 udev 规则的输出变量
3. 如何写 udev 规则,之前有文章介绍过
4. 不同系统 udev 规则文件位置和格式可能有差异,需根据实际操作系统版本和 udev 版本调整,遇到问题可查阅官方文档或在技术论坛求助。
5. `sudo udevadm test $(udevadm info -q path -n /dev/mmcblk0p1)`是一个 很重要的故障排查命令

至此,我们可以实现插入移动存储设备后,系统自动挂载到 /mnt/KBOX_EXT_* 目录下。

!(https://s2.loli.net/2025/04/16/SfGw62pYJV4vgWq.png)

1. 预处理移动设备
2. 在moonraker之前启动mergerfs挂载, systemd service
3. 识别到KBOX_EXT_开头的设备则自动挂载到/mnt udev

### 3.2.3 创建 mergerfs 服务实现灵活扩容

创建 mergerfs 服务系统来实现系统启动时自动创建虚拟目录,且在 moonraker 服务之前启动,以便能正确读取 gcodes 目录。

参考 mergerfs 官网并做了一些修改。

```ini
cat << _EOF_ > /tmp/mergerfs-klipper.service

Description = MergerFS Service for Klipper Hot-Swap Expansion
After = local-fs.target
Before = moonraker.service


Type=simple
KillMode=none
ExecStart=$(which mergerfs) \
-f \
-o cache.files=auto-full \
-o category.create=epff \
-o func.getattr=newest \
-o dropcacheonclose=true \
/mnt/KBOX_EXT_SD/gcodes=RW,100M:/mnt/KBOX_EXT_U1/gcodes=RW,100M:$HOME/local_gcodes=RW,200M \
$HOME/printer_data/gcodes
ExecStop=$(which fusermount) -uz $HOME/printer_data/gcodes
Restart=on-failure


WantedBy = multi-user.target
_EOF_

sudo mv /tmp/mergerfs-klipper.service /lib/systemd/system/mergerfs-klipper.service
sudo systemctl enable --now mergerfs-klipper.service
```

**注意:**

* `local-fs.target` 的核心用途是确保在系统启动期间,所有本地文件系统(像根文件系统、`/home`、`/var` 等分区)都能正确挂载。在达到这个目标状态之后,系统才会继续启动其他依赖于本地文件系统的服务。
* 注意需要 `-f` 参数
* 注意这里的选项,为了保证文件完整性,启用了缓存
* mergerfs 使用多种策略和选项控制优先写入哪个目录,这里选择的策略为 `epff`,即如果存在目标文件夹则优先写入,否则按由左至右首次发现的<u>**可用**</u>目录写入。
* mergerfs 默认只会向剩余可用空间大于 4G 的目录写入文件,由于我测试用的 mSD 卡是 4G 的(由于 1000/1024 实际不足4G,所以总不会写入),需要增加额外参数
* 即使不存在可移动存储,mergerfs 仍然可可以正确挂载
* local_gcodes 位于板载存储,总是可用,但是至少留有 200MB

# 四、拓展内容

## 4.1 写入策略问题

存储设备写入策略复杂,优先写入哪个设备由策略、RW/RO/NC 挂载、保留的最少空间等因素决定。我们希望有 sd 卡插入时优先写入 sd 卡,但默认最小保留空间为 4G,若磁盘所在文件系统可用空间少于 4G,系统就不会往该磁盘写入数据。我测试时用的 4G sd 卡,格式化后可用空间不足 4G,导致系统一直不往 sd 卡写入,这个问题排查了很久。另外,写入策略还有先发现先写入的特点,即先挂载的节点优先写入。解决办法一是更换容量更大的 sd 卡,确保格式化后可用空间大于 4G;二是深入了解系统存储管理机制,根据实际情况调整写入策略相关设置。例如,可通过修改 mergerfs 的配置文件,调整最小保留空间等参数,但这需要一定的技术基础,操作前建议备份相关配置文件,以防设置错误导致系统异常。

## 4.2 通过 runtime 接口调试

mergerfs 的日志信息相当于没有,需要参考官方文档:https://trapexit.github.io/mergerfs/runtime_interfaces/ 使用运行时接口

```shell
sudo apt-get install attr
sudo mergerfs -f -o cache.files=off,func.getattr=newest,dropcacheonclose=false,category.create=all /mnt/KBOX_EXT/gcodes:/home/pi/local_gcodes=RO /home/pi/printer_data/gcodes
getfattr -n user.mergerfs.category.create /home/pi/printer_data/gcodes/.mergerfs

# 创建文件
sudo mergerfs -f -o cache.files=off,func.getattr=newest,dropcacheonclose=false,category.create=all /mnt/KBOX_EXT/gcodes=RW:/home/pi/local_gcodes=RO /home/pi/printer_data/gcodes
touch /home/pi/printer_data/gcodes/new-file
getfattr -n user.mergerfs.allpaths /home/pi/printer_data/gcodes/new-file
# 报错 read-only fs

# 显示所有值
getfattr -d /home/pi/printer_data/gcodes/.mergerfs
```

## 4.3 关于cache.files

https://trapexit.github.io/mergerfs/faq/technical_behavior_and_limitations/#i-notice-massive-slowdowns-of-writes-when-enabling-cachefiles

## 4.4 支持参数化挂载多个目录

目前仅以 gcodes 目录演示,计划增加 timelaspe 目录,以及其他可能目录。

## 4.5 UDEV 规则故障排查

当 `udev` 规则未生效时,可以按照以下步骤进行排查:

### 4.5.1 规则文件语法检查

- **检查规则文件路径**:确保规则文件存放在 `/etc/udev/rules.d/` 目录下,且文件名以 `.rules` 结尾,通常文件名以数字开头(如 `80-kboxext-mount.rules`),数字表示规则执行的优先级。
- **检查语法错误**:`udev` 规则文件使用特定的语法,任何语法错误都可能导致规则不生效。可以使用 `udevadm test` 命令来测试规则文件的语法。例如,对于规则文件 `80-kboxext-mount.rules`,可以使用以下命令:

```bash
sudo udevadm test $(udevadm info -q path -n /dev/mmcblk0p1)
```

将 `/dev/mmcblk0p1` 替换为你要测试的实际设备。如果规则文件存在语法错误,该命令会输出相应的错误信息。

### 4.5.2 设备属性检查

- **重新加载规则**:有时候规则文件修改后,`udev` 可能没有及时加载新规则。可以使用以下命令重新加载规则:

```bash
sudo udevadm control --reload-rules
```

- **触发规则**:重新加载规则后,需要触发规则以使其生效。可以使用 `udevadm trigger` 命令触发设备的添加或移除事件。例如,对于设备 `/dev/mmcblk0p1`,可以使用以下命令触发添加事件:

```bash
sudo udevadm trigger --verbose --action=add --sysname-match=/dev/mmcblk0p1
sudo udevadm info -q path -n /dev/mmcblk0p1
sudo udevadm info -q env -n /dev/mmcblk0p1
```

### 4.5.3 设备属性检查

- **查看设备属性**:使用 `udevadm info` 命令查看设备的属性,确保规则中使用的属性(如 `ID_FS_LABEL`、`ID_FS_TYPE` 等)与实际设备的属性一致。例如,查看 `/dev/mmcblk0p1` 的设备属性:

```bash
udevadm info --attribute-walk --name=/dev/mmcblk0p1
```

该命令会输出设备的详细属性信息,检查规则中使用的属性是否正确。

- **检查设备路径和类型**:确保规则中对设备路径和类型的过滤条件符合实际设备。例如,如果规则中使用了 `KERNEL` 或 `ENV{ID_PATH}` 进行过滤,需要检查设备的内核名称和路径是否满足条件。

### 4.5.4 权限和依赖检查

- **检查外部脚本权限**:如果规则中使用了外部脚本(如 `RUN` 指令调用的脚本),确保脚本具有可执行权限。可以使用 `chmod` 命令添加执行权限,例如:

```bash
chmod +x /path/to/your/script.sh
```

- **检查依赖项**:确保规则中使用的命令和工具(如 `blkid`、`systemd-mount` 等)已经正确安装,并且在 `udev` 运行环境中可用。

### 4.5.5 日志和调试信息查看

- **查看系统日志**:`udev` 的相关信息通常会记录在系统日志中,可以使用 `journalctl` 命令查看系统日志,查找与 `udev` 相关的错误信息。例如:

```bash
sudo journalctl -u systemd-udevd.service
```

- **添加调试信息**:在规则文件中添加调试信息,使用 `RUN` 指令将调试信息输出到日志文件中。例如,在规则中添加以下内容:

```plaintext
ACTION=="add", RUN{program}+="/bin/echo 'Device added: %E{DEVNAME}' >> /var/log/udev_debug.log"
```

这样,当设备添加事件触发时,会将设备名称记录到 `/var/log/udev_debug.log` 文件中,方便查看调试信息。

通过以上步骤,应该能够逐步排查出 `udev` 规则未生效的原因。

## 最终效果

经过一系列设置和优化,我们的方案基本达成预期目标。现在,既能从电脑拷贝切好的 gcode 文件,插卡就能直接打印,大大提高了打印效率;又实现了持久化扩容,有足够空间存放模型切片文件、延时摄影素材等。热插拔功能让使用存储设备更加便捷,有效解决了早期 Klipper 上位机存储不足的问题,为 3D 打印工作提供了更稳定、高效的存储解决方案。希望大家通过这篇文章,都能顺利实现 Klipper 上位机的热插拔自动扩容,享受更流畅的 3D 打印体验。如果在实践过程中遇到问题,欢迎随时交流探讨。

### 关于 AI 辅助写作

多了一些废话,还是需要人工参与大量校正

### 关于 AI 辅助编程

需要作者懂一些基础编程知识,否则生成的代码不可用都不知道如何 DEBUG

还需要我继续学习如何更好地使用 AI 工具。



木子哦 发表于 2025-4-17 09:47:22

诶嘿!更新了
页: [1]
查看完整版本: 跟思兼学用Klipper(35) mergerfs助力热插拔自动扩容\脱机打印