SchOS/docs/plan/alpine.md
Nexoniarz df1394eaa3 SCFF: the live root ships compressed, with Xorg and Window Maker
SCFF, the SchOS Compressed File Format (include/fs/scff.h), is a block
image cut into 128 KB chunks, each its own zstd frame, behind an index.
tools/mkscff writes it from fs-ram.img on every core (-19, 11 s) and
checks each chunk decompresses to itself. The kernel carries the .scff
as its ramdisk and unpacks a chunk the first time a block in it is
read; zero blocks share one page. The kernel and the ISO are the
compressed size, and the root costs memory only for what is touched.
See docs/architecture/scff.md.

zstd 1.5.7 comes from ports/zstd like uACPI: the decoder is compiled
into the kernel at -O2, with a static context and a 64 KB stack of its
own, and mkscff builds the whole library from the same release.

world now has xorg-server, the fbdev and evdev drivers, xinit, Window
Maker, xterm and DejaVu, and /root/.xinitrc runs wmaker. That root is
358 MB, 102 MB as SCFF.

The ISO no longer carries the root a third time as an ISO 9660 tree,
which nothing read. schos.iso: 1389 MB -> 318 MB. El Torito keeps its
own copy of the EFI image, the layout real machines have booted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-24 21:17:31 +02:00

3.8 KiB

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 and Window Maker are in world, so the image starts with them: startx runs wmaker from /root/.xinitrc. Mesa brings LLVM with it, about 200 MB of the 358 MB root; the root ships compressed (SCFF), so that costs about 60 MB of ISO.