#!/usr/bin/env bash
# SPDX-License-Identifier: GPL-3.0-only
# eniatos-repo - client of the EniatOS package repository (https://repo.eniatos.com)
# Packages are built and signed locally (makepkg --sign), the server only verifies and indexes.
set -euo pipefail

URL=${ENIATOS_REPO_URL:-https://repo.eniatos.com}
TOKEN_FILE=${ENIATOS_REPO_TOKEN_FILE:-${XDG_CONFIG_HOME:-$HOME/.config}/eniatos-repo/token}

usage() {
    cat <<'EOF'
Usage: eniatos-repo push [--downgrade] PKG.pkg.tar.zst...   upload signed packages (PKG.sig next to it)
       eniatos-repo remove PKGNAME...                       drop from [eniatos] (archive keeps the files)
       eniatos-repo list                                    packages in [eniatos]
       eniatos-repo token NAME                              new upload token -> TOKEN_FILE,
                                                            prints the line for the server's tokens file
Token:  $ENIATOS_REPO_TOKEN or ~/.config/eniatos-repo/token
Server: $ENIATOS_REPO_URL (default https://repo.eniatos.com)
EOF
    exit "${1:-0}"
}

die() { echo "eniatos-repo: $*" >&2; exit 1; }

token() {
    if [[ -n ${ENIATOS_REPO_TOKEN:-} ]]; then echo "$ENIATOS_REPO_TOKEN"; return; fi
    [[ -r $TOKEN_FILE ]] || die "no token: set ENIATOS_REPO_TOKEN or create $TOKEN_FILE (eniatos-repo token NAME)"
    tr -d '[:space:]' < "$TOKEN_FILE"
}

# api METHOD PATH [curl args...] - prints the JSON answer, fails on HTTP errors
api() {
    local method=$1 path=$2 out code
    shift 2
    out=$(curl -sS -X "$method" -w '\n%{http_code}' \
            -H @<(printf 'Authorization: Bearer %s\n' "$(token)") "$@" "$URL$path") \
        || die "$method $path: connection failed"
    code=${out##*$'\n'}; out=${out%$'\n'*}
    if [[ $code != 2* ]]; then
        echo "$out" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("error","?"))' >&2 2>/dev/null \
            || echo "$out" >&2
        return 1
    fi
    echo "$out"
}

urlenc() { python3 -c 'import sys,urllib.parse; print(urllib.parse.quote(sys.argv[1]))' "$1"; }

cmd_push() {
    local q="" pkg f rc=0
    [[ ${1:-} == --downgrade ]] && { q="?downgrade=1"; shift; }
    [[ $# -gt 0 ]] || usage 1
    for pkg in "$@"; do
        f=${pkg##*/}
        [[ $f == *.pkg.tar.zst ]] || { echo "$pkg: not a *.pkg.tar.zst" >&2; rc=1; continue; }
        [[ -f $pkg ]] || { echo "$pkg: no such file" >&2; rc=1; continue; }
        [[ -f $pkg.sig ]] || { echo "$pkg: missing $pkg.sig (makepkg --sign / gpg --detach-sign)" >&2; rc=1; continue; }
        gpg --verify "$pkg.sig" "$pkg" 2>/dev/null || echo "$pkg: warning: local gpg --verify failed" >&2
        echo "==> $f"
        api PUT "/api/packages/$(urlenc "$f")$q" -T "$pkg" -H "Content-Type: application/octet-stream" \
            -H @<(printf 'X-Package-Signature: %s\n' "$(base64 -w0 "$pkg.sig")") || rc=1
    done
    return $rc
}

cmd_remove() {
    local n rc=0
    [[ $# -gt 0 ]] || usage 1
    for n in "$@"; do api DELETE "/api/packages/$(urlenc "$n")" || rc=1; done
    return $rc
}

cmd_list() {
    local out
    out=$(api GET /api/packages) || return 1
    echo "$out" | python3 -c '
import json, sys
for p in json.load(sys.stdin):
    print("%-32s %s" % (p["name"], p["version"]))'
}

cmd_token() {
    [[ -n ${1:-} ]] || usage 1
    local t
    [[ -e $TOKEN_FILE ]] && die "$TOKEN_FILE exists - remove it first"
    t=$(head -c 32 /dev/urandom | base64 -w0 | tr '+/' '-_' | tr -d '=')
    install -d -m700 "${TOKEN_FILE%/*}"
    (umask 077; echo "$t" > "$TOKEN_FILE")
    echo "token saved to $TOKEN_FILE; add this line to /srv/machines/repo/config/tokens on the server:"
    printf '%s  %s\n' "$(printf '%s' "$t" | sha256sum | cut -d' ' -f1)" "$1"
}

case "${1:-}" in
    push) shift; cmd_push "$@" ;;
    remove) shift; cmd_remove "$@" ;;
    list) cmd_list ;;
    token) shift; cmd_token "$@" ;;
    -h|--help) usage 0 ;;
    *) usage 1 ;;
esac
