Files
kappa/README.md
T
huntedbytheirs dd984f96d4 feat: system-agnostic package manager — 5 inits, 2 bootloaders, parallel scheduler
Complete rewrite of kappa from a sequential build tool into a
system-agnostic package manager with runtime init switching.

Core additions:
- 5 init system backends: systemd, openrc, s6, runit, dinit
  (service file generation, enable/disable, init_paths)
- 2 bootloader backends: grub, limine (config generation, fallback entries)
- Parallel scheduler with worker pool, depth-based priority (Beta/Alpha/Zeta),
  atomic claiming, dependency tracking, deduplication, and failure propagation
- Init-switch impact analysis: only rebuild packages using ${enabledinit}
- Init-agnostic service definitions: flat NamedService blocks replace
  per-init nesting
- Package conflicts: mutual incompatibility detection in resolver
- System groups: init-agnostic group creation in DSL
- Init-agnostic hostname/timezone: direct /etc/hostname and /etc/localtime writes
- Source tarball caching at /kappa/cache/ with atomic write-then-rename
- Package recipe caching with remote fetching and version comparison
- remotes = [...] block in system config for package repositories
- Auto-fetch: rebuild resolves missing packages from remotes
- uninstall phase in package definitions
- ${enabledinit} eval variable for init-conditional builds
- Shared util module (to_lower, shell_escape)
- 54 integration tests across two shell test suites
- Comprehensive README and CONTRIBUTING guide

Bug fixes from review:
- CRITICAL: Replace std::system() with fork+execvp (command injection)
- CRITICAL: Fix scheduler deadlock on successful completion
- CRITICAL: Fix rebuild init/kernel/bootloader change detection
- HIGH: Fix path traversal via unsanitized package names in cache
- HIGH: Fix TOCTOU race in cache write with atomic rename
- HIGH: Fix formatter dropping remotes/imports blocks
- HIGH: Fix formatter stripping empty-string assert values
- HIGH: Fix formatter non-idempotent output (sorted key iteration)
- HIGH: Populate ${enabledinit} from boot.init in BuildStep
- MEDIUM: Fix data race on non-atomic scheduler stop flag
- MEDIUM: Fix compute_depths() traversal direction
- MEDIUM: Add runit to doctor supported-init warning
- MEDIUM: Extract to_lower/shell_escape to shared kappa::util
- MEDIUM: Consolidate generator declarations in headers
2026-07-31 05:32:57 -04:00

155 lines
5.1 KiB
Markdown

<p align="center">
<img src="https://git.spectoria.dev/repo-avatars/f111d66d4a9972c13eb8aa4dddc7fe2c39aabdf0ab0ff0dc27a31a9aa1c2e968" width="180" alt="kappa mascot" />
</p>
# 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:
```sh
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 `.service` units. 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`, run `kappa 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 8` means 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 `.kap` files on demand, caches them, and only
re-fetches when the remote version is newer.
- **Source tarball caching.** Downloaded once, stored at `/kappa/cache/`.
Rebuilds don't touch the network unless versions change.
- **Conflicts.** `systemd` declares `conflicts = ["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`. No `systemctl`, no `rc-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
```sh
# 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](CONTRIBUTING.md). We're opinionated but we merge good
code.