#!/bin/sh
# Secria VPN — one-line installer.
#
#   curl -fsSL https://secria.me/install.sh | sudo sh
#
# Preferred path (Debian/Ubuntu, Fedora/RHEL): adds the GPG-signed Secria
# package repository, so this install AND every future update flow through the
# native package manager (apt upgrade / dnf upgrade). The repo signing key is
# fetched from the host and verified against the fingerprint pinned below
# before anything trusts it.
#
# Fallback path (every other distro, or SECRIA_NO_REPO=1): downloads the static
# tarball and verifies BOTH its SHA-256 (against SHA256SUMS) and the SLH-DSA
# post-quantum release signature on SHA256SUMS itself (via the bundled
# sphincs-verify tool, which is hash-pinned below). Fails closed: nothing is
# installed unless every enabled verification passes.
#
# Overridable via env:
#   SECRIA_BASE_URL   host root                      (default https://secria.me)
#   SECRIA_VERSION    version for the tarball path   (default latest)
#   SECRIA_BIN_DIR    binary install dir override    (default /usr/local/bin)
#   SECRIA_NO_REPO=1  skip repo setup, force the tarball path
set -eu

BASE_URL="${SECRIA_BASE_URL:-https://secria.me}"
VERSION="${SECRIA_VERSION:-latest}"

# --- Pinned trust anchors ----------------------------------------------------
# Filled in by release engineering when the corresponding key exists; a value
# still wrapped in __…__ means "not yet pinned" and disables that verification
# path with a loud warning (pre-release bootstrap only — before public launch
# every pin below MUST be set, making verification fail-closed).
#
# Fingerprint (40 hex chars, no spaces) of the apt/yum repo GPG signing key.
REPO_GPG_FINGERPRINT="__SECRIA_REPO_GPG_FINGERPRINT__"
# SHA-256 of the PEM-encoded SLH-DSA release public key served at /keys/release-slhdsa.pub.
RELEASE_PUBKEY_SHA256="a4660918748c980dc00b935ed2ede5ef18722a384f15a932e7246e9427b2eddb"
# SHA-256 of the standalone sphincs-verify tool binaries served under
# /download/tools/. Pinning the verifier here breaks the circular dependency of
# using a downloaded verifier to verify the download that contains it. The
# tool is republished under a versioned name every release, so the version the
# hashes pin is part of the anchor — all three values move together in one
# commit (a fixed URL would change hash under the pins on the next release).
SPHINCS_TOOL_SHA256_AMD64="ca2e4377781d25ad787a59e234d6a19ba1121517317b9e2bd8756b7e7db6dfb3"
SPHINCS_TOOL_SHA256_ARM64="e6706a630c660869385ba6ea1a13cf8952bbd8e5f38592f1d6bf9a62c1208a7c"
SPHINCS_TOOL_VERSION="1.0.4"

say() { printf '%s\n' "$*"; }
warn() { printf 'secria-vpn install: WARNING: %s\n' "$*" >&2; }
die() { printf 'secria-vpn install: %s\n' "$*" >&2; exit 1; }

# A pin is usable only when release engineering replaced the __…__ placeholder.
pin_set() {
	case "$1" in
	'' | __SECRIA_*__) return 1 ;;
	*) return 0 ;;
	esac
}

[ "$(uname -s)" = "Linux" ] || die "this installer supports Linux only"
[ "$(id -u)" -eq 0 ] || die "run as root, e.g.: curl -fsSL $BASE_URL/install.sh | sudo sh"

case "$(uname -m)" in
	x86_64 | amd64)  ARCH=amd64;  RPM_ARCH=x86_64;  SPHINCS_TOOL_SHA256="$SPHINCS_TOOL_SHA256_AMD64" ;;
	aarch64 | arm64) ARCH=arm64;  RPM_ARCH=aarch64; SPHINCS_TOOL_SHA256="$SPHINCS_TOOL_SHA256_ARM64" ;;
	*) die "unsupported architecture: $(uname -m) (supported: amd64, arm64)" ;;
