#!/bin/sh
# build-apkovl.sh — produce a Mountaineer apkovl tarball from apkovl-src/.
#
# Usage: build-apkovl.sh <profile> [<output-path>]
#
# <profile> matches a [profiles.<name>] section in apkovl-src/manifest.toml.
# <output-path> defaults to ./out/mountaineer-<profile>-<version>.apkovl.tar.gz.
#
# Build environment: Alpine host (the canonical path) or any system with
# GNU tar 1.28+. macOS users: `brew install gnu-tar` and rerun.
#
# Reproducibility: best-effort. Same source tree + same builder version →
# same bytes. We sort filenames, pin uid/gid/mode, zero mtimes, and gzip
# with --no-name. Bit-identical across builder versions is a v0.2 goal.

set -eu

# --- locate GNU tar -----------------------------------------------------
find_gnu_tar() {
    for candidate in gtar tar; do
        if command -v "$candidate" >/dev/null 2>&1; then
            if "$candidate" --version 2>&1 | grep -q 'GNU tar'; then
                echo "$candidate"
                return 0
            fi
        fi
    done
    return 1
}

if ! TAR=$(find_gnu_tar); then
    cat >&2 <<'EOF'
error: GNU tar is required (deterministic --sort/--mtime/--numeric-owner).
       Alpine:  apk add tar
       macOS:   brew install gnu-tar
       Debian:  apt install tar    (GNU tar by default)
EOF
    exit 1
fi

# --- locate the source tree ---------------------------------------------
here() { cd "$(dirname "$0")/.." && pwd; }
ROOT="$(here)"
SRC="$ROOT/apkovl-src"
MANIFEST="$SRC/manifest.toml"

profile=""
output=""
extras=""
while [ $# -gt 0 ]; do
    case "$1" in
        --extra) shift; extras="$extras $1" ;;
        --)      shift; break ;;
        -*)      echo "unknown flag: $1" >&2; exit 2 ;;
        *)       if [ -z "$profile" ]; then profile="$1"
                 elif [ -z "$output" ]; then output="$1"
                 else echo "too many positional args" >&2; exit 2; fi ;;
    esac
    shift
done

if [ -z "$profile" ]; then
    echo "usage: $0 <profile> [<output-path>] [--extra <dir>]..." >&2
    exit 2
fi

if [ ! -f "$MANIFEST" ]; then
    echo "error: manifest not found at $MANIFEST" >&2
    exit 1
fi

# --- parse manifest.toml (intentionally minimal) ------------------------
# The manifest is small enough to grep. If it grows past what a few awks
# can handle, replace this block with a real parser.
version=$(awk -F'"' '/^version *= */ {print $2; exit}' "$MANIFEST")
sources=$(awk -v p="$profile" '
    $0 ~ "^\\[profiles\\."p"\\]" {in_section=1; next}
    in_section && /^\[/ {in_section=0}
    in_section && /^sources *=/ {
        sub(/.*\[/, "")
        sub(/\].*/, "")
        gsub(/"|[[:space:]]/, "")
        print
        exit
    }
' "$MANIFEST")

if [ -z "$version" ]; then
    echo "error: could not parse version from $MANIFEST" >&2
    exit 1
fi
if [ -z "$sources" ]; then
    echo "error: profile '$profile' not found in $MANIFEST" >&2
    exit 1
fi

[ -n "$output" ] || output="$ROOT/out/mountaineer-$profile-$version.apkovl.tar.gz"
mkdir -p "$(dirname "$output")"

# --- stage merged tree --------------------------------------------------
stage=$(mktemp -d)
trap 'rm -rf "$stage"' EXIT

oldifs="$IFS"
IFS=','
for src in $sources; do
    if [ ! -d "$SRC/$src" ]; then
        echo "error: source directory '$src' not found under apkovl-src/" >&2
        exit 1
    fi
    # tar-pipe handles symlinks, modes, and recursion portably. Later
    # sources overwrite earlier ones — declared order matters.
    (cd "$SRC/$src" && "$TAR" cf - .) | (cd "$stage" && "$TAR" xf -)
done
IFS="$oldifs"

# --extra <dir>... — merge in dynamically-generated content after the
# declared sources. Used by genapkovl to embed the target apkovl and the
# installer into the live apkovl at build time.
for extra in $extras; do
    if [ ! -d "$extra" ]; then
        echo "error: --extra directory '$extra' not found" >&2
        exit 1
    fi
    (cd "$extra" && "$TAR" cf - .) | (cd "$stage" && "$TAR" xf -)
done

# Drop .keep markers (they retain empty dirs in the source; the dir ships,
# the marker doesn't).
find "$stage" -name '.keep' -delete

# Zero mtimes on every staged path so tar's metadata is determined entirely
# by file content and sort order, not when the build ran. Skip symlinks —
# touch follows them by default and the runlevel symlinks (etc/runlevels/*)
# point at root-owned /etc/init.d/* files that the build user can't touch.
# tar --mtime='@0' zeroes the archive entries regardless.
find "$stage" ! -type l -exec touch -d '@0' {} + 2>/dev/null || \
    find "$stage" ! -type l -exec touch -t 197001010000.00 {} +

# --- emit deterministic tarball -----------------------------------------
SOURCE_DATE_EPOCH=0
export SOURCE_DATE_EPOCH

(cd "$stage" && "$TAR" \
    --sort=name \
    --owner=0 \
    --group=0 \
    --numeric-owner \
    --mtime='@0' \
    --format=ustar \
    -cf - . ) | gzip --no-name -9 > "$output"

echo "wrote $output"
( cd "$(dirname "$output")" && sha256sum "$(basename "$output")" )
