#!/usr/bin/env bash
# get-gldoze.sh: fetch a verified GLDoze Desktop payload and install it.
#
# The default run does everything: download, verify the signed chain,
# unpack into your home directory, install the runtime libraries the
# payload needs, install the privileged helper, run the staged install
# into /usr/local, register the session and verify the result. Every
# step that needs root goes through sudo (NOPASSWD, or it asks). There
# is no su fallback: on a system without sudo, run the whole command as
# root yourself: curl -fsSL -O <this-script-url> && su -c 'bash get-gldoze.sh'

#
#   bash get-gldoze.sh                  # fetch + verify + unpack + install
#   bash get-gldoze.sh --no-install     # fetch + verify + unpack only
#   bash get-gldoze.sh --verify-only    # verify the published chain, fetch nothing heavy
#   bash get-gldoze.sh --target DIR     # unpack somewhere else
#   bash get-gldoze.sh --user NAME      # root mode: install for this user
#
# Root mode: run the whole script with su -c 'bash get-gldoze.sh' when the
# system has no sudo. The payload still lands in the LOGIN USER's
# ~/.local/share/gldoze/desktop (owned by them) - never under /root - and
# the session is registered for that user. The user is detected from
# SUDO_USER / logname; pass --user NAME if neither resolves.
#   bash get-gldoze.sh -- <args...>     # after unpacking, run the payload's
#                                       # gldoze-setup with these arguments
#
# Environment:
#   GLDOZE_DESKTOP_URL   override the download root (mirrors, testing)
set -euo pipefail

# The trust anchor: the GLDoze release key fingerprint. Everything downloaded
# must be signed by exactly this key; the published key file is only ever
# cross-checked against this value, never trusted on its own.
readonly RELEASE_FINGERPRINT="CD31BB31DBF435513F7B98D91580E84C2BFC2F60"
readonly DEFAULT_URL="https://www.gldoze.de/downloads/gldoze-desktop"
readonly MANIFEST_NAME="gldoze-desktop-manifest.toml"

url=${GLDOZE_DESKTOP_URL:-$DEFAULT_URL}
target=""
install_user=""
verify_only=0
no_install=0
run_args=()

usage() {
	# Under `curl ... | bash` there is no script file to read the header from.
	if [[ -r ${0:-} ]]; then
		sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//'
	else
		printf '%s\n' "get-gldoze: fetch and verify the GLDoze desktop release from https://www.gldoze.de/ (pass --help when saved to a file)."
	fi
}

fail() { echo "get-gldoze: $*" >&2; exit 3; }

while (($#)); do
	case $1 in
		--verify-only) verify_only=1; shift ;;
		--no-install) no_install=1; shift ;;
		--target) target=${2:?--target needs a directory}; shift 2 ;;
		--user) install_user=${2:?--user needs a login name}; shift 2 ;;
		-h|--help) usage; exit 0 ;;
		--) shift; run_args=("$@"); break ;;
		*) echo "get-gldoze: unknown argument: $1 (see --help)" >&2; exit 2 ;;
	esac
done

# --- who the install belongs to ---------------------------------------------
# Under su -c/sudo the script runs as root, but the payload cache and the
# session belong to the login user, never to /root.
install_home=""
if ((EUID == 0)) && ((verify_only == 0)); then
	if [[ -z $install_user ]]; then
		install_user=${SUDO_USER:-}
		[[ -n $install_user && $install_user != root ]] || install_user=$(logname 2>/dev/null || true)
		[[ $install_user != root ]] || install_user=""
	fi
	[[ -n $install_user ]] || fail "running as root but the login user could not be detected; pass --user NAME (bash get-gldoze.sh --user <yourname>)"
	install_home=$(getent passwd "$install_user" | cut -d: -f6)
	[[ -n $install_home && -d $install_home ]] || fail "no home directory for user '$install_user'"
	export SUDO_USER="$install_user"
	echo "get-gldoze: root mode - installing for user $install_user ($install_home)"
fi

# --- requirements: download and verify only, never install -------------------
missing=()
for tool in curl gpg sha256sum tar; do
	command -v "$tool" >/dev/null 2>&1 || missing+=("$tool")
done
if ! command -v zstd >/dev/null 2>&1; then
	# tar can also use its own zstd support when the binary is absent.
	tar --use-compress-program zstd --version >/dev/null 2>&1 || missing+=("zstd")
