#!/bin/sh
# Unrent agent installer.
#
#   curl -fsSL https://get.unrent.dev/install.sh | sudo sh -s -- --token <enrollment token>
#
# What it does (docs/install.md has the long version):
#   1. checks this server (root, Linux, systemd, amd64/arm64, curl or wget, sha256sum, 200 MB free, Docker);
#   2. downloads the agent release from get.unrent.dev and verifies its SHA-256 against the release manifest
#      AND the release's SHA256SUMS (fails closed on any mismatch);
#   3. installs unrentd, unrent-exec and unrent to /usr/local/lib/unrent/<version>/ (symlinked from
#      /usr/local/bin), creates the system user `unrent` and the systemd units;
#   4. enrolls this server with the one-time token (the token is passed on stdin, never printed);
#   5. starts the agent and waits until it is connected.
# Re-running it upgrades the binaries to the newest release and keeps the enrollment.
#
#   sh install.sh --uninstall [--purge]     remove the agent (and, with --purge, its identity and state)
#   sh install.sh --help                    every flag
#
# Everything runs inside main(), called on the last line, so a partial download runs nothing.
set -eu

REPO=PILOTAAI/unrent
LIB_DIR=/usr/local/lib/unrent
BIN_DIR=/usr/local/bin
UNIT_DIR=/usr/local/lib/systemd/system
STATE_DIR=/var/lib/unrent
ETC_DIR=/etc/unrent
AGENT_USER=unrent
BINARIES="unrentd unrent-exec unrent"
UNITS="unrent-exec.service unrentd.service"
MIN_FREE_KB=204800
CONNECT_TIMEOUT=30

step() { printf '==> %s\n' "$*"; }
info() { printf '    %s\n' "$*"; }
warn() { printf 'warning: %s\n' "$*" >&2; }
die() { printf 'error: %s\n' "$*" >&2; exit 1; }

usage() {
  cat <<'USAGE'
Unrent agent installer

  curl -fsSL https://get.unrent.dev/install.sh | sudo sh -s -- --token <enrollment token>

Options:
  --token <token>        one-time enrollment token from the console (first install, or with --force)
  --version <v>          install this release instead of the newest one
  --force                replace an existing enrollment (the old identity is moved aside, not deleted)
  --install-docker       install Docker from the distribution's packages if it is missing
  --uninstall            stop and remove the agent; keeps /var/lib/unrent and /etc/unrent
  --purge                with --uninstall: also delete /var/lib/unrent, /etc/unrent and the unrent user
  --download-only <dir>  download and verify the release into <dir>, install nothing (no root needed)
  -h, --help             this help

Development only (written to /etc/unrent/agent.env, used by both services):
  --api <url>            control plane (default https://agents.unrent.dev)
  --base-url <url>       where releases are downloaded from (default https://get.unrent.dev)
  --trust-ca <id=key>    extra org-CA key(s)
  --rp-id <host>         WebAuthn relying party for approvals
  --origin <url>         console origin for approvals
USAGE
}

# ---------------------------------------------------------------------------------------------------- arguments

parse_args() {
  TOKEN='' VERSION='' API='' BASE_URL='https://get.unrent.dev' TRUST_CA='' RP_ID='' ORIGIN=''
  FORCE=0 INSTALL_DOCKER=0 UNINSTALL=0 PURGE=0 DOWNLOAD_ONLY=''
  while [ $# -gt 0 ]; do
    opt=$1 val='' has_val=0
    case $opt in
      --*=*) val=${opt#*=} opt=${opt%%=*} has_val=1 ;;
    esac
    case $opt in
      --token|--version|--api|--base-url|--trust-ca|--rp-id|--origin|--download-only)
        if [ "$has_val" = 0 ]; then
          [ $# -ge 2 ] || die "$opt needs a value"
          val=$2
          shift
        fi
        [ -n "$val" ] || die "$opt needs a value"
        case $opt in
          --token) TOKEN=$val ;;
          --version) VERSION=$val ;;
          --api) API=$val ;;
          --base-url) BASE_URL=$val ;;
          --trust-ca) TRUST_CA=$val ;;
          --rp-id) RP_ID=$val ;;
          --origin) ORIGIN=$val ;;
          --download-only) DOWNLOAD_ONLY=$val ;;
        esac
        ;;
      --force) FORCE=1 ;;
      --install-docker) INSTALL_DOCKER=1 ;;
      --uninstall) UNINSTALL=1 ;;
      --purge) PURGE=1 ;;
      -h|--help) usage; exit 0 ;;
      *) die "unknown option $opt (see --help)" ;;
    esac
    shift
  done
  BASE_URL=${BASE_URL%/}
  validate_args
}

