Portability (Linux distro-agnostic): - Remove hardcoded Clang compiler enforcement; GCC now builds - Add find_package(Threads REQUIRED) for older glibc - Add cmake install() target - FHS 3.0 default root: /kappa -> /usr/local/kappa - fs::path operator/ for all init/bootloader paths (fixes prefix fragility) - Multi-distro zoneinfo search (FHS, NixOS, Guix, alt) - Portable tar extraction (drop GNU-only --no-same-permissions) - Runit enable/disable commands now prefix-aware - --root CLI flag before/after subcommand, lazy directory creation - Shebang constants de-duplicated to types.hpp Correctness (race conditions, UB, corruption): - Fix CWD race in scheduler: per-child chdir() instead of process-global - Fix UB const_cast in exec_cmd/exec_capture: mutable argv buffers - Fix non-atomic installed DB writes: tmp+rename pattern - Fix read_file() no longer calls exit(1), throws instead - Fix silent catch(...) parse errors now print diagnostics - Fix rebuild false positives with config_hash change detection - Fix s6 disable_cmd copy-paste bug (was identical to enable) - Fix runit enable_cmd incomplete, disable_cmd wrong target - Fix dinit env vars: functional env-file + companion .env Quality: - Add -Wall -Wextra -Wpedantic to CMake, fix 2 pre-existing warnings - Move parse_int from error.hpp to parse_util.hpp - Fix hash verification guard checks all three hash types - Check patch return code in fetch.cpp - Add explicit system_dir creation in ensure_directories() - Add resolve to needs_dirs for build_registry() consistency - Update stale /kappa path references in examples - Remove inaccurate -Werror claim in CONTRIBUTING.md - Add build-gcc/ and agent dirs to .gitignore - Suppress clang-tidy portability-avoid-pragma-once - Fix .gitignore /kappa pattern (was matching include/kappa/) - Delete stale vcpkg_installed/ directory 54/54 tests pass. Builds on Clang and GCC with 0 warnings.
160 lines
5.3 KiB
Markdown
160 lines
5.3 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_ROOT/cache/`
|
|
(default: `/usr/local/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
|
|
|
|
> **Note for 0.1.x users**: The default store root has moved from `/kappa` to
|
|
> `/usr/local/kappa` (FHS 3.0). Set `KAPPA_ROOT` to your existing `/kappa`
|
|
> directory to keep using the old location.
|
|
|
|
```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.
|