Files
DreamLife_Linux/README.md
T
2026-10-14 09:51:02 +08:00

627 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DreamLife Linux 内核 + BusyBox 最小系统构建指南
> **Linux 6.12 + BusyBox + QEMU** 的完整最小根文件系统。
> 本文档对应 `build.sh` 一键构建流程,包含配置说明、打包规范与故障排查。
---
## 目录结构
```
/home/zmn/
├── linux-6.12/ # 内核源码(./build.sh kernel-up 自动滚动到最新 6.12.x)
├── busybox/ # BusyBox 源码
├── rootfs/ # 根文件系统(打包源)
│ ├── init # 自定义 PID 1(挂载 + 首次建号 + 登录循环)
│ ├── bin/busybox # 静态链接 BusyBox
│ ├── bin/fastfetch # musl 动态 fastfetch
│ ├── etc/ # passwd/group/shadow/hostname/profile/.../os-release
│ ├── home/ # 普通用户 home(首次 setup 创建)
│ └── lib/modules/ # 内核 .ko 模块(modules_install 产物)
│ └── lib/ld-musl-x86_64.so.1 # musl 加载器(fastfetch)
├── kernel-minimal.fragment # 内核配置增量片段
├── busybox-minimal.fragment # BusyBox 裁剪片段
├── build.sh # 一键构建脚本
├── disk.img # 持久盘(virtio-blk ext4,build.sh 生成)
└── initramfs.cpio.gz # 打包产物
```
---
## 环境准备
```bash
sudo apt update
sudo apt install -y build-essential libncurses-dev bison flex \
libssl-dev libelf-dev git bc rsync cpio gzip wget gcc-14 qemu-system-x86
```
> 若无 `gcc-14`,`build.sh` 会自动回退到系统 `gcc`。
> 可用 `CC=gcc ./build.sh` 显式指定编译器。
---
## 一键构建(推荐)
```bash
cd /home/zmn
chmod +x build.sh
./build.sh # 全量:kernel + busybox + rootfs + initramfs
```
分步执行:
```bash
./build.sh kernel # 内核配置 + 编译 + 打包 initramfs(当前源码)
./build.sh kernel-up # 内核滚动更新:拉最新点版本 + 重编 + 刷模块 + 打包(KERNEL_SERIES=7.2 换大版本)
./build.sh busybox # BusyBox 配置 + 编译 + 安装 rootfs + 模块 + 打包
./build.sh rootfs # 仅重装 rootfs/模块并打包
./build.sh mylogin # 仅编译 mylogin(密码显示 *)并打包
./build.sh fastfetch # 仅安装 fastfetch(musl 链接)并打包
./build.sh apk # 仅安装包管理器(dpkl/apt 风格 + apk 兼容)+ 密钥/仓库/DHCP 脚本并打包
./build.sh initramfs # 仅重新打包
./build.sh disk # 仅生成/检查 disk.img(64MB ext4)
```
环境变量:
| 变量 | 默认值 | 说明 |
|------|--------|------|
| `CC` | `gcc-14`(缺失则 `gcc`) | C 编译器 |
| `JOBS` | `$(nproc)` | 并行度 |
| `KERNEL_DIR` | `/home/zmn/linux-6.12` | 内核源码(**目录名固定**,即使 `KERNEL_SERIES` 换系列也不改名,QEMU/测试均引用此路径) |
| `KERNEL_SERIES` | `6.12` | kernel-up 跟随的滚动系列,自动路由镜像 `v6.x`/`v7.x` 目录;换系列示例:`KERNEL_SERIES=7.2 ./build.sh kernel-up`(TUNA 实测最新 `7.2.8`) |
| `KERNEL_CACHE` | `/home/zmn/.cache/kernel` | 内核 tarball / 旧 config 备份缓存 |
| `BB_DIR` | `/home/zmn/busybox` | BusyBox 源码 |
| `ROOTFS` | `/home/zmn/rootfs` | 根文件系统 |
| `INITRAMFS` | `/home/zmn/initramfs.cpio.gz` | 打包输出 |
| `DISK` | `/home/zmn/disk.img` | 持久盘镜像路径 |
| `DISK_SIZE_MB` | `64` | 持久盘大小(MB) |
| `FF_CACHE` | `/home/zmn/.cache/fastfetch` | fastfetch 下载/构建缓存 |
| `FF_VER` | `2.68.1` | fastfetch 版本 |
| `APK_CACHE` | `/home/zmn/.cache/apk` | apk 静态包/密钥下载缓存 |
| `ALPINE_MIRROR` | `http://mirrors.tuna.tsinghua.edu.cn/alpine` | Alpine 仓库镜像(国内源;写入 `etc/apk/repositories`) |
---
## 手动构建步骤(原理)
### 1. 下载并解压内核
```bash
cd /home/zmn
wget https://mirrors.tuna.tsinghua.edu.cn/kernel/v6.x/linux-6.12.tar.xz
tar -xf linux-6.12.tar.xz
```
> 以上是首次手工获取;之后升级内核直接用 `./build.sh kernel-up`
> (自动查最新 6.12.x 点版本、携带 `.config` 换树、重编 + 刷模块 + 重打包)。
### 2. 配置内核
```bash
cd /home/zmn/linux-6.12
make ARCH=x86_64 defconfig
# 叠加最小化/硬化片段
# (build.sh 内部用 awk 合并 kernel-minimal.fragment,效果等价于 merge_config)
make ARCH=x86_64 olddefconfig
```
**相对 `x86_64_defconfig` 的关键改动(见 `kernel-minimal.fragment`):**
| 类别 | 配置项 | 作用 |
|------|--------|------|
| 安全 | `# CONFIG_SECURITY_SELINUX is not set` | rootfs 无 SELinux 策略,默认关闭避免启动告警 |
| 硬化 | `CONFIG_FORTIFY_SOURCE=y` / `HARDENED_USERCOPY=y` / `SLAB_FREELIST_HARDENED=y` | 内存与拷贝加固 |
| 硬化 | `# CONFIG_DEVMEM` / `# CONFIG_PROC_KCORE` | 关闭 /dev/mem、/proc/kcore 攻击面 |
| 可观测 | `CONFIG_IKCONFIG=y` + `IKCONFIG_PROC=y` | 运行时 `/proc/config.gz` |
| 命名空间 | `CONFIG_USER_NS=y` | 支持 `unshare -U` |
| 调试裁剪 | 关闭 `DEBUG_FS` / `FTRACE` / `KPROBES` / `KEXEC` | 减小体积与攻击面 |
| 驱动裁剪 | 关闭 Wireless / USB host / SCSI / SATA / CDROM / NFS / 9P / VFAT | QEMU 串口启动用不到 |
| 保留 | `VIRTIO_BLK/NET/PCI`、`E1000`、`EXT4_FS`、串口控制台 | 可选网卡/磁盘仍可用 |
验证:
```bash
grep -E 'CONFIG_(SECURITY_SELINUX|IKCONFIG|USER_NS|FORTIFY_SOURCE|DEVMEM|VIRTIO_BLK)=' .config
```
### 3. 编译内核
```bash
cd /home/zmn/linux-6.12
make ARCH=x86_64 CC=gcc-14 -j$(nproc)
# 成功标志:Kernel: arch/x86/boot/bzImage is ready
ls -la arch/x86/boot/bzImage
```
### 4. 配置 BusyBox
```bash
cd /home/zmn/busybox
make defconfig
# 静态链接(initramfs 无 /lib 动态加载器)
sed -i 's/^# CONFIG_STATIC is not set/CONFIG_STATIC=y/' .config
sed -i 's/^CONFIG_STATIC=.*/CONFIG_STATIC=y/' .config
# 禁用 tc(新版内核头文件已删除 CBQ 结构体,否则编译失败)
sed -i 's/^CONFIG_TC=y/# CONFIG_TC is not set/' .config
# 叠加裁剪片段(关闭 telnetd/httpd/ftpd/i2c/ubi 等)
# 见 busybox-minimal.fragment,由 build.sh 自动合并
make oldconfig </dev/null
grep -E '^CONFIG_STATIC=|^# CONFIG_TC is not set' .config
```
### 5. 编译并安装 BusyBox
```bash
cd /home/zmn/busybox
make CC=gcc-14 -j$(nproc)
file busybox # 必须显示 statically linked
make CONFIG_PREFIX=/home/zmn/rootfs install
```
### 6. rootfs 基础文件
`/etc` 至少包含(仓库中已提供):
| 文件 | 作用 |
|------|------|
| `etc/passwd` | 用户名/UID/shell(`login` 依赖);root 初始 `x`(口令在 shadow) |
| `etc/group` | 组信息(含 `users`) |
| `etc/shadow` | 口令;**root 初始 `*` 锁定**,首次 setup 时设置 |
| `etc/hostname` | 主机名 |
| `etc/hosts` | 本地解析 |
| `etc/resolv.conf` | DNS(有网时可用) |
| `etc/os-release` | 发行版标识(fastfetch 的 OS 字段读取此处) |
| `etc/profile` | 登录 shell 的 `PATH`/`PS1`(不覆盖 `HOME`);交互登录时清屏并运行 `fastfetch` |
| `bin/mylogin` | 自定义登录程序:密码逐字显示 `*`(`mylogin.c` 编译) |
| `bin/sudo` | 轻量 sudo:验证**当前用户**密码(显示 `*`)后以 root 执行;需在 `wheel` 组 |
| `bin/busybox` | **setuid root**(`chmod 4755`),否则 `su` 不可用 |
| `etc/issue` / `motd` | 登录前提示 |
| `etc/init.d/rcS` | 可选(busybox `init` 路径) |
> **安全**:出厂 rootfs **不带空口令**;用户/ root 密码在**首次开机交互**创建,
> 并写入持久盘(若挂载了 `disk.img`)。
### 7. init 脚本要点(首次登录 / 持久化)
当前 `/init` 完成:
1. 挂载 `proc`、`sysfs`、`devtmpfs`、`devpts`、`tmpfs` → `/tmp` 与 `/run`
2. 兜底创建 `console`/`ttyS0`/`null`/`urandom` 节点;`exec` 到 console 作为 stdio
3. 拉起 `lo` 与所有网卡;读 hostname;`depmod`;`syslogd`/`klogd`
4. **持久盘**:等待 `/dev/vda`,挂载到 `/data`(必要时 `mkfs.ext2`);
将 `/data/etc` **bind 覆盖 `/etc`** → 用户与口令重启不丢
5. **首次开机向导**(无 `/etc/.user_setup_done` 时):
- **先**设置 **root 密码**(两次确认,≥6 位;逐字显示 `*`)
- 再输入用户名(校验字符集,禁止 `root`/重复)并设置用户密码(同样显示 `*`)
- `chpasswd` / `adduser` 写入;用户加入 `wheel`(供 `sudo`);写标记文件
6. **登录循环**:`setsid cttyhack /bin/mylogin`(自定义登录,密码显示 `*`)
缺 `mylogin` 时回退 BusyBox `getty`+`login`(密码不可见)
**不**直接进 root shell
无 `disk.img` 时仍可 setup,但 `/etc` 在 RAM 中,**重启会重新进入创建向导**。
### 7.1 首次开机流程(验收)
```
Run /init as init process
Welcome to DreamLife Linux (BusyBox)
Storage: /dev/vda -> /data (persistent /etc)
==============================================
First boot: set root password, then create a user
==============================================
Set the root account password (needed for su / root login).
Root password: ******
Confirm Root password: ******
Root password set.
Username: alice
Password: *******
Confirm Password: *******
Created user 'alice'.
DreamLife-linux login: alice
Password: *******
[alice@DreamLife-linux ~]$ # 自动清屏 + fastfetch
```
之后重启(`disk.img` 在):**跳过**向导,直接 `DreamLife-linux login:`。
登录成功后 shell 读取 `/etc/profile`:**清屏**并自动运行 `fastfetch`,再进入提示符。
> 注意:`/etc` 在有盘时 bind 自 `/data/etc`。若旧 `disk.img` 里仍是旧 profile,
> 需 `rm disk.img && ./build.sh disk` 重置,或手动改 `/data/etc/profile`。
### 8. 安装内核模块到 rootfs
```bash
make -C linux-6.12 modules_install INSTALL_MOD_PATH=/home/zmn/rootfs
depmod -b /home/zmn/rootfs 6.12.0
```
> 内核中保持 `=m` 的选项(如 netfilter NAT 扩展)必须装进 `/lib/modules`,否则 `modprobe` 无效。
> `build.sh` 的 `rootfs`/`busybox` 步骤会自动执行上述命令。
### 8.1 安装 fastfetch(musl 动态链接)
```bash
./build.sh fastfetch
```
`step_fastfetch` 流程(缓存于 `FF_CACHE`,避免重复下载):
1. 下载官方 `fastfetch-musl-amd64.tar.gz`(`FF_VER`)
2. `strip` 后装入 `rootfs/bin/fastfetch`
3. 下载 musl `1.2.5` 源码本地编译,取 `libc.so` 装为:
- `rootfs/lib/libc.musl-x86_64.so.1`
- `rootfs/lib/ld-musl-x86_64.so.1` → 符号链接到上者
4. 用 loader 直接执行 `--version` 做冒烟测试
5. 随后 `step_initramfs` 重新打包
登录后运行:
```
[alice@DreamLife-linux ~]$ fastfetch
alice@DreamLife-linux
---------------------
OS: DreamLife Linux (BusyBox) x86_64
Kernel: Linux 6.12.0
...
```
> **说明**:根文件系统无 glibc/ld-linux,故采用 musl 官方二进制 + 自带 musl loader。
> fastfetch 部分高级模块依赖 `dlopen` 加载宿主共享库,在本最小系统中会自动降级/跳过。
### 8.2 安装包管理器 dpkl(apt 风格,底层 apk)+ 网络(DHCP)
```bash
./build.sh apk
```
`step_apk` 流程:
1. 下载 `APKINDEX.tar.gz` 解析当前版本号(`apk-tools-static`、`alpine-keys`)
2. 下载两个 `.apk`(本质是 tar.gz)到 `APK_CACHE` 并解包:
- `sbin/apk.static` → `rootfs/usr/libexec/apk`(Alpine 官方静态二进制,
移出 PATH;本系统已带 musl loader 故可直接运行)
- `etc/apk/keys/*.rsa.pub` → `rootfs/etc/apk/keys/`(仓库签名公钥)
3. 写 `rootfs/sbin/dpkl`:**apt 风格前端**(见下方用法表),
并建软链 `sbin/apk` → `dpkl`(旧习惯/`apk` 原生命令透传兼容)
4. 写 `rootfs/etc/apk/repositories`(main + community,纯 HTTP;
**默认国内源清华 TUNA**,可用 `ALPINE_MIRROR` 换成阿里云/华为云等,或官方 `http://dl-cdn.alpinelinux.org/alpine`)
5. 预建包数据库:`/usr/libexec/apk --root rootfs --arch x86_64 add --initdb --usermode`
(生成 `lib/apk/db/{installed,scripts.tar.gz,triggers}` 与 `etc/apk/{arch,world}`,
否则 guest 内首次使用会报 `Failed to open apk database`)
6. 写 `rootfs/usr/share/udhcpc/default.script`(BusyBox DHCP 钩子,见下)
7. 处理 `etc/hosts`:仅当镜像是官方 dl-cdn 时固定可达 IP;
用国内镜像时自动清掉历史固定行(见“已知网络坑”)
**网络**:QEMU 命令已默认带用户模式网卡
(`-netdev user,id=n0 -device virtio-net-pci,netdev=n0`)。init 挂盘后后台执行
`udhcpc -i eth0`,脚本下发 `10.0.2.15/24`、网关 `10.0.2.2`,并把 DNS
`10.0.2.3` 写入 `/etc/resolv.conf`(随 `/etc` bind 持久化)。
**持久化策略(init 的 `PERSIST=1` 分支:首启种子 + bind 挂载)**:
| 目录 | 首启种子 | 说明 |
|------|----------|------|
| `/etc` → `/data/etc` | `cp -a /etc/.` | 账号、仓库、DNS |
| `/usr` → `/data/usr` | `cp -a /usr/.` | apk 安装的可执行文件/库 |
| `/var` → `/data/var` | `cp -a /var/.` | apk 缓存、日志 |
| `/lib/apk` → `/data/lib/apk` | `cp -a /lib/apk/.` | **只**绑 apk 数据库;`/lib` 其余部分保持 initramfs 版本(内核模块/加载器不被磁盘版覆盖) |
**镜像选择(默认国内源)**:
- 默认 `http://mirrors.tuna.tsinghua.edu.cn/alpine`(清华 TUNA,纯 HTTP 直连,
guest 内 busybox wget 无需 CA 证书;阿里云、华为云亦支持纯 HTTP,实测均 200)。
- 本网络可用性实测:TUNA / 阿里云 / 华为云 纯 HTTP 200;中科大 USTC 403。
- 换源:`ALPINE_MIRROR=http://mirrors.aliyun.com/alpine ./build.sh apk` 后重打包。
换回官方源时 `step_apk` 会自动补 hosts 固定行,换用国内镜像时自动清除。
**已知网络坑(切回官方源时才会遇到)**:
- `dl-cdn.alpinelinux.org`(fastly)的 DNS 会给出若干从本网络黑洞的 IP
(`23.235.x`、`104.156.x` 不通 80 端口),apk 先撞上会超时失败 →
`step_apk` 检测到镜像为 dl-cdn 时自动把两个可达 IP 写入 `etc/hosts`
(musl 先查 hosts 再 DNS)。
- init 里把 `tcp_syn_retries` 调成 `2`(黑洞 SYN 快速失败;默认要等 ~127s),
对国内源无副作用。
- 速度参考:官方源在本网络仅约 25 KB/s,`apk update`(两个索引约 3MB)约 2 分钟;
TUNA 实测两个索引各约 1 秒(`apk update` 秒级完成)。
guest 内用法(`dpkl` = apt 风格命令,底层为 Alpine apk):
| dpkl(apt 风格) | 对应 apt 命令 | 实际执行 |
|------------------|---------------|----------|
| `dpkl update` | `apt update` | `apk update`(刷新索引) |
| `dpkl upgrade` | `apt upgrade` | `apk upgrade` |
| `dpkl install [-y] <pkg>...` | `apt install` | `apk add <pkg>...` |
| `dpkl remove [-y] <pkg>...` | `apt remove` | `apk del <pkg>...` |
| `dpkl purge <pkg>...` | `apt purge` | `apk del <pkg>...`(别名) |
| `dpkl search <kw>` | `apt search` | `apk search <kw>` |
| `dpkl show [pkg]...` | `apt show` | `apk info [pkg]...` |
| `dpkl list` | `apt list` | `apk info`(列已安装) |
| `dpkl clean` | `apt clean` | 清空 `/var/cache/apk` 下载缓存 |
| `dpkl version` | — | 显示 `dpkl, version 3.0.8-r0 ...` |
| `dpkl help` | `apt --help` | 内置帮助 |
- `-y` / `--yes` / `--assume-yes` / `--no-install-recommends` 会被接受并忽略
(apk 本来就是非交互的),照搬 apt 肌肉记忆不会报错。
- **`apk` 仍可用**:它是指向 `dpkl` 的软链,`apk add/del/info/...` 等原生命令
原样透传;真正的 apk 二进制在 `/usr/libexec/apk`(不在 PATH)。
- 典型流程:
```
dpkl update
dpkl install -y tree
dpkl list
dpkl remove tree
```
**滚动更新(Arch 式,`latest-stable` 稳定分支)**:
日常两连击即可把系统推到最新:
```
dpkl update # 刷新索引(TUNA 秒级)
dpkl upgrade # 全量升级所有已安装包(dpkl full-upgrade / dist-upgrade 为其别名)
```
- **会滚动的**:apk 数据库管理的包(`dpkl install` 装的一切,含 `tree`、`musl` 及
依赖),始终跟 `latest-stable` 小版本走(当前 3.22.x 系)。
- **不会滚动的**(宿主/自编,apk 管不到):
- 内核 → **`./build.sh kernel-up`**:自动查 TUNA 最新 `KERNEL_SERIES.x` 点版本
(实测 6.12.0 → 6.12.111),下载 → 携带 `.config` 换源码树 → 重编 →
刷 `/lib/modules` → 重打 initramfs → 重启 QEMU 即生效(`uname -r` 变化);
已是最新则增量重编(~5 分钟)。`./build.sh kernel` 仅重编当前源码。
换到全镜像最新大版本:`KERNEL_SERIES=7.2 ./build.sh kernel-up`(自动用 `v7.x` 目录,
实测最新 `7.2.8`);回滚换回 `KERNEL_SERIES=6.12` 即可,旧 config 备份在
`~/.cache/kernel/config-*.bak`。**生效需退出 QEMU 重新启动**(guest 内 `reboot`
复用启动时已载入 fw_cfg 的旧 bzImage),`uname -r` 验证。
- BusyBox、mylogin、sudo、fastfetch、dpkl、init → `./build.sh <对应步骤>`
- **musl 滚动安全**(已实测):`dpkl install` 拉入的 apk 版 musl 会接管
`/lib/ld-musl-x86_64.so.1`,此时宿主编译的 fastfetch(musl 动态链接)仍正常
运行(测试项 `fastfetch runs on apk-installed musl`)。
- **不要装这三个包**:`busybox`、`alpine-baselayout`、`apk-tools` —— 会覆盖
自编 BusyBox / 重建标准目录结构,破坏本系统底座。
- 64MB 小盘:升级/安装的 `.apk` 留在 `/var/cache/apk`,反复滚动后记得
`dpkl clean`(apt clean 同款)防撑满。
> Alpine 包是 musl 动态链接,本系统 loader 路径兼容,可直接运行。
> `tree` 会自动拉入 `musl` 依赖,两启实测均正常(boot1 的 alpine musl 写在
> 内存盘 `/lib`,boot2 回退 initramfs 自带 loader,musl ABI 兼容)。
> 与 BusyBox 直接冲突的包(`busybox`、`alpine-baselayout` 等)慎装。
---
### 9. 打包 initramfs(规范)
```bash
cd /home/zmn/rootfs
find . -print0 \
| cpio --null -o -H newc --owner root:root --reproducible \
| gzip -9 > /home/zmn/initramfs.cpio.gz
```
| 要点 | 原因 |
|------|------|
| `--owner root:root` | 解压后属主为 root,避免 uid=1000 文件导致权限问题 |
| `--reproducible` + 稳定目录序 | 构建可复现 |
| `gzip -9` | 最大压缩 |
| `-H newc` | 内核要求的 cpio 格式 |
| `find . -print0` | 正确处理特殊文件名 |
验证:
```bash
ls -la initramfs.cpio.gz
zcat initramfs.cpio.gz | cpio -t | grep -E '^(\./)?init$'
zcat initramfs.cpio.gz | cpio -tv | head # 属主应为 root root
```
### 10. QEMU 启动(推荐:带持久盘)
```bash
# build.sh 会在打包后自动生成 disk.img(64MB ext4)
qemu-system-x86_64 \
-kernel /home/zmn/linux-6.12/arch/x86/boot/bzImage \
-initrd /home/zmn/initramfs.cpio.gz \
-drive file=/home/zmn/disk.img,if=virtio,format=raw \
-netdev user,id=n0 -device virtio-net-pci,netdev=n0 \
-append "console=ttyS0 quiet loglevel=3 rdinit=/init" \
-display none -serial mon:stdio \
-m 256M
```
| 参数 | 作用 |
|------|------|
| `-kernel` | bzImage |
| `-initrd` | gzip 压缩的 cpio initramfs |
| `-drive ...,if=virtio` | virtio-blk 持久盘 → 账号存 `/etc`(bind 自 `/data/etc`) |
| `-netdev user ... virtio-net-pci` | 用户模式网卡(DHCP:10.0.2.15,网关/DNS 10.0.2.2/3) |
| `console=ttyS0` | 内核日志走串口 |
| `quiet` / `loglevel=3` | 屏蔽内核启动跑码(`dmesg` 仍可查看);init 内再 `dmesg -n 1` |
| `rdinit=/init` | 指定 initramfs 中的 PID 1 |
| `-display none` | 关闭 VGA 窗口 → **SeaBIOS 横幅不再转到终端** |
| `-serial mon:stdio` | 串口接到终端;`Ctrl+A` `C` 进 QEMU monitor,`quit` 退出 |
可选:
```bash
# 加速(宿主支持 KVM 时)
-enable-kvm -cpu host
```
**启动成功标志:**
```
DreamLife Linux BusyBox · starting ← 开机动画(spinner)
Welcome to DreamLife Linux (BusyBox)
Storage: /dev/vda -> /data (persistent /etc /usr /var /lib/apk)
==============================================
First boot: create a user and set passwords
==============================================
Username: ...
DreamLife-linux login:
```
登录后:
```
[alice@DreamLife-linux /]$ id
uid=1000(alice) gid=1000(alice) ...
```
此时应具备:
- 首次出现创建向导;完成后回到 `login:`(**不**直进 shell)
- 无 `job control turned off` 警告
- `hostname` 显示 `DreamLife-linux`
- `ps`/`id` 显示用户名(`/etc/passwd` 生效)
- `zcat /proc/config.gz`、`ls /lib/modules/$(uname -r)` 可用
**重置账号/已装软件包**:关机后 `rm -f /home/zmn/disk.img`,再 `./build.sh disk`
(或全量)。注意:apk 包数据库与装进 `/usr` 的包也在盘上,删盘一并重置。
退出:guest 内 `poweroff`,或宿主 `Ctrl+A` 再 `C` 再 `quit`。
---
## 目录与文件清单(rootfs 必备)
```
rootfs/
├── init # 自定义 PID 1(可执行)
├── bin/busybox # 静态 busybox + 符号链接
├── bin/fastfetch # musl 动态 fastfetch(step_fastfetch 安装)
├── bin/mylogin bin/sudo
├── sbin/dpkl # apt 风格包管理前端(step_apk 安装)
├── sbin/apk # 软链 → dpkl(旧命令透传兼容)
├── usr/libexec/apk # apk.static 真身(不在 PATH,dpkl 内部调用)
├── sbin/telinit # telinit 垫片 → poweroff/reboot -f
├── sbin/poweroff 等 # wrapper:busybox poweroff -f(见故障排查)
├── usr/share/udhcpc/default.script # DHCP 钩子(step_apk 写入)
├── sbin/ usr/bin/ usr/sbin/
├── etc/
│ ├── passwd group shadow hostname hosts # 用官方源时才含 dl-cdn IP 固定
│ ├── resolv.conf profile fstab issue motd os-release
│ ├── apk/ # repositories + keys/(Alpine 签名公钥)+ arch + world
│ ├── inittab # 可选(busybox init 路径)
│ └── init.d/rcS
├── lib/apk/db/ # 预建 apk 包数据库(installed/triggers/scripts.tar.gz)
├── lib/modules/6.12.0/ # .ko + modules.dep
├── lib/ld-musl-x86_64.so.1 # musl 动态加载器(fastfetch/apk 用)
├── lib/libc.musl-x86_64.so.1
├── proc/ sys/ dev/ dev/pts/ tmp/ run/ root/ var/log/
```
---
## 故障排查
### 只有闪烁光标 / 无任何输出
| 序号 | 检查 | 命令 | 修复 |
|------|------|------|------|
| 1 | bzImage 存在 | `ls arch/x86/boot/bzImage` | 重编内核 |
| 2 | 串口控制台 | `grep SERIAL_8250_CONSOLE .config` | 用 defconfig 起底 |
| 3 | initrd 支持 | `grep BLK_DEV_INITRD .config` | 开启 `BLK_DEV_INITRD` |
| 4 | busybox 静态 | `file rootfs/bin/busybox` | `CONFIG_STATIC=y` 重编 |
| 5 | init 可执行 | `ls -la rootfs/init` | `chmod +x` |
| 6 | 打包含 init | `zcat initramfs.cpio.gz \| cpio -t \| grep init` | 重新打包 |
### 常见错误对照
| 现象 / 报错 | 原因 | 解决 |
|-------------|------|------|
| `can't access tty; job control turned off` | 未挂 devpts 或未用 cttyhack | 检查 `/init` 是否含 `devpts` + `setsid cttyhack` |
| `tc.c: 'TCA_CBQ_MAX' undeclared` | `CONFIG_TC=y` 与新内核头不兼容 | 禁用 `CONFIG_TC` |
| 内核 panic `No working init found` | initramfs 缺 init 或路径错 | 检查 `rdinit=` 与权限 |
| SELinux 启动刷大量 AVC/无 policy 告警 | 默认启用 SELinux 但无策略 | `# CONFIG_SECURITY_SELINUX is not set` 后重编 |
| `modprobe: can't change directory to ...` | 未 `modules_install` | `make modules_install INSTALL_MOD_PATH=rootfs` |
| `ps`/`id` 显示数字 UID | 缺 `/etc/passwd` | 放入 `rootfs/etc/passwd` |
| `zcat /proc/config.gz` 不存在 | 未开 IKCONFIG | `CONFIG_IKCONFIG=y` + `IKCONFIG_PROC=y` |
| initramfs 文件属主非 root | 打包未加 `--owner` | `cpio --owner root:root` |
| 无线/USB 等驱动“编了但 QEMU 用不到” | defconfig 过大 | 叠加 `kernel-minimal.fragment` |
### apk / 网络 / 关机
| 现象 / 报错 | 原因 | 解决 |
|-------------|------|------|
| `dpkl: command not found` | 未执行 apk 步骤 | `./build.sh apk` 后重打包 |
| `dpkl upgrade` 后 `uname -r` 不变 | **内核不归 apk 管**(QEMU `-kernel` 从宿主加载 bzImage) | 宿主跑 `./build.sh kernel-up`,然后重启 QEMU |
| `Failed to open apk database` | 镜像里没有预建数据库 | `./build.sh apk`(内部 `--initdb`)后重打包 |
| `apk update: Operation timed out` 或一直卡住 | 镜像是官方 dl-cdn 且 DNS 给出黑洞 IP | `./build.sh apk`(默认 TUNA 国内源,无需固定;用 dl-cdn 时会自动写 `etc/hosts` 固定行);老 disk 删掉重来或 root 改 guest `/etc/hosts` |
| `apk update` 花 1~2 分钟 | 镜像是官方源且出网慢(实测约 25 KB/s) | 换国内源:`ALPINE_MIRROR=http://mirrors.tuna.tsinghua.edu.cn/alpine ./build.sh apk`(TUNA/阿里云/华为云均实测可用,USTC 403) |
| guest 无 IP / DNS 不通 | QEMU 没加网卡,或 udhcpc 未跑 | 加 `-netdev user,id=n0 -device virtio-net-pci,netdev=n0`;root 下手动 `udhcpc -i eth0` |
| `poweroff` 立刻回到提示符、QEMU 不退出 | 老镜像的 poweroff 只给 PID1 发信号(自定义 init 不处理,信号被内核丢弃) | `./build.sh rootfs` 重打包(`sbin/poweroff` 已换成 `busybox poweroff -f` wrapper);应急 `poweroff -f` |
### 体积参考(本仓库一次构建)
| 产物 | 约略大小 |
|------|----------|
| bzImage(精简后) | ~10 MB |
| busybox(静态) | ~2.4 MB |
| fastfetch(musl strip 后) | ~2.9 MB(+ musl libc ~0.8 MB) |
| apk(静态 apk.static) | ~5.2 MB |
| initramfs.cpio.gz | ~5.9 MB |
| disk.img(64MB ext4) | 64 MB(空洞文件,实际占用视写入量) |
---
## 验收清单
- [x] `./build.sh` 可一键完成全部步骤
- [x] rootfs 具备 `/etc` 基础文件
- [x] init 挂载 devpts/tmpfs,shell 支持 job control
- [x] 内核关闭 SELinux,开启 IKCONFIG/USER_NS/常见硬化
- [x] `=m` 模块安装到 `/lib/modules` 并生成 `modules.dep`
- [x] BusyBox 静态链接,裁剪无用网络服务
- [x] 打包 `--owner root:root` + `gzip -9`
- [x] **首次开机交互创建用户 + root 密码**
- [x] **创建后回到登录提示符,用新用户 / root 登录**
- [x] **`disk.img` 持久化 `/etc`:重启跳过创建向导**
- [x] 无 `disk.img` 时行为可预期(每次重启重新 setup)
- [x] **登录后 `fastfetch` 可用,OS 显示 DreamLife Linux**
- [x] **登录后自动清屏并展示 fastfetch**
- [x] **密码输入(setup + login + sudo)逐字显示 `*`,不回显明文**
- [x] **`sudo <cmd>` 可用(wheel 组 + 当前用户密码);`su` 可用(busybox SUID)**
- [x] **无 SeaBIOS/内核跑码,开机动画后进欢迎页**
- [x] **`./build.sh apk`:装 apk(静态)+ 签名密钥 + 仓库 + 预建包数据库 + DHCP 脚本**
- [x] **开机自动 DHCP:`10.0.2.15/24`、网关 `10.0.2.2`、DNS 写入 `resolv.conf`(持久)**
- [x] **`dpkl update` + `dpkl install -y tree` 成功(apt 风格),`tree`(musl 动态)可执行**
- [x] **`dpkl search/show/list/version` 可用;`apk` 软链透传兼容(`apk info` 正常)**
- [x] **滚动更新:`dpkl upgrade` 全量升级 exit 0;`dpkl clean` 清缓存可用**
- [x] **内核滚动:`./build.sh kernel-up` 6.12.0 → 6.12.111 → 7.2.8(`KERNEL_SERIES=7.2` 跨大版本,配置自动迁移),`uname -r` 对账通过**
- [x] **apk 版 musl 接管 loader 后 fastfetch(宿主 musl 二进制)仍正常运行**
- [x] **`/usr` `/var` `/lib/apk` bind 持久化:重启后 `tree` 与包数据库仍在(`dpkl list`)**
- [x] **`poweroff` 正常关机退出 QEMU,`reboot` 正常重启**
- [x] **自动化验收 `bash run_apk_test.sh` → 28/28 passed**
---
## 附录:关闭 QEMU 的其他方式
- guest 内:`poweroff` / `reboot` / `halt`(`sbin/*` 已换成强制 `-f` wrapper,
直接走 `reboot(2)` 系统调用,不依赖 init 信号;`telinit` 垫片兜底)
- 宿主:`Ctrl+A` 然后 `C` 然后 `quit`
## 附录:常见登录问题
| 现象 | 原因 | 解决 |
|------|------|------|
| 每次重启都要创建用户 | 未挂 `disk.img` | 加 `-drive file=disk.img,if=virtio,format=raw` |
| `login: ` 后密码被拒 | 向导中密码不一致/太短;或 root 仍为 `*` | 重跑 setup;或 `rm disk.img` 重来 |
| 无 `DreamLife-linux login:` | init 被截断/未更新 | `./build.sh rootfs` 后确认 init 含 `first_boot_setup` |
| `adduser: not found` | 未装 loginutils | 确认 `CONFIG_ADDUSER=y` 后 `./build.sh busybox` |
| `fastfetch: not found` | 未执行 fastfetch 步骤 | `./build.sh fastfetch`(会自动下载并重新打包) |
| `fastfetch: can't load interpreter` / loader 报错 | 缺 musl loader | 确认 `rootfs/lib/ld-musl-x86_64.so.1` 存在后重新打包 |
| 密码输入完全不可见(无 `*`) | 使用了旧 getty/login 或旧 profile | `./build.sh rootfs` 后确认有 `bin/mylogin`;`rm disk.img` 重置 |
| `Login incorrect` 但密码正确 | mylogin 未编译进 initramfs 或 shadow 解析旧版本 | `./build.sh mylogin && ./build.sh initramfs` |
| `sudo: not found` | 未编译 sudo 或未重新打包 | `./build.sh mylogin && ./build.sh initramfs` |
| `sudo: ... is not in the wheel group` | 老 disk 上的用户未加入 wheel | guest 内 root 执行 `addgroup <user> wheel`,或 `rm disk.img` 重置 |
| `su: must be suid to work properly` | busybox 未 SUID | `./build.sh mylogin`(会 `chmod 4755` busybox)后重打包 |
| 终端仍出现 `SeaBIOS (...)` | 仍用 `-nographic` | 改为 `-display none -serial mon:stdio` |