fi
if ((${#missing[@]})); then
	echo "get-gldoze: missing tools: ${missing[*]}" >&2
	echo "  Debian/Ubuntu: sudo apt-get install -y ${missing[*]}" >&2
	echo "  Arch:          sudo pacman -S --needed ${missing[*]}" >&2
	echo "  Fedora:        sudo dnf install -y ${missing[*]}" >&2
	exit 3
fi

work=$(mktemp -d "${TMPDIR:-/tmp}/gldoze-get-XXXXXX")
trap 'rm -rf "$work"' EXIT

echo "get-gldoze: fetching the release manifest"
curl -fsSL --proto =https --tlsv1.2 -o "$work/$MANIFEST_NAME" "$url/$MANIFEST_NAME" \
	|| fail "cannot fetch $url/$MANIFEST_NAME"
curl -fsSL --proto =https --tlsv1.2 -o "$work/$MANIFEST_NAME.asc" "$url/$MANIFEST_NAME.asc" \
	|| fail "cannot fetch the manifest signature"
curl -fsSL --proto =https --tlsv1.2 -o "$work/release-key.asc" "$url/gldoze-release-signing-key.asc" \
	|| fail "cannot fetch the release key"

# Flat manifest: one value per key, comments allowed.
distribution=$(awk '/^distribution *=/ { sub(/^[^=]*= */, ""); gsub(/"/, ""); print; exit }' "$work/$MANIFEST_NAME")
archive=$(awk '/^archive *=/ { sub(/^[^=]*= */, ""); gsub(/"/, ""); print; exit }' "$work/$MANIFEST_NAME")
checksum=$(awk '/^sha256 *=/ { sub(/^[^=]*= */, ""); gsub(/"/, ""); print; exit }' "$work/$MANIFEST_NAME")
fingerprint=$(awk '/^signing_key_fingerprint *=/ { sub(/[^=]*= */, ""); gsub(/"/, ""); print; exit }' "$work/$MANIFEST_NAME")

[[ -n $distribution && -n $archive && -n $checksum ]] \
	|| fail "the manifest is incomplete (distribution, archive, sha256 required)"
[[ $checksum =~ ^[0-9a-f]{64}$ ]] || fail "the manifest checksum is not a sha256 digest"
[[ $fingerprint == "$RELEASE_FINGERPRINT" ]] \
	|| fail "the manifest names signing key $fingerprint, this script trusts only $RELEASE_FINGERPRINT"

# Throwaway keyring: only the published key ever enters it.
export GNUPGHOME="$work/gnupg"
mkdir -m 700 "$GNUPGHOME"
gpg --batch --import "$work/release-key.asc" >/dev/null 2>&1 \
	|| fail "cannot import the release key"
gpg --batch --verify "$work/$MANIFEST_NAME.asc" "$work/$MANIFEST_NAME" >/dev/null 2>&1 \
	|| fail "the manifest signature does not verify"
echo "get-gldoze: manifest signature verified (fingerprint $fingerprint)"

if ((verify_only)); then
	echo "get-gldoze: published chain verified; nothing else fetched"
	exit 0
fi

echo "get-gldoze: fetching $archive"
curl -fSL --proto =https --tlsv1.2 -o "$work/$archive" "$url/$archive" \
	|| fail "cannot fetch the payload archive"
actual=$(sha256sum "$work/$archive" | cut -d' ' -f1)
[[ $actual == "$checksum" ]] \
	|| fail "checksum mismatch: expected $checksum, got $actual"
echo "get-gldoze: checksum verified"

curl -fsSL --proto =https --tlsv1.2 -o "$work/$archive.asc" "$url/$archive.asc" \
	|| fail "cannot fetch the archive signature"
gpg --batch --verify "$work/$archive.asc" "$work/$archive" >/dev/null 2>&1 \
	|| fail "the archive signature does not verify"
echo "get-gldoze: archive signature verified"

if [[ -z $target ]]; then
	if ((EUID == 0)) && [[ -n $install_home ]]; then
		# Root mode: a /root or otherwise root-owned XDG value must not
		# capture the user's payload; their home is the install directory.
		base=${XDG_DATA_HOME:-}
		[[ -z $base || $base == /root/* ]] && base="$install_home/.local/share"
		target=$base/gldoze/desktop
	else
		target=${XDG_DATA_HOME:-$HOME/.local/share}/gldoze/desktop
	fi
fi
mkdir -p "$target"
# The payload cache is ours alone; start from nothing so no stale file
# from an interrupted or older unpack can be staged into the release.
rm -rf "$target"
mkdir -p "$target"
echo "get-gldoze: unpacking into $target"
tar --use-compress-program zstd -xf "$work/$archive" -C "$target"
if ((EUID == 0)) && [[ -n $install_user ]]; then
	chown -R "$install_user:" "$target"
fi

# The archive may wrap the payload in a single directory; unwrap it so the
# layout matches an installed payload (bin/, libexec/, share/, install.sh).
root=$target
if [[ ! -x $root/bin/gldoze-setup ]]; then
	entries=("$root"/*)
	if ((${#entries[@]} == 1)) && [[ -d ${entries[0]} ]] && [[ -x ${entries[0]}/bin/gldoze-setup ]]; then
		root=${entries[0]}
	fi
fi
setup=$root/bin/gldoze-setup
[[ -x $setup ]] || fail "the payload did not contain bin/gldoze-setup under $target"

if ((${#run_args[@]})); then
	exec "$setup" "${run_args[@]}"
fi

if ((no_install)); then
	cat <<EOF

get-gldoze: GLDoze Desktop $distribution is ready in $root

Run bash get-gldoze.sh again without --no-install to install it, or drive
the payload's installer yourself:

  $setup --diagnose            # what this machine supports
  $setup --install-helper      # install the privileged helper (needs root)
  $setup --install --yes --package $root
  $setup --verify
EOF
	exit 0
fi

# --- the install flow --------------------------------------------------------
# Root steps run through sudo (NOPASSWD when available, otherwise sudo
# asks at your shell). There is deliberately NO su fallback: su cannot
# prompt reliably from every launch context and always fails here. When
# sudo is not usable, stop with the root invocation instead of guessing.
run_root() {
	if ((EUID == 0)); then
		# Already root (su -c / sudo): nothing to escalate.
		"$@"
		return
	fi
	if sudo -n true 2>/dev/null; then
		sudo "$@"
		return
	fi
	if command -v sudo >/dev/null 2>&1; then
		echo "get-gldoze: sudo will ask for your password"
		sudo "$@"
		return
	fi
	fail "no sudo on this system; re-run the whole command as root:
  curl -fsSL -O https://www.gldoze.de/downloads/gldoze-desktop/get-gldoze.sh && su -c 'bash get-gldoze.sh'"
}

echo
echo "get-gldoze: [1/6] runtime libraries"
if run_root "$setup" --install-runtime-deps --yes --package "$root"; then
	echo "get-gldoze: runtime libraries satisfied"
else
	echo "get-gldoze: WARNING: automatic runtime dependency install did not" >&2
	echo "  complete. If the install below fails on missing libraries, run" >&2
	echo "  '$setup --install-runtime-deps --package $root' to see the" >&2
	echo "  exact list, install them with your package manager, and re-run" >&2
	echo "  this script." >&2
fi

echo
echo "get-gldoze: [2/6] previous flat installs"
# Releases from before the staged layout copied files flat into /usr/local
# as regular files. Those sit exactly at GLDoze-owned paths, so they are
# removed here (never anything else) - the installer's strict foreign-file
# rule then has a clean prefix to work in.
if [[ -f /usr/local/bin/gldoze_desktop && ! -L /usr/local/bin/gldoze_desktop ]]; then
	echo "get-gldoze: removing the legacy flat install under /usr/local"
	while IFS= read -r payload_file; do
		relative=${payload_file#"$root"/}
		live=/usr/local/$relative
		if [[ -f $live && ! -L $live ]]; then
			run_root rm -f "$live"
		elif [[ -d $live && ! -L $live ]]; then
			# Empty directories left by an interrupted run are residue.
			run_root rmdir "$live" 2>/dev/null || true
		fi
	done < <(find "$root/bin" "$root/lib" "$root/libexec" "$root/share" -type f 2>/dev/null)
else
	echo "get-gldoze: no legacy flat install to clean up"
fi

echo
echo "get-gldoze: [3/6] privileged helper"
run_root "$setup" --install-helper

echo
echo "get-gldoze: [4/6] staged install into /usr/local"
install_payload() {
	if sudo -n true 2>/dev/null; then
		# Passwordless sudo: run as the normal user; the helper does the
		# privileged work itself and writes the audit log.
		"$setup" --install --yes --package "$root"
	else
		# No passwordless sudo: run the installer as root directly.
		run_root "$setup" --install --yes --package "$root"
	fi
}
install_ok=0
if install_payload; then
	install_ok=1
else
	# A failed install rolls the helper back with it; re-provision and
	# retry once so one failure does not strand the machine.
	echo "get-gldoze: the install did not complete; re-provisioning the helper and retrying once"
	run_root "$setup" --install-helper
	if install_payload; then install_ok=1; fi
fi
if (( ! install_ok )); then
	# Portable fallback: the payload's own installer (the same logic
	# go.sh drives) runs last so the operator never has to run
	# install.sh by hand after this script.
	echo "get-gldoze: falling back to the portable payload installer"
	run_root bash "$root/install.sh" --payload "$root" ||
		fail "install failed through both paths; run 'bash $root/install.sh --payload $root' as root and report its output"
fi

echo
echo "get-gldoze: [5/6] session registration"
# Prefer the installed frontend: its helper script sits in the root-owned
# release, which a privileged run is allowed to execute. The unpacked payload
# is user-owned and would be refused.
session_setup=/usr/local/bin/gldoze-setup
[[ -x $session_setup ]] || session_setup=$setup
run_root "$session_setup" --session-setup --yes \
	|| echo "get-gldoze: note: session registration did not complete; run '$session_setup --session-setup --yes' as root after fixing what it reports"

echo
echo "get-gldoze: [6/6] verification"
"$setup" --verify

cat <<EOF

get-gldoze: GLDoze Desktop $distribution is installed.
Log out and back in (or restart your display manager) and select GLDoze.
Manage the installation with gldozectl, check it any time with:

  gldoze-setup --verify
  gldoze-setup --diagnose
EOF