validate_args() {
  if [ -n "$TOKEN" ]; then
    case $TOKEN in
      *[!A-Za-z0-9_-]*) die "the enrollment token has unexpected characters; copy the command from the console again" ;;
    esac
  fi
  [ -z "$VERSION" ] || valid_version "$VERSION" || die "--version must look like 1.2.3 (got $VERSION)"
  valid_origin_url "$BASE_URL" || die "--base-url must be https:// (or http:// on localhost for development)"
  [ -z "$API" ] || valid_origin_url "$API" || die "--api must be https:// (or http:// on localhost for development)"
  [ -z "$ORIGIN" ] || valid_origin_url "$ORIGIN" || die "--origin must be https:// (or http:// on localhost for development)"
  for v in "$TRUST_CA" "$RP_ID"; do
    case $v in *[!A-Za-z0-9._:=,-]*) die "development flags may only contain letters, digits and . _ : = , -" ;; esac
  done
  if [ "$PURGE" = 1 ] && [ "$UNINSTALL" = 0 ]; then die "--purge only works with --uninstall"; fi
  if [ "$UNINSTALL" = 1 ] && [ -n "$TOKEN" ]; then die "--uninstall does not take --token"; fi
  if [ "$FORCE" = 1 ] && [ -z "$TOKEN" ]; then die "--force replaces an enrollment, so it needs --token"; fi
  return 0
}

valid_version() {
  printf '%s\n' "$1" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$'
}

# https://host[:port][/path], or http:// only for loopback (local development and tests).
valid_origin_url() {
  case $1 in
    *[!A-Za-z0-9._:/\[\]-]*) return 1 ;;
    https://?*) return 0 ;;
    http://127.0.0.1|http://127.0.0.1:*|http://127.0.0.1/*) return 0 ;;
    http://localhost|http://localhost:*|http://localhost/*) return 0 ;;
    http://\[::1\]|http://\[::1\]:*|http://\[::1\]/*) return 0 ;;
  esac
  return 1
}

# ---------------------------------------------------------------------------------------------------- helpers

detect_arch() {
  case $(uname -m) in
    x86_64|amd64) ARCH=amd64 ;;
    aarch64|arm64) ARCH=arm64 ;;
    *) die "unsupported CPU architecture $(uname -m): Unrent runs on amd64 (x86_64) and arm64 (aarch64)" ;;
  esac
}

detect_tools() {
  if command -v curl >/dev/null 2>&1; then
    DOWNLOADER=curl
  elif command -v wget >/dev/null 2>&1; then
    DOWNLOADER=wget
  else
    die "curl or wget is required: install one (e.g. apt-get install -y curl) and run this again"
  fi
  if command -v sha256sum >/dev/null 2>&1; then
    SHA=sha256sum
  elif command -v shasum >/dev/null 2>&1; then
    SHA=shasum
  else
    die "sha256sum (coreutils) or shasum is required to verify the download"
  fi
}

# fetch <url> <file>
fetch() {
  case $DOWNLOADER in
    curl)
      case $1 in
        https://*) curl -fsSL --proto '=https' --tlsv1.2 --retry 3 --connect-timeout 15 -o "$2" "$1" ;;
        *) curl -fsSL --retry 3 --connect-timeout 15 -o "$2" "$1" ;;
      esac
      ;;
    wget) wget -q -T 30 -t 3 -O "$2" "$1" ;;
  esac
}

