#!/usr/bin/env bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2026 ArchR (https://github.com/archr-linux/Arch-R)
#
# gen-pacman-repo: build a pacman repo (archr-core.db + .pkg.tar.zst per
# package) out of a finished ArchR build tree.
#
# Inputs:
#   BUILD_DIR     - the build.* dir that has install_pkg/<pkg>/ trees
#                   (defaults to the most recent build.*-RK3326 under cwd)
#   OUT_DIR       - where the .pkg.tar.zst and .db get written
#                   (defaults to ${BUILD_DIR}/../repo-out)
#   CHANNEL       - stable|next|dev. Goes into PKGINFO and the .db name
#                   (defaults to dev)
#   SIGNER        - GPG key id to sign packages and the .db with.
#                   If empty, packages are not signed (good for local CI).
#
# Output:
#   ${OUT_DIR}/archr.db (+.sig if signed)
#   ${OUT_DIR}/archr.files
#   ${OUT_DIR}/<pkg>-<ver>-<rel>-aarch64.pkg.tar.zst (+.sig)
#
# This is the GitHub-Releases-as-mirror path: after gen-pacman-repo,
# `gh release create repo-${CHANNEL} ${OUT_DIR}/*` uploads everything
# and pacman clients pick it up via the mirrorlist URL.

set -euo pipefail

BUILD_DIR="${BUILD_DIR:-$(ls -1dt build.*-RK3326*aarch64* 2>/dev/null | head -1)}"
[ -z "${BUILD_DIR:-}" ] && { echo "BUILD_DIR not set and no build.*-RK3326*aarch64* found"; exit 2; }
[ -d "${BUILD_DIR}/install_pkg" ] || { echo "no install_pkg/ under ${BUILD_DIR}"; exit 2; }

OUT_DIR="${OUT_DIR:-${BUILD_DIR}/../repo-out}"
CHANNEL="${CHANNEL:-dev}"
SIGNER="${SIGNER:-}"

REPO_NAME="archr"
ARCH="aarch64"

mkdir -p "${OUT_DIR}"
echo "gen-pacman-repo: build=${BUILD_DIR} out=${OUT_DIR} channel=${CHANNEL} signer=${SIGNER:-none}"

# Per-package metadata. Today the package.mk format does not carry a
# clean semver, so we synthesize: PKG_VERSION if numeric, else the
# build date. Real PKGBUILDs replace this when Arch-ification 2.3 lands.

