#!/usr/bin/env bash
#
# caliptra agent install script
#
# Installs the caliptra host agent on a Linux server. Designed to be run via:
#
#   curl -fsSL https://get.caliptra.co/install.sh | sudo bash -s -- \
#     --url https://api.caliptra.co/v1/webhooks/<uuid> \
#     --secret <hmac-secret> \
#     --enroll <one-time-token> \
#     --label provider=hetzner \
#     --label env=prod
#
# Idempotent: re-running updates the binary + config without re-enrolling
# (the agent consumes the enroll token on first boot only).
set -euo pipefail

# ---------------------------------------------------------------------------
# Defaults
# ---------------------------------------------------------------------------
REPO_BASE="https://get.caliptra.co"
VERSION=""
URL=""
SECRET=""
ENROLL=""
PREFIX="/usr/bin"
SKIP_VERIFY=false
SKIP_RULES=false
LABELS=()

# ---------------------------------------------------------------------------
# Argument parser
# ---------------------------------------------------------------------------
usage() {
	cat <<'EOF'
Usage: install.sh --url URL --secret SECRET --enroll TOKEN [options]

Required:
  --url URL           Full webhook URL (https://api.caliptra.co/v1/webhooks/<uuid>)
  --secret SECRET     HMAC secret for telemetry authentication
  --enroll TOKEN      One-time enrollment token (for the command channel)

Optional:
  --label KEY=VALUE   Repeatable host label (provider, env, role, team, ...)
  --version VERSION   Pin to a specific release (default: latest)
  --prefix PATH       Binary install path (default: /usr/bin)
  --repo URL          Alternate install box URL (default: https://get.caliptra.co)
  --skip-verify       Skip cosign + sha256 verification (DEV ONLY — DO NOT USE IN PROD)
  --skip-rules        Skip downloading the curated rule pack
  -h, --help          Show this help
EOF
}

while [[ $# -gt 0 ]]; do
	case "$1" in
	--url)        URL="$2"; shift 2 ;;
	--secret)     SECRET="$2"; shift 2 ;;
	--enroll)     ENROLL="$2"; shift 2 ;;
	--label)      LABELS+=("$2"); shift 2 ;;
	--version)    VERSION="$2"; shift 2 ;;
	--prefix)     PREFIX="$2"; shift 2 ;;
	--repo)       REPO_BASE="$2"; shift 2 ;;
	--skip-verify) SKIP_VERIFY=true; shift ;;
	--skip-rules) SKIP_RULES=false; shift ;;
	-h|--help)    usage; exit 0 ;;
	*)            echo "Error: unknown flag: $1" >&2; usage >&2; exit 1 ;;
	esac
done

# ---------------------------------------------------------------------------
# Pre-flight checks
# ---------------------------------------------------------------------------
[[ "$(id -u)" -ne 0 ]] && { echo "Error: must run as root (prefix with sudo)" >&2; exit 1; }
[[ -z "$URL" ]]   && { echo "Error: --url is required" >&2; exit 1; }
[[ -z "$SECRET" ]] && { echo "Error: --secret is required" >&2; exit 1; }
[[ -z "$ENROLL" ]] && { echo "Error: --enroll is required" >&2; exit 1; }

# Linux only — agent does not yet support macOS/Windows.
if [[ "$(uname -s)" != "Linux" ]]; then
	echo "Error: caliptra agent runs on Linux only (got $(uname -s))" >&2
	exit 1
fi

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

# Derive the API base from the webhook URL.
API_BASE=$(printf '%s\n' "$URL" | sed -E 's|^(https?://[^/]+).*|\1|')
[[ -z "$API_BASE" ]] && { echo "Error: could not derive API base from URL: $URL" >&2; exit 1; }

# Resolve latest version if not pinned.
if [[ -z "$VERSION" ]]; then
	VERSION=$(curl -fsSL "$REPO_BASE/metadata.json" 2>/dev/null \
		| sed -E -n 's/.*"version"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p' \
		| head -n1)
	[[ -z "$VERSION" ]] && { echo "Error: could not determine latest version from $REPO_BASE/metadata.json" >&2; exit 1; }
fi

# ---------------------------------------------------------------------------
# Banner
# ---------------------------------------------------------------------------
cat <<EOF

╭───────────────────────────────────────────────╮
│  Caliptra host agent                          │
│  Version:  $VERSION                           │
│  Arch:     linux/$ARCH                        │
│  Webhook:  $URL                               │
│  API base: $API_BASE                          │
│  Prefix:   $PREFIX                            │
╰───────────────────────────────────────────────╯

EOF

if [[ "$SKIP_VERIFY" == "true" ]]; then
	echo "!! WARNING: signature verification skipped (--skip-verify)" >&2
	echo "!! This MUST NOT be used in production." >&2