sha256_of() {
  if [ "$SHA" = sha256sum ]; then
    sha256sum "$1" | awk '{print $1}'
  else
    shasum -a 256 "$1" | awk '{print $1}'
  fi
}

# The manifest is our own JSON (apps/get/scripts/fetch-release.mjs); strip whitespace and match its fields.
manifest_flat() { tr -d ' \t\r\n' < "$1"; echo; }

# manifest_version <file>: the version, or "null" when no release has been published.
manifest_version() {
  mv_flat=$(manifest_flat "$1")
  case $mv_flat in
    *'"version":null'*) echo null; return 0 ;;
  esac
  printf '%s\n' "$mv_flat" | sed -n 's/.*"version":"\([^"]*\)".*/\1/p'
}

# manifest_sha <file> <arch>: the tarball's SHA-256 for linux-<arch>.
manifest_sha() {
  manifest_flat "$1" | sed -n "s/.*\"linux-$2\":{[^}]*\"sha256\":\"\([0-9a-f]\{64\}\)\".*/\1/p"
}

# sums_sha <SHA256SUMS> <file name>
sums_sha() {
  awk -v f="$2" '$2 == f || $2 == "*" f { print $1; exit }' "$1"
}

# download_release <dir>: fetch the manifest and tarball, verify both checksums. Sets REL_VERSION, TARBALL.
download_release() {
  dl_dir=$1
  if [ -n "$VERSION" ]; then
    manifest_url="$BASE_URL/releases/$VERSION/manifest.json"
  else
    manifest_url="$BASE_URL/latest.json"
  fi
  fetch "$manifest_url" "$dl_dir/manifest.json" || die "could not download $manifest_url (check the network, or the version with --version)"
  REL_VERSION=$(manifest_version "$dl_dir/manifest.json")
  if [ "$REL_VERSION" = null ]; then
    die "no agent release has been published yet at $BASE_URL; try again later"
  fi
  valid_version "$REL_VERSION" || die "the release manifest at $manifest_url is not valid"
  [ -z "$VERSION" ] || [ "$VERSION" = "$REL_VERSION" ] || die "the manifest at $manifest_url is for $REL_VERSION, not $VERSION"
  want=$(manifest_sha "$dl_dir/manifest.json" "$ARCH")
  [ -n "$want" ] || die "release $REL_VERSION has no linux-$ARCH build"

  name="unrent-$REL_VERSION-linux-$ARCH.tar.gz"
  TARBALL="$dl_dir/$name"
  step "Downloading Unrent agent $REL_VERSION (linux-$ARCH)"
  fetch "$BASE_URL/releases/$REL_VERSION/$name" "$TARBALL" || die "could not download $BASE_URL/releases/$REL_VERSION/$name"
  fetch "$BASE_URL/releases/$REL_VERSION/SHA256SUMS" "$dl_dir/SHA256SUMS" || die "could not download SHA256SUMS for $REL_VERSION"
  got=$(sha256_of "$TARBALL")
  sums=$(sums_sha "$dl_dir/SHA256SUMS" "$name")
  if [ "$got" != "$want" ]; then
    die "checksum mismatch for $name: expected $want (manifest), got $got. Nothing was installed. Do not retry from this network; report it to security@unrent.dev"
  fi
  if [ "$got" != "$sums" ]; then
    die "checksum mismatch for $name: SHA256SUMS says ${sums:-nothing}, the file is $got. Nothing was installed; report it to security@unrent.dev"
  fi
  info "sha256 $got (matches the manifest and SHA256SUMS)"
  info "provenance (optional): gh attestation verify $name --repo $REPO --signer-workflow $REPO/.github/workflows/agent-release.yml"
}

# extract_release <tarball> <dir>: unpack and check the layout. Sets PKG (the unpacked directory).
extract_release() {
  tar -xzf "$1" -C "$2" || die "could not unpack $1"
  PKG="$2/unrent-$REL_VERSION-linux-$ARCH"
  for b in $BINARIES; do [ -f "$PKG/bin/$b" ] || die "the release is missing bin/$b"; done
  for u in $UNITS; do [ -f "$PKG/systemd/$u" ] || die "the release is missing systemd/$u"; done
  [ "$(cat "$PKG/VERSION" 2>/dev/null)" = "$REL_VERSION" ] || die "the release's VERSION file does not say $REL_VERSION"
}

