Initial scaffold

This commit is contained in:
Astral
2026-09-01 08:00:05 +02:00
commit ffe32f6dda
4 changed files with 291 additions and 0 deletions
+58
View File
@@ -0,0 +1,58 @@
# Kama (窯)
**Kama** — the kiln — is the package manager for [Keru OS](https://github.com/AstralZX/KeruOS).
It's a single shell script (plus a small library) that builds packages from
source by executing their recipes. It is deliberately minimal: there is no
dependency resolver, no package database, and no binary format. Kama reads a
`make.conf`, and it runs whatever each recipe tells it to.
## Why shell?
Kama is the whole point of Keru stated plainly: the package manager shouldn't
be more complex than the packages it builds. It's not a program that manages
packages as much as a *runner* for recipe scripts — the clever part lives in
the recipes, not the mangement machinery.
## Usage
```
kama fetch <pkg> download + extract source into the cache
kama build <pkg> stage-build one package
kama install <pkg> build + install into $ROOT
kama make <pkg> fetch deps, build, install, purge temp deps (AUR-style)
kama purge remove temp build-time deps
kama info <pkg> show recipe metadata
```
## Configuration
Kama reads a `make.conf` (default `/etc/kama/make.conf`) for global build
flags and profile choices. Key variables:
- `ROOT` — target rootfs the packages install into
- `CACHEDIR` — persistent source + build cache
- `CFLAGS` / `MAKEFLAGS` — passed to every build
- `PKGDIR` — where the recipe repository lives (default: sibling `kama-packages`)
## Recipes
Packages are plain POSIX shell scripts. The syntax splits into two tiers:
- **Easy tier** — declare `name`, `version`, `url`, then a `build()` and
`install()`. Kama handles fetch + extract automatically.
- **Advanced tier** — override `pkg_fetch`, add `pkg_post`, `pkg_split`,
`provides`, `conflicts`, `arch`, etc.
See [the recipe format](KeruOS/blob/main/docs/recipe-format.md) in the KeruOS
repo for the full spec.
## Dependencies
Dependencies are handled AUR-style: build-time deps are fetched, compiled, and
stripped away once the real package is built, leaving only what the system
needs. This is the foundation of Keru's "nothing you didn't ask for" ethos.
## License
AGPL-3.0
Executable
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env sh
# kama — the Keru OS package manager
#
# Kama (窯, "kiln") builds packages from source by executing shell recipes.
# It is deliberately minimal: it reads make.conf and runs whatever each
# recipe tells it to. No dependency resolver, no package database.
#
# Usage:
# kama <command> [package ...]
#
# Commands:
# fetch <pkg> download + extract source into the cache
# build <pkg> stage-build one package
# install <pkg> build + install into $ROOT (staging-aware)
# cmake <pkg> high-level: fetch deps, build, install, purge temp deps
# purge remove temp build-time deps after a successful install
# info <pkg> show a recipe's metadata
set -eu
SELF_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SELF_DIR/lib/kama.sh"
cmd="${1:-help}"
shift || true
case "$cmd" in
fetch) kama_fetch "$@" ;;
build) kama_build "$@" ;;
install) kama_install "$@" ;;
make) kama_make "$@" ;;
purge) kama_purge ;;
info) kama_info "$@" ;;
help|--help|-h)
msg "Keru OS — Kama (窯) package manager"
cycle
;;
*)
die "unknown command: $cmd (try: kama help)"
;;
esac
+185
View File
@@ -0,0 +1,185 @@
# kama.sh — shared core library for the Kama package manager
#
# Sources make.conf, provides helpers, and implements the fetch/build/
# install/temp-dep logic. All the messy bits live here so recipes stay clean.
PREFIX="\033[1;36m[ kama ]\033[0m"
# --- config ---------------------------------------------------------
# Where the package recipe repository lives (kama-packages repo).
PKGDIR="${PKGDIR:-$SELF_DIR/../kama-packages}"
# merge a make.conf if one is provided
MAKE_CONF="${MAKE_CONF:-/etc/kama/make.conf}"
[ -f "$MAKE_CONF" ] && . "$MAKE_CONF"
ROOT="${ROOT:-/tmp/keru/root}"
CACHEDIR="${CACHEDIR:-/var/cache/kama}"
SRCDIR="$CACHEDIR/src"
DLDIR="$CACHEDIR/dl"
STAGEDIR="$CACHEDIR/stage"
# --- message helpers -------------------------------------------------
msg() { printf '%b %s\n' "$PREFIX" "$*"; }
warn() { printf '%b \033[1;33mwarning:\033[0m %s\n' "$PREFIX" "$*"; }
die() { printf '%b \033[1;31merror:\033[0m %s\n' "$PREFIX" "$*" >&2; exit 1; }
# --- easy/advanced tier mapping --------------------------------------
# If a recipe defines the easy-tier `build`/`install` sugar (and doesn't
# define the advanced pkg_* forms), alias them. This keeps simple recipes
# simple and gives advanced recipes full control.
map_tiers() {
# map build -> pkg_build unless pkg_build is overridden
if declare -f build >/dev/null 2>&1 && ! declare -f pkg_build >/dev/null 2>&1; then
pkg_build() { build; }
fi
if declare -f install >/dev/null 2>&1 && ! declare -f pkg_install >/dev/null 2>&1; then
pkg_install() { install; }
fi
# advanced recipes may define pkg_build/pkg_install directly — leave as-is
}
# --- default fetch: download + auto-extract -----------------------------
default_fetch() {
local tarball="$DLDIR/$(basename "$url")"
mkdir -p "$DLDIR" "$SRCDIR"
if [ ! -f "$tarball" ]; then
msg "downloading $url"
wget -q --show-progress "$SOURCE_MIRROR/$(basename "$url")" -O "$tarball" \
|| wget -q --show-progress "$url" -O "$tarball" \
|| die "download failed for $name"
else
msg "using cached source for $name"
fi
local s="$SRCDIR/$name"
rm -rf "$s"; mkdir -p "$s"
msg "extracting $name"
case "$tarball" in
*.tar.gz|*.tgz) tar xf "$tarball" -C "$s" --strip-components=1 ;;
*.tar.xz|*.txz) tar xJf "$tarball" -C "$s" --strip-components=1 ;;
*.tar.bz2|*.tbz2) tar xjf "$tarball" -C "$s" --strip-components=1 ;;
*.tar) tar xf "$tarball" -C "$s" --strip-components=1 ;;
*.zip) unzip -q "$tarball" -d "$s.tmp" && \
mv "$s.tmp"/*/* "$s/" ; rm -rf "$s.tmp" ;;
*) die "unsupported archive type: $tarball" ;;
esac
}
# --- locate a recipe --------------------------------------------------
find_recipe() {
local pkg="$1"
for rec in \
"$PKGDIR/$pkg/PKGBUILD" \
"$PKGDIR/$pkg.sh" \
"$PKGDIR/$pkg"/*.sh ; do
[ -f "$rec" ] && { printf '%s' "$rec"; return 0; }
done
return 1
}
# --- fetch + extract source -------------------------------------------
# --- fetch + extract source -------------------------------------------
kama_fetch() {
[ $# -ge 1 ] || die "fetch: missing package name"
for pkg in "$@"; do
local recipe; recipe="$(find_recipe "$pkg")" || die "no recipe for '$pkg'"
msg "fetching source for $pkg"
. "$recipe"
map_tiers
local src="$SRCDIR/$name"
if [ -d "$src" ] && [ -n "$(ls -A "$src" 2>/dev/null)" ]; then
msg "using cached source for $pkg"
continue
fi
# advanced recipe overrides pkg_fetch -> use it; otherwise default_fetch
if declare -f pkg_fetch >/dev/null 2>&1; then
pkg_fetch
else
default_fetch
fi
[ -d "$src" ] && [ -n "$(ls -A "$src" 2>/dev/null)" ] \
|| die "fetch produced no source for $pkg"
done
}
# --- stage-build a single recipe ---------------------------------------
kama_build() {
[ $# -ge 1 ] || die "build: missing package name"
for pkg in "$@"; do
local recipe; recipe="$(find_recipe "$pkg")" || die "no recipe for '$pkg'"
msg "building $pkg"
. "$recipe"
map_tiers
local src="$SRCDIR/$name"
local pkgdir="$STAGEDIR/$name"
[ -d "$src" ] && [ -n "$(ls -A "$src" 2>/dev/null)" ] || kama_fetch "$pkg"
rm -rf "$pkgdir"; mkdir -p "$pkgdir"
( cd "$src" && pkg_build ) || die "build failed for $pkg"
msg "staging install for $pkg"
( cd "$src" && pkg_install ) || die "install failed for $pkg"
( cd "$src" && pkg_post ) || die "post-install failed for $pkg"
pkg_split || true
done
}
# --- install staging dir into ROOT --------------------------------------
install_staged() {
local pkg="$1"
local pkgdir="$STAGEDIR/$pkg"
[ -d "$pkgdir" ] || die "nothing staged for '$pkg'"
msg "installing $pkg into \$ROOT"
( cd "$pkgdir" && cp -a . "$ROOT" )
}
# --- high-level: make a package (deps + build + install + purge) --------
kama_make() {
[ $# -ge 1 ] || die "make: missing package name"
for pkg in "$@"; do
local recipe; recipe="$(find_recipe "$pkg")" || die "no recipe for '$pkg'"
. "$recipe"
# temp deps: build, stage, install, then purge after
if [ -n "${deps+x}" ] && [ ${#deps[@]} -gt 0 ]; then
msg "building temp deps for $pkg: ${deps[*]}"
kama_make "${deps[@]}"
fi
kama_build "$pkg"
install_staged "$pkg"
if [ "${PURGE_TEMP_DEPS:-1}" = "1" ] && [ -n "${deps+x}" ] && [ ${#deps[@]} -gt 0 ]; then
msg "purging temp deps for $pkg: ${deps[*]}"
kama_purge "${deps[@]}"
fi
done
}
# --- show recipe metadata ------------------------------------------------
kama_info() {
[ $# -ge 1 ] || die "info: missing package name"
local recipe; recipe="$(find_recipe "$1")" || die "no recipe for '$1'"
. "$recipe"
printf 'name : %s\n' "$name"
printf 'version : %s\n' "$version"
printf 'url : %s\n' "$url"
printf 'deps : %s\n' "${deps[*]:-}"
printf 'runtime_deps: %s\n' "${runtime_deps[*]:-}"
}
# --- purge temp deps from ROOT --------------------------------------------
# NOTE: This is intentionally naive for the scaffold. A real implementation
# tracks what each recipe installed and removes exactly that, plus clears the
# staged dir. For now it clears the stage so a re-make re-pulls.
kama_purge() {
for pkg in "$@"; do
msg "purging temp dep '$pkg' (stage)"
rm -rf "$STAGEDIR/$pkg"
done
}
# --- init ----------------------------------------------------------------
mkdir -p "$ROOT" "$CACHEDIR" "$SRCDIR" "$DLDIR" "$STAGEDIR"
+7
View File
@@ -0,0 +1,7 @@
# Tests will live here.
# Planned:
# - recipe.sh unit tests for the easy/advanced tier mapping
# - fetch.sh fake download + extract
# - deps.sh AUR-style temp dep build + purge
# - make.conf.sh profile variable expansion
# - integration.sh end-to-end: build a tiny package into a throwaway ROOT