esac

if command -v curl >/dev/null 2>&1; then
	fetch() { curl -fsSL "$1" -o "$2"; }
elif command -v wget >/dev/null 2>&1; then
	fetch() { wget -qO "$2" "$1"; }
else
	die "need curl or wget"
fi
command -v sha256sum >/dev/null 2>&1 || die "need sha256sum (coreutils)"

TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT INT TERM

# --- Path 1: signed package repository (self-updating via apt/dnf) -----------
# Entered only when the repo key fingerprint is pinned and a supported package
# manager exists. From that point every failure is fatal — never a silent
# downgrade to the tarball path (an attacker who can block one URL must not be
# able to demote the install). SECRIA_NO_REPO=1 chooses the tarball explicitly.

fetch_and_pin_repo_key() {
	command -v gpg >/dev/null 2>&1 || die "gpg not found — cannot verify the pinned repo signing key (install gnupg, or set SECRIA_NO_REPO=1 to use the tarball path)"
	fetch "$BASE_URL/keys/secria-repo.asc" "$TMP/secria-repo.asc" || die "could not fetch the repo signing key — refusing to fall back to the tarball path (set SECRIA_NO_REPO=1 to choose it explicitly)"
	ACTUAL_FPR="$(gpg --batch --quiet --show-keys --with-colons "$TMP/secria-repo.asc" 2>/dev/null | awk -F: '/^fpr:/{print $10; exit}')"
	# A wrong fingerprint is an attack or a grave publishing error, never
	# something to fall back from — hard stop.
	[ "$ACTUAL_FPR" = "$REPO_GPG_FINGERPRINT" ] || die "repo signing key fingerprint mismatch (expected $REPO_GPG_FINGERPRINT, got ${ACTUAL_FPR:-none}) — refusing to trust this repository"
}

install_via_apt() {
	fetch_and_pin_repo_key
	say "Adding the Secria apt repository (key fingerprint verified)..."
	KEYRING=/usr/share/keyrings/secria-vpn-archive-keyring.gpg
	SOURCES_LIST=/etc/apt/sources.list.d/secria-vpn.list
	gpg --batch --yes --dearmor -o "$KEYRING" "$TMP/secria-repo.asc" || { rm -f "$KEYRING"; die "could not write $KEYRING"; }
	printf 'deb [signed-by=%s] %s/apt stable main\n' "$KEYRING" "$BASE_URL" > "$SOURCES_LIST" \
		|| { rm -f "$KEYRING" "$SOURCES_LIST"; die "could not write $SOURCES_LIST"; }
	say "Installing secria-vpn via apt..."
	apt-get update -qq || { rm -f "$KEYRING" "$SOURCES_LIST"; die "apt-get update failed — removed the Secria repo configuration so a re-run starts clean"; }
	apt-get install -y secria-vpn || { rm -f "$KEYRING" "$SOURCES_LIST"; die "apt-get install secria-vpn failed — removed the Secria repo configuration so a re-run starts clean"; }
	say "Done. Future updates arrive via: sudo apt upgrade"
}

install_via_dnf() {
	fetch_and_pin_repo_key
	say "Adding the Secria yum/dnf repository (key fingerprint verified)..."
	rpm --import "$TMP/secria-repo.asc" || die "rpm --import of the repo signing key failed"
	REPO_FILE=/etc/yum.repos.d/secria-vpn.repo
	cat > "$REPO_FILE" <<EOF || { rm -f "$REPO_FILE"; die "could not write $REPO_FILE"; }
[secria-vpn]
name=Secria VPN
baseurl=$BASE_URL/rpm/$RPM_ARCH
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=$BASE_URL/keys/secria-repo.asc
EOF
	say "Installing secria-vpn via $PKG_MGR..."
	"$PKG_MGR" install -y secria-vpn || { rm -f "$REPO_FILE"; die "$PKG_MGR install secria-vpn failed — removed $REPO_FILE so a re-run starts clean (the imported repo signing key remains in the rpm keyring)"; }
	say "Done. Future updates arrive via: sudo $PKG_MGR upgrade"
}

