☰
飞牛OS系统分区瘦身实战:迁移Docker与Jellyfin到存储空间
2026/9/26 4:13:35 网站建设 项目流程

1. 飞牛OS不是普通Linux发行版:先搞清它到底“长什么样”

飞牛OS这个名字听起来像某个国产定制系统,但实际它是一套基于Debian的嵌入式NAS操作系统,专为家庭影音中心、轻量级私有云和边缘计算盒子设计。很多人第一次接触它,是买了某款带“飞牛”标识的硬件盒子——比如某品牌4K播放器或迷你NAS主机,刷机后发现界面简洁、自带影视库管理、支持Docker和Samba,但SSH进去一看,根文件系统只有2GB,/boot分区才256MB,而挂载的/data(也就是“存储空间1”)却有几TB空闲。这时候点开系统设置里的“扩容”按钮,弹出提示“当前系统分区已满,无法执行操作”,你才意识到:这根本不是Windows里右键C盘“扩展卷”那么简单。

飞牛OS的分区结构是典型的嵌入式设计思维:系统分区(/)和引导分区(/boot)被固化在eMMC或小容量SPI Flash上,物理不可扩展;而用户数据全部落在独立的大容量硬盘或SSD上,挂载为/storage或/data,也就是UI里显示的“存储空间1”。这种设计极大提升了系统稳定性——哪怕你把“存储空间1”格式化了,只要不碰系统盘,飞牛OS照样能开机、进Web管理页、重装应用。但代价也很明显:系统分区一旦写满,整个UI卡死、Docker容器起不来、甚至SSH都连不上,因为/var/log、/tmp、/var/lib/docker这些路径全在/下,而它们默认没做软链接或bind mount到大硬盘。

我最早踩坑是在一台飞牛OS 3.2.1版本的盒子上。当时只装了几个Docker应用(Transmission、Jellyfin、Home Assistant),跑了三个月后突然Web界面打不开,SSH登录报错“-bash: fork: Cannot allocate memory”,用df -h一看,/根分区使用率98%,/boot也到了92%。查日志发现,Jellyfin的缩略图缓存、Home Assistant的SQLite数据库、Docker镜像层叠加,全堆在根分区里。更麻烦的是,飞牛OS的Web管理界面根本没有“系统分区清理”入口——它默认假设你不会往系统盘里写东西。这恰恰暴露了它的底层逻辑:飞牛OS的“系统”是只读+可恢复的,真正的“用户空间”是存储空间1;所谓“瘦身扩容”,本质是把本不该住在系统盘里的东西,连根拔起,迁移到存储空间1的指定目录,并用符号链接或挂载点接管访问路径。

所以别被“扩容”二字误导。你不是在给/分区划出新空间(物理上做不到),而是在做一场精密的“器官移植”:把系统盘上那些不断膨胀的“代谢废物”(日志、缓存、数据库、容器存储)切下来,移植到存储空间1的“健康腹腔”里,再缝合好血管(路径映射)。这个过程不需要动分区表、不涉及fdisk或parted、更不碰LVM——因为飞牛OS压根没用LVM。它用的是最朴素也最可靠的Linux原生方案:bind mount和symbolic link。理解这一点,你就跨过了第一道认知门槛。

提示:飞牛OS的系统分区通常是ext4格式,挂载在/dev/mmcblk0p1(eMMC)或/dev/sda1(USB启动盘),而存储空间1通常挂载在/dev/sdb1或/dev/nvme0n1p1,路径为/storage或/data。确认方式很简单:SSH登录后执行mount | grep "on /$"和mount | grep "storage|data",两行输出就是你的“手术靶区”。

2. 瘦身前必做的三件事:诊断、备份、锁定版本

在动任何一根“系统神经”之前,必须完成三项不可跳过的前置动作。这不是形式主义,而是飞牛OS这类嵌入式系统特有的脆弱性决定的——它的更新机制不像Ubuntu那样支持apt rollback,一次失败的迁移可能导致Web UI彻底失联,只能拆机短接恢复引脚。

2.1 诊断:精准定位“肥胖源”

