This commit is contained in:
Michal S.
2026-08-04 17:59:44 +01:00
commit 69071efd58
123 changed files with 7728 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
# Apple Container requires this file; the wrapper already cleans the context.
+3
View File
@@ -0,0 +1,3 @@
/build/
/base/cheesed/target/
.DS_Store
+301
View File
@@ -0,0 +1,301 @@
ARG MOUSE_ROOTFS_STAGE=rootfs-builder
FROM alpine@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b AS toolchain
RUN apk add --no-cache \
bc=1.08.2-r1 \
build-base=0.5-r4 \
clang22=22.1.3-r2 \
cmake=4.2.3-r0 \
curl=8.21.0-r0 \
diffutils=3.12-r0 \
elfutils-dev=0.195-r0 \
file=5.47-r2 \
findutils=4.10.0-r1 \
gawk=5.3.2-r2 \
lld22=22.1.3-r0 \
linux-headers=7.0.0-r1 \
llvm=22-r0 \
patch=2.8-r0 \
perl=5.42.2-r0 \
python3=3.14.5-r0 \
rustup=1.29.0-r0 \
rsync=3.4.3-r1 \
openssl-dev=3.5.7-r0 \
xz=5.8.3-r0
RUN apk add --no-cache flex=2.6.4-r8
ENV RUSTUP_HOME=/opt/rustup
ENV CARGO_HOME=/opt/cargo
ENV PATH=/opt/cargo/bin:/usr/lib/llvm22/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
RUN rustup-init -y \
--profile minimal \
--default-toolchain 1.94.0 \
--target x86_64-unknown-linux-musl \
&& chmod -R a+rX /opt/rustup /opt/cargo
WORKDIR /work/mouse-src
ARG MOUSE_BUILD_JOBS=8
ENV MOUSE_BUILD_JOBS=${MOUSE_BUILD_JOBS}
FROM scratch AS toolchain-sources
COPY mouse-sources/toolchain/ /opt/mouse-sources/
FROM scratch AS kernel-sources
COPY mouse-sources/kernel/ /opt/mouse-sources/
FROM scratch AS firmware-sources
COPY mouse-sources/firmware/ /opt/mouse-sources/
FROM scratch AS facilities-sources
COPY mouse-sources/facilities/ /opt/mouse-sources/
FROM scratch AS storage-sources
COPY mouse-sources/storage/ /opt/mouse-sources/
FROM scratch AS pkgsrc-sources
COPY mouse-sources/pkgsrc/ /opt/mouse-sources/
FROM scratch AS software-sources
COPY mouse-sources/software/ /opt/mouse-sources/
FROM --platform=linux/amd64 alpine@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b AS pkgsrc-seed-tools
FROM toolchain AS kernel-builder
RUN apk add --no-cache bison=3.8.2-r3
COPY --from=kernel-sources /opt/mouse-sources/ /opt/mouse-sources/
COPY mouse-src/base/kernel/ /work/mouse-src/base/kernel/
COPY mouse-src/scripts/build-kernel.sh /work/mouse-src/scripts/build-kernel.sh
RUN MOUSE_SOURCE_DIR=/opt/mouse-sources \
/work/mouse-src/scripts/build-kernel.sh
FROM kernel-builder AS kernel-test-builder
COPY mouse-src/tests/fixtures/kernel-module/ /work/mouse-src/tests/fixtures/kernel-module/
COPY mouse-src/scripts/build-kernel-test-module.sh /work/mouse-src/scripts/build-kernel-test-module.sh
RUN /work/mouse-src/scripts/build-kernel-test-module.sh
# Keep the resource-heavy kernel and LLVM builds from running concurrently.
# The constant marker adds an ordering edge without passing kernel artifacts
# into the static-base cache key.
FROM kernel-builder AS kernel-ready
RUN install -m 0444 /dev/null /kernel-ready
FROM toolchain AS static-base-bootstrap
RUN apk add --no-cache \
bison=3.8.2-r3 \
byacc=20260126-r0
COPY --from=kernel-ready /kernel-ready /tmp/kernel-ready
COPY --from=toolchain-sources /opt/mouse-sources/ /opt/mouse-sources/
COPY --from=kernel-sources /opt/mouse-sources/ /opt/mouse-sources/
COPY --from=software-sources /opt/mouse-sources/ /opt/mouse-sources/
COPY mouse-src/base/support/cross-compiler-driver /work/mouse-src/base/support/cross-compiler-driver
COPY mouse-src/base/support/rust-eh-personality.c /work/mouse-src/base/support/rust-eh-personality.c
COPY mouse-src/scripts/configure-cross-toolchain.sh /work/mouse-src/scripts/configure-cross-toolchain.sh
COPY mouse-src/scripts/static-base/common.sh /work/mouse-src/scripts/static-base/common.sh
COPY mouse-src/scripts/static-base/bootstrap.sh /work/mouse-src/scripts/static-base/bootstrap.sh
RUN MOUSE_SOURCE_DIR=/opt/mouse-sources \
sh /work/mouse-src/scripts/static-base/bootstrap.sh
FROM static-base-bootstrap AS static-base-libraries
COPY mouse-src/scripts/static-base/libraries-and-accounts.sh /work/mouse-src/scripts/static-base/libraries-and-accounts.sh
RUN MOUSE_SOURCE_DIR=/opt/mouse-sources \
sh /work/mouse-src/scripts/static-base/libraries-and-accounts.sh
FROM static-base-libraries AS static-base-toolchain
COPY mouse-src/scripts/static-base/native-toolchain.sh /work/mouse-src/scripts/static-base/native-toolchain.sh
RUN MOUSE_NATIVE_TOOLCHAIN_PHASE=llvm \
MOUSE_SOURCE_DIR=/opt/mouse-sources \
sh /work/mouse-src/scripts/static-base/native-toolchain.sh
RUN MOUSE_NATIVE_TOOLCHAIN_PHASE=package-tools \
MOUSE_SOURCE_DIR=/opt/mouse-sources \
sh /work/mouse-src/scripts/static-base/native-toolchain.sh
COPY mouse-src/scripts/static-base/dynamic-runtime.sh /work/mouse-src/scripts/static-base/dynamic-runtime.sh
RUN sh /work/mouse-src/scripts/static-base/dynamic-runtime.sh
FROM static-base-toolchain AS static-base-system
COPY mouse-src/base/patches/chimerautils-native-cdefs.patch /work/mouse-src/base/patches/chimerautils-native-cdefs.patch
COPY mouse-src/base/patches/chimerautils-scanflike.patch /work/mouse-src/base/patches/chimerautils-scanflike.patch
COPY mouse-src/base/patches/tcsh-gethost-native.patch /work/mouse-src/base/patches/tcsh-gethost-native.patch
COPY mouse-src/scripts/static-base/system-services.sh /work/mouse-src/scripts/static-base/system-services.sh
RUN MOUSE_SOURCE_DIR=/opt/mouse-sources \
sh /work/mouse-src/scripts/static-base/system-services.sh
FROM static-base-system AS static-base-facilities
COPY --from=facilities-sources /opt/mouse-sources/ /opt/mouse-sources/
COPY mouse-src/base/patches/nftables-posix-configure.patch /work/mouse-src/base/patches/nftables-posix-configure.patch
COPY mouse-src/scripts/static-base/facilities.sh /work/mouse-src/scripts/static-base/facilities.sh
RUN MOUSE_SOURCE_DIR=/opt/mouse-sources \
sh /work/mouse-src/scripts/static-base/facilities.sh
FROM static-base-facilities AS static-base-storage
COPY --from=storage-sources /opt/mouse-sources/ /opt/mouse-sources/
COPY mouse-src/scripts/static-base/storage.sh /work/mouse-src/scripts/static-base/storage.sh
RUN MOUSE_SOURCE_DIR=/opt/mouse-sources \
sh /work/mouse-src/scripts/static-base/storage.sh
FROM static-base-storage AS static-base-builder
COPY mouse-src/scripts/static-base/finish.sh /work/mouse-src/scripts/static-base/finish.sh
RUN MOUSE_SOURCE_DIR=/opt/mouse-sources \
sh /work/mouse-src/scripts/static-base/finish.sh
FROM --platform=linux/amd64 scratch AS pkgsrc-seed-builder
ARG MOUSE_BUILD_JOBS=8
COPY --from=static-base-builder /work/mouse-src/build/static/rootfs /
COPY --from=pkgsrc-sources /opt/mouse-sources/pkgsrc-2026Q2.tar.gz /usr/share/mouse/pkgsrc-2026Q2.tar.gz
COPY mouse-src/base/rootfs/etc/group /etc/group
COPY mouse-src/base/rootfs/etc/mouse-release /etc/mouse-release
COPY mouse-src/base/rootfs/etc/passwd /etc/passwd
COPY mouse-src/base/patches/pkgsrc-bootstrap-mouse.patch /usr/share/mouse/pkgsrc-bootstrap-mouse.patch
COPY mouse-src/scripts/build-pkgsrc-seed.sh /usr/libexec/mouse-build-pkgsrc-seed
COPY --from=pkgsrc-seed-tools /bin/busybox /usr/libexec/mouse-build-busybox
RUN MOUSE_BUILD_JOBS="$MOUSE_BUILD_JOBS" \
sh /usr/libexec/mouse-build-pkgsrc-seed
FROM toolchain AS btrfs-image-builder
RUN apk add --no-cache \
mtools=4.0.49-r0 \
pkgconf=2.5.1-r0 \
sfdisk=2.42.1-r0 \
util-linux-dev=2.42.1-r0 \
zlib-dev=1.3.2-r0 \
zstd-dev=1.5.7-r2
COPY --from=storage-sources /opt/mouse-sources/btrfs-progs-v7.0.tar.xz /opt/
COPY --from=storage-sources /opt/mouse-sources/limine-binary-12.3.2.tar.xz /opt/mouse-sources/limine-binary-12.3.2.tar.xz
RUN tar -C /work -xf /opt/btrfs-progs-v7.0.tar.xz \
&& cd /work/btrfs-progs-v7.0 \
&& ./configure \
--prefix=/usr \
--disable-backtrace \
--disable-documentation \
--disable-convert \
--disable-lzo \
--disable-libudev \
--disable-python \
--disable-zoned \
--with-crypto=builtin \
&& make \
-j"$MOUSE_BUILD_JOBS" \
mkfs.btrfs \
&& install -m 0755 \
/work/btrfs-progs-v7.0/mkfs.btrfs \
/opt/mkfs.btrfs
FROM static-base-builder AS image-commands-builder
COPY mouse-src/base/support/mouse-bootenv /work/mouse-src/base/support/mouse-bootenv
COPY mouse-src/scripts/audit-static-base.sh /work/mouse-src/scripts/audit-static-base.sh
COPY mouse-src/scripts/build-image-commands.sh /work/mouse-src/scripts/build-image-commands.sh
RUN /work/mouse-src/scripts/build-image-commands.sh \
&& /work/mouse-src/scripts/audit-static-base.sh /work/mouse-src/build/static/rootfs
FROM image-commands-builder AS test-fixtures-builder
COPY mouse-src/tests/fixtures/mouse-echo/mouse-echo.c /work/mouse-src/tests/fixtures/mouse-echo/mouse-echo.c
RUN /work/mouse-src/build/static/cross-tools/bin/x86_64-mouse-linux-musl-cc \
-O2 -Wall -Wextra -Werror \
/work/mouse-src/tests/fixtures/mouse-echo/mouse-echo.c \
-o /opt/mouse-echo
FROM image-commands-builder AS cheesed-builder
COPY mouse-src/base/cheesed/ /work/mouse-src/base/cheesed/
RUN CHEESED_TARGET=x86_64-unknown-linux-musl \
/work/mouse-src/base/cheesed/scripts/build-static.sh \
&& install -m 0755 \
/work/mouse-src/base/cheesed/target/x86_64-unknown-linux-musl/release/cheesed \
/opt/cheesed
FROM btrfs-image-builder AS rootfs-common
COPY --from=cheesed-builder /work/mouse-src/build/static/rootfs /work/mouse-src/build/static/rootfs
COPY --from=cheesed-builder /opt/cheesed /opt/cheesed
COPY --from=kernel-builder /work/mouse-src/build/vmlinuz-virt /work/mouse-src/build/vmlinuz-virt
COPY --from=pkgsrc-seed-builder /usr/local /opt/mouse-pkgsrc-seed/usr/local
COPY --from=pkgsrc-seed-builder /var/db/pkg /opt/mouse-pkgsrc-seed/var/db/pkg
COPY mouse-src/base/rootfs/ /work/mouse-src/base/rootfs/
COPY mouse-src/base/patches/pkgsrc-rc.subr-cgroup.patch /work/mouse-src/base/patches/pkgsrc-rc.subr-cgroup.patch
COPY mouse-src/scripts/audit-static-base.sh /work/mouse-src/scripts/audit-static-base.sh
COPY mouse-src/scripts/build-image.sh /work/mouse-src/scripts/build-image.sh
COPY mouse-src/scripts/build-boot-disk.sh /work/mouse-src/scripts/build-boot-disk.sh
COPY mouse-src/base/limine.conf /work/mouse-src/base/limine.conf
FROM rootfs-common AS rootfs-builder
COPY --from=kernel-builder /work/mouse-src/build/kernel/modules /opt/mouse-kernel-modules
RUN MOUSE_SOURCE_DIR=/opt/mouse-sources \
MOUSE_CHEESED_BINARY=/opt/cheesed \
MOUSE_BTRFS_IMAGE_TOOL=/opt/mkfs.btrfs \
MOUSE_PKGSRC_SEED=/opt/mouse-pkgsrc-seed \
MOUSE_KERNEL_MODULES=/opt/mouse-kernel-modules \
/work/mouse-src/scripts/build-image.sh
FROM rootfs-common AS rootfs-test-builder
COPY --from=kernel-test-builder /work/mouse-src/build/kernel/test-modules /opt/mouse-kernel-modules
COPY --from=test-fixtures-builder /opt/mouse-echo /opt/mouse-echo
COPY mouse-src/tests/fixtures/ /work/mouse-src/tests/fixtures/
RUN MOUSE_SOURCE_DIR=/opt/mouse-sources \
MOUSE_CHEESED_BINARY=/opt/cheesed \
MOUSE_BTRFS_IMAGE_TOOL=/opt/mkfs.btrfs \
MOUSE_PKGSRC_SEED=/opt/mouse-pkgsrc-seed \
MOUSE_KERNEL_MODULES=/opt/mouse-kernel-modules \
MOUSE_MOUSE_ECHO_BINARY=/opt/mouse-echo \
MOUSE_INCLUDE_TEST_FIXTURES=1 \
/work/mouse-src/scripts/build-image.sh
FROM ${MOUSE_ROOTFS_STAGE} AS selected-rootfs
FROM toolchain AS artifact-packer
COPY --from=selected-rootfs /work/mouse-src/build/mouse-disk.img /
COPY --from=firmware-sources /opt/mouse-sources/ovmf-0.0.202605-r0.apk /opt/
RUN mkdir -p /opt/ovmf \
&& tar -xf /opt/ovmf-0.0.202605-r0.apk \
-C /opt/ovmf \
usr/share/OVMF/OVMF_CODE.fd \
&& gzip -1 /mouse-disk.img
FROM scratch AS artifacts
COPY --from=kernel-builder /work/mouse-src/build/vmlinuz-virt /
COPY --from=artifact-packer /mouse-disk.img.gz /
COPY --from=artifact-packer /opt/ovmf/usr/share/OVMF/OVMF_CODE.fd /ovmf-code.fd
+41
View File
@@ -0,0 +1,41 @@
.PHONY: all check check-locks clean fetch image run
MOUSE_BUILDER_CPUS ?= 8
MOUSE_BUILDER_MEMORY ?= 8G
MOUSE_BUILD_JOBS ?= 8
MOUSE_BOOT_MODE ?= firmware
MOUSE_NETWORK ?= 1
BUILD_ENV = \
MOUSE_BUILDER_CPUS="$(MOUSE_BUILDER_CPUS)" \
MOUSE_BUILDER_MEMORY="$(MOUSE_BUILDER_MEMORY)" \
MOUSE_BUILD_JOBS="$(MOUSE_BUILD_JOBS)"
CHECKS = boot firmware world service-integration storage btrfs network \
multi-user toolchain pkgsrc
all: image
check-locks:
./scripts/check-source-locks.sh
fetch: check-locks
./scripts/fetch-sources.sh
image: check-locks
$(BUILD_ENV) ./scripts/build-in-container.sh
run:
MOUSE_BOOT_MODE="$(MOUSE_BOOT_MODE)" \
MOUSE_NETWORK="$(MOUSE_NETWORK)" \
./scripts/run-qemu.sh
check: check-locks
$(BUILD_ENV) MOUSE_INCLUDE_TEST_FIXTURES=1 \
./scripts/build-in-container.sh
@set -e; for check in $(CHECKS); do ./tests/acceptance/$$check.sh; done
clean:
rm -f build/vmlinuz-virt \
build/mouse-disk.img build/ovmf-code.fd \
build/mouse-rootfs.btrfs
+72
View File
@@ -0,0 +1,72 @@
# MOUSE
MOUSE is the **Monolithic Opinionated Unix-like System Environment**: a small
Linux system with a versioned base, pkgsrc for third-party software, and
`cheesed` as PID 1.
## Build
The build needs Apple Container, Docker, or Podman, plus QEMU and Expect for
the runtime checks.
```sh
make image
make run
make check
```
Artifacts and downloaded sources are kept in `build/`. The default builder has
8 CPUs, 8 GB of memory, and 8 build jobs; override those with
`MOUSE_BUILDER_CPUS`, `MOUSE_BUILDER_MEMORY`, and `MOUSE_BUILD_JOBS`.
`MOUSE_NETWORK=0 make run` disables QEMU networking.
Development images contain `root` and `toor`, both with the password
`cheddar`. `root` uses `/bin/sh` for emergency administration; `toor` uses
`/bin/tcsh` for ordinary administration. Change both passwords before exposing
an image to a network.
## System
The base owns `/bin`, `/sbin`, `/usr/bin`, and `/usr/sbin`; pkgsrc owns
`/usr/local`. `/var`, `/home`, `/srv`, and `/usr/local` are shared by both root
environments, `/etc` follows the selected root, and `/run` is rebuilt at boot.
The fixed `/usr/local` package prefix keeps pkgsrc out of base-owned paths.
Limine boots the default or alternate root from the same disk.
`mouse-bootenv prepare` snapshots the running root into the inactive one;
`activate` selects it and `rollback` selects the other root. Automated base
updates and configuration merging are not implemented yet.
`cheesed` mounts early filesystems, reaps children, supervises services in
cgroups, and handles shutdown. It identifies itself as
`Cheesed to meet you! v0.1.0`. Service definitions live in `/etc/rc.d` and
`/usr/local/etc/rc.d`; dependency cycles, missing facilities, and duplicate
names are rejected.
Persistent service policy lives only in `/etc/rc.conf`:
```sh
enable_network=YES
enable_cron=YES
enable_ntpd=YES
```
Assignments are exactly `enable_NAME=YES` or `enable_NAME=NO`; shell expansion,
includes, and other keys are invalid. `/sbin/service` controls the running
system without editing boot policy.
## Packages
Images contain the pinned pkgsrc 2026Q2 tree in `/usr/local/src/pkgsrc`, with
`bmake` and the package tools under `/usr/local`. Packages use
`/usr/local/etc` for configuration, `/var/db/pkg` for records, and
`/var/cache/pkgsrc` for distfiles and binary packages. This state survives a
base rollback.
## Checks
`make check` builds the assembled disk with test fixtures, then boots it under
QEMU. It covers BIOS and UEFI boot, both Btrfs roots, services and cgroup
cleanup, users, networking, storage tools, native C and C++ compilation, pkgsrc
installation, and persistence across reboots. The build also rejects changed
source checksums and dynamic base executables.
+2
View File
@@ -0,0 +1,2 @@
[target.x86_64-unknown-linux-musl]
linker = "rust-lld"
+16
View File
@@ -0,0 +1,16 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "cheesed"
version = "0.1.0"
dependencies = [
"libc",
]
[[package]]
name = "libc"
version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
+17
View File
@@ -0,0 +1,17 @@
[package]
name = "cheesed"
version = "0.1.0"
edition = "2024"
rust-version = "1.85"
description = "MOUSE PID 1 and service supervisor"
license = "BSD-2-Clause"
[dependencies]
libc = "=0.2.189"
[lints.rust]
unsafe_op_in_unsafe_fn = "deny"
[lints.clippy]
all = "deny"
pedantic = "deny"
+25
View File
@@ -0,0 +1,25 @@
#!/bin/sh
set -eu
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
repo_dir=$(dirname "$script_dir")
target=${CHEESED_TARGET:-x86_64-unknown-linux-musl}
if ! rustup target list --installed | grep -qx "$target"; then
printf '%s\n' "missing Rust target: $target" >&2
printf '%s\n' "install it with: rustup target add $target" >&2
exit 1
fi
(
cd "$repo_dir"
cargo build --locked --release --target "$target"
)
binary="$repo_dir/target/$target/release/cheesed"
if ! file "$binary" | grep -Eq 'statically linked|static-pie linked'; then
printf '%s\n' "release binary is not statically linked: $binary" >&2
exit 1
fi
printf '%s\n' "$binary"
+87
View File
@@ -0,0 +1,87 @@
//! Core process model for the MOUSE init and service supervisor.
use std::fmt;
#[cfg(any(target_os = "linux", test))]
mod policy;
/// The first message emitted after PID 1 bootstrap succeeds.
pub const STARTUP_BANNER: &str = concat!("Cheesed to meet you! v", env!("CARGO_PKG_VERSION"));
/// The terminal system action requested from PID 1.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ShutdownAction {
Reboot,
Poweroff,
}
impl fmt::Display for ShutdownAction {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Reboot => formatter.write_str("reboot"),
Self::Poweroff => formatter.write_str("poweroff"),
}
}
}
/// Maps the two shutdown signals accepted by PID 1.
#[must_use]
pub fn shutdown_action(signal: i32) -> Option<ShutdownAction> {
match signal {
libc::SIGINT => Some(ShutdownAction::Reboot),
libc::SIGTERM => Some(ShutdownAction::Poweroff),
_ => None,
}
}
#[cfg(target_os = "linux")]
mod linux;
/// Runs `cheesed` in its normal PID 1 mode.
///
/// # Errors
///
/// Returns an error if invoked outside PID 1. Once PID 1 initialization starts,
/// failures are logged and kept alive rather than returned.
#[cfg(target_os = "linux")]
pub fn run() -> Result<(), String> {
linux::run()
}
/// Rejects normal init mode on non-Linux development hosts.
///
/// # Errors
///
/// Always returns an error because PID 1 mode requires Linux.
#[cfg(not(target_os = "linux"))]
pub fn run() -> Result<(), String> {
Err("cheesed PID 1 mode is supported only on Linux".to_owned())
}
#[cfg(test)]
mod tests {
use super::{STARTUP_BANNER, ShutdownAction, shutdown_action};
#[test]
fn startup_banner_includes_the_package_version() {
assert_eq!(STARTUP_BANNER, "Cheesed to meet you! v0.1.0");
}
#[test]
fn sigint_requests_reboot() {
assert_eq!(shutdown_action(libc::SIGINT), Some(ShutdownAction::Reboot));
}
#[test]
fn sigterm_requests_poweroff() {
assert_eq!(
shutdown_action(libc::SIGTERM),
Some(ShutdownAction::Poweroff)
);
}
#[test]
fn unrelated_signal_is_not_a_shutdown_request() {
assert_eq!(shutdown_action(libc::SIGCHLD), None);
}
}
+172
View File
@@ -0,0 +1,172 @@
mod boot;
mod process;
mod services;
use self::boot::{configure_hostname, mount_early_filesystems, mount_mouse_storage};
use self::process::{
child_exited, install_signal_handlers, perform_shutdown, reap_children, spawn_emergency_shell,
take_shutdown_action,
};
use self::services::{ServiceManager, boot_services};
use crate::STARTUP_BANNER;
use std::fs::{File, OpenOptions};
use std::io::{self, Write};
use std::thread;
use std::time::Duration;
const CONSOLE_PATH: &str = "/dev/console";
const POLL_INTERVAL: Duration = Duration::from_millis(50);
const SHELL_RESTART_DELAY: Duration = Duration::from_secs(1);
pub(crate) fn run() -> Result<(), String> {
// SAFETY: getpid has no preconditions and does not mutate memory.
if unsafe { libc::getpid() } != 1 {
return Err("normal mode must run as PID 1".to_owned());
}
run_pid1()
}
fn run_pid1() -> ! {
let mut console = loop {
match Console::open() {
Ok(console) => break console,
Err(error) => {
eprintln!("cheesed: cannot open {CONSOLE_PATH} for early boot logging: {error}");
thread::sleep(SHELL_RESTART_DELAY);
}
}
};
// SAFETY: cheesed is single-threaded here and passes a conventional process umask.
unsafe {
libc::umask(0o022);
}
if let Err(error) = std::env::set_current_dir("/") {
remain_alive_after_fatal_error(&mut console, &format!("cannot chdir to /: {error}"));
}
if let Err(error) = install_signal_handlers() {
remain_alive_after_fatal_error(
&mut console,
&format!("cannot install signal handlers: {error}"),
);
}
if let Err(error) = mount_early_filesystems() {
remain_alive_after_fatal_error(
&mut console,
&format!("cannot mount early filesystems: {error}"),
);
}
if let Err(error) = mount_mouse_storage() {
remain_alive_after_fatal_error(
&mut console,
&format!("cannot mount MOUSE storage: {error}"),
);
}
if let Err(error) = configure_hostname() {
remain_alive_after_fatal_error(
&mut console,
&format!("cannot configure hostname: {error}"),
);
}
console.write_line(STARTUP_BANNER);
console.log("base bootstrap complete; cheesed is PID 1");
let mut services = None;
let emergency = match boot_services(&mut console) {
Ok(manager) => {
services = Some(manager);
false
}
Err(error) => {
console.log(&format!("{error}; entering emergency console"));
true
}
};
let shell_pid = if emergency {
match spawn_emergency_shell(&mut console) {
Ok(pid) => Some(pid),
Err(error) => {
console.log(&error);
None
}
}
} else {
None
};
steady_state(&mut console, services, emergency, shell_pid)
}
fn steady_state(
console: &mut Console,
mut services: Option<ServiceManager>,
emergency: bool,
mut shell_pid: Option<libc::pid_t>,
) -> ! {
loop {
if child_exited() {
let shell_exited = reap_children(console, shell_pid);
if shell_exited {
shell_pid = None;
}
}
if let Some(manager) = services.as_mut() {
manager.poll(console);
}
if let Some(action) = take_shutdown_action() {
perform_shutdown(console, shell_pid, services.as_mut(), action);
}
if emergency && shell_pid.is_none() {
console.log("emergency shell exited; restarting it in one second");
thread::sleep(SHELL_RESTART_DELAY);
shell_pid = Some(match spawn_emergency_shell(console) {
Ok(pid) => pid,
Err(error) => {
console.log(&format!("cannot restart emergency shell: {error}"));
continue;
}
});
}
thread::sleep(POLL_INTERVAL);
}
}
fn remain_alive_after_fatal_error(console: &mut Console, error: &str) -> ! {
console.log(error);
console.log("unrecoverable bootstrap failure; PID 1 will remain alive");
loop {
thread::sleep(Duration::from_secs(60));
}
}
struct Console {
file: File,
}
impl Console {
fn open() -> io::Result<Self> {
let file = OpenOptions::new()
.read(true)
.write(true)
.open(CONSOLE_PATH)?;
Ok(Self { file })
}
fn log(&mut self, message: &str) {
self.write_line(&format!("cheesed: {message}"));
}
fn write_line(&mut self, message: &str) {
let _ = writeln!(self.file, "{message}");
let _ = self.file.flush();
}
fn clone_file(&self) -> io::Result<File> {
self.file.try_clone()
}
}
+146
View File
@@ -0,0 +1,146 @@
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
const HOSTNAME_PATH: &str = "/etc/hostname";
const CGROUP_ROOT: &str = "/sys/fs/cgroup";
const MOUSE_ROOT_PARTUUID: &str = "4d4f5553-4500-4000-8000-000000000002";
const BTRFS_SUPER_MAGIC: u64 = 0x9123_683e;
pub(super) fn mount_early_filesystems() -> io::Result<()> {
mount_filesystem(c"devtmpfs".as_ptr(), c"/dev".as_ptr(), c"devtmpfs".as_ptr())?;
mount_filesystem(c"proc".as_ptr(), c"/proc".as_ptr(), c"proc".as_ptr())?;
mount_filesystem(c"sysfs".as_ptr(), c"/sys".as_ptr(), c"sysfs".as_ptr())?;
std::fs::create_dir_all(CGROUP_ROOT)?;
mount_filesystem(
c"cgroup2".as_ptr(),
c"/sys/fs/cgroup".as_ptr(),
c"cgroup2".as_ptr(),
)?;
mount_filesystem(c"tmpfs".as_ptr(), c"/run".as_ptr(), c"tmpfs".as_ptr())
}
fn mount_filesystem(
source: *const libc::c_char,
target: *const libc::c_char,
filesystem_type: *const libc::c_char,
) -> io::Result<()> {
// SAFETY: all pointers are valid, static, NUL-terminated C strings; no data
// argument is required for these pseudo-filesystems.
if unsafe {
libc::mount(
source,
target,
filesystem_type,
0,
std::ptr::null::<libc::c_void>(),
)
} == -1
{
let error = io::Error::last_os_error();
if error.raw_os_error() != Some(libc::EBUSY) {
return Err(error);
}
}
Ok(())
}
pub(super) fn mount_mouse_storage() -> io::Result<()> {
let device = match mouse_root_device() {
Ok(device) => device,
Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(()),
Err(error) => return Err(error),
};
let device = std::ffi::CString::new(device.as_os_str().as_bytes())
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
std::fs::create_dir_all("/run/mouse-root")?;
mount_btrfs(&device, c"/run/mouse-root", c"subvolid=5")?;
if root_is_btrfs()? {
for (target, subvolume) in [
(c"/usr/local", c"subvol=usr-local"),
(c"/var", c"subvol=var"),
(c"/home", c"subvol=home"),
(c"/srv", c"subvol=srv"),
] {
mount_btrfs(&device, target, subvolume)?;
}
}
Ok(())
}
fn mouse_root_device() -> io::Result<PathBuf> {
let expected = format!("PARTUUID={MOUSE_ROOT_PARTUUID}");
for entry in std::fs::read_dir("/sys/class/block")? {
let entry = entry?;
let Ok(uevent) = std::fs::read_to_string(entry.path().join("uevent")) else {
continue;
};
if uevent.lines().any(|line| line == expected) {
let device = Path::new("/dev").join(entry.file_name());
if device.exists() {
return Ok(device);
}
}
}
Err(io::Error::new(
io::ErrorKind::NotFound,
format!("MOUSE root partition {MOUSE_ROOT_PARTUUID} is not present"),
))
}
fn mount_btrfs(
device: &std::ffi::CStr,
target: &std::ffi::CStr,
options: &std::ffi::CStr,
) -> io::Result<()> {
// SAFETY: all pointers reference NUL-terminated strings for the duration
// of the call. The btrfs device and subvolume layout are fixed MOUSE image
// contracts rather than discovered mount policy.
if unsafe {
libc::mount(
device.as_ptr(),
target.as_ptr(),
c"btrfs".as_ptr(),
0,
options.as_ptr().cast::<libc::c_void>(),
)
} == -1
{
let error = io::Error::last_os_error();
if error.raw_os_error() != Some(libc::EBUSY) {
return Err(error);
}
}
Ok(())
}
fn root_is_btrfs() -> io::Result<bool> {
// SAFETY: status points to writable storage and the path is a static,
// NUL-terminated string.
let mut status: libc::statfs = unsafe { std::mem::zeroed() };
if unsafe { libc::statfs(c"/".as_ptr(), &raw mut status) } == -1 {
return Err(io::Error::last_os_error());
}
Ok(status.f_type as u64 == BTRFS_SUPER_MAGIC)
}
pub(super) fn configure_hostname() -> io::Result<()> {
let hostname = std::fs::read_to_string(HOSTNAME_PATH)?;
let hostname = hostname.trim();
if hostname.is_empty() || hostname.as_bytes().contains(&0) {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("{HOSTNAME_PATH} must contain a non-empty hostname"),
));
}
// SAFETY: hostname points to readable bytes for the supplied length, and
// sethostname does not require a terminating NUL.
if unsafe { libc::sethostname(hostname.as_ptr().cast::<libc::c_char>(), hostname.len()) } == -1
{
return Err(io::Error::last_os_error());
}
Ok(())
}
+437
View File
@@ -0,0 +1,437 @@
use super::services::ServiceManager;
use super::{Console, POLL_INTERVAL};
use crate::{ShutdownAction, shutdown_action};
use std::fs::OpenOptions;
use std::io;
use std::os::fd::AsRawFd;
use std::os::unix::process::{CommandExt, ExitStatusExt};
use std::path::Path;
use std::process::{Command, ExitStatus, Stdio};
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
use std::thread;
use std::time::{Duration, Instant};
const EMERGENCY_SHELL: &str = "/bin/tcsh";
pub(super) const TERMINATION_GRACE: Duration = Duration::from_secs(5);
static CHILD_EXITED: AtomicBool = AtomicBool::new(false);
static SHUTDOWN_SIGNAL: AtomicI32 = AtomicI32::new(0);
extern "C" fn handle_signal(signal: libc::c_int) {
if signal == libc::SIGCHLD {
CHILD_EXITED.store(true, Ordering::Relaxed);
} else {
let _ = SHUTDOWN_SIGNAL.compare_exchange(0, signal, Ordering::Relaxed, Ordering::Relaxed);
}
}
pub(super) fn child_exited() -> bool {
CHILD_EXITED.swap(false, Ordering::Relaxed)
}
pub(super) fn install_signal_handlers() -> io::Result<()> {
for signal in [libc::SIGCHLD, libc::SIGINT, libc::SIGTERM] {
// SAFETY: zero is a valid initial representation for sigaction before its
// fields and mask are initialized below.
let mut action: libc::sigaction = unsafe { std::mem::zeroed() };
action.sa_sigaction = handle_signal as *const () as usize;
action.sa_flags = libc::SA_RESTART;
if signal == libc::SIGCHLD {
action.sa_flags |= libc::SA_NOCLDSTOP;
}
// SAFETY: action owns a valid sigset_t and the signal number is known.
if unsafe { libc::sigemptyset(&raw mut action.sa_mask) } == -1 {
return Err(io::Error::last_os_error());
}
// SAFETY: action remains alive for the call and the old action is not needed.
if unsafe { libc::sigaction(signal, &raw const action, std::ptr::null_mut()) } == -1 {
return Err(io::Error::last_os_error());
}
}
Ok(())
}
pub(super) enum CommandResult {
Exited(ExitStatus),
Shutdown(ShutdownAction),
}
pub(super) fn command_with_console(
program: &str,
arguments: &[&str],
console: &mut Console,
timeout: Duration,
service_cgroup: Option<&Path>,
) -> Result<CommandResult, String> {
let stdin = console
.clone_file()
.map_err(|error| format!("cannot duplicate console input: {error}"))?;
let stdout = console
.clone_file()
.map_err(|error| format!("cannot duplicate console output: {error}"))?;
let stderr = console
.clone_file()
.map_err(|error| format!("cannot duplicate console error output: {error}"))?;
let mut command = Command::new(program);
command
.args(arguments)
.env("PATH", "/sbin:/bin:/usr/sbin:/usr/bin")
.stdin(Stdio::from(stdin))
.stdout(Stdio::from(stdout))
.stderr(Stdio::from(stderr));
let cgroup_procs = service_cgroup
.map(|cgroup| {
OpenOptions::new()
.write(true)
.open(cgroup.join("cgroup.procs"))
})
.transpose()
.map_err(|error| format!("cannot open service cgroup: {error}"))?;
if let Some(cgroup) = service_cgroup {
command.env("MOUSE_SERVICE_CGROUP", cgroup);
command.env("MOUSE_SERVICE_PIDS", cgroup_pids(cgroup)?);
}
let cgroup_fd = cgroup_procs.as_ref().map(AsRawFd::as_raw_fd);
// SAFETY: this closure runs between fork and exec, and setpgid is
// async-signal-safe. A separate group lets PID 1 terminate a whole
// transition, including service-script descendants.
unsafe {
command.pre_exec(move || {
if libc::setpgid(0, 0) == -1 {
return Err(io::Error::last_os_error());
}
if let Some(fd) = cgroup_fd {
let pid = b"0\n";
if libc::write(fd, pid.as_ptr().cast(), pid.len()) == -1 {
return Err(io::Error::last_os_error());
}
}
Ok(())
});
}
let child = command
.spawn()
.map_err(|error| format!("cannot run {program}: {error}"))?;
let pid = i32::try_from(child.id()).map_err(|_| "child PID does not fit pid_t".to_owned())?;
let deadline = Instant::now() + timeout;
loop {
if let Some(status) = reap_during_command(console, pid)? {
return Ok(CommandResult::Exited(status));
}
if let Some(action) = take_shutdown_action() {
terminate_process_group(pid, program, console);
return Ok(CommandResult::Shutdown(action));
}
if Instant::now() >= deadline {
terminate_process_group(pid, program, console);
return Err(format!(
"{program} {} timed out after {} seconds",
arguments.join(" "),
timeout.as_secs()
));
}
thread::sleep(POLL_INTERVAL);
}
}
fn cgroup_pids(cgroup: &Path) -> Result<String, String> {
let contents = std::fs::read_to_string(cgroup.join("cgroup.procs"))
.map_err(|error| format!("cannot read {}: {error}", cgroup.display()))?;
let mut pids = Vec::new();
for pid in contents.lines() {
if pid.is_empty() || !pid.bytes().all(|byte| byte.is_ascii_digit()) {
return Err(format!("invalid PID in {}", cgroup.display()));
}
pids.push(pid);
}
Ok(pids.join(" "))
}
pub(super) fn restore_shutdown_action(action: ShutdownAction) {
SHUTDOWN_SIGNAL.store(
match action {
ShutdownAction::Reboot => libc::SIGINT,
ShutdownAction::Poweroff => libc::SIGTERM,
},
Ordering::Relaxed,
);
}
pub(super) fn take_shutdown_action() -> Option<ShutdownAction> {
shutdown_action(SHUTDOWN_SIGNAL.swap(0, Ordering::Relaxed))
}
fn reap_during_command(
console: &mut Console,
command_pid: libc::pid_t,
) -> Result<Option<ExitStatus>, String> {
CHILD_EXITED.store(false, Ordering::Relaxed);
loop {
let mut status = 0;
// SAFETY: status is writable and WNOHANG makes the call nonblocking.
let pid = unsafe { libc::waitpid(-1, &raw mut status, libc::WNOHANG) };
match pid {
value if value == command_pid => {
return Ok(Some(ExitStatus::from_raw(status)));
}
value if value > 0 => {
console.log(&format!(
"reaped orphan PID {value} ({})",
describe_wait_status(status)
));
}
0 => return Ok(None),
_ => {
let error = io::Error::last_os_error();
return if error.raw_os_error() == Some(libc::ECHILD) {
Err("transition child disappeared without an exit status".to_owned())
} else {
Err(format!("waitpid failed during transition: {error}"))
};
}
}
}
}
fn terminate_process_group(pid: libc::pid_t, program: &str, console: &mut Console) {
// SAFETY: negative pid addresses the process group created before exec.
let term_result = unsafe { libc::kill(-pid, libc::SIGTERM) };
if term_result == -1 && io::Error::last_os_error().raw_os_error() != Some(libc::ESRCH) {
console.log(&format!("cannot terminate {program} process group {pid}"));
}
let deadline = Instant::now() + TERMINATION_GRACE;
while Instant::now() < deadline {
match reap_during_command(console, pid) {
Ok(Some(_)) | Err(_) => return,
Ok(None) => thread::sleep(POLL_INTERVAL),
}
}
// SAFETY: negative pid addresses the same known process group.
unsafe {
libc::kill(-pid, libc::SIGKILL);
}
loop {
match reap_during_command(console, pid) {
Ok(Some(_)) | Err(_) => return,
Ok(None) => thread::sleep(POLL_INTERVAL),
}
}
}
pub(super) fn describe_exit_status(status: ExitStatus) -> String {
status
.code()
.map_or_else(|| "a signal".to_owned(), |code| format!("status {code}"))
}
pub(super) fn spawn_emergency_shell(console: &mut Console) -> Result<libc::pid_t, String> {
let stdin = console
.clone_file()
.map_err(|error| format!("cannot duplicate console input: {error}"))?;
let stdout = console
.clone_file()
.map_err(|error| format!("cannot duplicate console output: {error}"))?;
let stderr = console
.clone_file()
.map_err(|error| format!("cannot duplicate console error output: {error}"))?;
let mut command = Command::new(EMERGENCY_SHELL);
command
.arg("-l")
.env_clear()
.env("HOME", "/root")
.env("LOGNAME", "root")
.env("PATH", "/sbin:/bin:/usr/sbin:/usr/bin")
.env("SHELL", EMERGENCY_SHELL)
.env("TERM", "linux")
.env("USER", "root")
.stdin(Stdio::from(stdin))
.stdout(Stdio::from(stdout))
.stderr(Stdio::from(stderr));
// SAFETY: this closure runs after fork and before exec in the single-threaded
// child. It invokes only async-signal-safe system calls and reports failures
// through a preallocated io::Error.
unsafe {
command.pre_exec(|| {
if libc::setsid() == -1 {
return Err(io::Error::last_os_error());
}
if libc::ioctl(libc::STDIN_FILENO, libc::TIOCSCTTY, 0) == -1 {
return Err(io::Error::last_os_error());
}
Ok(())
});
}
let child = command
.spawn()
.map_err(|error| format!("cannot start {EMERGENCY_SHELL}: {error}"))?;
let pid = i32::try_from(child.id()).map_err(|_| "child PID does not fit pid_t".to_owned())?;
console.log(&format!("started emergency shell as PID {pid}"));
Ok(pid)
}
pub(super) fn reap_children(console: &mut Console, shell_pid: Option<libc::pid_t>) -> bool {
let mut shell_exited = false;
loop {
let mut status = 0;
// SAFETY: status points to writable storage and WNOHANG makes this nonblocking.
let pid = unsafe { libc::waitpid(-1, &raw mut status, libc::WNOHANG) };
match pid {
value if value > 0 => {
let description = describe_wait_status(status);
if Some(value) == shell_pid {
console.log(&format!("emergency shell PID {value} {description}"));
shell_exited = true;
} else {
console.log(&format!("reaped orphan PID {value} ({description})"));
}
}
0 => break,
_ => {
let error = io::Error::last_os_error();
if error.raw_os_error() != Some(libc::ECHILD) {
console.log(&format!("waitpid failed: {error}"));
}
break;
}
}
}
shell_exited
}
fn describe_wait_status(status: libc::c_int) -> String {
if libc::WIFEXITED(status) {
format!("exited with status {}", libc::WEXITSTATUS(status))
} else if libc::WIFSIGNALED(status) {
format!("was killed by signal {}", libc::WTERMSIG(status))
} else {
format!("changed state with wait status {status}")
}
}
pub(super) fn perform_shutdown(
console: &mut Console,
shell_pid: Option<libc::pid_t>,
services: Option<&mut ServiceManager>,
action: ShutdownAction,
) -> ! {
console.log(&format!("received {action} request"));
stop_shell(console, shell_pid);
if let Some(manager) = services {
manager.stop_all(console);
}
// SAFETY: sync has no preconditions and is required before the terminal reboot call.
unsafe {
libc::sync();
}
unmount_mouse_storage(console);
remount_root_read_only(console);
let command = match action {
ShutdownAction::Reboot => libc::RB_AUTOBOOT,
ShutdownAction::Poweroff => libc::RB_POWER_OFF,
};
// SAFETY: cheesed is PID 1 and passes one of Linux's defined reboot commands.
if unsafe { libc::reboot(command) } == -1 {
console.log(&format!(
"{action} syscall failed: {}; PID 1 will remain alive",
io::Error::last_os_error()
));
}
loop {
thread::sleep(Duration::from_secs(60));
}
}
fn unmount_mouse_storage(console: &mut Console) {
for target in [
c"/srv",
c"/home",
c"/var",
c"/usr/local",
c"/run/mouse-root",
] {
// SAFETY: target is a static NUL-terminated mount path and no flags
// request lazy or forced unmounting.
if unsafe { libc::umount2(target.as_ptr(), 0) } == -1 {
let error = io::Error::last_os_error();
if !matches!(error.raw_os_error(), Some(libc::EINVAL | libc::ENOENT)) {
console.log(&format!(
"cannot unmount {}: {error}",
target.to_string_lossy()
));
}
}
}
}
fn remount_root_read_only(console: &mut Console) {
// SAFETY: all pointers are either null or point to static NUL-terminated
// strings. MS_REMOUNT changes the existing root mount rather than selecting
// or discovering storage.
let result = unsafe {
libc::mount(
std::ptr::null(),
c"/".as_ptr(),
std::ptr::null(),
libc::MS_REMOUNT | libc::MS_RDONLY,
std::ptr::null(),
)
};
if result == -1 {
console.log(&format!(
"cannot remount root filesystem read-only: {}",
io::Error::last_os_error()
));
} else {
console.log("root filesystem remounted read-only");
}
}
fn stop_shell(console: &mut Console, shell_pid: Option<libc::pid_t>) {
let Some(pid) = shell_pid else {
return;
};
// SAFETY: pid is a direct child PID and SIGTERM is a valid signal.
if unsafe { libc::kill(pid, libc::SIGTERM) } == -1 {
let error = io::Error::last_os_error();
if error.raw_os_error() != Some(libc::ESRCH) {
console.log(&format!("cannot stop emergency shell PID {pid}: {error}"));
}
return;
}
let deadline = Instant::now() + TERMINATION_GRACE;
while Instant::now() < deadline {
if wait_for_pid(pid) {
console.log(&format!("emergency shell PID {pid} stopped"));
return;
}
thread::sleep(POLL_INTERVAL);
}
console.log(&format!(
"emergency shell PID {pid} exceeded shutdown timeout; sending SIGKILL"
));
// SAFETY: pid is the known shell PID and SIGKILL is a valid signal.
unsafe {
libc::kill(pid, libc::SIGKILL);
}
let _ = wait_for_pid(pid);
}
fn wait_for_pid(pid: libc::pid_t) -> bool {
let mut status = 0;
// SAFETY: status points to writable storage, pid is a known child, and WNOHANG
// makes the check nonblocking.
let result = unsafe { libc::waitpid(pid, &raw mut status, libc::WNOHANG) };
result == pid
|| (result == -1 && io::Error::last_os_error().raw_os_error() == Some(libc::ECHILD))
}
+458
View File
@@ -0,0 +1,458 @@
use super::process::{
CommandResult, TERMINATION_GRACE, command_with_console, describe_exit_status,
restore_shutdown_action,
};
use super::{Console, POLL_INTERVAL};
use crate::policy::{ServiceCatalog, parse_rc_conf};
use std::collections::{BTreeMap, VecDeque};
use std::io::{self, BufRead, BufReader, Write};
use std::os::unix::fs::PermissionsExt;
use std::os::unix::net::UnixListener;
use std::path::Path;
use std::thread;
use std::time::{Duration, Instant};
const RC_CONF_PATH: &str = "/etc/rc.conf";
const SYSTEM_SERVICE_DEFINITIONS: &str = "/etc/rc.d";
const LOCAL_SERVICE_DEFINITIONS: &str = "/usr/local/etc/rc.d";
const REQUIRED_SERVICES: &str = "/usr/lib/mouse/required-services";
const SERVICE_CGROUP_ROOT: &str = "/sys/fs/cgroup/mouse.services";
const CONTROL_SOCKET: &str = "/run/cheesed.sock";
const SERVICE_TRANSITION_TIMEOUT: Duration = Duration::from_secs(120);
const SERVICE_STOP_TIMEOUT: Duration = Duration::from_secs(10);
const RESTART_WINDOW: Duration = Duration::from_secs(10);
const RESTART_DELAY: Duration = Duration::from_secs(1);
const RESTART_LIMIT: usize = 3;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ServiceState {
Stopped,
Started,
Running,
RestartPending,
Failed,
}
impl ServiceState {
fn description(self) -> &'static str {
match self {
Self::Stopped => "stopped",
Self::Started => "started",
Self::Running => "running",
Self::RestartPending => "restarting",
Self::Failed => "failed",
}
}
}
struct ManagedService {
state: ServiceState,
restart_times: VecDeque<Instant>,
restart_at: Option<Instant>,
}
impl Default for ManagedService {
fn default() -> Self {
Self {
state: ServiceState::Stopped,
restart_times: VecDeque::new(),
restart_at: None,
}
}
}
pub(super) struct ServiceManager {
catalog: ServiceCatalog,
services: BTreeMap<String, ManagedService>,
started_order: Vec<String>,
listener: UnixListener,
}
pub(super) fn boot_services(console: &mut Console) -> Result<ServiceManager, String> {
let catalog = ServiceCatalog::discover(
Path::new(SYSTEM_SERVICE_DEFINITIONS),
Path::new(LOCAL_SERVICE_DEFINITIONS),
Path::new(REQUIRED_SERVICES),
)
.map_err(|error| format!("cannot discover services: {error}"))?;
let contents = std::fs::read_to_string(RC_CONF_PATH)
.map_err(|error| format!("cannot read {RC_CONF_PATH}: {error}"))?;
let policy = parse_rc_conf(&contents, &catalog)
.map_err(|error| format!("invalid service policy: {error}"))?;
let enabled = policy.enabled().collect::<Vec<_>>();
if enabled.is_empty() {
console.log("enabled optional services: (none)");
} else {
console.log(&format!(
"enabled optional services: {}",
enabled.join(", ")
));
}
let order = catalog
.start_order(&policy)
.map_err(|error| format!("cannot order services: {error}"))?;
let mut manager = ServiceManager::new(catalog)?;
for service in order {
console.log(&format!("starting service {service}"));
if let Err(error) = manager.start_service(&service, console, false) {
if manager.catalog.is_required(&service) {
manager.stop_all(console);
return Err(format!("required service {service} failed: {error}"));
}
console.log(&format!("optional service {service} failed: {error}"));
}
}
console.log("service graph started; cgroup supervision active");
Ok(manager)
}
impl ServiceManager {
fn new(catalog: ServiceCatalog) -> Result<Self, String> {
std::fs::create_dir_all(SERVICE_CGROUP_ROOT)
.map_err(|error| format!("cannot create service cgroup root: {error}"))?;
let _ = std::fs::remove_file(CONTROL_SOCKET);
let listener = UnixListener::bind(CONTROL_SOCKET)
.map_err(|error| format!("cannot bind {CONTROL_SOCKET}: {error}"))?;
listener
.set_nonblocking(true)
.map_err(|error| format!("cannot make {CONTROL_SOCKET} nonblocking: {error}"))?;
std::fs::set_permissions(CONTROL_SOCKET, std::fs::Permissions::from_mode(0o600))
.map_err(|error| format!("cannot protect {CONTROL_SOCKET}: {error}"))?;
Ok(Self {
catalog,
services: BTreeMap::new(),
started_order: Vec::new(),
listener,
})
}
fn service(&mut self, name: &str) -> &mut ManagedService {
self.services.entry(name.to_owned()).or_default()
}
fn start_service(
&mut self,
name: &str,
console: &mut Console,
clear_history: bool,
) -> Result<String, String> {
let definition = self
.catalog
.definition(name)
.ok_or_else(|| format!("unknown service {name}"))?;
if matches!(
self.services.get(name).map(|service| service.state),
Some(ServiceState::Started | ServiceState::Running)
) {
return Ok(format!("{name} is already started"));
}
let script = definition.path().to_path_buf();
let cgroup = service_cgroup(name)?;
std::fs::create_dir_all(&cgroup)
.map_err(|error| format!("cannot create cgroup for {name}: {error}"))?;
kill_cgroup(&cgroup, console);
let result = command_with_console(
script
.to_str()
.ok_or_else(|| format!("service path for {name} is not UTF-8"))?,
&["start"],
console,
SERVICE_TRANSITION_TIMEOUT,
Some(&cgroup),
)?;
let status = match result {
CommandResult::Exited(status) => status,
CommandResult::Shutdown(action) => {
restore_shutdown_action(action);
return Err(format!("start of {name} interrupted by {action} request"));
}
};
if !status.success() {
kill_cgroup(&cgroup, console);
self.service(name).state = ServiceState::Failed;
return Err(format!(
"start command exited with {}",
describe_exit_status(status)
));
}
let state = if cgroup_populated(&cgroup)? {
ServiceState::Running
} else {
ServiceState::Started
};
let service = self.service(name);
service.state = state;
service.restart_at = None;
if clear_history {
service.restart_times.clear();
}
if !self.started_order.iter().any(|started| started == name) {
self.started_order.push(name.to_owned());
}
Ok(format!("{name} {}", state.description()))
}
fn stop_service(&mut self, name: &str, console: &mut Console) -> Result<String, String> {
let definition = self
.catalog
.definition(name)
.ok_or_else(|| format!("unknown service {name}"))?;
let current = self
.services
.get(name)
.map_or(ServiceState::Stopped, |s| s.state);
if current == ServiceState::Stopped {
return Ok(format!("{name} is already stopped"));
}
let script = definition.path().to_path_buf();
let cgroup = service_cgroup(name)?;
let result = command_with_console(
script
.to_str()
.ok_or_else(|| format!("service path for {name} is not UTF-8"))?,
&["stop"],
console,
SERVICE_STOP_TIMEOUT,
Some(&cgroup),
);
let (command_error, allow_grace) = match result {
Ok(CommandResult::Exited(status)) if status.success() => (None, true),
Ok(CommandResult::Exited(status)) => (
Some(format!(
"stop command exited with {}",
describe_exit_status(status)
)),
true,
),
Ok(CommandResult::Shutdown(action)) => {
restore_shutdown_action(action);
(Some(format!("stop interrupted by {action} request")), false)
}
Err(error) => (Some(error), false),
};
if allow_grace {
wait_for_empty_cgroup(&cgroup, TERMINATION_GRACE);
}
kill_cgroup(&cgroup, console);
let service = self.service(name);
service.state = ServiceState::Stopped;
service.restart_at = None;
if let Some(error) = command_error {
Err(error)
} else {
Ok(format!("{name} stopped"))
}
}
fn run_action(
&mut self,
action: &str,
name: &str,
console: &mut Console,
) -> Result<String, String> {
if self.catalog.definition(name).is_none() {
let refreshed = ServiceCatalog::discover(
Path::new(SYSTEM_SERVICE_DEFINITIONS),
Path::new(LOCAL_SERVICE_DEFINITIONS),
Path::new(REQUIRED_SERVICES),
)
.map_err(|error| format!("cannot refresh service definitions: {error}"))?;
if refreshed.definition(name).is_none() {
return Err(format!("unknown service {name}"));
}
self.catalog = refreshed;
}
match action {
"start" => self.start_service(name, console, true),
"stop" => self.stop_service(name, console),
"restart" => {
let _ = self.stop_service(name, console);
self.start_service(name, console, true)
}
"reload" => {
let definition = self
.catalog
.definition(name)
.ok_or_else(|| format!("unknown service {name}"))?;
let script = definition.path().to_path_buf();
let cgroup = service_cgroup(name)?;
match command_with_console(
script
.to_str()
.ok_or_else(|| format!("service path for {name} is not UTF-8"))?,
&["reload"],
console,
SERVICE_TRANSITION_TIMEOUT,
Some(&cgroup),
)? {
CommandResult::Exited(status) if status.success() => {
Ok(format!("{name} reloaded"))
}
CommandResult::Exited(status) => Err(format!(
"reload command exited with {}",
describe_exit_status(status)
)),
CommandResult::Shutdown(action) => {
restore_shutdown_action(action);
Err(format!("reload interrupted by {action} request"))
}
}
}
"status" => {
let state = self
.services
.get(name)
.map_or(ServiceState::Stopped, |service| service.state);
if matches!(state, ServiceState::Failed | ServiceState::Stopped) {
Err(format!("{name} {}", state.description()))
} else {
Ok(format!("{name} {}", state.description()))
}
}
_ => Err(format!("unsupported service action {action}")),
}
}
pub(super) fn poll(&mut self, console: &mut Console) {
loop {
match self.listener.accept() {
Ok((mut stream, _)) => {
let result = (|| {
stream.set_read_timeout(Some(Duration::from_secs(2)))?;
let mut request = String::new();
BufReader::new(stream.try_clone()?).read_line(&mut request)?;
if request.len() > 256 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"service request is too long",
));
}
let fields = request.split_ascii_whitespace().collect::<Vec<_>>();
if fields.len() != 2 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"expected ACTION SERVICE",
));
}
let response = match self.run_action(fields[0], fields[1], console) {
Ok(message) => format!("OK {message}\n"),
Err(error) => format!("ERR {error}\n"),
};
stream.write_all(response.as_bytes())?;
Ok(())
})();
if let Err(error) = result {
let _ = writeln!(stream, "ERR {error}");
}
}
Err(error) if error.kind() == io::ErrorKind::WouldBlock => break,
Err(error) => {
console.log(&format!("service control socket failed: {error}"));
break;
}
}
}
let now = Instant::now();
let names = self.services.keys().cloned().collect::<Vec<_>>();
let mut restart = Vec::new();
for name in names {
let cgroup = match service_cgroup(&name) {
Ok(path) => path,
Err(error) => {
console.log(&error);
continue;
}
};
let populated = cgroup_populated(&cgroup).unwrap_or(false);
let service = self.service(&name);
if service.state == ServiceState::Running && !populated {
while service
.restart_times
.front()
.is_some_and(|instant| now.duration_since(*instant) > RESTART_WINDOW)
{
service.restart_times.pop_front();
}
if service.restart_times.len() >= RESTART_LIMIT {
service.state = ServiceState::Failed;
console.log(&format!(
"service {name} exhausted {RESTART_LIMIT} restarts in {} seconds",
RESTART_WINDOW.as_secs()
));
} else {
service.restart_times.push_back(now);
service.restart_at = Some(now + RESTART_DELAY);
service.state = ServiceState::RestartPending;
console.log(&format!("service {name} exited; restart scheduled"));
}
}
if service.state == ServiceState::RestartPending
&& service.restart_at.is_some_and(|deadline| now >= deadline)
{
restart.push(name);
}
}
for name in restart {
match self.start_service(&name, console, false) {
Ok(message) => console.log(&message),
Err(error) => {
self.service(&name).state = ServiceState::Failed;
console.log(&format!("service {name} restart failed: {error}"));
}
}
}
}
pub(super) fn stop_all(&mut self, console: &mut Console) {
for name in self.started_order.clone().into_iter().rev() {
console.log(&format!("stopping service {name}"));
if let Err(error) = self.stop_service(&name, console) {
console.log(&format!("service {name} stop failed: {error}"));
}
}
}
}
fn service_cgroup(name: &str) -> Result<std::path::PathBuf, String> {
if name.is_empty()
|| !name
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-' | b'.'))
{
return Err(format!("invalid service name {name:?}"));
}
Ok(Path::new(SERVICE_CGROUP_ROOT).join(name))
}
fn cgroup_populated(cgroup: &Path) -> Result<bool, String> {
let contents = std::fs::read_to_string(cgroup.join("cgroup.events"))
.map_err(|error| format!("cannot read {}: {error}", cgroup.display()))?;
Ok(contents
.lines()
.any(|line| line.split_ascii_whitespace().eq(["populated", "1"])))
}
fn wait_for_empty_cgroup(cgroup: &Path, timeout: Duration) {
let deadline = Instant::now() + timeout;
while Instant::now() < deadline {
if !cgroup_populated(cgroup).unwrap_or(false) {
return;
}
thread::sleep(POLL_INTERVAL);
}
}
fn kill_cgroup(cgroup: &Path, console: &mut Console) {
if !cgroup_populated(cgroup).unwrap_or(false) {
return;
}
if let Err(error) = std::fs::write(cgroup.join("cgroup.kill"), b"1\n") {
console.log(&format!(
"cannot kill remaining processes in {}: {error}",
cgroup.display()
));
}
wait_for_empty_cgroup(cgroup, TERMINATION_GRACE);
}
+119
View File
@@ -0,0 +1,119 @@
use std::io::{BufRead, BufReader, Write};
use std::os::unix::net::UnixStream;
use std::path::Path;
const CONTROL_SOCKET: &str = "/run/cheesed.sock";
const SERVICE_USAGE: &str = "usage: service NAME {start|stop|restart|reload|status}";
fn main() {
let mut arguments = std::env::args();
let executable = arguments.next().unwrap_or_else(|| "cheesed".to_owned());
let name = Path::new(&executable)
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("cheesed");
let arguments = arguments.collect::<Vec<_>>();
let result = match name {
"cheesed" => cheesed::run(),
"service" => run_service(&arguments),
"poweroff" | "reboot" | "shutdown" => run_shutdown(name, &arguments),
_ => Err(format!("unknown cheesed invocation name: {name}")),
};
if let Err(error) = result {
eprintln!("{name}: {error}");
std::process::exit(1);
}
}
fn run_service(arguments: &[String]) -> Result<(), String> {
let [name, action] = arguments else {
return Err(SERVICE_USAGE.to_owned());
};
if !matches!(
action.as_str(),
"start" | "stop" | "restart" | "reload" | "status"
) {
return Err(SERVICE_USAGE.to_owned());
}
let mut stream = UnixStream::connect(CONTROL_SOCKET)
.map_err(|error| format!("cannot connect to cheesed: {error}"))?;
writeln!(stream, "{action} {name}").map_err(|error| format!("cannot send request: {error}"))?;
stream
.shutdown(std::net::Shutdown::Write)
.map_err(|error| format!("cannot finish request: {error}"))?;
let mut response = String::new();
BufReader::new(stream)
.read_line(&mut response)
.map_err(|error| format!("cannot read response: {error}"))?;
let response = response.trim_end();
if let Some(message) = response.strip_prefix("OK ") {
println!("{message}");
Ok(())
} else if let Some(message) = response.strip_prefix("ERR ") {
Err(message.to_owned())
} else {
Err("invalid response from cheesed".to_owned())
}
}
fn run_shutdown(name: &str, arguments: &[String]) -> Result<(), String> {
let signal = shutdown_signal(name, arguments)?;
// SAFETY: PID 1 and the selected signal are fixed by the validated command name.
if unsafe { libc::kill(1, signal) } == -1 {
return Err(format!(
"cannot signal cheesed: {}",
std::io::Error::last_os_error()
));
}
Ok(())
}
fn shutdown_signal(name: &str, arguments: &[String]) -> Result<libc::c_int, String> {
match (name, arguments) {
("poweroff" | "shutdown", []) => Ok(libc::SIGTERM),
("reboot", []) => Ok(libc::SIGINT),
("shutdown", [flag]) if matches!(flag.as_str(), "-h" | "-p") => Ok(libc::SIGTERM),
("shutdown", [argument]) if argument == "now" => Ok(libc::SIGTERM),
("shutdown", [flag, argument])
if matches!(flag.as_str(), "-h" | "-p") && argument == "now" =>
{
Ok(libc::SIGTERM)
}
("shutdown", [flag]) if flag == "-r" => Ok(libc::SIGINT),
("shutdown", [flag, argument]) if flag == "-r" && argument == "now" => Ok(libc::SIGINT),
("shutdown", _) => Err("usage: shutdown [-h|-p|-r] [now]".to_owned()),
_ => Err(format!("usage: {name}")),
}
}
#[cfg(test)]
mod tests {
use super::shutdown_signal;
fn arguments(values: &[&str]) -> Vec<String> {
values.iter().map(ToString::to_string).collect()
}
#[test]
fn multicall_shutdown_names_select_the_pid1_signal() {
assert_eq!(shutdown_signal("poweroff", &[]), Ok(libc::SIGTERM));
assert_eq!(shutdown_signal("reboot", &[]), Ok(libc::SIGINT));
assert_eq!(
shutdown_signal("shutdown", &arguments(&["-r", "now"])),
Ok(libc::SIGINT)
);
assert_eq!(
shutdown_signal("shutdown", &arguments(&["-p", "now"])),
Ok(libc::SIGTERM)
);
}
#[test]
fn multicall_shutdown_rejects_invalid_arguments() {
assert!(shutdown_signal("poweroff", &arguments(&["now"])).is_err());
assert!(shutdown_signal("shutdown", &arguments(&["later"])).is_err());
}
}
+568
View File
@@ -0,0 +1,568 @@
use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
#[derive(Debug, Eq, PartialEq)]
pub(crate) struct RcPolicy {
enabled: BTreeSet<String>,
}
impl RcPolicy {
pub(crate) fn enabled(&self) -> impl Iterator<Item = &str> {
self.enabled.iter().map(String::as_str)
}
}
#[derive(Debug)]
pub(crate) struct ServiceDefinition {
path: PathBuf,
provides: BTreeSet<String>,
requires: BTreeSet<String>,
before: BTreeSet<String>,
}
impl ServiceDefinition {
pub(crate) fn path(&self) -> &Path {
&self.path
}
}
#[derive(Debug)]
pub(crate) struct ServiceCatalog {
definitions: BTreeMap<String, ServiceDefinition>,
required: BTreeSet<String>,
}
impl ServiceCatalog {
pub(crate) fn discover(
system_definitions: &Path,
local_definitions: &Path,
required_services: &Path,
) -> Result<Self, PolicyError> {
let mut definitions = read_service_definitions(system_definitions, false)?;
for (name, definition) in read_service_definitions(local_definitions, true)? {
if let Some(system_definition) = definitions.insert(name.clone(), definition) {
return Err(PolicyError::new(format!(
"duplicate service definition {name}: {} and {}",
system_definition.path.display(),
definitions[&name].path.display()
)));
}
}
let required = read_required_services(required_services)?;
for service in &required {
if !definitions.contains_key(service) {
return Err(PolicyError::new(format!(
"required service {service} has no definition"
)));
}
}
Ok(Self {
definitions,
required,
})
}
pub(crate) fn definition(&self, name: &str) -> Option<&ServiceDefinition> {
self.definitions.get(name)
}
pub(crate) fn is_required(&self, name: &str) -> bool {
self.required.contains(name)
}
pub(crate) fn start_order(&self, policy: &RcPolicy) -> Result<Vec<String>, PolicyError> {
let selected = self
.required
.iter()
.cloned()
.chain(policy.enabled.iter().cloned())
.collect::<BTreeSet<_>>();
self.order_selected(&selected)
}
fn order_selected(&self, selected: &BTreeSet<String>) -> Result<Vec<String>, PolicyError> {
let mut all_providers = BTreeMap::<String, BTreeSet<String>>::new();
for (name, definition) in &self.definitions {
for token in &definition.provides {
all_providers
.entry(token.clone())
.or_default()
.insert(name.clone());
}
}
let mut outgoing = selected
.iter()
.map(|name| (name.clone(), BTreeSet::new()))
.collect::<BTreeMap<_, _>>();
let mut indegree = selected
.iter()
.map(|name| (name.clone(), 0_usize))
.collect::<BTreeMap<_, _>>();
for name in selected {
let definition = &self.definitions[name];
for token in &definition.requires {
let Some(providers) = all_providers.get(token) else {
return Err(PolicyError::new(format!(
"service {name} requires missing facility {token}"
)));
};
let selected_providers = providers
.intersection(selected)
.filter(|provider| *provider != name)
.cloned()
.collect::<Vec<_>>();
if selected_providers.is_empty() && !providers.contains(name) {
return Err(PolicyError::new(format!(
"service {name} requires disabled facility {token}"
)));
}
for provider in selected_providers {
add_edge(&mut outgoing, &mut indegree, &provider, name);
}
}
for token in &definition.before {
if let Some(providers) = all_providers.get(token) {
for target in providers.intersection(selected) {
if target != name {
add_edge(&mut outgoing, &mut indegree, name, target);
}
}
}
}
}
let mut ready = indegree
.iter()
.filter_map(|(name, degree)| (*degree == 0).then_some(name.clone()))
.collect::<BTreeSet<_>>();
let mut order = Vec::with_capacity(selected.len());
while let Some(name) = ready.pop_first() {
order.push(name.clone());
for dependent in outgoing[&name].clone() {
let degree = indegree
.get_mut(&dependent)
.expect("dependency graph contains every selected service");
*degree -= 1;
if *degree == 0 {
ready.insert(dependent);
}
}
}
if order.len() != selected.len() {
let cycle = indegree
.into_iter()
.filter_map(|(name, degree)| (degree != 0).then_some(name))
.collect::<Vec<_>>()
.join(", ");
return Err(PolicyError::new(format!(
"service dependency cycle contains: {cycle}"
)));
}
Ok(order)
}
}
fn add_edge(
outgoing: &mut BTreeMap<String, BTreeSet<String>>,
indegree: &mut BTreeMap<String, usize>,
from: &str,
to: &str,
) {
if outgoing
.get_mut(from)
.expect("edge source is selected")
.insert(to.to_owned())
{
*indegree.get_mut(to).expect("edge target is selected") += 1;
}
}
#[derive(Debug, Eq, PartialEq)]
pub(crate) struct PolicyError {
message: String,
}
impl PolicyError {
fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
}
}
}
impl fmt::Display for PolicyError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.message)
}
}
impl From<io::Error> for PolicyError {
fn from(error: io::Error) -> Self {
Self::new(error.to_string())
}
}
pub(crate) fn parse_rc_conf(
contents: &str,
catalog: &ServiceCatalog,
) -> Result<RcPolicy, PolicyError> {
let mut assignments = BTreeMap::new();
for (index, line) in contents.lines().enumerate() {
let line_number = index + 1;
if line.trim().is_empty() || line.trim_start().starts_with('#') {
continue;
}
let Some((key, value)) = line.split_once('=') else {
return Err(line_error(
line_number,
"expected enable_name=YES or enable_name=NO",
));
};
if key.contains(char::is_whitespace)
|| value.contains(char::is_whitespace)
|| line.matches('=').count() != 1
{
return Err(line_error(
line_number,
"whitespace, quoting, comments, and extra assignments are not allowed",
));
}
let Some(name) = key.strip_prefix("enable_") else {
return Err(line_error(line_number, "unknown rc.conf key"));
};
if !valid_service_name(name) {
return Err(line_error(line_number, "invalid service name"));
}
let enabled = match value {
"YES" => true,
"NO" => false,
_ => {
return Err(line_error(
line_number,
"enablement value must be exactly YES or NO",
));
}
};
if !catalog.definitions.contains_key(name) {
return Err(line_error(
line_number,
format!("service {name} has no installed definition"),
));
}
if catalog.required.contains(name) {
return Err(line_error(
line_number,
format!("required service {name} cannot be enabled or disabled"),
));
}
if assignments.insert(name.to_owned(), enabled).is_some() {
return Err(line_error(
line_number,
format!("duplicate assignment for service {name}"),
));
}
}
Ok(RcPolicy {
enabled: assignments
.into_iter()
.filter_map(|(name, enabled)| enabled.then_some(name))
.collect(),
})
}
fn read_service_definitions(
directory: &Path,
optional: bool,
) -> Result<BTreeMap<String, ServiceDefinition>, PolicyError> {
let entries = match fs::read_dir(directory) {
Ok(entries) => entries,
Err(error) if optional && error.kind() == io::ErrorKind::NotFound => {
return Ok(BTreeMap::new());
}
Err(error) => {
return Err(PolicyError::new(format!(
"cannot read {}: {error}",
directory.display()
)));
}
};
let mut definitions = BTreeMap::new();
for entry in entries {
let entry = entry?;
if !entry.file_type()?.is_file() {
continue;
}
let name = entry
.file_name()
.into_string()
.map_err(|_| PolicyError::new("service definition name is not UTF-8"))?;
if !valid_service_name(&name) {
return Err(PolicyError::new(format!(
"service definition {name:?} is not a valid MOUSE service name"
)));
}
let path = entry.path();
let mut definition = parse_service_definition(&path)?;
definition.provides.insert(name.clone());
definitions.insert(name, definition);
}
Ok(definitions)
}
fn parse_service_definition(path: &Path) -> Result<ServiceDefinition, PolicyError> {
let contents = fs::read_to_string(path).map_err(|error| {
PolicyError::new(format!(
"cannot read service definition {}: {error}",
path.display()
))
})?;
let mut provides = BTreeSet::new();
let mut requires = BTreeSet::new();
let mut before = BTreeSet::new();
for line in contents.lines() {
let line = line.trim_start();
let Some(comment) = line.strip_prefix('#') else {
continue;
};
let comment = comment.trim_start();
for (label, destination) in [
("PROVIDE:", &mut provides),
("REQUIRE:", &mut requires),
("BEFORE:", &mut before),
] {
if let Some(tokens) = comment.strip_prefix(label) {
for token in tokens.split_ascii_whitespace() {
if !valid_facility_name(token) {
return Err(PolicyError::new(format!(
"{} contains invalid {label} facility {token:?}",
path.display()
)));
}
destination.insert(token.to_owned());
}
}
}
}
Ok(ServiceDefinition {
path: path.to_owned(),
provides,
requires,
before,
})
}
fn read_required_services(path: &Path) -> Result<BTreeSet<String>, PolicyError> {
let contents = fs::read_to_string(path).map_err(|error| {
PolicyError::new(format!(
"cannot read required service list {}: {error}",
path.display()
))
})?;
let mut required = BTreeSet::new();
for (index, line) in contents.lines().enumerate() {
let name = line.trim();
if name.is_empty() || name.starts_with('#') {
continue;
}
if !valid_service_name(name) {
return Err(PolicyError::new(format!(
"{}:{}: invalid required service name {name:?}",
path.display(),
index + 1
)));
}
if !required.insert(name.to_owned()) {
return Err(PolicyError::new(format!(
"{}:{}: duplicate required service {name}",
path.display(),
index + 1
)));
}
}
Ok(required)
}
fn valid_service_name(name: &str) -> bool {
let mut bytes = name.bytes();
matches!(bytes.next(), Some(b'a'..=b'z'))
&& bytes.all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'_')
}
fn valid_facility_name(name: &str) -> bool {
!name.is_empty()
&& name
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-' | b'.'))
}
fn line_error(line_number: usize, message: impl fmt::Display) -> PolicyError {
PolicyError::new(format!("/etc/rc.conf:{line_number}: {message}"))
}
#[cfg(test)]
mod tests {
use super::{RcPolicy, ServiceCatalog, parse_rc_conf};
use std::collections::BTreeSet;
use std::fs;
use std::path::PathBuf;
use std::sync::atomic::{AtomicU64, Ordering};
static NEXT_TEMP: AtomicU64 = AtomicU64::new(0);
struct TestLayout {
root: PathBuf,
system_definitions: PathBuf,
local_definitions: PathBuf,
required_services: PathBuf,
}
impl TestLayout {
fn new() -> Self {
let serial = NEXT_TEMP.fetch_add(1, Ordering::Relaxed);
let root = std::env::temp_dir()
.join(format!("cheesed-policy-{}-{serial}", std::process::id()));
let system_definitions = root.join("etc/rc.d");
let local_definitions = root.join("usr/local/etc/rc.d");
let required_services = root.join("required-services");
fs::create_dir_all(&system_definitions).unwrap();
fs::create_dir_all(&local_definitions).unwrap();
fs::write(
system_definitions.join("required"),
"#!/bin/sh\n# PROVIDE: REQUIRED\n",
)
.unwrap();
fs::write(
system_definitions.join("optional"),
"#!/bin/sh\n# PROVIDE: OPTIONAL\n# REQUIRE: REQUIRED\n",
)
.unwrap();
fs::write(
local_definitions.join("ported"),
"#!/bin/sh\n# PROVIDE: PORTED\n# REQUIRE: OPTIONAL\n",
)
.unwrap();
fs::write(&required_services, "required\n").unwrap();
Self {
root,
system_definitions,
local_definitions,
required_services,
}
}
fn catalog(&self) -> ServiceCatalog {
ServiceCatalog::discover(
&self.system_definitions,
&self.local_definitions,
&self.required_services,
)
.unwrap()
}
}
impl Drop for TestLayout {
fn drop(&mut self) {
fs::remove_dir_all(&self.root).unwrap();
}
}
fn enabled(policy: &RcPolicy) -> BTreeSet<&str> {
policy.enabled().collect()
}
#[test]
fn accepts_comments_empty_lines_and_strict_assignments() {
let layout = TestLayout::new();
let policy = parse_rc_conf(
"\n # comment\nenable_optional=YES\nenable_ported=YES\n",
&layout.catalog(),
)
.unwrap();
assert_eq!(enabled(&policy), BTreeSet::from(["optional", "ported"]));
}
#[test]
fn rejects_shell_ambiguous_unknown_and_required_assignments() {
let layout = TestLayout::new();
let catalog = layout.catalog();
for invalid in [
"enable_optional =YES",
"enable_optional=\"YES\"",
"enable_optional=YES # comment",
"enable_optional=$(hostname)",
"enable_optional=YES=NO",
"rc_parallel=YES",
"enable_missing=NO",
"enable_required=YES",
] {
assert!(
parse_rc_conf(invalid, &catalog).is_err(),
"accepted {invalid:?}"
);
}
}
#[test]
fn orders_selected_services_from_rcorder_headers() {
let layout = TestLayout::new();
let catalog = layout.catalog();
let policy = parse_rc_conf("enable_optional=YES\nenable_ported=YES\n", &catalog).unwrap();
assert_eq!(
catalog.start_order(&policy).unwrap(),
["required", "optional", "ported"]
);
assert!(catalog.is_required("required"));
assert_eq!(
catalog.definition("ported").unwrap().path(),
layout.local_definitions.join("ported")
);
}
#[test]
fn rejects_disabled_missing_and_cyclic_dependencies() {
let layout = TestLayout::new();
let catalog = layout.catalog();
let disabled = parse_rc_conf("enable_ported=YES\n", &catalog).unwrap();
assert!(catalog.start_order(&disabled).is_err());
fs::write(
layout.system_definitions.join("required"),
"#!/bin/sh\n# PROVIDE: REQUIRED\n# REQUIRE: OPTIONAL\n",
)
.unwrap();
let catalog = layout.catalog();
let cyclic = parse_rc_conf("enable_optional=YES\n", &catalog).unwrap();
assert!(catalog.start_order(&cyclic).is_err());
}
#[test]
fn rejects_duplicate_system_and_local_service_definitions() {
let layout = TestLayout::new();
fs::write(layout.local_definitions.join("optional"), "#!/bin/sh\n").unwrap();
let error = ServiceCatalog::discover(
&layout.system_definitions,
&layout.local_definitions,
&layout.required_services,
)
.unwrap_err();
assert!(
error
.to_string()
.contains("duplicate service definition optional")
);
}
}
+122
View File
@@ -0,0 +1,122 @@
CONFIG_LOCALVERSION="-mouse"
CONFIG_DEFAULT_HOSTNAME="mouse"
CONFIG_64BIT=y
CONFIG_X86_64=y
CONFIG_SMP=y
CONFIG_HYPERVISOR_GUEST=y
CONFIG_PARAVIRT=y
CONFIG_ACPI=y
CONFIG_EFI=y
CONFIG_EFI_STUB=y
CONFIG_MULTIUSER=y
CONFIG_PRINTK=y
CONFIG_BUG=y
CONFIG_FUTEX=y
CONFIG_EPOLL=y
CONFIG_SIGNALFD=y
CONFIG_TIMERFD=y
CONFIG_EVENTFD=y
CONFIG_BINFMT_ELF=y
CONFIG_BINFMT_SCRIPT=y
CONFIG_FILE_LOCKING=y
CONFIG_SWAP=y
CONFIG_CGROUPS=y
CONFIG_KALLSYMS=y
CONFIG_MODULES=y
CONFIG_MODULE_UNLOAD=y
CONFIG_MODVERSIONS=y
CONFIG_BASIC_MODVERSIONS=y
CONFIG_BLOCK=y
CONFIG_BLK_DEV=y
CONFIG_EFI_PARTITION=y
CONFIG_DEVTMPFS=y
CONFIG_DEVTMPFS_MOUNT=y
CONFIG_FW_LOADER=y
CONFIG_PROC_FS=y
CONFIG_PROC_SYSCTL=y
CONFIG_SYSFS=y
CONFIG_TMPFS=y
CONFIG_TMPFS_POSIX_ACL=y
CONFIG_NET=y
CONFIG_PACKET=y
CONFIG_PACKET_DIAG=y
CONFIG_UNIX=y
CONFIG_UNIX_DIAG=y
CONFIG_INET=y
CONFIG_INET_DIAG=y
CONFIG_INET_TCP_DIAG=y
CONFIG_INET_UDP_DIAG=y
CONFIG_IPV6=y
CONFIG_NETLINK_DIAG=y
CONFIG_NAMESPACES=y
CONFIG_NET_NS=y
CONFIG_NETFILTER=y
CONFIG_NETFILTER_ADVANCED=y
CONFIG_NF_CONNTRACK=y
CONFIG_NF_NAT=y
CONFIG_NF_TABLES=y
CONFIG_NF_TABLES_INET=y
CONFIG_NF_TABLES_NETDEV=y
CONFIG_NFT_CT=y
CONFIG_NFT_LOG=y
CONFIG_NFT_LIMIT=y
CONFIG_NFT_MASQ=y
CONFIG_NFT_REDIR=y
CONFIG_NFT_NAT=y
CONFIG_NFT_REJECT=y
CONFIG_NFT_REJECT_INET=y
CONFIG_NET_SCHED=y
CONFIG_NET_SCH_HTB=y
CONFIG_NET_SCH_FQ_CODEL=y
CONFIG_NET_CLS_BASIC=y
CONFIG_NET_CLS_U32=y
CONFIG_NETDEVICES=y
CONFIG_NET_CORE=y
CONFIG_ETHERNET=y
CONFIG_DUMMY=y
CONFIG_BONDING=y
CONFIG_TUN=y
CONFIG_VETH=y
CONFIG_VLAN_8021Q=y
CONFIG_BRIDGE=y
CONFIG_BRIDGE_VLAN_FILTERING=y
CONFIG_PCI=y
CONFIG_PCI_MSI=y
CONFIG_VIRTIO_MENU=y
CONFIG_VIRTIO=y
CONFIG_VIRTIO_PCI=y
CONFIG_VIRTIO_BLK=y
CONFIG_VIRTIO_NET=y
CONFIG_NET_VENDOR_INTEL=y
CONFIG_E1000=y
CONFIG_E1000E=y
CONFIG_SCSI=y
CONFIG_BLK_DEV_SD=y
CONFIG_ATA=y
CONFIG_SATA_AHCI=y
CONFIG_BLK_DEV_NVME=y
CONFIG_TTY=y
CONFIG_UNIX98_PTYS=y
CONFIG_SERIAL_8250=y
CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_VT=y
CONFIG_VT_CONSOLE=y
CONFIG_VGA_CONSOLE=y
CONFIG_INPUT=y
CONFIG_INPUT_KEYBOARD=y
CONFIG_KEYBOARD_ATKBD=y
CONFIG_SERIO=y
CONFIG_SERIO_I8042=y
CONFIG_FB=y
CONFIG_FB_EFI=y
CONFIG_FRAMEBUFFER_CONSOLE=y
CONFIG_BTRFS_FS=y
CONFIG_BTRFS_FS_POSIX_ACL=y
+16
View File
@@ -0,0 +1,16 @@
timeout: 3
default_entry: 1
serial: yes
serial_baudrate: 115200
graphics: yes
interface_branding: MOUSE
/MOUSE
protocol: linux
path: boot():/vmlinuz-mouse
cmdline: console=tty0 console=ttyS0 root=PARTUUID=4d4f5553-4500-4000-8000-000000000002 rootfstype=btrfs rw rootwait init=/sbin/cheesed panic=-1
/MOUSE alternate
protocol: linux
path: boot():/vmlinuz-mouse
cmdline: console=tty0 console=ttyS0 root=PARTUUID=4d4f5553-4500-4000-8000-000000000002 rootfstype=btrfs rootflags=subvol=ROOT/alternate rw rootwait init=/sbin/cheesed panic=-1
@@ -0,0 +1,36 @@
diff --git a/include/sys/cdefs.h b/include/sys/cdefs.h
index 2dac3f9..aa16c50 100644
--- a/include/sys/cdefs.h
+++ b/include/sys/cdefs.h
@@ -28,6 +28,10 @@
#ifndef SYS_CDEFS_H
#define SYS_CDEFS_H
+#ifdef __APPLE__
+#include_next <sys/cdefs.h>
+#else
+
#include <features.h>
#ifdef __GLIBC__
@@ -53,4 +57,6 @@
#define __unreachable() __builtin_unreachable()
+#endif
+
#endif
diff --git a/include/string.h b/include/string.h
index 68108f6..9707bcd 100644
--- a/include/string.h
+++ b/include/string.h
@@ -38,7 +38,9 @@
extern "C" {
#endif
+#ifndef __APPLE__
void strmode(mode_t, char *);
+#endif
#ifndef HAVE_STRLCPY
size_t strlcpy(char *d, const char *s, size_t n);
#endif
+12
View File
@@ -0,0 +1,12 @@
diff --git a/include/sys/cdefs.h b/include/sys/cdefs.h
index aa16c50..4602b26 100644
--- a/include/sys/cdefs.h
+++ b/include/sys/cdefs.h
@@ -53,6 +53,7 @@
#define __dead2
#define __printf0like(x, y)
#define __printflike(x, y) __attribute__((format(printf, x, y)))
+#define __scanflike(x, y) __attribute__((format(scanf, x, y)))
#define __DECONST(a, v) ((a)(v))
#define __unused __attribute__((unused))
#define __unreachable() __builtin_unreachable()
@@ -0,0 +1,15 @@
diff --git a/configure b/configure
--- a/configure
+++ b/configure
@@ -17940,8 +17940,10 @@ $config_commands
echo " ${STABLE_RELEASE}"
echo "};"
echo "static char nftbuildstamp[] = {"
- for ((i = 56; i >= 0; i-= 8)); do
+ i=56
+ while test "$i" -ge 0; do
echo " ((uint64_t)MAKE_STAMP >> $i) & 0xff,"
+ i=$((i - 8))
done
echo "};"
) >nftversion.h
+13
View File
@@ -0,0 +1,13 @@
--- a/bootstrap/bootstrap
+++ b/bootstrap/bootstrap
@@ -683,7 +683,9 @@ Linux)
fi
# Debian/Ubuntu's awk is mawk, and mawk does not understand
# some regexps used in pkgsrc/mk.
- if [ -f /etc/debian_version ]; then
+ if [ -f /etc/mouse-release ]; then
+ bootstrap_awk=/usr/bin/awk
+ elif [ -f /etc/debian_version ]; then
need_awk=yes
# Arch uses gawk 5 that breaks some regexps. It doesn't provide pax
# anymore.
+27
View File
@@ -0,0 +1,27 @@
--- rc.subr.orig
+++ rc.subr
@@ -266,6 +266,14 @@
_procname=$1
_interpreter=$2
_psargs=$3
+ # cheesed places the rc script and every process it starts in one
+ # service cgroup. That cgroup is the process database on MOUSE, so rc.d
+ # scripts do not need ps or package-specific pidfile interpretation.
+ if [ "${MOUSE_SERVICE_PIDS+x}" = x ]; then
+ ${_RCCMD_echo} -n "$MOUSE_SERVICE_PIDS"
+ return
+ fi
+
_pref=
if [ $_interpreter != "." ]; then # an interpreted script
@@ -548,7 +556,8 @@
# checkyesno ${rcvar}
# and return if that failed
#
- if [ -n "${rcvar}" -a "$rc_arg" != "rcvar" ]; then
+ if [ -z "${MOUSE_SERVICE_CGROUP:-}" ] &&
+ [ -n "${rcvar}" -a "$rc_arg" != "rcvar" ]; then
if ! checkyesno ${rcvar}; then
return 0
fi
+26
View File
@@ -0,0 +1,26 @@
--- a/gethost.c
+++ b/gethost.c
@@ -29,7 +29,12 @@
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
-#include "sh.h"
+#include <assert.h>
+#include <ctype.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
#ifdef SCO
# define perror __perror
@@ -45,8 +50,7 @@
# undef system
#endif
-#include <ctype.h>
-#include <assert.h>
+#define INBUFSIZE 8192
#define ISSPACE(p) (isspace((unsigned char) (p)) && (p) != '\n')
+4
View File
@@ -0,0 +1,4 @@
SHELL=/bin/sh
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
# minute hour day month weekday user command
+5
View File
@@ -0,0 +1,5 @@
set path = ( /sbin /bin /usr/sbin /usr/bin /usr/local/sbin /usr/local/bin )
if ( $?prompt ) then
set prompt = "mouse:%~%# "
endif
+4
View File
@@ -0,0 +1,4 @@
umask 022
setenv SHELL /bin/tcsh
cd
+7
View File
@@ -0,0 +1,7 @@
GROUP=100
HOME=/home
INACTIVE=-1
EXPIRE=
SHELL=/bin/tcsh
SKEL=/etc/skel
CREATE_MAIL_SPOOL=no
+3
View File
@@ -0,0 +1,3 @@
# Package-owned kernel modules live on persistent /usr/local.
external * /usr/local/lib/modules
search external built-in
+4
View File
@@ -0,0 +1,4 @@
# The base image manages one wired interface.
allowinterfaces eth0
interface eth0
metric 100
+6
View File
@@ -0,0 +1,6 @@
root:x:0:root,toor
tty:x:5:
uucp:x:14:
users:x:100:
dhcpcd:x:101:
_ntp:x:102:
+1
View File
@@ -0,0 +1 @@
mouse
+2
View File
@@ -0,0 +1,2 @@
127.0.0.1 localhost mouse
::1 localhost mouse
+18
View File
@@ -0,0 +1,18 @@
ENV_PATH PATH=/bin:/usr/bin:/usr/local/bin
ENV_SUPATH PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
MAIL_DIR /var/mail
HUSHLOGIN_FILE .hushlogin
LOGIN_TIMEOUT 60
MOTD_FILE /etc/motd
PASS_MAX_DAYS 99999
PASS_MIN_DAYS 0
PASS_WARN_AGE 7
TTYGROUP tty
TTYPERM 0600
UID_MIN 1000
UID_MAX 60000
GID_MIN 1000
GID_MAX 60000
CREATE_HOME yes
USERGROUPS_ENAB yes
ENCRYPT_METHOD SHA512
+13
View File
@@ -0,0 +1,13 @@
# Load the exact module matching a kernel device alias. Device nodes themselves
# are supplied by devtmpfs and mdevd applies conservative default ownership.
$MODALIAS=.* root:root 0600 @modprobe -qb "$MODALIAS"
null root:root 0666
zero root:root 0666
full root:root 0666
random root:root 0444
urandom root:root 0444
console root:tty 0600
tty root:tty 0666
tty[0-9]* root:tty 0660
ttyS[0-9]* root:uucp 0660
.* root:root 0600
+2
View File
@@ -0,0 +1,2 @@
MOUSE 0.1.0 (x86_64)
Monolithic Opinionated Unix-like System Environment
+6
View File
@@ -0,0 +1,6 @@
NAME=MOUSE
ID=mouse
PRETTY_NAME="MOUSE 0.1.0"
VERSION="0.1.0"
VERSION_ID=0.1.0
ARCHITECTURE=x86_64
+1
View File
@@ -0,0 +1 @@
servers pool.ntp.org
+4
View File
@@ -0,0 +1,4 @@
root:x:0:0:root:/root:/bin/sh
toor:x:0:0:BSD-style root account:/root:/bin/tcsh
dhcpcd:x:101:101:dhcpcd privilege separation:/var/empty:/sbin/nologin
_ntp:x:102:102:OpenNTPD privilege separation:/var/empty:/sbin/nologin
+4
View File
@@ -0,0 +1,4 @@
# Persistent MOUSE service policy belongs in this file.
enable_network=YES
enable_cron=YES
enable_ntpd=YES
+33
View File
@@ -0,0 +1,33 @@
#!/bin/sh
# PROVIDE: console_login
# REQUIRE: LOGIN
. /etc/rc.subr
name=console_login
start_precmd=console_login_prestart
start_cmd=console_login_start
stop_cmd=:
autologin=
if grep -qw mouse.autologin=1 /proc/cmdline; then
autologin="--autologin root"
fi
console_login_prestart()
{
[ -e /run/utmp ] || : > /run/utmp
[ -e /var/log/wtmp ] || : > /var/log/wtmp
chmod 0664 /run/utmp /var/log/wtmp
}
console_login_start()
{
/usr/bin/setsid -f /sbin/agetty $autologin \
--login-program /bin/login --noclear --keep-baud \
115200,38400,9600 ttyS0 vt100 &
/usr/bin/setsid -f /sbin/agetty $autologin \
--login-program /bin/login --noclear tty1 linux &
}
run_rc_command "$1"
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh
# PROVIDE: cron
# REQUIRE: DAEMON
# BEFORE: LOGIN
. /etc/rc.subr
name=cron
command=/usr/sbin/cron
command_args="-n -s &"
run_rc_command "$1"
+11
View File
@@ -0,0 +1,11 @@
#!/bin/sh
# PROVIDE: DAEMON
# REQUIRE: machine_identity
. /etc/rc.subr
name=daemon
start_cmd=:
stop_cmd=:
run_rc_command "$1"
+11
View File
@@ -0,0 +1,11 @@
#!/bin/sh
# PROVIDE: LOGIN
# REQUIRE: DAEMON
. /etc/rc.subr
name=login
start_cmd=:
stop_cmd=:
run_rc_command "$1"
+20
View File
@@ -0,0 +1,20 @@
#!/bin/sh
# PROVIDE: LOOPBACK
# BEFORE: DAEMON
. /etc/rc.subr
name=loopback
loopback_start()
{
/sbin/ip link set lo up
/sbin/ip address replace 127.0.0.1/8 dev lo
/sbin/ip -6 address replace ::1/128 dev lo
}
start_cmd=loopback_start
stop_cmd=:
status_cmd=:
run_rc_command "$1"
+20
View File
@@ -0,0 +1,20 @@
#!/bin/sh
# PROVIDE: machine_identity
. /etc/rc.subr
name=machine_identity
start_cmd=machine_identity_start
stop_cmd=:
machine_identity_start()
{
if [ ! -s /etc/machine-id ]; then
sed 's/-//g' /proc/sys/kernel/random/uuid > /etc/machine-id.new ||
return 1
chmod 0444 /etc/machine-id.new || return 1
mv /etc/machine-id.new /etc/machine-id || return 1
fi
}
run_rc_command "$1"
+11
View File
@@ -0,0 +1,11 @@
#!/bin/sh
# PROVIDE: mdevd DEVFS
# BEFORE: machine_identity
. /etc/rc.subr
name=mdevd
command=/sbin/mdevd
command_args="-C &"
run_rc_command "$1"
+38
View File
@@ -0,0 +1,38 @@
#!/bin/sh
# PROVIDE: network NETWORKING
# REQUIRE: DAEMON
# BEFORE: LOGIN
. /etc/rc.subr
name=network
command=/sbin/dhcpcd
command_args="-4 -b -q -c /usr/libexec/mouse-dhcpcd-hook"
start_precmd=network_prestart
start_postcmd=network_poststart
stop_postcmd=network_poststop
network_prestart()
{
rm -f /run/mouse-network.ready
}
network_poststart()
{
waited=0
while [ ! -f /run/mouse-network.ready ]; do
if [ "$waited" -ge 20 ]; then
echo "timed out waiting for an IPv4 DHCP lease" >&2
return 1
fi
sleep 1
waited=$((waited + 1))
done
}
network_poststop()
{
rm -f /run/mouse-network.ready
}
run_rc_command "$1"
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh
# PROVIDE: ntpd TIMESYNC
# REQUIRE: DAEMON
# BEFORE: LOGIN
. /etc/rc.subr
name=ntpd
command=/usr/sbin/ntpd
command_args="-d &"
run_rc_command "$1"
+11
View File
@@ -0,0 +1,11 @@
#!/bin/sh
# PROVIDE: SERVERS
# REQUIRE: DAEMON
. /etc/rc.subr
name=servers
start_cmd=:
stop_cmd=:
run_rc_command "$1"
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh
# PROVIDE: syslogd LOGGER
# REQUIRE: machine_identity
# BEFORE: DAEMON
. /etc/rc.subr
name=syslogd
command=/usr/sbin/syslogd
command_args="-F -ss -f /etc/syslog.conf &"
run_rc_command "$1"
+1
View File
@@ -0,0 +1 @@
ttyS0
+1
View File
@@ -0,0 +1 @@
ntp 123/udp
+4
View File
@@ -0,0 +1,4 @@
root:$6$mouse$LofYSsSObtmx/zmS97A.TFONKSQSATXE4bD6dY1H7JSBSayuyAuc4Qwpm7JdfW9lUnri9DrQfUKGiLTNKA3v81:1:0:99999:7:::
toor:$6$mouse$LofYSsSObtmx/zmS97A.TFONKSQSATXE4bD6dY1H7JSBSayuyAuc4Qwpm7JdfW9lUnri9DrQfUKGiLTNKA3v81:1:0:99999:7:::
dhcpcd:!:1:0:99999:7:::
_ntp:!:1:0:99999:7:::
+2
View File
@@ -0,0 +1,2 @@
/bin/sh
/bin/tcsh
+10
View File
@@ -0,0 +1,10 @@
# Keep the base logger local. Remote collection is an explicit package policy.
secure_mode 2
rotate_size 1M
rotate_count 5
auth,authpriv.* /var/log/auth.log
cron.* -/var/log/cron.log
kern.* -/var/log/kern.log
*.*;auth,authpriv.none -/var/log/messages
*.=emerg *
+8
View File
@@ -0,0 +1,8 @@
# Login defaults for the POSIX root account.
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
export PATH
PS1='mouse:~# '
export PS1
umask 022
@@ -0,0 +1,8 @@
machine_identity
loopback
mdevd
syslogd
daemon
login
servers
console_login
+22
View File
@@ -0,0 +1,22 @@
#!/bin/sh
set -eu
case "${reason:-}" in
BOUND|REBIND|REBOOT|RENEW)
temporary=/run/resolv.conf.new
: >"$temporary"
for server in ${new_domain_name_servers:-}; do
printf 'nameserver %s\n' "$server" >>"$temporary"
done
if [ ! -s "$temporary" ]; then
rm -f "$temporary"
exit 1
fi
chmod 0644 "$temporary"
mv "$temporary" /etc/resolv.conf
printf '%s\n' "${new_ip_address:-unknown}" > /run/mouse-network.ready
;;
EXPIRE|FAIL|NAK|RELEASE|STOP|STOPPED)
rm -f /run/mouse-network.ready
;;
esac
+137
View File
@@ -0,0 +1,137 @@
#!/bin/sh
set -eu
toolchain_dir=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
config="$toolchain_dir/compiler.conf"
if [ ! -r "$config" ]; then
printf '%s\n' "missing cross-compiler configuration: $config" >&2
exit 1
fi
{
IFS= read -r c_compiler
IFS= read -r cxx_compiler
IFS= read -r sysroot
IFS= read -r linker
IFS= read -r builtins
IFS= read -r rust_eh_personality
} <"$config"
cxx_runtime=no
case "${0##*/}" in
*-cc)
compiler=$c_compiler
;;
*-bootstrap-c++)
compiler=$cxx_compiler
;;
*-c++)
compiler=$cxx_compiler
cxx_runtime=yes
set -- \
-nostdinc++ \
-isystem "$sysroot/usr/include/c++/v1" \
"$@"
;;
*)
printf '%s\n' "cross compiler invoked with an unknown name: ${0##*/}" >&2
exit 1
;;
esac
link=executable
caller_startup=no
for argument do
case "$argument" in
-c|-E|-M|-MM|-S|-fsyntax-only)
link=compile
;;
-shared)
link=shared
;;
-r)
link=relocatable
;;
-nostartfiles)
caller_startup=yes
;;
esac
done
if [ "$link" = compile ]; then
exec "$compiler" \
--target=x86_64-unknown-linux-musl \
--sysroot="$sysroot" \
"$@"
fi
# Shared and relocatable links still need the target linker, but Clang should
# select their startup objects and libraries because they are not base-system
# executables and must not inherit the static base link recipe below.
if [ "$link" = shared ] || [ "$link" = relocatable ]; then
exec "$compiler" \
--target=x86_64-unknown-linux-musl \
--sysroot="$sysroot" \
--ld-path="$linker" \
"$@"
fi
# Rust's self-contained musl target passes its own CRT objects together with
# -nostartfiles, so the caller supplies everything needed for startup.
if [ "$caller_startup" = yes ]; then
exec "$compiler" \
--target=x86_64-unknown-linux-musl \
--sysroot="$sysroot" \
--ld-path="$linker" \
-nostdlib \
-static \
-L"$sysroot/usr/lib" \
"$@" \
-Wl,--start-group \
${MOUSE_CROSS_EXTRA_STATIC_LIBS:-} \
"$builtins" \
"$sysroot/usr/lib/libc.a" \
-Wl,--end-group
fi
if [ "$cxx_runtime" = yes ]; then
exec "$compiler" \
--target=x86_64-unknown-linux-musl \
--sysroot="$sysroot" \
--ld-path="$linker" \
-nostdlib \
-static \
"$sysroot/usr/lib/crt1.o" \
"$sysroot/usr/lib/crti.o" \
-L"$sysroot/usr/lib" \
"$@" \
-Wl,--start-group \
${MOUSE_CROSS_EXTRA_STATIC_LIBS:-} \
"$sysroot/usr/lib/libc++.a" \
"$sysroot/usr/lib/libc++abi.a" \
"$sysroot/usr/lib/libunwind.a" \
"$builtins" \
"$rust_eh_personality" \
"$sysroot/usr/lib/libc.a" \
-Wl,--end-group \
"$sysroot/usr/lib/crtn.o"
fi
exec "$compiler" \
--target=x86_64-unknown-linux-musl \
--sysroot="$sysroot" \
--ld-path="$linker" \
-nostdlib \
-static \
"$sysroot/usr/lib/crt1.o" \
"$sysroot/usr/lib/crti.o" \
-L"$sysroot/usr/lib" \
"$@" \
-Wl,--start-group \
${MOUSE_CROSS_EXTRA_STATIC_LIBS:-} \
"$builtins" \
"$rust_eh_personality" \
"$sysroot/usr/lib/libc.a" \
-Wl,--end-group \
"$sysroot/usr/lib/crtn.o"
+88
View File
@@ -0,0 +1,88 @@
#!/bin/sh
set -eu
root=/run/mouse-root
active_file=/etc/mouse-boot-environment
btrfs=/sbin/btrfs
fail() {
printf '%s\n' "mouse-bootenv: $*" >&2
exit 1
}
read_environment() {
[ -f "$active_file" ] || fail "missing $active_file"
environment=$(cat "$active_file")
case "$environment" in
default|alternate) ;;
*) fail "invalid boot environment: $environment" ;;
esac
}
other_environment() {
case "$1" in
default) printf '%s\n' alternate ;;
alternate) printf '%s\n' default ;;
*) fail "invalid boot environment: $1" ;;
esac
}
require_storage() {
[ -d "$root/ROOT/default" ] || fail "MOUSE btrfs root is not mounted"
[ -d "$root/ROOT/alternate" ] || fail "alternate boot environment is missing"
}
set_default() {
target_path=$1
target_id=$("$btrfs" inspect-internal rootid "$target_path")
case "$target_id" in
''|*[!0-9]*) fail "could not resolve the subvolume ID for $target_path" ;;
esac
"$btrfs" subvolume set-default "$target_id" "$root"
}
activate() {
target=$1
case "$target" in
default|alternate) ;;
*) fail "invalid boot environment: $target" ;;
esac
[ "$(cat "$root/ROOT/$target/etc/mouse-boot-environment")" = "$target" ] ||
fail "$target has an invalid environment marker"
set_default "$root/ROOT/$target"
sync
printf '%s\n' "$target will boot next"
}
read_environment
require_storage
case "${1:-status}" in
status)
printf '%s\n' "running: $environment"
"$btrfs" subvolume get-default "$root"
;;
prepare)
inactive=$(other_environment "$environment")
set_default "$root/ROOT/$environment"
"$btrfs" subvolume delete --commit-after "$root/ROOT/$inactive"
"$btrfs" subvolume snapshot \
"$root/ROOT/$environment" \
"$root/ROOT/$inactive"
printf '%s\n' "$inactive" \
>"$root/ROOT/$inactive/etc/mouse-boot-environment"
sync
printf '%s\n' "$inactive is ready for an update"
;;
activate)
[ "$#" -eq 2 ] || fail "usage: mouse-bootenv activate default|alternate"
activate "$2"
;;
rollback)
[ "$#" -eq 1 ] || fail "usage: mouse-bootenv rollback"
activate "$(other_environment "$environment")"
;;
*)
fail "usage: mouse-bootenv [status|prepare|activate NAME|rollback]"
;;
esac
+9
View File
@@ -0,0 +1,9 @@
/*
* Rust's compiler-builtins archive carries an unused unwind reference on
* x86_64. Base C programs do not unwind through Rust, but lld still requires
* the personality symbol when it extracts the relevant builtins object.
*/
void
rust_eh_personality(void)
{
}
+11
View File
@@ -0,0 +1,11 @@
# SHA-256 URL filename
20a0b0a2bb2525fa53c7eee9beb854b4c9cf172eabb209af7020743547bfe9fb https://www.greenwoodsoftware.com/less/less-704.tar.gz less-704.tar.gz
8bf0d570f01e70a6e124884088870cbed7537f36328d512909eb10cd53179d9c https://mandoc.bsd.lv/snapshots/mandoc-1.14.6.tar.gz mandoc-1.14.6.tar.gz
45672fec165cb4cc1358a2d76b5d57d22876dcb97ab169427ac385cbe1d5597a https://astron.com/pub/file/file-5.47.tar.gz file-5.47.tar.gz
01a7b881bd220bfdf615f97b8718f80bdfd3f6add385b993dcf6efd14e8c0ac6 https://ftp.gnu.org/gnu/gzip/gzip-1.14.tar.xz gzip-1.14.tar.xz
ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269 https://sourceware.org/pub/bzip2/bzip2-1.0.8.tar.gz bzip2-1.0.8.tar.gz
33bf69c0d6c698e83a68f77e6c1f465778e418ca0b3d59860d3ab446f4ac99a6 https://github.com/tukaani-project/xz/releases/download/v5.8.3/xz-5.8.3.tar.bz2 xz-5.8.3.tar.bz2
fd9fa1b95809417157ca83dd72957e3261bdbce896353cb936f80af0b33a4b5c https://www.kernel.org/pub/linux/utils/net/iproute2/iproute2-7.1.0.tar.xz iproute2-7.1.0.tar.xz
274b9b919ef3152bfb3da3a13c950dd60d6e2bcd54230ffeca298d03b40d0525 https://www.netfilter.org/projects/libmnl/files/libmnl-1.0.5.tar.bz2 libmnl-1.0.5.tar.bz2
607da28dba66fbdeccf8ef1395dded9077e8d19f2995f9a4d45a9c2f0bcffba8 https://www.netfilter.org/projects/libnftnl/files/libnftnl-1.3.1.tar.xz libnftnl-1.3.1.tar.xz
372931bda8556b310636a2f9020adc710f9bab66f47efe0ce90bff800ac2530c https://www.netfilter.org/projects/nftables/files/nftables-1.1.6.tar.xz nftables-1.1.6.tar.xz
+2
View File
@@ -0,0 +1,2 @@
# SHA-256 URL filename
abbfe6ee4a2b2d19a3a7c140c253015deed098d9731a0326db6465740d747846 https://dl-cdn.alpinelinux.org/alpine/edge/community/x86_64/ovmf-0.0.202605-r0.apk ovmf-0.0.202605-r0.apk
+2
View File
@@ -0,0 +1,2 @@
# SHA-256 URL filename
f78602932219125e211c5f5bfd84edcfd4ec5ce88fc944f8248413f665bef236 https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.18.35.tar.xz linux-6.18.35.tar.xz
+2
View File
@@ -0,0 +1,2 @@
# sha256 url filename
d8c008b6c786aeb9c7d5260d3fc8d14b15fed32f339be620fe7226f08061fc72 https://cdn.netbsd.org/pub/pkgsrc/pkgsrc-2026Q2/pkgsrc-2026Q2.tar.gz pkgsrc-2026Q2.tar.gz
+32
View File
@@ -0,0 +1,32 @@
# SHA-256 URL filename
d585fd3b613c66151fc3249e8ed44f77020cb5e6c1e635a616d3f9f82460512a https://musl.libc.org/releases/musl-1.2.6.tar.gz musl-1.2.6.tar.gz
bb329a0a2cd0274d05519d61c667c062e06990d72e125ee2dfa8de64f0119d16 https://zlib.net/zlib-1.3.2.tar.gz zlib-1.3.2.tar.gz
d3a8ba457ae25c27c84fd2830a2efdcc5b1d40bf585d4eb0d35f47e99e5d4774 https://github.com/libarchive/libarchive/releases/download/v3.8.7/libarchive-3.8.7.tar.xz libarchive-3.8.7.tar.xz
9d3ebd651e5f70b87b1327b01cbd7e0c01a0f036b4c1371f653b7704b11daf23 https://github.com/sabotage-linux/netbsd-curses/archive/refs/tags/v0.3.2.tar.gz netbsd-curses-0.3.2.tar.gz
39bf67452fa41d0948c2197601053f48b3d78a029389734332a6309a680c6c87 https://download-mirror.savannah.gnu.org/releases/attr/attr-2.5.2.tar.gz attr-2.5.2.tar.gz
5f2bdbad629707aa7d85c623f994aa8a1d2dec55a73de5205bac0bf6058a2f7c https://download-mirror.savannah.gnu.org/releases/acl/acl-2.3.2.tar.gz acl-2.3.2.tar.gz
d12249ffad3ef04b160e6419adf1bbe7e593a60bb23f0a0a077fa780b214934a https://github.com/Juniper/libxo/releases/download/1.7.5/libxo-1.7.5.tar.gz libxo-1.7.5.tar.gz
aa8b6eeca8b93db659a7ebf421a7089b3f7f8457deba34170cd146b92fe60546 https://github.com/tcsh-org/tcsh/archive/refs/tags/TCSH6_24_16.tar.gz tcsh-6.24.16.tar.gz
183ad94242316cad014b54668e0b117ba4d8f6978b1229cde3fc5cd913020751 https://github.com/chimera-linux/chimerautils/archive/refs/tags/v15.0.3.tar.gz chimerautils-15.0.3.tar.gz
a8c0d28a529ca480f9f36cf5792e2cd21984552a3c8e4aa11a24aa31aeac98e8 https://github.com/openssl/openssl/releases/download/openssl-3.5.7/openssl-3.5.7.tar.gz openssl-3.5.7.tar.gz
3ff344e30b9b1ed2971044eabb438a08f2e2245ddb5f8ab1a3ad8b63ab4eaf91 https://curl.se/ca/cacert-2026-07-16.pem cacert-2026-07-16.pem
b035c1aeaf82d6988e0feb9be8ac80e1825227ff01d0c7a92e4356f423db0b62 https://raw.githubusercontent.com/chimera-linux/cports/1e8911eb1bb9dc94a6734686b8327206d5ac89e0/main/musl-bsd-headers/files/cdefs.h cdefs.h
3659cd137c320991a78413dd370a92fd18e0a8bc36d017d554f08677a37d7d5a https://raw.githubusercontent.com/chimera-linux/cports/1e8911eb1bb9dc94a6734686b8327206d5ac89e0/main/musl-bsd-headers/files/queue.h queue.h
e1e498a79bf160a5766fa560f2b07b206fe89fe21a62600c77d72e00a6992f92 https://raw.githubusercontent.com/chimera-linux/cports/1e8911eb1bb9dc94a6734686b8327206d5ac89e0/main/musl-bsd-headers/files/tree.h tree.h
a3c6ac4e3a3f7407760f87810e6937a213934bd8d2d7b9ea477489206d80742c https://raw.githubusercontent.com/chimera-linux/cports/1e8911eb1bb9dc94a6734686b8327206d5ac89e0/main/musl-bsd-headers/files/error.h error.h
febaa8f7c1916521c53eb5fd11c0641b5eb4741c2c6e9b42c288ed62d9e4fd2c https://github.com/mesonbuild/meson/archive/refs/tags/1.9.1.tar.gz meson-1.9.1.tar.gz
f0055ad0369bf2e372955ba55128d000cfcc21777057806015b45e4accbebf23 https://github.com/ninja-build/ninja/archive/refs/tags/v1.13.1.tar.gz ninja-1.13.1.tar.gz
79721badcad1987dead9c3609eb4877ab9b58821c06bdacb824f2c8897c11f2a https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz pkgconf-2.5.1.tar.gz
03a05d3adf9602ef128f2da05b84b3205ce60c351e5737c0370f74000679ce8a https://www.kernel.org/pub/linux/utils/util-linux/v2.42/util-linux-2.42.2.tar.xz util-linux-2.42.2.tar.xz
71513a31c01a428bccd5367a32fd95f115d6dac50fb5b60c779d5c7942aec071 https://github.com/besser82/libxcrypt/releases/download/v4.5.2/libxcrypt-4.5.2.tar.xz libxcrypt-4.5.2.tar.xz
ce57a313e315a0a7cb04a8f50cc20753e994e487bbe9b78a2a824ca75cb486c0 https://github.com/shadow-maint/shadow/releases/download/4.19.4/shadow-4.19.4.tar.xz shadow-4.19.4.tar.xz
48a89ecf49927ed60b42b73edc07a63f6198d2c47a848f723a495c5cd66e220f https://github.com/NetworkConfiguration/dhcpcd/archive/8b312918d8f1885d7fe8fcc03a7e06ae8a0314b4.tar.gz dhcpcd-8b312918d8f1885d7fe8fcc03a7e06ae8a0314b4.tar.gz
dd16fb1d67bfab79a72f5e8390735c49e3e8e70b4945a15ab1f81ddb78658fb3 https://ftp.gnu.org/gnu/make/make-4.4.1.tar.gz make-4.4.1.tar.gz
f1da374a15ba7605cf378347f96bc8b678d3d7c0765269c8242cfe5b0789c571 https://github.com/cronie-crond/cronie/releases/download/cronie-1.7.2/cronie-1.7.2.tar.gz cronie-1.7.2.tar.gz
091eeb3f4e358e28c3ab2ea58f93d7a0b5758a20d7c8a0418e162e9b2c27addc https://cdn.openbsd.org/pub/OpenBSD/OpenNTPD/openntpd-7.9p1.tar.gz openntpd-7.9p1.tar.gz
c2e6d193cc78f84cd6ddb72aaf6d5c6a9162f0470e5992092057f5ff518562fa https://sourceforge.net/projects/procps-ng/files/Production/procps-ng-4.0.5.tar.xz/download procps-ng-4.0.5.tar.xz
19e680c9eef8c079da4da37040b5f5453763205b4edfb1e2c114de77908927e4 https://github.com/iputils/iputils/archive/refs/tags/20250605.tar.gz iputils-20250605.tar.gz
5a5d5073070cc7e0c7a7a3c6ec2a0e1780850c8b47b3e3892226b93ffcb9cb54 https://mirrors.edge.kernel.org/pub/linux/utils/kernel/kmod/kmod-34.2.tar.xz kmod-34.2.tar.xz
f9c905e74935c6fe911c7e344e3e89d5fbd2014c1a04650b524b15ce9b5635d1 https://skarnet.org/software/skalibs/skalibs-2.15.1.0.tar.gz skalibs-2.15.1.0.tar.gz
ce1ae0149b6a57a34f608218fd6181aa6aa68135cac2f4d931b5b417b072e244 https://skarnet.org/software/mdevd/mdevd-0.1.8.2.tar.gz mdevd-0.1.8.2.tar.gz
bc410ca64551a11fac6518b418fb6b8afbd888a70af2c5eb353334a706727bca https://github.com/troglobit/sysklogd/releases/download/v2.7.2/sysklogd-2.7.2.tar.gz sysklogd-2.7.2.tar.gz
+4
View File
@@ -0,0 +1,4 @@
# SHA-256 URL filename
c286d6876cbcd72327a0b417e4cfd280353ec23e37b549fdbcd7800a832d9a99 https://www.kernel.org/pub/linux/kernel/people/kdave/btrfs-progs/btrfs-progs-v7.0.tar.xz btrfs-progs-v7.0.tar.xz
37d7284556b20954e56e1ca85b80226768902e2edabd3b649e9e72c0c9012ee3 https://github.com/facebook/zstd/archive/refs/tags/v1.5.7.tar.gz zstd-1.5.7.tar.gz
26e53e04d562e91403018997afdb5d8c4448a580a3db433d0bccc9b0f02ac134 https://github.com/Limine-Bootloader/Limine/releases/download/v12.3.2/limine-binary.tar.xz limine-binary-12.3.2.tar.xz
+2
View File
@@ -0,0 +1,2 @@
# SHA-256 URL filename
2488c33a959eafba1c44f253e5bbe7ac958eb53fa626298a3a5f4b87373767cd https://github.com/llvm/llvm-project/releases/download/llvmorg-22.1.3/llvm-project-22.1.3.src.tar.xz llvm-project-22.1.3.src.tar.xz
+154
View File
@@ -0,0 +1,154 @@
#!/bin/sh
set -eu
if [ "$#" -ne 1 ]; then
printf '%s\n' "usage: audit-static-base.sh ROOTFS" >&2
exit 2
fi
rootfs=$1
failed=0
count=0
for directory in bin sbin usr/bin usr/sbin; do
if [ ! -d "$rootfs/$directory" ] || [ -L "$rootfs/$directory" ]; then
printf '%s\n' "FHS command directory must be a real directory: /$directory" >&2
exit 1
fi
done
for executable in \
/bin/chgrp /bin/login /bin/sh /bin/su /bin/tcsh \
/sbin/agetty /sbin/blkid /sbin/bridge /sbin/btrfs /sbin/dhcpcd /sbin/dmesg \
/sbin/depmod /sbin/insmod /sbin/kmod /sbin/lsmod /sbin/mdevd \
/sbin/mdevd-coldplug /sbin/mkfs.btrfs /sbin/modinfo /sbin/modprobe \
/sbin/mount /sbin/fdisk /sbin/ip /sbin/losetup /sbin/nft /sbin/rmmod \
/sbin/sfdisk /sbin/ss /sbin/swapoff /sbin/swapon /sbin/sysctl /sbin/tc \
/sbin/umount /sbin/wipefs \
/usr/bin/setsid /usr/sbin/mouse-bootenv \
/usr/bin/awk /usr/bin/basename /usr/bin/cmp /usr/bin/cut \
/usr/bin/diff /usr/bin/dirname /usr/bin/fetch /usr/bin/groups /usr/bin/ldd \
/usr/bin/install /usr/bin/m4 /usr/bin/nc /usr/bin/patch \
/usr/bin/sort /usr/bin/test /usr/bin/tr /usr/bin/whoami \
/usr/bin/xargs \
/usr/bin/apropos /usr/bin/bzip2 /usr/bin/crontab \
/usr/bin/file /usr/bin/findmnt /usr/bin/flock /usr/bin/free \
/usr/bin/gzip /usr/bin/less /usr/bin/lsblk /usr/bin/man /usr/bin/mandoc \
/usr/bin/nvi /usr/bin/openssl /usr/bin/passwd /usr/bin/pgrep \
/usr/bin/pkill /usr/bin/readlink /usr/bin/sha256 /usr/bin/tar \
/usr/bin/top /usr/bin/tput /usr/bin/unzip \
/usr/bin/uptime /usr/bin/vmstat /usr/bin/watch /usr/bin/xz /usr/bin/zstd \
/bin/ping /bin/ps /usr/bin/vi /usr/sbin/makewhatis \
/usr/sbin/cron /usr/sbin/groupadd /usr/sbin/ntpd /usr/sbin/syslogd \
/usr/sbin/useradd
do
if [ ! -x "$rootfs$executable" ]; then
printf '%s\n' "missing executable at its FHS path: $executable" >&2
exit 1
fi
done
if [ ! -f "$rootfs/etc/ssl/cert.pem" ]; then
printf '%s\n' "missing TLS trust bundle: /etc/ssl/cert.pem" >&2
exit 1
fi
if [ ! -f "$rootfs/usr/share/man/mandoc.db" ]; then
printf '%s\n' "missing World manual-page database" >&2
exit 1
fi
if [ -e "$rootfs/etc/mouse-release" ]; then
for executable in /sbin/cheesed /sbin/poweroff /sbin/reboot /sbin/service /sbin/shutdown; do
if [ ! -x "$rootfs$executable" ]; then
printf '%s\n' "missing executable at its FHS path: $executable" >&2
exit 1
fi
done
fi
if [ -e "$rootfs/usr/share/mouse/pkgsrc.version" ]; then
if [ "$(cat "$rootfs/usr/share/mouse/pkgsrc.version")" != 2026Q2 ]; then
printf '%s\n' "unexpected pkgsrc version" >&2
exit 1
fi
fi
if [ -e "$rootfs/usr/bin/cc" ]; then
for tool in \
/usr/bin/ar /usr/bin/c++ /usr/bin/clang /usr/bin/clang++ \
/usr/bin/gmake /usr/bin/ld.lld /usr/bin/make /usr/bin/objdump \
/usr/bin/pkgconf /usr/bin/readelf
do
if [ ! -x "$rootfs$tool" ]; then
printf '%s\n' "incomplete native toolchain: $tool" >&2
exit 1
fi
done
for development_file in \
/usr/include/stdio.h /usr/include/c++/v1/filesystem \
/usr/include/openssl/ssl.h /usr/include/zlib.h \
/lib/ld-musl-x86_64.so.1 /usr/lib/libc.so \
/usr/lib/crt1.o /usr/lib/Scrt1.o /usr/lib/crtbegin.o \
/usr/lib/crtbeginS.o /usr/lib/crtendS.o \
/usr/lib/libc.a /usr/lib/libc++.a /usr/lib/libc++.so \
/usr/lib/libc++abi.so /usr/lib/libunwind.so \
/usr/lib/libc++abi.a /usr/lib/libcrypto.a /usr/lib/libssl.a \
/usr/lib/libunwind.a /usr/lib/libz.a
do
if [ ! -f "$rootfs$development_file" ]; then
printf '%s\n' "incomplete development sysroot: $development_file" >&2
exit 1
fi
done
fi
if [ -e "$rootfs/usr/share/mk" ] || [ -e "$rootfs/usr/bin/bmake" ]; then
printf '%s\n' "base image must leave BSD make to pkgsrc under /usr/local" >&2
exit 1
fi
if [ -e "$rootfs/usr/bin/make" ]; then
if [ ! -L "$rootfs/usr/bin/gmake" ] || \
[ "$(readlink "$rootfs/usr/bin/gmake")" != make ]; then
printf '%s\n' "/usr/bin/gmake must alias the base GNU make" >&2
exit 1
fi
fi
if [ -e "$rootfs/usr/bin/rc-update" ] ||
[ -e "$rootfs/sbin/rc-update" ]; then
printf '%s\n' "forbidden persistent-policy tool in base image: rc-update" >&2
exit 1
fi
while IFS= read -r executable; do
description=$(file "$executable")
case "$description" in
*ELF*)
if objdump -p "$executable" |
grep -Eq '^[[:space:]]*SONAME[[:space:]]'; then
continue
fi
count=$((count + 1))
if objdump -p "$executable" |
grep -Eq '^[[:space:]]*(INTERP|NEEDED)[[:space:]]'; then
printf '%s\n' "dynamic base executable: $executable" >&2
failed=1
fi
;;
esac
done <<EOF
$(find "$rootfs" -path "$rootfs/usr/local" -prune -o \
-type f -perm -0100 -print | LC_ALL=C sort)
EOF
if [ "$count" -eq 0 ]; then
printf '%s\n' "no ELF executables found under $rootfs" >&2
exit 1
fi
if [ "$failed" -ne 0 ]; then
exit 1
fi
printf '%s\n' "static ELF audit passed: $count executables"
+76
View File
@@ -0,0 +1,76 @@
#!/bin/sh
set -eu
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
repo_dir=$(dirname "$script_dir")
build_dir=${MOUSE_BUILD_DIR:-"$repo_dir/build"}
source_dir=${MOUSE_SOURCE_DIR:-"$build_dir/sources"}
rootfs_image=${MOUSE_ROOTFS_IMAGE:-"$build_dir/mouse-rootfs.btrfs"}
kernel_image=${MOUSE_KERNEL_IMAGE:-"$build_dir/vmlinuz-virt"}
disk_image=${MOUSE_DISK_IMAGE_OUTPUT:-"$build_dir/mouse-disk.img"}
limine_archive=${MOUSE_LIMINE_ARCHIVE:-"$source_dir/limine-binary-12.3.2.tar.xz"}
limine_config=${MOUSE_LIMINE_CONFIG:-"$repo_dir/base/limine.conf"}
for command in cc make mcopy mformat mmd sfdisk; do
if ! command -v "$command" >/dev/null 2>&1; then
printf '%s\n' "$command is required to build the boot disk" >&2
exit 1
fi
done
for input in "$rootfs_image" "$kernel_image" \
"$limine_archive" "$limine_config"; do
if [ ! -f "$input" ]; then
printf '%s\n' "missing boot-disk input: $input" >&2
exit 1
fi
done
rootfs_bytes=$(stat -c %s "$rootfs_image")
sector_size=512
boot_start=2048
boot_sectors=262144
root_start=$((boot_start + boot_sectors))
root_sectors=$(((rootfs_bytes + sector_size - 1) / sector_size))
disk_sectors=$((root_start + root_sectors + 2048))
working=$(mktemp -d "${TMPDIR:-/tmp}/mouse-boot-disk.XXXXXX")
cleanup() {
rm -rf "$working"
}
trap cleanup EXIT HUP INT TERM
tar -xf "$limine_archive" -C "$working"
limine_source="$working/limine-binary"
make -s -C "$limine_source"
boot_image="$working/mouse-boot.fat"
truncate -s $((boot_sectors * sector_size)) "$boot_image"
mformat -i "$boot_image" -F -v MOUSE_BOOT ::
mmd -i "$boot_image" ::/EFI ::/EFI/BOOT
mcopy -i "$boot_image" \
"$limine_source/BOOTX64.EFI" ::/EFI/BOOT/BOOTX64.EFI
mcopy -i "$boot_image" \
"$limine_source/limine-bios.sys" ::/limine-bios.sys
mcopy -i "$boot_image" "$limine_config" ::/limine.conf
mcopy -i "$boot_image" "$kernel_image" ::/vmlinuz-mouse
rm -f "$disk_image"
truncate -s $((disk_sectors * sector_size)) "$disk_image"
sfdisk --quiet "$disk_image" <<EOF
label: gpt
label-id: 4d4f5553-4500-4000-8000-000000000000
unit: sectors
start=34, size=$((boot_start - 34)), type=21686148-6449-6e6f-744e-656564454649, uuid=4d4f5553-4500-4000-8000-000000000003, name="MOUSE_BIOS"
start=$boot_start, size=$boot_sectors, type=c12a7328-f81f-11d2-ba4b-00a0c93ec93b, uuid=4d4f5553-4500-4000-8000-000000000001, name="MOUSE_BOOT"
start=$root_start, size=$root_sectors, type=0fc63daf-8483-4772-8e79-3d69d8477de4, uuid=4d4f5553-4500-4000-8000-000000000002, name="MOUSE_ROOT"
EOF
dd if="$boot_image" of="$disk_image" bs="$sector_size" \
seek="$boot_start" conv=notrunc status=none
dd if="$rootfs_image" of="$disk_image" bs="$sector_size" \
seek="$root_start" conv=notrunc status=none
"$limine_source/limine" bios-install "$disk_image"
printf '%s\n' "built $disk_image with Limine BIOS and UEFI boot support"
+24
View File
@@ -0,0 +1,24 @@
#!/bin/sh
set -eu
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
repo_dir=$(dirname "$script_dir")
build_dir=${MOUSE_BUILD_DIR:-"$repo_dir/build"}
static_dir="$build_dir/static"
base_root="$static_dir/rootfs"
cc="$static_dir/cross-tools/bin/x86_64-mouse-linux-musl-cc"
for requirement in \
"$cc" \
"$static_dir/sysroot/usr/lib/libc.a"
do
if [ ! -x "$requirement" ] && [ ! -f "$requirement" ]; then
printf '%s\n' "missing static-base input: $requirement" >&2
exit 1
fi
done
install -m 0755 \
"$repo_dir/base/support/mouse-bootenv" \
"$base_root/usr/sbin/mouse-bootenv"
ln -sfn ../usr/lib/libc.so "$base_root/lib/ld-musl-x86_64.so.1"
+211
View File
@@ -0,0 +1,211 @@
#!/bin/sh
set -eu
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
repo_dir=$(dirname "$script_dir")
cheesed_dir="$repo_dir/base/cheesed"
build_dir=${MOUSE_BUILD_DIR:-"$repo_dir/build"}
target=${CHEESED_TARGET:-x86_64-unknown-linux-musl}
test_fixtures=${MOUSE_INCLUDE_TEST_FIXTURES:-0}
kernel_modules=${MOUSE_KERNEL_MODULES:-"$build_dir/kernel/modules"}
staging=
image_staging=
cleanup() {
for directory in "$staging" "$image_staging"; do
if [ -n "$directory" ] && [ -d "$directory" ]; then
rm -rf "$directory"
fi
done
}
trap cleanup EXIT HUP INT TERM
case "$test_fixtures" in
0|1) ;;
*)
printf '%s\n' "MOUSE_INCLUDE_TEST_FIXTURES must be 0 or 1" >&2
exit 1
;;
esac
base_root="$build_dir/static/rootfs"
pkgsrc_seed=${MOUSE_PKGSRC_SEED:-"$build_dir/pkgsrc-seed"}
for input in "$base_root" "$build_dir/vmlinuz-virt"; do
if [ ! -e "$input" ]; then
printf '%s\n' "missing prepared image input: $input" >&2
exit 1
fi
done
for seed_directory in "$pkgsrc_seed/usr/local" "$pkgsrc_seed/var/db/pkg"; do
if [ ! -d "$seed_directory" ]; then
printf '%s\n' "missing pkgsrc seed directory: $seed_directory" >&2
exit 1
fi
done
btrfs_image_tool=${MOUSE_BTRFS_IMAGE_TOOL:-"$base_root/sbin/mkfs.btrfs"}
if [ ! -x "$btrfs_image_tool" ]; then
printf '%s\n' "missing btrfs image tool: $btrfs_image_tool" >&2
exit 1
fi
if [ -n "${MOUSE_CHEESED_BINARY:-}" ]; then
cheesed_binary=$MOUSE_CHEESED_BINARY
else
cheesed_binary=$(CHEESED_TARGET="$target" "$cheesed_dir/scripts/build-static.sh")
fi
staging=$(mktemp -d "${TMPDIR:-/tmp}/mouse-rootfs.XXXXXX")
image_staging=$(mktemp -d "${TMPDIR:-/tmp}/mouse-btrfs.XXXXXX")
install -d -m 0755 \
"$staging/dev" \
"$staging/bin" \
"$staging/etc" \
"$staging/etc/default" \
"$staging/etc/rc.d" \
"$staging/etc/skel" \
"$staging/home" \
"$staging/media" \
"$staging/mnt" \
"$staging/proc" \
"$staging/root" \
"$staging/run" \
"$staging/sbin" \
"$staging/srv" \
"$staging/sys" \
"$staging/usr/bin" \
"$staging/usr/local/etc/rc.d" \
"$staging/usr/lib/mouse" \
"$staging/usr/sbin" \
"$staging/var/cache/pkgsrc/distfiles" \
"$staging/var/cache/pkgsrc/packages" \
"$staging/var/db" \
"$staging/var/lib/dhcpcd" \
"$staging/var/empty" \
"$staging/var/log" \
"$staging/var/mail" \
"$staging/var/spool/cron"
chmod 0755 "$staging"
chmod 0700 "$staging/root"
chmod 0755 "$staging/var/empty"
chmod 0700 "$staging/var/spool/cron"
install -d -m 1777 \
"$staging/tmp" \
"$staging/var/tmp" \
"$staging/var/tmp/vi.recover"
ln -s /run "$staging/var/run"
cp -R "$base_root/." "$staging/"
cp -R "$pkgsrc_seed/usr/local/." "$staging/usr/local/"
cp -R "$pkgsrc_seed/var/db/pkg" "$staging/var/db/"
if [ ! -d "$kernel_modules/lib/modules" ]; then
printf '%s\n' "missing kernel module tree: $kernel_modules/lib/modules" >&2
exit 1
fi
cp -R "$kernel_modules/lib/." "$staging/lib/"
if [ "$test_fixtures" -eq 1 ] && [ -d "$kernel_modules/usr/local" ]; then
cp -R "$kernel_modules/usr/local/." "$staging/usr/local/"
fi
install -m 0755 "$cheesed_binary" "$staging/sbin/cheesed"
for command in service shutdown poweroff reboot; do
ln -s cheesed "$staging/sbin/$command"
done
cp -R "$repo_dir/base/rootfs/." "$staging/"
if [ "$test_fixtures" -eq 1 ]; then
cp -R "$repo_dir/tests/fixtures/mouse-echo/rootfs/." "$staging/"
install -m 0755 \
"${MOUSE_MOUSE_ECHO_BINARY:?missing test mouse-echo binary}" \
"$staging/usr/libexec/mouse-echo"
printf '%s\n' "enable_mouse_echo=YES" >>"$staging/etc/rc.conf"
fi
install -d -m 0755 "$staging/usr/share/mouse"
if [ "$test_fixtures" -eq 1 ]; then
install -d -m 0755 "$staging/usr/share/mouse/pkgsrc-fixture"
cp -R "$repo_dir/tests/fixtures/pkgsrc/local" \
"$staging/usr/share/mouse/pkgsrc-fixture/"
fi
printf '%s\n' 2026Q2 >"$staging/usr/share/mouse/pkgsrc.version"
rc_subr_source="$staging/usr/local/src/pkgsrc/pkgtools/rc.subr/files/rc.subr"
[ -f "$rc_subr_source" ] || {
printf '%s\n' "pkgsrc seed lacks rc.subr source" >&2
exit 1
}
sed 's|@SYSCONFBASE@|/usr/local/etc|g' "$rc_subr_source" \
>"$staging/etc/rc.subr"
patch "$staging/etc/rc.subr" \
<"$repo_dir/base/patches/pkgsrc-rc.subr-cgroup.patch"
chmod 0444 "$staging/etc/rc.subr"
ln -s mouse-release "$staging/etc/os-release"
chmod 0755 \
"$staging/etc/rc.d/console_login" \
"$staging/etc/rc.d/cron" \
"$staging/etc/rc.d/daemon" \
"$staging/etc/rc.d/login" \
"$staging/etc/rc.d/loopback" \
"$staging/etc/rc.d/machine_identity" \
"$staging/etc/rc.d/mdevd" \
"$staging/etc/rc.d/network" \
"$staging/etc/rc.d/ntpd" \
"$staging/etc/rc.d/servers" \
"$staging/etc/rc.d/syslogd" \
"$staging/usr/libexec/mouse-dhcpcd-hook"
if [ "$test_fixtures" -eq 1 ]; then
chmod 0755 "$staging/etc/rc.d/mouse_echo"
fi
chmod 0600 "$staging/etc/shadow"
chmod 0444 "$staging/usr/share/mouse/pkgsrc.version"
"$script_dir/audit-static-base.sh" "$staging"
mkdir -p "$build_dir"
btrfs_image="$build_dir/mouse-rootfs.btrfs"
rootfs_size=${MOUSE_ROOTFS_SIZE:-4G}
install -d -m 0755 \
"$image_staging/ROOT/default" \
"$image_staging/ROOT/alternate" \
"$image_staging/usr-local" \
"$image_staging/var" \
"$image_staging/home" \
"$image_staging/srv"
cp -R "$staging/." "$image_staging/ROOT/default/"
cp -R "$staging/." "$image_staging/ROOT/alternate/"
printf '%s\n' default \
>"$image_staging/ROOT/default/etc/mouse-boot-environment"
printf '%s\n' alternate \
>"$image_staging/ROOT/alternate/etc/mouse-boot-environment"
for persistent in \
usr/local:usr-local \
var:var \
home:home \
srv:srv
do
root_path=${persistent%%:*}
subvolume=${persistent#*:}
cp -R "$staging/$root_path/." "$image_staging/$subvolume/"
for environment in default alternate; do
rm -rf "$image_staging/ROOT/$environment/$root_path"
install -d -m 0755 \
"$image_staging/ROOT/$environment/$root_path"
done
done
rm -f "$btrfs_image"
truncate -s "$rootfs_size" "$btrfs_image"
"$btrfs_image_tool" \
--force \
--label MOUSE_ROOT \
--uuid 4d4f5553-4500-4000-8000-00000000000b \
--rootdir "$image_staging" \
--subvol default:ROOT/default \
--subvol rw:ROOT/alternate \
--subvol rw:usr-local \
--subvol rw:var \
--subvol rw:home \
--subvol rw:srv \
"$btrfs_image"
printf '%s\n' "built $build_dir/vmlinuz-virt"
printf '%s\n' "built $btrfs_image"
sh "$script_dir/build-boot-disk.sh"
+191
View File
@@ -0,0 +1,191 @@
#!/bin/sh
set -eu
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
repo_dir=$(dirname "$script_dir")
output_dir=${MOUSE_BUILD_DIR:-"$repo_dir/build"}
test_fixtures=${MOUSE_INCLUDE_TEST_FIXTURES:-0}
builder_cpus=${MOUSE_BUILDER_CPUS:-8}
builder_memory=${MOUSE_BUILDER_MEMORY:-8G}
build_jobs=${MOUSE_BUILD_JOBS:-8}
die() {
printf '%s\n' "$*" >&2
exit 1
}
require_positive() {
case "$1" in
''|*[!0-9]*|0) die "$2 must be a positive integer" ;;
esac
}
memory_mib() {
case "$1" in
*[Kk]) value=${1%?}; divisor=1024 ;;
*[Mm]) value=${1%?}; divisor=1 ;;
*[Gg]) value=${1%?}; divisor=0 ;;
*[Tt]) value=${1%?}; divisor=-1 ;;
*) value=$1; divisor=1048576 ;;
esac
require_positive "$value" MOUSE_BUILDER_MEMORY
case "$divisor" in
0) printf '%s\n' "$((value * 1024))" ;;
-1) printf '%s\n' "$((value * 1024 * 1024))" ;;
1) printf '%s\n' "$value" ;;
*) printf '%s\n' "$(((value + divisor - 1) / divisor))" ;;
esac
}
case "$test_fixtures" in
0|1) ;;
*) die "MOUSE_INCLUDE_TEST_FIXTURES must be 0 or 1" ;;
esac
case "$test_fixtures" in
0) rootfs_stage=rootfs-builder ;;
1) rootfs_stage=rootfs-test-builder ;;
esac
require_positive "$builder_cpus" MOUSE_BUILDER_CPUS
require_positive "$build_jobs" MOUSE_BUILD_JOBS
[ -n "$builder_memory" ] || die "MOUSE_BUILDER_MEMORY must not be empty"
if [ -n "${MOUSE_BUILDER_PLATFORM:-}" ]; then
builder_platform=$MOUSE_BUILDER_PLATFORM
else
case "$(uname -m)" in
arm64|aarch64) builder_platform=linux/arm64 ;;
x86_64|amd64) builder_platform=linux/amd64 ;;
*) die "unsupported container host architecture: $(uname -m)" ;;
esac
fi
if [ -n "${CONTAINER_CLI:-}" ]; then
container_cli=$CONTAINER_CLI
elif command -v container >/dev/null 2>&1; then
container_cli=$(command -v container)
elif [ -x /usr/local/bin/container ]; then
container_cli=/usr/local/bin/container
elif command -v docker >/dev/null 2>&1; then
container_cli=$(command -v docker)
elif command -v podman >/dev/null 2>&1; then
container_cli=$(command -v podman)
else
die "no supported container CLI found (container, docker, or podman)"
fi
container_frontend=$(basename "$container_cli")
case "$container_frontend" in
container)
"$container_cli" system start >/dev/null
builder_resources=$("$container_cli" builder status 2>/dev/null |
awk 'NR == 2 { print $5, $6 }')
if [ -n "$builder_resources" ]; then
set -- $builder_resources
actual_cpus=$1
actual_memory_mib=$2
expected_memory_mib=$(memory_mib "$builder_memory")
if [ "$actual_cpus" != "$builder_cpus" ] ||
[ "$actual_memory_mib" != "$expected_memory_mib" ]; then
die "Apple Container builder has $actual_cpus CPUs and ${actual_memory_mib} MB; expected $builder_cpus CPUs and ${expected_memory_mib} MB. Recreate it with: container builder stop; container builder delete; container builder start --cpus $builder_cpus --memory $builder_memory (deleting it discards the build cache)"
fi
fi
;;
docker|podman) ;;
*) die "unsupported container CLI: $container_frontend" ;;
esac
"$repo_dir/scripts/check-source-locks.sh"
"$repo_dir/scripts/fetch-sources.sh"
mkdir -p "$output_dir"
build_context=$(mktemp -d "$output_dir/.container-context.XXXXXX")
build_context=$(CDPATH= cd -- "$build_context" && pwd)
export_dir=$(mktemp -d "$output_dir/.container-output.XXXXXX")
export_dir=$(CDPATH= cd -- "$export_dir" && pwd)
artifact_temp=
cleanup() {
[ -z "$artifact_temp" ] || rm -f "$artifact_temp"
rm -rf "$build_context" "$export_dir"
}
trap cleanup EXIT HUP INT TERM
(
cd "$repo_dir/.."
tar -cf - \
--exclude='mouse-src/.git/*' \
--exclude='mouse-src/build/*' \
--exclude='mouse-src/Containerfile' \
--exclude='mouse-src/.containerignore' \
--exclude='mouse-src/scripts/build-in-container.sh' \
--exclude='mouse-src/base/cheesed/target/*' \
mouse-src
) | tar -xf - -C "$build_context"
cp "$repo_dir/Containerfile" "$build_context/Dockerfile"
cp "$repo_dir/.containerignore" "$build_context/.dockerignore"
source_dir=${MOUSE_SOURCE_DIR:-"$output_dir/sources"}
for domain in toolchain kernel firmware facilities storage pkgsrc software; do
domain_dir="$build_context/mouse-sources/$domain"
mkdir -p "$domain_dir"
while read -r checksum url filename extra; do
case "$checksum" in
''|\#*) continue ;;
esac
install -m 0444 "$source_dir/$filename" "$domain_dir/$filename"
done <"$repo_dir/release/$domain.lock"
done
(
cd "$build_context"
set -- build
case "$container_frontend" in
container)
set -- "$@" --cpus "$builder_cpus" --memory "$builder_memory"
;;
docker|podman)
set -- "$@" \
--cpu-period 100000 \
--cpu-quota "$((builder_cpus * 100000))" \
--memory "$builder_memory"
;;
esac
set -- "$@" \
--platform "$builder_platform" \
--target artifacts \
--build-arg "MOUSE_BUILD_JOBS=$build_jobs" \
--build-arg "MOUSE_ROOTFS_STAGE=$rootfs_stage" \
--output "type=local,dest=$export_dir" \
.
"$container_cli" "$@"
)
artifacts_dir=$export_dir
if [ ! -f "$artifacts_dir/vmlinuz-virt" ]; then
platform_dir=$(printf '%s\n' "$builder_platform" | tr / _)
artifacts_dir="$export_dir/out.tar/$platform_dir"
fi
for artifact in vmlinuz-virt ovmf-code.fd; do
[ -f "$artifacts_dir/$artifact" ] || \
die "container build did not export $artifact"
install -m 0644 "$artifacts_dir/$artifact" "$output_dir/$artifact"
done
artifact=mouse-disk.img
[ -f "$artifacts_dir/$artifact.gz" ] || \
die "container build did not export $artifact.gz"
artifact_temp="$output_dir/.$artifact.tmp"
gzip -dc "$artifacts_dir/$artifact.gz" >"$artifact_temp"
chmod 0644 "$artifact_temp"
mv -f "$artifact_temp" "$output_dir/$artifact"
artifact_temp=
for artifact in \
vmlinuz-virt \
ovmf-code.fd \
mouse-disk.img
do
printf '%s\n' "exported $output_dir/$artifact"
done
+29
View File
@@ -0,0 +1,29 @@
#!/bin/sh
set -eu
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
repo_dir=$(dirname "$script_dir")
build_dir=${MOUSE_BUILD_DIR:-"$repo_dir/build"}
kernel_version=$(cat "$build_dir/kernel/release")
kernel_source="$build_dir/kernel/linux-6.18.35"
kernel_output="$build_dir/kernel/output"
module_output="$build_dir/kernel/module-test"
module_root="$build_dir/kernel/test-modules"
rm -rf "$module_output" "$module_root"
mkdir -p "$module_output"
cp "$repo_dir/tests/fixtures/kernel-module/mouse_test.c" \
"$repo_dir/tests/fixtures/kernel-module/Makefile" \
"$module_output/"
make -s -C "$kernel_source" \
O="$kernel_output" \
ARCH=x86_64 \
LLVM=1 \
M="$module_output" \
modules
cp -R "$build_dir/kernel/modules" "$module_root"
install -d -m 0755 \
"$module_root/usr/local/lib/modules/$kernel_version/extra"
install -m 0644 "$module_output/mouse_test.ko" \
"$module_root/usr/local/lib/modules/$kernel_version/extra/mouse_test.ko"
+80
View File
@@ -0,0 +1,80 @@
#!/bin/sh
set -eu
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
repo_dir=$(dirname "$script_dir")
build_dir=${MOUSE_BUILD_DIR:-"$repo_dir/build"}
source_dir=${MOUSE_SOURCE_DIR:-"$build_dir/sources"}
kernel_version=6.18.35
kernel_archive="$source_dir/linux-$kernel_version.tar.xz"
kernel_source="$build_dir/kernel/linux-$kernel_version"
kernel_output="$build_dir/kernel/output"
kernel_config="$repo_dir/base/kernel/x86_64.config"
jobs=${MOUSE_BUILD_JOBS:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf '1\n')}
if [ ! -f "$kernel_archive" ]; then
printf '%s\n' "missing $kernel_archive; run scripts/fetch-sources.sh first" >&2
exit 1
fi
if [ ! -f "$kernel_config" ]; then
printf '%s\n' "missing MOUSE kernel config: $kernel_config" >&2
exit 1
fi
mkdir -p "$build_dir/kernel"
rm -rf "$kernel_source" "$kernel_output"
tar -C "$build_dir/kernel" -xf "$kernel_archive"
mkdir -p "$kernel_output"
make -C "$kernel_source" \
O="$kernel_output" \
ARCH=x86_64 \
LLVM=1 \
KCONFIG_ALLCONFIG="$kernel_config" \
allnoconfig
while IFS= read -r requested || [ -n "$requested" ]; do
case "$requested" in
CONFIG_*=*) ;;
*) continue ;;
esac
if ! grep -Fqx "$requested" "$kernel_output/.config"; then
printf '%s\n' \
"kernel did not enable requested setting: $requested" >&2
exit 1
fi
done <"$kernel_config"
make -C "$kernel_source" \
O="$kernel_output" \
ARCH=x86_64 \
LLVM=1 \
KBUILD_BUILD_USER=mouse \
KBUILD_BUILD_HOST=mouse \
-j"$jobs" \
bzImage modules
kernel_release=$(make -s -C "$kernel_source" \
O="$kernel_output" \
ARCH=x86_64 \
LLVM=1 \
kernelrelease)
module_root="$build_dir/kernel/modules"
rm -rf "$module_root"
make -s -C "$kernel_source" \
O="$kernel_output" \
ARCH=x86_64 \
LLVM=1 \
INSTALL_MOD_PATH="$module_root" \
modules_install
printf '%s\n' "$kernel_release" >"$build_dir/kernel/release"
install -m 0644 "$kernel_output/.config" \
"$module_root/lib/modules/$kernel_release/config"
install -m 0644 "$kernel_output/Module.symvers" \
"$module_root/lib/modules/$kernel_release/Module.symvers"
install -m 0644 \
"$kernel_output/arch/x86/boot/bzImage" \
"$build_dir/vmlinuz-virt"
printf '%s\n' \
"built Linux $kernel_release with built-in btrfs and loadable-module support"
+89
View File
@@ -0,0 +1,89 @@
#!/bin/sh
set -eu
archive=${MOUSE_PKGSRC_ARCHIVE:-/usr/share/mouse/pkgsrc-2026Q2.tar.gz}
patch_file=${MOUSE_PKGSRC_PATCH:-/usr/share/mouse/pkgsrc-bootstrap-mouse.patch}
source_parent=/usr/local/src
pkgsrc=$source_parent/pkgsrc
mk_conf=/usr/local/etc/mk.conf
build_busybox=/usr/libexec/mouse-build-busybox
fail() {
printf '%s\n' "build-pkgsrc-seed: $*" >&2
exit 1
}
[ "$(id -u)" -eq 0 ] || fail "must run as root"
[ -f "$archive" ] || fail "missing pinned pkgsrc archive: $archive"
[ -f "$patch_file" ] || fail "missing MOUSE pkgsrc bootstrap patch: $patch_file"
[ -x "$build_busybox" ] || fail "missing build-only copy helper"
[ ! -e /usr/local/bin/bmake ] || fail "pkgsrc seed root is not empty"
[ ! -e /var/db/pkg ] || fail "pkgsrc package database already exists"
mkdir -p \
"$source_parent" \
/usr/local/etc/rc.d \
/tmp \
/var/cache/pkgsrc/distfiles \
/var/cache/pkgsrc/packages
chmod 1777 /tmp
tar -xzf "$archive" -C "$source_parent"
[ -x "$pkgsrc/bootstrap/bootstrap" ] || fail "archive did not produce a pkgsrc tree"
[ -f "$pkgsrc/mk/init/bsd.init.mk" ] || fail "pkgsrc tree lacks init-system support"
patch -d "$pkgsrc" -p1 <"$patch_file"
grep -Fq 'bootstrap_awk=/usr/bin/awk' "$pkgsrc/bootstrap/bootstrap" ||
fail "pkgsrc tree lacks the MOUSE World awk policy"
# Apple Rosetta reports ENOSYS for the copy operation used by MOUSE's static
# cp. Use an amd64 build-only applet while pkgsrc populates its work directory;
# only /usr/local and /var/db/pkg leave this stage.
mv /bin/cp /bin/cp.mouse
ln -s "$build_busybox" /bin/cp
(
cd "$pkgsrc/bootstrap"
env \
AWK=/usr/bin/awk \
CC=/usr/bin/cc \
CXX=/usr/bin/c++ \
SH=/bin/sh \
./bootstrap \
--compiler clang \
--make-jobs "${MOUSE_BUILD_JOBS:-1}" \
--pkgdbdir /var/db/pkg \
--prefer-pkgsrc yes \
--prefix /usr/local \
--sysconfbase /usr/local/etc \
--sysconfdir /usr/local/etc \
--varbase /var
)
rm /bin/cp
mv /bin/cp.mouse /bin/cp
rm "$build_busybox"
cat >>"$mk_conf" <<'EOF'
# MOUSE pkgsrc policy
PKGSRC_COMPILER= clang
LOCALBASE= /usr/local
PKG_SYSCONFBASE= /usr/local/etc
PKG_DBDIR= /var/db/pkg
VARBASE= /var
DISTDIR= /var/cache/pkgsrc/distfiles
PACKAGES= /var/cache/pkgsrc/packages
INIT_SYSTEM= rc.d
RCD_SCRIPTS_DIR= /usr/local/etc/rc.d
PKG_RCD_SCRIPTS= YES
PREFER_PKGSRC= yes
EOF
ln -s /etc/rc.subr /usr/local/etc/rc.subr
rm -rf "$pkgsrc/bootstrap/work"
[ -x /usr/local/bin/bmake ] || fail "bootstrap did not install bmake"
[ -x /usr/local/sbin/pkg_info ] || fail "bootstrap did not install pkg_info"
[ -d /var/db/pkg ] || fail "bootstrap did not create the package database"
grep -Eq '^TOOLS_PLATFORM\.awk\?=[[:space:]]*/usr/bin/awk$' "$mk_conf" ||
fail "bootstrap did not preserve the World awk policy"
printf '%s\n' "built pkgsrc 2026Q2 seed"
+197
View File
@@ -0,0 +1,197 @@
#!/bin/sh
set -eu
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
repo_dir=$(dirname "$script_dir")
lock_dir="$repo_dir/release"
temporary=$(mktemp -d "${TMPDIR:-/tmp}/mouse-source-lock-check.XXXXXX")
cleanup() {
rm -rf "$temporary"
}
trap cleanup EXIT HUP INT TERM
MOUSE_VALIDATE_ONLY=1 "$script_dir/fetch-sources.sh"
awk 'NF && $1 !~ /^#/ { print $3 }' \
"$lock_dir/toolchain.lock" \
"$lock_dir/kernel.lock" \
"$lock_dir/facilities.lock" \
"$lock_dir/storage.lock" \
"$lock_dir/software.lock" |
LC_ALL=C sort -u >"$temporary/locked"
awk '$1 == "extract" { print $2 }' "$script_dir/static-base"/*.sh |
LC_ALL=C sort -u >"$temporary/referenced-archives"
sed -n 's/.*"$source_dir\/\([^"$]*\)".*/\1/p' \
"$script_dir/static-base"/*.sh \
"$script_dir/build-boot-disk.sh" |
LC_ALL=C sort -u >"$temporary/referenced-files"
cat "$temporary/referenced-archives" "$temporary/referenced-files" |
LC_ALL=C sort -u >"$temporary/referenced"
if ! diff -u "$temporary/locked" "$temporary/referenced"; then
printf '%s\n' "source locks do not match static-base component inputs" >&2
exit 1
fi
toolchain_count=$(awk 'NF && $1 !~ /^#/ { count++ } END { print count + 0 }' \
"$lock_dir/toolchain.lock")
kernel_count=$(awk 'NF && $1 !~ /^#/ { count++ } END { print count + 0 }' \
"$lock_dir/kernel.lock")
if [ "$toolchain_count" -ne 1 ] || \
! awk 'NF && $1 !~ /^#/ { print $3 }' "$lock_dir/toolchain.lock" |
grep -Eq '^llvm-project-[^/]+$'
then
printf '%s\n' "toolchain.lock must contain exactly the LLVM source" >&2
exit 1
fi
if [ "$kernel_count" -ne 1 ] || \
! awk 'NF && $1 !~ /^#/ { print $3 }' "$lock_dir/kernel.lock" |
grep -Eq '^linux-[^/]+$'
then
printf '%s\n' "kernel.lock must contain exactly the Linux source" >&2
exit 1
fi
pkgsrc_count=$(awk 'NF && $1 !~ /^#/ { count++ } END { print count + 0 }' \
"$lock_dir/pkgsrc.lock")
if [ "$pkgsrc_count" -ne 1 ] || \
! awk 'NF && $1 !~ /^#/ { print $3 }' "$lock_dir/pkgsrc.lock" |
grep -Eq '^pkgsrc-[0-9]{4}Q[1-4]\.tar\.gz$'
then
printf '%s\n' "pkgsrc.lock must contain exactly one stable pkgsrc archive" >&2
exit 1
fi
firmware_count=$(awk 'NF && $1 !~ /^#/ { count++ } END { print count + 0 }' \
"$lock_dir/firmware.lock")
if [ "$firmware_count" -ne 1 ] || \
! awk 'NF && $1 !~ /^#/ { print $3 }' "$lock_dir/firmware.lock" |
grep -Eq '^ovmf-[^/]+\.apk$'
then
printf '%s\n' "firmware.lock must contain exactly one OVMF package" >&2
exit 1
fi
storage_count=$(awk 'NF && $1 !~ /^#/ { count++ } END { print count + 0 }' \
"$lock_dir/storage.lock")
if [ "$storage_count" -lt 1 ] || \
! awk 'NF && $1 !~ /^#/ { print $3 }' "$lock_dir/storage.lock" |
grep -Eq '^btrfs-progs-[^/]+$'
then
printf '%s\n' "storage.lock must contain the btrfs-progs source" >&2
exit 1
fi
if awk 'NF && $1 !~ /^#/ { print $3 }' "$lock_dir/software.lock" |
grep -Eq '^(llvm-project|linux|btrfs-progs|less|mandoc|iproute2|nftables)-'
then
printf '%s\n' "software.lock contains a source owned by another cache domain" >&2
exit 1
fi
if awk 'NF && $1 !~ /^#/ { print $3 }' "$lock_dir/facilities.lock" |
grep -Eq '^(llvm-project|linux|btrfs-progs)-'
then
printf '%s\n' "facilities.lock contains a kernel, toolchain, or storage source" >&2
exit 1
fi
awk '
/^FROM toolchain AS kernel-builder$/ { in_kernel = 1; next }
/^FROM / && in_kernel { exit }
in_kernel { print }
' "$repo_dir/Containerfile" >"$temporary/kernel-stage"
if ! grep -Fq 'COPY --from=kernel-sources ' "$temporary/kernel-stage"; then
printf '%s\n' "kernel-builder does not import kernel-sources" >&2
exit 1
fi
if grep -Eq 'COPY --from=(toolchain|storage|software)-sources ' \
"$temporary/kernel-stage"
then
printf '%s\n' "kernel-builder imports an unrelated source domain" >&2
exit 1
fi
if grep -Fq 'tests/fixtures' "$temporary/kernel-stage"; then
printf '%s\n' "production kernel stage imports test fixtures" >&2
exit 1
fi
awk '
/^FROM kernel-builder AS kernel-ready$/ { in_marker = 1; next }
/^FROM / && in_marker { exit }
in_marker { print }
' "$repo_dir/Containerfile" >"$temporary/kernel-ready-stage"
if ! grep -Fq 'RUN install -m 0444 /dev/null /kernel-ready' \
"$temporary/kernel-ready-stage"
then
printf '%s\n' "kernel-ready does not create its completion marker" >&2
exit 1
fi
for stage_script in \
static-base-bootstrap:bootstrap.sh \
static-base-libraries:libraries-and-accounts.sh \
static-base-system:system-services.sh \
static-base-facilities:facilities.sh \
static-base-toolchain:native-toolchain.sh \
static-base-storage:storage.sh \
static-base-builder:finish.sh
do
stage=${stage_script%%:*}
component=${stage_script#*:}
awk -v heading="AS $stage" '
/^FROM / {
if (active) exit
active = index($0, heading) != 0
next
}
active { print }
' "$repo_dir/Containerfile" >"$temporary/$stage"
if ! grep -Fq \
"COPY mouse-src/scripts/static-base/$component " \
"$temporary/$stage"
then
printf '%s\n' \
"$stage does not own static-base component $component" >&2
exit 1
fi
if [ "$(grep -Fc \
"COPY mouse-src/scripts/static-base/$component " \
"$repo_dir/Containerfile")" -ne 1 ]
then
printf '%s\n' \
"$component must enter exactly one container cache stage" >&2
exit 1
fi
done
if ! grep -Fq \
'COPY --from=kernel-ready /kernel-ready /tmp/kernel-ready' \
"$temporary/static-base-bootstrap"
then
printf '%s\n' "static-base build does not wait for kernel completion" >&2
exit 1
fi
for production_stage in image-commands-builder rootfs-builder; do
awk -v heading="AS $production_stage" '
/^FROM / {
if (active) exit
active = index($0, heading) != 0
next
}
active { print }
' "$repo_dir/Containerfile" >"$temporary/$production_stage"
if grep -Fq 'tests/fixtures' "$temporary/$production_stage"; then
printf '%s\n' "$production_stage imports test fixtures" >&2
exit 1
fi
done
for test_stage in kernel-test-builder test-fixtures-builder rootfs-test-builder; do
if ! grep -Fq "AS $test_stage" "$repo_dir/Containerfile"; then
printf '%s\n' "missing isolated test stage: $test_stage" >&2
exit 1
fi
done
printf '%s\n' "source lock and cache-boundary checks passed"
+51
View File
@@ -0,0 +1,51 @@
#!/bin/sh
set -eu
if [ "$#" -ne 9 ]; then
printf '%s\n' \
"usage: configure-cross-toolchain.sh DEST SYSROOT CLANG CLANGXX LLD BUILTINS RUST_EH_PERSONALITY LLVM_AR LLVM_STRIP" >&2
exit 2
fi
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
repo_dir=$(dirname "$script_dir")
destination=$1
sysroot=$2
clang=$3
clangxx=$4
lld=$5
builtins=$6
rust_eh_personality=$7
llvm_ar=$8
llvm_strip=$9
target=x86_64-mouse-linux-musl
for requirement in \
"$clang" "$clangxx" "$lld" "$builtins" "$rust_eh_personality" "$llvm_ar" "$llvm_strip"
do
if [ ! -f "$requirement" ]; then
printf '%s\n' "missing cross-toolchain input: $requirement" >&2
exit 1
fi
done
install -d -m 0755 "$destination/bin"
install -m 0755 \
"$repo_dir/base/support/cross-compiler-driver" \
"$destination/bin/compiler-driver"
printf '%s\n' \
"$clang" \
"$clangxx" \
"$sysroot" \
"$lld" \
"$builtins" \
"$rust_eh_personality" \
>"$destination/compiler.conf"
ln -sf compiler-driver "$destination/bin/$target-cc"
ln -sf compiler-driver "$destination/bin/$target-c++"
ln -sf compiler-driver "$destination/bin/$target-bootstrap-c++"
ln -sf "$llvm_ar" "$destination/bin/$target-ar"
ln -sf "$llvm_ar" "$destination/bin/$target-ranlib"
ln -sf "$llvm_strip" "$destination/bin/$target-strip"
+114
View File
@@ -0,0 +1,114 @@
#!/bin/sh
set -eu
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
repo_dir=$(dirname "$script_dir")
build_dir=${MOUSE_BUILD_DIR:-"$repo_dir/build"}
source_dir=${MOUSE_SOURCE_DIR:-"$build_dir/sources"}
lock_dir="$repo_dir/release"
if [ "$#" -eq 0 ]; then
set -- \
"$lock_dir/toolchain.lock" \
"$lock_dir/kernel.lock" \
"$lock_dir/firmware.lock" \
"$lock_dir/facilities.lock" \
"$lock_dir/pkgsrc.lock" \
"$lock_dir/storage.lock" \
"$lock_dir/software.lock"
fi
seen=$(mktemp "${TMPDIR:-/tmp}/mouse-source-locks.XXXXXX")
cleanup() {
rm -f "$seen"
}
trap cleanup EXIT HUP INT TERM
for lock_file in "$@"; do
if [ ! -f "$lock_file" ]; then
printf '%s\n' "missing source lock: $lock_file" >&2
exit 1
fi
line_number=0
while read -r expected url filename extra; do
line_number=$((line_number + 1))
case "$expected" in
""|\#*) continue ;;
esac
if [ -n "${extra:-}" ]; then
printf '%s\n' "$lock_file:$line_number: expected three fields" >&2
exit 1
fi
if [ "${#expected}" -ne 64 ]; then
printf '%s\n' "$lock_file:$line_number: SHA-256 must have 64 characters" >&2
exit 1
fi
case "$expected" in
*[!0-9a-f]*)
printf '%s\n' "$lock_file:$line_number: SHA-256 must be lowercase hexadecimal" >&2
exit 1
;;
esac
case "$url" in
https://*) ;;
*)
printf '%s\n' "$lock_file:$line_number: source URL must use HTTPS" >&2
exit 1
;;
esac
case "$filename" in
""|.|..|*/*)
printf '%s\n' "$lock_file:$line_number: invalid source filename: $filename" >&2
exit 1
;;
esac
if grep -Fqx "$filename" "$seen"; then
printf '%s\n' "$lock_file:$line_number: duplicate source filename: $filename" >&2
exit 1
fi
printf '%s\n' "$filename" >>"$seen"
done <"$lock_file"
done
if [ "${MOUSE_VALIDATE_ONLY:-0}" = 1 ]; then
exit 0
fi
mkdir -p "$source_dir"
for lock_file in "$@"; do
while read -r expected url filename extra; do
case "$expected" in
""|\#*) continue ;;
esac
destination="$source_dir/$filename"
if [ ! -f "$destination" ]; then
temporary="$destination.part"
printf '%s\n' "fetching $filename"
curl \
--fail \
--location \
--proto '=https' \
--tlsv1.2 \
--connect-timeout 20 \
--retry 4 \
--retry-all-errors \
"$url" \
-o "$temporary"
mv "$temporary" "$destination"
fi
if command -v sha256sum >/dev/null 2>&1; then
actual=$(sha256sum "$destination" | awk '{print $1}')
else
actual=$(shasum -a 256 "$destination" | awk '{print $1}')
fi
if [ "$actual" != "$expected" ]; then
printf '%s\n' "checksum mismatch: $filename" >&2
printf '%s\n' "expected: $expected" >&2
printf '%s\n' "actual: $actual" >&2
exit 1
fi
done <"$lock_file"
done
+152
View File
@@ -0,0 +1,152 @@
#!/bin/sh
set -eu
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
repo_dir=$(dirname "$script_dir")
build_dir=${MOUSE_BUILD_DIR:-"$repo_dir/build"}
boot_mode=${MOUSE_BOOT_MODE:-firmware}
kernel_args=${MOUSE_KERNEL_ARGS:-}
disk_image=${MOUSE_DISK_IMAGE:-"$build_dir/mouse-disk.img"}
disk_bus=${MOUSE_DISK_BUS:-virtio}
firmware=${MOUSE_FIRMWARE:-bios}
uefi_code=${MOUSE_UEFI_CODE:-"$build_dir/ovmf-code.fd"}
network=${MOUSE_NETWORK:-0}
network_backend=${MOUSE_NETWORK_BACKEND:-user}
network_device=${MOUSE_NETWORK_DEVICE:-virtio}
memory=${MOUSE_MEMORY:-512M}
if [ -n "${QEMU_SYSTEM_X86_64:-}" ]; then
qemu=$QEMU_SYSTEM_X86_64
else
qemu=qemu-system-x86_64
fi
case "$qemu" in
*/*)
if [ ! -x "$qemu" ]; then
printf '%s\n' "$qemu is not executable" >&2
exit 1
fi
;;
*)
if ! command -v "$qemu" >/dev/null 2>&1; then
printf '%s\n' "qemu-system-x86_64 is required to boot the image" >&2
exit 1
fi
;;
esac
if [ ! -f "$build_dir/vmlinuz-virt" ]; then
printf '%s\n' "run 'make image' before booting MOUSE" >&2
exit 1
fi
disk_drive="file=$disk_image,format=raw,if=none,id=mouse-root"
disk_controller=
case "$disk_bus" in
virtio)
disk_device="virtio-blk-pci,drive=mouse-root,disable-legacy=on"
;;
sata)
disk_controller="ich9-ahci,id=mouse-ahci"
disk_device="ide-hd,drive=mouse-root,bus=mouse-ahci.0"
;;
nvme)
disk_device="nvme,drive=mouse-root,serial=MOUSE0001"
;;
*)
printf '%s\n' "invalid MOUSE_DISK_BUS: $disk_bus" >&2
exit 2
;;
esac
set -- "$qemu" \
-machine q35,accel=tcg \
-cpu max \
-m "$memory" \
-no-reboot \
-display none \
-nographic \
-nic none
case "$firmware" in
bios) ;;
uefi)
if [ ! -f "$uefi_code" ]; then
printf '%s\n' "missing pinned UEFI firmware: $uefi_code" >&2
exit 1
fi
set -- "$@" \
-drive "if=pflash,format=raw,readonly=on,file=$uefi_code"
;;
*)
printf '%s\n' "invalid MOUSE_FIRMWARE: $firmware" >&2
exit 2
;;
esac
case "$boot_mode" in
firmware)
if [ ! -f "$disk_image" ]; then
printf '%s\n' "missing boot disk: $disk_image" >&2
exit 1
fi
set -- "$@" -drive "$disk_drive"
if [ -n "$disk_controller" ]; then
set -- "$@" -device "$disk_controller"
fi
set -- "$@" -device "$disk_device"
;;
disk)
if [ ! -f "$disk_image" ]; then
printf '%s\n' "missing root disk: $disk_image" >&2
exit 1
fi
kernel_args=${kernel_args:-"console=ttyS0 root=PARTUUID=4d4f5553-4500-4000-8000-000000000002 rootfstype=btrfs rw rootwait init=/sbin/cheesed panic=-1"}
set -- "$@" \
-kernel "$build_dir/vmlinuz-virt" \
-drive "$disk_drive"
if [ -n "$disk_controller" ]; then
set -- "$@" -device "$disk_controller"
fi
set -- "$@" -device "$disk_device"
;;
*)
printf '%s\n' "invalid MOUSE_BOOT_MODE: $boot_mode" >&2
exit 2
;;
esac
case "$network" in
0) ;;
1)
if ! "$qemu" -netdev help 2>&1 |
grep -Fxq "$network_backend"; then
printf '%s\n' \
"QEMU does not provide the requested network backend: $network_backend" \
"install a QEMU build with that backend or set MOUSE_NETWORK_BACKEND" >&2
exit 1
fi
case "$network_device" in
virtio) nic_device="virtio-net-pci,netdev=mouse-net,disable-legacy=on" ;;
e1000) nic_device="e1000,netdev=mouse-net" ;;
e1000e) nic_device="e1000e,netdev=mouse-net" ;;
*)
printf '%s\n' "invalid MOUSE_NETWORK_DEVICE: $network_device" >&2
exit 2
;;
esac
set -- "$@" \
-netdev "$network_backend,id=mouse-net" \
-device "$nic_device"
;;
*)
printf '%s\n' "invalid MOUSE_NETWORK: $network" >&2
exit 2
;;
esac
if [ "$boot_mode" = disk ]; then
set -- "$@" -append "$kernel_args"
fi
exec "$@"
+221
View File
@@ -0,0 +1,221 @@
#!/bin/sh
set -eu
component_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
. "$component_dir/common.sh"
rm -rf "$static_dir"
install -d -m 0755 \
"$source_build_dir" \
"$sysroot" \
"$base_root/bin" \
"$base_root/sbin" \
"$base_root/usr/bin" \
"$base_root/usr/sbin" \
"$tools_dir/bin"
# llvm-ar selects ranlib mode from argv[0]. This gives build systems a real
# ranlib frontend even on the Rust toolchain for macOS, which ships only the
# underlying multi-call binary.
ln -s "$llvm_ar" "$tools_dir/bin/llvm-ranlib"
llvm_ranlib="$tools_dir/bin/llvm-ranlib"
extract() {
tar -xf "$source_dir/$1" -C "$source_build_dir"
}
extract meson-1.9.1.tar.gz
extract ninja-1.13.1.tar.gz
extract pkgconf-2.5.1.tar.gz
extract linux-6.18.35.tar.xz
extract musl-1.2.6.tar.gz
extract zlib-1.3.2.tar.gz
extract libarchive-3.8.7.tar.xz
extract llvm-project-22.1.3.src.tar.xz
extract netbsd-curses-0.3.2.tar.gz
extract attr-2.5.2.tar.gz
extract acl-2.3.2.tar.gz
extract libxo-1.7.5.tar.gz
extract tcsh-6.24.16.tar.gz
extract chimerautils-15.0.3.tar.gz
extract openssl-3.5.7.tar.gz
extract util-linux-2.42.2.tar.xz
extract libxcrypt-4.5.2.tar.xz
extract shadow-4.19.4.tar.xz
extract dhcpcd-8b312918d8f1885d7fe8fcc03a7e06ae8a0314b4.tar.gz
extract make-4.4.1.tar.gz
extract cronie-1.7.2.tar.gz
extract openntpd-7.9p1.tar.gz
extract procps-ng-4.0.5.tar.xz
extract iputils-20250605.tar.gz
extract kmod-34.2.tar.xz
extract skalibs-2.15.1.0.tar.gz
extract mdevd-0.1.8.2.tar.gz
extract sysklogd-2.7.2.tar.gz
(
cd "$source_build_dir/ninja-1.13.1"
python3 configure.py --bootstrap
install -m 0755 ninja "$tools_dir/bin/ninja"
)
meson_source="$source_build_dir/meson-1.9.1/meson.py"
PATH="$tools_dir/bin:$PATH"
export PATH
python3 "$meson_source" setup \
"$source_build_dir/pkgconf-pkgconf-2.5.1/build" \
"$source_build_dir/pkgconf-pkgconf-2.5.1" \
--prefix="$tools_dir" \
--buildtype=release \
--default-library=static
python3 "$meson_source" compile -C "$source_build_dir/pkgconf-pkgconf-2.5.1/build"
python3 "$meson_source" install -C "$source_build_dir/pkgconf-pkgconf-2.5.1/build"
musl_cc="$clang --target=x86_64-unknown-linux-musl --ld-path=$lld"
musl_mulxc3="$static_dir/musl-mulxc3.o"
"$clang" \
--target=x86_64-unknown-linux-musl \
--sysroot="$sysroot" \
-fPIC \
-I"$source_build_dir/llvm-project-22.1.3.src/compiler-rt/lib/builtins" \
-c "$source_build_dir/llvm-project-22.1.3.src/compiler-rt/lib/builtins/mulxc3.c" \
-o "$musl_mulxc3"
(
cd "$source_build_dir/musl-1.2.6"
CC="$musl_cc" \
AR="$llvm_ar" \
RANLIB="$llvm_ranlib" \
./configure \
--target=x86_64 \
--prefix=/usr \
--syslibdir=/lib
make -s -j"$jobs" LIBCC="$builtins $musl_mulxc3"
make -s DESTDIR="$sysroot" LIBCC="$builtins $musl_mulxc3" install
)
make -s -C "$source_build_dir/linux-6.18.35" \
ARCH=x86 \
headers_install \
INSTALL_HDR_PATH="$sysroot/usr"
install -d -m 0755 "$sysroot/usr/include/sys"
install -m 0644 "$source_dir/cdefs.h" "$sysroot/usr/include/sys/cdefs.h"
install -m 0644 "$source_dir/queue.h" "$sysroot/usr/include/sys/queue.h"
install -m 0644 "$source_dir/tree.h" "$sysroot/usr/include/sys/tree.h"
install -m 0644 "$source_dir/error.h" "$sysroot/usr/include/error.h"
rust_eh_personality="$static_dir/rust-eh-personality.o"
"$clang" \
--target=x86_64-unknown-linux-musl \
--sysroot="$sysroot" \
-c "$repo_dir/base/support/rust-eh-personality.c" \
-o "$rust_eh_personality"
"$script_dir/configure-cross-toolchain.sh" \
"$cross_tools_dir" \
"$sysroot" \
"$clang" \
"$clangxx" \
"$lld" \
"$builtins" \
"$rust_eh_personality" \
"$llvm_ar" \
"$llvm_strip"
target=x86_64-mouse-linux-musl
cc="$cross_tools_dir/bin/$target-cc"
cxx="$cross_tools_dir/bin/$target-c++"
bootstrap_cxx="$cross_tools_dir/bin/$target-bootstrap-c++"
ar="$cross_tools_dir/bin/$target-ar"
ranlib="$cross_tools_dir/bin/$target-ranlib"
strip="$cross_tools_dir/bin/$target-strip"
llvm_runtimes_build="$source_build_dir/llvm-runtimes-build"
cmake \
-G Ninja \
-S "$source_build_dir/llvm-project-22.1.3.src/runtimes" \
-B "$llvm_runtimes_build" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_C_COMPILER="$cc" \
-DCMAKE_C_COMPILER_TARGET=x86_64-unknown-linux-musl \
-DCMAKE_CXX_COMPILER="$bootstrap_cxx" \
-DCMAKE_CXX_COMPILER_TARGET=x86_64-unknown-linux-musl \
-DCMAKE_AR="$ar" \
-DCMAKE_RANLIB="$ranlib" \
-DCMAKE_SYSTEM_NAME=Linux \
-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY \
-DLLVM_ENABLE_RUNTIMES="compiler-rt;libcxx;libcxxabi;libunwind" \
-DLLVM_ENABLE_PER_TARGET_RUNTIME_DIR=OFF \
-DCOMPILER_RT_BUILD_BUILTINS=ON \
-DCOMPILER_RT_BUILD_CRT=ON \
-DCOMPILER_RT_BUILD_GWP_ASAN=OFF \
-DCOMPILER_RT_BUILD_LIBFUZZER=OFF \
-DCOMPILER_RT_BUILD_MEMPROF=OFF \
-DCOMPILER_RT_BUILD_ORC=OFF \
-DCOMPILER_RT_BUILD_PROFILE=OFF \
-DCOMPILER_RT_BUILD_SANITIZERS=OFF \
-DCOMPILER_RT_BUILD_XRAY=OFF \
-DCOMPILER_RT_DEFAULT_TARGET_ONLY=ON \
-DLIBCXX_ENABLE_SHARED=OFF \
-DLIBCXX_ENABLE_STATIC=ON \
-DLIBCXX_ENABLE_STATIC_ABI_LIBRARY=OFF \
-DLIBCXX_ENABLE_EXPERIMENTAL_LIBRARY=OFF \
-DLIBCXX_INCLUDE_BENCHMARKS=OFF \
-DLIBCXX_INCLUDE_TESTS=OFF \
-DLIBCXX_HAS_MUSL_LIBC=ON \
-DLIBCXXABI_ENABLE_SHARED=OFF \
-DLIBCXXABI_ENABLE_STATIC=ON \
-DLIBCXXABI_HAS_CXA_THREAD_ATEXIT_IMPL=OFF \
-DLIBCXXABI_INCLUDE_TESTS=OFF \
-DLIBCXXABI_USE_LLVM_UNWINDER=ON \
-DLIBUNWIND_ENABLE_SHARED=OFF \
-DLIBUNWIND_ENABLE_STATIC=ON \
-DLIBUNWIND_INCLUDE_TESTS=OFF
cmake --build "$llvm_runtimes_build" -j "$jobs"
DESTDIR="$sysroot" cmake --install "$llvm_runtimes_build"
clang_runtime_dir="$sysroot/usr/lib/clang/22/lib/x86_64-unknown-linux-musl"
install -d -m 0755 "$clang_runtime_dir"
install -m 0644 \
"$sysroot/usr/lib/linux/libclang_rt.builtins-x86_64.a" \
"$clang_runtime_dir/libclang_rt.builtins.a"
for runtime_object in \
clang_rt.crtbegin-x86_64.o \
clang_rt.crtend-x86_64.o
do
install -m 0644 \
"$sysroot/usr/lib/linux/$runtime_object" \
"$clang_runtime_dir/$runtime_object"
done
install -m 0644 \
"$sysroot/usr/lib/linux/clang_rt.crtbegin-x86_64.o" \
"$sysroot/usr/lib/crtbeginT.o"
install -m 0644 \
"$sysroot/usr/lib/linux/clang_rt.crtend-x86_64.o" \
"$sysroot/usr/lib/crtend.o"
{
printf '%s\n' \
'[binaries]' \
"c = '$cc'" \
"cpp = '$cxx'" \
"ar = '$ar'" \
"strip = '$strip'" \
"pkg-config = '$tools_dir/bin/pkgconf'" \
'' \
'[host_machine]' \
"system = 'linux'" \
"cpu_family = 'x86_64'" \
"cpu = 'x86_64'" \
"endian = 'little'" \
'' \
'[built-in options]' \
"c_args = ['-O2']" \
"cpp_args = ['-O2']" \
"c_link_args = ['-static']" \
"cpp_link_args = ['-static']" \
"default_library = 'static'" \
"prefer_static = true"
} >"$cross_file"
+55
View File
@@ -0,0 +1,55 @@
#!/bin/sh
component_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
script_dir=$(dirname "$component_dir")
repo_dir=$(dirname "$script_dir")
build_dir=${MOUSE_BUILD_DIR:-"$repo_dir/build"}
source_dir=${MOUSE_SOURCE_DIR:-"$build_dir/sources"}
static_dir="$build_dir/static"
source_build_dir="$static_dir/src"
sysroot="$static_dir/sysroot"
base_root="$static_dir/rootfs"
tools_dir="$static_dir/tools"
cross_tools_dir="$static_dir/cross-tools"
jobs=${MOUSE_BUILD_JOBS:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf '2')}
case "$static_dir" in
"$repo_dir"/build/static) ;;
*)
printf '%s\n' "refusing to clear unexpected static build path: $static_dir" >&2
exit 1
;;
esac
target_libdir=$(rustc --print target-libdir --target x86_64-unknown-linux-musl)
builtins=$(find "$target_libdir" -maxdepth 1 -name 'libcompiler_builtins-*.rlib' -print | head -1)
clang=$(command -v clang)
clangxx=$(command -v clang++)
lld=$(command -v ld.lld)
llvm_ar=$(command -v llvm-ar)
llvm_strip=$(command -v llvm-strip)
build_triplet=$(cc -dumpmachine)
native_cc=cc
for requirement in \
"$clang" "$clangxx" "$lld" "$builtins" "$llvm_ar" "$llvm_strip"
do
if [ ! -f "$requirement" ]; then
printf '%s\n' "missing LLVM/Rust cross-build component: $requirement" >&2
exit 1
fi
done
llvm_ranlib="$tools_dir/bin/llvm-ranlib"
meson_source="$source_build_dir/meson-1.9.1/meson.py"
PATH="$tools_dir/bin:$PATH"
export PATH
target=x86_64-mouse-linux-musl
cc="$cross_tools_dir/bin/$target-cc"
cxx="$cross_tools_dir/bin/$target-c++"
bootstrap_cxx="$cross_tools_dir/bin/$target-bootstrap-c++"
ar="$cross_tools_dir/bin/$target-ar"
ranlib="$cross_tools_dir/bin/$target-ranlib"
strip="$cross_tools_dir/bin/$target-strip"
cross_file="$static_dir/chimerautils.cross"
+125
View File
@@ -0,0 +1,125 @@
#!/bin/sh
set -eu
component_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
. "$component_dir/common.sh"
for runtime_file in \
/lib/ld-musl-x86_64.so.1 \
/usr/lib/libc.so \
/usr/lib/crt1.o \
/usr/lib/Scrt1.o \
/usr/lib/crti.o \
/usr/lib/crtn.o \
/usr/lib/crtbeginT.o \
/usr/lib/crtend.o
do
if [ ! -f "$base_root$runtime_file" ]; then
printf '%s\n' "missing dynamic runtime file: $runtime_file" >&2
exit 1
fi
done
for root in "$sysroot" "$base_root"; do
ln -sf crtbeginT.o "$root/usr/lib/crtbegin.o"
ln -sf crtbeginT.o "$root/usr/lib/crtbeginS.o"
ln -sf crtend.o "$root/usr/lib/crtendS.o"
done
make_dynamic_compiler() {
wrapper=$1
compiler=$2
cat >"$wrapper" <<EOF
#!/bin/sh
set -eu
shared=no
for argument do
if [ "\$argument" = -shared ]; then
shared=yes
break
fi
done
if [ "\$shared" = no ]; then
exec "$compiler" "\$@"
fi
exec "$compiler" \
-nostdlib \
"$sysroot/usr/lib/crti.o" \
"$sysroot/usr/lib/crtbeginS.o" \
-L"$sysroot/usr/lib" \
"\$@" \
"$dynamic_builtins" \
-lc \
"$sysroot/usr/lib/crtendS.o" \
"$sysroot/usr/lib/crtn.o"
EOF
chmod 0755 "$wrapper"
}
dynamic_cc="$cross_tools_dir/bin/x86_64-mouse-linux-musl-dynamic-cc"
dynamic_cxx="$cross_tools_dir/bin/x86_64-mouse-linux-musl-dynamic-c++"
dynamic_builtins="$sysroot/usr/lib/clang/22/lib/x86_64-unknown-linux-musl/libclang_rt.builtins.a"
if [ ! -f "$dynamic_builtins" ]; then
printf '%s\n' "missing compiler-rt builtins: $dynamic_builtins" >&2
exit 1
fi
make_dynamic_compiler "$dynamic_cc" "$cc"
make_dynamic_compiler "$dynamic_cxx" "$bootstrap_cxx"
shared_runtimes_build="$source_build_dir/llvm-shared-runtimes-build"
cmake \
-G Ninja \
-S "$source_build_dir/llvm-project-22.1.3.src/runtimes" \
-B "$shared_runtimes_build" \
-DCMAKE_BUILD_TYPE=MinSizeRel \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_C_COMPILER="$dynamic_cc" \
-DCMAKE_C_COMPILER_TARGET=x86_64-unknown-linux-musl \
-DCMAKE_CXX_COMPILER="$dynamic_cxx" \
-DCMAKE_CXX_COMPILER_TARGET=x86_64-unknown-linux-musl \
-DCMAKE_AR="$ar" \
-DCMAKE_RANLIB="$ranlib" \
-DCMAKE_SYSTEM_NAME=Linux \
-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY \
-DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi;libunwind" \
-DLLVM_ENABLE_PER_TARGET_RUNTIME_DIR=OFF \
-DLIBCXX_ENABLE_SHARED=ON \
-DLIBCXX_ENABLE_STATIC=OFF \
-DLIBCXX_HAS_MUSL_LIBC=ON \
-DLIBCXX_HAS_ATOMIC_LIB=NO \
-DLIBCXX_INCLUDE_BENCHMARKS=OFF \
-DLIBCXX_INCLUDE_TESTS=OFF \
-DLIBCXX_USE_COMPILER_RT=ON \
-DLIBCXXABI_ENABLE_SHARED=ON \
-DLIBCXXABI_ENABLE_STATIC=OFF \
-DLIBCXXABI_HAS_CXA_THREAD_ATEXIT_IMPL=OFF \
-DLIBCXXABI_INCLUDE_TESTS=OFF \
-DLIBCXXABI_USE_COMPILER_RT=ON \
-DLIBCXXABI_USE_LLVM_UNWINDER=ON \
-DLIBUNWIND_ENABLE_SHARED=ON \
-DLIBUNWIND_ENABLE_STATIC=OFF \
-DLIBUNWIND_INCLUDE_TESTS=OFF \
-DLIBUNWIND_USE_COMPILER_RT=ON
cmake --build "$shared_runtimes_build" -j "$jobs"
DESTDIR="$sysroot" cmake --install "$shared_runtimes_build"
cp -R "$sysroot/usr/lib/." "$base_root/usr/lib/"
cat >"$base_root/usr/bin/clang.cfg" <<'EOF'
--target=x86_64-unknown-linux-musl
--sysroot=/
--ld-path=/usr/bin/ld.lld
--rtlib=compiler-rt
--unwindlib=libunwind
EOF
cat >"$base_root/usr/bin/clang++.cfg" <<'EOF'
--target=x86_64-unknown-linux-musl
--sysroot=/
--ld-path=/usr/bin/ld.lld
-stdlib=libc++
--rtlib=compiler-rt
--unwindlib=libunwind
-lc++abi
EOF
+352
View File
@@ -0,0 +1,352 @@
#!/bin/sh
set -eu
component_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
. "$component_dir/common.sh"
extract() {
tar -xf "$source_dir/$1" -C "$source_build_dir"
}
extract less-704.tar.gz
extract mandoc-1.14.6.tar.gz
extract file-5.47.tar.gz
extract gzip-1.14.tar.xz
extract bzip2-1.0.8.tar.gz
extract xz-5.8.3.tar.bz2
extract iproute2-7.1.0.tar.xz
extract libmnl-1.0.5.tar.bz2
extract libnftnl-1.3.1.tar.xz
extract nftables-1.1.6.tar.xz
pkgconfig_path="$sysroot/usr/lib/pkgconfig:$sysroot/usr/share/pkgconfig"
install_man_page() {
page=$1
name=${2:-${page##*/}}
section=${name##*.}
install -d -m 0755 "$base_root/usr/share/man/man$section"
install -m 0644 "$page" "$base_root/usr/share/man/man$section/$name"
}
install_command_man_page() {
tree=$1
command=$2
page=$(find "$tree" -type f -name "$command.[1-9]" -print | head -1)
if [ -n "$page" ]; then
install_man_page "$page"
fi
}
(
cd "$source_build_dir/less-704"
CC="$cc" \
AR="$ar" \
RANLIB="$ranlib" \
LIBS="-lcurses -lterminfo" \
./configure \
--build="$build_triplet" \
--host=x86_64-linux-musl \
--prefix=/usr \
--sysconfdir=/etc \
--with-regex=posix
make -s -j"$jobs" less lesskey lessecho
install -m 0755 less lesskey lessecho "$base_root/usr/bin/"
)
install_man_page "$source_build_dir/less-704/less.nro" less.1
install_man_page "$source_build_dir/less-704/lesskey.nro" lesskey.1
install_man_page "$source_build_dir/less-704/lessecho.nro" lessecho.1
(
cd "$source_build_dir/bzip2-1.0.8"
make -s -j"$jobs" \
CC="$cc" \
AR="$ar" \
RANLIB="$ranlib" \
CFLAGS="-O2" \
LDFLAGS="-static" \
bzip2 bzip2recover libbz2.a
install -m 0644 bzlib.h "$sysroot/usr/include/bzlib.h"
install -m 0644 libbz2.a "$sysroot/usr/lib/libbz2.a"
install -m 0755 bzip2 bzip2recover "$base_root/usr/bin/"
)
ln -s bzip2 "$base_root/usr/bin/bunzip2"
ln -s bzip2 "$base_root/usr/bin/bzcat"
for page in bzip2.1 bzgrep.1 bzmore.1 bzdiff.1; do
install_man_page "$source_build_dir/bzip2-1.0.8/$page"
done
(
cd "$source_build_dir/xz-5.8.3"
CC="$cc" \
AR="$ar" \
RANLIB="$ranlib" \
./configure \
--build="$build_triplet" \
--host=x86_64-linux-musl \
--prefix=/usr \
--disable-shared \
--enable-static \
--disable-nls \
--disable-doc \
--disable-xzdec \
--disable-lzmadec \
--disable-lzmainfo
make -s -j"$jobs" -C src/liblzma
make -s -C src/liblzma DESTDIR="$sysroot" install
make -s -j"$jobs" -C src/xz xz
install -m 0755 src/xz/xz "$base_root/usr/bin/xz"
)
for command in unxz xzcat lzma unlzma lzcat; do
ln -s xz "$base_root/usr/bin/$command"
done
for page in xz.1; do
install_man_page "$source_build_dir/xz-5.8.3/src/xz/$page"
done
(
cd "$source_build_dir/gzip-1.14"
CC="$cc" \
AR="$ar" \
RANLIB="$ranlib" \
gl_cv_func_getcwd_path_max=yes \
./configure \
--build="$build_triplet" \
--host=x86_64-linux-musl \
--prefix=/usr \
--disable-nls
make -s -j"$jobs"
install -m 0755 gzip "$base_root/usr/bin/gzip"
for script in gunzip zcat zcmp zdiff zgrep zless zmore; do
install -m 0755 "$script" "$base_root/usr/bin/$script"
done
)
for page in gzip.1 gunzip.1 zcat.1 zcmp.1 zdiff.1 zgrep.1 zless.1 zmore.1; do
install_man_page "$source_build_dir/gzip-1.14/$page"
done
(
cd "$source_build_dir/file-5.47"
CC="$cc" \
AR="$ar" \
RANLIB="$ranlib" \
PKG_CONFIG="$tools_dir/bin/pkgconf" \
PKG_CONFIG_LIBDIR="$pkgconfig_path" \
PKG_CONFIG_SYSROOT_DIR="$sysroot" \
./configure \
--build="$build_triplet" \
--host=x86_64-linux-musl \
--prefix=/usr \
--disable-shared \
--enable-static \
--disable-zstdlib \
--disable-lzlib \
--disable-libseccomp
make -s -C src magic.h
make -s -j"$jobs" -C src file
make -s -C magic magic.mgc
install -m 0755 src/file "$base_root/usr/bin/file"
install -d -m 0755 "$base_root/usr/share/misc"
install -m 0644 magic/magic.mgc "$base_root/usr/share/misc/magic.mgc"
)
install_man_page "$source_build_dir/file-5.47/doc/file.man" file.1
install_man_page "$source_build_dir/file-5.47/doc/magic.man" magic.5
unzip_source="$source_build_dir/libarchive-unzip-3.8.7"
install -d -m 0755 "$unzip_source"
tar -xf "$source_dir/libarchive-3.8.7.tar.xz" \
-C "$unzip_source" \
--strip-components=1
(
cd "$unzip_source"
CC="$cc" \
AR="$ar" \
RANLIB="$ranlib" \
PKG_CONFIG="$tools_dir/bin/pkgconf" \
PKG_CONFIG_LIBDIR="$pkgconfig_path" \
PKG_CONFIG_SYSROOT_DIR="$sysroot" \
./configure \
--build="$build_triplet" \
--host=x86_64-linux-musl \
--prefix=/usr \
--disable-shared \
--enable-static \
--disable-bsdtar \
--disable-bsdcat \
--disable-bsdcpio \
--enable-bsdunzip=static \
--disable-acl \
--disable-xattr \
--without-libb2 \
--without-iconv \
--without-lz4 \
--without-zstd \
--without-xml2 \
--without-expat \
--without-openssl \
--without-cng
make -s -j"$jobs" bsdunzip
install -m 0755 bsdunzip "$base_root/usr/bin/unzip"
)
install_man_page "$unzip_source/unzip/bsdunzip.1" unzip.1
mandoc_source="$source_build_dir/mandoc-1.14.6"
cat >"$mandoc_source/configure.local" <<EOF
CC="$cc"
AR="$ar"
CFLAGS="-O2"
LDFLAGS="-static"
STATIC="-static"
PREFIX="/usr"
BINDIR="/usr/bin"
SBINDIR="/usr/sbin"
MANDIR="/usr/share/man"
MANPATH_BASE="/usr/share/man:/usr/local/man"
MANPATH_DEFAULT="/usr/share/man:/usr/local/man"
OSENUM=MANDOC_OS_OTHER
OSNAME="MOUSE"
BINM_PAGER="less"
LN="ln -sf"
EOF
(
cd "$mandoc_source"
./configure
make -s -j"$jobs"
make -s DESTDIR="$base_root" base-install
)
iproute_source="$source_build_dir/iproute2-7.1.0"
(
cd "$iproute_source"
CC="$cc" \
AR="$ar" \
YACC=byacc \
PKG_CONFIG="$tools_dir/bin/pkgconf" \
PKG_CONFIG_LIBDIR="$pkgconfig_path" \
PKG_CONFIG_SYSROOT_DIR="$sysroot" \
sh ./configure
# Enter component directories through the top-level makefile. It defines
# and exports iproute2's path constants, feature macros, and UAPI includes;
# invoking the sub-makes directly silently drops them.
make -s -j"$jobs" \
CC="$cc" \
AR="$ar" \
YACC=byacc \
SHARED_LIBS=n \
LDFLAGS="-static" \
SUBDIRS=lib
make -s -j"$jobs" \
CC="$cc" \
AR="$ar" \
YACC=byacc \
SHARED_LIBS=n \
LDFLAGS="-static" \
SUBDIRS="ip tc bridge misc"
for target in ip/ip tc/tc bridge/bridge misc/ss; do
install -m 0755 "$target" "$base_root/sbin/${target##*/}"
done
)
for command in ip tc bridge ss; do
install_command_man_page "$iproute_source/man" "$command"
done
(
cd "$source_build_dir/libmnl-1.0.5"
CC="$cc" AR="$ar" RANLIB="$ranlib" ./configure \
--build="$build_triplet" \
--host=x86_64-linux-musl \
--prefix=/usr \
--disable-shared \
--enable-static
make -s -j"$jobs"
make -s DESTDIR="$sysroot" install
)
(
cd "$source_build_dir/libnftnl-1.3.1"
CC="$cc" \
AR="$ar" \
RANLIB="$ranlib" \
PKG_CONFIG="$tools_dir/bin/pkgconf" \
PKG_CONFIG_LIBDIR="$pkgconfig_path" \
PKG_CONFIG_SYSROOT_DIR="$sysroot" \
./configure \
--build="$build_triplet" \
--host=x86_64-linux-musl \
--prefix=/usr \
--disable-shared \
--enable-static
make -s -j"$jobs"
make -s DESTDIR="$sysroot" install
)
# These are static-only cross-build dependencies. Libtool archives retain the
# target's /usr/lib prefix and make nftables' host libtool look outside the
# sysroot; the real .a archives and pkg-config metadata are sufficient.
rm -f "$sysroot/usr/lib/libmnl.la" "$sysroot/usr/lib/libnftnl.la"
(
cd "$source_build_dir/nftables-1.1.6"
patch -p1 <"$repo_dir/base/patches/nftables-posix-configure.patch"
CC="$cc" \
AR="$ar" \
RANLIB="$ranlib" \
YACC=byacc \
PKG_CONFIG="$tools_dir/bin/pkgconf" \
PKG_CONFIG_LIBDIR="$pkgconfig_path" \
PKG_CONFIG_SYSROOT_DIR="$sysroot" \
./configure \
--build="$build_triplet" \
--host=x86_64-linux-musl \
--prefix=/usr \
--sbindir=/sbin \
--disable-shared \
--enable-static \
--disable-man-doc \
--with-mini-gmp \
--without-cli \
--without-xtables \
--without-json \
--without-unitdir
make -s -j"$jobs" src/nft
install -m 0755 src/nft "$base_root/sbin/nft"
)
install_man_page "$source_build_dir/nftables-1.1.6/doc/nft.8"
for command in \
blkid dmesg fdisk findmnt flock losetup lsblk mount sfdisk swapoff swapon umount wipefs
do
install_command_man_page "$source_build_dir/util-linux-2.42.2" "$command"
done
for command in free pgrep pkill ps sysctl top uptime vmstat watch; do
install_command_man_page "$source_build_dir/procps-ng-4.0.5" "$command"
done
install_command_man_page "$source_build_dir/iputils-20250605" ping
while IFS= read -r page; do
filename=${page##*/}
command=${filename%.*}
if [ -x "$base_root/bin/$command" ] ||
[ -x "$base_root/sbin/$command" ] ||
[ -x "$base_root/usr/bin/$command" ] ||
[ -x "$base_root/usr/sbin/$command" ]
then
install_man_page "$page"
fi
done <<EOF
$(find "$source_build_dir/chimerautils-15.0.3" -type f -name '*.[1-9]' -print | LC_ALL=C sort)
EOF
host_mandoc_source="$static_dir/host-mandoc-1.14.6"
install -d -m 0755 "$host_mandoc_source"
tar -xf "$source_dir/mandoc-1.14.6.tar.gz" \
-C "$host_mandoc_source" \
--strip-components=1
cat >"$host_mandoc_source/configure.local" <<EOF
CC="$native_cc"
PREFIX="$tools_dir/mandoc-host"
EOF
(
cd "$host_mandoc_source"
./configure
make -s -j"$jobs" mandoc
install -d -m 0755 "$tools_dir/mandoc-host"
install -m 0755 mandoc "$tools_dir/mandoc-host/makewhatis"
)
+9
View File
@@ -0,0 +1,9 @@
#!/bin/sh
set -eu
component_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
. "$component_dir/common.sh"
printf '%s\n' '#!/bin/sh' 'exec /usr/bin/tput clear' >"$base_root/usr/bin/clear"
chmod 0755 "$base_root/usr/bin/clear"
printf '%s\n' "$base_root"
@@ -0,0 +1,263 @@
#!/bin/sh
set -eu
component_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
. "$component_dir/common.sh"
(
cd "$source_build_dir/zlib-1.3.2"
CC="$cc" \
AR="$ar" \
RANLIB="$ranlib" \
./configure \
--prefix=/usr \
--static
make -s -j"$jobs"
make -s DESTDIR="$sysroot" install
)
(
cd "$source_build_dir/libarchive-3.8.7"
CC="$cc" \
AR="$ar" \
RANLIB="$ranlib" \
CPPFLAGS="-I$sysroot/usr/include" \
LDFLAGS="-L$sysroot/usr/lib" \
PKG_CONFIG="$tools_dir/bin/pkgconf" \
PKG_CONFIG_LIBDIR="$sysroot/usr/lib/pkgconfig" \
PKG_CONFIG_SYSROOT_DIR="$sysroot" \
./configure \
--build="$build_triplet" \
--host=x86_64-linux-musl \
--prefix=/usr \
--disable-shared \
--enable-static \
--enable-bsdtar=static \
--disable-bsdcat \
--disable-bsdcpio \
--disable-bsdunzip \
--disable-acl \
--disable-xattr \
--without-bz2lib \
--without-libb2 \
--without-iconv \
--without-lz4 \
--without-lzma \
--without-zstd \
--without-xml2 \
--without-expat \
--without-openssl \
--without-cng
make -s -j"$jobs" bsdtar
install -m 0755 bsdtar "$base_root/usr/bin/tar"
)
(
cd "$source_build_dir/openssl-3.5.7"
CC="$cc" \
AR="$ar" \
RANLIB="$ranlib" \
./Configure \
linux-x86_64 \
--prefix=/usr \
--libdir=lib \
--openssldir=/etc/ssl \
no-afalgeng \
no-apps \
no-docs \
no-dso \
no-module \
no-secure-memory \
no-shared \
no-tests
make -s -j"$jobs" build_libs
make -s DESTDIR="$sysroot" install_sw
)
install -d -m 0755 "$base_root/etc/ssl"
install -m 0644 \
"$source_dir/cacert-2026-07-16.pem" \
"$base_root/etc/ssl/cert.pem"
(
cd "$source_build_dir/libxcrypt-4.5.2"
CC="$cc" \
AR="$ar" \
RANLIB="$ranlib" \
./configure \
--build="$build_triplet" \
--host=x86_64-linux-musl \
--prefix=/usr \
--disable-shared \
--enable-static \
--disable-obsolete-api \
--disable-symvers \
--enable-hashes=sha512crypt
make -s -j"$jobs"
make -s DESTDIR="$sysroot" install
)
(
cd "$source_build_dir/shadow-4.19.4"
CC="$cc" \
AR="$ar" \
RANLIB="$ranlib" \
PKG_CONFIG="$tools_dir/bin/pkgconf" \
PKG_CONFIG_LIBDIR="$sysroot/usr/lib/pkgconfig" \
PKG_CONFIG_SYSROOT_DIR="$sysroot" \
./configure \
--build="$build_triplet" \
--host=x86_64-linux-musl \
--prefix=/usr \
--disable-shared \
--enable-static \
--disable-logind \
--disable-nls \
--disable-shadowgrp \
--without-audit \
--without-libpam \
--without-selinux \
--without-acl \
--without-attr \
--without-libbsd \
--without-skey \
--without-tcb \
--without-nscd
make -s -j"$jobs" -C lib
shadow_commands="
groupadd groupdel groupmod login passwd su
useradd userdel usermod
"
MOUSE_CROSS_EXTRA_STATIC_LIBS="$sysroot/usr/lib/libcrypt.a" \
make -s -j"$jobs" -C src $shadow_commands
for command in $shadow_commands; do
mode=0755
destination="$base_root/usr/sbin"
case "$command" in
login) destination="$base_root/bin" ;;
passwd)
mode=4755
destination="$base_root/usr/bin"
;;
su)
mode=4755
destination="$base_root/bin"
;;
esac
install -m "$mode" "src/$command" "$destination/$command"
done
)
(
cd "$source_build_dir/dhcpcd-8b312918d8f1885d7fe8fcc03a7e06ae8a0314b4"
CC="$cc" \
AR="$ar" \
RANLIB="$ranlib" \
./configure \
--os=linux \
--target=x86_64-linux-musl \
--prefix=/usr \
--sbindir=/usr/bin \
--sysconfdir=/etc \
--libexecdir=/usr/libexec \
--dbdir=/var/lib/dhcpcd \
--rundir=/run \
--enable-static \
--disable-ipv6 \
--disable-auth \
--enable-privsep \
--privsepuser=dhcpcd \
--disable-ntp \
--without-udev \
--without-libpcap \
--without-openssl \
--with-hooks=
make -s -j"$jobs" -C src
install -m 0755 src/dhcpcd "$base_root/sbin/dhcpcd"
)
(
cd "$source_build_dir/netbsd-curses-0.3.2"
make -s -f GNUmakefile -j"$jobs" \
CC="$cc" \
HOSTCC="$native_cc" \
AR="$ar" \
RANLIB="$ranlib" \
CFLAGS="-O2" \
CPPFLAGS="-I. -I./libterminfo -DTERMINFO_COMPILE -DTERMINFO_DB -DTERMINFO_COMPAT" \
LDFLAGS="-static" \
PREFIX=/usr \
all-static
make -s -f GNUmakefile \
CC="$cc" \
HOSTCC="$native_cc" \
AR="$ar" \
RANLIB="$ranlib" \
CFLAGS="-O2" \
CPPFLAGS="-I. -I./libterminfo -DTERMINFO_COMPILE -DTERMINFO_DB -DTERMINFO_COMPAT" \
LDFLAGS="-static" \
PREFIX=/usr \
DESTDIR="$sysroot" \
install-static
)
build_autoconf_library() {
source_name=$1
build_target=$2
shift 2
(
cd "$source_build_dir/$source_name"
CC="$cc" \
AR="$ar" \
RANLIB="$ranlib" \
PKG_CONFIG="$tools_dir/bin/pkgconf" \
PKG_CONFIG_LIBDIR="$sysroot/usr/lib/pkgconfig" \
PKG_CONFIG_SYSROOT_DIR="$sysroot" \
./configure \
--build="$build_triplet" \
--host=x86_64-linux-musl \
--prefix=/usr \
--disable-shared \
--enable-static \
"$@"
make -s -j"$jobs" "$build_target"
make -s DESTDIR="$sysroot" \
install-libLTLIBRARIES \
install-data-local \
install-pkgconfDATA
)
}
build_autoconf_library attr-2.5.2 libattr.la
build_autoconf_library acl-2.3.2 libacl.la
# The projects' install-time EXPORT substitution uses a GNU sed word-boundary
# extension which macOS sed accepts but does not apply. Normalize the installed
# public declarations for consumers of the cross-built static libraries.
find "$sysroot/usr/include/attr" "$sysroot/usr/include/acl" \
"$sysroot/usr/include/sys/acl.h" \
-type f -exec perl -pi -e 's/\bEXPORT\b/extern/g' {} +
(
cd "$source_build_dir/libxo-1.7.5"
CC="$cc" \
AR="$ar" \
RANLIB="$ranlib" \
ac_cv_func_malloc_0_nonnull=yes \
ac_cv_func_realloc_0_nonnull=yes \
./configure \
--build="$build_triplet" \
--host=x86_64-linux-musl \
--prefix=/usr \
--disable-shared \
--enable-static \
--disable-libxo-options \
--disable-gettext \
--enable-text-only
make -s -C libxo -j"$jobs" libxo.la
make -s -C libxo DESTDIR="$sysroot" \
install-libLTLIBRARIES \
install-libxoincHEADERS
make -s DESTDIR="$sysroot" install-pkgconfigDATA
)
+159
View File
@@ -0,0 +1,159 @@
#!/bin/sh
set -eu
component_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
. "$component_dir/common.sh"
build_llvm() {
llvm_host_build="$source_build_dir/llvm-host-tools-build"
cmake \
-G Ninja \
-S "$source_build_dir/llvm-project-22.1.3.src/llvm" \
-B "$llvm_host_build" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER="$native_cc" \
-DCMAKE_CXX_COMPILER=c++ \
-DLLVM_ENABLE_PROJECTS=clang \
-DLLVM_TARGETS_TO_BUILD=X86 \
-DLLVM_INCLUDE_BENCHMARKS=OFF \
-DLLVM_INCLUDE_EXAMPLES=OFF \
-DLLVM_INCLUDE_TESTS=OFF \
-DCLANG_INCLUDE_TESTS=OFF
cmake --build "$llvm_host_build" -j "$jobs" \
--target llvm-tblgen clang-tblgen
llvm_target_build="$source_build_dir/llvm-target-build"
llvm_target_install="$static_dir/llvm-target-install"
cmake \
-G Ninja \
-S "$source_build_dir/llvm-project-22.1.3.src/llvm" \
-B "$llvm_target_build" \
-DCMAKE_BUILD_TYPE=MinSizeRel \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_C_COMPILER="$cc" \
-DCMAKE_CXX_COMPILER="$cxx" \
-DCMAKE_AR="$ar" \
-DCMAKE_RANLIB="$ranlib" \
-DCMAKE_SYSTEM_NAME=Linux \
-DCMAKE_SYSTEM_PROCESSOR=x86_64 \
-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY \
-DLLVM_TABLEGEN="$llvm_host_build/bin/llvm-tblgen" \
-DCLANG_TABLEGEN="$llvm_host_build/bin/clang-tblgen" \
-DLLVM_ENABLE_PROJECTS="clang;lld" \
-DLLVM_TARGETS_TO_BUILD=X86 \
-DLLVM_DEFAULT_TARGET_TRIPLE=x86_64-unknown-linux-musl \
-DLLVM_HOST_TRIPLE=x86_64-unknown-linux-musl \
-DLLVM_ENABLE_BINDINGS=OFF \
-DLLVM_ENABLE_FFI=OFF \
-DLLVM_ENABLE_LIBEDIT=OFF \
-DLLVM_ENABLE_LIBXML2=OFF \
-DLLVM_ENABLE_PIC=OFF \
-DLLVM_ENABLE_TERMINFO=OFF \
-DLLVM_ENABLE_ZLIB=OFF \
-DLLVM_ENABLE_ZSTD=OFF \
-DLLVM_INCLUDE_BENCHMARKS=OFF \
-DLLVM_INCLUDE_EXAMPLES=OFF \
-DLLVM_INCLUDE_TESTS=OFF \
-DLLVM_INCLUDE_UTILS=ON \
-DCLANG_ENABLE_ARCMT=OFF \
-DCLANG_ENABLE_STATIC_ANALYZER=OFF \
-DCLANG_INCLUDE_TESTS=OFF \
-DLLVM_DISTRIBUTION_COMPONENTS="clang;clang-resource-headers;lld;llvm-ar;llvm-nm;llvm-objdump;llvm-readelf;llvm-size;llvm-strings;llvm-strip"
DESTDIR="$llvm_target_install" \
cmake --build "$llvm_target_build" -j "$jobs" \
--target install-distribution
cp -R "$sysroot/usr/include" "$base_root/usr/"
cp -R "$sysroot/usr/lib/." "$base_root/usr/lib/"
cp -R "$sysroot/lib" "$base_root/"
cp -R "$llvm_target_install/usr/." "$base_root/usr/"
}
build_package_tools() {
pkgconf_target_build="$source_build_dir/pkgconf-pkgconf-2.5.1/build-target"
python3 "$meson_source" setup \
"$pkgconf_target_build" \
"$source_build_dir/pkgconf-pkgconf-2.5.1" \
--cross-file="$cross_file" \
--prefix=/usr \
--buildtype=release \
--default-library=static
python3 "$meson_source" compile -C "$pkgconf_target_build"
DESTDIR="$base_root" \
python3 "$meson_source" install -C "$pkgconf_target_build"
ln -s pkgconf "$base_root/usr/bin/pkg-config"
(
cd "$source_build_dir/make-4.4.1"
CC="$cc" \
AR="$ar" \
RANLIB="$ranlib" \
./configure \
--build="$build_triplet" \
--host=x86_64-linux-musl \
--prefix=/usr \
--disable-load \
--disable-nls \
--without-guile
make -j"$jobs"
make DESTDIR="$base_root" install
)
ln -s make "$base_root/usr/bin/gmake"
ln -s clang "$base_root/usr/bin/cc"
ln -s clang++ "$base_root/usr/bin/c++"
ln -s ld.lld "$base_root/usr/bin/ld"
ln -s llvm-ar "$base_root/usr/bin/ar"
ln -s llvm-nm "$base_root/usr/bin/nm"
ln -s llvm-objdump "$base_root/usr/bin/objdump"
ln -s llvm-readelf "$base_root/usr/bin/readelf"
ln -s llvm-ar "$base_root/usr/bin/llvm-ranlib"
cat >"$base_root/usr/bin/ranlib" <<'EOF'
#!/bin/sh
set -eu
# BSD ranlib accepts -t to refresh an existing archive index. LLVM's ranlib
# does not expose that option, but rebuilding the index is equivalent for the
# package build that requested it.
if [ "${1-}" = "-t" ]; then
shift
fi
exec /usr/bin/llvm-ranlib "$@"
EOF
chmod 0755 "$base_root/usr/bin/ranlib"
ln -s llvm-strip "$base_root/usr/bin/strip"
ln -s ../../lib/ld-musl-x86_64.so.1 "$base_root/usr/bin/ldd"
cat >"$base_root/usr/bin/clang.cfg" <<'EOF'
--target=x86_64-unknown-linux-musl
--sysroot=/
--ld-path=/usr/bin/ld.lld
--rtlib=compiler-rt
--unwindlib=libunwind
-static
EOF
cat >"$base_root/usr/bin/clang++.cfg" <<'EOF'
--target=x86_64-unknown-linux-musl
--sysroot=/
--ld-path=/usr/bin/ld.lld
-stdlib=libc++
--rtlib=compiler-rt
--unwindlib=libunwind
-lc++abi
-static
EOF
}
case "${MOUSE_NATIVE_TOOLCHAIN_PHASE:-all}" in
llvm) build_llvm ;;
package-tools) build_package_tools ;;
all)
build_llvm
build_package_tools
;;
*)
printf '%s\n' "invalid MOUSE_NATIVE_TOOLCHAIN_PHASE" >&2
exit 2
;;
esac
+107
View File
@@ -0,0 +1,107 @@
#!/bin/sh
set -eu
component_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
. "$component_dir/common.sh"
extract() {
tar -xf "$source_dir/$1" -C "$source_build_dir"
}
extract btrfs-progs-v7.0.tar.xz
extract zstd-1.5.7.tar.gz
install -d -m 0755 \
"$sysroot/usr/include" \
"$sysroot/usr/lib" \
"$sysroot/usr/lib/pkgconfig"
make -s -C "$source_build_dir/zstd-1.5.7/lib" -j"$jobs" \
CC="$cc" \
AR="$ar" \
RANLIB="$ranlib" \
libzstd.a
make -s -C "$source_build_dir/zstd-1.5.7/lib" \
PREFIX=/usr \
LIBDIR=/usr/lib \
PKGCONFIGDIR=/usr/lib/pkgconfig \
DESTDIR="$sysroot" \
install-static install-includes install-pc
make -s -C "$source_build_dir/zstd-1.5.7/programs" -j"$jobs" \
CC="$cc" \
AR="$ar" \
RANLIB="$ranlib" \
LDFLAGS="-static" \
ZSTD_LEGACY_SUPPORT=0 \
ZSTD_MULTITHREAD=0 \
zstd-release
install -m 0755 \
"$source_build_dir/zstd-1.5.7/programs/zstd" \
"$base_root/usr/bin/zstd"
ln -s zstd "$base_root/usr/bin/unzstd"
ln -s zstd "$base_root/usr/bin/zstdcat"
install -d -m 0755 "$base_root/usr/share/man/man1"
install -m 0644 \
"$source_build_dir/zstd-1.5.7/programs/zstd.1" \
"$base_root/usr/share/man/man1/zstd.1"
(
cd "$source_build_dir/util-linux-2.42.2"
CC="$cc" \
CXX="$cxx" \
AR="$ar" \
RANLIB="$ranlib" \
./configure \
--build="$build_triplet" \
--host=x86_64-linux-musl \
--prefix=/usr \
--disable-all-programs \
--enable-libblkid \
--enable-libuuid \
--disable-shared \
--enable-static
make -s -j"$jobs"
make -s DESTDIR="$sysroot" install
)
btrfs_source="$source_build_dir/btrfs-progs-v7.0"
pkgconfig_path="$sysroot/usr/lib/pkgconfig:$sysroot/usr/share/pkgconfig"
compiler_rt="$sysroot/usr/lib/clang/22/lib/x86_64-unknown-linux-musl/libclang_rt.builtins.a"
(
cd "$btrfs_source"
CC="$cc" \
AR="$ar" \
RANLIB="$ranlib" \
CFLAGS="-O2" \
LDFLAGS="-static" \
ac_cv_func_malloc_0_nonnull=yes \
ac_cv_func_realloc_0_nonnull=yes \
PKG_CONFIG="$tools_dir/bin/pkgconf" \
PKG_CONFIG_ALL_STATIC=1 \
PKG_CONFIG_LIBDIR="$pkgconfig_path" \
PKG_CONFIG_SYSROOT_DIR="$sysroot" \
./configure \
--build="$build_triplet" \
--host=x86_64-linux-musl \
--prefix=/usr \
--disable-backtrace \
--disable-documentation \
--disable-shared \
--enable-static \
--disable-convert \
--disable-lzo \
--disable-libudev \
--disable-python \
--disable-zoned \
--with-crypto=builtin
MOUSE_CROSS_EXTRA_STATIC_LIBS="$compiler_rt" \
make -s -j"$jobs" btrfs.static mkfs.btrfs.static
"$strip" btrfs.static mkfs.btrfs.static
install -m 0755 btrfs.static "$base_root/sbin/btrfs"
install -m 0755 mkfs.btrfs.static "$base_root/sbin/mkfs.btrfs"
)
ln -s btrfs "$base_root/sbin/btrfsck"
"$tools_dir/mandoc-host/makewhatis" "$base_root/usr/share/man"
+489
View File
@@ -0,0 +1,489 @@
#!/bin/sh
set -eu
component_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
. "$component_dir/common.sh"
patch -d "$source_build_dir/tcsh-TCSH6_24_16" -p1 \
<"$repo_dir/base/patches/tcsh-gethost-native.patch"
patch -d "$source_build_dir/chimerautils-15.0.3" -p1 \
<"$repo_dir/base/patches/chimerautils-native-cdefs.patch"
patch -d "$source_build_dir/chimerautils-15.0.3" -p1 \
<"$repo_dir/base/patches/chimerautils-scanflike.patch"
(
cd "$source_build_dir/tcsh-TCSH6_24_16"
CC="$cc" \
LIBS="-lcurses -lterminfo" \
./configure \
--build="$build_triplet" \
--host=x86_64-linux-musl \
--prefix=/usr \
--bindir=/bin \
--disable-nls-catalogs
make -j"$jobs"
install -m 0755 tcsh "$base_root/bin/tcsh"
)
util_linux_build="$source_build_dir/util-linux-build"
install -d -m 0755 "$util_linux_build"
(
cd "$util_linux_build"
CC="$cc" \
AR="$ar" \
RANLIB="$ranlib" \
CFLAGS="-O2" \
LDFLAGS="-static" \
PKG_CONFIG="$tools_dir/bin/pkgconf" \
PKG_CONFIG_LIBDIR="$sysroot/usr/lib/pkgconfig:$sysroot/usr/share/pkgconfig" \
PKG_CONFIG_SYSROOT_DIR="$sysroot" \
"$source_build_dir/util-linux-2.42.2/configure" \
--build="$build_triplet" \
--host=x86_64-linux-musl \
--prefix=/usr \
--disable-shared \
--enable-static \
--disable-nls \
--disable-all-programs \
--enable-libuuid \
--enable-libblkid \
--enable-libmount \
--enable-libsmartcols \
--enable-libfdisk \
--enable-agetty \
--enable-nologin \
--enable-setsid \
--enable-mount \
--enable-blkid \
--enable-fdisks \
--enable-losetup \
--enable-wipefs \
--enable-swapon \
--enable-lsblk \
--enable-findmnt \
--enable-flock \
--enable-dmesg \
--enable-static-programs=blkid,fdisk,losetup,mount,sfdisk,umount
make -s -j"$jobs" \
agetty nologin setsid mount umount blkid fdisk sfdisk losetup \
wipefs swapon swapoff lsblk findmnt flock dmesg
)
install_util_linux() {
command=$1
destination=$2
binary=$(find "$util_linux_build" -type f -name "$command" -perm -0100 -print | head -1)
if [ -z "$binary" ]; then
printf '%s\n' "missing util-linux build output: $command" >&2
exit 1
fi
install -m 0755 "$binary" "$destination"
}
install_util_linux agetty "$base_root/sbin/agetty"
install_util_linux nologin "$base_root/sbin/nologin"
install_util_linux setsid "$base_root/usr/bin/setsid"
install_util_linux mount "$base_root/sbin/mount"
install_util_linux umount "$base_root/sbin/umount"
install_util_linux blkid "$base_root/sbin/blkid"
install_util_linux fdisk "$base_root/sbin/fdisk"
install_util_linux sfdisk "$base_root/sbin/sfdisk"
install_util_linux losetup "$base_root/sbin/losetup"
install_util_linux wipefs "$base_root/sbin/wipefs"
install_util_linux swapon "$base_root/sbin/swapon"
install_util_linux swapoff "$base_root/sbin/swapoff"
install_util_linux lsblk "$base_root/usr/bin/lsblk"
install_util_linux findmnt "$base_root/usr/bin/findmnt"
install_util_linux flock "$base_root/usr/bin/flock"
install_util_linux dmesg "$base_root/sbin/dmesg"
chimerautils_build="$source_build_dir/chimerautils-15.0.3/build"
PKG_CONFIG_LIBDIR="$sysroot/usr/lib/pkgconfig:$sysroot/usr/share/pkgconfig" \
PKG_CONFIG_SYSROOT_DIR="$sysroot" \
python3 "$meson_source" setup \
"$chimerautils_build" \
"$source_build_dir/chimerautils-15.0.3" \
--cross-file="$cross_file" \
--prefix=/usr \
--buildtype=release \
-Dopenssl=enabled \
-Dlibedit=disabled \
-Dzlib=disabled \
-Dlzma=disabled \
-Dbzip2=disabled \
-Dzstd=disabled \
-Dpam=disabled \
-Dselinux=disabled \
-Dchimera_realpath=disabled
commands="
awk basename cat chmod chown chroot cksum cmp col colrm column comm
cp csplit cut date dd df diff diff3 dirname du echo env expand expr false
ed fetch find fmt fold gencat getopt grep head hexdump hostname id join kill
ln logger logname ls m4 md5 mesg mkdir mkfifo mknod mktemp mv nc nice nl nohup
nproc paste patch pathchk pr printenv printf pwd realpath renice rev rm rmdir script
sdiff sed seq sh sleep sort split stat stty sync tail tee time timeout touch tr true
truncate tsort tty ul uname unexpand uniq users vis wc whereis which who xargs yes
vi
"
targets="
src.freebsd/awk/awk
src.freebsd/coreutils/basename/basename
src.freebsd/coreutils/cat/cat
src.freebsd/coreutils/chmod/chmod
src.freebsd/coreutils/chown/chown
src.freebsd/coreutils/chroot/chroot
src.freebsd/coreutils/cksum/cksum
src.freebsd/diffutils/cmp/cmp
src.freebsd/miscutils/col/col
src.freebsd/miscutils/colrm/colrm
src.freebsd/miscutils/column/column
src.freebsd/coreutils/comm/comm
src.freebsd/coreutils/cp/cp
src.freebsd/coreutils/csplit/csplit
src.freebsd/coreutils/cut/cut
src.freebsd/coreutils/date/date
src.freebsd/coreutils/dd/dd
src.freebsd/coreutils/df/df
src.freebsd/diffutils/diff/diff
src.freebsd/diffutils/diff3/diff3
src.freebsd/coreutils/dirname/dirname
src.freebsd/coreutils/du/du
src.freebsd/ed/ed
src.freebsd/coreutils/echo/echo
src.freebsd/coreutils/env/env
src.freebsd/coreutils/expand/expand
src.freebsd/coreutils/expr/expr
src.freebsd/coreutils/false/false
src.freebsd/fetch/fetch
src.freebsd/findutils/find/find
src.freebsd/coreutils/fmt/fmt
src.freebsd/coreutils/fold/fold
src.freebsd/gencat/gencat
src.freebsd/miscutils/getopt/getopt
src.freebsd/grep/grep
src.freebsd/coreutils/head/head
src.freebsd/miscutils/hexdump/hexdump
src.freebsd/coreutils/hostname/hostname
src.freebsd/coreutils/id/id
src.freebsd/coreutils/join/join
src.freebsd/miscutils/kill/kill
src.freebsd/coreutils/ln/ln
src.freebsd/miscutils/logger/logger
src.freebsd/coreutils/logname/logname
src.freebsd/coreutils/ls/ls
src.freebsd/m4/m4
src.freebsd/coreutils/md5/md5
src.freebsd/miscutils/mesg/mesg
src.freebsd/coreutils/mkdir/mkdir
src.freebsd/coreutils/mkfifo/mkfifo
src.freebsd/coreutils/mknod/mknod
src.freebsd/coreutils/mktemp/mktemp
src.freebsd/coreutils/mv/mv
src.freebsd/netcat/nc
src.freebsd/coreutils/nice/nice
src.freebsd/coreutils/nl/nl
src.freebsd/coreutils/nohup/nohup
src.freebsd/coreutils/nproc/nproc
src.freebsd/nvi/vi
src.freebsd/coreutils/paste/paste
src.freebsd/patch/patch
src.freebsd/coreutils/pathchk/pathchk
src.freebsd/coreutils/pr/pr
src.freebsd/coreutils/printenv/printenv
src.freebsd/coreutils/printf/printf
src.freebsd/coreutils/pwd/pwd
src.freebsd/coreutils/realpath/realpath
src.freebsd/miscutils/renice/renice
src.freebsd/miscutils/rev/rev
src.freebsd/coreutils/rm/rm
src.freebsd/coreutils/rmdir/rmdir
src.freebsd/miscutils/script/script
src.freebsd/diffutils/sdiff/sdiff
src.freebsd/sed/sed
src.freebsd/coreutils/seq/seq
src.freebsd/sh/sh
src.freebsd/coreutils/sleep/sleep
src.freebsd/coreutils/sort/sort
src.freebsd/coreutils/split/split
src.freebsd/coreutils/stat/stat
src.freebsd/coreutils/stty/stty
src.freebsd/coreutils/sync/sync
src.freebsd/coreutils/tail/tail
src.freebsd/coreutils/tee/tee
src.freebsd/miscutils/time/time
src.freebsd/coreutils/timeout/timeout
src.freebsd/coreutils/touch/touch
src.freebsd/coreutils/tr/tr
src.freebsd/coreutils/true/true
src.freebsd/coreutils/truncate/truncate
src.freebsd/coreutils/tsort/tsort
src.freebsd/coreutils/tty/tty
src.freebsd/miscutils/ul/ul
src.freebsd/coreutils/uname/uname
src.freebsd/coreutils/unexpand/unexpand
src.freebsd/coreutils/uniq/uniq
src.freebsd/coreutils/users/users
src.freebsd/vis/vis
src.freebsd/coreutils/wc/wc
src.freebsd/miscutils/whereis/whereis
src.freebsd/which/which
src.freebsd/coreutils/who/who
src.freebsd/findutils/xargs/xargs
src.freebsd/coreutils/yes/yes
src.freebsd/coreutils/xinstall/xinstall
src.freebsd/coreutils/test/xtest
"
PKG_CONFIG_LIBDIR="$sysroot/usr/lib/pkgconfig:$sysroot/usr/share/pkgconfig" \
PKG_CONFIG_SYSROOT_DIR="$sysroot" \
python3 "$meson_source" compile -C "$chimerautils_build" $targets
for command in $commands; do
binary=$(find "$chimerautils_build" -type f -name "$command" -perm -0100 -print | head -1)
if [ -z "$binary" ]; then
printf '%s\n' "missing ChimeraUtils build output: $command" >&2
exit 1
fi
destination="$base_root/usr/bin"
case "$command" in
cat|chmod|chown|cp|date|dd|df|echo|hostname|kill|ln|ls|mkdir|mv|pwd|rm|rmdir|sed|sh|sleep|stty|sync|touch|uname)
destination="$base_root/bin"
;;
esac
install -m 0755 "$binary" "$destination/$command"
done
ln -s chown "$base_root/bin/chgrp"
ln -s vi "$base_root/usr/bin/nvi"
ln -s vi "$base_root/usr/bin/view"
(
cd "$source_build_dir/procps-ng-4.0.5"
CC="$cc" \
AR="$ar" \
RANLIB="$ranlib" \
ac_cv_func_malloc_0_nonnull=yes \
ac_cv_func_realloc_0_nonnull=yes \
LDFLAGS="-static" \
PKG_CONFIG="$tools_dir/bin/pkgconf" \
PKG_CONFIG_LIBDIR="$sysroot/usr/lib/pkgconfig" \
PKG_CONFIG_SYSROOT_DIR="$sysroot" \
./configure \
--build="$build_triplet" \
--host=x86_64-linux-musl \
--prefix=/usr \
--disable-shared \
--enable-static \
--disable-nls \
--disable-pidof \
--disable-pidwait \
--disable-kill \
--disable-w \
--disable-numa \
--without-systemd \
--without-elogind
make -s -j"$jobs" \
src/ps/pscommand \
src/pgrep \
src/pkill \
src/top/top \
src/free \
src/uptime \
src/vmstat \
src/watch \
src/sysctl
install -m 0755 src/ps/pscommand "$base_root/bin/ps"
install -m 0755 src/pgrep "$base_root/usr/bin/pgrep"
install -m 0755 src/pkill "$base_root/usr/bin/pkill"
install -m 0755 src/top/top "$base_root/usr/bin/top"
install -m 0755 src/free "$base_root/usr/bin/free"
install -m 0755 src/uptime "$base_root/usr/bin/uptime"
install -m 0755 src/vmstat "$base_root/usr/bin/vmstat"
install -m 0755 src/watch "$base_root/usr/bin/watch"
install -m 0755 src/sysctl "$base_root/sbin/sysctl"
)
iputils_build="$source_build_dir/iputils-20250605/build"
PKG_CONFIG_LIBDIR="$sysroot/usr/lib/pkgconfig:$sysroot/usr/share/pkgconfig" \
PKG_CONFIG_SYSROOT_DIR="$sysroot" \
python3 "$meson_source" setup \
"$iputils_build" \
"$source_build_dir/iputils-20250605" \
--cross-file="$cross_file" \
--prefix=/usr \
--buildtype=release \
--default-library=static \
-DUSE_CAP=false \
-DUSE_IDN=false \
-DUSE_GETTEXT=false \
-DBUILD_ARPING=false \
-DBUILD_CLOCKDIFF=false \
-DBUILD_TRACEPATH=false \
-DBUILD_MANS=false \
-DSKIP_TESTS=true
python3 "$meson_source" compile -C "$iputils_build" ping
install -m 4755 "$iputils_build/ping/ping" "$base_root/bin/ping"
(
cd "$source_build_dir/cronie-1.7.2"
CC="$cc" \
AR="$ar" \
RANLIB="$ranlib" \
LDFLAGS="-static" \
./configure \
--build="$build_triplet" \
--host=x86_64-linux-musl \
--prefix=/usr \
--sysconfdir=/etc \
--localstatedir=/var \
--runstatedir=/run \
--disable-anacron \
--without-pam \
--without-selinux \
--without-audit
make -s -j"$jobs"
install -m 0755 src/crond "$base_root/usr/sbin/cron"
install -m 4755 src/crontab "$base_root/usr/bin/crontab"
)
(
cd "$source_build_dir/openntpd-7.9p1"
CC="$cc" \
AR="$ar" \
RANLIB="$ranlib" \
YACC=byacc \
LDFLAGS="-static" \
./configure \
--build="$build_triplet" \
--host=x86_64-linux-musl \
--prefix=/usr \
--sysconfdir=/etc \
--localstatedir=/var \
--runstatedir=/run \
--with-privsep-user=_ntp \
--disable-https-constraint \
--disable-shared \
--enable-static
make -s -j"$jobs" -C compat
make -s -j"$jobs" -C src ntpd
install -m 0755 src/ntpd "$base_root/usr/sbin/ntpd"
ln -s ntpd "$base_root/usr/sbin/ntpctl"
)
(
cd "$source_build_dir/sysklogd-2.7.2"
CC="$cc" \
AR="$ar" \
RANLIB="$ranlib" \
LDFLAGS="-static" \
PKG_CONFIG="$tools_dir/bin/pkgconf" \
./configure \
--build="$build_triplet" \
--host=x86_64-linux-musl \
--prefix=/usr \
--sysconfdir=/etc \
--runstatedir=/run \
--disable-shared \
--enable-static \
--without-logger \
--with-systemd=no
make -s -j"$jobs" -C src syslogd
install -m 0755 src/syslogd "$base_root/usr/sbin/syslogd"
)
(
cd "$source_build_dir/skalibs-2.15.1.0"
CC="$cc" AR="$ar" RANLIB="$ranlib" ./configure \
--prefix=/usr \
--build="$build_triplet" \
--target=x86_64-linux-musl \
--with-include="$sysroot/usr/include" \
--with-lib="$sysroot/usr/lib" \
--with-sysdep-devurandom=yes \
--with-sysdep-posixspawnearlyreturn=no \
--with-sysdep-procselfexe=/proc/self/exe \
--with-sysdep-selectinfinite=yes \
--disable-shared
make -s -j"$jobs" CC="$cc" AR="$ar" RANLIB="$ranlib"
make -s DESTDIR="$sysroot" install
)
(
cd "$source_build_dir/mdevd-0.1.8.2"
CC="$cc" AR="$ar" RANLIB="$ranlib" ./configure \
--prefix=/usr \
--build="$build_triplet" \
--target=x86_64-linux-musl \
--with-sysdeps="$sysroot/usr/lib/skalibs/sysdeps" \
--with-include="$sysroot/usr/include" \
--with-lib="$sysroot/usr/lib" \
--enable-static-libc \
--disable-shared
make -s -j"$jobs" CC="$cc" AR="$ar" RANLIB="$ranlib"
install -m 0755 mdevd "$base_root/sbin/mdevd"
install -m 0755 mdevd-coldplug "$base_root/sbin/mdevd-coldplug"
)
kmod_build="$source_build_dir/kmod-34.2/build"
PKG_CONFIG_LIBDIR="$sysroot/usr/lib/pkgconfig:$sysroot/usr/share/pkgconfig" \
PKG_CONFIG_SYSROOT_DIR="$sysroot" \
python3 "$meson_source" setup \
"$kmod_build" \
"$source_build_dir/kmod-34.2" \
--cross-file="$cross_file" \
--prefix=/usr \
--buildtype=release \
--default-library=static \
-Dzstd=disabled \
-Dxz=disabled \
-Dzlib=disabled \
-Dopenssl=disabled \
-Dmanpages=false \
-Ddocs=false \
-Dbuild-tests=false
python3 "$meson_source" compile -C "$kmod_build" kmod:executable
install -m 0755 "$kmod_build/kmod" "$base_root/sbin/kmod"
for command in depmod insmod lsmod modinfo modprobe rmmod; do
ln -s kmod "$base_root/sbin/$command"
done
(
cd "$source_build_dir/openssl-3.5.7"
make -s distclean
CC="$cc" \
AR="$ar" \
RANLIB="$ranlib" \
./Configure \
linux-x86_64 \
--prefix=/usr \
--libdir=lib \
--openssldir=/etc/ssl \
no-afalgeng \
no-docs \
no-dso \
no-module \
no-secure-memory \
no-shared \
no-tests
make -s -j"$jobs" build_generated
make -s depend
make -s -j"$jobs" apps/openssl
install -m 0755 apps/openssl "$base_root/usr/bin/openssl"
)
install -m 0755 \
"$chimerautils_build/src.freebsd/coreutils/xinstall/xinstall" \
"$base_root/usr/bin/install"
install -m 0755 \
"$chimerautils_build/src.freebsd/coreutils/test/xtest" \
"$base_root/usr/bin/test"
ln -s test "$base_root/usr/bin/["
ln -s install "$base_root/usr/bin/binstall"
ln -s id "$base_root/usr/bin/groups"
ln -s id "$base_root/usr/bin/whoami"
ln -s md5 "$base_root/usr/bin/sha256"
ln -s stat "$base_root/usr/bin/readlink"
install -m 0755 "$sysroot/usr/bin/tput" "$base_root/usr/bin/tput"
ln -s tcsh "$base_root/bin/csh"
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/expect -f
if {$argc != 1} {
puts stderr "usage: boot.exp RUNNER"
exit 2
}
set runner [file normalize [lindex $argv 0]]
set timeout 30
set acceptance_name "boot"
source [file join [file dirname [info script]] harness.exp]
proc boot_mouse {runner} {
global spawn_id
spawn -noecho $runner
await_exact "Cheesed to meet you! v0.1.0" "the cheesed startup banner"
await_exact "cheesed: base bootstrap complete; cheesed is PID 1" "PID 1 confirmation"
await_exact "MOUSE 0.1.0 (x86_64)" "the MOUSE login banner"
await_exact "mouse:~# " "the root shell prompt"
}
boot_mouse $runner
send -- "hostname\r"
await_exact "mouse\r" "the configured hostname"
await_exact "mouse:~# " "the prompt after hostname"
send -- "cat /proc/1/comm\r"
await_exact "cheesed\r" "cheesed in /proc/1"
await_exact "mouse:~# " "the prompt after the PID 1 check"
send -- "sh -c 'test -c /dev/null'; echo DEV_STATUS_\$?\r"
await_exact "DEV_STATUS_0\r" "a mounted devtmpfs"
await_exact "mouse:~# " "the prompt after the devtmpfs check"
send -- "clear; echo CLEAR_STATUS_\$?\r"
await_exact "CLEAR_STATUS_0\r" "a successful clear command"
await_exact "mouse:~# " "the prompt after clear"
send -- {sh -c 'i=0; while [ "$i" -lt 8 ]; do (sleep 1) & i=$((i + 1)); done'}
send -- "\r"
await_exact "mouse:~# " "the prompt after starting orphan probes"
await_exact "cheesed: reaped orphan PID " "PID 1 orphan reaping"
send -- "sleep 2\r"
await_exact "mouse:~# " "all orphan probes to exit"
send -- {sh -c 'if grep -l ") Z " /proc/[0-9]*/stat >/dev/null; then hostname; else cat /proc/1/comm; fi'}
send -- "\r"
await_exact "cheesed\r" "a zombie-free process table"
await_exact "mouse:~# " "the prompt after the zombie check"
send -- "kill -TERM 1\r"
await_exact "cheesed: received poweroff request" "the poweroff request"
await_clean_exit "poweroff"
boot_mouse $runner
send -- "kill -INT 1\r"
await_exact "cheesed: received reboot request" "the reboot request"
await_clean_exit "reboot"
puts "boot acceptance tests passed"
+19
View File
@@ -0,0 +1,19 @@
#!/bin/sh
set -eu
acceptance_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
. "$acceptance_dir/harness.sh"
acceptance_init
acceptance_require_commands "the boot acceptance test" expect
acceptance_copy_disk boot
MOUSE_KERNEL_ARGS="console=ttyS0 root=PARTUUID=4d4f5553-4500-4000-8000-000000000002 rootfstype=btrfs rw rootwait init=/sbin/cheesed mouse.autologin=1 panic=-1 quiet loglevel=3"
MOUSE_BOOT_MODE=disk
MOUSE_DISK_IMAGE="$test_disk"
MOUSE_NETWORK=0
export MOUSE_KERNEL_ARGS
export MOUSE_BOOT_MODE
export MOUSE_DISK_IMAGE
export MOUSE_NETWORK
exec expect "$acceptance_dir/boot.exp" "$runner"
+71
View File
@@ -0,0 +1,71 @@
#!/usr/bin/expect -f
if {$argc != 3} {
puts stderr "usage: btrfs.exp RUNNER DISK MODE"
exit 2
}
set runner [file normalize [lindex $argv 0]]
set disk [file normalize [lindex $argv 1]]
set mode [lindex $argv 2]
if {$mode ni {first alternate rollback powerloss-stage powerloss-verify}} {
puts stderr "invalid btrfs mode: $mode"
exit 2
}
set timeout 180
set acceptance_name "btrfs"
source [file join [file dirname [info script]] harness.exp]
acceptance_log
set env(MOUSE_DISK_IMAGE) $disk
set env(MOUSE_MEMORY) 1G
set env(MOUSE_BOOT_MODE) disk
set env(MOUSE_KERNEL_ARGS) "console=ttyS0 root=PARTUUID=4d4f5553-4500-4000-8000-000000000002 rootfstype=btrfs rw rootwait init=/sbin/cheesed mouse.autologin=1 panic=-1"
spawn -noecho $runner
await_exact "cheesed: base bootstrap complete; cheesed is PID 1" "PID 1 confirmation"
await_exact "mouse login: root (automatic login)" "the serial login"
await_prompt
sendline {sh -c "grep -Eq '^[^ ]+ / btrfs ' /proc/mounts && grep -Eq '^[^ ]+ /usr/local btrfs ' /proc/mounts && grep -Eq '^[^ ]+ /var btrfs ' /proc/mounts && grep -Eq '^[^ ]+ /home btrfs ' /proc/mounts && grep -Eq '^[^ ]+ /srv btrfs ' /proc/mounts"; echo MOUNTS_$?}
await_exact "MOUNTS_0\r" "the btrfs root and persistent subvolume mounts"
await_exact "mouse:~# " "the prompt after mount verification"
sendline {btrfs version; echo VERSION_$?}
await_exact "btrfs-progs v7.0\r" "the pinned btrfs userspace version"
await_exact "VERSION_0\r" "the btrfs version status"
await_exact "mouse:~# " "the prompt after version"
if {$mode in {first powerloss-stage}} {
sendline {sh -c 'test "$(cat /etc/mouse-boot-environment)" = default && mkdir -p /var/lib/mouse /home/mouse-state /usr/local/share/mouse /srv/mouse && echo base-default >/etc/bootenv-state && echo var-shared >/var/lib/mouse/state && echo home-shared >/home/mouse-state/value && echo local-shared >/usr/local/share/mouse/state && echo srv-shared >/srv/mouse/state'; echo WRITE_$?}
await_exact "WRITE_0\r" "boot-environment and persistent state creation"
await_exact "mouse:~# " "the prompt after state creation"
sendline {mouse-bootenv prepare; echo PREPARE_$?}
await_exact "alternate is ready for an update\r" "the inactive snapshot preparation"
await_exact "PREPARE_0\r" "the preparation status"
await_exact "mouse:~# " "the prompt after preparation"
sendline {sh -c 'echo alternate-updated >/run/mouse-root/ROOT/alternate/etc/bootenv-state'; mouse-bootenv activate alternate; echo ACTIVATE_$?}
await_exact "alternate will boot next\r" "the alternate activation"
await_exact "ACTIVATE_0\r" "the activation status"
if {$mode eq "powerloss-stage"} {
exec kill -KILL [exp_pid]
expect eof
catch wait
puts "power-loss interruption staged"
exit 0
}
} elseif {$mode in {alternate powerloss-verify}} {
sendline {sh -c 'test "$(cat /etc/mouse-boot-environment)" = alternate && test "$(cat /etc/bootenv-state)" = alternate-updated && test "$(cat /var/lib/mouse/state)" = var-shared && test "$(cat /home/mouse-state/value)" = home-shared && test "$(cat /usr/local/share/mouse/state)" = local-shared && test "$(cat /srv/mouse/state)" = srv-shared'; echo ALTERNATE_$?}
await_exact "ALTERNATE_0\r" "the alternate root and shared persistent state"
if {$mode eq "alternate"} {
await_exact "mouse:~# " "the prompt after alternate verification"
sendline {mouse-bootenv rollback; echo ROLLBACK_$?}
await_exact "default will boot next\r" "the default rollback activation"
await_exact "ROLLBACK_0\r" "the rollback status"
}
} else {
sendline {sh -c 'test "$(cat /etc/mouse-boot-environment)" = default && test "$(cat /etc/bootenv-state)" = base-default && test "$(cat /var/lib/mouse/state)" = var-shared && test "$(cat /home/mouse-state/value)" = home-shared && test "$(cat /usr/local/share/mouse/state)" = local-shared && test "$(cat /srv/mouse/state)" = srv-shared'; echo DEFAULT_$?}
await_exact "DEFAULT_0\r" "the rolled-back root and shared persistent state"
}
await_exact "mouse:~# " "the prompt before poweroff"
poweroff
puts "$mode btrfs acceptance passed"
+20
View File
@@ -0,0 +1,20 @@
#!/bin/sh
set -eu
acceptance_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
. "$acceptance_dir/harness.sh"
acceptance_init
acceptance_require_commands "the btrfs acceptance test" expect
acceptance_copy_disk btrfs
"$acceptance_dir/btrfs.exp" "$runner" "$test_disk" first
"$acceptance_dir/btrfs.exp" "$runner" "$test_disk" alternate
"$acceptance_dir/btrfs.exp" "$runner" "$test_disk" rollback
powerloss_disk="$temporary/mouse-disk-powerloss.img"
acceptance_clone_disk "$build_dir/mouse-disk.img" "$powerloss_disk"
"$acceptance_dir/btrfs.exp" "$runner" "$powerloss_disk" powerloss-stage
"$acceptance_dir/btrfs.exp" "$runner" "$powerloss_disk" powerloss-verify
printf '%s\n' \
"btrfs root, boot-environment, and power-loss acceptance tests passed"
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/expect -f
if {$argc != 3} {
puts stderr "usage: firmware.exp RUNNER LABEL CHECK"
exit 2
}
set runner [file normalize [lindex $argv 0]]
set label [lindex $argv 1]
set check [lindex $argv 2]
set timeout 90
set acceptance_name "firmware-$check"
source [file join [file dirname [info script]] harness.exp]
acceptance_log
spawn -noecho $runner
await_exact "Cheesed to meet you! v0.1.0" "MOUSE through the Limine firmware path"
await_exact "mouse login: " "the authenticated firmware-boot login"
sendline {root}
await_exact "Password: " "the root password prompt"
sendline {cheddar}
await_exact "mouse:~# " "the root shell"
switch -- $check {
raw-virtio {
set check_command {[ -e /sys/block/vda ]}
}
raw-sata {
set check_command {[ -e /sys/block/sda ]}
}
raw-uefi-sata {
set check_command {[ -d /sys/firmware/efi ] && [ -e /sys/block/sda ]}
}
raw-uefi-nvme {
set check_command {[ -d /sys/firmware/efi ] && [ -e /sys/block/nvme0n1 ]}
}
default {
puts stderr "unknown firmware check: $check"
exit 2
}
}
sendline "sh -c 'grep -q \"root=PARTUUID=4d4f5553-4500-4000-8000-000000000002\" /proc/cmdline && grep -Eq \"^\[^ \]+ / btrfs \" /proc/mounts && $check_command'; echo FIRMWARE_\$?"
await_exact "FIRMWARE_0\r" "the GPT, Limine, PARTUUID, and Btrfs boot contract"
await_exact "mouse:~# " "the prompt after firmware verification"
poweroff
puts "$label acceptance passed"
+45
View File
@@ -0,0 +1,45 @@
#!/bin/sh
set -eu
acceptance_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
. "$acceptance_dir/harness.sh"
acceptance_init
acceptance_require_commands "the firmware acceptance test" expect
acceptance_require_file \
"$build_dir/mouse-disk.img" \
"run 'make check' before checking BIOS firmware"
acceptance_require_file \
"$build_dir/ovmf-code.fd" \
"run 'make check' before checking UEFI firmware"
acceptance_make_temporary firmware
raw_disk="$temporary/mouse-disk.img"
acceptance_clone_disk "$build_dir/mouse-disk.img" "$raw_disk"
run_firmware()
{
label=$1
check=$2
disk=$3
bus=$4
firmware=$5
MOUSE_BOOT_MODE=firmware \
MOUSE_DISK_IMAGE="$disk" \
MOUSE_DISK_BUS="$bus" \
MOUSE_FIRMWARE="$firmware" \
MOUSE_UEFI_CODE="$build_dir/ovmf-code.fd" \
MOUSE_MEMORY="${MOUSE_MEMORY:-1G}" \
expect "$acceptance_dir/firmware.exp" "$runner" "$label" "$check"
}
run_firmware "BIOS raw VirtIO" raw-virtio \
"$raw_disk" virtio bios
run_firmware "BIOS raw AHCI" raw-sata \
"$raw_disk" sata bios
run_firmware "UEFI raw AHCI" raw-uefi-sata \
"$raw_disk" sata uefi
run_firmware "UEFI raw NVMe" raw-uefi-nvme \
"$raw_disk" nvme uefi
printf '%s\n' "BIOS and UEFI firmware acceptance passed"
+73
View File
@@ -0,0 +1,73 @@
if {![info exists acceptance_name]} {
puts stderr "acceptance harness requires acceptance_name"
exit 2
}
proc fail {message} {
global acceptance_name
puts stderr "$acceptance_name acceptance failed: $message"
exit 1
}
proc await_exact {value description} {
expect {
-exact $value { return }
timeout { fail "timed out waiting for $description" }
eof { fail "QEMU exited while waiting for $description" }
}
}
proc await {value description} {
await_exact $value $description
}
proc await_regexp {value description} {
expect {
-re $value { return }
timeout { fail "timed out waiting for $description" }
eof { fail "QEMU exited while waiting for $description" }
}
}
proc sendline {line} {
send -- $line
send -- "\r"
}
proc prompt {} {
await_exact "mouse:~# " "the root prompt"
}
proc await_prompt {} {
send -- "\r"
await_exact "mouse:~# " "the root prompt"
}
proc await_clean_exit {action} {
expect {
eof {
set result [wait]
set status [lindex $result 3]
if {$status != 0} {
fail "QEMU exited with status $status after $action"
}
}
timeout { fail "QEMU did not exit after $action" }
}
}
proc poweroff {} {
sendline {poweroff}
await_exact "cheesed: received poweroff request" "the poweroff request"
await_exact "cheesed: root filesystem remounted read-only" "the read-only root remount"
await_clean_exit "poweroff"
}
proc acceptance_log {} {
global env
if {[info exists env(MOUSE_EXPECT_LOG)]} {
log_user 1
} else {
log_user 0
}
}
+67
View File
@@ -0,0 +1,67 @@
#!/bin/sh
acceptance_init() {
acceptance_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
repo_dir=$(CDPATH= cd -- "$acceptance_dir/../.." && pwd)
runner="$repo_dir/scripts/run-qemu.sh"
build_dir=${MOUSE_BUILD_DIR:-"$repo_dir/build"}
temporary=
}
acceptance_require_commands() {
description=$1
shift
for command in "$@"; do
if ! command -v "$command" >/dev/null 2>&1; then
printf '%s\n' "$command is required for $description" >&2
exit 1
fi
done
}
acceptance_require_file() {
path=$1
instruction=$2
if [ ! -f "$path" ]; then
printf '%s\n' "$instruction" >&2
exit 1
fi
}
acceptance_cleanup_extra() {
:
}
acceptance_cleanup() {
acceptance_cleanup_extra
if [ -n "$temporary" ] && [ -d "$temporary" ]; then
rm -rf "$temporary"
fi
}
acceptance_make_temporary() {
name=$1
temporary=$(mktemp -d "${TMPDIR:-/tmp}/mouse-$name.XXXXXX")
trap acceptance_cleanup EXIT HUP INT TERM
}
acceptance_copy_disk() {
name=$1
description=${2:-$1}
base_disk="$build_dir/mouse-disk.img"
acceptance_require_file \
"$base_disk" \
"run 'make check' before checking $description"
acceptance_make_temporary "$name"
test_disk="$temporary/mouse-disk.img"
acceptance_clone_disk "$base_disk" "$test_disk"
}
acceptance_clone_disk() {
source=$1
destination=$2
if [ "$(uname -s)" = Darwin ] && cp -c "$source" "$destination" 2>/dev/null; then
return
fi
cp "$source" "$destination"
}
+99
View File
@@ -0,0 +1,99 @@
#!/usr/bin/expect -f
if {$argc != 3} {
puts stderr "usage: multi-user.exp RUNNER DISK MODE"
exit 2
}
set runner [file normalize [lindex $argv 0]]
set disk [file normalize [lindex $argv 1]]
set mode [lindex $argv 2]
if {$mode ni {provision login verify}} {
puts stderr "invalid multi-user mode: $mode"
exit 2
}
set timeout 75
set acceptance_name "multi-user"
source [file join [file dirname [info script]] harness.exp]
acceptance_log
set env(MOUSE_BOOT_MODE) disk
set env(MOUSE_DISK_IMAGE) $disk
set env(MOUSE_NETWORK) 0
if {$mode eq "provision"} {
set env(MOUSE_KERNEL_ARGS) "console=ttyS0 root=PARTUUID=4d4f5553-4500-4000-8000-000000000002 rootfstype=btrfs rw rootwait init=/sbin/cheesed mouse.autologin=1 panic=-1 quiet loglevel=3"
} else {
set env(MOUSE_KERNEL_ARGS) "console=ttyS0 root=PARTUUID=4d4f5553-4500-4000-8000-000000000002 rootfstype=btrfs rw rootwait init=/sbin/cheesed panic=-1 quiet loglevel=3"
}
spawn -noecho $runner
if {$mode eq "provision"} {
await "mouse login: root (automatic login)" "the provisioning login"
await "mouse:~# " "the provisioning shell"
sendline {groupadd -R /run/mouse-root/ROOT/default operators; useradd -R /run/mouse-root/ROOT/default -M -g operators -s /bin/tcsh alice; mkdir /run/mouse-root/home/alice; chown 1000:1000 /run/mouse-root/home/alice; echo CREATE_$status}
await "CREATE_0\r" "account creation"
await "mouse:~# " "the provisioning prompt"
sendline {sh -c 'grep -q "^root:x:0:0:root:/root:/bin/sh$" /run/mouse-root/ROOT/default/etc/passwd && grep -q "^toor:x:0:0:BSD-style root account:/root:/bin/tcsh$" /run/mouse-root/ROOT/default/etc/passwd'; echo ADMIN_$status}
await "ADMIN_0\r" "the BSD-style administrator accounts"
await "mouse:~# " "the provisioning prompt"
sendline {sh -c 'usermod -R /run/mouse-root/ROOT/default -p "$(head -n 1 /run/mouse-root/ROOT/default/etc/shadow | cut -d: -f2)" alice'; echo PASSWORD_$status}
await "PASSWORD_0\r" "the alternate-root password setup"
await "mouse:~# " "the provisioning prompt"
sendline {sh -c 'test "$(grep "^alice:" /run/mouse-root/ROOT/default/etc/shadow | cut -d: -f2)" = "$(head -n 1 /run/mouse-root/ROOT/default/etc/shadow | cut -d: -f2)" && grep -q "^alice:x:1000:1000::/home/alice:/bin/tcsh$" /run/mouse-root/ROOT/default/etc/passwd'; echo ACCOUNT_$status}
await "ACCOUNT_0\r" "the on-disk login account"
await "mouse:~# " "the provisioning prompt"
sendline {echo enable_mouse_echo=YES >/run/mouse-root/ROOT/default/etc/rc.conf; echo enable_network=NO >>/run/mouse-root/ROOT/default/etc/rc.conf; echo POLICY_$status}
await "POLICY_0\r" "the offline test policy"
await "mouse:~# " "the provisioning prompt"
poweroff
puts "account provisioning acceptance passed"
exit 0
}
await "mouse login: " "the authenticated login prompt"
set timeout 2
expect {
-exact "root (automatic login)" { fail "normal disk enabled root autologin" }
timeout {}
}
set timeout 75
sendline {alice}
await "Password: " "the password prompt"
if {$mode eq "login"} {
sendline {wrong-password}
await "Login incorrect" "failed authentication rejection"
await "mouse login: " "the retry login prompt"
sendline {alice}
await "Password: " "the second password prompt"
}
sendline {cheddar}
await "mouse:~> " "the ordinary user shell"
sendline {id}
await "uid=1000(alice) gid=1000(operators) groups=1000(operators)\r" "the ordinary identity"
await "mouse:~> " "the user prompt"
sendline {sh -c 'test "$HOME" = /home/alice && test "$SHELL" = /bin/tcsh && test -O /home/alice && ! touch /usr/alice-must-not-write'; echo USER_$status}
await "USER_0\r" "home ownership and base protection"
await "mouse:~> " "the user prompt"
sendline {su - root}
await "Password: " "the explicit elevation password"
sendline {cheddar}
await "mouse:~# " "the elevated root shell"
sendline {id}
await "uid=0(root) gid=0(root) groups=0(root)\r" "authorised root elevation"
await "mouse:~# " "the root prompt"
sendline {sh -c 'test -d /bin && test -d /sbin && test -d /usr/bin && test -d /usr/sbin && test -x /bin/ln && test -x /sbin/cheesed && test -x /usr/bin/passwd && test -x /usr/sbin/useradd'; echo FHS_$?}
await "FHS_0\r" "the FHS command-directory split"
await "mouse:~# " "the root prompt"
sendline {sh -c 'rm -f /tmp/ln-source /tmp/ln-target; echo mouse >/tmp/ln-source; ln /tmp/ln-source /tmp/ln-target; test "$(cat /tmp/ln-target)" = mouse'; echo LN_$?}
await "LN_0\r" "the statically linked ln command"
await "mouse:~# " "the root prompt"
sendline {exit}
await "mouse:~> " "the ordinary user shell after leaving root"
sendline {su - toor}
await "Password: " "the toor password prompt"
sendline {cheddar}
await "mouse:~# " "the toor shell"
sendline {echo $LOGNAME $SHELL}
await "toor /bin/tcsh\r" "the toor login and friendly shell"
await "mouse:~# " "the toor prompt"
poweroff
puts "$mode authenticated multi-user acceptance passed"
+13
View File
@@ -0,0 +1,13 @@
#!/bin/sh
set -eu
acceptance_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
. "$acceptance_dir/harness.sh"
acceptance_init
acceptance_require_commands "the multi-user acceptance test" expect
acceptance_copy_disk multi-user "multi-user operation"
"$acceptance_dir/multi-user.exp" "$runner" "$test_disk" provision
"$acceptance_dir/multi-user.exp" "$runner" "$test_disk" login
"$acceptance_dir/multi-user.exp" "$runner" "$test_disk" verify
printf '%s\n' "multi-user acceptance tests passed"

Some files were not shown because too many files have changed in this diff Show More