#!/usr/bin/env bash
# Meshanics agent installer — one step from factory-fresh to fleet.
#
#   curl -fsSL https://meshanics.com/install.sh | sudo bash -s -- --token <TOKEN>
#
# Get a token from the console: Devices → Add device. Prefer to read before
# you run? Download this file, inspect it, then execute it the same way.
#
# What it does:
#   1. Downloads the agent binary for this CPU architecture.
#   2. Generates a private key ON THIS DEVICE (it never leaves the device)
#      and sends a certificate signing request to the control plane.
#   3. Exchanges your enrollment token for a signed device certificate, the
#      fleet CA and the TUF trusted root (the root of update trust).
#   4. Installs and starts the agent as a hardened systemd service.
#
# Options:
#   --token TOKEN   enrollment token (required)
#   --name NAME     device name (default: this host's lowercased short name)
#   --panel URL     console/API base   (default: https://panel.meshanics.com)
#   --base URL      download base      (default: https://meshanics.com)
set -euo pipefail

PANEL="https://panel.meshanics.com"
BASE="https://meshanics.com"
TOKEN=""
NAME="$(hostname -s | tr '[:upper:]' '[:lower:]')"

while [[ $# -gt 0 ]]; do
  case "$1" in
    --token) TOKEN="${2:-}"; shift 2 ;;
    --name)  NAME="${2:-}";  shift 2 ;;
    --panel) PANEL="${2:-}"; shift 2 ;;
    --base)  BASE="${2:-}";  shift 2 ;;
    *) echo "unknown option: $1" >&2; exit 2 ;;
  esac
done

[[ -n "$TOKEN" ]] || { echo "missing --token (create one in the console: Devices → Add device)" >&2; exit 2; }
[[ $EUID -eq 0 ]] || { echo "run as root: curl -fsSL .../install.sh | sudo bash -s -- --token ..." >&2; exit 1; }
for cmd in curl tar openssl systemctl; do
  command -v "$cmd" >/dev/null || { echo "$cmd is required" >&2; exit 1; }
done

case "$(uname -m)" in
  aarch64|arm64) ARCH=arm64 ;;
  x86_64|amd64)  ARCH=amd64 ;;
  *) echo "unsupported architecture: $(uname -m)" >&2; exit 1 ;;
esac

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

echo "→ downloading agent (linux/$ARCH)"
curl -fsSL -o "$TMP/meshanics-agent" "$BASE/dl/meshanics-agent-linux-$ARCH"

echo "→ generating device key + CSR for '$NAME' (key stays on this device)"
openssl ecparam -name prime256v1 -genkey -noout -out "$TMP/device.key"
openssl req -new -key "$TMP/device.key" -subj "/CN=$NAME" -out "$TMP/device.csr"

echo "→ enrolling with the control plane"
HTTP_CODE=$(curl -sS -o "$TMP/enroll.out" -w '%{http_code}' \
  --data-urlencode "token=$TOKEN" \
  --data-urlencode "name=$NAME" \
  --data-urlencode "csr@$TMP/device.csr" \
  "$PANEL/api/v1/enroll")
if [[ "$HTTP_CODE" != 200 ]]; then
  echo "enrollment failed (HTTP $HTTP_CODE):" >&2
  cat "$TMP/enroll.out" >&2; echo >&2
  exit 1
fi

install -d -m 0755 /etc/meshanics /var/lib/meshanics
tar -xf "$TMP/enroll.out" -C /etc/meshanics   # device.crt ca.crt root.json agent.env
install -m 0600 "$TMP/device.key" /etc/meshanics/device.key
chmod 0644 /etc/meshanics/device.crt /etc/meshanics/ca.crt /etc/meshanics/root.json
chmod 0600 /etc/meshanics/agent.env
install -m 0755 "$TMP/meshanics-agent" /usr/local/bin/meshanics-agent

# shellcheck disable=SC1091
source /etc/meshanics/agent.env   # MESHANICS_HOST

# Let the container runtime pull application images through the Meshanics
# control plane using this device's identity. Standard certs.d layout,
# honored by both podman and docker.
for certsdir in /etc/containers/certs.d /etc/docker/certs.d; do
  install -d -m 0755 "$certsdir/$MESHANICS_HOST:9444"
  install -m 0644 /etc/meshanics/ca.crt "$certsdir/$MESHANICS_HOST:9444/ca.crt"
  install -m 0644 /etc/meshanics/device.crt "$certsdir/$MESHANICS_HOST:9444/client.cert"
  install -m 0600 /etc/meshanics/device.key "$certsdir/$MESHANICS_HOST:9444/client.key"
done

cat > /etc/systemd/system/meshanics-agent.service <<'UNIT'
[Unit]
Description=Meshanics device agent (meshanics-agent)
Documentation=https://meshanics.com/docs/install
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/meshanics-agent \
  -server ${MESHANICS_HOST}:9443 \
  -artifact-server https://${MESHANICS_HOST}:9444 \
  -cert /etc/meshanics/device.crt \
  -key /etc/meshanics/device.key \
  -ca /etc/meshanics/ca.crt \
  -tuf-root /etc/meshanics/root.json \
  -data-dir /var/lib/meshanics
EnvironmentFile=/etc/meshanics/agent.env
Restart=always
RestartSec=5
# Hardening: the agent only needs its state dir, its config dir (which
# it wipes on decommission) and the runtime trust dirs.
NoNewPrivileges=true
ProtectSystem=full
ReadWritePaths=/var/lib/meshanics /etc/meshanics /etc/containers/certs.d /etc/docker/certs.d
# Read our own journal for operator-requested log pull.
SupplementaryGroups=systemd-journal
# Exit 42 = decommissioned: stay down instead of crash-looping.
RestartPreventExitStatus=42
ProtectHome=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target
UNIT

systemctl daemon-reload
systemctl enable meshanics-agent
# restart (not just start): re-running the installer on a device that is
# already running must pick up the new binary and freshly enrolled cert.
systemctl restart meshanics-agent

sleep 2
systemctl --no-pager status meshanics-agent | head -8 || true
echo
echo "✓ '$NAME' enrolled. It should appear in the console within seconds."
