#!/usr/bin/env bash
#
# LibrePortal bootstrap installer.
#
#   curl -fsSL https://get.libreportal.org/install.sh | sudo bash
#   curl -fsSL https://get.libreportal.org/install.sh | sudo bash -s -- \
#       --system-dir=/mnt/ssd/lp --backups-dir=/mnt/usb/lp-backups --manager-user=admin
#
# Fetches a versioned, checksum-verified release tarball over plain HTTPS (no git,
# no auth), extracts it to the system root, and hands off to init.sh. Git/local
# remain available for development. Self-contained on purpose — it's downloaded and
# run on its own, before any LibrePortal code exists on the box.
set -euo pipefail

# --- defaults ---------------------------------------------------------------
CHANNEL="stable"
VERSION_PIN=""
MODE="release"                                   # release | local | git
LOCAL_SRC=""
GIT_URL="" ; GIT_USER="" ; GIT_TOKEN=""
RELEASE_BASE_URL="${LP_RELEASE_BASE_URL:-https://get.libreportal.org}"
PASSWORD="${LP_PASSWORD:-}"
SYSTEM_DIR="" ; CONTAINERS_DIR="" ; BACKUPS_DIR="" ; MANAGER_USER="" ; ALLOW_HOME=""
DRY_RUN=0
NO_VERIFY_SIG=0

# minisign public key. Keep the SECRET key offline. Once you run `minisign -G`,
# paste the public key here AND into libreportal.pub. While it contains REPLACE_ME,
# signature verification is skipped (the sha256 still runs); once replaced, a valid
# signature becomes REQUIRED for release installs.
LP_MINISIGN_PUBKEY="RWREPLACE_ME_run_minisign_-G_then_paste_the_public_key_here_and_in_install.sh"

usage() {
    sed -n '3,12p' "$0" | sed 's/^# \{0,1\}//'
    cat <<'EOF'

Options:
  --channel=stable|edge      Release channel (default stable)
  --version=X.Y.Z            Pin a specific version (else latest in channel)
  --system-dir=PATH          Manager-owned control plane (default /libreportal-system)
  --containers-dir=PATH      Live app data       (default /libreportal-containers)
  --backups-dir=PATH         Backup repos        (default /libreportal-backups)
  --manager-user=NAME        Control-plane user  (default libreportal)
  --allow-home               Permit containers/backups inside /home/<user>
  --password=PASS            Manager/WebUI password (else a random one is generated)
  --base-url=URL             Override the release host (also: LP_RELEASE_BASE_URL)
  --local=PATH               Dev: install from a local source folder
  --git-url=URL              Dev: install by cloning a git repo
  --git-user=U --git-token=T Dev: git credentials
  --dry-run                  Fetch + verify + stage, but don't install
EOF
}

# --- parse flags ------------------------------------------------------------
for arg in "$@"; do
    case "$arg" in
        --channel=*)        CHANNEL="${arg#*=}" ;;
        --version=*)        VERSION_PIN="${arg#*=}" ;;
        --system-dir=*)     SYSTEM_DIR="${arg#*=}" ;;
        --containers-dir=*) CONTAINERS_DIR="${arg#*=}" ;;
        --backups-dir=*)    BACKUPS_DIR="${arg#*=}" ;;
        --manager-user=*)   MANAGER_USER="${arg#*=}" ;;
        --allow-home)       ALLOW_HOME="--allow-home" ;;
        --password=*)       PASSWORD="${arg#*=}" ;;
        --base-url=*)       RELEASE_BASE_URL="${arg#*=}" ;;
        --local=*)          MODE="local"; LOCAL_SRC="${arg#*=}" ;;
        --git-url=*)        MODE="git";   GIT_URL="${arg#*=}" ;;
        --git-user=*)       GIT_USER="${arg#*=}" ;;
        --git-token=*)      GIT_TOKEN="${arg#*=}" ;;
        --dry-run)          DRY_RUN=1 ;;
        --no-verify-signature) NO_VERIFY_SIG=1 ;;
        -h|--help)          usage; exit 0 ;;
        *) echo "install.sh: unknown option '$arg' (try --help)" >&2; exit 2 ;;
    esac
