我搓了一个Linux系统?
我从零撸了一个 Linux 发行版,然后把它发出来了
lfOS(LumenFluxOS / 流光OS)v0.0.1 测试版发布
仓库:https://github.com/Nu0vo1212/LumenFlux-lfOS
下载:https://github.com/Nu0vo1212/LumenFlux-lfOS/releases/tag/v0.0.1
先说结论
我做了一个 Linux 发行版。
不是那种”基于 Ubuntu 装几个包改个壁纸”的发行版,是真的从源码开始编译、
自己编内核、自己写 init、一个包一个包拼出来的那种。
它现在能跑。今天我把第一个测试版发出来了。
1 | 内存占用 120 MB |
对,没有 systemd,也没有桌面。这是个服务器系统。
为什么想不开要做这个
起因挺简单的。
我用 Debian 挺久了,一直有个感觉:它有点胖。
不是说 Debian 不好,Debian 很好,稳定得像块石头。但很多时候我只想要一个
能跑服务的最小系统,结果装完一看,几百兆没了,一堆我这辈子都不会用到的
东西躺在硬盘里。systemd 我更是又爱又恨——它是很强大,但我就想要个”开机跑
几个脚本”的东西,它给我端上来一整套服务管理宇宙。
所以就冒出一个念头:那我自己做一个?
我知道这个念头很危险。做发行版这事,历史上无数人试过,大部分人卡在
“编译工具链”这一步就再也没出来过。但我想着,反正就是折腾嘛,卡住就卡住。
结果没想到真做出来了。
做的时候是什么体验
一句话:大部分时间在踩坑,小部分时间在怀疑人生。
我用的方法是 LFS/BLFS 那一套:先交叉编译出一套工具链,用这套工具链编译出
目标系统的所有软件,最后把内核和根文件系统打包成 ISO。
听起来挺顺的。实际上:
坑一:su 命令引发的血案
我自建的 login 包里包含了 /usr/bin/su,结果 Debian 的 util-linux
包里也有 /usr/bin/su。dpkg 一看到两个包抢同一个文件,直接报错退出。
问题在于它的报错方式特别有欺骗性:dpkg 事务中断了,但前面的包已经
标记成”已安装”了——只是文件根本没落地。
于是出现了一个诡异的场景:
1 | $ dpkg -l | grep xorg |
数据库说装了,文件系统说没有。我盯着这个矛盾看了很久。
这个坑一次性搞坏了两个东西:宝塔面板装不上,Xorg 也装不上。后来才发现
是同一个原因。
教训:dpkg 报错一定要当回事,它的”事务性”没你想的那么强。
坑二:992 个文件的属主不对,包括 11 个 setuid 程序
这个坑差点酿成大祸。
我在镜像里发现 992 个文件的属主是 uid 1000,而 uid 1000 是我构建时用的
那个普通用户。
如果只是普通文件也就算了,问题是这里面包括:
/etc/shadow/etc/passwd- 11 个 setuid root 程序(passwd、mount、sudo 之类)
这意味着什么?意味着任何能创建 uid 1000 用户的攻击者,都能改这些文件,
进而直接拿到 root。这是个实打实的提权漏洞。
为什么会出现?
因为我打包成两种格式:squashfs 用了 -all-root 参数(强制所有文件归 root),
所以没问题;而 ext4 镜像没有这个参数,构建用户 uid 1000 就直接漏进去了。
修法:加了一道 58-finalize.sh,打包前强制把 uid 1000 全部改成 root。
顺手还发现 shadow 组根本不存在——gid 42 被 _apt 占了,导致 /etc/shadow
的属主是错的。翻了 base-passwd/group.master 才确认 shadow 应该是 gid 42,
把 _apt 挪到 999 才修好。
坑三:init 里少了个 /tmp,两个功能直接废掉
这个坑我踩了两次,因为它太隐蔽了。
我的 init 脚本里到处是这种写法:
1 | nft -f /etc/nftables.conf 2>/tmp/nft-load.err |
用 /tmp 接错误输出,很正常的写法对吧?
问题是 init 跑的时候 /tmp 还不存在。bash 遇到写不了的重定向会直接报错,
于是:
nft被判定为”加载失败” → 日志说「防火墙规则加载失败」sshd -t被判定为”检查未通过” → 日志说「sshd 配置检查未通过」
最坑的是,因为错误文件也写不出来,你连真正的失败原因都看不到,只能看到
一句干巴巴的”失败了”。
我第一次修 sshd 的时候没意识到 nft 也是同一个原因,修了一个漏了一个,
来回折腾了两轮。
修法:在 init 的第 1 步(挂载伪文件系统)之后立刻 mkdir -p /tmp /run,
一步到位。这个坑现在写在 init 的注释里了。
坑四:ufw 配置文件里的中文注释
我想给配置文件加点中文注释方便看,结果 ufw 直接崩了。
1 | ufw/util.py line 256, in write_to_file |
原来 ufw 是用 ascii 编码重写配置文件的,你往里放任何非 ASCII 字符
(中文、emoji),它一写就炸。
崩了也就算了,要命的是它会把配置文件写坏,然后 ufw enable 的时候
因为默认策略是 DROP、放行 SSH 的规则又没加载成功……
我把自己锁在门外了。
SSH 连不上,最后是靠 VirtualBox 的串口控制台爬进去救回来的。
修法:所有 ufw 配置文件强制纯 ASCII,还写了个扫描脚本盯着。
坑五:内核配置项写错了名字,编译器一声不吭
这个坑最阴。
我想给内核加 ufw 需要的 netfilter 扩展,写了这么两行:
1 | CONFIG_NETFILTER_XT_TARGET_REJECT=y |
编译,通过,一点问题没有。
然后我发现 ufw 还在报 warning,说 limit 模块找不到。我去查 .config,
发现这两项压根没出现在配置文件里——不是 =n,是完全不存在。
翻了半天源码才搞明白:这两个符号在 Linux 6.15 里根本不存在。
kconfig 遇到未知符号的行为是:静默忽略。不报错、不警告、不留痕迹。
正确的名字是:
1 | CONFIG_IP_NF_TARGET_REJECT=y ← 不是 NETFILTER_XT_TARGET_REJECT |
教训:改内核配置之后,一定要回头确认它真的写进 .config 了。
“编译通过”完全不代表”配置生效”。
还有一段弯路:我试图给它做图形界面
这个必须坦白。
做完了服务器版之后,我突发奇想:能不能做个像 Windows 那样的桌面?
于是在 VirtualBox 里折腾了好几天。结论是——VirtualBox 下所有 X 路线全部不通。
五条路,一条一条试:
| 尝试 | 结果 |
|---|---|
| VMSVGA + vmwgfx | 报 unsupported hypervisor,DRM 模式列表是空的 |
| VBoxVGA + vboxvideo | 内核直接 Oops(vbox_crtc_set_base_and_mode 空指针),Xorg 卡死在 D 状态,kill -9 都没用 |
| nomodeset + vesafb + Xorg fbdev | FBIOPUT_VSCREENINFO succeeded but modified mode —— vesafb 的模式是只读的,X 必然失败 |
| uvesafb | 我把 v86d 正确打进 initramfs 了,dmesg 里能看到它真的去调 VBE 了……然后 VirtualBox 的 VBE BIOS 返回 err=-3 |
| Xorg vesa 驱动 | No devices detected |
最后我干脆绕开整个 X 栈,直接用 Python 往 /dev/fb0 画界面。画出来了,
有任务栏、开始菜单、能拖的窗口,中文也能显示(我把 3755 个汉字预渲染成了
16×16 点阵,压缩后 77KB)。
看起来还行,但说实话离”能用”差得远。后来想了想,一个服务器发行版要什么桌面,
就把这部分全删了,删掉了 26.8 GB 的中间产物。
这段经历不算白费,至少让我把 VirtualBox 的图形栈摸透了,也确认了
lfOS 的定位就是服务器。
它现在能干什么
回到正题。v0.0.1 虽然是测试版,但基本的东西都有了。
能开机
自研的 bash init,9 步走完:
1 | 1/9 挂载伪文件系统 6/9 (保留) |
大概 15 秒起来。
能 SSH
开机自动生成 ED25519 主机密钥(不是打包时预生成的——那样所有机器共用一个
私钥,不安全),sshd 自动起来。
防火墙跟 Ubuntu 一样
这是我这轮特意做的。原本用的是自己写的 nftables 规则,但用惯了 Ubuntu 的人
上手会别扭,所以换成 ufw:
1 | ufw status verbose |
默认策略是 deny incoming / allow outgoing / disable routed,开机自动启用,
预置放行 22 端口。
底层走的是 iptables-nft 兼容层,落到内核的 nftables。为了让它跑顺,
我往内核里补了这几个东西:
1 | CONFIG_NETFILTER_XT_MATCH_LIMIT # 速率限制(防扫描) |
补完之后实测,iptables 再也不报模块缺失的 warning 了。
能装软件
apt 和 dpkg 都在,直接从 Debian trixie 源拉包。CA 证书有 150 张,HTTPS 开箱可用。
有一批常用工具
bash、sshd、nft、apt、dpkg、adduser、sudo、nano、curl、wget、less、htop、
ping、dig、nc、ss、ip……日常够用了。
怎么玩
方式一:Live ISO
1 | # 挂到虚拟机,或者写 U 盘 |
ISO 写了混合 MBR,BIOS 和 UEFI 都能引导。Live 模式用 overlay,重启还原。
方式二:磁盘镜像(数据能留住)
1 | # VirtualBox |
登录凭据(⚠️ 测试用,正式用之前一定要改):
1 | 用户 root 密码 lfos 端口 22 |
几个得说清楚的限制
毕竟是 v0.0.1,还是个测试版,有些地方得提前说明:
默认密码是
lfos,而且PermitRootLogin yes—— 这就是为了测试方便,
你要是拿去公网跑,被人扫到就是分分钟的事。一定要改。apt 源默认是
deb.debian.org—— 我实测过,在我这儿只有 19 kB/s,
慢得让人想砸键盘。换成mirrors.aliyun.com能到 600 kB/s 左右,快 15 倍
以上。建议你上手第一件事就是换源。ISO 是 Live 模式 —— overlay 文件系统,重启就还原。要持久化得用磁盘镜像。
内核 9.5 MB,比 8 MB 的目标胖了点 —— 还有裁剪空间,但暂时没动。
没有图形界面 —— 这是故意的,如前所述。
下一步打算
几个想做的事情,按优先级排:
- apt 源默认换成国内镜像 —— 这个最影响体验,应该优先做
- 内核继续裁剪,往 8 MB 以内压
- 把构建流程再整理一遍,加上更多自动化检查
- 补更多测试用例,特别是安全相关的
- 考虑支持更多虚拟化平台(QEMU/KVM 应该比 VirtualBox 友好得多)
最后
做这个东西让我重新理解了一件事:**”从零开始”和”基于别人的东西改”,中间的差距
比想象中大得多。**
基于 Debian 装几个包,我可能两天就搞定一个能用的系统。但从工具链开始编,
每一样东西都得自己想清楚为什么在这儿、它依赖什么、谁依赖它。踩的每一个坑
背后都是一个我原本不知道的机制。
比如说,我原来根本不知道 kconfig 对未知符号是静默忽略的。比如说,我原来
也没想过 2>/tmp/xxx 这种写法在系统启动的最早期会失效。
这些东西写文档里可能就一句话,但真踩上去,能卡你一整天。
不过做出来了还是开心的。
v0.0.1 只是个开始,能用,但离”好用”还有距离。如果你有兴趣试试,
欢迎去仓库看看,发现问题直接提 issue——测试版就是用来找问题的。
仓库:https://github.com/Nu0vo1212/LumenFlux-lfOS
下载:https://github.com/Nu0vo1212/LumenFlux-lfOS/releases/tag/v0.0.1