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
This commit is contained in:
@@ -1,3 +1,154 @@
|
||||
<p align="center">
|
||||
<img src="https://git.spectoria.dev/repo-avatars/f111d66d4a9972c13eb8aa4dddc7fe2c39aabdf0ab0ff0dc27a31a9aa1c2e968" width="180" alt="kappa mascot" />
|
||||
</p>
|
||||
|
||||
# kappa
|
||||
|
||||
A alternative implementation of Iota for the ZereneOS project.
|
||||
**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.
|
||||
|
||||
Reference in New Issue
Block a user