build_one_pkg() {
    local src="$1"
    local dirname
    dirname="$(basename "${src}")"

    # Skip anything that is not an actually built package tree.
    [ -d "${src}" ] || return 0

    # The install_pkg dir name is "<pkgname>-<version_or_githash>". Strip
    # the trailing suffix to recover the package name; the suffix is
    # either a 40-char git hash or starts with a digit (semver).
    local name version
    name="$(echo "${dirname}" | sed -E 's/-[0-9a-f]{40}$//; s/-[0-9].*$//')"

    # Try to recover the exact PKG_VERSION from a matching package.mk
    # under either the ArchR overlay or the top-level packages tree.
    # Search any depth; multiple categories share the same package name
    # in some cases, take the ArchR overlay first.
    local pkgmk=""
    pkgmk="$(find projects/ArchR/packages -mindepth 2 -maxdepth 6 \
                -path "*/${name}/package.mk" -print -quit 2>/dev/null)"
    if [ -z "${pkgmk}" ]; then
        pkgmk="$(find packages -mindepth 2 -maxdepth 6 \
                    -path "*/${name}/package.mk" -print -quit 2>/dev/null)"
    fi

    version=""
    if [ -n "${pkgmk}" ]; then
        version="$(awk -F'"' '/^PKG_VERSION/ {print $2; exit}' "${pkgmk}")"
    fi
    # Some package.mk files set PKG_VERSION dynamically, e.g.:
    #   PKG_VERSION="$(get_pkg_version gstreamer)"
    # awk pulls the unresolved shell expansion as a string, which then
    # turns into garbage like "..get_pkg_version.gstreamer." after the
    # charset filter. Detect any $( ... ), :- or ${ and bail to the
    # dirname fallback below, which holds the version the build system
    # actually substituted.
    case "${version}" in
        *'$('* | *'${'* ) version="" ;;
    esac
    # Fallback: pull the suffix back out of the dirname.
    if [ -z "${version}" ]; then
        version="${dirname#${name}-}"
    fi
    version="${version:-$(date +%Y%m%d)}"

    # pacman version must match [a-zA-Z0-9.+_]+. Long hashes are fine
    # by this charset, but '/' or '~' would break: tr the rest. Use
    # printf instead of echo to avoid the trailing newline being
    # turned into a stray '.' by tr -c.
    version="$(printf '%s' "${version}" | tr -c 'a-zA-Z0-9.+_' '.')"

    # pkgrel comes from the package's PKG_REV (the standard knob for "same
    # upstream version, new package build", e.g. a changed local overlay
    # like a joypad autoconfig). Hardcoding 1 here meant any local-only
    # change never produced a newer package, so pacman never saw it as an
    # update. Default to 1 when PKG_REV is unset or dynamic.
    local pkgrel=1
    if [ -n "${pkgmk}" ]; then
        local rev
        rev="$(awk -F'"' '/^PKG_REV/ {print $2; exit}' "${pkgmk}")"
        case "${rev}" in
            ''|*'$('* | *'${'* ) : ;;
            *) pkgrel="${rev}" ;;
        esac
    fi
    local outfile="${OUT_DIR}/${name}-${version}-${pkgrel}-${ARCH}.pkg.tar.zst"

    # .PKGINFO sidecar that pacman reads on install. pkgbase is
    # required by repo-add (pacman 7+); xdata pkgtype=pkg matches
    # makepkg output so clients treat us as a normal split-package.
    local pkginfo="${src}/.PKGINFO"
    cat >"${pkginfo}" <<EOF
# Generated by gen-pacman-repo
pkgname = ${name}
pkgbase = ${name}
xdata = pkgtype=pkg
pkgver = ${version}-${pkgrel}
pkgdesc = ArchR ${name} (channel: ${CHANNEL})
url = https://arch-r.io
builddate = $(date +%s)
packager = ArchR Build <build@arch-r.io>
size = $(du -sb "${src}" | cut -f1)
arch = ${ARCH}
license = GPL
EOF

    # Pack with .PKGINFO as the FIRST tar entry; pacman/repo-add stream
    # the archive and need .PKGINFO up front to validate. Drop the "./"
    # prefix so listings match what makepkg produces.
    (
        cd "${src}"
        filelist="$(mktemp)"
        {
            echo ".PKGINFO"
            # .image is build-system plumbing (raw kernel + symbols the
            # image script consumes); shipping it would dump ~25MB at
            # /.image on every kernel update for nothing.
            find . -mindepth 1 -maxdepth 1 -not -name '.PKGINFO' -not -name '.image' -printf '%P\n'
        } > "${filelist}"
        # The depmod-generated indexes are stale by construction inside a
        # package (the kernel builds them before any out-of-tree module
        # exists); shipping them wiped the joypad and WiFi drivers from
        # modules.dep on every linux update. The device rebuilds them via
        # the archr-depmod hook; modules.builtin*/order stay (kernel-owned
        # depmod inputs).
        tar --zstd -cf "${outfile}" --owner=0 --group=0 \
            --exclude='*/modules/*/modules.dep' \
            --exclude='*/modules/*/modules.dep.bin' \
            --exclude='*/modules/*/modules.alias' \
            --exclude='*/modules/*/modules.alias.bin' \
            --exclude='*/modules/*/modules.symbols' \
            --exclude='*/modules/*/modules.symbols.bin' \
            --exclude='*/modules/*/modules.devname' \
            --exclude='*/modules/*/modules.softdep' \
            --exclude='*/modules/*/modules.weakdep' \
            --exclude='*/modules/*/modules.builtin.bin' \
            --exclude='*/modules/*/modules.builtin.alias.bin' \
            -T "${filelist}"
        rm -f "${filelist}"
    )
    rm -f "${pkginfo}"

    # Per-package signing is intentionally NOT done here. The repo db
    # carries the SHA256 of every pkg.tar.zst and is itself GPG-signed,
    # which gives pacman an unbroken trust chain without doubling the
    # asset count on GitHub Releases (1000-per-release cap).
    # The signer is still passed to repo-add below so the db gets its
    # own .sig and trust flows from there.

    echo "  + ${name} ${version}-${pkgrel}"
}