installed_version() {
  iv=$(readlink "$LIB_DIR/current" 2>/dev/null || true)
  iv=${iv##*/}
  for b in $BINARIES; do [ -x "$LIB_DIR/$iv/bin/$b" ] || iv=''; done
  printf '%s\n' "$iv"
}

is_enrolled() { [ -f "$STATE_DIR/agent.json" ]; }

agent_field() { # agent_field <agent.json> <key>
  manifest_flat "$1" | sed -n "s/.*\"$2\":\"\([^\"]*\)\".*/\1/p"
}

# ---------------------------------------------------------------------------------------------------- preflight

preflight() {
  step "Checking this server"
  [ "$(id -u)" = 0 ] || die "run this as root: curl -fsSL https://get.unrent.dev/install.sh | sudo sh -s -- --token <token>"
  [ "$(uname -s)" = Linux ] || die "Unrent runs on Linux servers only (this is $(uname -s))"
  [ -d /run/systemd/system ] || die "systemd is required (it runs the agent and restarts it); this server does not boot with systemd"
  detect_arch
  detect_tools
  free_kb=$(df -Pk /var/lib 2>/dev/null | awk 'NR == 2 { print $4 }')
  case $free_kb in ''|*[!0-9]*) free_kb=0 ;; esac
  if [ "$free_kb" -gt 0 ] && [ "$free_kb" -lt "$MIN_FREE_KB" ]; then
    die "at least 200 MB must be free in /var/lib (only $((free_kb / 1024)) MB is)"
  fi
  if [ -n "$TOKEN" ] && is_enrolled && [ "$FORCE" = 0 ]; then
    die "this server is already enrolled as agent $(agent_field "$STATE_DIR/agent.json" agent_id) (org $(agent_field "$STATE_DIR/agent.json" org_id)).
       To upgrade it, run this again without --token. To enroll it again (for example into another organisation),
       add --force: the current identity is moved aside and the old agent stops working."
  fi
  if [ -z "$TOKEN" ] && ! is_enrolled; then
    die "this server is not enrolled yet: copy the install command (with --token) from https://app.unrent.dev"
  fi
  for t in setpriv runuser; do
    if command -v "$t" >/dev/null 2>&1; then DROP=$t; break; fi
  done
  [ -n "${DROP:-}" ] || die "setpriv or runuser (util-linux) is required to run the enrollment unprivileged"
  info "linux-$ARCH, systemd, $DOWNLOADER, $SHA, $((free_kb / 1024)) MB free in /var/lib"
  check_docker
}

check_docker() {
  if command -v docker >/dev/null 2>&1; then
    dv=$(docker version --format '{{.Server.Version}}' 2>/dev/null) || dv=''
    if [ -n "$dv" ]; then
      info "Docker $dv"
    else
      warn "the docker command is installed but the Docker daemon is not reachable (systemctl status docker); container jobs fail until it runs"
    fi
    return 0
  fi
  if [ "$INSTALL_DOCKER" = 0 ]; then
    warn "Docker is not installed. The agent connects and can inspect this server, but cannot deploy apps or
         databases until Docker is installed. Run this again with --install-docker, or install Docker Engine yourself."
    return 0
  fi
  install_docker
}

