#!/bin/sh
# Mirqat agent installer.  Outbound-only — no inbound ports, no SSH keys.
#   curl -fsSL https://mirqat.dev/install.sh | sh -s -- --token mq_enroll_XXXX
#
# Installs a tiny POSIX-sh collector that pushes disk/CPU/RAM/load to the
# Mirqat ingest endpoint every 30s, and — only when you trigger one from the
# dashboard — runs a guarded maintenance action from a fixed local allow-list
# (disk report, clear /tmp, vacuum journald, drop caches, restart a service).
# It never runs anything outside that list.
set -eu

MIRQAT_URL="${MIRQAT_URL:-https://mirqat.dev}"
TOKEN="${MIRQAT_TOKEN:-}"
INTERVAL="${MIRQAT_INTERVAL:-30}"

while [ $# -gt 0 ]; do
  case "$1" in
    --token) TOKEN="$2"; shift 2 ;;
    --url)   MIRQAT_URL="$2"; shift 2 ;;
    --interval) INTERVAL="$2"; shift 2 ;;
    *) shift ;;
  esac
done

if [ -z "$TOKEN" ]; then
  echo "mirqat: missing --token (get one from the app - Add - Install agent)" >&2
  exit 1
fi

# Needs root to write /opt and install the systemd unit. When piped from curl
# the script is on stdin, so we can't re-exec cleanly — print the exact fix.
if [ "$(id -u)" -ne 0 ]; then
  echo "mirqat: root required (writes /opt/mirqat-agent, installs a systemd unit)." >&2
  echo "        re-run with sudo on the shell, not curl:" >&2
  echo "  curl -fsSL https://mirqat.dev/install.sh | sudo sh -s -- --token $TOKEN" >&2
  exit 1
fi

DIR=/opt/mirqat-agent
echo "mirqat: installing agent -> $DIR (ingest: $MIRQAT_URL)"
mkdir -p "$DIR"

# -- enroll: exchange the one-time token for a per-agent credential ----------
OS="$(uname -s) $(uname -m)"
ENROLL="$(curl -fsSL -X POST "$MIRQAT_URL/api/enroll" \
  -H 'content-type: application/json' \
  -d "{\"token\":\"$TOKEN\",\"os\":\"$OS\",\"agent_version\":\"1.5.0\"}" || true)"
AGENT_TOKEN="$(printf '%s' "$ENROLL" | sed -n 's/.*"agent_token":"\([^"]*\)".*/\1/p')"
[ -z "$AGENT_TOKEN" ] && AGENT_TOKEN="$TOKEN"   # ingest also accepts the enroll token

cat > "$DIR/agent.conf" <<EOF
MIRQAT_URL=$MIRQAT_URL
AGENT_TOKEN=$AGENT_TOKEN
INTERVAL=$INTERVAL
EOF

# -- the collector -----------------------------------------------------------
cat > "$DIR/agent.sh" <<'AGENT'
#!/bin/sh
set -eu
. /opt/mirqat-agent/agent.conf
VER=1.5.0
TAB=$(printf '\t')

cpu_sample() { awk '/^cpu /{print $2+$3+$4+$5+$6+$7+$8, $2+$3+$4+$7+$8}' /proc/stat; }

read_cpu() {
  set -- $(cpu_sample); T1=$1; B1=$2
  sleep 1
  set -- $(cpu_sample); T2=$1; B2=$2
  DT=$((T2 - T1)); DB=$((B2 - B1))
  if [ "$DT" -gt 0 ]; then echo $(( (DB * 100) / DT )); else echo 0; fi
}

collect() {
  DISK=$(df -P / 2>/dev/null | awk 'NR==2{gsub("%","",$5); print $5}'); DISK=${DISK:-0}
  MEM=$(awk '/MemTotal/{t=$2} /MemAvailable/{a=$2} END{ if(t>0) printf "%d",(t-a)*100/t; else print 0 }' /proc/meminfo 2>/dev/null); MEM=${MEM:-0}
  SWAP=$(awk '/SwapTotal/{t=$2} /SwapFree/{f=$2} END{ if(t>0) printf "%d",(t-f)*100/t; else print 0 }' /proc/meminfo 2>/dev/null); SWAP=${SWAP:-0}
  set -- $(cat /proc/loadavg 2>/dev/null); L1=${1:-0}; L5=${2:-0}; L15=${3:-0}
  UP=$(awk '{printf "%d",$1}' /proc/uptime 2>/dev/null); UP=${UP:-0}
  CPU=$(read_cpu)
  printf '{"cpu_pct":%s,"ram_pct":%s,"swap_pct":%s,"disk_pct":%s,"disk_mount":"/","load1":%s,"load5":%s,"load15":%s,"uptime_s":%s,"agent_version":"%s"}' \
    "$CPU" "$MEM" "$SWAP" "$DISK" "$L1" "$L5" "$L15" "$UP" "$VER"
}

# --- guarded maintenance actions -------------------------------------------
# This local case statement is the real allow-list: the server can only ASK for
# one of these keys, and anything unrecognised is refused here regardless of
# what the server says. The single argument (restart-service's unit name) is
# re-validated to a strict charset and passed as ONE argv token, never through
# a shell string, so no argument can inject a command.
run_action() {  # $1=action key  $2=arg
  case "$1" in
    disk-report)
      df -h 2>&1
      echo
      echo "--- inodes ---"
      df -i 2>&1 | head -20
      echo
      echo "--- largest directories on / (depth 1, same filesystem, best-effort) ---"
      # -x keeps du on the root fs; time-box it so a huge tree can't wedge the
      # agent or blow past the server's running-TTL.
      if command -v timeout >/dev/null 2>&1; then
        timeout 45 du -xhd1 / 2>/dev/null | sort -rh | head -12 || echo "(scan skipped or exceeded 45s)"
      else
        du -xhd1 / 2>/dev/null | sort -rh | head -12
      fi
      ;;
    clear-tmp)
      find /tmp -xdev -type f -mtime +2 -delete 2>/dev/null || true
      echo "cleared files under /tmp not modified in 2+ days"
      echo "free on /tmp now: $(df -h /tmp 2>/dev/null | awk 'NR==2{print $4}')"
      ;;
    vacuum-journal)
      if command -v journalctl >/dev/null 2>&1; then
        journalctl --vacuum-size=200M 2>&1
      else
        echo "journalctl not present on this host"; return 1
      fi
      ;;
    drop-caches)
      sync
      if [ -w /proc/sys/vm/drop_caches ]; then
        echo 3 > /proc/sys/vm/drop_caches && echo "reclaimable page cache dropped"
        free -h 2>/dev/null || true
      else
        echo "cannot write /proc/sys/vm/drop_caches (need root)"; return 1
      fi
      ;;
    restart-service)
      # Reject empty, a leading dash (would be read as a systemctl option), or any
      # char outside the strict set. Then pass the name after `--` so it can only
      # ever be a unit, never an option.
      case "$2" in
        ''|-*|*[!A-Za-z0-9@._-]*) echo "refused: invalid service name"; return 2 ;;
      esac
      if command -v systemctl >/dev/null 2>&1; then
        systemctl restart -- "$2" 2>&1 && echo "restarted $2" && systemctl is-active -- "$2" 2>&1
      else
        echo "systemctl not present on this host"; return 1
      fi
      ;;
    *)
      echo "refused: unknown action '$1'"; return 3 ;;
  esac
}