echo "gen-pacman-repo: packaging install_pkg/* into .pkg.tar.zst..."
for src in "${BUILD_DIR}/install_pkg/"*; do
    build_one_pkg "${src}"
done

# repo-add walks the .pkg.tar.zst files and produces archr.db.tar.gz
# (which pacman renames to archr.db at fetch time) plus archr.files.
echo "gen-pacman-repo: building repo db..."
local_db="${OUT_DIR}/${REPO_NAME}.db.tar.gz"
local_files="${OUT_DIR}/${REPO_NAME}.files.tar.gz"
rm -f "${local_db}" "${local_files}"

if [ -n "${SIGNER}" ]; then
    repo-add --sign --key "${SIGNER}" "${local_db}" "${OUT_DIR}"/*.pkg.tar.zst
else
    repo-add "${local_db}" "${OUT_DIR}"/*.pkg.tar.zst
fi

# Pacman fetches archr.db (no .tar.gz); the conventional way is to ship
# the .tar.gz and a .db symlink alongside.
( cd "${OUT_DIR}" && \
    ln -sf "${REPO_NAME}.db.tar.gz" "${REPO_NAME}.db" && \
    ln -sf "${REPO_NAME}.files.tar.gz" "${REPO_NAME}.files" )

# Seed an installed (local) db. Without it the shipped image's
# /var/lib/pacman/local is empty: `pacman -Q` returns nothing and
# `pacman -Syu` (archr-update) can only install user addons, never upgrade
# the base. We register every just-built package as installed (db entry
# only; the files already live in the image squashfs) into a staging dbpath
# and tar the resulting local/ tree. It is published as a repo asset and
# pulled in on first boot by the 004-seed-pacmandb autostart hook (so no
# image-build wiring is needed; the seed always matches this build).
echo "gen-pacman-repo: seeding installed (local) db..."
seed_dbpath="${OUT_DIR}/.seed-dbpath"
rm -rf "${seed_dbpath}" "${OUT_DIR}/.seed-root"
mkdir -p "${seed_dbpath}/local" "${seed_dbpath}/sync" "${OUT_DIR}/.seed-root"
cp "${local_db}" "${seed_dbpath}/sync/${REPO_NAME}.db"
# The host pacman runs with the host architecture and signature policy,
# which rejects our unsigned aarch64 payload; give it a minimal config
# scoped to this seed operation.
seed_conf="${seed_dbpath}/pacman.conf"
printf '[options]\nArchitecture = aarch64\nSigLevel = Never\n' > "${seed_conf}"
if fakeroot pacman -U --dbonly --noconfirm --config "${seed_conf}" \
        --root "${OUT_DIR}/.seed-root" --dbpath "${seed_dbpath}" \
        --cachedir "${OUT_DIR}" \
        "${OUT_DIR}"/*.pkg.tar.zst >/dev/null 2>&1; then
    tar -C "${seed_dbpath}" -czf "${OUT_DIR}/${REPO_NAME}-localdb.tar.gz" local
    echo "  wrote ${REPO_NAME}-localdb.tar.gz ($(ls -1 "${seed_dbpath}/local" | wc -l) entries)"
else
    echo "  WARNING: local-db seed failed; base will not be pacman-upgradable" >&2
fi
rm -rf "${seed_dbpath}" "${OUT_DIR}/.seed-root"

echo ""
echo "gen-pacman-repo: ready at ${OUT_DIR}"
ls -1sh "${OUT_DIR}" | head -20
echo ""
echo "Next:  gh release create repo-${CHANNEL} \\"
echo "           --repo archr-linux/archr-repo \\"
echo "           --title \"ArchR repo ${CHANNEL} $(date +%Y%m%d)\" \\"
echo "           \"${OUT_DIR}\"/*"
