#!/bin/bash
# h42 fdroid – eigenes F-Droid-Repo auf hov4 (fdroidserver in /srv/fdroid, öffentlich unter $PKG_URL/fdroid/repo).
#   h42 fdroid init                 Signierschlüssel, config.yml, leerer Index, Webroot (wiederholbar)
#   h42 fdroid add <apk>... [--force]   APKs aufnehmen, Index neu signieren; gleiche App-Version nur mit --force
#   h42 fdroid list                 Apps und Versionen (Repo und Archiv)
#   h42 fdroid remove <appid> [<versionCode>]   App bzw. eine Version entfernen
#   h42 fdroid info                 Repo-Adresse, Fingerabdruck und Link zum Hinzufügen
#   h42 fdroid update               Index neu erzeugen (z. B. nach Änderungen in metadata/<appid>.yml)
#   h42 fdroid access [zugriff]     Zugriff auf /fdroid/ anzeigen bzw. setzen: auth (immer Basic-Auth),
#                                   lan+auth (LAN/VPN direkt), lan, public (Default nach init)
#   h42 fdroid passwd <user>        Basic-Auth-Benutzer anlegen/Passwort ändern
# apt unter /apt/ bleibt davon unberührt. Mit Passwort in der F-Droid-App beim Hinzufügen Benutzer und Passwort
# angeben (fragt bei 401 selbst danach) – bei lan+auth daher nicht im LAN hinzufügen, sonst fehlt es unterwegs.
# Die APKs müssen schon mit dem eigenen Release-Schlüssel signiert sein; fdroid signiert nur den Index.
# Schlüssel: /etc/h42-fdroid/keystore.p12 (Passwort in /srv/fdroid/config.yml) – beides sichern.
# Einstellungen: /etc/h42/pkg.conf (PKG_ROOT, PKG_URL, FDROID_*)
set -euo pipefail
. "${H42_LIB:-/usr/lib/h42}/common.sh"   # usage, die, h42_acl, HTPASSWD_DIR
need_root

