The live root was a fixed 96 MB image in the kernel: 60 MB of zeros on the ISO, twice, and room for 60 MB of packages whatever the machine had. Now the image is only what the root holds, plus slack, measured by building it once large. Past its end the ramdisk goes on to half of the memory free at mount, as pages taken the first time each block is written; an unwritten block reads as zeros and costs nothing. SchFS keeps no free-space map on disk, so growing a volume is a larger total before open builds the map: grow_to, set for the ramdisk root. 8 GB machine: a 4 GB root, Xorg installed into it in 77 s with 7.6 GB still free. The ISO is 172 MB instead of 344. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
79 lines
3.7 KiB
Markdown
79 lines
3.7 KiB
Markdown
# apk: Alpine's packages on SchOS
|
|
|
|
Alpine is musl with a different init, and SchOS is musl with a different
|
|
kernel. Alpine's binaries run on SchOS unmodified: its dynamic linker, its
|
|
busybox, and apk itself, fetching over TLS through SchOS's network stack.
|
|
So instead of porting programs one at a time, SchOS can install them.
|
|
|
|
## The model
|
|
|
|
The root filesystem is an apk-managed root:
|
|
|
|
| Part | Where it comes from |
|
|
|---|---|
|
|
| musl, busybox, apk-tools, alpine-keys, CA certificates | Alpine's minirootfs (`ports/alpine`) |
|
|
| what `ports/alpine/world` lists (zsh, terminfo, ...) | Alpine's packages, pinned in `world.lock` |
|
|
| schinit, its jobs, SchOS's tools, toybox, mksh, configuration | **schos-base**, an apk package the build makes from `sysroot/` |
|
|
|
|
`apk add` installs anything from Alpine's main and community repositories.
|
|
`apk upgrade` updates Alpine's packages, and schos-base from the SchOS
|
|
repository at `/var/lib/schos/repo`.
|
|
|
|
PID 1 is still schinit. Alpine's OpenRC is not installed, and packages that
|
|
ship OpenRC services install them where nothing reads them.
|
|
|
|
## Who owns what
|
|
|
|
Where Alpine and SchOS both have a program, Alpine's wins, and SchOS keeps
|
|
only what is its own (see `tools/posix/mkaroot.sh`):
|
|
|
|
- `/bin/sh`, `cat`, `sed`, `find` and the rest are busybox. Alpine's package
|
|
scripts are written against busybox.
|
|
- zsh is Alpine's. SchOS keeps its `zshrc` and `zshenv` (`replaces=zsh`).
|
|
- `/etc/os-release` and `/etc/motd` are SchOS's (`replaces=`).
|
|
- `passwd`, `group` and `profile` are Alpine's. schos-base's post-install
|
|
sets root's shell to zsh, and SchOS's profile is `/etc/profile.d/schos.sh`.
|
|
- mksh is `/bin/mksh`. schinit's recovery shell looks for it first because it
|
|
is static: a broken musl cannot take recovery down with it.
|
|
|
|
## Building
|
|
|
|
make qemu boots build/schfs.img, the root as a 1 GB SchFS disk
|
|
make iso the same root as the live system's ramdisk
|
|
make alpine-lock re-resolve world against Alpine (needs the network)
|
|
|
|
It is the only root. sysroot/ is what schos-base is packaged from.
|
|
|
|
Only `alpine-lock` asks Alpine what is current. A normal build installs
|
|
exactly what `world.lock` names: files in `dist/alpine/`, fetched once and
|
|
checked against the lock's sha256, and against Alpine's signatures by apk.
|
|
|
|
schos-base and its repository index are signed with a key made on the first
|
|
build (`build/apk-keys/`, never committed). The root trusts it beside
|
|
Alpine's keys.
|
|
|
|
## Kernel work this needed
|
|
|
|
All of it found by running apk:
|
|
|
|
- **TCP receive window:** it reopened before the reader drained, so lwIP
|
|
refused data and the sender waited on retransmission timers (5 KB/s). It
|
|
now reopens on read, with a 32-segment window.
|
|
- **mmap address reuse:** freed ranges were never reused, so musl's malloc
|
|
used up the 256 mapping records with 780 MB of RAM free.
|
|
- **unlinkat / renameat:** bare `-1`, read as EPERM, where apk expects ENOENT;
|
|
renameat could not replace its target, and wrote v1 dirents into SchFS.
|
|
- **`#!` scripts in execve**, and ENOEXEC: apk runs triggers directly.
|
|
- **chroot**, and mounting devtmpfs and proc more than once.
|
|
|
|
## Next
|
|
|
|
- The live ISO runs the apk root from RAM. The image in the kernel is only
|
|
what the root holds; at boot the volume grows into half of the free memory
|
|
(4 GB of root on an 8 GB machine), backed by pages as they are written.
|
|
Xorg installs into it in about a minute. All of it is gone at reboot. Keeping packages needs an installer that writes the root
|
|
to a SATA or NVMe disk, and a kernel that finds its root there by the
|
|
SchFS GPT type rather than as IDE disk 1.
|
|
- Xorg from apk (`xorg-server xf86-video-fbdev xf86-input-evdev`) instead of
|
|
the hand port in [xorg.md](xorg.md). The packages install; running them is
|
|
the next test.
|