# Distribution packages only: signed by the distribution, updated with the rest of the system, and no second
# `curl | sh`. Docker 27 or newer is required for databases and apps; older distribution packages are refused.
install_docker() {
  step "Installing Docker from the distribution's packages"
  if command -v apt-get >/dev/null 2>&1; then
    apt-get update -q >/dev/null || die "apt-get update failed"
    cand=$(apt-cache policy docker.io 2>/dev/null | awk '/Candidate:/ { print $2 }')
    cand_major=${cand#*:}
    cand_major=${cand_major%%.*}
    case $cand_major in
      ''|*[!0-9]*) die "this distribution has no docker.io package; install Docker Engine (https://docs.docker.com/engine/install/) and run this again" ;;
    esac
    [ "$cand_major" -ge 27 ] || die "this distribution's docker.io is $cand (Unrent needs 27 or newer); install Docker Engine from https://docs.docker.com/engine/install/ and run this again"
    DEBIAN_FRONTEND=noninteractive apt-get install -y -q docker.io >/dev/null || die "installing docker.io failed"
  elif command -v dnf >/dev/null 2>&1 && [ -f /etc/fedora-release ]; then
    dnf install -y -q moby-engine >/dev/null || die "installing moby-engine failed"
  else
    die "--install-docker supports Debian/Ubuntu (docker.io) and Fedora (moby-engine); install Docker Engine from https://docs.docker.com/engine/install/ and run this again"
  fi
  systemctl enable --now docker.service >/dev/null 2>&1 || die "Docker was installed but did not start: systemctl status docker"
  info "Docker $(docker version --format '{{.Server.Version}}' 2>/dev/null || true)"
}

# ---------------------------------------------------------------------------------------------------- install

ensure_user() {
  if ! getent passwd "$AGENT_USER" >/dev/null 2>&1; then
    nologin=$(command -v nologin 2>/dev/null || true)
    for s in /usr/sbin/nologin /sbin/nologin; do [ -n "$nologin" ] || [ ! -x "$s" ] || nologin=$s; done
    useradd --system --user-group --no-create-home --home-dir /nonexistent --shell "${nologin:-/bin/false}" \
      --comment "Unrent agent" "$AGENT_USER" || die "could not create the system user $AGENT_USER"
    info "created system user $AGENT_USER"
  fi
}

# /var/lib/unrent: root:unrent 0750. unrent-exec (root) owns everything in it; unrentd may only read the three
# enrollment files (root:unrent 0640). keys/ stays root-only (0700/0600): unrentd asks the helper to sign.
fix_state_perms() {
  mkdir -p "$STATE_DIR" "$ETC_DIR"
  chown root:"$AGENT_USER" "$STATE_DIR"
  chmod 0750 "$STATE_DIR"
  for f in agent.json org_cert.json policy.json; do
    if [ -f "$STATE_DIR/$f" ]; then
      chown root:"$AGENT_USER" "$STATE_DIR/$f"
      chmod 0640 "$STATE_DIR/$f"
    fi
  done
  if [ -d "$STATE_DIR/keys" ]; then
    chown -R root:root "$STATE_DIR/keys"
    chmod 0700 "$STATE_DIR/keys"
    find "$STATE_DIR/keys" -type f -exec chmod 0600 {} +
  fi
}

# install_files: binaries side by side per version, symlinks, units. Sets CHANGED=1 when anything changed.
install_files() {
  prev=$(installed_version)
  dest="$LIB_DIR/$REL_VERSION"
  if [ "$prev" != "$REL_VERSION" ]; then
    stage="$LIB_DIR/.stage.$$"
    rm -rf "$stage"
    mkdir -p "$stage"
    cp -R "$PKG/." "$stage/"
    chown -R root:root "$stage"
    chmod 0755 "$stage" "$stage/bin" "$stage/bin/"*
    rm -rf "$dest"
    mv "$stage" "$dest"
    ln -sfn "$REL_VERSION" "$LIB_DIR/current"
    CHANGED=1
    if [ -n "$prev" ]; then info "upgraded $prev -> $REL_VERSION"; else info "installed $REL_VERSION to $dest"; fi
  fi
  for b in $BINARIES; do ln -sfn "$LIB_DIR/current/bin/$b" "$BIN_DIR/$b"; done
  # Keep the current and the previous version, delete older ones.
  for d in "$LIB_DIR"/*; do
    if [ -L "$d" ] || [ ! -d "$d" ]; then continue; fi
    case ${d##*/} in "$REL_VERSION"|"$prev") ;; *) rm -rf "$d" ;; esac
  done
  got_version=$("$BIN_DIR/unrentd" version 2>/dev/null || true)
  [ "$got_version" = "$REL_VERSION" ] || die "the installed unrentd reports version '$got_version', expected $REL_VERSION"
  mkdir -p "$UNIT_DIR"
  for u in $UNITS; do
    if ! cmp -s "$dest/systemd/$u" "$UNIT_DIR/$u"; then
      install -m 0644 "$dest/systemd/$u" "$UNIT_DIR/$u"
      CHANGED=1
    fi
  done
}

