#!/bin/bash # ═══════════════════════════════════════════════════════════════ # NOVA Remote Agent — One-Liner Discovery # Usage: export NOVA_KEY=nv-XXXX-XXXX-XXXX && curl -s https://agents.08.ma/scan | bash # Ou: curl -s https://agents.08.ma/scan/nv-XXXX-XXXX-XXXX | bash # Zéro dépendance, zéro installation, 30 secondes. # ═══════════════════════════════════════════════════════════════ set -e COCKPIT="${NOVA_COCKPIT:-https://agents.08.ma}" NOVA_KEY="${NOVA_KEY:-}" HOSTNAME=$(hostname 2>/dev/null || cat /etc/hostname 2>/dev/null || echo "unknown") HOSTNAME=$(echo "$HOSTNAME" | cut -d. -f1 | tr '[:upper:]' '[:lower:]' | tr '_' '-') # ── Validate NOVA_KEY ── if [ -z "$NOVA_KEY" ]; then echo "❌ NOVA_KEY manquante." echo " Générez votre clé sur https://agents.08.ma/nova/onboarding" echo " Puis : export NOVA_KEY=nv-XXXX-XXXX-XXXX && curl -s $COCKPIT/scan | bash" exit 1 fi if [[ ! "$NOVA_KEY" =~ ^nv-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$ ]]; then echo "❌ NOVA_KEY invalide : $NOVA_KEY" echo " Format attendu : nv-XXXX-XXXX-XXXX (ex: nv-7D61-2DDF-422F)" exit 1 fi # Validate key against cockpit VALIDATION=$(curl -sf "$COCKPIT/api/nova-key/$NOVA_KEY/validate" 2>/dev/null || echo '{"ok":false}') if echo "$VALIDATION" | grep -q '"ok":false'; then echo "❌ NOVA_KEY non reconnue par le cockpit." echo " Vérifiez votre clé sur https://agents.08.ma/nova/onboarding" exit 1 fi TENANT=$(echo "$VALIDATION" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tenant_id','unknown'))" 2>/dev/null || echo "unknown") PLAN=$(echo "$VALIDATION" | python3 -c "import sys,json; print(json.load(sys.stdin).get('plan','free'))" 2>/dev/null || echo "free") echo "🔍 NOVA Remote Agent — $HOSTNAME" echo " Tenant : $TENANT ($PLAN)" echo " Cockpit: $COCKPIT" echo "" # ── ① SELF ── echo "🖥️ LOCAL — scanning this machine..." CPU=$(cat /proc/cpuinfo 2>/dev/null | grep 'model name' | head -1 | cut -d: -f2- | xargs 2>/dev/null || echo "?") MEM=$(free -h 2>/dev/null | awk '/^Mem:/{print $2}' || echo "?") DISK=$(df -h / 2>/dev/null | awk 'NR==2{print $2"/"$3" used"}' || echo "?") OS_INFO="$(uname -s 2>/dev/null) $(uname -r 2>/dev/null)" UPTIME=$(uptime -p 2>/dev/null | cut -c4- || echo "?") LOCAL_IP=$(hostname -I 2>/dev/null | awk '{print $1}' || ip -4 addr show 2>/dev/null | grep inet | grep -v 127.0.0.1 | head -1 | awk '{print $2}' | cut -d/ -f1 || echo "127.0.0.1") curl -sf -X POST "$COCKPIT/api/agents/register" \ -H "Content-Type: application/json" \ -d "{\"id\":\"$HOSTNAME\",\"name\":\"${HOSTNAME^^}\",\"desc\":\"Self — $OS_INFO — $CPU — $MEM RAM — $UPTIME\",\"accent\":\"cyan\",\"icon\":\"server\",\"tags\":[\"self\",\"one-liner\"],\"status\":\"online\",\"host\":\"$LOCAL_IP\",\"cluster\":\"$TENANT\",\"nova_key\":\"$NOVA_KEY\",\"metrics\":{\"ip\":\"$LOCAL_IP\",\"cpu\":\"$CPU\",\"ram\":\"$MEM\",\"disk\":\"$DISK\",\"os\":\"$OS_INFO\",\"uptime\":\"$UPTIME\"}}" > /dev/null 2>&1 && echo " ✅ Self registered" || echo " ⚠️ Self registration failed" # ── ② ARP ── echo "📡 ARP — scanning neighbors..." arp_out=$(ip neigh show 2>/dev/null || arp -a 2>/dev/null || echo "") COUNT=0 while IFS= read -r line; do [ -z "$line" ] && continue ip=$(echo "$line" | awk '{print $1}') mac=$(echo "$line" | awk '{print $3}' 2>/dev/null || echo "?") [ "$ip" = "127.0.0.1" ] && continue [ "$mac" = "(incomplete)" ] && continue [ -z "$mac" ] && continue agent_id="arp-$(echo "$ip" | tr '.' '-')" curl -sf -X POST "$COCKPIT/api/agents/register" \ -H "Content-Type: application/json" \ -d "{\"id\":\"$agent_id\",\"name\":\"ARP-$ip\",\"desc\":\"ARP neighbor @ $ip — MAC $mac\",\"accent\":\"gold\",\"icon\":\"wifi\",\"tags\":[\"arp\",\"lan\",\"discovered\"],\"status\":\"online\",\"host\":\"$ip\",\"cluster\":\"$TENANT\",\"nova_key\":\"$NOVA_KEY\",\"metrics\":{\"ip\":\"$ip\",\"mac\":\"$mac\",\"source\":\"arp\"}}" > /dev/null 2>&1 && COUNT=$((COUNT+1)) done <<< "$arp_out" echo " ✅ $COUNT ARP neighbors registered" # ── ③ QUICK PORT SCAN ── echo "🔌 Quick port scan..." PORTS="22 80 443 445 3306 5432 6379 8080 8443 9090 3000 5000 8006 9443" PORT_COUNT=0 for ip_line in $arp_out; do ip=$(echo "$ip_line" | awk '{print $1}') [ -z "$ip" ] && continue [[ "$ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || continue [ "$ip" = "127.0.0.1" ] && continue for port in $PORTS; do timeout 0.5 bash -c "echo >/dev/tcp/$ip/$port" 2>/dev/null && { agent_id="p$(echo "$ip" | tr '.' '-')-$port" curl -sf -X POST "$COCKPIT/api/agents/register" \ -H "Content-Type: application/json" \ -d "{\"id\":\"$agent_id\",\"name\":\"Port $port @ $ip\",\"desc\":\"Open TCP port $port on $ip\",\"accent\":\"gold\",\"icon\":\"activity\",\"tags\":[\"port-scan\",\"port:$port\",\"discovered\"],\"status\":\"online\",\"host\":\"$ip\",\"cluster\":\"$TENANT\",\"nova_key\":\"$NOVA_KEY\",\"metrics\":{\"ip\":\"$ip\",\"port\":\"$port\",\"source\":\"quick-scan\"}}" > /dev/null 2>&1 && PORT_COUNT=$((PORT_COUNT+1)) } || true done done echo " ✅ $PORT_COUNT open ports found" # ── ④ DOCKER (if available) ── if command -v docker >/dev/null 2>&1 && docker ps >/dev/null 2>&1; then echo "🐳 Docker containers..." DOCKER_COUNT=0 while IFS= read -r line; do [ -z "$line" ] && continue cname=$(echo "$line" | awk '{print $NF}') cimage=$(echo "$line" | awk '{print $2}') curl -sf -X POST "$COCKPIT/api/agents/register" \ -H "Content-Type: application/json" \ -d "{\"id\":\"docker-$cname\",\"name\":\"$cname\",\"desc\":\"Docker container $cname ($cimage)\",\"accent\":\"cyan\",\"icon\":\"container\",\"tags\":[\"docker\",\"container\",\"discovered\"],\"status\":\"online\",\"host\":\"$LOCAL_IP\",\"cluster\":\"$TENANT\",\"nova_key\":\"$NOVA_KEY\",\"metrics\":{\"image\":\"$cimage\",\"source\":\"docker-ps\"}}" > /dev/null 2>&1 && DOCKER_COUNT=$((DOCKER_COUNT+1)) done <<< "$(docker ps --format '{{.Image}} {{.Names}}' 2>/dev/null || echo '')" echo " ✅ $DOCKER_COUNT containers found" fi echo "" echo "📊 Scan complet — $((COUNT + PORT_COUNT + (${DOCKER_COUNT:-0}) + 1)) devices registered" echo " Tenant : $TENANT" echo " Plan : $PLAN" echo " 🔗 Cockpit: $COCKPIT?tenant=$TENANT"