From ffe32f6dda96b9a3173a10ee95c2799524d31b59 Mon Sep 17 00:00:00 2001 From: Astral <208268648+AstralZX@users.noreply.github.com> Date: Tue, 1 Sep 2026 08:00:05 +0200 Subject: [PATCH] Initial scaffold --- README.md | 58 +++++++++++++++ kama | 41 +++++++++++ lib/kama.sh | 185 ++++++++++++++++++++++++++++++++++++++++++++++++ tests/README.md | 7 ++ 4 files changed, 291 insertions(+) create mode 100644 README.md create mode 100755 kama create mode 100644 lib/kama.sh create mode 100644 tests/README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..3c05aa0 --- /dev/null +++ b/README.md @@ -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 download + extract source into the cache +kama build stage-build one package +kama install build + install into $ROOT +kama make fetch deps, build, install, purge temp deps (AUR-style) +kama purge remove temp build-time deps +kama info 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 diff --git a/kama b/kama new file mode 100755 index 0000000..171c68a --- /dev/null +++ b/kama @@ -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 [package ...] +# +# Commands: +# fetch download + extract source into the cache +# build stage-build one package +# install build + install into $ROOT (staging-aware) +# cmake high-level: fetch deps, build, install, purge temp deps +# purge remove temp build-time deps after a successful install +# info 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 diff --git a/lib/kama.sh b/lib/kama.sh new file mode 100644 index 0000000..dd63346 --- /dev/null +++ b/lib/kama.sh @@ -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" diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..806f290 --- /dev/null +++ b/tests/README.md @@ -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