#!/usr/bin/env bash
#
# install.sh — one-command installer for the Restorable agent.
#
# Canonical invocation:
#   curl -fsSL https://get.restorable.app | sh
#
# Behavior (in order):
#   1. Re-exec under sudo if not root.
#   2. Detect linux-amd64 / linux-arm64 (fail on anything else).
#   3. Download the signed binary and its minisign signature from
#      https://get.restorable.app/${VERSION}/restorable-agent-${ARCH}.
#   4. Verify the signature. Abort on failure.
#   5. Create the `restorable` system user (idempotent).
#   6. Add `restorable` to the `docker` group (idempotent).
#   7. Install the binary to /usr/local/bin/restorable-agent (0755).
#   8. Create /etc/restorable/ (root:restorable 0750) and
#      /var/lib/restorable/{,keys,blobs,receipts,events}
#      (restorable:restorable 0700).
#   9. Install the systemd unit to
#      /etc/systemd/system/restorable-agent.service.
#  10. systemctl daemon-reload. Do NOT enable or start the service —
#      the customer runs `restorable-agent setup` next, then
#      `systemctl enable --now restorable-agent`.
#
# The installer does not prompt for a claim code. That's a deliberate
# UX choice: setup is the claim-code step, installation is not.
#
# Testability: all target paths are overridable via env vars, and the
# download step can be bypassed with RESTORABLE_LOCAL_BINARY=<path>.
# See deploy/install/install_test.sh for the smoke-test harness.

set -euo pipefail

# ── Defaults (override via env for testing / custom layouts) ─────────
RESTORABLE_VERSION="${RESTORABLE_VERSION:-v0.1.0}"
RESTORABLE_BASE_URL="${RESTORABLE_BASE_URL:-https://get.restorable.app}"
RESTORABLE_PUBKEY_URL="${RESTORABLE_PUBKEY_URL:-${RESTORABLE_BASE_URL}/pub/restorable-release.pub}"

# Prefix + per-directory overrides. RESTORABLE_PREFIX prepends a root
# to every path (useful for sandbox tests / staged installs). The
# individual DIR vars can also be set directly.
RESTORABLE_PREFIX="${RESTORABLE_PREFIX:-}"
RESTORABLE_BINDIR="${RESTORABLE_BINDIR:-${RESTORABLE_PREFIX}/usr/local/bin}"
RESTORABLE_SYSCONFDIR="${RESTORABLE_SYSCONFDIR:-${RESTORABLE_PREFIX}/etc/restorable}"
RESTORABLE_STATEDIR="${RESTORABLE_STATEDIR:-${RESTORABLE_PREFIX}/var/lib/restorable}"
RESTORABLE_SYSTEMD_UNITDIR="${RESTORABLE_SYSTEMD_UNITDIR:-${RESTORABLE_PREFIX}/etc/systemd/system}"

RESTORABLE_USER="${RESTORABLE_USER:-restorable}"
RESTORABLE_GROUP="${RESTORABLE_GROUP:-restorable}"
RESTORABLE_DOCKER_GROUP="${RESTORABLE_DOCKER_GROUP:-docker}"

# Test hooks.
RESTORABLE_LOCAL_BINARY="${RESTORABLE_LOCAL_BINARY:-}"   # skip download, use this binary
RESTORABLE_SKIP_SUDO="${RESTORABLE_SKIP_SUDO:-0}"         # don't re-exec under sudo
RESTORABLE_SKIP_VERIFY="${RESTORABLE_SKIP_VERIFY:-0}"     # skip minisign verify (tests only)
RESTORABLE_SKIP_CHOWN="${RESTORABLE_SKIP_CHOWN:-0}"       # skip ownership changes (tests only)
RESTORABLE_SKIP_PREREQS="${RESTORABLE_SKIP_PREREQS:-0}"   # skip dependency install (tests / customers managing their own deps)

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

# ── Dependency bootstrap. ────────────────────────────────────────────
#
# The agent needs Docker (for the ephemeral restore-test scratch
# container) and minisign (for verifying the signed binary). By
# default the installer installs both if missing. Opt out with
# RESTORABLE_SKIP_PREREQS=1 if you manage dependencies yourself or
# are running in a sandbox (the smoke test does this).
#
# Docker comes from https://get.docker.com — the same convenience
# script Docker themselves recommend. It handles distro detection
# and repo setup. If you prefer a distro package (docker.io on
# Debian/Ubuntu, moby-engine on Fedora, podman-docker on Rocky /
# Alma), install it yourself before running this and set
# RESTORABLE_SKIP_PREREQS=1.
#
# Minisign comes from your distro's package manager. Supported:
# apt (Debian/Ubuntu/derivatives), dnf (Fedora/Rocky/Alma — needs
# EPEL on Rocky/Alma).