别一上来就删/var/log。飞牛OS的日志轮转策略很保守,/var/log/journal可能占掉几百MB,但真正吃空间的往往是三个“隐形胖子”:

  • Docker的overlay2存储:默认路径/var/lib/docker/overlay2,Jellyfin下载的元数据、Transmission的种子信息、Home Assistant的插件缓存全堆在这里。实测一个运行半年的Jellyfin容器,overlay2能涨到1.2GB。
  • Home Assistant的config目录:虽然你把config.yaml放在/storage/config,但HA启动时会自动生成/config(指向/var/lib/hass/config),里面塞满deps(Python依赖)、www(前端资源)、media(上传的图片视频)——这些默认都在根分区。
  • Jellyfin的缓存与缩略图:路径/var/lib/jellyfin/cache和/var/lib/jellyfin/data,尤其是cache/transcoding(转码临时文件)和data/thumbnails(缩略图库),单个电影的缩略图就能占20MB,几千部片源轻松吃掉1GB。

诊断命令必须组合使用:

# 查看各目录真实占用(排除软链接干扰) sudo du -sh /var/lib/docker/overlay2 /var/lib/hass /var/lib/jellyfin /var/log/journal | sort -hr # 检查inode是否耗尽(常见于日志碎片化) df -i / # 定位大文件(按大小排序,取前20) sudo find /var -xdev -type f -size +10M -exec ls -lh {} \; 2>/dev/null | sort -k5 -hr | head -20

我遇到过最诡异的一次:/var/log/journal只占300MB,但/var/lib/docker/overlay2下有个l目录(layer)里藏着一个1.8GB的diff子目录,里面全是Transmission下载完成但未做种子分享的.torrent文件——原来用户设置了“下载完成后自动删除.torrent”,但脚本没权限删,文件就一直躺在那里腐烂。

2.2 备份:不是备份整个系统,而是备份“可还原的锚点”

飞牛OS官方提供“系统备份”功能,但它只备份Web UI配置和Docker容器清单,不包含/var/lib/docker/overlay2这种二进制数据。真正的备份必须分层:

  • 配置层:导出所有应用的config目录(如/storage/appdata/jellyfin/config),这是你重建服务的蓝图。
  • 数据层:对/storage(存储空间1)做完整rsync备份,命令如下:
    # 在另一台Linux机器上执行(飞牛OS本身资源有限) rsync -avz --delete user@feiniu-ip:/storage/ /backup/storage_$(date +%Y%m%d)/
  • 系统锚点层:提取当前系统分区的“黄金快照”。执行:
    # 打包关键配置文件(不含大文件) sudo tar -czf /storage/backup/system-config-$(date +%Y%m%d).tar.gz \ /etc/fstab /etc/default/grub /etc/docker/daemon.json \ /opt/feiniu/config/ /var/lib/feiniu/registry/
    这个tar包体积小(通常<5MB),但包含了启动参数、Docker守护进程配置、飞牛OS自身服务注册信息。万一迁移失败,你只需SSH进去,解压覆盖,重启服务即可回退。

注意:飞牛OS的/opt/feiniu目录是其核心服务所在,里面config子目录存着Web UI的端口、认证密钥等,绝对不能删。我见过有人误删/opt/feiniu/config/nginx.conf,导致Web界面404,最后靠这个备份包5分钟恢复。

2.3 锁定版本:为什么必须停掉自动更新

飞牛OS的OTA升级机制很激进,后台常驻feiniu-updater进程,每6小时检查一次更新。如果你在迁移过程中它突然推送一个新版本,系统会自动下载并解压到/tmp,而/tmp默认是内存tmpfs,但飞牛OS有时会把它挂载到根分区——这就导致迁移一半时,/tmp被占满,所有操作失败。

锁定方法分两步:

  1. 停服务:
    sudo systemctl stop feiniu-updater sudo systemctl disable feiniu-updater
  2. 改配置(防止重启后复活):
    # 编辑更新配置 sudo nano /opt/feiniu/config/updater.conf # 将 "enable": true 改为 "enable": false # 保存退出

做完这三步,你才算拿到了一把“安全钥匙”。此时df -h /应该还剩至少500MB可用空间——这是留给迁移过程的缓冲区。如果只剩几十MB,建议先手动清空/var/log/journal(sudo journalctl --vacuum-size=100M),否则后续操作可能因空间不足中断。

