A build system for compiling your own C applications to run as BareMetal apps: a musl libc port (syscalls dispatched into libBareMetal calls instead of trapped), an EXT2 file I/O layer via lwext4, a lwIP-based TCP/IP networking, Mbed TLS for TLS/SSL, curl/libcurl (HTTP/HTTPS only) on top of all of it, SQLite on top of EXT2 via its own small VFS, a CPython 3.14 interpreter (python.app, see PYTHON.md) on top of everything else, and a Lua 5.5 interpreter (lua.app, see LUA.md). See OPENISSUES.md for what's supported and what isn't.
gcc, ld, make, curl, tar, unzip (a standard Linux toolchain works).
Run once, from this directory:
./setup.sh
This downloads musl 1.2.6 and applies the BareMetal port patch, then downloads lwIP 2.2.0, Mbed TLS 3.6.6, curl 8.21.0, the SQLite 3.46.1 amalgamation, a pinned lwext4 commit, CPython 3.14.7, and Lua 5.5.1 (all used as-is, unmodified), creating build/musl-1.2.6/, build/lwip-2.2.0/, build/mbedtls-3.6.6/, build/curl-8.21.0/, build/sqlite-3.46.1/, build/lwext4-58bcf89/, build/Python-3.14.7/, and build/lua-5.5.1/. All are pinned versions -- the patch and the port/lwip_port//port/mbedtls_port//port/curl_port//port/sqlite_port//port/lwext4_port//port/python_port//port/lua_port/ glue are written against these exact releases. CPython also needs a one-time native host build (see PYTHON.md) to generate a handful of architecture-independent sources (the parser tables, frozen bytecode, ...) -- the only step here that isn't just a fetch-and-compile, and the reason a first ./setup.sh run takes noticeably longer than a re-run. setup.sh finishes by building python.app and lua.app itself, so they're ready to go immediately -- no separate build-app.sh invocation needed unless you change port/python_port/'s own sources (see Building an app below). (setup.sh just runs scripts/get-musl.sh, scripts/get-lwip.sh, scripts/get-mbedtls.sh, scripts/get-curl.sh, scripts/get-sqlite.sh, scripts/get-lwext4.sh, scripts/get-python.sh, and scripts/get-lua.sh in turn, if you want to re-run one on its own.)
./build-app.sh myapp.c # builds your own app -> myapp.app
Downloaded sources and intermediate .o files live under build/; the final .app is placed here in the top-level directory. It's a flat binary linked at 0xFFFF800000000000 (see port/c.ld), ready to load as a BareMetal app (e.g. copy it onto a disk image formatted with a plain EXT2 filesystem -- mkfs.ext2 -- and load it from the BareMetal monitor or run it as a unikernel).
./clean.sh removes library code and build artifacts (.o/.a/.app) from this directory and build/ without touching the fetched musl-1.2.6//lwip-2.2.0//mbedtls-3.6.6//curl-8.21.0//sqlite-3.46.1//lwext4-58bcf89//Python-3.14.7//lua-5.5.1/ zip/tarball.
python.app -- the CPython interpreter itself -- is already built by
./setup.sh (see Setup above), ready to go with no separate step. To
rebuild it after changing anything under port/python_port/, run the
same three-source-file build setup.sh itself runs (it's assembled
from this port's own glue rather than a single .c file):
./build-app.sh port/python_port/python.c port/python_port/config_baremetal.c port/python_port/frozen_encodings_baremetal.c
See PYTHON.md for what each of those does and how to point
python.app at your own program.
setup.sh-- fetches musl, lwIP, Mbed TLS, curl, and lwext4 (see Setup above).build-app.sh-- builds an app (see Building an app above).clean.sh-- removes build artifacts.hello.c-- minimal demo app (muslprintf, argc/argv/envp).clock.c-- prints the current wall-clock time (viatime()and a directb_system(WALLCLOCK, ...)call) and time elapsed since boot (clock_gettime(CLOCK_MONOTONIC, ...)).crawler.c/https_crawler.c-- a small HTTP(S) web crawler, speaking raw HTTP by hand overport/net_shim.c's sockets and TLS by hand overport/tls_shim.c's mbedTLS wrapper.curltest.c-- a minimal demo of libcurl's easy interface (an HTTP/ HTTPS GET) -- the same sockets and the same vendored mbedTLS as above, but reached through curl's own APIs instead.sqltest.c-- a minimal demo of SQLite: creates a table on a real EXT2-backed database file, inserts rows across two transactions, and queries them back -- exercisingport/sqlite_port/sqlite_vfs.c's read/write/journal handling end to end.fs_test.c-- exercisesport/ext4_shim.c's POSIX file I/O end to end: create/read/write/lseek/fstat/stat/unlink,chdir/getcwd,mkdir/opendir/readdir/rmdir, andsymlink/readlink, all against the EXT2 image lwext4 mounts.threads.c-- exercisesport/thread_shim.c's cooperative pthreads end to end:pthread_create/join/detach/self/equal, mutexes (normal/trylock/recursive), condition variables (signal/broadcast/timedwait), rwlocks,pthread_once, thread-specific data (pthread_key_*and__thread), spinlocks, barriers, andsched_yield.port/python_port/python.c(+config_baremetal.c+frozen_encodings_baremetal.c) -- builds intopython.app, a real CPython 3.14.7 interpreter. Runs/pylib/main.pyoff the EXT2 disk image as its program (port/python_port/install-main.shdeploys one there) -- seePYTHON.mdfor the full story of how this port works, what's supported, and how to run your own script.port/lua_port/lua.c-- builds intolua.app, a real Lua 5.5.1 interpreter with the full standard library. Runs/lua/main.luaoff the EXT2 disk image as its program (port/lua_port/install-main.shdeploys one there) -- seeLUA.md.port/-- the port glue every app links against:crt0.c,c.ld-- startup and linker script for the flat-binary, ring-0, fixed-address BareMetal environment (no ELF loader, no syscall trap).posix_shim.c/.h-- the syscall dispatcher musl's patchedsyscall_arch.hcalls into, plus the heap (brk/mmap) backing it.ext4_shim.c/.h-- POSIX file I/O (open/read/write/stat/mkdir/readdir/symlink/chdir/...) on top of a real EXT2 filesystem, mounted and served through lwext4.lwext4_port/-- lwext4's block device glue overb_nvs_read/b_nvs_write, plus the EXT2-only feature config (generated/ext4_config.h).net_glue.c/.h,net_shim.c/.h,lwip_port/-- a blocking BSD-socket-shaped layer over lwIP's raw callback API, plus the Ethernet netif driver and port config.dns_shim.c--gethostbyname(), backed by lwIP's resolver.thread_shim.c/.h-- cooperative pthreads (SYS_clone/SYS_futex/SYS_sched_yield), scheduled by ab_system(CALLBACK_TIMER, ...)-driven round-robin tick -- see its own file header for the design andOPENISSUES.md's "Process model" section for what's supported.tls_shim.c/.h,mbedtls_port/-- a small blocking HTTPS-shaped TLS client wrapper over Mbed TLS, plus its port config (baremetal_mbedtls_config.h) and RNG hook (entropy_hardware_poll.c, viardrand).curl_port/curl_config.h-- libcurl's build config for this port (HTTP/HTTPS only, mbedTLS backend,gethostbyname()-based resolver, no threads -- see its own file header andOPENISSUES.md's "libcurl" section for the reasoning behind each).sqlite_port/sqlite_baremetal_config.h,sqlite_port/sqlite_vfs.c-- SQLite's build config for this port (SQLITE_OS_OTHER=1, single-threaded, no WAL/mmap/load-extension -- see its own file header) and the smallsqlite3_vfsimplementation it requires in place of SQLite's ownos_unix.c, built directly overposix_shim.c/ext4_shim.cthe same waytls_shim.c/net_shim.care (seesqlite_vfs.c's own header andOPENISSUES.md's "SQLite" section for the reasoning behind each choice).libBareMetal.c/.h/.asm-- the low-level calls into the BareMetal kernel (b_output,b_net_tx, ...) everything above is built on.python_port/-- CPython's build config for this port (pyconfig.h,pyconfig_baremetal.h), this port's own entry point and built-in module table in place of CPython's normalPrograms/python.c/generatedModules/config.c(python.c/config_baremetal.c), the frozenencodingsslice core init needs before any real file can be read (frozen_encodings_baremetal.c/frozen_encodings/), and the deploy scripts (install-stdlib.sh/install-main.sh) -- seePYTHON.mdfor the reasoning behind each.lua_port/-- this port's own entry point for Lua (lua.c, in place of Lua's standalonesrc/lua.c), the deploy script (install-main.sh), and a smoke test (main_test.lua) -- seeLUA.md.
scripts/-- the fetch scriptssetup.shcalls:get-musl.sh-- downloads musl 1.2.6 and appliesport/musl_port/musl-1.2.6-baremetal.patch(syscall transport, TLS bootstrap, cancellation-point syscalls, and the two raw-syscallasm sites --clone/__unmapself-- thread_shim.c's threads need routed through the same dispatcher), then installsport/musl_port/musl-1.2.6-config.makas musl'sconfig.mak(equivalent to running musl's./configurewith the flags this port needs, without you having to runconfigureyourself).get-lwip.sh-- downloads lwIP 2.2.0. lwIP is vendored unmodified; all lwIP-side port work lives inport/lwip_port/andport/net_glue.c/net_shim.cinstead of patches to lwIP itself.get-mbedtls.sh-- downloads Mbed-TLS 3.6.6. Mbed-TLS is vendored unmodified; all Mbed-TLS-side port work lives inport/tls_shim.cinstead of patches to Mbed-TLS itself.get-curl.sh-- downloads curl 8.21.0. curl is vendored unmodified too; all curl-side port work lives inport/curl_port/curl_config.hinstead of patches to curl itself.get-sqlite.sh-- downloads the SQLite 3.46.1 amalgamation (sqlite3.c/sqlite3.h). Vendored unmodified as well; all SQLite-side port work lives inport/sqlite_port/instead of patches tosqlite3.citself.get-lwext4.sh-- downloads a pinned lwext4 commit (its last tagged release predates six years of upstream fixes). Vendored unmodified; all lwext4-side port work lives inport/ext4_shim.candport/lwext4_port/instead of patches to lwext4 itself.get-python.sh-- downloads CPython 3.14.7. Vendored unmodified; all Python-side port work lives inport/python_port/instead of patches to CPython itself -- seePYTHON.md.get-lua.sh-- downloads Lua 5.5.1 (checksum-verified). Vendored unmodified; all Lua-side port work lives inport/lua_port/-- seeLUA.md.
This is not a general-purpose POSIX environment: no fork/exec, no signals (yet?), TCP/UDP only (no raw sockets exposed), 30s timeout on blocking socket calls. Threading (pthread_create and friends, threads.c) works, but as cooperative user-level threads on one core, not real kernel threads -- see port/thread_shim.c's file header. See OPENISSUES.md for the full list and the reasoning behind each cut.