#!/bin/sh
# Route6 client installer.
#
#   curl -fsSL https://dl.route6.me/install.sh | sh
#
# POSIX sh on purpose: this has to run under busybox on Alpine and under the
# stripped-down shells inside agent sandboxes, not just bash.
#
# What it does, in order: work out your platform, resolve the version, download
# the matching build AND the signed checksum manifest, REFUSE TO CONTINUE unless
# they match, then put the binary somewhere on your PATH.
#
# The checksum step is not decoration. This script fetches an executable over
# the network and that executable will hold your network identity, so a download
# that cannot be verified is discarded rather than run.
#
# Environment:
#   ROUTE6_API_KEY       if set, write ~/.r6me/config.toml (mode 0600)
#   R6ME_VERSION         install this version instead of the current stable
#   R6ME_BASE_URL        alternate artifact host (staging, mirrors, tests)
#   R6ME_INSTALL_DIR     override the install directory
#   R6ME_ARCH_OVERRIDE   force an arch string (used by the test gate)
#
# Flags: --version X --key K --dir D --dry-run --no-systemd --systemd --help
set -eu

BASE_URL="${R6ME_BASE_URL:-https://dl.route6.me}"
MIRROR_URL="https://github.com/route6me/r6me-releases/releases/download"
VERSION="${R6ME_VERSION:-}"
API_KEY="${ROUTE6_API_KEY:-}"
INSTALL_DIR="${R6ME_INSTALL_DIR:-}"
DRY_RUN=0
WANT_SYSTEMD=auto

say()  { printf '%s\n' "$*"; }
err()  { printf 'error: %s\n' "$*" >&2; }
die()  { err "$*"; exit 1; }

usage() {
    cat <<'EOF'
Route6 client installer

  curl -fsSL https://dl.route6.me/install.sh | sh

Options:
  --version <vX.Y.Z>  install a specific version (default: current stable)
  --key <sk_a6_...>   write ~/.r6me/config.toml with this API key
  --dir <path>        install directory (default: /usr/local/bin, or
                      ~/.local/bin when not running as root)
  --systemd           install a systemd unit even if not detected as default
  --no-systemd        never install a systemd unit
  --dry-run           report what would happen and change nothing
  --help              this text

Environment: ROUTE6_API_KEY, R6ME_VERSION, R6ME_BASE_URL, R6ME_INSTALL_DIR
EOF
}

while [ $# -gt 0 ]; do
    case "$1" in
        --version) VERSION="${2:-}"; shift 2 ;;
        --key)     API_KEY="${2:-}"; shift 2 ;;
        --dir)     INSTALL_DIR="${2:-}"; shift 2 ;;
        --dry-run) DRY_RUN=1; shift ;;
        --systemd) WANT_SYSTEMD=yes; shift ;;
        --no-systemd) WANT_SYSTEMD=no; shift ;;
        --help|-h) usage; exit 0 ;;
        *) die "unknown option: $1 (try --help)" ;;
    esac
done

# ---------------------------------------------------------------- platform ---
os=$(uname -s 2>/dev/null || echo unknown)
case "$os" in
    Linux)  OS=linux ;;
    Darwin) OS=darwin ;;
    *) die "unsupported operating system: $os. Windows builds are published at ${BASE_URL}/ — install them by hand." ;;
esac

raw_arch="${R6ME_ARCH_OVERRIDE:-$(uname -m 2>/dev/null || echo unknown)}"
case "$raw_arch" in
    x86_64|amd64)   ARCH=amd64 ;;
    aarch64|arm64)  ARCH=arm64 ;;
    armv7l|armv7)   ARCH=armv7 ;;
    armv6l|armv6)   ARCH=armv6 ;;
    armv5*|armv5)   ARCH=armv5 ;;
    riscv64)        ARCH=riscv64 ;;
    mips)           ARCH=mips_softfloat ;;
    mipsel|mipsle)  ARCH=mipsle_softfloat ;;
    *) die "unsupported architecture: $raw_arch. Published builds are listed at ${BASE_URL}/" ;;
esac

# darwin only ships amd64/arm64 — say so rather than 404ing later.
if [ "$OS" = darwin ]; then
    case "$ARCH" in
        amd64|arm64) ;;
        *) die "unsupported architecture for macOS: $raw_arch" ;;
    esac
