mirror of
https://github.com/myronblair/jarvis
synced 2026-07-29 09:12:36 -05:00
Compare commits
12 Commits
84cd2ded50
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| af03a2f2d8 | |||
| dfc92a6791 | |||
| 05522edb1d | |||
| 435af8ccc9 | |||
| a9ea75db98 | |||
| 651455cb47 | |||
| 90e4ded7c9 | |||
| c1275d47a6 | |||
| 08fbfaa3e4 | |||
| 1f25b5d04d | |||
| 874f6e8c5c | |||
| 42a82c40cb |
@@ -175,7 +175,7 @@ def fetch_ha_states(cfg: dict) -> list:
|
||||
dt = datetime.fromisoformat(lc.replace("Z", "+00:00"))
|
||||
lc = dt.astimezone(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
|
||||
except Exception:
|
||||
lc = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
|
||||
lc = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
entities.append({
|
||||
"entity_id": entity_id,
|
||||
@@ -194,13 +194,11 @@ def main():
|
||||
heartbeat_every = int(cfg.get("heartbeat_every", 10))
|
||||
|
||||
api_key = state.get("api_key", "")
|
||||
if not api_key:
|
||||
while not api_key:
|
||||
api_key = register(cfg, state)
|
||||
if not api_key:
|
||||
log("Could not register. Retrying in 60s...")
|
||||
time.sleep(60)
|
||||
main()
|
||||
return
|
||||
|
||||
headers = {"X-Agent-Key": api_key}
|
||||
last_push = 0
|
||||
@@ -229,6 +227,7 @@ def main():
|
||||
last_push = now
|
||||
else:
|
||||
log("No HA entities fetched (HA down or token invalid?)")
|
||||
last_push = now
|
||||
|
||||
time.sleep(heartbeat_every)
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -18,13 +18,13 @@ define(chr(39)+'CLAUDE_MODEL'.chr(39), chr(39)+'claude-sonnet-4-6'.chr(39))
|
||||
define(chr(39)+'CLAUDE_MAX_TOKENS'.chr(39), 1024);
|
||||
|
||||
define(chr(39)+'GROQ_API_KEY'.chr(39), chr(39)+'gsk_...'.chr(39));
|
||||
define(chr(39)+'GROQ_MODEL_SEARCH'.chr(39), chr(39)+'groq/compound-mini'.chr(39));
|
||||
define(chr(39)+'GROQ_MODEL_SEARCH'.chr(39), chr(39)+'compound-beta-mini'.chr(39));
|
||||
define(chr(39)+'GROQ_MODEL_GENERAL'.chr(39), chr(39)+'llama-3.3-70b-versatile'.chr(39));
|
||||
define(chr(39)+'GROQ_TIMEOUT'.chr(39), 30);
|
||||
|
||||
define(chr(39)+'OLLAMA_HOST'.chr(39), chr(39)+'http://10.48.200.95:11434'.chr(39));
|
||||
define(chr(39)+'OLLAMA_MODEL_PRIMARY'.chr(39), chr(39)+'llama3.2:1b'.chr(39));
|
||||
define(chr(39)+'OLLAMA_MODEL_HEAVY'.chr(39), chr(39)+'llama3.1:70b'.chr(39));
|
||||
define(chr(39)+'OLLAMA_HOST'.chr(39), chr(39)+'http://10.48.200.210:11434'.chr(39));
|
||||
define(chr(39)+'OLLAMA_MODEL_PRIMARY'.chr(39), chr(39)+'llama3.1:8b'.chr(39));
|
||||
define(chr(39)+'OLLAMA_MODEL_HEAVY'.chr(39), chr(39)+'llama3.1:8b'.chr(39));
|
||||
define(chr(39)+'OLLAMA_TIMEOUT'.chr(39), 90);
|
||||
|
||||
define(chr(39)+'LOCAL_SUBNET'.chr(39), chr(39)+'10.48.200'.chr(39));
|
||||
|
||||
@@ -111,7 +111,8 @@ switch ($agentAction) {
|
||||
|
||||
// ── HEARTBEAT ────────────────────────────────────────────────────────────
|
||||
case 'heartbeat':
|
||||
update_agent_seen($agent['agent_id'], 'online', trim($data['version'] ?? '') ?: null);
|
||||
$hbStatus = in_array($data['status'] ?? '', ['online','offline']) ? $data['status'] : 'online';
|
||||
update_agent_seen($agent['agent_id'], $hbStatus, trim($data['version'] ?? '') ?: null);
|
||||
|
||||
// Return any pending commands for this agent
|
||||
$commands = JarvisDB::query(
|
||||
|
||||
@@ -181,10 +181,10 @@ function collect_all(): array {
|
||||
$results['sites'] = 'skipped (fresh)';
|
||||
} else try {
|
||||
$sites = [
|
||||
"jarvis" => "http://jarvis.orbishosting.com:1972",
|
||||
"jarvis" => "http://127.0.0.1",
|
||||
'tomsjavajive' => 'https://tomsjavajive.com',
|
||||
'epictravelexp'=> 'https://epictravelexpeditions.com',
|
||||
'parkersling' => 'https://parkerslingshotrentals.com',
|
||||
'parkerslingshotrentals' => 'https://parkerslingshotrentals.com',
|
||||
'orbishosting' => 'https://orbishosting.com',
|
||||
'orbisportal' => 'https://orbis.orbishosting.com',
|
||||
'tomtomgames' => 'https://tomtomgames.com',
|
||||
|
||||
+48
-11
@@ -55,7 +55,7 @@ CREATE TABLE `agent_metrics` (
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_agent_time` (`agent_id`,`recorded_at`),
|
||||
KEY `idx_recorded` (`recorded_at`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=29445 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=31422 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
@@ -182,7 +182,7 @@ CREATE TABLE `conversations` (
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_session` (`session_id`),
|
||||
KEY `idx_created` (`created_at`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=325 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=335 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
@@ -206,7 +206,7 @@ CREATE TABLE `ha_entities` (
|
||||
UNIQUE KEY `uk_agent_entity` (`agent_id`,`entity_id`),
|
||||
KEY `idx_domain` (`domain`),
|
||||
KEY `idx_updated` (`updated_at`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=8436 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=77909 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
@@ -226,7 +226,7 @@ CREATE TABLE `kb_facts` (
|
||||
`updated_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `unique_fact` (`category`,`fact_key`,`host`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=39129 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=41478 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
@@ -247,7 +247,7 @@ CREATE TABLE `kb_intents` (
|
||||
`active` tinyint(1) DEFAULT 1,
|
||||
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=47 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
@@ -266,7 +266,7 @@ CREATE TABLE `kb_ollama_models` (
|
||||
`pulled_at` timestamp NULL DEFAULT current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `model_name` (`model_name`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
@@ -283,7 +283,7 @@ CREATE TABLE `kb_preferences` (
|
||||
`updated_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `pref_key` (`pref_key`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
@@ -340,7 +340,7 @@ CREATE TABLE `network_devices` (
|
||||
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_ip` (`ip`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=409 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=5556 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
@@ -406,10 +406,10 @@ CREATE TABLE `usage_patterns` (
|
||||
`hour` tinyint(2) NOT NULL,
|
||||
`dow` tinyint(1) NOT NULL,
|
||||
`hit_count` int(11) DEFAULT 1,
|
||||
`last_used` datetime DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||
`last_seen` datetime DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_intent_time` (`intent_name`,`hour`,`dow`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
@@ -441,4 +441,41 @@ CREATE TABLE `users` (
|
||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||
|
||||
-- Dump completed on 2026-06-29 20:43:24
|
||||
--
|
||||
-- Table structure for table `guardian_config`
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `guardian_config` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`key_name` varchar(64) NOT NULL,
|
||||
`value` varchar(255) NOT NULL DEFAULT '',
|
||||
`updated_at` datetime DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_key` (`key_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
--
|
||||
-- Table structure for table `guardian_events`
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `guardian_events` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`event_type` varchar(64) NOT NULL,
|
||||
`severity` enum('info','warning','critical') NOT NULL DEFAULT 'info',
|
||||
`agent_id` varchar(64) NOT NULL DEFAULT '',
|
||||
`hostname` varchar(128) NOT NULL DEFAULT '',
|
||||
`metric` varchar(64) NOT NULL DEFAULT '',
|
||||
`value` float NOT NULL DEFAULT 0,
|
||||
`threshold` float NOT NULL DEFAULT 0,
|
||||
`message` text NOT NULL,
|
||||
`ai_analysis` text NOT NULL DEFAULT '',
|
||||
`acknowledged` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`created_at` datetime NOT NULL DEFAULT current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_severity` (`severity`),
|
||||
KEY `idx_ack` (`acknowledged`),
|
||||
KEY `idx_created` (`created_at`),
|
||||
KEY `idx_agent` (`agent_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Dump completed on 2026-06-29 23:15:44
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
-- JARVIS KB Seed Data
|
||||
-- Preferences
|
||||
INSERT INTO kb_preferences (pref_key, pref_value) VALUES
|
||||
('user_name', 'Myron'),
|
||||
('user_title', 'Mr. Blair'),
|
||||
('ai_model', 'llama3.1:8b'),
|
||||
('timezone', 'America/Chicago')
|
||||
ON DUPLICATE KEY UPDATE pref_value = VALUES(pref_value);
|
||||
|
||||
-- Intents: greeting, time, system, network, proxmox, ollama, tasks, HA
|
||||
INSERT INTO kb_intents (intent_name, pattern, response_template, fact_category, action_type, priority, active) VALUES
|
||||
|
||||
-- Greetings
|
||||
('greeting', '(?i)^(hello|hi|hey|good (morning|afternoon|evening)|what.?s up|howdy)\\b', 'Good {current_time}, {user_title}. All systems are online. How can I assist you?', 'system', 'response', 10, 1),
|
||||
|
||||
-- Time / date
|
||||
('current_time', '(?i)\\b(what.?s the (time|current time)|what time is it|tell me the time)\\b', 'It is currently {current_time}, {user_title}.', NULL, 'response', 9, 1),
|
||||
('current_date', '(?i)\\b(what.?s (today.?s date|the date)|what day is it|today.?s date)\\b', 'Today is {current_date}, {user_title}.', NULL, 'response', 9, 1),
|
||||
|
||||
-- System status
|
||||
('system_status', '(?i)\\b(system (status|health)|how.?s (the system|everything)|jarvis status|all systems)\\b', 'JARVIS is fully operational, {user_title}. CPU: {cpu_usage}%, Memory: {mem_percent}% used ({mem_used_gb}GB / {mem_total_gb}GB). Disk: {disk_used} used of {disk_total}. Uptime: {uptime}. Network agents: {online_count}/{total_count} online.', 'system', 'response', 8, 1),
|
||||
('cpu_status', '(?i)\\b(cpu|processor) (usage|load|status|percent|utilization)\\b', 'Current CPU usage is {cpu_usage}%, {user_title}. Load averages: {load_1m} (1m), {load_5m} (5m), {load_15m} (15m).', 'system', 'response', 8, 1),
|
||||
('memory_status', '(?i)\\b(memory|ram|mem) (usage|status|free|used|available)\\b', 'Memory: {mem_used_gb}GB used of {mem_total_gb}GB ({mem_percent}% utilized), {user_title}. Free: {mem_free_gb}GB.', 'system', 'response', 8, 1),
|
||||
('disk_status', '(?i)\\b(disk|storage|drive) (usage|space|status|free|used|available)\\b', 'Disk status: {disk_used} used of {disk_total} total, {disk_free} free, {user_title}.', 'system', 'response', 8, 1),
|
||||
('uptime', '(?i)\\b(uptime|how long.*running|how long.*up|server uptime)\\b', 'JARVIS has been running for {uptime}, {user_title}.', 'system', 'response', 7, 1),
|
||||
|
||||
-- Network status
|
||||
('network_status', '(?i)\\b(network (status|health|agents)|agents (online|status)|how many (agents|devices) (online|running))\\b', 'Network status: {online_count} of {total_count} agents are online, {user_title}.', 'network', 'response', 8, 1),
|
||||
('network_scan', '(?i)\\b(run (a )?network scan|scan (the )?network|nmap scan|network devices)\\b', 'Initiating network scan, {user_title}.', NULL, 'action', 7, 1),
|
||||
|
||||
-- Proxmox
|
||||
('proxmox_status', '(?i)\\b(proxmox (status|health)|vm (status|count|summary)|virtual machines|how many vms)\\b', 'Proxmox: {vm_running} of {vm_total} VMs/containers running, {user_title}. Host CPU: {pve_cpu_percent}%, Memory: {pve_mem_used_gb}GB / {pve_mem_total_gb}GB ({pve_mem_percent}%).', 'proxmox', 'response', 8, 1),
|
||||
('vm_suggestions', '(?i)\\b(vm (resources|performance|usage)|check vms|resource usage)\\b', 'Checking VM resource usage, {user_title}.', 'proxmox', 'action', 7, 1),
|
||||
|
||||
-- Ollama / AI
|
||||
('ollama_status', '(?i)\\b(ollama (status|health|models)|ai models|llm status|local (ai|models))\\b', 'Ollama is {status} with {model_count} model(s) available: {available_models}, {user_title}.', 'ollama', 'response', 7, 1),
|
||||
|
||||
-- Site health
|
||||
('site_status', '(?i)\\b(site(s)? (status|health|up|down)|website status|are (the )?sites (up|down))\\b', 'Site health — jarvis: {jarvis}, orbishosting: {orbishosting}, tomtomgames: {tomtomgames}, tomsjavajive: {tomsjavajive}, parkerslingshotrentals: {parkersling}, epictravelexpeditions: {epictravelexp}, {user_title}.', 'sites', 'response', 7, 1),
|
||||
|
||||
-- Tasks / planner
|
||||
('task_count', '(?i)\\b(how many tasks|pending tasks|task (count|summary)|my tasks)\\b', 'You have {pending_count} pending tasks and {overdue_count} overdue, {user_title}.', NULL, 'response', 7, 1),
|
||||
('planner_briefing', '(?i)\\b((daily )?briefing|what.?s (on|happening) today|today.?s schedule|morning briefing)\\b', 'Fetching your daily briefing, {user_title}.', NULL, 'action', 8, 1),
|
||||
|
||||
-- Home Assistant
|
||||
('ha_lights_on', '(?i)\\b(turn (on|off) (the |all )?lights?|lights? (on|off)|switch (on|off) (the )?lights?)\\b', 'Sending light command, {user_title}.', NULL, 'action', 8, 1),
|
||||
('ha_scene', '(?i)\\b(activate (a |the )?scene|set (a |the )?scene|home scene)\\b', 'Activating home scene, {user_title}.', NULL, 'action', 7, 1),
|
||||
|
||||
-- Jellyfin
|
||||
('jellyfin_now_playing', '(?i)\\b(what.?s (playing|on)|now playing|jellyfin.*playing|playing.*jellyfin)\\b', 'Checking Jellyfin now playing, {user_title}.', NULL, 'action', 7, 1),
|
||||
('jellyfin_library', '(?i)\\b(jellyfin (library|media|shows?|movies?)|media library|show.*library)\\b', 'Fetching Jellyfin library, {user_title}.', NULL, 'action', 6, 1),
|
||||
('jellyfin_pause', '(?i)\\b(pause (jellyfin|playback|media)|stop (playing|jellyfin))\\b', 'Pausing Jellyfin, {user_title}.', NULL, 'action', 7, 1),
|
||||
|
||||
-- DO server
|
||||
('do_status', '(?i)\\b(do (server|status)|digital ocean (status|server)|vps status)\\b', 'Digital Ocean server is {do_status}, {user_title}.', 'do_server', 'response', 7, 1),
|
||||
|
||||
-- Focus / panels
|
||||
('focus_mode', '(?i)\\b(focus (mode|on)|enable focus|concentration mode)\\b', 'Enabling focus mode, {user_title}.', NULL, 'action', 6, 1),
|
||||
('show_panels', '(?i)\\b(show (all )?panels|expand (all|everything)|full view)\\b', 'Expanding all panels, {user_title}.', NULL, 'action', 6, 1),
|
||||
|
||||
-- Help
|
||||
('help', '(?i)^(help|what can you do|commands|capabilities|what do you know)\\s*\\??$', 'I can help you with: system status, network status, VM/Proxmox status, Ollama AI models, site health, tasks and planner briefings, Jellyfin media, Home Assistant lights and devices, and general questions via Ollama. What would you like to know, {user_title}?', NULL, 'response', 5, 1)
|
||||
|
||||
ON DUPLICATE KEY UPDATE
|
||||
pattern = VALUES(pattern),
|
||||
response_template = VALUES(response_template),
|
||||
active = 1;
|
||||
|
||||
SELECT COUNT(*) AS intents_seeded FROM kb_intents;
|
||||
SELECT COUNT(*) AS prefs_seeded FROM kb_preferences;
|
||||
@@ -0,0 +1,17 @@
|
||||
[Unit]
|
||||
Description=JARVIS Arc Reactor
|
||||
After=network-online.target mysql.service
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/opt/jarvis-arc
|
||||
ExecStart=/opt/jarvis-arc/venv/bin/python3 /opt/jarvis-arc/reactor.py
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
User=root
|
||||
StandardOutput=append:/home/jarvis.orbishosting.com/logs/arc_reactor.log
|
||||
StandardError=append:/home/jarvis.orbishosting.com/logs/arc_reactor.log
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
+35
-4
@@ -80,11 +80,42 @@ while IFS= read -r path; do
|
||||
systemctl reload lsws 2>/dev/null || systemctl restart lsws 2>/dev/null
|
||||
log "OLS reloaded for JARVIS deploy"
|
||||
|
||||
# Sync reactor.py to runtime location if it changed
|
||||
if echo "$CHANGED" | grep -q 'deploy/reactor.py'; then
|
||||
# Patch live config.php with correct Ollama host/model and Groq search model
|
||||
CONFIG="$path/api/config.php"
|
||||
if [ -f "$CONFIG" ]; then
|
||||
sed -i "s|http://10\.48\.200\.95:11434|http://10.48.200.210:11434|g" "$CONFIG"
|
||||
sed -i "s|'llama3\.2:1b'|'llama3.1:8b'|g" "$CONFIG"
|
||||
sed -i "s|\"llama3\.2:1b\"|\"llama3.1:8b\"|g" "$CONFIG"
|
||||
sed -i "s|'llama3\.1:70b'|'llama3.1:8b'|g" "$CONFIG"
|
||||
sed -i "s|\"llama3\.1:70b\"|\"llama3.1:8b\"|g" "$CONFIG"
|
||||
sed -i "s|'groq/compound-mini'|'compound-beta-mini'|g;s|\"groq/compound-mini\"|\"compound-beta-mini\"|g" "$CONFIG"
|
||||
log "Patched config.php: Ollama IP→.210, model→llama3.1:8b, Groq search→compound-beta-mini"
|
||||
fi
|
||||
|
||||
# Self-install the deploy script on every run
|
||||
cp "$path/deploy/jarvis-deploy.sh" /usr/local/bin/jarvis-deploy.sh 2>/dev/null && chmod +x /usr/local/bin/jarvis-deploy.sh
|
||||
|
||||
# Sync reactor.py + service file to runtime location if they changed
|
||||
if echo "$CHANGED" | grep -q 'deploy/reactor.py\|deploy/jarvis-arc.service\|deploy/requirements.txt'; then
|
||||
mkdir -p /opt/jarvis-arc /home/jarvis.orbishosting.com/logs
|
||||
cp "$path/deploy/reactor.py" /opt/jarvis-arc/reactor.py
|
||||
systemctl restart jarvis-arc
|
||||
log "Arc Reactor updated and restarted (reactor.py changed)"
|
||||
cp "$path/deploy/requirements.txt" /opt/jarvis-arc/requirements.txt 2>/dev/null
|
||||
# Bootstrap venv if it doesn't exist
|
||||
if [ ! -f /opt/jarvis-arc/venv/bin/activate ]; then
|
||||
log "Arc Reactor venv missing — creating and installing packages"
|
||||
python3 -m venv /opt/jarvis-arc/venv
|
||||
/opt/jarvis-arc/venv/bin/pip install -q -r /opt/jarvis-arc/requirements.txt
|
||||
log "Arc Reactor venv ready"
|
||||
fi
|
||||
if echo "$CHANGED" | grep -q 'deploy/jarvis-arc.service'; then
|
||||
cp "$path/deploy/jarvis-arc.service" /etc/systemd/system/jarvis-arc.service
|
||||
systemctl daemon-reload
|
||||
systemctl enable jarvis-arc
|
||||
log "Arc Reactor service file installed and enabled"
|
||||
fi
|
||||
systemctl restart jarvis-arc 2>/dev/null || \
|
||||
bash -c "pkill -f reactor.py 2>/dev/null; sleep 1; cd /opt/jarvis-arc && source venv/bin/activate && nohup python3 reactor.py >> /home/jarvis.orbishosting.com/logs/arc_reactor.log 2>&1 &"
|
||||
log "Arc Reactor updated and restarted"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
+77
-21
@@ -49,8 +49,8 @@ CLAUDE_API_KEY = "sk-ant-api03-JL6vjFeyEfajQmaTOmsT6AfLLPs2icrIAvvJ0hdi4DuMi0155
|
||||
CLAUDE_MODEL = "claude-sonnet-4-6"
|
||||
GROQ_API_KEY = "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"
|
||||
GROQ_MODEL = "llama-3.3-70b-versatile"
|
||||
OLLAMA_HOST = "http://10.48.200.95:11434"
|
||||
OLLAMA_MODEL = "llama3.2:1b"
|
||||
OLLAMA_HOST = "http://10.48.200.210:11434"
|
||||
OLLAMA_MODEL = "llama3.1:8b"
|
||||
|
||||
GMAIL_USER = "myronblair@gmail.com"
|
||||
GMAIL_PASS = "demsvdylwweacbcx"
|
||||
@@ -175,6 +175,16 @@ async def _ollama_call(messages: list, system: str = "") -> str:
|
||||
data = await resp.json()
|
||||
return data.get("response", "")
|
||||
|
||||
async def _ollama_vision_call(image_b64: str, prompt: str) -> str:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
f"{OLLAMA_HOST}/api/generate",
|
||||
json={"model": "llava:7b", "prompt": prompt, "images": [image_b64], "stream": False},
|
||||
timeout=aiohttp.ClientTimeout(total=120),
|
||||
) as resp:
|
||||
data = await resp.json()
|
||||
return data.get("response", "").strip()
|
||||
|
||||
async def handle_llm(payload: dict) -> dict:
|
||||
message = payload.get("message", "")
|
||||
system = payload.get("system", "You are JARVIS, an Iron Man-style AI assistant.")
|
||||
@@ -658,10 +668,16 @@ async def handle_screenshot(payload: dict) -> dict:
|
||||
height = result.get("height", 0)
|
||||
file_size = result.get("file_size", 0)
|
||||
|
||||
# Run Claude vision analysis if we have an image
|
||||
# Run vision analysis if we have an image — llava first, Claude fallback
|
||||
analysis = ""
|
||||
provider_used = ""
|
||||
if do_analyze and image_b64:
|
||||
try:
|
||||
analysis = await _ollama_vision_call(image_b64, analyze_prompt)
|
||||
provider_used = "ollama:llava"
|
||||
log.info(f"[VISION] llava analysis complete ({len(analysis)} chars)")
|
||||
except Exception as e:
|
||||
log.warning(f"[VISION] llava failed: {e} — falling back to Claude")
|
||||
try:
|
||||
import anthropic
|
||||
client = anthropic.AsyncAnthropic(api_key=CLAUDE_API_KEY)
|
||||
@@ -679,17 +695,17 @@ async def handle_screenshot(payload: dict) -> dict:
|
||||
)
|
||||
analysis = msg.content[0].text if msg.content else ""
|
||||
provider_used = "claude"
|
||||
log.info(f"[VISION] Claude analysis complete ({len(analysis)} chars)")
|
||||
except Exception as e:
|
||||
log.warning(f"[VISION] Claude vision failed: {e}")
|
||||
analysis = f"Vision analysis unavailable: {e}"
|
||||
log.info(f"[VISION] Claude fallback analysis complete ({len(analysis)} chars)")
|
||||
except Exception as e2:
|
||||
log.warning(f"[VISION] Claude fallback also failed: {e2}")
|
||||
analysis = f"Vision analysis unavailable: {e2}"
|
||||
elif do_analyze and not image_b64 and result.get("snapshot_type") == "text":
|
||||
# Text-only sysinfo snapshot — summarize with LLM
|
||||
# Text-only sysinfo snapshot — summarize with Ollama
|
||||
try:
|
||||
snap_text = json.dumps(result, indent=2)[:3000]
|
||||
prompt = f"Summarize this server system snapshot for JARVIS. Highlight any concerns:\n\n{snap_text}"
|
||||
analysis = await llm_call([{"role": "user", "content": prompt}], "groq")
|
||||
provider_used = "groq"
|
||||
analysis = await llm_call([{"role": "user", "content": prompt}], "ollama")
|
||||
provider_used = "ollama"
|
||||
except Exception as e:
|
||||
analysis = f"Analysis unavailable: {e}"
|
||||
|
||||
@@ -742,8 +758,22 @@ async def handle_vision(payload: dict) -> dict:
|
||||
if not image_b64:
|
||||
raise ValueError("No image data provided")
|
||||
|
||||
log.info(f"[VISION] Analysis: screenshot_id={screenshot_id} agent={hostname}")
|
||||
log.info(f"[VISION] Analysis: screenshot_id={screenshot_id} agent={hostname} provider={provider}")
|
||||
|
||||
analysis = ""
|
||||
provider_used = provider
|
||||
|
||||
if provider == "ollama" or provider == "llava":
|
||||
analysis = await _ollama_vision_call(image_b64, prompt)
|
||||
provider_used = "ollama:llava"
|
||||
else:
|
||||
# Default: llava first, Claude fallback
|
||||
try:
|
||||
analysis = await _ollama_vision_call(image_b64, prompt)
|
||||
provider_used = "ollama:llava"
|
||||
log.info(f"[VISION] llava analysis complete ({len(analysis)} chars)")
|
||||
except Exception as e:
|
||||
log.warning(f"[VISION] llava failed: {e} — falling back to Claude")
|
||||
try:
|
||||
import anthropic
|
||||
client = anthropic.AsyncAnthropic(api_key=CLAUDE_API_KEY)
|
||||
@@ -760,14 +790,15 @@ async def handle_vision(payload: dict) -> dict:
|
||||
}],
|
||||
)
|
||||
analysis = msg.content[0].text if msg.content else ""
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"Vision analysis failed: {e}")
|
||||
provider_used = "claude"
|
||||
except Exception as e2:
|
||||
raise RuntimeError(f"Vision analysis failed (llava: {e}, claude: {e2})")
|
||||
|
||||
# Update stored screenshot if we have an ID
|
||||
if screenshot_id:
|
||||
await db_execute(
|
||||
"UPDATE agent_screenshots SET vision_analysis=%s, vision_provider=%s WHERE id=%s",
|
||||
(analysis, "claude", int(screenshot_id))
|
||||
(analysis, provider_used, int(screenshot_id))
|
||||
)
|
||||
|
||||
return {
|
||||
@@ -775,7 +806,7 @@ async def handle_vision(payload: dict) -> dict:
|
||||
"screenshot_id": screenshot_id,
|
||||
"prompt": prompt,
|
||||
"analysis": analysis,
|
||||
"provider": "claude",
|
||||
"provider": provider_used,
|
||||
}
|
||||
|
||||
|
||||
@@ -1022,7 +1053,7 @@ async def guardian_loop() -> None:
|
||||
"for Myron. Be direct about severity and what action to take. "
|
||||
"No markdown, no headers."
|
||||
)
|
||||
ai_msg = await llm_call([{"role": "user", "content": ai_prompt}], "groq")
|
||||
ai_msg = await llm_call([{"role": "user", "content": ai_prompt}], "ollama")
|
||||
# Update the most recent guardian event with AI analysis
|
||||
await db_execute(
|
||||
"""UPDATE guardian_events SET ai_analysis=%s
|
||||
@@ -1051,7 +1082,7 @@ async def _guardian_inject_chat(message: str) -> None:
|
||||
"""Write a proactive JARVIS message into the conversations table so the HUD picks it up."""
|
||||
try:
|
||||
await db_execute(
|
||||
"""INSERT INTO conversations (session_id, role, message, created_at)
|
||||
"""INSERT INTO conversations (session_id, role, content, created_at)
|
||||
VALUES ('guardian', 'assistant', %s, NOW())""",
|
||||
(message,)
|
||||
)
|
||||
@@ -1065,7 +1096,7 @@ async def handle_sitrep(payload: dict) -> dict:
|
||||
payload: { detail: brief|full, provider: groq }
|
||||
"""
|
||||
detail = payload.get("detail", "full")
|
||||
provider = payload.get("provider", "groq")
|
||||
provider = payload.get("provider", "ollama")
|
||||
|
||||
log.info(f"[GUARDIAN] SITREP requested (detail={detail})")
|
||||
|
||||
@@ -1806,7 +1837,7 @@ Keep the tone confident and action-oriented. Format with clear sections. Under 4
|
||||
|
||||
# Store in conversations so HUD can surface it
|
||||
await db_execute(
|
||||
"INSERT INTO conversations (session_id, role, message, created_at) VALUES ('guardian','assistant',%s,NOW())",
|
||||
"INSERT INTO conversations (session_id, role, content, created_at) VALUES ('guardian','assistant',%s,NOW())",
|
||||
(review_text,)
|
||||
)
|
||||
|
||||
@@ -2209,6 +2240,31 @@ async def lifespan(app: FastAPI):
|
||||
log.info(f"◈ JARVIS Arc Reactor v{VERSION} starting on {HOST}:{PORT}")
|
||||
await get_pool()
|
||||
await db_execute("UPDATE arc_status SET started_at=NOW(), last_heartbeat=NOW(), version=%s WHERE id=1", (VERSION,))
|
||||
await db_execute("""CREATE TABLE IF NOT EXISTS guardian_config (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
key_name VARCHAR(64) NOT NULL,
|
||||
value VARCHAR(255) NOT NULL DEFAULT '',
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_key (key_name)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci""")
|
||||
await db_execute("""CREATE TABLE IF NOT EXISTS guardian_events (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
event_type VARCHAR(64) NOT NULL,
|
||||
severity ENUM('info','warning','critical') NOT NULL DEFAULT 'info',
|
||||
agent_id VARCHAR(64) NOT NULL DEFAULT '',
|
||||
hostname VARCHAR(128) NOT NULL DEFAULT '',
|
||||
metric VARCHAR(64) NOT NULL DEFAULT '',
|
||||
value FLOAT NOT NULL DEFAULT 0,
|
||||
threshold FLOAT NOT NULL DEFAULT 0,
|
||||
message TEXT NOT NULL,
|
||||
ai_analysis TEXT NOT NULL DEFAULT '',
|
||||
acknowledged TINYINT(1) NOT NULL DEFAULT 0,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
KEY idx_severity (severity),
|
||||
KEY idx_ack (acknowledged),
|
||||
KEY idx_created (created_at),
|
||||
KEY idx_agent (agent_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci""")
|
||||
asyncio.create_task(job_poller())
|
||||
asyncio.create_task(heartbeat_loop())
|
||||
asyncio.create_task(guardian_loop())
|
||||
@@ -2728,13 +2784,13 @@ async def guardian_chat_events(since: str = ""):
|
||||
"""Return proactive guardian messages injected into conversations."""
|
||||
if since:
|
||||
rows = await db_fetchall(
|
||||
"SELECT id, message, created_at FROM conversations "
|
||||
"SELECT id, content AS message, created_at FROM conversations "
|
||||
"WHERE session_id='guardian' AND created_at > %s ORDER BY created_at ASC",
|
||||
(since,)
|
||||
)
|
||||
else:
|
||||
rows = await db_fetchall(
|
||||
"SELECT id, message, created_at FROM conversations "
|
||||
"SELECT id, content AS message, created_at FROM conversations "
|
||||
"WHERE session_id='guardian' ORDER BY created_at DESC LIMIT 10"
|
||||
)
|
||||
return rows or []
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
fastapi
|
||||
uvicorn[standard]
|
||||
aiomysql
|
||||
aiohttp
|
||||
anthropic
|
||||
trafilatura
|
||||
@@ -242,7 +242,9 @@ if ($action) {
|
||||
$search = strtolower(trim($_GET['search'] ?? ''));
|
||||
$skipDomains = ['sensor','binary_sensor','button','update','select','number',
|
||||
'device_tracker','event','image','person','zone','tts','conversation',
|
||||
'assist_satellite','input_button'];
|
||||
'assist_satellite','input_button','media_player','scene','water_heater',
|
||||
'alarm_control_panel','automation','script','calendar','notify',
|
||||
'weather','camera','siren','remote','todo','lawn_mower'];
|
||||
$skipKeywords = ['pre_release','_record','_ftp_','_push_','_hub_ringtone',
|
||||
'_siren_on','_email_on','_manual_record','_infrared_',
|
||||
'do_not_disturb','matter_server','zerotier','mariadb',
|
||||
@@ -548,8 +550,22 @@ if ($action) {
|
||||
j(['ok'=>true,'msg'=>ucwords(str_replace('_',' ',$wId)).' triggered']);
|
||||
} else { bad('Unknown cron worker'); }
|
||||
} elseif ($wType === 'daemon' && $wId === 'arc_reactor' && $wAction === 'restart') {
|
||||
shell_exec('pkill -f reactor.py 2>/dev/null; sleep 1; cd /opt/jarvis-arc && source venv/bin/activate && nohup python3 reactor.py >> /home/jarvis.orbishosting.com/logs/arc_reactor.log 2>&1 &');
|
||||
shell_exec('bash -c "mkdir -p /home/jarvis.orbishosting.com/logs; systemctl restart jarvis-arc 2>/dev/null || (pkill -f reactor.py 2>/dev/null; sleep 1; cd /opt/jarvis-arc && source venv/bin/activate && nohup python3 reactor.py >> /home/jarvis.orbishosting.com/logs/arc_reactor.log 2>&1 &)"');
|
||||
j(['ok'=>true,'msg'=>'Arc Reactor restarting']);
|
||||
} elseif ($wType === 'daemon' && $wId === 'arc_reactor' && $wAction === 'setup') {
|
||||
$setupLog = '/home/jarvis.orbishosting.com/logs/arc_reactor.log';
|
||||
$cmd = 'bash -c "'.
|
||||
'mkdir -p /opt/jarvis-arc /home/jarvis.orbishosting.com/logs && '.
|
||||
'cp /var/www/jarvis/deploy/reactor.py /opt/jarvis-arc/reactor.py && '.
|
||||
'cp /var/www/jarvis/deploy/requirements.txt /opt/jarvis-arc/requirements.txt && '.
|
||||
'if [ ! -d /opt/jarvis-arc/venv ]; then python3 -m venv /opt/jarvis-arc/venv; fi && '.
|
||||
'/opt/jarvis-arc/venv/bin/pip install -q -r /opt/jarvis-arc/requirements.txt && '.
|
||||
'pkill -f reactor.py 2>/dev/null; sleep 1 && '.
|
||||
'cd /opt/jarvis-arc && source venv/bin/activate && '.
|
||||
'nohup python3 reactor.py >> '.$setupLog.' 2>&1 &'.
|
||||
'" >> '.$setupLog.' 2>&1';
|
||||
shell_exec($cmd);
|
||||
j(['ok'=>true,'msg'=>'Arc Reactor setup started — check log in ~30s then restart']);
|
||||
} else { bad('Invalid worker action'); }
|
||||
break;
|
||||
case 'arc_status':
|
||||
@@ -2330,7 +2346,10 @@ async function loadWorkers() {
|
||||
<td class="ts">jarvis-do :7474</td>
|
||||
<td>${rdot}${ron?'<span style="color:var(--green)">ONLINE</span>':'<span style="color:var(--red)">OFFLINE</span>'}</td>
|
||||
<td class="ts">${rinfo}</td>
|
||||
<td><button onclick="workerAction('daemon','arc_reactor','restart')" style="${wBtn('red')}">↻ RESTART</button></td>
|
||||
<td style="display:flex;gap:4px">
|
||||
<button onclick="workerAction('daemon','arc_reactor','restart')" style="${wBtn('red')}">↻ RESTART</button>
|
||||
<button onclick="workerAction('daemon','arc_reactor','setup')" style="${wBtn('orange')}">⚙ SETUP</button>
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
async function loadDashboard() {
|
||||
|
||||
+2
-13
@@ -50,20 +50,9 @@ if (!isset($repoMap[$repo])) {
|
||||
}
|
||||
|
||||
$path = $repoMap[$repo];
|
||||
|
||||
// NovaCPX lives on a private VM — the VM polls GitHub every minute via cron
|
||||
// This webhook receipt confirms GitHub delivered the push notification
|
||||
if ($path === '__NOVACPX_VM__') {
|
||||
$commit = $data['after'] ?? 'HEAD';
|
||||
$msg = "[" . date('Y-m-d H:i:s') . "] NovaCPX push by $pusher (commit: $commit) — VM will deploy within 1 min";
|
||||
file_put_contents(DEPLOY_LOG, $msg . "\n", FILE_APPEND | LOCK_EX);
|
||||
echo json_encode(['ok' => true, 'queued' => 'novacpx', 'commit' => $commit]);
|
||||
exit;
|
||||
}
|
||||
$ts = date('Y-m-d H:i:s');
|
||||
|
||||
file_put_contents(DEPLOY_QUEUE, $path . "\n", FILE_APPEND | LOCK_EX);
|
||||
|
||||
$msg = "[" . date('Y-m-d H:i:s') . "] Queued deploy: $repo by $pusher -> $path";
|
||||
file_put_contents(DEPLOY_LOG, $msg . "\n", FILE_APPEND | LOCK_EX);
|
||||
file_put_contents(DEPLOY_LOG, "[$ts] Queued deploy: $repo by $pusher -> $path\n", FILE_APPEND | LOCK_EX);
|
||||
|
||||
echo json_encode(['ok' => true, 'queued' => $repo, 'path' => $path]);
|
||||
|
||||
Reference in New Issue
Block a user