fi

# ---------------------------------------------------------------------------
# Stage files in a temp dir; cleanup on exit
# ---------------------------------------------------------------------------
WORKDIR=$(mktemp -d)
trap 'rm -rf "$WORKDIR"' EXIT

cd "$WORKDIR"

# ---------------------------------------------------------------------------
# Download binary + checksums + signature
# ---------------------------------------------------------------------------
echo "→ Downloading caliptra_${VERSION}_linux_${ARCH}..."
curl -fsSL \
	"$REPO_BASE/releases/$VERSION/caliptra_${VERSION}_linux_${ARCH}" \
	-o caliptra

echo "→ Downloading checksums + signature..."
curl -fsSL "$REPO_BASE/releases/$VERSION/SHA256SUMS"     -o SHA256SUMS     || true
curl -fsSL "$REPO_BASE/releases/$VERSION/SHA256SUMS.sig" -o SHA256SUMS.sig || true
curl -fsSL "$REPO_BASE/releases/$VERSION/SHA256SUMS.pem" -o SHA256SUMS.pem || true

# ---------------------------------------------------------------------------
# Verify
# ---------------------------------------------------------------------------
if [[ "$SKIP_VERIFY" != "true" ]]; then
	if [[ ! -s SHA256SUMS ]]; then
		echo "Error: SHA256SUMS missing or empty — refusing to install" >&2
		exit 1
	fi

	# Cosign signature verification (installs cosign on the fly if missing).
	if [[ -s SHA256SUMS.sig && -s SHA256SUMS.pem ]]; then
		COSIGN_BIN=""
		if command -v cosign >/dev/null 2>&1; then
			COSIGN_BIN="cosign"
		else
			echo "→ Installing cosign for signature verification..."
			curl -fsSL \
				"https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-${ARCH}" \
				-o cosign
			chmod +x cosign
			COSIGN_BIN="$WORKDIR/cosign"
		fi

		echo "→ Verifying cosign signature on SHA256SUMS..."
		if ! "$COSIGN_BIN" verify-blob \
			--certificate SHA256SUMS.pem \
			--signature SHA256SUMS.sig \
			--certificate-identity-regexp 'https://github.com/caliptra/.*' \
			--certificate-oidc-issuer https://token.actions.githubusercontent.com \
			SHA256SUMS >/dev/null 2>&1; then
			echo "Error: cosign signature verification failed — refusing to install" >&2
			exit 1
		fi
	else
		echo "Warning: cosign signature files missing — falling back to bare SHA256" >&2
	fi

	# Verify the binary hash is the one in SHA256SUMS.
	EXPECTED=$(grep -E "caliptra_${VERSION}_linux_${ARCH}\$" SHA256SUMS | awk '{print $1}')
	if [[ -z "$EXPECTED" ]]; then
		echo "Error: binary not listed in SHA256SUMS — refusing to install" >&2
		exit 1
	fi
	ACTUAL=$(sha256sum caliptra | awk '{print $1}')
	if [[ "$EXPECTED" != "$ACTUAL" ]]; then
		echo "Error: sha256 mismatch (want=$EXPECTED got=$ACTUAL)" >&2
		exit 1
	fi
	echo "✓ Verified sha256=$ACTUAL"
fi

# ---------------------------------------------------------------------------
# Install binary
# ---------------------------------------------------------------------------
echo "→ Installing binary to $PREFIX/caliptra..."
install -m 0755 caliptra "$PREFIX/caliptra"

# ---------------------------------------------------------------------------
# Create runtime directories
# ---------------------------------------------------------------------------
echo "→ Creating /etc/caliptra, /var/lib/caliptra, /var/log/caliptra..."
mkdir -p \
	/etc/caliptra/rules.d \
	/var/lib/caliptra/state \
	/var/lib/caliptra/quarantine \
	/var/lib/caliptra/spill \
	/var/log/caliptra
chmod 0700 /var/lib/caliptra/state /var/lib/caliptra/quarantine

# ---------------------------------------------------------------------------
# Render agent.yaml
# ---------------------------------------------------------------------------
echo "→ Writing /etc/caliptra/agent.yaml..."
LABELS_BLOCK=""
for label in "${LABELS[@]:-}"; do
	[[ -z "$label" ]] && continue
	K="${label%%=*}"
	V="${label#*=}"
	LABELS_BLOCK+="    ${K}: \"${V}\""$'\n'
done
[[ -z "$LABELS_BLOCK" ]] && LABELS_BLOCK="    # (no labels supplied)\n"

cat > /etc/caliptra/agent.yaml <<EOF
# Caliptra agent configuration — generated by install.sh $(date -u +%Y-%m-%dT%H:%M:%SZ)
# Do not edit by hand unless you know what you're doing; re-running install.sh
# will overwrite this file.

