moonshot is an operating system I am building from scratch for x86_64. It boots via Multiboot + GRUB in QEMU, has preemptive multitasking with a round-robin scheduler, its own pixel framebuffer and font renderer, a tiled windowing layer, a login gate, an interactive shell with a built-in text editor and pixel-art editor, a REPL with a JIT compiler, a disk-backed filesystem, a PS/2 mouse driver, and ring-3 userspace: real ELF64 programs with their own page tables, loaded from the filesystem and given a window of their own. The kernel is written in c0, a small systems language whose compiler, coff, is a separate project in the same stack.
The end goal is an OS with a real graphical interface and its own windowing system, not a serial-only kernel. Every test runs headless in QEMU and reads the serial log or the framebuffer; there is also a graphical mode you can sit in front of.
| Path | What it is |
|---|---|
kmain.c0 |
The kernel entry point. It includes every other .c0 subsystem file, so coff compiles one flat translation unit. |
*.c0 |
The kernel subsystems: knekt (core), jenna (memory/paging), chrone (scheduler), jakel (filesystem), kakel (windowing), punkt (framebuffer + font), raket (init and login), skalman (shell), skrift (editor), pensel (pixel-art editor), klick (mouse), jit (JIT compiler), repl (REPL), syscall (ring-3 syscalls and the ELF loader), serial, interrupts, keyboard, vga (text-mode fallback), ata (disk). |
*_data.c0 |
Generated. The ring-3 programs the kernel seeds onto the filesystem at first boot, as byte arrays. Every one is rebuilt from source by gen_programs.sh; see Baked-in programs. |
programs/ |
The sources of those programs: two syscall probes, Gnista's engine demo, the Infecteria game and a frame-rate probe. |
elf_tests/ |
Nine small c0 programs compiled with coff --elf and run inside the OS by verify_elf_backend.sh, so the machine-code backend is tested where it matters. gen_elf_tests.py bakes them into elf_tests_data.c0. |
boot.s |
The hand-written Multiboot entry stub that gets the CPU into 64-bit long mode before calling into c0-compiled code. |
ring3_handlers.s |
Ring-3 entry and exit trampolines (syscall/sysret and iretq). |
linker.ld |
The linker script that places the kernel at 1 MiB. |
build.sh |
Verifies the compiler, then builds moonshot.elf. |
sync_addrs.sh |
Patches the JIT helper function addresses into kmain.c0 after linking. Needed when any code changes size. |
gen_seed.py, gen_programs.sh, gen_elf_tests.py |
Turn compiled programs into the *_data.c0 byte arrays. |
run_qemu.sh |
The main regression gate. Builds, boots headlessly, and asserts invariants on the serial log. |
test_*.sh, verify_*.sh |
Eighteen more test scripts; see Testing. |
run_graphical.sh |
Boots the kernel in a real QEMU window you can type and click in. |
screenshot.sh |
Boots headlessly and dumps the framebuffer to screenshots/ as a PNG. |
grub.cfg |
The GRUB menu entry. |
LICENSE |
GPL-3.0. |
You need Linux on x86_64, and these packages on Arch (apt names vary):
qemu-system-x86— for booting the kernelgrub,libisoburn,mtools— forgrub-mkrescue(building the bootable ISO)asandldfrom binutils — the kernel is still assembled and linked by themtccorgcc— for coff's one-time bootstrap onlypython3— for the seed generators and the QEMU monitor scripts the tests usebash,sed,awk,dd,sha256sum,timeout— standard system tools
The kernel is compiled by coff1, the self-hosted c0 compiler. That compiler
lives in its own repository, and this one expects to find it at ../c0-coff/
next to the moonshot directory. This release builds from source with the
released coff; no pre-built image is needed.
# 1. clone both repositories side by side
git clone git@github.com:cofflang/coff.git c0-coff
git clone git@github.com:cofflang/moonshot.git moonshot
# 2. build moonshot
cd moonshot
./build.sh
./sync_addrs.shbuild.sh bootstraps coff1 itself if it is missing or older than its
source, using tcc when present and gcc otherwise, and then runs coff's full
bootstrap audit before letting the new compiler build anything. If you have
already bootstrapped coff by hand following its README, build.sh runs that
audit once and records the result. Either way the first build takes a few
minutes and every build after it is seconds.
./run_qemu.sh # headless: boots, checks the serial log, exits
./run_graphical.sh # a real QEMU window (needs qemu-ui-gtk on Arch)Both create disk.img if it does not exist. Log in as root with an empty
password; help lists the shell commands, help (command) explains one.
Some things to try: edit notes.txt, draw hero.spr, spawn3 gnista.elf
or spawn3 infecteria.elf for a graphical program in its own window, split and
Alt+Tab for more windows, Alt+W to close a program's window, mouse to
watch the pointer, eval 6 * 7 for the REPL.
run_graphical.sh forces QEMU's GTK window through XWayland. Under native
Wayland, GTK delivers clicks to a relative-pointer guest but almost no motion,
which looks exactly like a broken mouse driver and is not one.
./run_qemu.sh # the main gate: boot + about 40 serial-log invariants
./test_keyboard.sh # typing, caps lock, tab, alt-tab window switching
./test_shell.sh # spawn, kill, task lifecycle
./test_repl.sh # REPL expressions, JIT compilation, run-file
./test_disk.sh # filesystem write, sync, reload, read-back
./test_panic.sh # divide-by-zero panic with correct vector
./test_doublefault.sh # double fault caught on the IST1 stack
./test_fb_scroll.sh # framebuffer scroll correctness
./test_kakel_scroll.sh # window scroll clipping
./test_vga_scroll.sh # VGA text-mode fallback scroll
./verify_elf_backend.sh # the nine elf_tests/ programs run in ring 3, exit codes checked
./verify_alloc.sh # layout + sizeof + alloc() from ring 3
./verify_ticks.sh # ticks() from ring 3
./verify_windowed_ring3.sh # a ring-3 program printing into its own window
./verify_gnista.sh # the engine demo drawing real pixels
./verify_tavla.sh # graphical windows: focus strip, Alt+W, recovery
./verify_resize.sh # a graphical program surviving a window resize
./verify_klick.sh # the mouse driver, packet decoding, pointer drawing
./verify_login_reset.sh # a fresh window never inherits a loginEvery script boots the kernel in QEMU and passes or fails on what the serial
log or the framebuffer actually contains. The scripts that inject code into
the kernel (panic, double fault, the three scroll tests) restore the original
sources via a trap handler, so the tree is clean after they finish, pass or
fail. Several tests assume disk.img exists; run run_qemu.sh first.
moonshot has no way to receive a file from the host, so every ring-3 program
it ships is compiled with coff --elf, turned into a c0 function that writes
the bytes into a buffer, and written onto the filesystem at first boot. Those
byte arrays are the *_data.c0 files, and a byte array nobody can regenerate
is a binary blob with a c0 extension, so their sources are in programs/ and
./gen_programs.shrebuilds all of them with the same coff1 that builds the kernel. After it
runs, git diff on the *_data.c0 files is empty. If it is not, either a
program changed or the compiler's output did, and either way you want to
know. gen_elf_tests.py does the same for elf_tests/.
Gnista, the game engine the demo and Infecteria are built on, is its own
project; the copies in programs/ are what these seeds were built from.
The kernel is compiled by a binary, and a binary is where a Thompson-style
compiler backdoor would live. build.sh therefore never trusts coff1 on a
timestamp: it hashes the binary on every build against the hash recorded when
coff's bootstrap audit last passed (coff1.sha256, written by build.sh and
by nothing else), and refuses to produce a kernel on a mismatch. A coff1
with no recorded hash is audited before it compiles anything. The details,
the threat model and what is still trusted blind (GNU as and ld, python3)
are in coff's TRUST.md.
Contributions are welcome. Some things to know before opening a pull request:
- The kernel is compiled exclusively through
coff1, the self-hosted c0 compiler. Any new c0 language feature or builtin needed here must land in coff first. ./run_qemu.shand every test script must stay green.- After any change that shifts code size, run
./sync_addrs.shbefore booting. The kernel will silently crash at boot with stale JIT helper addresses. - After changing anything in
programs/orelf_tests/, regenerate the seeds and commit the result. - Keep it minimal. No new dependencies, no third-party code.
GPL-3.0-or-later. See LICENSE.
Copyright (C) 2026 tavro