detect_pkg_manager() {
  if command -v apt-get >/dev/null 2>&1; then
    echo "apt"
  elif command -v dnf >/dev/null 2>&1; then
    echo "dnf"
  else
    echo "unknown"
  fi
}

install_docker_if_missing() {
  if command -v docker >/dev/null 2>&1; then
    log "docker already installed"
  else
    log "installing docker via https://get.docker.com (takes a minute)"
    command -v curl >/dev/null 2>&1 || die "curl not installed"
    curl -fsSL https://get.docker.com | sh
    command -v docker >/dev/null 2>&1 || die "docker install failed"
  fi
  log "ensuring docker daemon is running"
  systemctl enable --now docker
}

install_minisign_if_missing() {
  local pm="$1"
  if command -v minisign >/dev/null 2>&1; then
    log "minisign already installed"
    return 0
  fi
  case "${pm}" in
    apt)
      log "installing minisign via apt"
      apt-get update -qq
      apt-get install -y -qq minisign
      ;;
    dnf)
      log "installing minisign via dnf"
      if ! dnf install -y -q minisign 2>/dev/null; then
        log "minisign not in default repos; trying EPEL"
        dnf install -y -q epel-release
        dnf install -y -q minisign
      fi
      ;;
    *)
      die "cannot auto-install minisign on this distro (unknown package manager). Install manually and re-run, or set RESTORABLE_SKIP_PREREQS=1"
      ;;
  esac
  command -v minisign >/dev/null 2>&1 || die "minisign install failed"
}

install_prerequisites() {
  if [[ "${RESTORABLE_SKIP_PREREQS}" == "1" ]]; then
    log "prerequisites: skipped (RESTORABLE_SKIP_PREREQS=1)"
    return 0
  fi

  local pm
  pm="$(detect_pkg_manager)"
  log "detected package manager: ${pm}"

  install_docker_if_missing
  install_minisign_if_missing "${pm}"
}

# ── Elevate to root. ─────────────────────────────────────────────────
if [[ "${RESTORABLE_SKIP_SUDO}" != "1" && "$(id -u)" -ne 0 ]]; then
  if command -v sudo >/dev/null 2>&1; then
    log "re-executing under sudo"
    exec sudo -E bash "$0" "$@"
  fi
  die "must run as root (sudo not available)"
fi

# ── Detect architecture. ─────────────────────────────────────────────
UNAME_S="$(uname -s)"
UNAME_M="$(uname -m)"
case "${UNAME_S}" in
  Linux) OS="linux" ;;
  *) die "unsupported OS: ${UNAME_S} (only Linux)" ;;
esac
case "${UNAME_M}" in
  x86_64|amd64) ARCH_SUFFIX="linux-amd64" ;;
  aarch64|arm64) ARCH_SUFFIX="linux-arm64" ;;
  *) die "unsupported architecture: ${UNAME_M}" ;;
esac
log "detected ${OS}/${ARCH_SUFFIX}"

# ── Install prerequisites (docker + minisign) ───────────────────────
install_prerequisites

# ── Staging area. ────────────────────────────────────────────────────
WORKDIR="$(mktemp -d)"
trap 'rm -rf "${WORKDIR}"' EXIT

# ── Download + verify. ───────────────────────────────────────────────
STAGED_BINARY="${WORKDIR}/restorable-agent"

if [[ -n "${RESTORABLE_LOCAL_BINARY}" ]]; then
  log "using local binary: ${RESTORABLE_LOCAL_BINARY}"
  cp "${RESTORABLE_LOCAL_BINARY}" "${STAGED_BINARY}"
  chmod +x "${STAGED_BINARY}"
else
  BIN_URL="${RESTORABLE_BASE_URL}/${RESTORABLE_VERSION}/restorable-agent-${ARCH_SUFFIX}"
  SIG_URL="${BIN_URL}.minisig"
  log "downloading ${BIN_URL}"
  curl -fsSL "${BIN_URL}" -o "${STAGED_BINARY}"
  log "downloading ${SIG_URL}"
  curl -fsSL "${SIG_URL}" -o "${STAGED_BINARY}.minisig"
  log "downloading ${RESTORABLE_PUBKEY_URL}"
  curl -fsSL "${RESTORABLE_PUBKEY_URL}" -o "${WORKDIR}/restorable-release.pub"

  if [[ "${RESTORABLE_SKIP_VERIFY}" != "1" ]]; then
    command -v minisign >/dev/null 2>&1 || die "minisign not installed (https://jedisct1.github.io/minisign/)"
    log "verifying minisign signature"
    # TODO(simon): wire the real release pubkey. Key ceremony deferred.
    minisign -Vm "${STAGED_BINARY}" -p "${WORKDIR}/restorable-release.pub" \
      || die "minisign signature verification FAILED — aborting"
  else
    log "signature verification SKIPPED (RESTORABLE_SKIP_VERIFY=1)"
  fi
  chmod +x "${STAGED_BINARY}"
