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
80 lines
3.8 KiB
Markdown
80 lines
3.8 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 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](../architecture/scff.md)), so that costs about 60 MB of ISO.
|