| pkgs | ||
| .gitignore | ||
| kuno | ||
| README.md | ||
Kuno
Build exactly the Linux system you need. Nothing more.
Kuno is a minimal Linux build system designed for maximum flexibility and experimentation. Whether you're building a rootfs tarball, a custom kernel for specific hardware, or testing different combinations of core utilities, kuno gives you complete control without the complexity.
Why Kuno?
Building minimal Linux systems lets you:
- Build rootfs-only - Create tarballs or container images without compiling a kernel
- Build full systems - Custom kernels with embedded initramfs, tailored for your hardware
- Swap core tools - Replace busybox with toybox, or mix implementations (busybox ls + GNU grep)
- Experiment freely - Test different init systems, minimal vs full-featured tools, specialized appliances
The goal: Test countless combinations of tools tailored to your specific needs. No bloat, just what you actually use.
Philosophy
"The best code is the code we don't write"
Kuno embraces simplicity:
- Pure POSIX shell - no complex build languages or DSLs
- Minimal abstractions - straightforward patterns over frameworks
- Composition over coupling - packages configure each other without hard dependencies
How It Works
Cross-Package Configuration
The core kuno pattern: packages configure other packages before they're built.
# In linux's prepare():
config linux init
config cmdline init # Config names are semantic, chosen for clarity
# In nitro's prepare():
has linux && config cmdline add rdinit=/bin/nitro
has busybox && config busybox add HOSTNAME MOUNT UMOUNT
# In linux's configure():
kconf_apply linux .config # Apply accumulated configuration
This enables composition without coupling. The nitro package can be built standalone, or it can configure linux/busybox when they're present in the build - no special APIs needed.
Config names are semantic: Linux uses linux and cmdline. The toolchain uses cflags and ldflags. Names are chosen for clarity, not tied to package names.
Conditional Dependencies
Kuno uses two-pass dependency resolution to support optional dependencies:
dependencies() {
require toolchain/mxm
# These only create dependencies if the packages exist
has linux && require linux
has busybox && require busybox
}
prepare() {
fetch_git nitro https://github.com/leahneukirchen/nitro.git v0.6
# Cross-package configuration happens here
has busybox && config busybox add HOSTNAME MOUNT UMOUNT
if has linux; then
config cmdline add rdinit=/bin/nitro
config linux add \
UNIX NET TMPFS \
PROC_FS SYSFS DEVTMPFS \
BINFMT_ELF BINFMT_SCRIPT
fi
}
Pass 1: Discovers unconditional dependencies (has() returns false)
Pass 2: Re-runs with has() enabled, adding conditional dependencies
This ensures correct build order - nitro's prepare() runs before busybox's configure(), so configs accumulate before they're applied.
Package Composition
Create variants without polluting base packages:
pkgs/
tinysshd/ # Clean base package
build
nitro/
build # Nitro init system
tinysshd/ # Variant: tinysshd configured for nitro
build
The variant nitro/tinysshd can require and configure the base tinysshd package, keeping both packages reusable and maintainable.
Architecture
- Language: Pure POSIX shell scripts
- Toolchain: GCC 15.1.0 via musl-cross-make (supports multiple architectures)
- Linking: All binaries statically linked against musl libc
- Builds: Reproducible with cached downloads (HTTP tarballs or git archives)
- Dependencies: Automatic resolution via topological sorting
The toolchain is built via musl-cross-make, enabling cross-compilation for various target architectures (x86_64, arm64, etc.) from any host.
Build Process
Each package defines stages in a build file:
dependencies() → prepare() → configure() → build() → install() → post_install()
All stages are optional. Packages install directly into build/x86_64/rootfs/ using the cdrootfs helper. The linux kernel's post_install() stage packages the entire rootfs into initramfs.cpio.gz and embeds it.
Directory Structure
dl/ # Download cache (tarballs + git archives)
build/
host/mxm/ # musl-cross-make toolchain
x86_64/ # Target architecture builds
pkgs/ # Package build directories
configs/ # Configuration files (semantic names)
rootfs/ # Root filesystem (install destination)
pkgs/
toolchain/mxm/ # Toolchain package
busybox/ # Target packages
linux/
nitro/
tinysshd/ # Package variants via subdirectories
Core Concepts
Config System
The config command manages simple text files (one value per line) in build/x86_64/configs/:
config cflags init # Create config file
config cflags add -O2 -pipe # Append values (one per line)
config cflags add -ffunction-sections # Add more values
config cflags contains -O2 # Check if present (returns 0/1)
config cflags flatten # Output: -O2 -pipe -ffunction-sections
The flatten command is particularly useful for passing accumulated config values to build tools:
CFLAGS="$(config cflags flatten)" make
For kernel-style .config files, use kconf_apply:
kconf_apply linux .config # Converts to CONFIG_FEATURE=y format
Conditional Patterns
The has() function checks if a package is in the current build:
has busybox && config busybox add MOUNT # Conditional config
has linux && require linux # Conditional dependency
This pattern reads naturally: "if we have this package, configure/require it."
Package Stages
- dependencies(): Declare dependencies with
require, usehas()for conditionals - prepare(): Fetch sources, initialize configs, configure other packages
- configure(): Apply configs, run configure scripts
- build(): Compile the software
- install(): Install to rootfs via
cdrootfs "/bin" - post_install(): Run after all dependencies installed (kernel uses this for initramfs)
Cross-Compilation
The mxm helper sets up the musl-cross-make environment:
mxm make -j"$(nproc)" # Cross-compile with musl toolchain
It exports CC, CXX, AR, RANLIB, CFLAGS, and LDFLAGS for static linking.
Example: Building Packages
# Build a single package (dependencies built automatically)
./kuno build busybox
# Build a package variant
./kuno build nitro/tinysshd
# Configure a package
./kuno menuconfig linux
# Verbose build output
KUNO_VERBOSE=1 ./kuno build linux
The Vision
Kuno is designed to make building custom Linux systems fun and exploratory:
- Want a minimal container rootfs? Build just the packages you need, skip the kernel.
- Building for a Raspberry Pi? Custom kernel with only the drivers you need.
- Curious about toybox vs busybox? Swap them and compare.
- Need a network appliance? Strip everything except networking tools.
The architecture supports all of this through simple, composable patterns. No special modes, no complex configuration - just build what you need.