- fetch_recipe_from_repos() iterates repos by priority, channels, with
mirror fallback — caches indexes at /cache/indexes/
- Conditional HTTP downloads (curl -z) to only re-fetch stale indexes
- build_registry and fetch-package prefer repos { } over remotes = [...]
when both are present
- Fallback chain: local .kap → repos (index-aware) → remotes (legacy)
kappa
Anywhere, any init, anytime.
A declarative, source-based package manager that doesn't care what init system you run. Or what bootloader. Or what CPU architecture. Kappa builds your entire system from source — and lets you swap the init system like you'd swap a wallpaper.
Why
Every other package manager picked a side. apt married systemd. pacman
shackled itself to Arch's ecosystem. emerge gave you choice but at the cost
of your weekend. Nix gave you reproducibility but took your filesystem with it.
Kappa is what you get when you stop negotiating. You declare what your system is, and kappa figures out how to build it. Change your mind about the init system? Rebuild only the packages that care — the other 800 stay put.
What it does
# Your system, in one file:
boot {
kernel = "linux"
init = "s6" # swap to "systemd" anytime
bootloader = "limine" # or "grub"
root = "/dev/sda1"
}
packages {
nginx { version = ">=1.24" }
postgresql {}
zlib {}
}
services {
nginx { enable = true }
}
Then:
kappa rebuild config.kap # builds everything, generates service files
kappa rebuild config.kap # boot.init = "openrc" — only 5 packages actually rebuild
Features that nobody else has
-
Init-system-as-configuration.
boot.init = "s6"→ generates s6 service directories. Change it to"systemd"→ regenerates.serviceunits. Change it to"openrc"→ generates init.d scripts. The package definitions don't know or care which init you picked. That's kappa's problem. -
Post-install init switching. Change
boot.init, runkappa rebuild, reboot. You're now on a different init system. Only packages that actually use${enabledinit}in their build scripts need recompiling. Everything else just gets new service files generated. -
Bootloader rollback. Every rebuild creates a fallback boot entry pointing at the previous generation's init. If the new one doesn't boot, the old one is one reboot away.
-
Parallel scheduler.
-w 4 -j 8means four packages building simultaneously, eight jobs each. The scheduler uses depth-based priority grouping so leaf dependencies unblock as much work as possible first. -
Package recipe caching. Declare
remotes = ["https://repo.example.com/"]in your config. Kappa fetches.kapfiles on demand, caches them, and only re-fetches when the remote version is newer. -
Source tarball caching. Downloaded once, stored at
$KAPPA_ROOT/cache/(default:/usr/local/kappa/cache/). Rebuilds don't touch the network unless versions change. -
Conflicts.
systemddeclaresconflicts = ["eudev", "elogind"]. The resolver catches mutual incompatibility before a build starts. -
Init-agnostic system config.
groups { wheel { gid = 998 } }— kappa creates the groups.system { hostname = "mybox" }— kappa writes/etc/hostname. Nosystemctl, norc-update, no init dependency.
5 init systems. 2 bootloaders. Zero lock-in.
| Init | Service location | Enable command |
|---|---|---|
| systemd | /etc/systemd/system/{name}.service |
systemctl enable |
| openrc | /etc/init.d/{name} |
rc-update add |
| s6 | /etc/s6/sv/{name}/run |
s6-rc-bundle-update |
| runit | /etc/sv/{name}/run |
ln -sf /etc/sv/{name} /var/service/ |
| dinit | /etc/dinit.d/{name} |
dinitctl enable |
| Bootloader | Config path |
|---|---|
| limine | /boot/limine.cfg |
| grub | /boot/grub/grub.cfg |
Quick start
Note for 0.1.x users: The default store root has moved from
/kappato/usr/local/kappa(FHS 3.0). SetKAPPA_ROOTto your existing/kappadirectory to keep using the old location.
# Build kappa (needs Clang 17+, CMake 3.20+, C++23)
cmake -B build -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
cmake --build build
# Write a config
cat > system.kap << 'EOF'
system { hostname = "kappa.local" }
packages { nginx {} }
services { nginx { enable = true } }
boot {
kernel = "linux"; init = "s6"; root = "/dev/sda1"; bootloader = "limine"
}
users { root { shell = "/bin/zsh" } }
remotes = ["https://packages.kappa-os.org/stable/"]
EOF
# Parse it
build/kappa parse-config system.kap
# Rebuild
build/kappa rebuild system.kap
Subcommands
| Command | What it does |
|---|---|
parse-package <file> |
Validate a .kap package definition |
parse-config <file> |
Validate a system configuration |
validate <file> |
Validate any kappa file |
format <file> |
Pretty-print to canonical style |
doctor <file> |
Check for issues and warnings |
resolve <config> |
Compute a build plan |
fetch <package> |
Download and verify source tarballs |
fetch-package <name> |
Fetch a package recipe from remotes |
build <package> |
Build a single package |
rebuild <config> |
Diff config against installed state, rebuild changed |
list |
Show installed packages |
rollback |
Show available generations |
License
BSD 2-Clause. Do whatever you want. Just don't sue us.
Contributing
See CONTRIBUTING.md. We're opinionated but we merge good code.