#!/usr/bin/env bash
# Install the TulaDB dashboard 0.0.1-preview from its offline bundle, with no
# network access.
#
# Put this script in a directory with the release files for your platform
# (linux x86_64 or arm64/aarch64): SHA256SUMS, the two wheels, the
# requirements archive, this platform's wheelhouse part(s) and, for the Lake
# screen, its DuckDB extension part(s). Then:
#
#   bash ./tuladb-dashboard-0.0.1-preview-install.sh [--prefix DIR] [--no-lake | --no-extensions]
#   DIR/bin/tuladb-dashboard --host 127.0.0.1 --port 8080 --ledger-host <engine>
#
# Verify SHA256SUMS itself first (its Sigstore signature, see the release
# notes or docs/offline-install.md); this script then checks every file it
# uses against SHA256SUMS before touching it, installs the Python packages
# with `pip install --no-index --find-links ... --require-hashes`, installs
# the DuckDB extensions from their files, and writes launchers that set
# TULADB_LAKE_EXTENSION_DIR and TULADB_DASHBOARD_DATA_DIR.
#
# --no-lake leaves the Lake screen out (no DuckDB); --no-extensions installs
# it without the DuckDB extensions, for a lake that is a local DuckDB file
# only (BigLake and S3 Tables need httpfs, aws, iceberg and avro).
#
# Needs Python 3.12 (`python3.12`, or set PYTHON) with the venv module, and
# sha256sum, tar and gzip.
set -euo pipefail

VERSION="0.0.1-preview"
DUCKDB_VERSION="1.5.6"
DASHBOARD_WHEEL="tuladb_dashboard-0.0.1rc0-py3-none-any.whl"
SDK_WHEEL="tuladb_sdk-0.0.1.dev0-py3-none-any.whl"
COMMIT="d7ee0bebd6cd618fb065adc7210625e60dbe9c0f"

HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PREFIX="${HOME}/.local/share/tuladb-dashboard"
LAKE=1
EXT=1
PY="${PYTHON:-python3.12}"

usage() { sed -n '2,22p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; }
while [ $# -gt 0 ]; do
  case "$1" in
    --prefix) PREFIX="$2"; shift 2 ;;
    --prefix=*) PREFIX="${1#--prefix=}"; shift ;;
    --no-lake) LAKE=0; EXT=0; shift ;;
    --no-extensions) EXT=0; shift ;;
    -h|--help) usage; exit 0 ;;
    *) echo "unknown argument: $1" >&2; usage >&2; exit 2 ;;
  esac
done

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

case "$(uname -s)-$(uname -m)" in
  Linux-x86_64|Linux-amd64) ARCH=x86_64 ;;
  Linux-aarch64|Linux-arm64) ARCH=aarch64 ;;
  *) die "this bundle is for Linux x86_64 and aarch64, not $(uname -s) $(uname -m)" ;;
esac

"$PY" -c 'import sys, venv; assert sys.version_info[:2] == (3, 12)' 2>/dev/null \
  || die "needs Python 3.12 with venv ($PY); set PYTHON=/path/to/python3.12"

cd "$HERE"
[ -f SHA256SUMS ] || die "SHA256SUMS is missing next to this script"

# Every file used, checked against SHA256SUMS before use. A file SHA256SUMS
# does not list is refused, not trusted.
verify() {
  local f line
  for f in "$@"; do
    [ -f "$f" ] || die "missing $f (download it with the other release files)"
    line="$(awk -v n="$f" '$2 == n { print; found = 1 } END { exit !found }' SHA256SUMS)" \
      || die "$f is not listed in SHA256SUMS"
    printf '%s\n' "$line" | sha256sum -c --quiet - || die "$f does not match SHA256SUMS"
  done
}

