Расширяем поддержку оборудования в PR2100
Небольшая предыстория к ниже описанному.
В этот раз попробую решить недостаток с работой usb, как известно то usb устройства на PR2100 могут работать только как накопитель, все остальное просто не воспринимается. Аналогичные проблемы описаны на форуме wd тут и тут. Есть несколько вариантов решения:
- собрать модуль для текущего ядра и загружать его:
- каждый раз вручную
- при старте системы
- собрать новое ядро со встроенным модулем
Идея эксперемента в том чтоб не трогать родную прошивку, а запускаясь с флешки подхватывать настройки и основной образ из памяти NAS. В случае если что то пойдет не так, достаточно вытащить флешку и загрузить родную прошивку, весь софт и все контейнеры продолжат работать.
Для начала стоит посмотреть как вообще запускается система на NAS. Поэтому монтирую первый раздел mmc и смотрю что там:
grub.cfg
root@hub ~ # mount /dev/mmcblk0p1 /tmp/mmc
root@hub ~ # cat /tmp/mmc/EFI/BOOT/grub.cfg
set default="0"
set timeout="1"
set fallback=1
# Serial console id:
#set gfxpayload=text
set scid="serial"
serial --speed=115200 --word=8 --parity=no --stop=1# ${scid}
terminal_output --append ${scid}
terminal_input --append ${scid}
set kver="4.14.22"
set kname="bzImage-${kver}"
set kcmdline="root=/dev/ram0 rootfstype=ramfs rootwait console=ttyS0,115200n8 net.ifnames=0 acpi_enforce_resources=lax"
menuentry "Linux ${kver}" {
Alpha_CRC32_Reset
search --no-floppy --set=root --label wdnas_kernel
Alpha_CRC32_Check /uImage
search --no-floppy --set=root --label wdnas_initramfs
Alpha_CRC32_Check /uRamdisk
search --no-floppy --set=root --label wdnas_kernel
linux /uImage ${kcmdline}
search --no-floppy --set=root --label wdnas_initramfs
initrd /uRamdisk
}
menuentry "Rescure Mode - Linux ${kver}" {
Alpha_CRC32_Reset
search --no-floppy --set=root --label wdnas_rescue_fw
Alpha_CRC32_Check /uImage /uRamdisk
linux /uImage ${kcmdline}
initrd /uRamdisk}
Стоит обычный grub который:
- ищет по метке нужные разделы
- проверяет CRC ядра и initramfs
- загружает ядро и initramfs
Примечательно то что несмотря на присутствие нормального bios, ядро и initramfs упакованы в формате u-boot. Модуль ядра может загружаться несколькими способами:
- вместе с ядром
- в момент загрузки initramfs
- при запуске основных сервисов
Значит, для того чтоб добавить минимальную поддержку zigbee достаточно будет собрать модуль ядра и загрузить его на этапе загрузки initramfs. Пользуясь моментом попробую также обновить busybox.
Ядро
WD предоставляет исходники для open source компонентов, ими я и воспользуюсь. Для этого идем на сайт поддержки и качаем GPL исходники для WD My Cloud PR2100. :
wget https://download.wdc.com/gpl/WDMyCloud_PR2100_GPL_v5.31.102_20250605.tar.gz -O PR2100_gpl.tar.gzНа данный момент последняя 5.31.102_20250605
$ ls -l
итого 48
-rwxr-xr-x 1 user user 741 июн 5 09:48 build_kernel.sh
-rwxr-xr-x 1 user user 1710 июн 5 09:48 build_open-source-packages.sh
drwxr-xr-x 131 user user 4096 июн 5 09:47 deb-prebuilt
drwxr-xr-x 4 user user 4096 июн 5 09:47 dockerfile
drwxr-xr-x 3 user user 4096 июн 5 09:47 firmware
drwxr-xr-x 2 user user 4096 июн 5 09:48 gpl-deb-packages
drwxr-xr-x 3 user user 4096 авг 12 18:13 kernel
drwxr-xr-x 2 user user 4096 июн 5 09:42 LICENSES
drwxr-xr-x 4 user user 4096 июн 5 09:48 nas
drwxr-xr-x 59 user user 4096 июн 5 09:48 open-source-packages
drwxr-xr-x 2 user user 4096 июн 5 09:47 RestSDK
-rwxr-xr-x 1 user user 1202 июн 5 09:48 WDMyCloud_PR2100_GPL_Release_Notes_v5.31.102.txt
$ ls -l kernel/
-rwxr-xr-x 1 user user 929 июн 5 09:47 build_ext_kernel_module.sh
-rw-r--r-- 1 user user 161423529 июн 5 09:47 linux-4.14.22.tar.gz
-rw-r--r-- 1 user user 22292 июн 5 09:47 netatop-2.0.tar.gz
# Распаковываем архив с ядром
$ tar -xf kernel/linux-4.14.22.tar.gz -C kernel/
$ cd linux-4.14.22
После распаковки смотрим что внутри
По хорошему для того чтобы все было совместимо лучше собирать с той же версией gcc что и оригинальное ядро. Поэтому имеет смысл использовать докер контейнер для сборки ядра и оcтального.
dockerfile
FROM debian:buster
ENV DEBIAN_FRONTEND=noninteractive
RUN sed -i 's/deb.debian/archive.debian/g' /etc/apt/sources.list
RUN apt update && apt install -y build-essential bc kmod cpio flex bison libssl-dev wget vim libncurses-dev libelf-dev u-boot-tools nano && rm -rf /var/lib/apt/lists/*
WORKDIR /kernel_build
CMD ["/bin/bash"]
# сборка контейнера
docker build -t kernel-builder-buster:latest .
# запуск из каталога с исходниками
docker run --rm -it -v ./kernel/linux-4.14.22:/kernel_build --workdir /kernel_build kernel-builder-buster:latestМодули ядра
В целом, в каталоге с исходниками уже есть готовый конфиг. Так как мне нужны драйвера для zigbee координатора, а еще я хочу подключить BT адаптер, надо включить соответствующие модули для сборки. Пробуем:
# ./xbuild.sh clean
# scripts/config --module CONFIG_USB_ACM
# scripts/config --module CONFIG_BT
# scripts/config --module CONFIG_BT_HCIBTUSB
# scripts/config --module CONFIG_BT_RTL
# scripts/config --disable BT_LEDS
# scripts/config --disable LEDS_TRIGGERS
# sed -i '/Realtek.*Bluetooth devices/ a\ { USB_DEVICE(0x0bda, 0xa729), .driver_info = BTUSB_REALTEK },' kernel/linux-4.14.22/drivers/bluetooth/btusb.c
# make olddefconfig
# ./xbuild.sh build
...
# find . |grep "bluetooth.ko\|btusb.ko\|btrtl.ko\|cdc-acm.ko\|ecdh_generic.ko\|btintel.ko\|btbcm.ko" |grep modules
./_install/lib/modules/4.14.22/kernel/crypto/ecdh_generic.ko
./_install/lib/modules/4.14.22/kernel/net/bluetooth/bluetooth.ko
./_install/lib/modules/4.14.22/kernel/drivers/usb/class/cdc-acm.ko
./_install/lib/modules/4.14.22/kernel/drivers/bluetooth/btbcm.ko
./_install/lib/modules/4.14.22/kernel/drivers/bluetooth/btusb.ko
./_install/lib/modules/4.14.22/kernel/drivers/bluetooth/btrtl.ko
./_install/lib/modules/4.14.22/kernel/drivers/bluetooth/btintel.ko
# mkdir -p mods
# find _install/ \( -name "bluetooth.ko" -o -name "btusb.ko" -o -name "btrtl.ko" -o -name "cdc-acm.ko" -o -name "ecdh_generic.ko" -o -name "btintel.ko" -o -name "btbcm.ko" \) -exec cp -v {} mods/ \;
'_install/lib/modules/4.14.22/kernel/crypto/ecdh_generic.ko' -> 'mods/ecdh_generic.ko'
'_install/lib/modules/4.14.22/kernel/net/bluetooth/bluetooth.ko' -> 'mods/bluetooth.ko'
'_install/lib/modules/4.14.22/kernel/drivers/usb/class/cdc-acm.ko' -> 'mods/cdc-acm.ko'
'_install/lib/modules/4.14.22/kernel/drivers/bluetooth/btbcm.ko' -> 'mods/btbcm.ko'
'_install/lib/modules/4.14.22/kernel/drivers/bluetooth/btusb.ko' -> 'mods/btusb.ko'
'_install/lib/modules/4.14.22/kernel/drivers/bluetooth/btrtl.ko' -> 'mods/btrtl.ko'
'_install/lib/modules/4.14.22/kernel/drivers/bluetooth/btintel.ko' -> 'mods/btintel.ko'
После окончания работы ./xbuild build получил готовое ядро:
# ls -l arch/x86_64/boot/
total 4752
lrwxrwxrwx 1 root root 22 Aug 12 16:01 bzImage -> ../../x86/boot/bzImage
-rw-r--r-- 1 root root 4862800 Aug 12 16:02 uImage
# file arch/x86_64/boot/uImage
arch/x86_64/boot/uImage: u-boot legacy uImage, kernel, Linux/Intel x86, OS Kernel Image (gzip), 4862736 bytes, Tue Aug 12 16:02:32 2025, Load Address: 0x00000000, Entry Point: 0x00000000, Header CRC: 0xAAAA538A, Data CRC: 0xFFFFFFFFГотовое ядро
Собранные модули лежат в каталоге mods, ради теста подкинул cdc-acm.ko на NAS и загрузил:
rsync -avz mods/lib/modules/4.14.22/kernel/drivers/usb/class/cdc-acm.ko hub.cave-labs.ru:/home/root/
root@hub ~ # ls -l
-rw-r--r-- 1 root root 58120 Aug 13 12:48 cdc-acm.ko
root@hub ~ # insmod ./cdc-acm.ko
root@hub ~ # dmesg |tail -n 10
...
[404322.428280] usbcore: registered new interface driver cdc_acm
[404322.434723] cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters
[404392.169098] usb 1-3: new full-speed USB device number 2 using xhci_hcd
[404392.305852] cdc_acm 1-3:1.0: ttyACM0: USB ACM device
[404392.312471] sdhci-pci 0000:00:12.0: SDHCI controller found [8086:2296] (rev 35)
root@hub ~ # ls -l /dev/ttyACM0
crw-rw---- 1 root root 166, 0 Aug 13 12:57 /dev/ttyACM0
О_о внезапная победа, после подключения модуля zigbee свисток обнаружился и появился интерфейс ttyACM0, его можно прокинуть в докер и полноценно пользоваться homeassistant. Теперь ядро как таковое уже не нужно и можно обойтись только модулем.
Bluetooth
Важно: далее описаны действия и предположения применительны только к моему bt адаптеру: Realtek Bluetooth 5.1 / 5.3 Radio (RTL8761BU / RTL8761BUV). Как оказалось, просто так собрать и закинуть модуль недостаточно, нужно еще и firmware:
[ 123.812922] Bluetooth: Core ver 2.22
[ 123.817019] NET: Registered protocol family 31
[ 123.821989] Bluetooth: HCI device and connection manager initialized
[ 123.829105] Bluetooth: HCI socket layer initialized
[ 123.834592] Bluetooth: L2CAP socket layer initialized
[ 123.840255] Bluetooth: SCO socket layer initialized
[ 124.368593] usbcore: registered new interface driver btusb
[ 124.654999] usbcore: registered new interface driver cdc_acm
[ 124.661362] cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters
....
[ 209.500873] Bluetooth: hci0: rtl: examining hci_ver=0a hci_rev=000b lmp_ver=0a lmp_subver=8761
[ 209.500878] Bluetooth: hci0: rtl: loading rtl_bt/rtl8761a_config.bin
[ 209.500904] bluetooth hci0: Direct firmware load for rtl_bt/rtl8761a_config.bin failed with error -2
[ 209.500907] Bluetooth: hci0: rtl: loading rtl_bt/rtl8761a_fw.bin
[ 209.500925] bluetooth hci0: Direct firmware load for rtl_bt/rtl8761a_fw.bin failed with error -2
[ 209.500927] Bluetooth: hci0: Failed to load rtl_bt/rtl8761a_fw.binНашел firmware, подкинул:
~ # dmesg | grep -iE 'bluetooth|hci0'
[ 10.704413] bluetooth: Unknown symbol crypto_ecdh_key_len (err 0)
[ 10.711398] bluetooth: Unknown symbol crypto_ecdh_encode_key (err 0)
[ 11.052748] Bluetooth: Core ver 2.22
[ 11.061735] Bluetooth: HCI device and connection manager initialized
[ 11.068835] Bluetooth: HCI socket layer initialized
[ 11.074290] Bluetooth: L2CAP socket layer initialized
[ 11.079939] Bluetooth: SCO socket layer initialized
[ 11.110730] Bluetooth: hci0: rtl: examining hci_ver=0a hci_rev=000b lmp_ver=0a lmp_subver=8761
[ 11.121873] Bluetooth: hci0: rtl: loading rtl_bt/rtl8761a_config.bin
[ 11.137194] Bluetooth: hci0: rtl: loading rtl_bt/rtl8761a_fw.bin
[ 11.145748] Bluetooth: hci0: rom_version status=0 version=1
[ 11.153317] Bluetooth: hci0: unknown project id 14обнадеживает
unknown project id 14 говорит нам о том что модуль ядра не знает что это. Попробуем пропатчить, добавляем в drivers/bluetooth/btrtl.c :
{ RTL_ROM_LMP_8761A, 14 },Собираем и пытаемся проверить работоспособность:
root@hub:/# bluetoothctl show
Controller XX:XX:XX:XX:XX:XX (public)
Name: BlueZ 5.66
Alias: BlueZ 5.66
Class: 0x00000000
Powered: yes
Discoverable: no
DiscoverableTimeout: 0x000000b4
Pairable: no
UUID: Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb)
UUID: Generic Access Profile (00001800-0000-1000-8000-00805f9b34fb)
UUID: PnP Information (00001200-0000-1000-8000-00805f9b34fb)
UUID: A/V Remote Control Target (0000110c-0000-1000-8000-00805f9b34fb)
UUID: A/V Remote Control (0000110e-0000-1000-8000-00805f9b34fb)
UUID: Device Information (0000180a-0000-1000-8000-00805f9b34fb)
Modalias: usb:v1D6Bp0246d0542
Discovering: no
Roles: central
Roles: peripheral
Advertising Features:
ActiveInstances: 0x00 (0)
SupportedInstances: 0x05 (5)
SupportedIncludes: tx-power
SupportedIncludes: appearance
SupportedIncludes: local-nameRAM диск
Ядро трогать уже нет смысла, а лезть в файлы прошивки мне пока что не очень хочется, поэтому самый логичный на мой взгляд выход это модифицировать initramfs так чтоб на этапе загрузки в ядро загружались модули с флешки. В качестве initramfs загружается uRamdisk с раздела с меткой wdnas_initramfs. Посмотрим что там:
root@hub ~ # mount /dev/mmcblk0p3 /tmp/mmc
root@hub ~ # ls -l /tmp/mmc/
-rw-r--r-- 1 root root 4004704 Jul 6 16:10 uRamdisk
root@hub ~ #
$ rsync -az hub.cave-labs.ru:/tmp/mmc/uRamdisk ./
$ file uRamdisk
uRamdisk: u-boot legacy uImage, Initramfs, Linux/Intel x86, RAMDisk Image (gzip), 4004640 bytes, Mon May 19 11:06:01 2025, Load Address: 00000000, Entry Point: 00000000, Header CRC: 0XB477CB30, Data CRC: 0XE9E4BF5Aмонтируем раздел с initramfs и достаем uRamdisk
$ tail -c +65 uRamdisk > uRamdisk.raw
$ mkdir -p uRam
$ cd uRam/
$ gzip -dc ../uRamdisk.raw | cpio -idmvдостаем полезные данные из initramfs
В самом рамдиске ничего экзотичного нет, стартует busybox который выполняет /etc/rc.sh:
содержимое etc/rc.sh
$ cat etc/rc.sh
#!/bin/sh
#
# SPDX-FileCopyrightText: 2020 Western Digital Corporation or its affiliates.
#
# SPDX-License-Identifier: GPL-2.0-or-later
#
MOUNT_CMD="busybox mount"
UMOUNT_CMD="busybox umount"
#echo "initramfs: remount %root%"
#${MOUNT_CMD} -o remount -w %root% /
echo "initramfs: mount all in /etc/fstab"
${MOUNT_CMD} -a
sleep 1
${MOUNT_CMD} -t proc proc /proc
${MOUNT_CMD} -a
# Cgroup support
CGROUP_ROOT=/sys/fs/cgroup
${MOUNT_CMD} -t cgroup -o rw,memory,cpu cgroup ${CGROUP_ROOT}
echo 8192 > /proc/sys/vm/min_free_kbytes
echo 4096 > /proc/sys/net/core/somaxconn
echo 16777216 > /proc/sys/net/core/wmem_max
echo 16777216 > /proc/sys/net/core/rmem_max
echo 163840 > /proc/sys/net/core/wmem_default
echo 163840 > /proc/sys/net/core/rmem_default
echo 3000 > /proc/sys/net/core/netdev_max_backlog
echo 1800 > /proc/sys/net/ipv4/tcp_keepalive_time
echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout
echo 2048 > /proc/sys/net/ipv4/tcp_max_syn_backlog
#echo 1 > /proc/sys/net/ipv4/tcp_syncookies
echo 0 > /proc/sys/net/ipv4/tcp_timestamps
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
touch /dev/mdev.seq
#mount -t devtmpfs none /dev
mkdir /dev/pts
${MOUNT_CMD} -t devpts -o noexec,nosuid,gid=5,mode=0620 devpts /dev/pts || true
# Hack for shell to get job control
#if [ ! -L /dev/console ]; then
# rm -f /dev/console
# ln -s /dev/ttyS0 /dev/console
#fi
WAIT_DEV=4
# Wait for EMMC & FileSystem being recognized,
# $WAIT_DEV secs should be enough, sad for this.
echo -n "initramfs: wait for EMMC being setup"
for i in $(seq 1 ${WAIT_DEV}); do
echo -n " ."
sleep 1
done
echo
# This one need the mount from util-linux, busybox's one can't handle LABEL
#echo "initramfs: mounting label=image.cfs partition"
#mount -t ext4 LABEL=image.cfs /usr/local/tmp
chk_image
#echo "initramfs: mounting squashfs'd image.cfs"
#${MOUNT_CMD} -t squashfs -o loop /usr/local/tmp/image.cfs /usr/local/modules
echo -n "initramfs: checking integrity of system partitions "
ucpd="$(blkid | grep 'wdnas_efi' | awk -F: '{ print $1}')"
ucpd="${ucpd:0:13}"
echo "($ucpd)"
# 1st: vfat EFI
if stat "${ucpd}1" >/dev/null 2>&1; then
dosfsck -p ${ucpd}1 2>/dev/null
else
echo "initramfs: cannot find devnode: ${ucpd}1"
fi
# all others ext4
for i in $(seq 2 9); do
if stat "${ucpd}$i" >/dev/null 2>&1; then
if [ "$i" = "6" ] || [ "$i" = "9" ]; then
e2fsck -p "${ucpd}$i" 2>/dev/null || e2fsck -f -y "${ucpd}$i"
else
e2fsck -p "${ucpd}$i" 2>/dev/null
fi
else
echo "initramfs: cannot find devnode: ${ucpd}$i"
fi
done
echo "-------- Sleep 1 Second for recovering journal -------"
sleep 1 # sleep 1 second for recovering journal.
echo "-------------------------------"
echo "initramfs: mount tmpfs config partition"
mkdir /usr/local/tmp_wdnas_config
mount -t ext4 -o rw,noatime,nodiratime,sync LABEL=wdnas_config /usr/local/tmp_wdnas_config
rm -rf /usr/local/config
if [ ! -e /usr/local/tmp_wdnas_config/config ]; then
mkdir /usr/local/tmp_wdnas_config/config
fi
ln -s /usr/local/tmp_wdnas_config/config /usr/local/config
echo "initramfs: running realworld system_init"/usr/local/modules/script/system_init
Если кратко, то процесс запуска такой:
- монтируются основные разделы и выполняются основные настройки
- запускается chk_image, бинарник который творит магию с проверкой образа image.cfs и монтированием его в /usr/local/modules.
- проверяются системные разделы на mmc
- запускается system_init который загружает модули ядра и стартует все сервисы, включая web панель управления.
В теории можно забить на chk_image и просто смонтировать образ раскоментировав маунты в скрипте:
# This one need the mount from util-linux, busybox's one can't handle LABEL
echo "initramfs: mounting label=image.cfs partition"
mount -t ext4 LABEL=image.cfs /usr/local/tmp
#chk_image
echo "initramfs: mounting squashfs'd image.cfs"
${MOUNT_CMD} -t squashfs -o loop /usr/local/tmp/image.cfs /usr/local/modulesНо я решил оставить chk_image как есть, а после него свою магию творить буду я. А именно, монтирование оверлея:
# This one need the mount from util-linux, busybox's one can't handle LABEL
#echo "initramfs: mounting label=image.cfs partition"
#mount -t ext4 LABEL=image.cfs /usr/local/tmp
chk_image
#echo "initramfs: mounting squashfs'd image.cfs"
#${MOUNT_CMD} -t squashfs -o loop /usr/local/tmp/image.cfs /usr/local/modules
# ---------- overlay + custom kernel modules, from usb ----------
USB_OVERLAY=/mnt/usb_overlay
LOWER=/usr/local/modules
LOWER_RO=/usr/local/modules_ro
CUSTOM=$USB_OVERLAY/upper/custom_modules
mkdir -p "$USB_OVERLAY" "$LOWER_RO"
if busybox mount -t ext4 -o rw LABEL=usb_overlay "$USB_OVERLAY"; then
mkdir -p "$USB_OVERLAY/upper" "$USB_OVERLAY/work"
[ -f "$CUSTOM/exportfs.ko" ] && busybox insmod "$CUSTOM/exportfs.ko" 2>&1
[ -f "$CUSTOM/overlay.ko" ] && busybox insmod "$CUSTOM/overlay.ko" 2>&1
if grep -q overlay /proc/filesystems; then
busybox mount --move "$LOWER" "$LOWER_RO" &&
busybox mount -t overlay overlay -o lowerdir=$LOWER_RO,upperdir=$USB_OVERLAY/upper,workdir=$USB_OVERLAY/work "$LOWER" &&
echo "initramfs: overlay active" ||
{ echo "initramfs: overlay mount failed, restoring squashfs"; busybox mount --move "$LOWER_RO" "$LOWER"; }
else
echo "initramfs: kernel has no overlay support, staying stock"
fi
else
echo "initramfs: usb_overlay not found, staying stock"
fiВключение хаков и кастомных модулей:
# bluetooth firmware
mkdir -p /lib/firmware/rtl_bt
cp "$USB_OVERLAY/firmware/rtl_bt/"* /lib/firmware/rtl_bt/ 2>&1 && echo "initramfs: bt firmware installed"
# docker storage-driver override
if [ -f "$USB_OVERLAY/upper/files/docker_daemon.json" ]; then
mkdir -p /etc/docker
cp "$USB_OVERLAY/upper/files/docker_daemon.json" /etc/docker/daemon.json
echo "initramfs: docker daemon.json installed from usb overlay"
fi# custom kernel modules, order-independent retry
echo "initramfs: inserting custom kernel modules"
MOD_DIR=/usr/local/modules/custom_modules
PENDING=$(ls "$MOD_DIR"/*.ko 2>/dev/null)
PASS=0
while [ -n "$PENDING" ] && [ "$PASS" -lt 10 ]; do
PASS=$((PASS + 1)); NEXT=""
for m in $PENDING; do
OUT=$(busybox insmod "$m" 2>&1)
if [ -z "$OUT" ] || echo "$OUT" | grep -qi "File exists"; then
echo "initramfs: loaded $(basename "$m")"
else
NEXT="$NEXT $m"
fi
done
[ "$NEXT" = "$PENDING" ] && break
PENDING=$NEXT
done
[ -n "$PENDING" ] && echo "initramfs: WARNING, not loaded:$PENDING"Так же не лишним будет выключить проверку разделов:
echo -n "initramfs: checking integrity of system partitions "
ucpd="$(blkid | grep 'wdnas_efi' | awk -F: '{ print $1}')"
ucpd="${ucpd:0:13}"
echo "($ucpd)"
# 1st: vfat EFI
if stat "${ucpd}1" >/dev/null 2>&1; then
dosfsck -p ${ucpd}1 2>/dev/null
else
echo "initramfs: cannot find devnode: ${ucpd}1"
fi
# all others ext4
for i in $(seq 2 9); do
if stat "${ucpd}$i" >/dev/null 2>&1; then
if [ "$i" = "6" ] || [ "$i" = "9" ]; then
e2fsck -p "${ucpd}$i" 2>/dev/null || e2fsck -f -y "${ucpd}$i"
else
e2fsck -p "${ucpd}$i" 2>/dev/null
fi
else
echo "initramfs: cannot find devnode: ${ucpd}$i"
fi
doneубираем либо коментируем этот кусок
В конце монтируем домашний каталог для рута:
# ---------- Persistent partition for /home/root ----------
echo "initramfs: mounting usb_home to /home/root"
mkdir -p /home/root
mount -t ext4 LABEL=usb_home /home/root 2>/dev/null || echo "initramfs: usb_home not found, /home/root still ramfs"
Раз уж я полез в initramfs, то неплохо было бы обновить busybox:
# качаем последний busybox
wget https://busybox.net/downloads/busybox-1.37.0.tar.bz2
# Распаковываем
tar xf busybox-1.37.0.tar.bz2
cd busybox-1.37.0
# заходим в контейнер для сборки
docker run --rm -it -v ./:/kernel_build --workdir /kernel_build kernel-builder-buster:latest
# сборка
make defconfig
make -j$(nproc) LDFLAGS="--static"
# проверка результата
./busybox
BusyBox v1.37.0 (2025-08-12 17:11:32 UTC) multi-call binary.
# Копирование в initramfs
cd ..
cp -f busybox-1.37.0/busybox uRam/bin/busyboxсборка и замена busybox
В конце запаковываем uRamdisk обратно:
find . | cpio -o -H newc | gzip > ../uRamdisk.raw.new
cd ..
mkimage -A x86 -O linux -T ramdisk -C gzip -a 0x0 -e 0x0 -n "New uRamdisk" -d uRamdisk.raw.new uRamdisk.new
$ file uRamdisk.new
uRamdisk.new: u-boot legacy uImage, New uRamdisk, Linux/Intel x86, RAMDisk Image (gzip), 5229658 bytes, Tue Aug 12 18:11:13 2025, Load Address: 00000000, Entry Point: 00000000, Header CRC: 0X29AAA346, Data CRC: 0X594DA0BF
Rootfs
После монтирования image.cfs, запускается system_init. Из интересного:
- функция Insert_Kernel_Driver которая подгружает все модули из /usr/local/modules/driver/
- создание симлинков из modules в bin/sbin/usr и копирование конфигов в etc происходит на этом этапе.
- если знать где хранятся конфиги то можно изменить или переопределить стандартные настройки, например повесить стандартный web ui на нестандартный порт и проксировать через NPM или изменить версию ПО там самым избавиться от автообновления.
# Монтируем на NAS
# mount /dev/mmcblk0p4 /tmp/mmc
# ls -l /tmp/mmc/
-rw-r--r-- 1 root root 10 Aug 12 21:20 _un
-rw-r--r-- 1 root root 209004544 Jul 6 16:10 image.cfs
# Копируем к себе
$ rsync -az hub.cave-labs.ru:/tmp/mmc/image.cfs ./Шпаргалка для squashfs:
# Посмотреть параметры оригинала
unsquashfs -s -o 2048 image.cfs
# Распаковывка
unsquashfs -o 2048 image.cfs
# Упаковка обратно
mksquashfs squashfs-root new_image.cfs -comp xz -b 131072
# Вытаскиваем хедер из оригинала
dd if=image.cfs of=header.bin bs=2048 count=1
# Склеиваем вместе
cat header.bin new_image.cfs > image_patched.cfsпроверить готовый образ можно через binwalk
На этом из интересного все, сильно копаться в прошивке пока что не имеет особого смысла.
Готовим флешку
Для начала посмотрим какие есть разделы есть на mmc:
/dev/mmcblk0p1: LABEL_FATBOOT="wdnas_efi" LABEL="wdnas_efi" UUID="640F-1CC4" BLOCK_SIZE="512" TYPE="vfat" PARTLABEL="EFI System" PARTUUID="c7d73ff4-bb7a-419c-b76c-f67325cd6015"
/dev/mmcblk0p2: LABEL="wdnas_kernel" UUID="001208be-c41c-4f72-9a0c-fd745ce42aca" BLOCK_SIZE="1024" TYPE="ext4" PARTLABEL="kernel" PARTUUID="d58b27ab-f44e-4eb9-86e8-68d04932f5d5"
/dev/mmcblk0p3: LABEL="wdnas_initramfs" UUID="8ce8b16c-0eae-46b6-806c-cbaa0c744785" BLOCK_SIZE="1024" TYPE="ext4" PARTLABEL="ramdisk" PARTUUID="7201983b-a751-4262-aa12-464e627c76ff"
/dev/mmcblk0p4: LABEL="wdnas_image.cfs" UUID="cac6bb5a-5fdd-472f-a9ab-eb5e534bdcbc" BLOCK_SIZE="4096" TYPE="ext4" PARTLABEL="image.cfs" PARTUUID="6bec99c3-813f-4555-894b-2830b578edd2"
/dev/mmcblk0p5: LABEL="wdnas_rescue_fw" UUID="366ca9d2-601d-4681-8e10-27067ff6bb9a" BLOCK_SIZE="1024" TYPE="ext4" PARTLABEL="rescue_fw" PARTUUID="ae0f3c54-724b-4100-87a6-8876642ce1f3"
/dev/mmcblk0p6: LABEL="wdnas_config" UUID="eccec0dd-f604-44e0-8ec5-8a0b5a57f1bf" BLOCK_SIZE="1024" TYPE="ext4" PARTLABEL="config" PARTUUID="a20cf9b0-f7a4-49c5-a734-417631d791fb"
/dev/mmcblk0p9: LABEL="wdnas_backup" UUID="c586d83b-d618-4257-a03d-a24d1b78e004" BLOCK_SIZE="1024" TYPE="ext4" PARTLABEL="backup" PARTUUID="8cbc089c-930f-4e9b-9489-d31d0d81b662"описание разделов
mmcblk0p1 - efi раздел
mmcblk0p2 - раздел с ядром
mmcblk0p3 - initramfs
mmcblk0p4 - образ image.cfs
mmcblk0p5 - rescue ядро и initramfs
mmcblk0p6 - судя по всему раздел с временными конфигами(сбрасываются через reset)
mmcblk0p9 - постоянные конфиги
По сути для того чтобы стартануть, нам нужен загрузочный раздел c EFI, ядро и initramfs, а все остальное может загружаться из памяти NAS. Первая попытка имела частичный успех, NAS запустился и работал как полный сток, однако после некоторых тестов выяснилось что у меня отвалился докер, причем он не запускается что с флешки что с родной прошивки. Помогло переустановить сам докер и подкинуть все его старые файлы, ну и заново задеплоить все приложения.
Новая схема разделов на флешке выглядит так:
NAME FSTYPE LABEL
sdc
├─sdc1 vfat
├─sdc2 ext2 usb_wdnas_kernel
├─sdc3 ext2 usb_wdnas_initrd
├─sdc4 ext2 usb_overlay
└─sdc6 ext2 usb_homeЗагрузчик: тут без фантазии, берем содержимое efi с NAS, правим grub и запускаемся с usb. Примерный конфиг:
set default="0"
set timeout="1"
set fallback=1
# Serial console id:
#set gfxpayload=text
set scid="serial"
serial --speed=115200 --word=8 --parity=no --stop=1# ${scid}
terminal_output --append ${scid}
terminal_input --append ${scid}
set kver="4.14.22"
set kname="bzImage-${kver}"
set kcmdline="root=/dev/ram0 rootfstype=ramfs rootwait console=ttyS0,115200n8 net.ifnames=0 acpi_enforce_resources=lax"
menuentry "WD PR2100 - USB boot" {
search --no-floppy --set=root --label usb_wdnas_kernel
linux /uImage ${kcmdline}
search --no-floppy --set=root --label usb_wdnas_initrd
initrd /uRamdisk
}
Копируем загрузчик, ядро, initramfs и оверлей на флешку, вставляем в NAS, подключаемся к UART и молимся пробуем загрузиться

Ладно, хотя бы видно проблему. По какой то причине не видно разделов о чем говорит "no such device". Эксперементально пришел к тому что выше ext2 grub не видит.

Иии.. у меня случился нервный срыв снова отвалился докер. в случае без usb:
root@hub ~ # docker ps
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
root@hub ~ # /mnt/HD/HD_a2/Nas_Prog/docker/docker/start.sh
DOCKER START: setup daemon
DOCKER START: start daemon
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
а если загрузиться с флешки то:
~ # ps aux |grep docker
5396 root 0:00 /sbin/dockerd --ip-masq=true
5406 root 0:00 containerd --config /var/run/docker/containerd/containerd.toml --log-level info
6221 root 0:00 grep docker
~ # docker ps --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
~ #т.е. в сток прошивке докер даже не запустился, а на usb запустился но не нашел контейнеров. Но(!) в этот раз я решил разобраться в проблеме и предварительная причина: storage драйвер докера почему то поменялся на overlay2. Видимо потому что я ранее подгружал модуль ядра для монтирования оверлея. Проверяем:
mkdir -p /etc/docker
cat > /etc/docker/daemon.json << 'EOF'
{
"storage-driver": "vfs"
}
EOF
rm -rf /mnt/HD/HD_a2/Nas_Prog/_docker/overlay2
/mnt/HD/HD_a2/Nas_Prog/docker/start.sh /mnt/HD/HD_a2/Nas_Prog/docker/docker
docker ps --all
docker info | grep -i "storage driver"временный конфиг и перезапуск для применения изменений
Решение: насколько я догадываюсь, в сток прошивке используется последний выбранный драйвер, в таком случае для usb пропишем явно:
{
"storage-driver": "vfs"
}upper/files/docker_daemon.json
При каждом запуске применяем изменения:
# docker storage-driver override
if [ -f "$USB_OVERLAY/upper/files/docker_daemon.json" ]; then
mkdir -p /etc/docker
cp "$USB_OVERLAY/upper/files/docker_daemon.json" /etc/docker/daemon.json
echo "initramfs: docker daemon.json installed from usb overlay"
fiподкидываем нужный нам конфиг
Перезагружаемся и проверяем:
~ # docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
aa09752548cc gitea/gitea:latest "/usr/bin/entrypoint…" 4 days ago Up 3 seconds 0.0.0.0:3000->3000/tcp, 0.0.0.0:222->22/tcp gitea
1862422d2aa4 pihole/pihole:latest "start.sh" 3 weeks ago Up 1 second (health: starting) 67/udp, 0.0.0.0:53->53/tcp, 0.0.0.0:53->53/udp, 123/udp, 443/tcp, 0.0.0.0:9001->80/tcp pihole
9e6504313333 homeassistant/home-assistant:stable "/init" 5 months ago Up 3 seconds 0.0.0.0:8123->8123/tcp homeassistant
ffb703de9066 koenkk/zigbee2mqtt:latest "docker-entrypoint.s…" 9 months ago Up 4 seconds 8080/tcp zigbee2mqtt
0b74633f83b1 ghost:latest "docker-entrypoint.s…" 9 months ago Up 3 seconds 0.0.0.0:2368->2368/tcp ghost
b24ea3ec4626 mariadb:latest "docker-entrypoint.s…" 9 months ago Up 2 seconds 3306/tcp mariadb
c0e071824faa jc21/nginx-proxy-manager:latest "/init" 9 months ago Up 2 seconds 0.0.0.0:81->81/tcp, 0.0.0.0:443->443/tcp, 0.0.0.0:8080->80/tcp nginx_proxy
194a8242e044 portainer/portainer-ce "/portainer" 9 months ago Up 5 seconds 8000/tcp, 9443/tcp, 0.0.0.0:9000->9000/tcp Ура победа, все работает
Итог
Итак это победа, мы имеем флешку которая не меняет стоковую прошивку, но расширяет её возможности. В то же время можно вытащить флешку и загрузиться в полностью оригинальную прошивку и все продолжит работать. Бонусом имеем:
- обновленный busybox
- поддержка zigbee и bluetooth
- возможность дальнейших кастомизаций с помощью overlay.
Плюшки оверлея:
- overlay/upper/busybox-bin - path который прописывается на постоянном /home/root и добавляет новые алиасы для busybox
- overlay/upper/custom_modules - собранные модули ядра для поддержки оборудования
- overlay/upper/files - конфиг для докера с переопределением storage-driver для обратной совместимости