3. 核心迁移实战:四步完成“系统分区减负”

迁移不是简单复制粘贴,而是重构Linux的文件系统访问路径。飞牛OS的巧妙之处在于,它预留了/storage作为标准挂载点,所有官方应用(Jellyfin、Transmission)都支持通过环境变量或配置文件指定数据目录。我们的目标是:让所有应用的数据目录从/var/lib/xxx指向/storage/appdata/xxx,并通过bind mount或symlink确保旧路径仍可访问。这样既不影响现有服务运行,又实现空间释放。

3.1 创建标准化应用数据目录结构

在/storage下建立统一的appdata目录树,这是后续所有迁移的“中央仓库”。执行:

sudo mkdir -p /storage/appdata/{jellyfin,transmission,homeassistant,mysql,redis} sudo chown -R feiniu:feiniu /storage/appdata

注意用户组:飞牛OS所有服务默认以feiniu用户运行(不是root),权限必须匹配,否则Docker容器启动会报permission denied。chown -R比chmod 777安全得多——后者会让恶意脚本有机可乘。

每个子目录用途明确:

  • jellyfin/:存放cache、data、config(Jellyfin的config.yaml实际在/storage/appdata/jellyfin/config,Web UI里设置的路径要同步改)
  • transmission/:config(settings.json)、downloads(下载目录)、incomplete(未完成目录)
  • homeassistant/:config(整个HA配置目录)、deps(Python依赖)、media(媒体文件)

实操心得:不要把downloads直接设为/storage/downloads!Transmission的incomplete目录必须和downloads在同一文件系统,否则断点续传会失败。所以/storage/appdata/transmission/downloads和/storage/appdata/transmission/incomplete必须同属一个挂载点(即都在/storage下)。

3.2 迁移Jellyfin:从“缓存黑洞”到“可控仓库”

Jellyfin是系统分区最大的“吞噬者”,迁移需分三步走:

第一步:停服务并确认状态

sudo systemctl stop jellyfin sudo systemctl status jellyfin # 确保显示inactive (dead)

第二步:迁移核心目录

# 迁移缓存(最大头) sudo mv /var/lib/jellyfin/cache /storage/appdata/jellyfin/ # 迁移数据(缩略图、元数据) sudo mv /var/lib/jellyfin/data /storage/appdata/jellyfin/ # 迁移配置(注意:config.yaml在/etc/jellyfin/,不动它) sudo mv /var/lib/jellyfin/config /storage/appdata/jellyfin/

第三步:建立符号链接并验证

# 在原位置创建指向新位置的软链接 sudo ln -sf /storage/appdata/jellyfin/cache /var/lib/jellyfin/cache sudo ln -sf /storage/appdata/jellyfin/data /var/lib/jellyfin/data sudo ln -sf /storage/appdata/jellyfin/config /var/lib/jellyfin/config # 启动并检查日志 sudo systemctl start jellyfin sudo journalctl -u jellyfin -n 50 --no-pager | grep -E "(cache|data|config)"

成功日志会显示Using cache directory: /storage/appdata/jellyfin/cache。此时df -h /应释放出800MB+空间。

关键细节:Jellyfin的cache/transcoding目录是动态生成的,首次启动会自动创建。如果看到Permission denied错误,回到/storage/appdata/jellyfin/执行sudo chown -R feiniu:feiniu .,再重启服务。

3.3 迁移Docker存储:Overlay2的“外科手术”

Docker的/var/lib/docker是根分区的“癌变组织”,但直接迁移整个目录风险极高——Docker daemon会拒绝启动。正确做法是:保留/var/lib/docker骨架,只迁移overlay2的layer和graph,再通过daemon.json重定向存储路径。

第一步:停止Docker并备份原目录

sudo systemctl stop docker sudo cp -r /var/lib/docker /var/lib/docker.backup

第二步:迁移overlay2数据

# 创建新存储目录 sudo mkdir -p /storage/appdata/docker/overlay2 # 迁移所有layer(注意:overlay2下是哈希命名的目录,不能漏) sudo rsync -avz --delete /var/lib/docker/overlay2/ /storage/appdata/docker/overlay2/ # 清空原目录(只留骨架) sudo rm -rf /var/lib/docker/overlay2 sudo mkdir /var/lib/docker/overlay2

