← 返回

我搓了一个Linux系统?

2026/10/07 20:50 技术 技术

我从零撸了一个 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
2
3
4
5
6
内存占用    120 MB
磁盘占用 238 MB
ISO 体积 166 MB
内核 6.15.4(自己编的)
systemd 没有
图形界面 没有

对,没有 systemd,也没有桌面。这是个服务器系统。


为什么想不开要做这个

起因挺简单的。

我用 Debian 挺久了,一直有个感觉:它有点胖。

不是说 Debian 不好,Debian 很好,稳定得像块石头。但很多时候我只想要一个
能跑服务的最小系统,结果装完一看,几百兆没了,一堆我这辈子都不会用到的
东西躺在硬盘里。systemd 我更是又爱又恨——它是很强大,但我就想要个”开机跑
几个脚本”的东西,它给我端上来一整套服务管理宇宙。

所以就冒出一个念头:那我自己做一个?

我知道这个念头很危险。做发行版这事,历史上无数人试过,大部分人卡在
“编译工具链”这一步就再也没出来过。但我想着,反正就是折腾嘛,卡住就卡住。

结果没想到真做出来了。


做的时候是什么体验

一句话:大部分时间在踩坑,小部分时间在怀疑人生。

我用的方法是 LFS/BLFS 那一套:先交叉编译出一套工具链,用这套工具链编译出
目标系统的所有软件,最后把内核和根文件系统打包成 ISO。

听起来挺顺的。实际上:

坑一:su 命令引发的血案

我自建的 login 包里包含了 /usr/bin/su,结果 Debian 的 util-linux
包里也有 /usr/bin/su。dpkg 一看到两个包抢同一个文件,直接报错退出。

问题在于它的报错方式特别有欺骗性:dpkg 事务中断了,但前面的包已经
标记成”已安装”了——只是文件根本没落地。

于是出现了一个诡异的场景:

1
2
3
4
$ dpkg -l | grep xorg
ii xorg 1:7.7+23 X.Org X server
$ which 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
2
3
ufw/util.py line 256, in write_to_file
rc = os.write(fd, bytes(out, 'ascii'))
UnicodeEncodeError: 'ascii' codec can't encode characters

原来 ufw 是用 ascii 编码重写配置文件的,你往里放任何非 ASCII 字符
(中文、emoji),它一写就炸。

崩了也就算了,要命的是它会把配置文件写坏,然后 ufw enable 的时候
因为默认策略是 DROP、放行 SSH 的规则又没加载成功……

我把自己锁在门外了。

SSH 连不上,最后是靠 VirtualBox 的串口控制台爬进去救回来的。

修法:所有 ufw 配置文件强制纯 ASCII,还写了个扫描脚本盯着。

坑五:内核配置项写错了名字,编译器一声不吭

这个坑最阴。

我想给内核加 ufw 需要的 netfilter 扩展,写了这么两行:

1
2
CONFIG_NETFILTER_XT_TARGET_REJECT=y
CONFIG_NETFILTER_XT_MATCH_RT=y

编译,通过,一点问题没有。

然后我发现 ufw 还在报 warning,说 limit 模块找不到。我去查 .config,
发现这两项压根没出现在配置文件里——不是 =n,是完全不存在。

翻了半天源码才搞明白:这两个符号在 Linux 6.15 里根本不存在。

kconfig 遇到未知符号的行为是:静默忽略。不报错、不警告、不留痕迹。

正确的名字是:

1
2
CONFIG_IP_NF_TARGET_REJECT=y     ← 不是 NETFILTER_XT_TARGET_REJECT
CONFIG_IP6_NF_MATCH_RT=y ← 不是 NETFILTER_XT_MATCH_RT

教训:改内核配置之后,一定要回头确认它真的写进 .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
2
3
4
5
1/9 挂载伪文件系统      6/9 (保留)
2/9 设置主机名 7/9 执行 /etc/rc.local
3/9 应用内核参数 8/9 系统自检
4/9 配置网络 9/9 启动交互 shell
5/9 加载防火墙规则

大概 15 秒起来。

能 SSH

开机自动生成 ED25519 主机密钥(不是打包时预生成的——那样所有机器共用一个
私钥,不安全),sshd 自动起来。

防火墙跟 Ubuntu 一样

这是我这轮特意做的。原本用的是自己写的 nftables 规则,但用惯了 Ubuntu 的人
上手会别扭,所以换成 ufw:

1
2
3
4
5
6
ufw status verbose
ufw allow 80/tcp
ufw deny 3306
ufw allow from 10.0.2.0/24 to any port 8080 proto tcp
ufw delete allow 80/tcp
ufw app list # 自带 10 个应用预设

默认策略是 deny incoming / allow outgoing / disable routed,开机自动启用,
预置放行 22 端口。

底层走的是 iptables-nft 兼容层,落到内核的 nftables。为了让它跑顺,
我往内核里补了这几个东西:

1
2
3
4
CONFIG_NETFILTER_XT_MATCH_LIMIT   # 速率限制(防扫描)
CONFIG_NETFILTER_XT_MATCH_HL # hop-limit / TTL 匹配
CONFIG_IP6_NF_MATCH_RT # IPv6 路由头匹配
CONFIG_IP_NF_TARGET_REJECT # ufw 默认用 REJECT 而不是静默 DROP

补完之后实测,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
2
# 挂到虚拟机,或者写 U 盘
dd if=lfos-server.iso of=/dev/sdX bs=4M status=progress

ISO 写了混合 MBR,BIOS 和 UEFI 都能引导。Live 模式用 overlay,重启还原。

方式二:磁盘镜像(数据能留住)

1
2
3
4
5
# VirtualBox
VBoxManage convertfromraw lfos-server-disk.raw lfos.vdi --format VDI

# QEMU
qemu-system-x86_64 -m 2048 -hda lfos-server-disk.raw

登录凭据(⚠️ 测试用,正式用之前一定要改):

1
用户 root    密码 lfos    端口 22

几个得说清楚的限制

毕竟是 v0.0.1,还是个测试版,有些地方得提前说明:

  1. 默认密码是 lfos,而且 PermitRootLogin yes —— 这就是为了测试方便,
    你要是拿去公网跑,被人扫到就是分分钟的事。一定要改。

  2. apt 源默认是 deb.debian.org —— 我实测过,在我这儿只有 19 kB/s,
    慢得让人想砸键盘。换成 mirrors.aliyun.com 能到 600 kB/s 左右,快 15 倍
    以上。建议你上手第一件事就是换源。

  3. ISO 是 Live 模式 —— overlay 文件系统,重启就还原。要持久化得用磁盘镜像。

  4. 内核 9.5 MB,比 8 MB 的目标胖了点 —— 还有裁剪空间,但暂时没动。

  5. 没有图形界面 —— 这是故意的,如前所述。


下一步打算

几个想做的事情,按优先级排:

  • 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