api:
  base_url: ${API_BASE}
  command_wait_sec: 25
  timeout_sec: 10

webhook:
  url: ${URL}
  secret: ${SECRET}

host:
  labels:
$(printf '%b' "$LABELS_BLOCK" | sed 's/^/  /')

profile: standard
pipeline:
  ring_max_events: 4096
  batch_max_events: 50
  batch_flush_sec: 5
  dedup_window_sec: 60

commands:
  enabled: true
  deny_process_names:
    - systemd
    - sshd
    - caliptra

limits:
  gomemlimit: 400MiB
EOF
chmod 0600 /etc/caliptra/agent.yaml

# ---------------------------------------------------------------------------
# Write enroll token (one-time, consumed on first boot)
# ---------------------------------------------------------------------------
echo "→ Writing /etc/caliptra/enroll.token..."
printf '%s' "$ENROLL" > /etc/caliptra/enroll.token
chmod 0600 /etc/caliptra/enroll.token

# ---------------------------------------------------------------------------
# Download curated rules
# ---------------------------------------------------------------------------
if [[ "$SKIP_RULES" != "true" ]]; then
	echo "→ Installing curated rules to /etc/caliptra/rules.d/..."
	INDEX_URL="$REPO_BASE/rules.d/${VERSION}/index.txt"
	if INDEX=$(curl -fsSL "$INDEX_URL" 2>/dev/null); then
		# shellcheck disable=SC2206
		RULE_FILES=($INDEX)
		for rule in "${RULE_FILES[@]:-}"; do
			[[ -z "$rule" ]] && continue
			curl -fsSL "$REPO_BASE/rules.d/${VERSION}/${rule}" \
				-o "/etc/caliptra/rules.d/${rule}" || true
		done
		echo "  installed ${#RULE_FILES[@]} rules"
	else
		echo "  no rule index found at $INDEX_URL (skipping)"
	fi
fi

# ---------------------------------------------------------------------------
# Write systemd unit (inline so --prefix is respected)
# ---------------------------------------------------------------------------
echo "→ Installing systemd unit..."
cat > /etc/systemd/system/caliptra.service <<EOF
[Unit]
Description=Caliptra host agent
Documentation=https://docs.caliptra.co/agent
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=${PREFIX}/caliptra -config /etc/caliptra/agent.yaml -cred /var/lib/caliptra/agent.cred
Restart=always
RestartSec=5

# Resource limits — protect the host from a runaway agent.
MemoryMax=512M
CPUQuota=50%

# Hardening — minimal privileges.
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/var/lib/caliptra /var/log/caliptra /etc/caliptra
ProtectHome=true
PrivateTmp=true
CapabilityBoundingSet=CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
AmbientCapabilities=CAP_SYS_PTRACE CAP_DAC_READ_SEARCH

[Install]
WantedBy=multi-user.target
EOF

# ---------------------------------------------------------------------------
# Enable + start
# ---------------------------------------------------------------------------
echo "→ Enabling systemd service..."
systemctl daemon-reload
systemctl enable caliptra >/dev/null 2>&1 || true

echo "→ Starting caliptra..."
systemctl restart caliptra

# ---------------------------------------------------------------------------
# Wait for first heartbeat / enrollment
# ---------------------------------------------------------------------------
echo "→ Waiting for enrollment + first heartbeat (up to 30s)..."
for i in $(seq 1 15); do
	sleep 2
	if [[ -f /var/lib/caliptra/agent.cred ]] && systemctl is-active --quiet caliptra; then
		# Extract agent_id from the credential file.
		AGENT_ID=$(grep -E '"agent_id"' /var/lib/caliptra/agent.cred 2>/dev/null \
			| sed -E 's/.*"agent_id"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/' \
			| head -n1)
		echo ""
		echo "✓ Caliptra agent installed and running"
		[[ -n "$AGENT_ID" ]] && echo "  agent_id: $AGENT_ID"
		echo ""
		echo "Telemetry target: $URL"
		echo "Command channel:  $API_BASE/v1/agent/commands"
		echo ""
		echo "Status:  systemctl status caliptra"
		echo "Logs:    journalctl -u caliptra -f"
		echo "Config:  /etc/caliptra/agent.yaml"
		exit 0
	fi
done

# Heartbeat didn't land in time — surface the status without failing.
echo ""
echo "⚠  Agent installed but did not enrol within 30 seconds."
echo "   Check the logs: journalctl -u caliptra -n 50"
echo "   Common causes:"
echo "     - Network blocking outbound HTTPS to $API_BASE"
echo "     - Enrollment token already consumed (mint a new one)"
echo "     - Wrong --url / --secret / --enroll values"
exit 0