fi

# ------------------------------------------------------------------- tools ---
if command -v curl >/dev/null 2>&1; then
    dl() { curl -fsSL "$1" -o "$2"; }
    dls() { curl -fsSL "$1"; }
elif command -v wget >/dev/null 2>&1; then
    dl() { wget -qO "$2" "$1"; }
    dls() { wget -qO- "$1"; }
else
    die "need curl or wget to download"
fi

# sha256: coreutils on Linux, shasum on macOS, busybox has sha256sum too.
if command -v sha256sum >/dev/null 2>&1; then
    sum() { sha256sum "$1" | cut -d' ' -f1; }
elif command -v shasum >/dev/null 2>&1; then
    sum() { shasum -a 256 "$1" | cut -d' ' -f1; }
else
    die "need sha256sum or shasum to verify the download"
fi

# ----------------------------------------------------------------- version ---
# The canonical host publishes a one-line pointer so this script does not have
# to scrape a directory index (and so a bad release can be rolled back for every
# new install by moving one file).
if [ -z "$VERSION" ]; then
    VERSION=$(dls "${BASE_URL}/stable" 2>/dev/null | tr -d ' \t\r\n') || true
    [ -n "$VERSION" ] || die "could not resolve the current version from ${BASE_URL}/stable — pass --version vX.Y.Z"
fi
case "$VERSION" in
    v*) ;;
    *) VERSION="v${VERSION}" ;;
esac
BARE="${VERSION#v}"

ASSET="r6me_${BARE}_${OS}_${ARCH}.tar.gz"

# ------------------------------------------------------------- destination ---
if [ -z "$INSTALL_DIR" ]; then
    if [ "$(id -u)" = "0" ]; then
        INSTALL_DIR=/usr/local/bin
    else
        INSTALL_DIR="${HOME}/.local/bin"
    fi
fi

say "Route6 client ${VERSION}"
say "  platform:  ${OS}/${ARCH}"
say "  source:    ${BASE_URL}/${VERSION}/${ASSET}"
say "  install:   ${INSTALL_DIR}/r6me"

if [ "$DRY_RUN" = "1" ]; then
    say ""
    say "--dry-run: nothing was changed."
    exit 0
fi

# ------------------------------------------------------------- download ------
tmp=$(mktemp -d 2>/dev/null || mktemp -d -t r6me)
trap 'rm -rf "$tmp"' EXIT INT TERM

say ""
say "downloading..."
if ! dl "${BASE_URL}/${VERSION}/${ASSET}" "${tmp}/${ASSET}" 2>/dev/null; then
    say "  ${BASE_URL} unreachable, trying the public mirror"
    dl "${MIRROR_URL}/${VERSION}/${ASSET}" "${tmp}/${ASSET}" \
        || die "could not download ${ASSET} from either location"
    dl "${MIRROR_URL}/${VERSION}/checksums.txt" "${tmp}/checksums.txt" \
        || die "could not download checksums.txt from the mirror"
else
    dl "${BASE_URL}/${VERSION}/checksums.txt" "${tmp}/checksums.txt" \
        || die "could not download checksums.txt — refusing to install an unverified binary"
fi

# --------------------------------------------------------------- verify ------
# Fail closed on every branch: a missing manifest line is as bad as a mismatch,
# because both mean we cannot say what we just downloaded.
want=$(grep " ${ASSET}\$" "${tmp}/checksums.txt" 2>/dev/null | cut -d' ' -f1 || true)
[ -n "$want" ] || die "checksum for ${ASSET} is not listed in checksums.txt — refusing to install"
got=$(sum "${tmp}/${ASSET}")
if [ "$want" != "$got" ]; then
    err "checksum mismatch for ${ASSET}"
    err "  expected: ${want}"
    err "  actual:   ${got}"
    die "refusing to install — the download does not match the published checksum"
fi
say "  checksum verified"

# -------------------------------------------------------------- install ------
( cd "$tmp" && tar xzf "$ASSET" ) || die "could not unpack ${ASSET}"
[ -f "${tmp}/r6me" ] || die "archive did not contain an r6me binary"