install_via_pacman() {
	fetch_and_pin_repo_key
	say "Adding the Secria pacman repository (key fingerprint verified)..."
	# pacman verifies package signatures against its own keyring, so import the
	# fingerprint-checked key and locally sign it before trusting the repo.
	pacman-key --init >/dev/null 2>&1 || true
	pacman-key --add "$TMP/secria-repo.asc" || die "pacman-key --add failed"
	pacman-key --lsign-key "$REPO_GPG_FINGERPRINT" || die "pacman-key --lsign-key failed — could not locally trust the Secria repo key"
	if ! grep -q '^\[secria-vpn\]' /etc/pacman.conf; then
		# Packages are individually signed by the fingerprint-pinned key; the DB is
		# not, so require package sigs and leave the DB signature optional.
		printf '\n[secria-vpn]\nSigLevel = Required DatabaseOptional\nServer = %s/arch/$arch\n' "$BASE_URL" >> /etc/pacman.conf
	fi
	say "Installing secria-vpn via pacman..."
	pacman -Sy --noconfirm secria-vpn || die "pacman -Sy secria-vpn failed — remove the [secria-vpn] stanza from /etc/pacman.conf before retrying"
	say "Done. Future updates arrive via: sudo pacman -Syu"
}

if [ -z "${SECRIA_NO_REPO:-}" ]; then
	if pin_set "$REPO_GPG_FINGERPRINT"; then
		if command -v apt-get >/dev/null 2>&1; then
			install_via_apt
			exit 0
		elif command -v dnf >/dev/null 2>&1; then
			PKG_MGR=dnf
			install_via_dnf
			exit 0
		elif command -v yum >/dev/null 2>&1; then
			PKG_MGR=yum
			install_via_dnf
			exit 0
		elif command -v pacman >/dev/null 2>&1; then
			install_via_pacman
			exit 0
		fi
	else
		warn "repo GPG fingerprint not pinned in this installer build — using the tarball path (no package-manager auto-updates)"
	fi
fi

# --- Path 2: static tarball (SHA-256 + SLH-DSA verified) ---------------------

TARBALL="secria-vpn-${VERSION}-linux-${ARCH}.tar.gz"

say "Downloading ${TARBALL}..."
fetch "$BASE_URL/download/linux/$TARBALL" "$TMP/$TARBALL" || die "download failed: $BASE_URL/download/linux/$TARBALL"

# Linux-scoped, independently-signed manifest so a Windows/macOS release can't invalidate the Linux one-liner's SLH-DSA signature; fall back to the shared manifest on older hosts.
MANIFEST=SHA256SUMS.linux
if fetch "$BASE_URL/$MANIFEST" "$TMP/$MANIFEST"; then
	:
else
	MANIFEST=SHA256SUMS
	fetch "$BASE_URL/$MANIFEST" "$TMP/$MANIFEST" || die "could not fetch checksums"
fi

# SLH-DSA (post-quantum) release signature over SHA256SUMS. Verifying this one
# file extends trust to every artifact listed in it. Enabled once the pins at
# the top of this script are set; a tampered SHA256SUMS, public key, or
# verifier binary is always fatal — never a fallback.
#
# The four SLH-DSA pins are published as one block (the release summary emits
# key hash, both tool hashes, and the tool version together), so a mixed state
# is never a legitimate build — only an operator slip. Warning-and-skipping on
# it would silently downgrade an installer that was meant to verify: hard stop.
PINS_SET=0
for PIN in "$RELEASE_PUBKEY_SHA256" "$SPHINCS_TOOL_SHA256_AMD64" "$SPHINCS_TOOL_SHA256_ARM64" "$SPHINCS_TOOL_VERSION"; do
	if pin_set "$PIN"; then PINS_SET=$((PINS_SET + 1)); fi
