#!/usr/bin/env bash # Gretl Agent installer # Usage: GR_TOKEN=xxx bash <(curl -fsSL https://gretl.dev/install-agent.sh) set -e AGENT_DIR="/opt/gretl" AGENT_BIN="$AGENT_DIR/agent.js" EBPF_BIN="$AGENT_DIR/gretl-ebpf" SERVICE_FILE="/etc/systemd/system/gretl-agent.service" EBPF_SERVICE_FILE="/etc/systemd/system/gretl-ebpf.service" API="${GR_API:-https://api.gretl.dev}" EBPF_ENABLED=false RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m' info() { echo -e "${CYAN}[gretl]${NC} $*"; } success() { echo -e "${GREEN}[gretl]${NC} $*"; } warn() { echo -e "${YELLOW}[gretl]${NC} $*"; } die() { echo -e "${RED}[gretl] ERROR:${NC} $*" >&2; exit 1; } # ── Checks ──────────────────────────────────────────────────────────────────── [[ $EUID -ne 0 ]] && die "Run as root: sudo bash <(curl -fsSL https://gretl.dev/install-agent.sh)" [[ -z "$GR_TOKEN" ]] && die "Set GR_TOKEN before running:\n GR_TOKEN=your-token bash <(curl -fsSL https://gretl.dev/install-agent.sh)" echo "" echo " ⚡ Gretl Agent installer" echo "" # ── Detect cloud provider ───────────────────────────────────────────────────── detect_cloud() { # AWS IMDSv2 TOKEN_AWS=$(curl -sf -X PUT "http://169.254.169.254/latest/api/token" \ -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" 2>/dev/null || true) if [[ -n "$TOKEN_AWS" ]]; then INSTANCE_ID=$(curl -sf -H "X-aws-ec2-metadata-token: $TOKEN_AWS" \ http://169.254.169.254/latest/meta-data/instance-id 2>/dev/null || true) REGION=$(curl -sf -H "X-aws-ec2-metadata-token: $TOKEN_AWS" \ http://169.254.169.254/latest/meta-data/placement/region 2>/dev/null || true) PUBLIC_IP=$(curl -sf -H "X-aws-ec2-metadata-token: $TOKEN_AWS" \ http://169.254.169.254/latest/meta-data/public-ipv4 2>/dev/null || true) PRIVATE_IP=$(curl -sf -H "X-aws-ec2-metadata-token: $TOKEN_AWS" \ http://169.254.169.254/latest/meta-data/local-ipv4 2>/dev/null || true) if [[ -n "$INSTANCE_ID" ]]; then CLOUD_PROVIDER="aws"; CLOUD_RESOURCE_ID="$INSTANCE_ID"; CLOUD_REGION="$REGION" HOST="${PUBLIC_IP:-$PRIVATE_IP}" SERVER_LABEL="AWS EC2 ($REGION)" return fi fi # Azure IMDS AZURE=$(curl -sf -H "Metadata: true" \ "http://169.254.169.254/metadata/instance?api-version=2021-02-01" 2>/dev/null || true) if [[ -n "$AZURE" ]]; then INSTANCE_ID=$(echo "$AZURE" | grep -o '"vmId":"[^"]*"' | cut -d'"' -f4 || true) REGION=$(echo "$AZURE" | grep -o '"location":"[^"]*"' | head -1 | cut -d'"' -f4 || true) PUBLIC_IP=$(curl -sf -H "Metadata: true" \ "http://169.254.169.254/metadata/instance/network/interface/0/ipv4/ipAddress/0/publicIpAddress?api-version=2021-02-01&format=text" 2>/dev/null || true) if [[ -n "$INSTANCE_ID" ]]; then CLOUD_PROVIDER="azure"; CLOUD_RESOURCE_ID="$INSTANCE_ID"; CLOUD_REGION="$REGION" HOST="${PUBLIC_IP:-$(hostname -I | awk '{print $1}')}" SERVER_LABEL="Azure VM ($REGION)" return fi fi # GCP metadata server GCP=$(curl -sf -H "Metadata-Flavor: Google" \ http://metadata.google.internal/computeMetadata/v1/instance/id 2>/dev/null || true) if [[ -n "$GCP" ]]; then INSTANCE_ID="$GCP" REGION=$(curl -sf -H "Metadata-Flavor: Google" \ http://metadata.google.internal/computeMetadata/v1/instance/zone 2>/dev/null | awk -F/ '{print $NF}' || true) PUBLIC_IP=$(curl -sf -H "Metadata-Flavor: Google" \ "http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip" 2>/dev/null || true) CLOUD_PROVIDER="gcp"; CLOUD_RESOURCE_ID="$INSTANCE_ID"; CLOUD_REGION="$REGION" HOST="${PUBLIC_IP:-$(hostname -I | awk '{print $1}')}" SERVER_LABEL="GCP VM ($REGION)" return fi # Generic / bare metal CLOUD_PROVIDER=""; CLOUD_RESOURCE_ID=""; CLOUD_REGION="" HOST=$(hostname -I | awk '{print $1}') SERVER_LABEL="$(hostname)" } info "Detecting cloud provider…" detect_cloud if [[ -n "$CLOUD_PROVIDER" ]]; then success "Detected: $SERVER_LABEL (${CLOUD_PROVIDER^^} instance $CLOUD_RESOURCE_ID)" else warn "No cloud provider detected — registering as bare-metal server" fi # ── Install Node.js if needed ───────────────────────────────────────────────── if ! command -v node &>/dev/null; then info "Installing Node.js 20 LTS…" if command -v apt-get &>/dev/null; then curl -fsSL https://deb.nodesource.com/setup_20.x | bash - >/dev/null 2>&1 apt-get install -y nodejs >/dev/null 2>&1 elif command -v yum &>/dev/null; then curl -fsSL https://rpm.nodesource.com/setup_20.x | bash - >/dev/null 2>&1 yum install -y nodejs >/dev/null 2>&1 elif command -v dnf &>/dev/null; then curl -fsSL https://rpm.nodesource.com/setup_20.x | bash - >/dev/null 2>&1 dnf install -y nodejs >/dev/null 2>&1 else die "Cannot install Node.js — install it manually then re-run this script." fi success "Node.js $(node -v) installed" else info "Node.js $(node -v) already installed" fi # ── Register server with Gretl backend ─────────────────────────────────────── info "Registering server with Gretl…" SERVER_NAME="${GR_SERVER_NAME:-$SERVER_LABEL}" ENVIRONMENT="${GR_ENVIRONMENT:-other}" PAYLOAD=$(cat </dev/null || true) if [[ -z "$REGISTER_RESP" ]]; then # May already exist — fetch existing SERVERS_RESP=$(curl -sf "$API/servers" \ -H "Authorization: Bearer $GR_TOKEN" 2>/dev/null || true) SERVER_ID=$(echo "$SERVERS_RESP" | grep -o "\"id\":\"[^\"]*\"" | head -1 | cut -d'"' -f4 || true) [[ -z "$SERVER_ID" ]] && die "Could not register server. Check your GR_TOKEN." else SERVER_ID=$(echo "$REGISTER_RESP" | grep -o "\"id\":\"[^\"]*\"" | head -1 | cut -d'"' -f4 || true) [[ -z "$SERVER_ID" ]] && die "Unexpected response from API: $REGISTER_RESP" fi success "Server registered — id=$SERVER_ID" # ── Install agent ───────────────────────────────────────────────────────────── info "Installing agent to $AGENT_DIR…" mkdir -p "$AGENT_DIR" curl -fsSL "https://gretl.dev/agent.js" -o "$AGENT_BIN" 2>/dev/null || \ curl -fsSL "https://raw.githubusercontent.com/slowdutch/gretl-sdks/main/agent/agent.js" -o "$AGENT_BIN" chmod +x "$AGENT_BIN" # ── eBPF detection + conditional install ───────────────────────────────────── check_ebpf_support() { # Kernel version check — require >= 5.8 KVER_MAJOR=$(uname -r | cut -d. -f1) KVER_MINOR=$(uname -r | cut -d. -f2 | cut -d- -f1) if [[ "$KVER_MAJOR" -lt 5 ]] || { [[ "$KVER_MAJOR" -eq 5 ]] && [[ "$KVER_MINOR" -lt 8 ]]; }; then warn "Kernel $(uname -r) < 5.8 — eBPF not supported, skipping" return 1 fi # CAP_BPF check — kernel 5.8+ exposes /proc/sys/kernel/unprivileged_bpf_disabled # We're running as root so CAP_BPF is available, but BTF must be present if [[ ! -f /sys/kernel/btf/vmlinux ]]; then warn "BTF not available (/sys/kernel/btf/vmlinux missing) — eBPF CO-RE not supported, skipping" return 1 fi # Check CAP_PERFMON or fallback to perf_event_paranoid <= 2 PARANOID=$(cat /proc/sys/kernel/perf_event_paranoid 2>/dev/null || echo "3") if [[ "$PARANOID" -gt 2 ]]; then warn "perf_event_paranoid=$PARANOID — setting to 2 for eBPF profiling" echo 2 > /proc/sys/kernel/perf_event_paranoid echo "kernel.perf_event_paranoid=2" >> /etc/sysctl.d/99-gretl-ebpf.conf fi return 0 } install_ebpf_agent() { info "Installing gretl-ebpf agent…" ARCH=$(uname -m) case "$ARCH" in x86_64) EBPF_ARCH="amd64" ;; aarch64) EBPF_ARCH="arm64" ;; *) warn "Unsupported architecture $ARCH for eBPF agent — skipping" return 1 ;; esac EBPF_URL="https://github.com/slowdutch/gretl-ebpf-agent/releases/latest/download/gretl-ebpf-linux-${EBPF_ARCH}" if ! curl -fsSL "$EBPF_URL" -o "$EBPF_BIN" 2>/dev/null; then warn "Could not download gretl-ebpf binary — skipping eBPF agent" return 1 fi chmod +x "$EBPF_BIN" # Grant CAP_BPF + CAP_PERFMON on the binary so it can run without full root if command -v setcap &>/dev/null; then setcap cap_bpf,cap_perfmon,cap_net_admin,cap_sys_ptrace+ep "$EBPF_BIN" 2>/dev/null || true fi # Systemd unit for the eBPF agent cat > "$EBPF_SERVICE_FILE" </dev/null 2>&1 systemctl restart gretl-ebpf sleep 2 if systemctl is-active --quiet gretl-ebpf; then success "gretl-ebpf is running ✓ (kernel $(uname -r), BTF CO-RE enabled)" EBPF_ENABLED=true else warn "gretl-ebpf failed to start — running without eBPF. Check: journalctl -u gretl-ebpf -n 30" fi } # Run eBPF setup if kernel and capabilities support it if check_ebpf_support; then install_ebpf_agent else info "eBPF not available on this host — Gretl will use polling-based idle detection" fi # ── Systemd service ─────────────────────────────────────────────────────────── cat > "$SERVICE_FILE" </dev/null 2>&1 systemctl restart gretl-agent sleep 2 STATUS=$(systemctl is-active gretl-agent) echo "" if [[ "$STATUS" == "active" ]]; then success "gretl-agent is running ✓" echo "" echo " Server ID : $SERVER_ID" echo " Token : ${GR_TOKEN:0:8}••••••••" echo " API : $API" echo "" echo " View logs : journalctl -u gretl-agent -f" echo " Status : systemctl status gretl-agent" else die "Agent failed to start. Check: journalctl -u gretl-agent -n 50" fi