fi

# ── User + group (idempotent). ───────────────────────────────────────
if ! id -u "${RESTORABLE_USER}" >/dev/null 2>&1; then
  log "creating system user ${RESTORABLE_USER}"
  useradd --system \
    --home-dir "${RESTORABLE_STATEDIR}" \
    --shell /usr/sbin/nologin \
    "${RESTORABLE_USER}"
else
  log "system user ${RESTORABLE_USER} already exists (reusing)"
fi

if getent group "${RESTORABLE_DOCKER_GROUP}" >/dev/null 2>&1; then
  log "adding ${RESTORABLE_USER} to ${RESTORABLE_DOCKER_GROUP} group"
  usermod -aG "${RESTORABLE_DOCKER_GROUP}" "${RESTORABLE_USER}"
else
  # install_prerequisites() should have installed Docker by now, so
  # the group exists. If it does not, Docker install was skipped
  # (RESTORABLE_SKIP_PREREQS=1) and something is off with the host.
  log "${RESTORABLE_DOCKER_GROUP} group missing — skipping group membership"
  log "  (install Docker and re-run, or run 'usermod -aG docker ${RESTORABLE_USER}' manually)"
fi

# ── Install binary. ──────────────────────────────────────────────────
log "installing binary to ${RESTORABLE_BINDIR}/restorable-agent"
mkdir -p "${RESTORABLE_BINDIR}"
if [[ "${RESTORABLE_SKIP_CHOWN}" == "1" ]]; then
  install -m 0755 "${STAGED_BINARY}" "${RESTORABLE_BINDIR}/restorable-agent"
else
  install -o root -g root -m 0755 "${STAGED_BINARY}" "${RESTORABLE_BINDIR}/restorable-agent"
fi

# ── Create directories. ──────────────────────────────────────────────
log "creating ${RESTORABLE_SYSCONFDIR}"
mkdir -p "${RESTORABLE_SYSCONFDIR}"
chmod 0750 "${RESTORABLE_SYSCONFDIR}"
if [[ "${RESTORABLE_SKIP_CHOWN}" != "1" ]]; then
  chown "root:${RESTORABLE_GROUP}" "${RESTORABLE_SYSCONFDIR}"
fi

log "creating ${RESTORABLE_STATEDIR} (and subdirs)"
for sub in "" keys blobs receipts events; do
  d="${RESTORABLE_STATEDIR}${sub:+/${sub}}"
  mkdir -p "${d}"
  chmod 0700 "${d}"
  if [[ "${RESTORABLE_SKIP_CHOWN}" != "1" ]]; then
    chown "${RESTORABLE_USER}:${RESTORABLE_GROUP}" "${d}"
  fi
done

# ── Install systemd unit. ────────────────────────────────────────────
UNIT_DEST="${RESTORABLE_SYSTEMD_UNITDIR}/restorable-agent.service"
log "writing systemd unit to ${UNIT_DEST}"
mkdir -p "${RESTORABLE_SYSTEMD_UNITDIR}"
cat >"${UNIT_DEST}" <<'UNIT_EOF'
[Unit]
Description=Restorable agent
After=network-online.target docker.service
Requires=docker.service
Wants=network-online.target

[Service]
Type=simple
User=restorable
Group=restorable
SupplementaryGroups=docker

EnvironmentFile=/etc/restorable/agent-env
ExecStart=/usr/local/bin/restorable-agent serve \
  --config /etc/restorable/agent.yaml \
  --state-dir /var/lib/restorable

Restart=on-failure
RestartSec=10s

# Filesystem boundary
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/restorable
PrivateTmp=true

# Privilege boundary
NoNewPrivileges=true
CapabilityBoundingSet=
AmbientCapabilities=

# Kernel boundary
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictNamespaces=true
LockPersonality=true
MemoryDenyWriteExecute=true
SystemCallArchitectures=native

# Network boundary
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6

[Install]
WantedBy=multi-user.target
UNIT_EOF
chmod 0644 "${UNIT_DEST}"

log "systemctl daemon-reload"
systemctl daemon-reload

# ── Final hint. ──────────────────────────────────────────────────────
cat <<EOF

Installed.

Next:
  1. Create an agent in the dashboard and copy the 8-character claim code.
  2. Run setup (as the restorable user):
       sudo -u ${RESTORABLE_USER} restorable-agent setup \\
         --orchestrator https://app.restorable.app \\
         --code <CLAIM-CODE>
  3. Start the service:
       sudo systemctl enable --now restorable-agent

Docs: https://restorable.app/docs/quickstart
EOF