# Poll for queued actions, run each from the local catalog, report the result.
# Everything here is best-effort: a failure never aborts the metrics loop.
poll_actions() {
  ACTIONS=$(curl -fsS "$MIRQAT_URL/api/agent/actions" -H "authorization: Bearer $AGENT_TOKEN" 2>/dev/null || true)
  [ -z "$ACTIONS" ] && return 0
  printf '%s\n' "$ACTIONS" | while IFS="$TAB" read -r ID ACT ARG; do
    [ -z "$ID" ] && continue
    set +e
    OUT=$(run_action "$ACT" "${ARG:-}" 2>&1); CODE=$?
    set -e
    OUT=$(printf '%s' "$OUT" | head -c 8000)
    curl -fsS -X POST "$MIRQAT_URL/api/agent/result" \
      -H "authorization: Bearer $AGENT_TOKEN" \
      -H "x-action-id: $ID" -H "x-exit-code: $CODE" \
      -H 'content-type: text/plain; charset=utf-8' \
      --data-binary "$OUT" >/dev/null 2>&1 || echo "mirqat-agent: result post failed for $ID"
  done
  return 0
}

echo "mirqat-agent: reporting to $MIRQAT_URL every ${INTERVAL}s (v$VER)"
while true; do
  BODY=$(collect)
  curl -fsS -X POST "$MIRQAT_URL/api/ingest" \
    -H "authorization: Bearer $AGENT_TOKEN" \
    -H 'content-type: application/json' \
    -d "$BODY" >/dev/null 2>&1 || echo "mirqat-agent: push failed (will retry)"
  poll_actions || true
  sleep "$INTERVAL"
done
AGENT
chmod +x "$DIR/agent.sh"

# -- run it (systemd if available, else nohup) -------------------------------
if command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]; then
  cat > /etc/systemd/system/mirqat-agent.service <<EOF
[Unit]
Description=Mirqat Agent
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/bin/sh /opt/mirqat-agent/agent.sh
Restart=always
RestartSec=5
Nice=10

[Install]
WantedBy=multi-user.target
EOF
  systemctl daemon-reload
  systemctl enable --now mirqat-agent >/dev/null 2>&1 || systemctl restart mirqat-agent
  echo "mirqat: agent running as systemd service (systemctl status mirqat-agent)"
else
  pkill -f /opt/mirqat-agent/agent.sh 2>/dev/null || true
  nohup /bin/sh "$DIR/agent.sh" >/var/log/mirqat-agent.log 2>&1 &
  echo "mirqat: agent running (nohup, log: /var/log/mirqat-agent.log)"
fi

echo "mirqat: done - your meerkat is taking post. Watch the colony at $MIRQAT_URL/app"