done

err() { echo "install.sh: $*" >&2; exit 1; }
need() { command -v "$1" >/dev/null 2>&1 || err "missing required tool: $1"; }

[[ $DRY_RUN -eq 1 || $EUID -eq 0 ]] || err "must run as root (try: sudo). Use --dry-run to test fetch only."

# Resolve the system root (honour env; the default matches paths.sh).
SYSTEM_DIR="${SYSTEM_DIR:-${LP_SYSTEM_DIR:-/libreportal-system}}"
INSTALL_DIR="$SYSTEM_DIR/install"

need tar
if   command -v curl >/dev/null 2>&1; then DL=curl
elif command -v wget >/dev/null 2>&1; then DL=wget
else err "need curl or wget"; fi
SHACMD=""
command -v sha256sum >/dev/null 2>&1 && SHACMD="sha256sum"
[[ -z "$SHACMD" ]] && command -v shasum >/dev/null 2>&1 && SHACMD="shasum -a 256"
[[ -n "$SHACMD" || "$MODE" != "release" ]] || err "need sha256sum or shasum to verify the release"

fetch() {  # fetch <url> [outfile]   (outfile omitted -> stdout)
    if [[ "$DL" == curl ]]; then
        [[ -n "${2:-}" ]] && curl -fsSL "$1" -o "$2" || curl -fsSL "$1"
    else
        [[ -n "${2:-}" ]] && wget -qO "$2" "$1" || wget -qO- "$1"
    fi
}
json_str() { printf '%s' "$1" | grep -oE "\"$2\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | head -1 | sed -E 's/.*"([^"]*)"$/\1/'; }

STAGE="$(mktemp -d)"; trap 'rm -rf "$STAGE"' EXIT
SRC=""

# --- obtain the code --------------------------------------------------------
case "$MODE" in
    release)
        local_ver="$VERSION_PIN" tarname="" want_sha=""
        if [[ -z "$local_ver" ]]; then
            echo "Resolving $CHANNEL channel from $RELEASE_BASE_URL ..."
            manifest="$(fetch "$RELEASE_BASE_URL/$CHANNEL/latest.json")" || err "cannot fetch channel manifest"
            local_ver="$(json_str "$manifest" version)"
            tarname="$(json_str "$manifest" url)"
            want_sha="$(json_str "$manifest" sha256)"
        fi
        [[ -n "$local_ver" ]] || err "could not determine version"
        [[ -n "$tarname" ]] || tarname="libreportal-${local_ver}.tar.gz"
        tarurl="$RELEASE_BASE_URL/$CHANNEL/$tarname"

        echo "Downloading $tarurl ..."
        fetch "$tarurl" "$STAGE/$tarname" || err "download failed"
        if [[ -z "$want_sha" ]]; then
            fetch "$tarurl.sha256" "$STAGE/$tarname.sha256" || err "no checksum available for $tarname"
            want_sha="$(cut -d' ' -f1 < "$STAGE/$tarname.sha256")"
        fi
        got_sha="$($SHACMD "$STAGE/$tarname" | cut -d' ' -f1)"
        [[ "$got_sha" == "$want_sha" ]] || err "CHECKSUM MISMATCH — refusing to install. expected $want_sha, got $got_sha"
        echo "✓ checksum verified ($got_sha)"

        # Signature: once the public key is real (not the REPLACE_ME placeholder), a
        # valid minisign signature is REQUIRED — it survives a compromised host (the
        # checksum alone doesn't, since the host serves it too). --no-verify-signature
        # is a dev escape hatch only.
        if [[ "$LP_MINISIGN_PUBKEY" != *REPLACE_ME* && "$NO_VERIFY_SIG" -ne 1 ]]; then
            command -v minisign >/dev/null 2>&1 || { echo "Installing minisign to verify the release ..."; apt-get install -y minisign >/dev/null 2>&1 || true; }
            command -v minisign >/dev/null 2>&1 || err "minisign required to verify the release but couldn't be installed"
            fetch "$tarurl.minisig" "$STAGE/$tarname.minisig" || err "release signature (.minisig) missing — refusing"
            # -P takes the public key as a string (our inline constant is just the
            # key line, not a full 2-line pubkey file).
            minisign -Vm "$STAGE/$tarname" -P "$LP_MINISIGN_PUBKEY" -x "$STAGE/$tarname.minisig" >/dev/null 2>&1 \
                || err "SIGNATURE INVALID — refusing to install (the release isn't authentic)"
            echo "✓ signature verified"
        fi
        mkdir -p "$STAGE/x"
        tar xzf "$STAGE/$tarname" -C "$STAGE/x" --strip-components=1
        SRC="$STAGE/x"
        ;;
    local)
        [[ -d "$LOCAL_SRC" ]] || err "local source folder not found: $LOCAL_SRC"
        SRC="$LOCAL_SRC"
        ;;
    git)
        need git
        [[ -n "$GIT_URL" ]] || err "--git-url is required for a git install"
        local_url="$GIT_URL"
        [[ -n "$GIT_USER" ]] && local_url="${GIT_URL%://*}://${GIT_USER}:${GIT_TOKEN}@${GIT_URL#*://}"
        git clone -q "$local_url" "$STAGE/x" || err "git clone failed"
        SRC="$STAGE/x"
        ;;