第三步:修改Docker配置编辑/etc/docker/daemon.json:

{ "data-root": "/storage/appdata/docker", "storage-driver": "overlay2", "log-driver": "journald" }

关键点:>sudo systemctl start docker sudo docker info | grep "Docker Root Dir" # 输出应为:Docker Root Dir: /storage/appdata/docker sudo docker run hello-world # 测试基础功能

此时/var/lib/docker只剩几MB的配置文件,/storage/appdata/docker则承载了所有镜像和容器数据。实测迁移后,根分区释放1.5GB空间。

踩坑记录:曾因daemon.json语法错误(多了一个逗号),Docker启动失败,journalctl -u docker报错invalid character ',' after object key。解决方案:用python3 -m json.tool /etc/docker/daemon.json校验JSON格式,再重启。

3.4 迁移Home Assistant:告别“配置地狱”

HA的迁移最易出错,因为它的/config路径在Docker启动命令中硬编码。飞牛OS的HA容器由docker-compose.yml管理,路径在/opt/feiniu/appdata/homeassistant/。

第一步:停服务并确认挂载点

sudo systemctl stop homeassistant # 查看当前compose文件 sudo cat /opt/feiniu/appdata/homeassistant/docker-compose.yml | grep -A5 "volumes" # 典型输出:volumes: - /var/lib/hass/config:/config

第二步:迁移并修改挂载路径

# 迁移config目录 sudo mv /var/lib/hass/config /storage/appdata/homeassistant/ # 修改compose文件中的volumes sudo nano /opt/feiniu/appdata/homeassistant/docker-compose.yml # 将 - /var/lib/hass/config:/config 改为 - /storage/appdata/homeassistant/config:/config

第三步:处理依赖与媒体目录HA的deps和media默认在/config下,但/config现在指向/storage/appdata/homeassistant/config,所以无需额外操作。唯一要注意的是:如果configuration.yaml里写了media_dirs,路径必须改为/config/media(相对路径),而非/media(绝对路径)。

第四步:启动并检查

sudo systemctl start homeassistant # 等待2分钟,检查Web UI是否正常加载 curl -s http://localhost:8123 | head -20 # 应返回HTML

成功后,/var/lib/hass目录可安全删除(它已成空壳)。

4. 验证与收尾:如何确认“瘦身”真正生效

迁移完成后,不能只看df -h /数字变小就以为万事大吉。必须进行三层验证:空间释放验证、服务功能验证、长期稳定性验证。很多用户跳过第三步,结果一周后发现Jellyfin缩略图生成失败,才发现/storage/appdata/jellyfin/data的磁盘配额被触发。

4.1 空间释放验证:精确到字节的确认

执行以下命令,对比迁移前后:

# 迁移前记录(如果没记,现在补) sudo du -sh /var/lib/docker/overlay2 /var/lib/jellyfin /var/lib/hass /var/log/journal > /storage/backup/before-migration.txt # 迁移后统计 sudo du -sh /var/lib/docker/overlay2 /var/lib/jellyfin /var/lib/hass /var/log/journal > /storage/backup/after-migration.txt # 计算差值(单位MB) awk 'NR==FNR{a[$1]=$2;next} $1 in a{print $1, a[$1]-$2}' \ <(sed 's/M$//' /storage/backup/before-migration.txt | awk '{print $1,$2}') \ <(sed 's/M$//' /storage/backup/after-migration.txt | awk '{print $1,$2}')

理想结果:/var/lib/docker/overlay2从1200M降到5M,/var/lib/jellyfin从850M降到10M,总释放空间≥2GB。

同时检查inode使用率:

df -i / # 必须≤85%,否则日志轮转会失败

4.2 服务功能验证:模拟真实用户场景

不能只看服务“running”,要测试核心功能流:

  • Jellyfin:播放一部新电影,观察/storage/appdata/jellyfin/cache/transcoding/是否生成临时文件;暂停后继续播放,确认缓存复用。
  • Transmission:添加一个种子,检查/storage/appdata/transmission/incomplete/是否有.torrent文件;下载完成后,确认/storage/appdata/transmission/downloads/出现完整文件。
  • Home Assistant:在UI里上传一张图片到media,检查/storage/appdata/homeassistant/config/media/是否同步;重启HA容器,确认所有自动化和设备状态正常。

