diff --git a/recipes/core/mdevd/recipe.kdl b/recipes/core/mdevd/recipe.kdl index f650145e..7fd4809b 100644 --- a/recipes/core/mdevd/recipe.kdl +++ b/recipes/core/mdevd/recipe.kdl @@ -1,7 +1,7 @@ recipe { name "mdevd" version "0.1.8.1" - release 1 + release 2 description "A small daemon managing kernel hotplug events, similarly to udevd" url "http://skarnet.org/software/mdevd/" archs "aarch64" "x86_64" @@ -46,6 +46,10 @@ source "https://raw.githubusercontent.com/illiliti/libudev-zero/refs/heads/maste blake3 "a5a0e4875dd26f438ce73177881b13530a99b6916b0b3466bf875dff9b45bea7" } +source "service" { + blake3 "d499904ba4962d285f0e9b9db9ada186a4560be7172fcc88715833e033a9585b" +} + build { script r#" ./configure \ @@ -68,6 +72,7 @@ install { install -m0754 "${MERE_SOURCES_DIR}/settle-nics" "${DESTDIR}/lib/mdev/helpers/" install -m0754 "${MERE_SOURCES_DIR}/sound-control" "${DESTDIR}/lib/mdev/helpers/" install -m0754 "${MERE_SOURCES_DIR}/storage-device" "${DESTDIR}/lib/mdev/helpers/" + install -m0755 "${MERE_SOURCES_DIR}/service" "${DESTDIR}/lib/mdev/helpers/service" install -m0754 uevents "${DESTDIR}/lib/mdev/helpers/" "# @@ -80,15 +85,17 @@ package "mdevd" { service "mdevd" { type "daemon" - command "/sbin/mdevd" "-D" "3" + command "/lib/mdev/helpers/service" "start" + log false ready-notification 3 essential true - depends-on "mount-proc" "mount-rw" + depends-on "mount-proc" "mount-rw" "mount-sys" "mount-runtime" } service "mdevd-coldplug" { type "oneshot" - up "if { install -d /run/.libudev-zero } mdevd-coldplug" + up "/lib/mdev/helpers/service" "coldplug" + log false essential true depends-on "mdevd" } diff --git a/recipes/core/mdevd/service b/recipes/core/mdevd/service new file mode 100644 index 00000000..d1a6acd9 --- /dev/null +++ b/recipes/core/mdevd/service @@ -0,0 +1,12 @@ +#!/bin/sh +set -eu +export PATH=/bin:/sbin:/usr/bin:/usr/sbin +exec > /dev/console 2>&1 +case ${1:-} in + start) exec /sbin/mdevd -D 3 -O 4 ;; + coldplug) + mkdir -p /run/.libudev-zero + exec /sbin/mdevd-coldplug -O 4 + ;; + *) printf 'mdevd: unknown service action: %s\n' "${1:-}" >&2; exit 2 ;; +esac diff --git a/recipes/core/mere-boot/README.md b/recipes/core/mere-boot/README.md new file mode 100644 index 00000000..588a6b2e --- /dev/null +++ b/recipes/core/mere-boot/README.md @@ -0,0 +1,96 @@ +# Mere boot services + +`mere-boot` supplies the minimum local-disk boot graph for dinit. Networking, +SSH, time synchronization, D-Bus, and desktop services are optional. No +initramfs is required; the kernel must contain the root-disk/filesystem drivers, +console support, and automatic devtmpfs mounting needed to reach `/sbin/init`. + +## Installation contract + +Install `busybox`, `dinit` (unpatched 0.22.1 is sufficient), `mdevd` (release 2 or later), +`shadow`, and `mere-boot` into the **system** profile with +`settings { init-provider "dinit"; }` in Mere's +configuration. These are explicit installation requirements: recipe `depends` +lists build inputs, and script-invoked programs are not inferred ELF dependencies. +The image or installer must expose the active system generation's `bin`, `sbin`, +`lib`, and `usr` directories at the filesystem root. Boot the kernel with +`init=/sbin/init`; the launcher execs dinit so it remains PID 1. + +Provision the root directories `/dev`, `/proc`, `/sys`, `/run`, `/tmp`, `/var`, +`/root`, and `/home` before booting a read-only root. `/usr` and the Mere store +must be accessible on the root filesystem before init starts. A separately +mounted early `/usr`, encrypted root, and remote root need additional design. +Provide an `/etc/fstab` containing the required local mounts (an empty file is +valid), account databases, a usable Shadow/PAM login configuration, and an +administrator-provisioned root credential. This package never sets a password, +unlocks an account, or creates an unauthenticated recovery shell. Its minimal +`/etc/profile` default makes both `/bin` and `/usr/bin` (and administrative paths) +available in a login shell; an existing administrator profile is preserved. + +Mere generates the ordinary service descriptions in `/usr/share/dinit.d` during +system-profile activation. The `boot`, `recovery`, and `single` descriptions are +package `/etc` defaults because bundles and console attachment are not currently +expressible with service primitives. They are copied by normal activation when +absent; existing administrator files remain unchanged. Review `mere etc diff` +and merge relevant defaults on an existing installation. Do not replace the +whole machine's `/etc` merely to adopt this package. + +## Graph and ordering + +- `mount-proc` establishes procfs. `mount-sys`, `mount-dev`, and `mount-rw` depend + on it and establish sysfs, devtmpfs, and a writable root. +- `mount-runtime` waits for those services, establishes `/run`, devpts and shared + memory, sets the temporary-directory mode, and exposes `/dev/fd`. +- mdevd starts after those mounts and signals readiness. Its coldplug service + creates the libudev-zero runtime directory and then enumerates devices. +- `mount-fstab` waits for coldplug before mounting the local filesystems. +- `system-setup` creates log/home directories, applies optional hostname and + sysctl configuration, and enables configured swap. `sysinit` requires it. +- `tty1` waits for `sysinit` and starts BusyBox getty with Shadow login. The + default `boot` bundle requires both `sysinit` and `tty1`. + +The init launcher prepares `/run` before dinit creates its control socket, so a +later tmpfs mount cannot hide `/run/dinitctl`. The graph's runtime-mount helper +recognizes this existing mount. Setup is otherwise performed by recipe service +primitives, whose helpers use POSIX shell and BusyBox utilities. + +The startup mount services deliberately do not unmount filesystems when released: +a partial boot may need those filesystems for authenticated recovery. On normal +shutdown dinit stops the complete service set first; its shutdown utility then +terminates remaining processes, disables swap, syncs, and unmounts/remounts +filesystems before invoking kernel power-off or reboot. Do not add early mount +stop commands which race optional daemons during shutdown. + +## Machine configuration and failure handling + +`/etc/dinit.d/boot.d` is the administrator's optional-service set. For a QEMU or +headless machine with a `ttyS0` serial console, enable the supplied serial service: + +```sh +mkdir -p /etc/dinit.d/boot.d +ln -s /usr/share/dinit.d/ttyS0 /etc/dinit.d/boot.d/ttyS0 +``` + +This records next-boot intent; use `mere start ttyS0` separately to start it now. +The supplied serial speed is 115200. Other ports/speeds can use an administrator +service override. Optional-service failures do not satisfy or replace the +required foundational graph. Optional daemons with additional filesystem/device +requirements should declare those dependencies in their own recipes. + +Setup diagnostics go to the kernel-selected console; dinit also writes +`/run/dinit.log`, including failures before `/var` is mounted. Daemons using +Mere's ordinary logging metadata retain their own configured log files. Log +rotation is separate work. + +A required setup failure prevents normal boot completion. Dinit presents its +boot-failure menu on the console; select `e` for recovery. Recovery then starts +a console-attached Shadow login, initially requesting root. Automatic recovery +is left disabled so the menu and login never compete for console input. + +The helper reopens dinit's control socket after failed startup so lifecycle and +shutdown commands remain available. Credentials and normal PAM policy still +apply; locked or missing credentials do not grant a shell. There is no automatic +reboot. Recovery does not depend on `sysinit`, so failure of that graph cannot +suppress the recovery login. An operator repairs the system and explicitly +requests reboot or power-off. `single` selects the same authenticated recovery +path directly. diff --git a/recipes/core/mere-boot/boot b/recipes/core/mere-boot/boot new file mode 100644 index 00000000..4fe5664f --- /dev/null +++ b/recipes/core/mere-boot/boot @@ -0,0 +1,4 @@ +type = internal +depends-on = sysinit +depends-on = tty1 +waits-for.d = boot.d diff --git a/recipes/core/mere-boot/boot.keep b/recipes/core/mere-boot/boot.keep new file mode 100644 index 00000000..af891682 --- /dev/null +++ b/recipes/core/mere-boot/boot.keep @@ -0,0 +1 @@ +# Optional boot services are enabled here by the administrator. diff --git a/recipes/core/mere-boot/getty b/recipes/core/mere-boot/getty new file mode 100644 index 00000000..eadcf204 --- /dev/null +++ b/recipes/core/mere-boot/getty @@ -0,0 +1,28 @@ +#!/bin/sh +# Getty/login owns the selected tty. Keep supervising across normal logouts. +set -eu +export PATH=/bin:/sbin:/usr/bin:/usr/sbin +if [ ! -c "/dev/$1" ] || [ ! -x /sbin/getty ] || [ ! -x /usr/bin/login ]; then + printf 'mere-boot: console or login unavailable: %s\n' "$1" > /dev/console + exit 1 +fi +child= +stop() { + trap - TERM INT HUP + if [ -n "$child" ]; then + kill "$child" 2>/dev/null || : + wait "$child" 2>/dev/null || : + fi + exit 0 +} +trap stop TERM INT HUP +while :; do + /sbin/getty -l /usr/bin/login "${2:-38400}" "$1" & + child=$! + wait "$child" || : + child= + sleep 1 & + child=$! + wait "$child" || : + child= +done diff --git a/recipes/core/mere-boot/init b/recipes/core/mere-boot/init new file mode 100644 index 00000000..a9475858 --- /dev/null +++ b/recipes/core/mere-boot/init @@ -0,0 +1,11 @@ +#!/bin/sh +# Keep PID 1 when handing system management to dinit. +export PATH=/bin:/sbin:/usr/bin:/usr/sbin +umask 022 +# The control socket must live on the final /run mount, not a hidden lower one. +if ! { mkdir -p /run && { mountpoint -q /run || mount -t tmpfs -o mode=0755,nosuid,nodev tmpfs /run; }; }; then + printf 'mere-boot: cannot prepare /run; entering authenticated recovery\n' >&2 + exec /bin/dinit -d /etc/dinit.d -d /usr/share/dinit.d -t recovery +fi +exec /bin/dinit -d /etc/dinit.d -d /usr/share/dinit.d \ + --log-file /run/dinit.log "$@" diff --git a/recipes/core/mere-boot/login-profile b/recipes/core/mere-boot/login-profile new file mode 100644 index 00000000..43b0561c --- /dev/null +++ b/recipes/core/mere-boot/login-profile @@ -0,0 +1,2 @@ +# Standard command paths for a Mere login shell. +export PATH=/usr/local/bin:/usr/local/sbin:/bin:/sbin:/usr/bin:/usr/sbin diff --git a/recipes/core/mere-boot/recipe.kdl b/recipes/core/mere-boot/recipe.kdl new file mode 100644 index 00000000..d600dead --- /dev/null +++ b/recipes/core/mere-boot/recipe.kdl @@ -0,0 +1,143 @@ +recipe { + name "mere-boot" + version "0.1.0" + release 1 + description "Foundational boot services for Mere Linux with dinit" + url "https://merelinux.org" + licenses "MIT" + archs "aarch64" "x86_64" + depends "busybox" +} + +source "init" { + blake3 "916bef670194f057612fa9013e67a556b0dce8cff2669eb63b903405aa01e72a" +} + +source "setup" { + blake3 "714fa6451e8ea0d12ee974fdab8fee76184188752aa97d9aaee36d8009bf705b" +} + +source "getty" { + blake3 "e942324c8eab2e87c0515f208eaa260316acb60d78e0682b224648a1dfb95a99" +} + +source "recovery" { + blake3 "0a8ff9019220c10d5aec8c2c4a6a297c92320d447218a10a7d49555ad06239d3" +} + +source "boot" { + blake3 "5f18e95596194b75ba5c1c88bb415582fd19bca610e82d28d87739492c499ac4" +} + +source "recovery.service" { + blake3 "e18e1be428ee230085d03584bab216beb477869c90a32f14eb0a04f10b1b9b5d" +} + +source "single" { + blake3 "f59f08b983904c6c47544e83d7fc276d58771297af184f638c0a167623bee445" +} + +source "boot.keep" { + blake3 "5d9e9021b9a8c8c781e714b68bad269e2a5509b8b9317ddd4c91dc2899ff99d5" +} + +source "login-profile" { + blake3 "43bdcd99c1deb6e66e5efa3b396558a7b2297159d83eb141a1698a028173a581" +} + +install { + script r#" + install -d "${DESTDIR}/sbin" "${DESTDIR}/usr/libexec/mere-boot" \ + "${DESTDIR}/etc/dinit.d/boot.d" + install -m 0755 "${MERE_SOURCES_DIR}/init" "${DESTDIR}/sbin/init" + for helper in setup getty recovery; do + install -m 0755 "${MERE_SOURCES_DIR}/$helper" "${DESTDIR}/usr/libexec/mere-boot/" + done + install -m 0644 "${MERE_SOURCES_DIR}/login-profile" "${DESTDIR}/etc/profile" + install -m 0644 "${MERE_SOURCES_DIR}/boot.keep" "${DESTDIR}/etc/dinit.d/boot.d/.keep" + install -m 0644 "${MERE_SOURCES_DIR}/boot" "${DESTDIR}/etc/dinit.d/boot" + install -m 0644 "${MERE_SOURCES_DIR}/recovery.service" "${DESTDIR}/etc/dinit.d/recovery" + install -m 0644 "${MERE_SOURCES_DIR}/single" "${DESTDIR}/etc/dinit.d/single" + "# +} + +package "mere-boot" { + files "sbin/" "usr/" "etc/" + + service "mount-proc" { + type "oneshot" + up "/usr/libexec/mere-boot/setup" "proc" + essential true + log false + } + + service "mount-sys" { + type "oneshot" + up "/usr/libexec/mere-boot/setup" "sys" + essential true + log false + depends-on "mount-proc" + } + + service "mount-dev" { + type "oneshot" + up "/usr/libexec/mere-boot/setup" "dev" + essential true + log false + depends-on "mount-proc" + } + + service "mount-rw" { + type "oneshot" + up "/usr/libexec/mere-boot/setup" "rw" + essential true + log false + depends-on "mount-proc" + } + + service "mount-runtime" { + type "oneshot" + up "/usr/libexec/mere-boot/setup" "runtime" + essential true + log false + depends-on "mount-rw" "mount-sys" "mount-dev" + } + + service "mount-fstab" { + type "oneshot" + up "/usr/libexec/mere-boot/setup" "local" + essential true + log false + depends-on "mount-runtime" "mdevd-coldplug" + } + + service "system-setup" { + type "oneshot" + up "/usr/libexec/mere-boot/setup" "system" + essential true + log false + depends-on "mount-fstab" + } + + service "sysinit" { + type "oneshot" + up "/bin/true" + essential true + log false + depends-on "system-setup" + } + + service "tty1" { + type "daemon" + command "/usr/libexec/mere-boot/getty" "tty1" "38400" + log false + depends-on "sysinit" + } + + service "ttyS0" { + type "daemon" + command "/usr/libexec/mere-boot/getty" "ttyS0" "115200" + log false + depends-on "sysinit" + } +} diff --git a/recipes/core/mere-boot/recovery b/recipes/core/mere-boot/recovery new file mode 100644 index 00000000..84c40ab5 --- /dev/null +++ b/recipes/core/mere-boot/recovery @@ -0,0 +1,9 @@ +#!/bin/sh +export PATH=/bin:/sbin:/usr/bin:/usr/sbin +printf '\nmere-boot: foundational startup failed. Authentication is required.\n' +printf 'Repair the system, then request reboot or power-off explicitly.\n' +# Dinit closes its control socket after the failed graph stops. Reopen it so +# an authenticated operator can use dinitctl and the shutdown utilities. +kill -s USR1 1 || printf 'mere-boot: could not reopen the dinit control socket\n' >&2 +# Never use login -f or fall back to an unauthenticated shell. +exec /usr/bin/login root diff --git a/recipes/core/mere-boot/recovery.service b/recipes/core/mere-boot/recovery.service new file mode 100644 index 00000000..720fdc2a --- /dev/null +++ b/recipes/core/mere-boot/recovery.service @@ -0,0 +1,4 @@ +type = process +command = /usr/libexec/mere-boot/recovery +options = runs-on-console +restart = false diff --git a/recipes/core/mere-boot/setup b/recipes/core/mere-boot/setup new file mode 100644 index 00000000..4b1c5ae4 --- /dev/null +++ b/recipes/core/mere-boot/setup @@ -0,0 +1,48 @@ +#!/bin/sh +set -eu +export PATH=/bin:/sbin:/usr/bin:/usr/sbin +exec > /dev/console 2>&1 +mount_once() { + target=$1 + shift + mountpoint -q "$target" || mount "$@" "$target" +} +case ${1:-} in + proc) + mount_once /proc -t proc -o nosuid,nodev,noexec proc + ;; + sys) + mount_once /sys -t sysfs -o nosuid,nodev,noexec sysfs + ;; + dev) + mount_once /dev -t devtmpfs -o mode=0755,nosuid devtmpfs + ;; + rw) + mount -o remount,rw / + ;; + runtime) + mkdir -p /run /dev/pts /dev/shm /tmp + mount_once /run -t tmpfs -o mode=0755,nosuid,nodev tmpfs + mount_once /dev/pts -t devpts -o nosuid,noexec,mode=0620 devpts + mount_once /dev/shm -t tmpfs -o mode=1777,nosuid,nodev tmpfs + chmod 1777 /tmp + [ -e /dev/fd ] || ln -s /proc/self/fd /dev/fd + ;; + local) + # Installation must supply fstab, even when it is intentionally empty. + [ -r /etc/fstab ] + mount -a + ;; + system) + mkdir -p /var/log /var/tmp /root /home + chmod 1777 /var/tmp + chmod 0700 /root + [ ! -r /etc/hostname ] || hostname -F /etc/hostname + [ ! -r /etc/sysctl.conf ] || sysctl -p /etc/sysctl.conf + swapon -a + ;; + *) + printf 'mere-boot: unknown setup action: %s\n' "${1:-}" >&2 + exit 2 + ;; +esac diff --git a/recipes/core/mere-boot/single b/recipes/core/mere-boot/single new file mode 100644 index 00000000..e82a71b7 --- /dev/null +++ b/recipes/core/mere-boot/single @@ -0,0 +1,2 @@ +type = internal +depends-on = recovery