done
if [ "$PINS_SET" -ne 0 ] && [ "$PINS_SET" -ne 4 ]; then
	die "inconsistent SLH-DSA trust anchors ($PINS_SET of 4 pinned) — set RELEASE_PUBKEY_SHA256, SPHINCS_TOOL_SHA256_AMD64, SPHINCS_TOOL_SHA256_ARM64 and SPHINCS_TOOL_VERSION together (all four are emitted by the release publish summary)"
fi

if pin_set "$RELEASE_PUBKEY_SHA256" && pin_set "$SPHINCS_TOOL_SHA256" && pin_set "$SPHINCS_TOOL_VERSION"; then
	say "Verifying SLH-DSA release signature..."
	SPHINCS_TOOL="sphincs-verify-${SPHINCS_TOOL_VERSION}-linux-${ARCH}"
	fetch "$BASE_URL/${MANIFEST}.sig"                 "$TMP/${MANIFEST}.sig" || die "could not fetch ${MANIFEST}.sig"
	fetch "$BASE_URL/keys/release-slhdsa.pub"         "$TMP/release.pub"    || die "could not fetch release public key"
	fetch "$BASE_URL/download/tools/$SPHINCS_TOOL"    "$TMP/sphincs-verify" || die "could not fetch $SPHINCS_TOOL"

	PUB_ACTUAL="$(sha256sum "$TMP/release.pub" | awk '{print $1}')"
	[ "$PUB_ACTUAL" = "$RELEASE_PUBKEY_SHA256" ] || die "release public key hash mismatch (expected $RELEASE_PUBKEY_SHA256, got $PUB_ACTUAL) — refusing to install"

	TOOL_ACTUAL="$(sha256sum "$TMP/sphincs-verify" | awk '{print $1}')"
	[ "$TOOL_ACTUAL" = "$SPHINCS_TOOL_SHA256" ] || die "sphincs-verify tool hash mismatch (expected $SPHINCS_TOOL_SHA256, got $TOOL_ACTUAL) — refusing to install"

	chmod +x "$TMP/sphincs-verify"
	"$TMP/sphincs-verify" \
		--public-key "$TMP/release.pub" \
		--signature "$TMP/${MANIFEST}.sig" \
		--message "$TMP/$MANIFEST" >/dev/null \
		|| die "SLH-DSA signature verification FAILED for $MANIFEST — refusing to install"
	say "SLH-DSA signature valid."
else
	warn "SLH-DSA signature verification skipped — trust anchors not pinned in this installer build (pre-release only; SHA-256 check still applies)"
fi

say "Verifying checksum..."
EXPECT="$(grep " ${TARBALL}\$" "$TMP/$MANIFEST" | awk '{print $1}')"
[ -n "$EXPECT" ] || die "no checksum for ${TARBALL} in $MANIFEST"
ACTUAL="$(sha256sum "$TMP/$TARBALL" | awk '{print $1}')"
[ "$EXPECT" = "$ACTUAL" ] || die "checksum mismatch — refusing to install (expected $EXPECT, got $ACTUAL)"

say "Installing..."
tar -xzf "$TMP/$TARBALL" -C "$TMP"
SRC="$(find "$TMP" -type f -path '*/bin/secria-vpn' -perm -u+x | head -n1)"
[ -n "$SRC" ] || SRC="$(find "$TMP" -type f -name secria-vpn -perm -u+x | head -n1)"
[ -n "$SRC" ] || die "binary not found in archive"
SRC_ROOT="$(dirname "$(dirname "$SRC")")"

# One install path, always the tarball's own installer: a second copy of it here drifted, and
# the copy was the one that silently skipped the AppArmor override and the dependency check.
[ -x "$SRC_ROOT/install.sh" ] || die "the archive has no install.sh — refusing to install by hand"
if [ -n "${SECRIA_BIN_DIR:-}" ]; then
	SECRIA_BIN_DIR="$SECRIA_BIN_DIR" "$SRC_ROOT/install.sh"
else
	"$SRC_ROOT/install.sh"
fi