# Development overrides go to /etc/unrent/agent.env, read by both units. Written only when a flag is given, so an
# upgrade without flags keeps them; delete the file to return to production defaults.
write_agent_env() {
  flags=''
  [ -z "$TRUST_CA" ] || flags="$flags --trust-ca=$TRUST_CA"
  [ -z "$RP_ID" ] || flags="$flags --rp-id=$RP_ID"
  [ -z "$ORIGIN" ] || flags="$flags --origin=$ORIGIN"
  [ -n "$flags" ] || return 0
  env_file="$ETC_DIR/agent.env"
  new="# Written by the Unrent installer: development overrides for unrentd and unrent-exec.
UNRENT_FLAGS=\"${flags# }\""
  if [ "$(cat "$env_file" 2>/dev/null)" != "$new" ]; then
    mkdir -p "$ETC_DIR"
    printf '%s\n' "$new" > "$env_file"
    chmod 0644 "$env_file"
    CHANGED=1
    info "development overrides in $env_file"
  fi
}

# Run as the unprivileged user: enrollment is network-facing code. It writes into a private temporary state
# directory; root then moves the files into place with the right owners (keys root-only).
# "$@" is rebuilt with set -- inside; enroll takes no arguments.
# shellcheck disable=SC2120
enroll() {
  step "Enrolling this server"
  # A previous identity (--force) or leftovers of an interrupted enrollment are moved aside, never deleted.
  aside="$STATE_DIR/previous-$(date +%Y%m%d%H%M%S)"
  for f in agent.json org_cert.json policy.json keys; do
    [ -e "$STATE_DIR/$f" ] || continue
    if [ ! -d "$aside" ]; then
      systemctl stop unrentd.service unrent-exec.service 2>/dev/null || true
      mkdir -m 0700 "$aside"
    fi
    mv "$STATE_DIR/$f" "$aside/"
  done
  [ ! -d "$aside" ] || info "moved the previous identity to $aside"
  work=$(mktemp -d "$STATE_DIR/.enroll.XXXXXX")
  chown "$AGENT_USER:$AGENT_USER" "$work"
  chmod 0700 "$work"
  set -- enroll --token-file - --state "$work/state"
  [ -z "$API" ] || set -- "$@" --api "$API"
  [ -z "$TRUST_CA" ] || set -- "$@" --trust-ca "$TRUST_CA"
  uid=$(id -u "$AGENT_USER") gid=$(id -g "$AGENT_USER")
  if [ "$DROP" = setpriv ]; then
    set -- setpriv --reuid="$uid" --regid="$gid" --clear-groups --no-new-privs "$BIN_DIR/unrentd" "$@"
  else
    set -- runuser -u "$AGENT_USER" -- "$BIN_DIR/unrentd" "$@"
  fi
  if ! printf '%s\n' "$TOKEN" | (cd / && "$@") > "$work/agent_id" 2> "$work/err"; then
    tail -n 3 "$work/err" >&2
    rm -rf "$work"
    die "enrollment failed (above). Tokens are single-use and expire after 15 minutes: create a new one in the console"
  fi
  for f in agent.json org_cert.json policy.json keys; do
    [ -e "$work/state/$f" ] || { rm -rf "$work"; die "enrollment did not write $f"; }
    mv "$work/state/$f" "$STATE_DIR/$f"
  done
  rm -rf "$work"
  CHANGED=1
}

# connected_since <invocation id>: unrentd's last connection event in this run is "connected".
connected_since() {
  journalctl --no-pager -q -o cat "_SYSTEMD_INVOCATION_ID=$1" 2>/dev/null \
    | grep -E 'msg=connected|msg="connection ended' | tail -n 1 | grep -q 'msg=connected'
}