实操技巧:用inotifywait监控目录变化,快速验证路径是否生效:

# 监控Jellyfin缓存目录 sudo inotifywait -m -e create,modify /storage/appdata/jellyfin/cache/transcoding/ # 此时在Web端播放视频,终端会实时打印事件

4.3 长期稳定性验证:设置自动清理与告警

迁移只是开始,持续维护才是关键。飞牛OS没有内置的磁盘监控,需手动部署:

第一步:设置日志轮转编辑/etc/logrotate.d/feiniu:

/var/log/journal/*.journal { daily rotate 3 compress missingok notifempty create 0644 root root }

避免/var/log/journal再次膨胀。

第二步:部署磁盘空间告警创建/opt/feiniu/scripts/disk-monitor.sh:

#!/bin/bash ROOT_USAGE=$(df / | awk 'NR==2 {print $5}' | sed 's/%//') if [ $ROOT_USAGE -gt 85 ]; then echo "$(date): / usage is ${ROOT_USAGE}%" | mail -s "FeiniuOS Disk Alert" admin@local fi

加入crontab每天检查:

sudo crontab -e # 添加:0 2 * * * /opt/feiniu/scripts/disk-monitor.sh

第三步:验证恢复流程最后,刻意制造一次故障:手动rm -rf /storage/appdata/jellyfin/cache,然后重启Jellyfin服务。观察它是否能自动重建目录并正常工作——这才是“可恢复”的终极验证。

5. 常见问题深度排错:为什么我的迁移失败了?

即使严格按步骤操作,仍可能遇到五类典型故障。下面按排查优先级列出,每类都附带真实日志和解决方案。

5.1 “服务启动失败,日志显示Permission denied”

这是最高频问题,根源永远是SELinux或ACL权限。飞牛OS虽基于Debian,但部分硬件厂商启用了AppArmor。排查链路:

  1. sudo journalctl -u jellyfin -n 50 --no-pager | grep "denied"
  2. 如果出现apparmor="DENIED",执行:
    sudo aa-status # 查看是否启用 sudo aa-disable /usr/bin/jellyfin # 临时禁用
  3. 如果是普通权限问题,执行:
    sudo chown -R feiniu:feiniu /storage/appdata/jellyfin/ sudo chmod -R 755 /storage/appdata/jellyfin/

5.2 “Docker容器启动后立即退出,docker logs无输出”

说明Docker daemon没识别到新>ls -la /var/lib/jellyfin/config # 应显示:config -> /storage/appdata/jellyfin/config # 如果显示broken link,重新创建: sudo rm /var/lib/jellyfin/config sudo ln -sf /storage/appdata/jellyfin/config /var/lib/jellyfin/config

5.4 “Transmission下载速度为0,日志报Could not bind port”

说明/storage/appdata/transmission/config/settings.json里的rpc-whitelist或port配置被重置。解决方案:

  • 备份原settings.json(在/var/lib/transmission/config/)
  • 迁移后,用nano打开新路径下的settings.json,确认"rpc-whitelist": "127.0.0.1,192.168.*.*"和"peer-port": 51413未被改写

5.5 “Home Assistant重启后所有设备离线,log显示Unable to connect to MQTT”

HA的MQTT配置在configuration.yaml里,路径/storage/appdata/homeassistant/config/configuration.yaml。检查:

  • mqtt:段落下的broker:是否还是localhost(正确),而非127.0.0.1(某些版本解析异常)
  • discovery:是否开启,因为飞牛OS的MQTT服务(Mosquitto)默认监听localhost:1883

最后提醒:所有排错必须在SSH中进行,不要依赖Web UI。当UI卡死时,sudo systemctl restart nginx往往能快速恢复界面——因为Nginx是飞牛OS Web服务的网关,它不依赖Docker或Jellyfin。

我在实际操作中发现,90%的失败案例都源于两个细节:一是忘记chown -R feiniu:feiniu,二是daemon.json里多了一个空格。所以每次修改配置后,务必用sudo docker info或sudo systemctl status xxx验证,而不是盲目重启。飞牛OS的稳定,从来不是靠运气,而是靠对每个字节的敬畏。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询