Adopt live production state as git baseline

This commit is contained in:
root
2026-07-07 07:55:47 -05:00
commit 588cfe3f10
85 changed files with 30896 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
#!/bin/bash
# JARVIS Network Scanner — runs on PVE1, pushes nmap results to JARVIS
# Cron: */3 * * * * /usr/local/bin/jarvis-netscan.sh >/dev/null 2>&1
JARVIS_URL="http://10.48.200.211"
JARVIS_HOST="jarvis.orbishosting.com"
REG_KEY=$(cat /etc/jarvis-agent/reg-key 2>/dev/null)
if [ -z "$REG_KEY" ]; then
echo "$(date): ERROR: /etc/jarvis-agent/reg-key not found" >&2
exit 1
fi
SUBNET="10.48.200.0/24"
TMPFILE=$(mktemp)
nmap -sn --send-ip "$SUBNET" 2>/dev/null > "$TMPFILE"
if [ ! -s "$TMPFILE" ]; then
echo "$(date): nmap produced no output" >&2
rm -f "$TMPFILE"
exit 1
fi
JSON=$(python3 - "$TMPFILE" <<'PYEOF'
import sys, re, json
with open(sys.argv[1]) as f:
data = f.read()
devices = []
cur = None
for line in data.splitlines():
line = line.strip()
m = re.match(r'Nmap scan report for (?:(\S+) \()?(\d+\.\d+\.\d+\.\d+)\)?', line)
if m:
if cur:
devices.append(cur)
hn = m.group(1) if m.group(1) and m.group(1) != m.group(2) else ''
cur = {'ip': m.group(2), 'hostname': hn, 'mac': '', 'vendor': ''}
elif cur:
m2 = re.match(r'MAC Address: ([0-9A-Fa-f:]{17}) \(([^)]+)\)', line)
if m2:
cur['mac'] = m2.group(1).lower()
cur['vendor'] = '' if m2.group(2) == 'Unknown' else m2.group(2)
if cur:
devices.append(cur)
print(json.dumps({'devices': devices}))
PYEOF
)
rm -f "$TMPFILE"
if [ -z "$JSON" ]; then
echo "$(date): JSON parse failed" >&2
exit 1
fi
RESPONSE=$(curl -sk --max-time 15 \
-X POST "$JARVIS_URL/api/netscan" \
-H "Host: $JARVIS_HOST" \
-H "Content-Type: application/json" \
-H "X-Registration-Key: $REG_KEY" \
-d "$JSON" 2>/dev/null)
echo "$(date): $RESPONSE"
+74
View File
@@ -0,0 +1,74 @@
#!/bin/bash
# JARVIS VoIP Phone Probe — runs every minute on PVE1
# Pings all Yealink phones + checks FusionPBX SIP registration (read-only)
# 200.3 is on an external FusionPBX — ping only, no SIP check
JARVIS_URL="http://10.48.200.211"
JARVIS_HOST="jarvis.orbishosting.com"
REG_KEY=$(cat /etc/jarvis-agent/reg-key 2>/dev/null)
if [ -z "$REG_KEY" ]; then
echo "$(date): ERROR: /etc/jarvis-agent/reg-key not found" >&2
exit 1
fi
FUSION_HOST="134.209.72.226"
# IP|alias|extension(none=skip SIP check)|mac
PHONES=(
"10.48.200.2|Yealink — Myron Main (Ext 1000)|1000|80:5e:c0:35:04:77"
"10.48.200.3|Yealink — United Mirror & Glass (External SIP)|none|c4:fc:22:28:63:71"
"10.48.200.43|Yealink T48S — Tommy Main (Ext 1001)|1001|80:5e:0c:15:0c:4f"
"10.48.200.86|Yealink — Myron Vanguard WiFi (Offline During Work Hrs)|none|"
"10.48.200.65|Yealink — Myron Vanguard Work (Ext 1003)|1003|c4:fc:22:13:e1:89"
)
# Get SIP registrations from FusionPBX (read-only)
REG_OUTPUT=$(ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 -o BatchMode=yes \
root@$FUSION_HOST "fs_cli -x 'show registrations'" 2>/dev/null || echo "")
# Collect results as TSV, delegate JSON building to python3 to avoid injection
RESULTS=""
for PHONE in "${PHONES[@]}"; do
IFS='|' read -r IP ALIAS EXT MAC <<< "$PHONE"
if ping -c 1 -W 2 "$IP" > /dev/null 2>&1; then
STATUS="online"
else
STATUS="offline"
fi
if [ "$EXT" = "none" ]; then
SIP="external"
elif [ -n "$REG_OUTPUT" ] && echo "$REG_OUTPUT" | grep -q "^${EXT},"; then
SIP="registered"
else
SIP="unregistered"
fi
RESULTS="${RESULTS}${IP}\t${ALIAS}\t${MAC}\t${STATUS}\t${SIP}\t${EXT}\n"
done
JSON=$(printf "%b" "$RESULTS" | python3 -c "
import sys, json
devices = []
for line in sys.stdin:
line = line.rstrip('\n')
if not line:
continue
parts = line.split('\t')
if len(parts) < 6:
continue
ip, alias, mac, status, sip, ext = parts[:6]
devices.append({
'ip': ip, 'alias': alias, 'mac': mac,
'vendor': 'Yealink', 'status': status,
'sip_status': sip, 'extension': ext,
})
print(json.dumps({'devices': devices}))
")
curl -sk --max-time 10 \
-X POST "$JARVIS_URL/api/netscan" \
-H "Host: $JARVIS_HOST" \
-H "Content-Type: application/json" \
-H "X-Registration-Key: $REG_KEY" \
-d "$JSON" > /dev/null 2>&1
+99
View File
@@ -0,0 +1,99 @@
#!/usr/bin/env python3
"""
JARVIS Ping Probe — runs on PVE1 (10.48.200.90), which is on the LAN.
Pings devices that can't run the full agent, then calls JARVIS heartbeat
on their behalf so the dashboard shows live status.
"""
import json
import subprocess
import urllib.request
import urllib.error
import ssl
JARVIS_URL = "http://10.48.200.211"
HOST_HEADER = "jarvis.orbishosting.com"
# Devices to probe: agent_id → api_key
DEVICES = {
"fortigate_gw": "00103aea6fcbf837bc55e11b445a3620",
"yealink_t48s": "2bf8bd7ca8dd31c28fd16aa956e15f88",
"homeassistant_ha": "6f8077dee7a7b4af202bc80886f1223d",
}
# Map agent_id → IP (for ping)
IPS = {
"fortigate_gw": "10.48.200.1",
"yealink_t48s": "10.48.200.43",
"homeassistant_ha": "10.48.200.97",
}
def ping(ip: str) -> bool:
result = subprocess.run(
["ping", "-c", "1", "-W", "2", ip],
capture_output=True, timeout=5
)
return result.returncode == 0
def heartbeat(agent_id: str, api_key: str, alive: bool):
# If device is down we still send heartbeat so JARVIS updates last_seen
# and sets status based on the alive flag via the metric payload
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
payload = json.dumps({}).encode()
req = urllib.request.Request(
f"{JARVIS_URL}/api/agent/heartbeat",
data=payload, method="POST"
)
req.add_header("Content-Type", "application/json")
req.add_header("X-Agent-Key", api_key)
req.add_header("Host", HOST_HEADER)
try:
with urllib.request.urlopen(req, timeout=10, context=ctx):
pass
except Exception:
pass
def update_status(agent_id: str, api_key: str, status: str):
"""Push a minimal metric so JARVIS knows if device is up or down."""
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
payload = json.dumps({
"type": "system",
"data": {
"hostname": agent_id,
"cpu_percent": 0,
"ping_only": True,
"ping_status": status,
}
}).encode()
req = urllib.request.Request(
f"{JARVIS_URL}/api/agent/metrics",
data=payload, method="POST"
)
req.add_header("Content-Type", "application/json")
req.add_header("X-Agent-Key", api_key)
req.add_header("Host", HOST_HEADER)
try:
with urllib.request.urlopen(req, timeout=10, context=ctx):
pass
except Exception:
pass
def main():
for agent_id, api_key in DEVICES.items():
ip = IPS.get(agent_id, "")
alive = ping(ip) if ip else False
status = "online" if alive else "offline"
print(f"{agent_id} ({ip}): {status}", flush=True)
heartbeat(agent_id, api_key, alive)
update_status(agent_id, api_key, status)
if __name__ == "__main__":
main()