start_and_wait() {
  step "Starting the agent"
  systemctl daemon-reload
  systemctl enable unrent-exec.service unrentd.service >/dev/null 2>&1
  if [ "$CHANGED" = 1 ] || ! systemctl is-active --quiet unrentd.service; then
    systemctl restart unrent-exec.service unrentd.service
  fi
  invocation=$(systemctl show -p InvocationID --value unrentd.service 2>/dev/null || true)
  agent_id=$(agent_field "$STATE_DIR/agent.json" agent_id)
  i=0
  while [ "$i" -lt "$CONNECT_TIMEOUT" ]; do
    if [ -n "$invocation" ] && connected_since "$invocation"; then
      step "Connected. Open https://app.unrent.dev to see this server."
      info "agent $agent_id, version $REL_VERSION"
      return 0
    fi
    sleep 1
    i=$((i + 1))
  done
  journalctl --no-pager -q -n 15 -u unrent-exec.service -u unrentd.service >&2 2>/dev/null || true
  die "the agent ($agent_id) is installed but has not connected within ${CONNECT_TIMEOUT}s. Check outbound HTTPS to the control plane, then: journalctl -u unrentd -u unrent-exec"
}

# ---------------------------------------------------------------------------------------------------- uninstall

uninstall() {
  step "Removing the Unrent agent"
  [ "$(id -u)" = 0 ] || die "run this as root: sudo sh install.sh --uninstall"
  if command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]; then
    systemctl disable --now unrentd.service unrent-exec.service >/dev/null 2>&1 || true
  fi
  for u in $UNITS; do rm -f "$UNIT_DIR/$u"; done
  if command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]; then
    systemctl daemon-reload
    systemctl reset-failed unrentd.service unrent-exec.service >/dev/null 2>&1 || true
  fi
  for b in $BINARIES; do
    case $(readlink "$BIN_DIR/$b" 2>/dev/null || true) in "$LIB_DIR"/*) rm -f "$BIN_DIR/$b" ;; esac
  done
  rm -rf "$LIB_DIR"
  info "stopped the services and removed the binaries and units"
  if [ "$PURGE" = 1 ]; then
    rm -rf "$STATE_DIR" "$ETC_DIR"
    if getent passwd "$AGENT_USER" >/dev/null 2>&1; then userdel "$AGENT_USER" 2>/dev/null || true; fi
    if getent group "$AGENT_USER" >/dev/null 2>&1; then groupdel "$AGENT_USER" 2>/dev/null || true; fi
    info "deleted $STATE_DIR, $ETC_DIR and the $AGENT_USER user"
  else
    info "kept $STATE_DIR (identity, app and database state) and $ETC_DIR; --purge deletes them"
  fi
  info "containers, databases and their data were not touched. Remove this server in https://app.unrent.dev"
}

# ---------------------------------------------------------------------------------------------------- main

main() {
  parse_args "$@"
  if [ "$UNINSTALL" = 1 ]; then
    uninstall
    return 0
  fi
  TMP=$(mktemp -d "${TMPDIR:-/tmp}/unrent-install.XXXXXX")
  trap 'rm -rf "$TMP"' EXIT
  trap 'exit 130' INT TERM
  CHANGED=0

  if [ -n "$DOWNLOAD_ONLY" ]; then
    detect_arch
    detect_tools
    download_release "$TMP"
    mkdir -p "$DOWNLOAD_ONLY"
    extract_release "$TARBALL" "$DOWNLOAD_ONLY"
    cp "$TARBALL" "$TMP/SHA256SUMS" "$DOWNLOAD_ONLY/"
    step "Verified and unpacked to $PKG (nothing installed)"
    return 0
  fi

  preflight
  download_release "$TMP"
  extract_release "$TARBALL" "$TMP"
  step "Installing"
  ensure_user
  fix_state_perms
  install_files
  write_agent_env
  # shellcheck disable=SC2119
  [ -z "$TOKEN" ] || enroll
  fix_state_perms
  start_and_wait
}

[ "${UNRENT_INSTALL_SOURCE_ONLY:-}" = 1 ] || main "$@"
