# M2: native s6 init and the ARM development VM Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record. [Documentation index](README.md) · [Rootfs build](rootfs.md) · [Services](services.md) M2 adds the system's real init path. The kernel enters the generated execline launcher at `/sbin/init`; that immediately executes `s6-linux-init`, which prepares `/run` and replaces itself with `s6-svscan`. The supervisor remains PID 1. Bash is used for some stage-2 configuration tasks, as an ordinary child process. No shell loop, systemd, runit, or BusyBox owns PID 1. You can boot and inspect this system on the x86_64 workstation using a complete ARM QEMU machine. This is a generic development VM, not a Raspberry Pi image. It does not test Pi firmware, a cartridge boot path, or the physical monitor. ## Build and verify First complete the [build-host setup](getting-started.md) and the [rootfs prerequisites](rootfs.md#1-prepare-the-workstation). M2 additionally uses the Arch `e2fsprogs` and `libarchive` packages to create an ext4 VM disk directly from the tar, without mounting it or requiring root: ```sh sudo pacman -S --needed e2fsprogs libarchive make rootfs PROFILE=cli make rootfs-test make init-test ``` `make init-test` installs `qemu-system-aarch64` inside the project-local Void build container when missing. Void packages its emulator with display, audio, and other libraries, even though this test uses only a serial console. Those dependencies stay in the build container; they do not become target packages or host services. The test also downloads a signed generic ARM kernel, pinned by version and SHA-256 in `config/vm-test.conf`. Its AHCI, ext4, serial console and PCI drivers are built in and devtmpfs is automounted by the kernel, so no test initramfs or replacement stage 0 is needed. The kernel package is approximately 161 MB; only its boot files are extracted. Its module collection is not installed into FDS. The separate [M4 boot test](boot.md) uses the actual FDS Pi kernel and its initramfs-to-EROFS handoff. The VM uses software CPU emulation, 1 GiB of RAM, two virtual CPUs, no network, and no host USB, display, filesystem sharing, or block-device passthrough. Its root filesystem is mounted read-only. QEMU discards any block changes in a temporary disk overlay, preserving the generated test disk. Successful output includes: ```text FDS_M2_PID1: /usr/bin/s6-svscan FDS_M2_FILESYSTEM: read-only root, writable tmpfs, root ownership and capabilities FDS_M2_BOOT: getty, eudev and Dasung supervised without a monitor FDS_M2_SERVICE: s6-rc start, echo, supervised restart, stop and start passed FDS_M2_PASS PASS: M2 ARM boot, native s6 PID 1, service control, and orderly power-off ``` The test must finish within 180 seconds. This is a failure deadline, not a boot performance target. A panic, failed assertion, missing marker, timeout, or missing power-off message makes the command fail. Retained logs explain the result. ## Open a real booted ARM shell After `make init-test` succeeds: ```sh make vm ``` The same guest checks run, then the VM opens a temporary root shell on its serial console. Try: ```sh readlink /proc/1/exe findmnt / findmnt /run s6-rc -l /run/s6-rc -a list s6-svstat /run/service/dasungd dasungd status ``` The rootfs's ordinary getty still requires authentication and root remains locked. The VM hook stops getty before opening its shell so they do not compete for the serial console; `s6-svstat` therefore reports getty down during that shell session. The password-free shell exists only in the disposable VM test overlay, when `make vm` explicitly requests it. The checker and shell hook do not ship in the normal rootfs archive. Type `exit` to request orderly VM power-off. Ctrl+C stops the emulator immediately if the guest is stuck; it does not shut down your host. The shell's `/run`, `/tmp`, `/var/tmp`, and `/var/log` writes disappear at power-off. Changes to `/etc` or `/usr` fail because SYSTEM is read-only. Rebuild the rootfs and rerun `make init-test` after changing packages or init configuration; `make vm` otherwise boots the previous successful test disk. ## Control the test service The `test-echo` service is an actual Unix-socket echo server, supervised by s6. It is included in the service database but excluded from the normal boot bundle. In the VM shell: ```sh s6-rc -l /run/s6-rc -u change test-echo s6-svstat -o up,ready,pid /run/service/test-echo printf 'hello from ARM\n' | s6-ipcclient /run/fds-test/echo.sock s6-ioconnect -t 3000 s6-svc -wR -T 5000 -r /run/service/test-echo s6-rc -l /run/s6-rc -d change test-echo ``` Its socket is root-only, under `/run/fds-test` with mode 0700. The server sends an actual readiness notification when it is listening. The automated test checks the reply, confirms the PID changes on restart, stops the service, and starts it again. No arbitrary boot delay substitutes for readiness. ## What starts at boot | Component | Role and dependencies | | --- | --- | | Native stage 1 | Consume the kernel-provided `/dev`, mount `/run`; start s6 supervision and the catch-all logger | | `runtime-fs` | Mount proc, sysfs, devpts and volatile temporary/log filesystems | | `hostname` | Apply the image's `/etc/hostname` | | `console` | Create volatile login accounting files after runtime mounts | | `getty` | Offer a login on `/dev/console`, after console and hostname setup | | `eudevd` | Process device events; readiness requires a successful control request | | `udev-trigger` | Request initial subsystem and device events after eudev readiness | | `dasungd-runtime`, `dasungd-log`, `dasungd` | Prepare private state/log paths and supervise the base monitor controller | Console startup does not depend on eudev enumeration, a monitor connection, the network, or a cartridge scan. No global `udevadm settle` is used. The eudev package has no native s6 notification, so `s6-notifyoncheck` performs a bounded check of its control channel. It signals readiness only after a successful response. Cartridge discovery, the FDS CLI, desktop activation and on-demand Ethernet were added after the original M2 milestone. The current image includes their real implementations; see [Services](services.md), [Cartridges](cartridges.md) and [Desktop](desktop.md). This page focuses on the native init mechanism and its generic test fixture. SSH remains outside the default boot bundle. ## Files and build-time work | Location | Meaning | | --- | --- | | `packages/fds-init/` | Init skeleton, runtime mount/accounting helpers, and package template | | `s6/source/` | Complete current service graph, split between init, Dasung and cartridge/desktop packages | | `/etc/s6-linux-init/current/` | Generated native init environment and stage scripts in the image | | `/etc/s6-rc/compiled/` | Compiled service database, produced during image construction | | `/run/service/`, `/run/s6-rc/` | Writable supervision and live service state | | `/run/uncaught-logs/` | Catch-all service log, also copied to the console | | `/run/log/dasungd/` | Rotating monitor-controller log | | `out/m2-vm-latest/serial.log` | Last successful full ARM boot test | | `out/m2-vm-latest/rootfs.ext4` | Disposable test disk; not a Pi or SYSTEM cartridge image | `s6-linux-init-maker` and `s6-rc-compile` run during the rootfs build under ARM emulation. Boot only reads their output. The VM test preserves that exact compiled database. Its only init hook runs the original stage-2 script to completion, then runs the checker as a child process. Normal `out/rootfs-aarch64.tar` contains neither the checker nor the development-shell hook. The s6-generated template contains FIFOs. Archive validation permits them only inside its `run-image` directory; those templates are copied into writable `/run` at boot. Before entering `/sbin/init`, the kernel or later stage 0 must provide a mounted devtmpfs at `/dev`. Stage 1 deliberately does not mount it twice. Services never place their live FIFOs or status files on read-only SYSTEM. M10 adds cartridge-aware preparation before s6-rc stops the remaining services. Its native shutdown hook stays alive if DATA cannot be verified and unmounted. `s6-linux-init-shutdownd` still performs final process cleanup and the kernel power action. See [Shutdown and reboot](power.md) for usage, blocked-request recovery, and the current M10 acceptance status. ## Troubleshooting - **`make vm` asks you to run `make init-test`:** no successful VM disk has been published yet. Build the rootfs, then run the test. - **Kernel pin/hash failure:** do not substitute an unverified kernel. The exact configured package must remain available or be deliberately updated and retested. - **Disk creation reports missing tar support:** use an `e2fsprogs` build with libarchive support and install `libarchive`. No loop mount or sudo fallback is used. - **No guest success marker:** read `out/logs/init-checks.log` and the printed `out/m2-vm.*` directory's `serial.log`. Failed test workspaces are retained. - **Root login is denied on the ordinary getty:** this is expected with the locked base accounts. Use the explicit development VM shell, not a default password. - **Dasung says disconnected:** the VM intentionally has no physical monitor. Use [Dasung integration](dasung.md) for its eventual hardware validation. Upstream references: [s6-linux-init](https://skarnet.org/software/s6-linux-init/overview.html), [init generation](https://skarnet.org/software/s6-linux-init/s6-linux-init-maker.html), [s6-rc initialization](https://skarnet.org/software/s6-rc/s6-rc-init.html), and [ext4 creation](https://man7.org/linux/man-pages/man8/mke2fs.8.html).