LOCKS="tuladb-dashboard-${VERSION}-requirements.tar.gz"
LOCK="requirements-lake.lock"
[ "$LAKE" = 1 ] || LOCK="requirements.lock"
shopt -s nullglob
WHEELHOUSE=(tuladb-dashboard-"${VERSION}"-linux-"${ARCH}"-wheelhouse-*.tar.gz)
EXTENSIONS=(tuladb-dashboard-"${VERSION}"-linux-"${ARCH}"-duckdb-extensions-*.tar.gz)
shopt -u nullglob
[ ${#WHEELHOUSE[@]} -gt 0 ] || die "no wheelhouse part for linux $ARCH here"
if [ "$EXT" = 1 ] && [ ${#EXTENSIONS[@]} -eq 0 ]; then
  die "no DuckDB extension part for linux $ARCH (the Lake screen needs them); add them, or pass --no-extensions or --no-lake"
fi
verify "$LOCKS" "$DASHBOARD_WHEEL" "$SDK_WHEEL" "${WHEELHOUSE[@]}"
if [ "$EXT" = 1 ]; then verify "${EXTENSIONS[@]}"; fi

echo "installing TulaDB dashboard $VERSION (linux $ARCH) into $PREFIX"
mkdir -p "$PREFIX/bin" "$PREFIX/data"
chmod 0700 "$PREFIX/data"
STAGE="$(mktemp -d "${TMPDIR:-/tmp}/tuladb-dashboard-install.XXXXXX")"
trap 'rm -rf "$STAGE"' EXIT
mkdir -p "$STAGE/wheels" "$STAGE/locks"
tar -xzf "$LOCKS" -C "$STAGE/locks" --no-same-owner "$LOCK"
for part in "${WHEELHOUSE[@]}"; do
  tar -xzf "$part" -C "$STAGE/wheels" --no-same-owner
done
cp "$DASHBOARD_WHEEL" "$SDK_WHEEL" "$STAGE/wheels/"

# A fresh environment each time: an upgrade replaces, it never mixes.
rm -rf "$PREFIX/venv"
"$PY" -m venv "$PREFIX/venv"
VPY="$PREFIX/venv/bin/python"
export PIP_NO_INDEX=1 PIP_DISABLE_PIP_VERSION_CHECK=1 PIP_NO_CACHE_DIR=1
"$VPY" -m pip install --quiet --no-index --find-links "$STAGE/wheels" --only-binary=:all: \
  --require-hashes -r "$STAGE/locks/$LOCK"
"$VPY" -m pip install --quiet --no-index --no-deps "$STAGE/wheels/$SDK_WHEEL" "$STAGE/wheels/$DASHBOARD_WHEEL"

EXT_DIR="$PREFIX/duckdb-extensions"
if [ "$EXT" = 1 ]; then
  rm -rf "$EXT_DIR"
  mkdir -p "$STAGE/ext" "$EXT_DIR"
  for part in "${EXTENSIONS[@]}"; do
    tar -xzf "$part" -C "$STAGE/ext" --no-same-owner
  done
  for gz in "$STAGE"/ext/*.duckdb_extension.gz; do
    gzip -dc "$gz" > "${gz%.gz}"
  done
  # INSTALL from the local files: DuckDB lays them out under the extension
  # directory as it would from its repository (and checks their signatures
  # on LOAD), with no network. Then LOAD each, so a bad file fails here.
  "$VPY" - "$EXT_DIR" "$DUCKDB_VERSION" "$STAGE/ext" <<'PYEOF'
import sys
from pathlib import Path
import duckdb
ext_dir, want, src = sys.argv[1], sys.argv[2], Path(sys.argv[3])
assert duckdb.__version__ == want, f"duckdb {duckdb.__version__} installed, bundle is for {want}"
con = duckdb.connect(config={"extension_directory": ext_dir, "autoinstall_known_extensions": False})
names = sorted(p.name.split(".")[0] for p in src.glob("*.duckdb_extension"))
for name in names:
    con.execute(f"INSTALL '{src / (name + '.duckdb_extension')}'")
for name in names:
    con.execute(f"LOAD {name}")
print("duckdb extensions installed:", ", ".join(names))
PYEOF
fi

write_launcher() {
  local name="$1" target="$2"
  cat > "$PREFIX/bin/$name" <<EOF
#!/bin/sh
# TulaDB dashboard $VERSION launcher (written by the offline installer).
export TULADB_DASHBOARD_DATA_DIR="\${TULADB_DASHBOARD_DATA_DIR:-$PREFIX/data}"
export LEDGER_DASHBOARD_COMMIT="\${LEDGER_DASHBOARD_COMMIT:-$COMMIT}"
$( [ "$EXT" = 1 ] && printf 'export TULADB_LAKE_EXTENSION_DIR="${TULADB_LAKE_EXTENSION_DIR:-%s}"\n' "$EXT_DIR" )
exec "$PREFIX/venv/bin/$target" "\$@"
EOF
  chmod 0755 "$PREFIX/bin/$name"
}
write_launcher tuladb-dashboard tuladb-dashboard
write_launcher tuladb-dashboard-users tuladb-dashboard-users
write_launcher tuladb-dashboard-python python

echo "installed. Run: $PREFIX/bin/tuladb-dashboard --help"
echo "data (users, audit log, reconciliation state): $PREFIX/data"
if [ "$EXT" = 1 ]; then echo "TULADB_LAKE_EXTENSION_DIR=$EXT_DIR (set by the launcher)"; fi
exit 0