PKG_ROOT=/srv/pkg PKG_URL=https://pkg.h42.net FDROID_BASE=/srv/fdroid FDROID_KEYDIR=/etc/h42-fdroid
FDROID_REPO_NAME=h42 FDROID_REPO_DESCRIPTION="Eigene Android-Apps" FDROID_ARCHIVE_OLDER=3
H42_PKG_CONF=${H42_PKG_CONF:-/etc/h42/pkg.conf}
[ ! -r "$H42_PKG_CONF" ] || . "$H42_PKG_CONF"
BASE=$FDROID_BASE
KEYSTORE=$FDROID_KEYDIR/keystore.p12
WEB=$PKG_ROOT/fdroid
FQDN=${PKG_URL#*://}; FQDN=${FQDN%%/*}
ACCESS_CONF=/etc/nginx/h42-static.d/$FQDN/fdroid.conf
HTPASSWD=$HTPASSWD_DIR/$FQDN-fdroid
umask 022

cfg() { sed -n "s/^$1: *//p" "$BASE/config.yml" | head -1; }   # einzeiliger Wert aus config.yml
need_init() { [ -e "$BASE/config.yml" ] || die "noch nicht eingerichtet – erst h42 fdroid init"; }
fd() { (cd "$BASE" && fdroid "$@"); }
fingerprint() {  # SHA-256 des Index-Signierzertifikats, wie F-Droid ihn erwartet (hex, ohne Trennzeichen)
  local alias; alias=$(cfg repo_keyalias)
  FDROID_STOREPASS=$(cfg keystorepass) keytool -exportcert -alias "${alias:-h42}" -keystore "$KEYSTORE" \
      -storepass:env FDROID_STOREPASS 2>/dev/null \
    | openssl x509 -inform der -noout -fingerprint -sha256 | sed 's/.*=//; s/://g'
}
apkinfo() {  # apkinfo <apk> -> "appid versionCode" (versionCode leer, wenn das APK keinen hat)
  python3 -I -c 'import sys
from fdroidserver.common import get_apk_id
i, c, _ = get_apk_id(sys.argv[1])
print(i, c or "")' "$1" 2>/dev/null || true
}
versions() {  # appid versionCode versionName datei repo|archive – aus den erzeugten Indizes
  local section idx
  for section in repo archive; do
    idx=$BASE/$section/index-v2.json
    [ -e "$idx" ] || continue
    jq -r --arg s "$section" '.packages // {} | to_entries[] | .key as $id | .value.versions[]
      | [$id, .manifest.versionCode, .manifest.versionName, (.file.name | ltrimstr("/")), $s] | @tsv' "$idx"
  done
}
reindex() {
  fd update --create-metadata --pretty
  echo "Index aktualisiert: $PKG_URL/fdroid/repo"
}
info() {
  local fpr; fpr=$(fingerprint)
  echo "Repo:         $PKG_URL/fdroid/repo"
  echo "Fingerabdruck: $fpr"
  echo "Hinzufügen:   $PKG_URL/fdroid/repo?fingerprint=$fpr"
  echo "(Seite mit QR-Code: $PKG_URL/fdroid/repo/index.html)"
  echo "Zugriff:      $(current_access)"
}

current_access() { if [ -e "$ACCESS_CONF" ]; then sed -n '1s/.*Zugriff: //p' "$ACCESS_CONF"; else echo public; fi; }
set_access() {  # set_access <zugriff>
  local access=$1 acl new old
  acl=$(h42_acl "$access" "$FQDN F-Droid" "$HTPASSWD") || die "unbekannter Zugriff: $access (auth|lan+auth|lan|public)"
  grep -qs "h42-static.d/$FQDN/" "/etc/nginx/sites-enabled/${FQDN//./_}.conf" \
    || die "vhost $FQDN hat keinen Include für h42-static.d – erst: h42 vhost add $FQDN $PKG_ROOT public (aktualisiert ihn)"
  old=$(mktemp); [ ! -e "$ACCESS_CONF" ] || cp -a "$ACCESS_CONF" "$old"
  if [ "$access" = public ]; then
    rm -f "$ACCESS_CONF"
  else
    if [[ $access == *auth ]]; then
      install -d -m 750 -o root -g www-data "$HTPASSWD_DIR"
      [ -e "$HTPASSWD" ] || install -m 640 -o root -g www-data /dev/null "$HTPASSWD"
    fi
    install -d -m 755 "${ACCESS_CONF%/*}"
    new=$(cat <<CONF
# h42 fdroid access – Zugriff: $access
location ^~ /fdroid/ {
    $acl

    location ~ /\. { deny all; }
    location ~ \.apk\$ { types { application/vnd.android.package-archive apk; } }
}
CONF
)
    printf '%s\n' "$new" > "$ACCESS_CONF"
  fi
  if ! nginx -t 2>/dev/null; then
    if [ -s "$old" ]; then cp -a "$old" "$ACCESS_CONF"; else rm -f "$ACCESS_CONF"; fi
    rm -f "$old"; nginx -t || true
    die "nginx-Konfiguration fehlerhaft – alter Stand wiederhergestellt"
  fi
  rm -f "$old"
  systemctl reload nginx
  echo "$PKG_URL/fdroid/: $access"
  if [[ $access == *auth && ! -s $HTPASSWD ]]; then
    echo "Hinweis: noch kein Benutzer – h42 fdroid passwd <user> (bis dahin kommt niemand per Passwort rein)"
  fi
}

case ${1:-} in
  access)
    if [ -z "${2:-}" ]; then current_access; else set_access "$2"; fi
    ;;
  passwd)
    user=${2:?user fehlt}
    install -d -m 750 -o root -g www-data "$HTPASSWD_DIR"
    [ -e "$HTPASSWD" ] || install -m 640 -o root -g www-data /dev/null "$HTPASSWD"
    htpasswd -B "$HTPASSWD" "$user"
    ;;
  init)
    install -d -m 711 "$BASE"          # nginx muss zu repo/ durch, config.yml bleibt 600
    install -d -m 700 "$FDROID_KEYDIR"
    if [ ! -e "$BASE/config.yml" ]; then
      # fdroidserver 2.4 legt den Schlüssel trotz --keystore im aktuellen Verzeichnis an,
      # schreibt aber den angegebenen Pfad in config.yml – danach verschieben
      [ ! -e "$KEYSTORE" ] || die "$KEYSTORE existiert schon, aber $BASE/config.yml fehlt (Passwort?) – von Hand klären"
      fd init --no-prompt --keystore "$KEYSTORE" --repo-keyalias h42 -d "CN=$FDROID_REPO_NAME F-Droid"
      if [ ! -e "$KEYSTORE" ] && [ -e "$BASE/keystore.p12" ]; then mv "$BASE/keystore.p12" "$KEYSTORE"; fi
      [ -e "$KEYSTORE" ] || die "Schlüssel nicht erzeugt: $KEYSTORE"
      chmod 600 "$KEYSTORE" "$BASE/config.yml"
    fi
    # eigene Einstellungen am Ende von config.yml, nur wenn noch nicht gesetzt (danach von Hand pflegen)
    add_cfg() { grep -q "^$1:" "$BASE/config.yml" || printf '%s: %s\n' "$1" "$2" >> "$BASE/config.yml"; }
    add_cfg repo_url "$PKG_URL/fdroid/repo"
    add_cfg archive_url "$PKG_URL/fdroid/archive"
    add_cfg repo_name "\"$FDROID_REPO_NAME\""
    add_cfg repo_description "\"$FDROID_REPO_DESCRIPTION\""
    add_cfg archive_name "\"$FDROID_REPO_NAME – Archiv\""
    add_cfg archive_description "\"Ältere Versionen aus $FDROID_REPO_NAME\""
    add_cfg archive_older "$FDROID_ARCHIVE_OLDER"
    install -d -m 755 "$BASE/repo" "$BASE/archive" "$BASE/metadata"
    reindex
    install -d -m 755 "$PKG_ROOT" "$WEB"
    ln -sfn "$BASE/repo" "$WEB/repo"
    ln -sfn "$BASE/archive" "$WEB/archive"
    echo
    info
    echo "Sichern: $KEYSTORE und $BASE/config.yml (Passwort)"
    ;;
  add)
    shift; need_init
    force=; apks=()
    for a in "$@"; do
      case $a in --force) force=1 ;; -*) usage ;; *) apks+=("$a") ;; esac
    done
    [ ${#apks[@]} -gt 0 ] || usage
    known=$(versions)
    for apk in "${apks[@]}"; do   # erst alle prüfen, dann kopieren
      [ -f "$apk" ] || die "keine Datei: $apk"
      read -r id code <<<"$(apkinfo "$apk")"
      [ -n "${id:-}" ] || die "kein lesbares APK: $apk"
      [[ ${code:-} =~ ^[0-9]+$ ]] || die "$apk ($id) hat keinen versionCode – im Build setzen (versionCode in build.gradle)"
      if [ -z "$force" ] && awk -F'\t' -v i="$id" -v c="$code" '$1==i && $2==c {f=1} END {exit !f}' <<<"$known"; then
        die "$id Version $code ist schon im Repo (--force ersetzt sie)"
      fi
    done
    for apk in "${apks[@]}"; do
      read -r id code <<<"$(apkinfo "$apk")"
      # gleiche Version an anderer Stelle (z. B. im Archiv oder anders benannt) entfernen
      awk -F'\t' -v i="$id" -v c="$code" '$1==i && $2==c {print $5 "/" $4}' <<<"$known" \
        | while read -r f; do rm -fv "${BASE:?}/$f"; done
      install -m 644 "$apk" "$BASE/repo/${id}_$code.apk"
      echo "aufgenommen: $id Version $code"
    done
    rm -f "$BASE/tmp/apkcache.json"   # Cache ist nach Dateinamen geordnet; ersetzte Dateien neu einlesen
    reindex
    ;;
  list)
    need_init
    { printf 'APP\tCODE\tVERSION\tDATEI\tORT\n'; versions | sort -k1,1 -k2,2nr; } | column -t -s $'\t'
    ;;
  remove)
    need_init
    id=${2:?appid fehlt}; code=${3:-}
    files=$(versions | awk -F'\t' -v i="$id" -v c="$code" '$1==i && (c=="" || $2==c) {print $5 "/" $4}')
    [ -n "$files" ] || die "nicht im Repo: $id${code:+ Version $code}"
    while read -r f; do rm -fv "${BASE:?}/$f"; done <<<"$files"
    [ -n "$code" ] || rm -fv "$BASE/metadata/$id.yml"   # ganze App weg: Metadaten auch
    reindex
    ;;
  info)   need_init; info ;;
  update) need_init; reindex ;;
  *) usage ;;
esac
