No description
Find a file
2026-01-15 22:44:47 +01:00
pkgs Add a naive cache cleaning implementation 2026-01-15 22:44:47 +01:00
.gitignore Improve package download and cache 2026-01-13 16:59:33 +01:00
kuno Add a naive cache cleaning implementation 2026-01-15 22:44:47 +01:00
README.md Add README 2026-01-14 21:41:11 +01:00

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, use has() 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.