The base is built into the kernel
The shell, coreutils and util-linux ride in the kernel image, and the desktop is in the Moonwater module, so the base keeps working whatever else is installed.
A research Linux distribution: its own shell and coreutils, software from five other distributions, one script that decides what each machine does, and one command to control the system.
Moonwater writes its own shell, coreutils and util-linux, along with tools such as awk, sed, tar and zstd: 209 commands in one program. Underneath is its own C library, with 353 routines written by hand in assembly.
grep -r -n struct over the Linux source treechmod -R over the Linux source treewc -L over 30 MB of wordspaste -s -d, over the benchmark recordsLower is faster. Moonwater's own benchmark runs, recorded in its commit history, September 2026. Moonwater's grep and chmod ran on 16 threads; GNU grep and chmod are single-threaded, and on one core Moonwater's chmod takes 116 ms. The wc -L figure is on a CPU with AVX-512 VBMI2. The shell checks make's configure script (sh -n) in 24.5M cycles, against bash's 31M and dash's 19M.
Each program with a standard counterpart is tested against it: both run on the same inputs, and status, output, effects on disk and diagnostics are compared. The shell's suite has 28,142 cases, each run under bash, bash --posix or dash; its two latest runs matched on 25,707 and 25,733.
Bowl runs the package managers of Alpine, Arch, Debian, Fedora and Nix directly on Moonwater, with no VM. What they install runs as an ordinary program on the same kernel, next to Moonwater's own shell and coreutils.
apk3.7 MBpacman126 MBapt49 MBdnf70 MBnix27 MBEach bowl lives under /bowls. Sizes are the x86_64 downloads.
$ bowl setup fedora
[Bowl] downloading https://dl.fedoraproject.org/…
[Bowl] landing Fedora at /bowls/fedora
[Bowl] /bowls/bin/dnf -> /bowls/fedora/usr/bin/dnf
[Bowl] Fedora is ready. dnf makecache
$ dnf -y install hello
…
$ hello
[Bowl] /bowls/bin/hello -> /bowls/fedora/usr/bin/hello
Hello, world! bowl setup checks there is room, downloads the distribution, unpacks it and puts its package manager on the path. After that a program's name is enough: the shell finds it in its bowl and adds a launcher for it to /bowls/bin.
Bowl binds in the loader and C library the program needs and starts it. Moonwater's own /bin stays, and no process sits in between.
pacman, apt, apk, dnf and nix run inside their distribution, with their own process list, hostname and IPC. The host's /sys and /proc/sys are read-only to them. This is chosen by the program's name, not by a flag.
Bowl does no instruction emulation and no syscall proxying. It is not a security sandbox: a bowl's programs run as root. On a live stick bowls live in memory; moonwater install puts them on a data partition, and moonwater wipe keeps them.
Every button, lid, cable and boot is an event, and main.moonwater.sh answers it. Watch one laptop's file at work.
function moonwater_init { moonwater link join office} function moonwater_dock { if [ "$1" = on ]; then moonwater priority internet wired else moonwater priority internet wifi fi} function moonwater_lid_close { moonwater canvas off} function moonwater_lid_open { moonwater canvas on} function moonwater_poweroff { poweroff} moonwater_initmoonwater_<event>moonwater_eventmoonwater_endAny shell command, moonwater commands, and programs installed with Bowl.
function moonwater_init {
case $1 in
live|disk*)
moonwater wipe
chromium --kiosk \
--user-data-dir=/tmp/kiosk "$URL" &
;;
esac
} function moonwater_init {
case $1 in
live|disk*)
moonwater canvas off
moonwater link join office
/bowls/bin/exhibit &
;;
esac
} /root/main.moonwater.shYour script, if it exists and passes the checksmain.moonwater.shCompiled into the kernel module at build time: the system's fallbackThe built-in script lives in the module, so a machine whose disks never appear still has a working power button. Yours is used only if it is a regular file owned by root, writable by no one else, not a symlink and at most 64 KiB. A key the script does not bind still types.
moonwater manages where the system lives, what runs at boot and what the machine's events do.
installuselivewipebindbind initbind exitcanvaskeyboardwifibluetoothpriority internettimetimezonentplinklink shelllink run$ moonwater keyboard se
[Moonwater] keyboard se
$ moonwater bind mute
[Moonwater] mute: /root/main.moonwater.sh:8 Settings live in two places: the boot image, and /root on the data partition. When the machine script handles an event, moonwater bind points at its line.
A new machine sets its time zone, starts NTP and brings up its network by itself. Time zone, clock, keyboard and network preference are each one moonwater command, saved in /root.
Asked of Cloudflare over HTTPS when a network appears. moonwater timezone ZONE makes it manual.
Then the resolver the network hands out, the only one that knows local names.
pool.ntp.org, then Google's and Cloudflare's servers, until the kernel reports the clock synchronised.
A link with carrier beats one without. When cable and Wi-Fi are both up, the cable wins.
Canvas starts at boot and drives each screen at the largest mode it lists.
Not detected. Set it once with moonwater keyboard se.
$ moonwater timezone
[Moonwater] timezone Europe/Stockholm, Central European Time (auto, from Cloudflare)
local 2026-09-26 14:02:11 CEST
$ moonwater ntp
[Moonwater] ntp on, pool.ntp.org, sampling on, synchronised
$ moonwater priority internet
[Moonwater] internet prefers wired
$ moonwater keyboard
[Moonwater] keyboard us None of it was set by hand. Each setting is one moonwater command to read it back, and the same command with a value to change it.
moonwater link, an alternative to ssh.A terminal or a command on another machine, by key. There are no users and no passwords, and a paired machine gets a shell or runs a command only once you allow it.
# on each machine: its key
$ moonwater link key
# give each the other's key and address
$ moonwater link pair server KEY 10.0.0.2
# on the server: let the laptop in, grant by grant
$ moonwater link allow laptop shell run
# then, from the laptop
$ moonwater link shell server
$ moonwater link run server uname -a Every computer's clock runs a little fast or slow, and syncing it once an hour doesn't fix that. Moonwater asks three time servers, five exchanges each, and believes the one with the tightest error bound. Then it works out how fast its own crystal runs and corrects the rate, so the clock stays right between syncs, not just at them.
Measured on 26 September 2026 from one machine in Sweden. Each system ran in its default configuration in identical KVM guests on the same host and network: chrony and systemd-timesyncd as Debian 13 ships them. True time is the median of Netnod's stratum-1 servers, read from the same guest once a minute. The first two charts are medians over the second half of an hour-long run. The crystal error was added by changing the kernel's tick. chrony resets that tick itself, so it has no result for that chart. The worst-answer chart compares Moonwater's server choice with trusting the first server that answers, as a basic client does.
moonwater ntp sampling on|off, on by default. The first answer after boot sets the clock outright, so it's right from the start. After that, small corrections are eased in and never jump time backwards, and the crystal's rate is learned at every sync: one 100 ppm fast was learned to within 1 ppm in 13 minutes. Syncs start 4 minutes apart and stretch to 30 as the rate settles. Answers are checked field by field, and a server that says slow down, or whose own error exceeds a second, is dropped. A server named in /root/ntp.server is used on its own.
The shell, coreutils and util-linux ride in the kernel image, and the desktop is in the Moonwater module, so the base keeps working whatever else is installed.
moonwater wipe empties /home and /root but keeps the settings, the machine script and /bowls.
What each program may run, spawn and reach on the network, in one place: cat to read, echo to change, then seal.
The build tool runs on Moonwater's own stack. On Linux, compiling it needs only a C compiler and an assembler.
Canvas draws with the CPU, on any display the kernel can drive.
Each tzdata zone maps to the POSIX rule in its own file's footer.
In its own C library; 351 of them for all of x86_64, arm64 and riscv64.
No relocations, no dynamic linking, no section table.
Moonwater has its own Kconfig. Turn off a part a machine does not need and it is gone from the build: disabled commands are dropped from the binary, not just from the path.
# no desktop; the kernel console stays
CONFIG_MOONWATER_CANVAS=n
# the shell, without the utilities
CONFIG_MOONWATER_UTILITIES=n
# a kernel only, with no bundled userspace
CONFIG_MOONWATER_SHELL=n
CONFIG_MOONWATER_UTILITIES=n
# build with your own machine script inside
CONFIG_MOONWATER_MACHINE_SCRIPT="kiosk.sh" No download yet: three commands build it and boot it in a virtual machine.
# get the source
$ git clone https://github.com/dawnlarsson/moonwater
$ cd moonwater
# build it and boot it in a virtual machine
$ sh build.sh --run
$ sh build.sh --runBuilds it, then boots it in a virtual machine on this computer.
sh build.sh --arch arm64 --runThe same for arm64, or riscv64.
sh build.sh --usbBuilds it, lists the drives and prints the commands that write the stick.
It builds on Linux, or anywhere else with --host naming a Linux machine to build on. Real hardware boots it through UEFI, with Secure Boot off.