ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

绿联 DH2100 私有云:从零装上 Docker,并原生跑 MediaGo(附所有踩坑)

绿联 DH2100 私有云:从零装上 Docker,并原生跑 MediaGo(附所有踩坑)

设备:绿联私有云DH2100(Realtek RTD1296 / ARM Cortex‑A53 四核 1.4GHz /aarch64 64 位/ 2GB DDR4 / 8GB eMMC / 千兆)
系统:UGOS 18.06(⚠️ 不是 Debian,也不是 UGOS Pro)
成果:Docker 24.0.9 跑起来 + MediaGo原生 arm64(不用 qemu 模拟)+ 下载直接落 NAS 自己的硬盘

网上关于 DH2100 能不能装 Docker 的说法非常混乱,有说不支持的、有说 x86 处理器+2.5G 网口的(跟官方参数完全不符)。这篇是在我自己机器上一步步实测通过的完整记录,包括三个别人没提过的致命坑。


一、先搞清楚你面对的是什么系统

开启 SSH 登录后第一条命令:

cat/etc/os-release# NAME="UGOS"# VERSION="18.06-SNAPSHOT" ← 这个版本号是 OpenWrt 的命名习惯uname-a# Linux UGREEN-xxxx 4.9.119 #0 SMP PREEMPT ... aarch64 GNU/Linuxdf-h# /rom 243.8M 243.8M 0 100% /rom ← OpenWrt 典型特征# /overlay 5.1G 932.6M 3.9G 19% /overlay# /dev/md0 9.0T 7.7T 877G 90% /mnt/md0 ← 你的数据盘# /dev/md1 9.0T 172.8G 8.4T 2% /mnt/md1

结论:UGOS 是基于 OpenWrt 18.06 定制的嵌入式系统,这决定了后面所有做法:

特性影响
无 apt / dpkg / opkg / yum❌ 不能apt install docker,只能用静态二进制
busybox ash(无 nohup、无 setsid)守护进程要用start-stop-daemon -b
busybox 的timeout是旧语法要写timeout -t 60 cmd,不是timeout 60 cmd
wget 直接 Segmentation fault下载别用它,用 scp 或容器内的网络栈
内核 4.9.119,未编译 POSIX mqueue⚠️ 最大的坑,见第四节

二、拿到 root:端口是 922,不是 22

绿联的「远程调试」开启后,SSH 端口是922:

ssh-p922<你的账号>@192.168.0.101# 普通账号能进,但不在 sudoers,提不了权ssh-p922root@192.168.0.101# ← 要用这个

root 密码就是 UGOS 调试页面给的验证码(一般 3 天有效)。登进去确认:

id# uid=0(root) gid=0(root) groups=0(root),1023(media_rw)

坑 1:用普通账号登录后会发现sudo存在(且是 setuid root),但会告诉你
xxx is not in sudoers file。而且sudo 不带 TTY 时是静默失败的——不加-tt你什么都看不到,
很容易误判为"sudo 卡住了"。


三、装 Docker(静态二进制方案)

1. 选版本:别用最新的

4.9 内核配 Docker 29 太激进,**Docker 24.0.9(containerd 1.7)**是最稳的。

# 在本机下载(阿里云镜像),再 scp 上去,因为 NAS 上的 wget 是坏的curl-LOhttps://mirrors.aliyun.com/docker-ce/linux/static/stable/aarch64/docker-24.0.9.tgzscp-P922docker-24.0.9.tgz root@192.168.0.101:/mnt/md1/

2. 解压到 /usr/local

ssh-p922root@192.168.0.101cd/usr/local&&tarxzf /mnt/md1/docker-24.0.9.tgz /usr/local/docker/dockerd--version# Docker version 24.0.9

3. 关键启动参数

UGOS 自带 OpenWrt 的fw3 防火墙,不能让 Docker 乱改 iptables;内核也没有 bridge/veth 模块。

exportPATH=/usr/local/docker:/usr/bin:/bin:/usr/sbin:/sbin /sbin/start-stop-daemon-S-b-x/usr/local/docker/dockerd --\--data-root=/mnt/md1/docker\--exec-root=/var/run/docker\--pidfile=/var/run/docker.pid\--iptables=false--ip6tables=false\--bridge=none
  • --data-root=/mnt/md1/docker:镜像容器全放数据盘(那块有 8.4T)
  • --iptables=false:不碰防火墙规则
  • --bridge=none:不建 docker0 ⇒代价:容器必须用--network host
  • start-stop-daemon -b:因为没有 nohup

4. 配国内镜像源

/etc/docker/daemon.json:

{"registry-mirrors":["https://docker.1ms.run","https://docker.m.daocloud.io"]}

注意:NAS 本身的 DNS 是通的,但registry-1.docker.io会被污染解析到 31.13.95.34,
所以必须配加速源才能 pull。


四、⭐ 致命坑:内核没有 mqueue,容器一律起不来

满怀信心执行:

dockerrun--rm--networkhostalpineechohi

报错:

runc create failed: unable to start container process: error mounting "mqueue" to rootfs at "/dev/mqueue": no such device

原因:内核没编译CONFIG_POSIX_MQUEUE。验证一下,全是死路:

grepmqueue /proc/filesystems# 没有ls/proc/sys/fs/mqueue# 不存在find/lib/modules-iname"*mqueue*"# 无对应模块(mqueue 本身也不是可加载模块)

--ipc=host、--privileged全都绕不过,因为 runc 无条件会挂/dev/mqueue。

解法:给 runc 套一层壳