mkdir -p "$INSTALL_DIR" || die "could not create ${INSTALL_DIR}"
# install(1) is absent on some minimal images; cp+chmod is universal. Write to a
# temp name and move, so upgrading does not truncate a running binary in place.
cp "${tmp}/r6me" "${INSTALL_DIR}/.r6me.new" || die "could not write to ${INSTALL_DIR}"
chmod 0755 "${INSTALL_DIR}/.r6me.new"
mv -f "${INSTALL_DIR}/.r6me.new" "${INSTALL_DIR}/r6me" || die "could not install into ${INSTALL_DIR}"
say "  installed ${INSTALL_DIR}/r6me"

# ---------------------------------------------------------------- config -----
STATE_DIR="${HOME:-/root}/.r6me"

# Record how the binary got here. Nothing on the wire reveals this — the same
# executable is what this script unpacks, what the npm and pip launchers exec,
# and what the container image runs — so the installer leaves a mark and the
# daemon reports it on connect. It is what makes "is anyone still installing the
# old client" answerable.
#
# NEVER overwrite an existing marker. `route6 upgrade` from the npm launcher
# runs this same script, and relabelling that machine as a curl install would
# quietly move a number the legacy-sunset decision reads.
mkdir -p "$STATE_DIR" 2>/dev/null || true
chmod 700 "$STATE_DIR" 2>/dev/null || true
if [ ! -s "${STATE_DIR}/install_channel" ]; then
    printf 'curl\n' > "${STATE_DIR}/install_channel" 2>/dev/null || true
fi

if [ -n "$API_KEY" ]; then
    mkdir -p "$STATE_DIR"
    chmod 700 "$STATE_DIR" 2>/dev/null || true
    if [ -f "${STATE_DIR}/config.toml" ] && grep -q '^api_key' "${STATE_DIR}/config.toml" 2>/dev/null; then
        say "  ${STATE_DIR}/config.toml already has a key — left alone"
    else
        printf 'api_key = "%s"\n' "$API_KEY" > "${STATE_DIR}/config.toml"
        chmod 600 "${STATE_DIR}/config.toml"
        say "  wrote ${STATE_DIR}/config.toml"
    fi
fi

# --------------------------------------------------------------- systemd -----
# Offered, never assumed. It does not apply on macOS, inside containers (which
# have their own supervisor), or for a non-root install — and an agent sandbox
# frequently has no init at all. Not having it is not an error.
install_unit=0
case "$WANT_SYSTEMD" in
    no)  install_unit=0 ;;
    yes) install_unit=1 ;;
    auto)
        if [ "$OS" = linux ] && [ "$(id -u)" = "0" ] \
           && command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]; then
            install_unit=1
        fi ;;
esac

if [ "$install_unit" = "1" ]; then
    if [ ! -d /run/systemd/system ] && [ "$WANT_SYSTEMD" = yes ]; then
        say "  --systemd asked for, but systemd is not running here — skipping"
    else
        cat > /etc/systemd/system/r6me.service <<EOF
[Unit]
Description=Route6 agent
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=${INSTALL_DIR}/r6me up --fg
Restart=always
User=root
Environment=HOME=${HOME:-/root}

[Install]
WantedBy=multi-user.target
EOF
        systemctl daemon-reload >/dev/null 2>&1 || true
        say "  installed /etc/systemd/system/r6me.service"
        say "    start it with: systemctl enable --now r6me"
    fi
fi

# ------------------------------------------------------------------ next -----
say ""
"${INSTALL_DIR}/r6me" version 2>/dev/null || true

case ":${PATH}:" in
    *":${INSTALL_DIR}:"*) ;;
    *) say ""
       say "note: ${INSTALL_DIR} is not on your PATH. Add it:"
       say "  export PATH=\"${INSTALL_DIR}:\$PATH\"" ;;
esac

say ""
if [ -z "$API_KEY" ] && [ ! -f "${STATE_DIR}/config.toml" ]; then
    say "Next: put your API key in ${STATE_DIR}/config.toml"
    say "  mkdir -p ${STATE_DIR} && chmod 700 ${STATE_DIR}"
    say "  echo 'api_key = \"sk_a6_your_key_here\"' > ${STATE_DIR}/config.toml"
    say "  chmod 600 ${STATE_DIR}/config.toml"
    say ""
fi
say "Then: r6me up && r6me status"