esac

[[ -f "$SRC/init.sh" && -d "$SRC/scripts" ]] || err "fetched tree is not a LibrePortal source (missing init.sh/scripts)"

if [[ $DRY_RUN -eq 1 ]]; then
    echo "[dry-run] verified $MODE source at: $SRC"
    echo "[dry-run] would copy -> $INSTALL_DIR and run init.sh with:"
    echo "          system=$SYSTEM_DIR containers=${CONTAINERS_DIR:-(default)} backups=${BACKUPS_DIR:-(default)} manager=${MANAGER_USER:-(default)} $ALLOW_HOME"
    exit 0
fi

# --- place the code + hand off to init.sh -----------------------------------
[[ -n "$PASSWORD" ]] || { PASSWORD="$(head -c 18 /dev/urandom | base64 | tr -d '/+=' | cut -c1-20)"; GEN_PW=1; }

mkdir -p "$INSTALL_DIR"
cp -a "$SRC/." "$INSTALL_DIR/"
cp -f "$INSTALL_DIR/init.sh" /root/ 2>/dev/null || true

flags=( --unattended )
[[ -n "$SYSTEM_DIR" ]]     && flags+=( "--system-dir=$SYSTEM_DIR" )
[[ -n "$CONTAINERS_DIR" ]] && flags+=( "--containers-dir=$CONTAINERS_DIR" )
[[ -n "$BACKUPS_DIR" ]]    && flags+=( "--backups-dir=$BACKUPS_DIR" )
[[ -n "$MANAGER_USER" ]]   && flags+=( "--manager-user=$MANAGER_USER" )
[[ -n "$ALLOW_HOME" ]]     && flags+=( "$ALLOW_HOME" )

# Positional contract: init <password> <git_user> <git_token> <git_url> <unattended> <install_mode>
case "$MODE" in
    git)   set -- init "$PASSWORD" "${GIT_USER:-empty}" "${GIT_TOKEN:-empty}" "$GIT_URL" true git ;;
    local) set -- init "$PASSWORD" empty empty empty true local ;;
    *)     set -- init "$PASSWORD" empty empty empty true release ;;
esac

[[ "${GEN_PW:-0}" == 1 ]] && echo "Generated manager/WebUI password: $PASSWORD  (also recorded in the install log)"
echo "Running installer ..."
cd "$INSTALL_DIR"
# Tell init.sh the code is already staged + verified, so initGIT skips re-fetching.
export LP_ALREADY_FETCHED=1
export LP_RELEASE_BASE_URL          # carry the (possibly overridden) host through
exec bash "$INSTALL_DIR/init.sh" "${flags[@]}" "$@"