把真正的 runc 改名,用一个脚本顶替它的位置,在创建容器前把 OCI spec 里的 mqueue 挂载改成 tmpfs(等价占位):

cd/usr/local/dockermvrunc runc.realcat>runc<<'EOF' #!/bin/sh REAL_RUNC=/usr/local/docker/runc.real BUNDLE="" prev="" for a in "$@"; do if [ "$prev" = "--bundle" ]; then BUNDLE="$a"; fi prev="$a" done if [ -n "$BUNDLE" ] && [ -f "$BUNDLE/config.json" ]; then sed -i 's/"type":[[:space:]]*"mqueue"/"type":"tmpfs"/g; s/"source":[[:space:]]*"mqueue"/"source":"tmpfs"/g' "$BUNDLE/config.json" fi exec "$REAL_RUNC" "$@" EOFchmod+x runc

然后重启 dockerd(让 containerd 走新的 runc),再试:

dockerrun--rm--networkhostalpine:3.19uname-m# aarch64 ✅ 成功

副作用:容器内 POSIX 消息队列不可用(绝大多数应用不受影响)。
UGOS 升级固件后需要重打这个补丁。


五、原生 arm64 跑 MediaGo(不用 qemu)

MediaGo 官方镜像caorushizi/mediago:3.5.0有个著名的架构 bug:

/usr/local/bin/mediago-core -> x86-64 ← 唯一的异类 /app/deps/aria2c -> arm64 ← 其余依赖都是原生 /app/deps/ffmpeg -> arm64 /app/deps/yt-dlp -> arm64 /app/deps/N_m3u8DL-RE -> arm64

在树莓派上通用解法是装 qemu 模拟。但在绿联这台没有包管理器的机器上装 qemu 很麻烦,
更好的办法:只替换那个唯一的 x86-64 主程序。

把它准备好的arm64 版 mediago-core传上去,然后挂载覆盖:

scp-P922mediago-core-arm64 root@192.168.0.101:/mnt/md1/mediago-core-arm64

⚠️ 坑 3:entrypoint 会硬塞参数

镜像的docker-entrypoint.sh长这样:

execmediago-core\--port=8899\--static-dir=/app/static\# ← arm64 版主程序不认这个 flag--enable-auth\# ← 还会强制打开鉴权--db-path=... --config-dir=... --local-dir=... --deps-dir=/app/deps\"$@"

所以即使你在docker run后面写自己的参数,它追加的--static-dir依然存在,
arm64 主程序会直接罢工:flag provided but not defined: -static-dir。

解法:用--entrypoint直接指定主程序,完全绕过这个脚本(连--enable-auth也一并绕掉,实现免登录):

dockerrun-d--namemediago\--networkhost--restartunless-stopped\-eTZ=Asia/Shanghai\--entrypoint/usr/local/bin/mediago-core\-v/mnt/md1/mediago-core-arm64:/usr/local/bin/mediago-core:ro\-v/mnt/md1/mediago/data:/app/mediago/data\-v/mnt/md1/mediago/logs:/app/mediago/logs\-v/mnt/md0/.ugreen_nas/25507/downloads/mediago:/app/mediago/downloads\docker.1ms.run/caorushizi/mediago:3.5.0\--port=8899\--db-path=/app/mediago/data/mediago.db\--config-dir=/app/mediago/data\--log-dir=/app/mediago/logs\--local-dir=/app/mediago/downloads\--deps-dir=/app/deps

-static-dir这一条要从参数里去掉(arm64 版主程序的 flag 列表里没有它,
用--help可以核对:-config-dir -db-path -deps-dir -enable-auth -local-dir -log-dir -log-level -max-runner -port -proxy -schema-path -use-proxy)。

其中/mnt/md0/.ugreen_nas/25507就是你的 SMB 共享根目录
(从/etc/config/samba4里option path查得到),
所以下载的文件会直接出现在 NAS 正常的文件管理里,不用再绕一层网络挂载。


六、验证(每条都要做)

# 1. 容器是否稳定dockerps# mediago Up 2 minutes# 2. Web 是否可访问curl-s-o/dev/null-w"%{http_code}\n"http://192.168.0.101:8899/# 200# 3. 是否免登录curl-shttp://192.168.0.101:8899/api/auth/status# {"data":{"setuped":false}} ← false 才是免登录# 4. 下载器是否真的能执行(原生 arm64)dockerexecmediago /app/deps/aria2c--version# aria2 version 1.36.0dockerexecmediago /app/deps/ffmpeg-version# ffmpeg version 6.0# 5. ⭐ 落盘闭环:容器内写 → NAS 共享里真的出现dockerexecmediagosh-c'echo ok > /app/mediago/downloads/_t.txt'cat/mnt/md0/.ugreen_nas/25507/downloads/mediago/_t.txt# 必须能读到

七、日常使用 & 已知限制

/usr/local/docker-up# 启动 Docker daemon(UGOS 无 systemd,不会自启)/usr/local/mediago-up# 启动 MediaGo(会顺带拉起 dockerd)

限制清单一览:

项目说明
开机自启需自己在/etc/init.d/写 OpenWrt 服务脚本,UGOS 升级可能被覆盖
容器网络必须--network host,不能用-p端口映射(bridge 被关掉)
内存实测 1552MB,跑 MediaGo 后剩 ~720MB,只适合轻量容器,别跑转码类
远程调试验证码 3 天过期,过期后 SSH 进不去,需要重新生成
保修官方不承诺这类玩法,理论上存在风险;所有文件都在/usr/local、/mnt/md1下,可完整回滚:删掉那几个目录即可,没有动过 UGOS 系统文件
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进