mirror of
https://github.com/myronblair/jarvis
synced 2026-06-30 17:50:23 -05:00
Compare commits
2 Commits
84cd2ded50
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 874f6e8c5c | |||
| 42a82c40cb |
@@ -1,151 +0,0 @@
|
|||||||
#Requires -RunAsAdministrator
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
JARVIS Agent installer for Windows.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
Installs JARVIS Agent as a Windows Service that auto-starts at boot.
|
|
||||||
Requires: PowerShell 5.1+, internet access, and Administrator rights.
|
|
||||||
|
|
||||||
.EXAMPLE
|
|
||||||
# Interactive install (prompts for registration key):
|
|
||||||
irm https://jarvis.orbishosting.com/agent/install-windows.ps1 | iex
|
|
||||||
|
|
||||||
# Silent install with key:
|
|
||||||
$env:JARVIS_REG_KEY='your_key_here'; irm https://jarvis.orbishosting.com/agent/install-windows.ps1 | iex
|
|
||||||
#>
|
|
||||||
|
|
||||||
$ErrorActionPreference = 'Stop'
|
|
||||||
$JARVIS_URL = 'https://jarvis.orbishosting.com'
|
|
||||||
$INSTALL_DIR = 'C:\ProgramData\jarvis-agent'
|
|
||||||
$SERVICE_NAME = 'JARVISAgent'
|
|
||||||
$AGENT_SCRIPT = "$INSTALL_DIR\jarvis-agent-windows.py"
|
|
||||||
$CONFIG_FILE = "$INSTALL_DIR\config.json"
|
|
||||||
|
|
||||||
function Write-Step { param($msg) Write-Host "`n[JARVIS] $msg" -ForegroundColor Cyan }
|
|
||||||
function Write-OK { param($msg) Write-Host " OK: $msg" -ForegroundColor Green }
|
|
||||||
function Write-Fail { param($msg) Write-Host " ERROR: $msg" -ForegroundColor Red; exit 1 }
|
|
||||||
|
|
||||||
Write-Host "`n========================================" -ForegroundColor Yellow
|
|
||||||
Write-Host " JARVIS Agent Installer for Windows" -ForegroundColor Yellow
|
|
||||||
Write-Host "========================================`n" -ForegroundColor Yellow
|
|
||||||
|
|
||||||
# ── Stop existing service if running ─────────────────────────────────────────
|
|
||||||
$existing = Get-Service -Name $SERVICE_NAME -ErrorAction SilentlyContinue
|
|
||||||
if ($existing) {
|
|
||||||
Write-Step "Stopping existing JARVIS Agent service..."
|
|
||||||
if ($existing.Status -eq 'Running') {
|
|
||||||
Stop-Service -Name $SERVICE_NAME -Force
|
|
||||||
Start-Sleep 2
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
& python "$INSTALL_DIR\jarvis-agent-windows.py" remove 2>$null
|
|
||||||
} catch {}
|
|
||||||
Write-OK "Existing service removed."
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Check / install Python ────────────────────────────────────────────────────
|
|
||||||
Write-Step "Checking Python..."
|
|
||||||
$py = Get-Command python -ErrorAction SilentlyContinue
|
|
||||||
if (-not $py) {
|
|
||||||
Write-Host " Python not found. Installing via winget..." -ForegroundColor Yellow
|
|
||||||
if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
|
|
||||||
Write-Fail "winget not available. Please install Python 3.11+ from https://python.org and re-run."
|
|
||||||
}
|
|
||||||
winget install -e --id Python.Python.3.11 --silent --accept-package-agreements --accept-source-agreements
|
|
||||||
$env:PATH = [System.Environment]::GetEnvironmentVariable("PATH","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("PATH","User")
|
|
||||||
$py = Get-Command python -ErrorAction SilentlyContinue
|
|
||||||
if (-not $py) { Write-Fail "Python install failed. Please install manually from https://python.org" }
|
|
||||||
}
|
|
||||||
$pyVersion = & python --version 2>&1
|
|
||||||
Write-OK $pyVersion
|
|
||||||
|
|
||||||
# ── Install pywin32 ───────────────────────────────────────────────────────────
|
|
||||||
Write-Step "Checking pywin32..."
|
|
||||||
$checkWin32 = & python -c "import win32service; print('ok')" 2>&1
|
|
||||||
if ($checkWin32 -ne 'ok') {
|
|
||||||
Write-Host " Installing pywin32..." -ForegroundColor Yellow
|
|
||||||
& python -m pip install --quiet pywin32
|
|
||||||
& python -m pywin32_postinstall -install 2>$null
|
|
||||||
Write-OK "pywin32 installed."
|
|
||||||
} else {
|
|
||||||
Write-OK "pywin32 already installed."
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Create install dir ────────────────────────────────────────────────────────
|
|
||||||
Write-Step "Creating install directory..."
|
|
||||||
New-Item -ItemType Directory -Path $INSTALL_DIR -Force | Out-Null
|
|
||||||
Write-OK $INSTALL_DIR
|
|
||||||
|
|
||||||
# ── Download agent script ─────────────────────────────────────────────────────
|
|
||||||
Write-Step "Downloading JARVIS agent..."
|
|
||||||
try {
|
|
||||||
Invoke-WebRequest -Uri "$JARVIS_URL/agent/jarvis-agent-windows.py" -OutFile $AGENT_SCRIPT -UseBasicParsing
|
|
||||||
Write-OK "Agent downloaded to $AGENT_SCRIPT"
|
|
||||||
} catch {
|
|
||||||
Write-Fail "Failed to download agent: $_"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Get registration key ──────────────────────────────────────────────────────
|
|
||||||
$regKey = $env:JARVIS_REG_KEY
|
|
||||||
if (-not $regKey -and (Test-Path $CONFIG_FILE)) {
|
|
||||||
$existingCfg = Get-Content $CONFIG_FILE | ConvertFrom-Json
|
|
||||||
$regKey = $existingCfg.registration_key
|
|
||||||
if ($regKey) { Write-OK "Using existing registration key from config." }
|
|
||||||
}
|
|
||||||
if (-not $regKey) {
|
|
||||||
$regKey = Read-Host "`n Enter JARVIS registration key"
|
|
||||||
if (-not $regKey) { Write-Fail "Registration key required." }
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Get hostname ──────────────────────────────────────────────────────────────
|
|
||||||
$hostname = $env:COMPUTERNAME
|
|
||||||
$customHostname = $env:JARVIS_HOSTNAME
|
|
||||||
if ($customHostname) { $hostname = $customHostname }
|
|
||||||
|
|
||||||
# ── Write config ──────────────────────────────────────────────────────────────
|
|
||||||
Write-Step "Writing config..."
|
|
||||||
$cfg = @{
|
|
||||||
jarvis_url = $JARVIS_URL
|
|
||||||
registration_key = $regKey
|
|
||||||
hostname = $hostname
|
|
||||||
agent_type = 'windows'
|
|
||||||
ssl_verify = $true
|
|
||||||
poll_interval = 30
|
|
||||||
heartbeat_every = 10
|
|
||||||
update_check_hours = 24
|
|
||||||
watch_services = @('WinDefend', 'Spooler', 'wuauserv')
|
|
||||||
} | ConvertTo-Json -Depth 5
|
|
||||||
$cfg | Out-File -FilePath $CONFIG_FILE -Encoding utf8
|
|
||||||
Write-OK "Config written to $CONFIG_FILE"
|
|
||||||
|
|
||||||
# ── Install Windows Service ───────────────────────────────────────────────────
|
|
||||||
Write-Step "Installing Windows service..."
|
|
||||||
$pyPath = (Get-Command python).Source
|
|
||||||
& $pyPath "$AGENT_SCRIPT" --startup auto install
|
|
||||||
if ($LASTEXITCODE -ne 0) { Write-Fail "Service install failed." }
|
|
||||||
Write-OK "Service '$SERVICE_NAME' installed."
|
|
||||||
|
|
||||||
# ── Start service ─────────────────────────────────────────────────────────────
|
|
||||||
Write-Step "Starting service..."
|
|
||||||
Start-Service -Name $SERVICE_NAME
|
|
||||||
Start-Sleep 3
|
|
||||||
$svc = Get-Service -Name $SERVICE_NAME
|
|
||||||
if ($svc.Status -ne 'Running') { Write-Fail "Service failed to start. Check C:\ProgramData\jarvis-agent\jarvis-agent.log" }
|
|
||||||
Write-OK "Service is running."
|
|
||||||
|
|
||||||
# ── Test connectivity ─────────────────────────────────────────────────────────
|
|
||||||
Write-Step "Testing JARVIS connection..."
|
|
||||||
try {
|
|
||||||
$ping = Invoke-RestMethod -Uri "$JARVIS_URL/api/ping" -TimeoutSec 10
|
|
||||||
Write-OK "JARVIS is online: $($ping.codename)"
|
|
||||||
} catch {
|
|
||||||
Write-Host " WARNING: Could not reach JARVIS at $JARVIS_URL - check connectivity." -ForegroundColor Yellow
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host "`n========================================" -ForegroundColor Green
|
|
||||||
Write-Host " JARVIS Agent installed successfully!" -ForegroundColor Green
|
|
||||||
Write-Host " Hostname: $hostname" -ForegroundColor Green
|
|
||||||
Write-Host " Service: $SERVICE_NAME (auto-start at boot)" -ForegroundColor Green
|
|
||||||
Write-Host " Logs: C:\ProgramData\jarvis-agent\jarvis-agent.log" -ForegroundColor Green
|
|
||||||
Write-Host "========================================`n" -ForegroundColor Green
|
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
JARVIS HA Poller — pulls entity states from Home Assistant REST API
|
|
||||||
and pushes them to JARVIS as a homeassistant-type agent.
|
|
||||||
Runs on VM211 as a systemd service (jarvis-ha-poller).
|
|
||||||
|
|
||||||
Config: /etc/jarvis-agent/ha-poller.json
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import socket
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import urllib.request
|
|
||||||
import urllib.error
|
|
||||||
import ssl
|
|
||||||
from datetime import datetime, timezone
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
CONFIG_PATH = "/etc/jarvis-agent/ha-poller.json"
|
|
||||||
STATE_PATH = "/var/lib/jarvis-agent/ha-poller-state.json"
|
|
||||||
AGENT_VERSION = "1.0"
|
|
||||||
AGENT_ID = "homeassistant_ha"
|
|
||||||
HOSTNAME = "homeassistant"
|
|
||||||
|
|
||||||
# Domains to skip — don't send to JARVIS (saves DB space, keeps UI clean)
|
|
||||||
SKIP_DOMAINS = {
|
|
||||||
'sensor', 'binary_sensor', 'button', 'update', 'select', 'number',
|
|
||||||
'device_tracker', 'event', 'image', 'person', 'zone', 'tts',
|
|
||||||
'conversation', 'assist_satellite', 'input_button', 'media_player',
|
|
||||||
'scene', 'water_heater', 'alarm_control_panel', 'automation',
|
|
||||||
'script', 'calendar', 'notify', 'weather', 'sun', 'persistent_notification',
|
|
||||||
'tag', 'system_health', 'timer', 'counter',
|
|
||||||
'camera', 'siren', 'remote', 'todo', 'lawn_mower',
|
|
||||||
}
|
|
||||||
|
|
||||||
def log(msg: str):
|
|
||||||
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
||||||
print(f"[{ts}] {msg}", flush=True)
|
|
||||||
|
|
||||||
def load_config() -> dict:
|
|
||||||
if not os.path.exists(CONFIG_PATH):
|
|
||||||
print(f"[ERROR] Config not found at {CONFIG_PATH}", flush=True)
|
|
||||||
sys.exit(1)
|
|
||||||
with open(CONFIG_PATH) as f:
|
|
||||||
return json.load(f)
|
|
||||||
|
|
||||||
def load_state() -> dict:
|
|
||||||
if os.path.exists(STATE_PATH):
|
|
||||||
with open(STATE_PATH) as f:
|
|
||||||
return json.load(f)
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def save_state(state: dict):
|
|
||||||
Path(STATE_PATH).parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
with open(STATE_PATH, "w") as f:
|
|
||||||
json.dump(state, f, indent=2)
|
|
||||||
|
|
||||||
def _ssl_ctx(verify: bool):
|
|
||||||
if not verify:
|
|
||||||
ctx = ssl.create_default_context()
|
|
||||||
ctx.check_hostname = False
|
|
||||||
ctx.verify_mode = ssl.CERT_NONE
|
|
||||||
return ctx
|
|
||||||
return None
|
|
||||||
|
|
||||||
def jarvis_post(url: str, payload: dict, headers: dict, ssl_verify: bool, timeout: int = 15) -> dict:
|
|
||||||
body = json.dumps(payload).encode()
|
|
||||||
req = urllib.request.Request(url, data=body, method="POST")
|
|
||||||
req.add_header("Content-Type", "application/json")
|
|
||||||
for k, v in headers.items():
|
|
||||||
req.add_header(k, v)
|
|
||||||
try:
|
|
||||||
ctx = _ssl_ctx(ssl_verify)
|
|
||||||
with urllib.request.urlopen(req, timeout=timeout, context=ctx) as resp:
|
|
||||||
return json.loads(resp.read().decode())
|
|
||||||
except urllib.error.HTTPError as e:
|
|
||||||
return {"error": f"HTTP {e.code}: {e.read().decode()[:200]}"}
|
|
||||||
except Exception as e:
|
|
||||||
return {"error": str(e)}
|
|
||||||
|
|
||||||
def ha_get(url: str, token: str, timeout: int = 15) -> dict | list | None:
|
|
||||||
req = urllib.request.Request(url)
|
|
||||||
req.add_header("Authorization", f"Bearer {token}")
|
|
||||||
req.add_header("Content-Type", "application/json")
|
|
||||||
try:
|
|
||||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
||||||
return json.loads(resp.read().decode())
|
|
||||||
except Exception as e:
|
|
||||||
log(f"HA API error: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def register(cfg: dict, state: dict) -> str:
|
|
||||||
jarvis_url = cfg["jarvis_url"].rstrip("/")
|
|
||||||
ssl_verify = bool(cfg.get("ssl_verify", False))
|
|
||||||
reg_key = cfg["registration_key"]
|
|
||||||
|
|
||||||
log(f"Registering HA poller with JARVIS at {jarvis_url}...")
|
|
||||||
result = jarvis_post(
|
|
||||||
f"{jarvis_url}/api/agent/register",
|
|
||||||
{
|
|
||||||
"hostname": HOSTNAME,
|
|
||||||
"version": AGENT_VERSION,
|
|
||||||
"agent_type": "homeassistant",
|
|
||||||
"ip_address": cfg.get("ha_url", "").split("//")[-1].split(":")[0],
|
|
||||||
"capabilities": ["ha_entities", "ha_state"],
|
|
||||||
"agent_id": AGENT_ID,
|
|
||||||
},
|
|
||||||
{"X-Registration-Key": reg_key},
|
|
||||||
ssl_verify,
|
|
||||||
)
|
|
||||||
if "error" in result:
|
|
||||||
log(f"Registration failed: {result['error']}")
|
|
||||||
return ""
|
|
||||||
api_key = result.get("api_key", "")
|
|
||||||
if api_key:
|
|
||||||
state["api_key"] = api_key
|
|
||||||
state["agent_id"] = AGENT_ID
|
|
||||||
save_state(state)
|
|
||||||
log(f"Registered. agent_id={AGENT_ID}")
|
|
||||||
return api_key
|
|
||||||
|
|
||||||
def push_entities(cfg: dict, api_key: str, entities: list) -> bool:
|
|
||||||
jarvis_url = cfg["jarvis_url"].rstrip("/")
|
|
||||||
ssl_verify = bool(cfg.get("ssl_verify", False))
|
|
||||||
headers = {"X-Agent-Key": api_key}
|
|
||||||
|
|
||||||
# Send in batches of 200
|
|
||||||
batch_size = 200
|
|
||||||
total = len(entities)
|
|
||||||
ok = True
|
|
||||||
for i in range(0, total, batch_size):
|
|
||||||
batch = entities[i:i+batch_size]
|
|
||||||
result = jarvis_post(
|
|
||||||
f"{jarvis_url}/api/agent/ha_state",
|
|
||||||
{"entities": batch},
|
|
||||||
headers,
|
|
||||||
ssl_verify,
|
|
||||||
)
|
|
||||||
if "error" in result:
|
|
||||||
log(f"Push batch {i//batch_size+1} failed: {result['error']}")
|
|
||||||
ok = False
|
|
||||||
return ok
|
|
||||||
|
|
||||||
def heartbeat(cfg: dict, api_key: str) -> bool:
|
|
||||||
jarvis_url = cfg["jarvis_url"].rstrip("/")
|
|
||||||
ssl_verify = bool(cfg.get("ssl_verify", False))
|
|
||||||
result = jarvis_post(
|
|
||||||
f"{jarvis_url}/api/agent/heartbeat",
|
|
||||||
{"version": AGENT_VERSION},
|
|
||||||
{"X-Agent-Key": api_key},
|
|
||||||
ssl_verify,
|
|
||||||
timeout=10,
|
|
||||||
)
|
|
||||||
return "error" not in result
|
|
||||||
|
|
||||||
def fetch_ha_states(cfg: dict) -> list:
|
|
||||||
ha_url = cfg["ha_url"].rstrip("/")
|
|
||||||
token = cfg["ha_token"]
|
|
||||||
states = ha_get(f"{ha_url}/api/states", token)
|
|
||||||
if not states or not isinstance(states, list):
|
|
||||||
return []
|
|
||||||
|
|
||||||
entities = []
|
|
||||||
for s in states:
|
|
||||||
entity_id = s.get("entity_id", "")
|
|
||||||
domain = entity_id.split(".")[0] if "." in entity_id else ""
|
|
||||||
if domain in SKIP_DOMAINS:
|
|
||||||
continue
|
|
||||||
attrs = s.get("attributes", {})
|
|
||||||
# Convert ISO 8601 (e.g. "2026-06-28T21:26:01.922366+00:00") to MySQL datetime
|
|
||||||
lc = s.get("last_changed", "")
|
|
||||||
try:
|
|
||||||
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")
|
|
||||||
|
|
||||||
entities.append({
|
|
||||||
"entity_id": entity_id,
|
|
||||||
"name": attrs.get("friendly_name") or entity_id,
|
|
||||||
"state": s.get("state", ""),
|
|
||||||
"attributes": attrs,
|
|
||||||
"last_changed": lc,
|
|
||||||
})
|
|
||||||
return entities
|
|
||||||
|
|
||||||
def main():
|
|
||||||
cfg = load_config()
|
|
||||||
state = load_state()
|
|
||||||
|
|
||||||
poll_interval = int(cfg.get("poll_interval", 30))
|
|
||||||
heartbeat_every = int(cfg.get("heartbeat_every", 10))
|
|
||||||
|
|
||||||
api_key = state.get("api_key", "")
|
|
||||||
if 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
|
|
||||||
log(f"HA Poller v{AGENT_VERSION} running. Polling HA every {poll_interval}s, heartbeat every {heartbeat_every}s.")
|
|
||||||
|
|
||||||
while True:
|
|
||||||
now = time.time()
|
|
||||||
|
|
||||||
# Heartbeat
|
|
||||||
if not heartbeat(cfg, api_key):
|
|
||||||
log("Heartbeat failed (401?) — re-registering...")
|
|
||||||
state.clear()
|
|
||||||
save_state(state)
|
|
||||||
api_key = register(cfg, state)
|
|
||||||
if not api_key:
|
|
||||||
time.sleep(60)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Push entity states every poll_interval
|
|
||||||
if now - last_push >= poll_interval:
|
|
||||||
entities = fetch_ha_states(cfg)
|
|
||||||
if entities:
|
|
||||||
ok = push_entities(cfg, api_key, entities)
|
|
||||||
if ok:
|
|
||||||
log(f"Pushed {len(entities)} HA entities to JARVIS.")
|
|
||||||
last_push = now
|
|
||||||
else:
|
|
||||||
log("No HA entities fetched (HA down or token invalid?)")
|
|
||||||
|
|
||||||
time.sleep(heartbeat_every)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -184,7 +184,7 @@ function collect_all(): array {
|
|||||||
"jarvis" => "http://jarvis.orbishosting.com:1972",
|
"jarvis" => "http://jarvis.orbishosting.com:1972",
|
||||||
'tomsjavajive' => 'https://tomsjavajive.com',
|
'tomsjavajive' => 'https://tomsjavajive.com',
|
||||||
'epictravelexp'=> 'https://epictravelexpeditions.com',
|
'epictravelexp'=> 'https://epictravelexpeditions.com',
|
||||||
'parkersling' => 'https://parkerslingshotrentals.com',
|
'parkerslingshotrentals' => 'https://parkerslingshotrentals.com',
|
||||||
'orbishosting' => 'https://orbishosting.com',
|
'orbishosting' => 'https://orbishosting.com',
|
||||||
'orbisportal' => 'https://orbis.orbishosting.com',
|
'orbisportal' => 'https://orbis.orbishosting.com',
|
||||||
'tomtomgames' => 'https://tomtomgames.com',
|
'tomtomgames' => 'https://tomtomgames.com',
|
||||||
|
|||||||
@@ -83,8 +83,7 @@ if ($method === 'POST' && $action === 'service') {
|
|||||||
// Serve entities from ha_entities table (real-time agent push data)
|
// Serve entities from ha_entities table (real-time agent push data)
|
||||||
$skipDomains = ['sensor','binary_sensor','button','update','select','number',
|
$skipDomains = ['sensor','binary_sensor','button','update','select','number',
|
||||||
'device_tracker','event','image','person','zone','tts','conversation',
|
'device_tracker','event','image','person','zone','tts','conversation',
|
||||||
'assist_satellite','input_button','media_player','scene','water_heater',
|
'assist_satellite','input_button','media_player','scene','water_heater'];
|
||||||
'alarm_control_panel','automation','script','calendar','notify','weather','camera','siren','remote','todo','lawn_mower'];
|
|
||||||
$skipKeywords = [
|
$skipKeywords = [
|
||||||
// HACS / system toggles
|
// HACS / system toggles
|
||||||
'pre_release','get_hacs','matter_server','zerotier','mariadb',
|
'pre_release','get_hacs','matter_server','zerotier','mariadb',
|
||||||
|
|||||||
+8
-202
@@ -1,9 +1,4 @@
|
|||||||
/*M!999999\- enable the sandbox mode */
|
/*M!999999\- enable the sandbox mode */
|
||||||
-- MariaDB dump 10.19 Distrib 10.11.14-MariaDB, for debian-linux-gnu (x86_64)
|
|
||||||
--
|
|
||||||
-- Host: localhost Database: jarvis_db
|
|
||||||
-- ------------------------------------------------------
|
|
||||||
-- Server version 10.11.14-MariaDB-0ubuntu0.24.04.1
|
|
||||||
|
|
||||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||||
@@ -15,11 +10,6 @@
|
|||||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `agent_commands`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `agent_commands`;
|
DROP TABLE IF EXISTS `agent_commands`;
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
/*!40101 SET character_set_client = utf8mb4 */;
|
||||||
@@ -38,11 +28,6 @@ CREATE TABLE `agent_commands` (
|
|||||||
KEY `idx_created` (`created_at`)
|
KEY `idx_created` (`created_at`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `agent_metrics`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `agent_metrics`;
|
DROP TABLE IF EXISTS `agent_metrics`;
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
/*!40101 SET character_set_client = utf8mb4 */;
|
||||||
@@ -55,13 +40,8 @@ CREATE TABLE `agent_metrics` (
|
|||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
KEY `idx_agent_time` (`agent_id`,`recorded_at`),
|
KEY `idx_agent_time` (`agent_id`,`recorded_at`),
|
||||||
KEY `idx_recorded` (`recorded_at`)
|
KEY `idx_recorded` (`recorded_at`)
|
||||||
) ENGINE=InnoDB AUTO_INCREMENT=29445 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
) ENGINE=InnoDB AUTO_INCREMENT=28329 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `alerts`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `alerts`;
|
DROP TABLE IF EXISTS `alerts`;
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
/*!40101 SET character_set_client = utf8mb4 */;
|
||||||
@@ -78,13 +58,8 @@ CREATE TABLE `alerts` (
|
|||||||
`auto_resolve` tinyint(1) DEFAULT 0,
|
`auto_resolve` tinyint(1) DEFAULT 0,
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
KEY `idx_source_key` (`source_key`)
|
KEY `idx_source_key` (`source_key`)
|
||||||
) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `api_cache`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `api_cache`;
|
DROP TABLE IF EXISTS `api_cache`;
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
/*!40101 SET character_set_client = utf8mb4 */;
|
||||||
@@ -95,80 +70,6 @@ CREATE TABLE `api_cache` (
|
|||||||
PRIMARY KEY (`cache_key`)
|
PRIMARY KEY (`cache_key`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `appointments`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `appointments`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
|
||||||
CREATE TABLE `appointments` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`title` varchar(255) NOT NULL,
|
|
||||||
`description` text DEFAULT NULL,
|
|
||||||
`category` varchar(64) DEFAULT 'personal',
|
|
||||||
`start_at` datetime NOT NULL,
|
|
||||||
`end_at` datetime DEFAULT NULL,
|
|
||||||
`location` varchar(255) DEFAULT NULL,
|
|
||||||
`all_day` tinyint(1) DEFAULT 0,
|
|
||||||
`reminder_min` int(11) DEFAULT 30,
|
|
||||||
`alerted` tinyint(1) DEFAULT 0,
|
|
||||||
`created_at` datetime DEFAULT current_timestamp(),
|
|
||||||
`updated_at` datetime DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
KEY `idx_start` (`start_at`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `arc_jobs`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `arc_jobs`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
|
||||||
CREATE TABLE `arc_jobs` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`job_type` varchar(64) NOT NULL,
|
|
||||||
`payload` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
|
|
||||||
`priority` int(11) DEFAULT 0,
|
|
||||||
`status` enum('queued','running','done','failed','cancelled') DEFAULT 'queued',
|
|
||||||
`result` longtext DEFAULT NULL,
|
|
||||||
`error` varchar(2000) DEFAULT NULL,
|
|
||||||
`created_by` varchar(128) DEFAULT NULL,
|
|
||||||
`created_at` datetime DEFAULT current_timestamp(),
|
|
||||||
`started_at` datetime DEFAULT NULL,
|
|
||||||
`completed_at` datetime DEFAULT NULL,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
KEY `idx_status` (`status`),
|
|
||||||
KEY `idx_created` (`created_at`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `arc_status`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `arc_status`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
|
||||||
CREATE TABLE `arc_status` (
|
|
||||||
`id` int(11) NOT NULL DEFAULT 1,
|
|
||||||
`version` varchar(20) DEFAULT NULL,
|
|
||||||
`started_at` datetime DEFAULT NULL,
|
|
||||||
`last_heartbeat` datetime DEFAULT NULL,
|
|
||||||
`active_jobs` int(11) DEFAULT 0,
|
|
||||||
`jobs_done` int(11) DEFAULT 0,
|
|
||||||
`jobs_failed` int(11) DEFAULT 0,
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `conversations`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `conversations`;
|
DROP TABLE IF EXISTS `conversations`;
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
/*!40101 SET character_set_client = utf8mb4 */;
|
||||||
@@ -184,11 +85,6 @@ CREATE TABLE `conversations` (
|
|||||||
KEY `idx_created` (`created_at`)
|
KEY `idx_created` (`created_at`)
|
||||||
) ENGINE=InnoDB AUTO_INCREMENT=325 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
) ENGINE=InnoDB AUTO_INCREMENT=325 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `ha_entities`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `ha_entities`;
|
DROP TABLE IF EXISTS `ha_entities`;
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
/*!40101 SET character_set_client = utf8mb4 */;
|
||||||
@@ -206,13 +102,8 @@ CREATE TABLE `ha_entities` (
|
|||||||
UNIQUE KEY `uk_agent_entity` (`agent_id`,`entity_id`),
|
UNIQUE KEY `uk_agent_entity` (`agent_id`,`entity_id`),
|
||||||
KEY `idx_domain` (`domain`),
|
KEY `idx_domain` (`domain`),
|
||||||
KEY `idx_updated` (`updated_at`)
|
KEY `idx_updated` (`updated_at`)
|
||||||
) ENGINE=InnoDB AUTO_INCREMENT=8436 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `kb_facts`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `kb_facts`;
|
DROP TABLE IF EXISTS `kb_facts`;
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
/*!40101 SET character_set_client = utf8mb4 */;
|
||||||
@@ -226,13 +117,8 @@ CREATE TABLE `kb_facts` (
|
|||||||
`updated_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
`updated_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
UNIQUE KEY `unique_fact` (`category`,`fact_key`,`host`)
|
UNIQUE KEY `unique_fact` (`category`,`fact_key`,`host`)
|
||||||
) ENGINE=InnoDB AUTO_INCREMENT=39129 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
) ENGINE=InnoDB AUTO_INCREMENT=26088 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `kb_intents`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `kb_intents`;
|
DROP TABLE IF EXISTS `kb_intents`;
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
/*!40101 SET character_set_client = utf8mb4 */;
|
||||||
@@ -249,11 +135,6 @@ CREATE TABLE `kb_intents` (
|
|||||||
PRIMARY KEY (`id`)
|
PRIMARY KEY (`id`)
|
||||||
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `kb_ollama_models`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `kb_ollama_models`;
|
DROP TABLE IF EXISTS `kb_ollama_models`;
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
/*!40101 SET character_set_client = utf8mb4 */;
|
||||||
@@ -268,11 +149,6 @@ CREATE TABLE `kb_ollama_models` (
|
|||||||
UNIQUE KEY `model_name` (`model_name`)
|
UNIQUE KEY `model_name` (`model_name`)
|
||||||
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `kb_preferences`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `kb_preferences`;
|
DROP TABLE IF EXISTS `kb_preferences`;
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
/*!40101 SET character_set_client = utf8mb4 */;
|
||||||
@@ -285,11 +161,6 @@ CREATE TABLE `kb_preferences` (
|
|||||||
UNIQUE KEY `pref_key` (`pref_key`)
|
UNIQUE KEY `pref_key` (`pref_key`)
|
||||||
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `known_commands`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `known_commands`;
|
DROP TABLE IF EXISTS `known_commands`;
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
/*!40101 SET character_set_client = utf8mb4 */;
|
||||||
@@ -302,11 +173,6 @@ CREATE TABLE `known_commands` (
|
|||||||
PRIMARY KEY (`id`)
|
PRIMARY KEY (`id`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `metrics_history`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `metrics_history`;
|
DROP TABLE IF EXISTS `metrics_history`;
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
/*!40101 SET character_set_client = utf8mb4 */;
|
||||||
@@ -318,13 +184,8 @@ CREATE TABLE `metrics_history` (
|
|||||||
`recorded_at` timestamp NULL DEFAULT current_timestamp(),
|
`recorded_at` timestamp NULL DEFAULT current_timestamp(),
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
KEY `idx_metric_time` (`metric_name`,`recorded_at`)
|
KEY `idx_metric_time` (`metric_name`,`recorded_at`)
|
||||||
) ENGINE=InnoDB AUTO_INCREMENT=34771 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
) ENGINE=InnoDB AUTO_INCREMENT=33415 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `network_devices`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `network_devices`;
|
DROP TABLE IF EXISTS `network_devices`;
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
/*!40101 SET character_set_client = utf8mb4 */;
|
||||||
@@ -342,11 +203,6 @@ CREATE TABLE `network_devices` (
|
|||||||
UNIQUE KEY `uk_ip` (`ip`)
|
UNIQUE KEY `uk_ip` (`ip`)
|
||||||
) ENGINE=InnoDB AUTO_INCREMENT=409 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
) ENGINE=InnoDB AUTO_INCREMENT=409 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `registered_agents`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `registered_agents`;
|
DROP TABLE IF EXISTS `registered_agents`;
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
/*!40101 SET character_set_client = utf8mb4 */;
|
||||||
@@ -354,11 +210,10 @@ CREATE TABLE `registered_agents` (
|
|||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
`agent_id` varchar(128) NOT NULL,
|
`agent_id` varchar(128) NOT NULL,
|
||||||
`hostname` varchar(255) NOT NULL,
|
`hostname` varchar(255) NOT NULL,
|
||||||
`agent_type` enum('linux','homeassistant','proxmox','windows','macos') NOT NULL DEFAULT 'linux',
|
`agent_type` enum('linux','homeassistant','proxmox') NOT NULL DEFAULT 'linux',
|
||||||
`ip_address` varchar(45) DEFAULT NULL,
|
`ip_address` varchar(45) DEFAULT NULL,
|
||||||
`api_key` varchar(64) NOT NULL,
|
`api_key` varchar(64) NOT NULL,
|
||||||
`capabilities` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`capabilities`)),
|
`capabilities` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`capabilities`)),
|
||||||
`version` varchar(32) DEFAULT NULL,
|
|
||||||
`last_seen` datetime DEFAULT NULL,
|
`last_seen` datetime DEFAULT NULL,
|
||||||
`status` enum('online','offline','unknown') NOT NULL DEFAULT 'unknown',
|
`status` enum('online','offline','unknown') NOT NULL DEFAULT 'unknown',
|
||||||
`created_at` datetime DEFAULT current_timestamp(),
|
`created_at` datetime DEFAULT current_timestamp(),
|
||||||
@@ -366,56 +221,8 @@ CREATE TABLE `registered_agents` (
|
|||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
UNIQUE KEY `uk_agent_id` (`agent_id`),
|
UNIQUE KEY `uk_agent_id` (`agent_id`),
|
||||||
KEY `idx_status` (`status`)
|
KEY `idx_status` (`status`)
|
||||||
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `tasks`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `tasks`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
|
||||||
CREATE TABLE `tasks` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`title` varchar(255) NOT NULL,
|
|
||||||
`notes` text DEFAULT NULL,
|
|
||||||
`category` varchar(64) DEFAULT 'personal',
|
|
||||||
`priority` enum('urgent','high','normal','low') DEFAULT 'normal',
|
|
||||||
`status` enum('pending','in_progress','done','cancelled') DEFAULT 'pending',
|
|
||||||
`due_date` date DEFAULT NULL,
|
|
||||||
`due_time` time DEFAULT NULL,
|
|
||||||
`completed_at` datetime DEFAULT NULL,
|
|
||||||
`created_at` datetime DEFAULT current_timestamp(),
|
|
||||||
`updated_at` datetime DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
KEY `idx_status_due` (`status`,`due_date`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `usage_patterns`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `usage_patterns`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
|
||||||
CREATE TABLE `usage_patterns` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`intent_name` varchar(64) NOT NULL,
|
|
||||||
`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(),
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `uk_intent_time` (`intent_name`,`hour`,`dow`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `users`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `users`;
|
DROP TABLE IF EXISTS `users`;
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!40101 SET character_set_client = utf8mb4 */;
|
/*!40101 SET character_set_client = utf8mb4 */;
|
||||||
@@ -429,7 +236,7 @@ CREATE TABLE `users` (
|
|||||||
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
UNIQUE KEY `username` (`username`)
|
UNIQUE KEY `username` (`username`)
|
||||||
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
||||||
|
|
||||||
@@ -441,4 +248,3 @@ CREATE TABLE `users` (
|
|||||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||||
|
|
||||||
-- Dump completed on 2026-06-29 20:43:24
|
|
||||||
|
|||||||
@@ -1,151 +1,278 @@
|
|||||||
#Requires -RunAsAdministrator
|
# JARVIS Agent Installer — Windows (PowerShell)
|
||||||
<#
|
# Registers the agent as a proper Windows Service (Win 8.1+, no open window required).
|
||||||
.SYNOPSIS
|
# Requires pywin32. Runs the service as LocalSystem.
|
||||||
JARVIS Agent installer for Windows.
|
#
|
||||||
|
# Run as Administrator:
|
||||||
|
# Set-ExecutionPolicy Bypass -Scope Process
|
||||||
|
# .\install-windows.ps1 -JarvisUrl https://jarvis.orbishosting.com -Key YOUR_KEY
|
||||||
|
#
|
||||||
|
# One-liner (PowerShell as Admin):
|
||||||
|
# irm https://jarvis.orbishosting.com/agent/install-windows.ps1 | iex
|
||||||
|
|
||||||
.DESCRIPTION
|
param(
|
||||||
Installs JARVIS Agent as a Windows Service that auto-starts at boot.
|
[string]$JarvisUrl = "",
|
||||||
Requires: PowerShell 5.1+, internet access, and Administrator rights.
|
[string]$Key = "",
|
||||||
|
[string]$AgentName = ""
|
||||||
|
)
|
||||||
|
|
||||||
.EXAMPLE
|
# param() defaults don't apply when piped through iex — set here as fallback
|
||||||
# Interactive install (prompts for registration key):
|
if (-not $JarvisUrl) { $JarvisUrl = "https://jarvis.orbishosting.com" }
|
||||||
irm https://jarvis.orbishosting.com/agent/install-windows.ps1 | iex
|
if (-not $Key) { $Key = "f846a9aaf7ce9a61742c63c87c4186052a71d2a580c65518" }
|
||||||
|
if (-not $AgentName) { $AgentName = $env:COMPUTERNAME.ToLower() }
|
||||||
|
|
||||||
# Silent install with key:
|
$ErrorActionPreference = "Stop"
|
||||||
$env:JARVIS_REG_KEY='your_key_here'; irm https://jarvis.orbishosting.com/agent/install-windows.ps1 | iex
|
$InstallDir = "C:\ProgramData\jarvis-agent"
|
||||||
#>
|
$AgentScript = "$InstallDir\jarvis-agent-windows.py"
|
||||||
|
$ConfigFile = "$InstallDir\config.json"
|
||||||
|
$ServiceName = "JARVISAgent"
|
||||||
|
$OldTaskName = "JARVIS-Agent" # legacy scheduled-task name
|
||||||
|
|
||||||
$ErrorActionPreference = 'Stop'
|
Write-Host ""
|
||||||
$JARVIS_URL = 'https://jarvis.orbishosting.com'
|
Write-Host " ====================================" -ForegroundColor Cyan
|
||||||
$INSTALL_DIR = 'C:\ProgramData\jarvis-agent'
|
Write-Host " JARVIS Agent Installer v3.1 " -ForegroundColor Cyan
|
||||||
$SERVICE_NAME = 'JARVISAgent'
|
Write-Host " Windows Service Edition " -ForegroundColor Cyan
|
||||||
$AGENT_SCRIPT = "$INSTALL_DIR\jarvis-agent-windows.py"
|
Write-Host " ====================================" -ForegroundColor Cyan
|
||||||
$CONFIG_FILE = "$INSTALL_DIR\config.json"
|
Write-Host ""
|
||||||
|
|
||||||
function Write-Step { param($msg) Write-Host "`n[JARVIS] $msg" -ForegroundColor Cyan }
|
# ── Require admin ──────────────────────────────────────────────────────────────
|
||||||
function Write-OK { param($msg) Write-Host " OK: $msg" -ForegroundColor Green }
|
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
|
||||||
function Write-Fail { param($msg) Write-Host " ERROR: $msg" -ForegroundColor Red; exit 1 }
|
[Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||||
|
Write-Error "Run PowerShell as Administrator and try again."
|
||||||
Write-Host "`n========================================" -ForegroundColor Yellow
|
|
||||||
Write-Host " JARVIS Agent Installer for Windows" -ForegroundColor Yellow
|
|
||||||
Write-Host "========================================`n" -ForegroundColor Yellow
|
|
||||||
|
|
||||||
# ── Stop existing service if running ─────────────────────────────────────────
|
|
||||||
$existing = Get-Service -Name $SERVICE_NAME -ErrorAction SilentlyContinue
|
|
||||||
if ($existing) {
|
|
||||||
Write-Step "Stopping existing JARVIS Agent service..."
|
|
||||||
if ($existing.Status -eq 'Running') {
|
|
||||||
Stop-Service -Name $SERVICE_NAME -Force
|
|
||||||
Start-Sleep 2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── Prompt if not provided ─────────────────────────────────────────────────────
|
||||||
|
$JarvisUrl = $JarvisUrl.TrimEnd("/")
|
||||||
|
|
||||||
|
# ── Find or install Python 3 (system-wide so LocalSystem service can reach it) ─
|
||||||
|
Write-Host "[1/6] Checking for Python 3..." -ForegroundColor Cyan
|
||||||
|
|
||||||
|
$pythonPath = $null
|
||||||
|
|
||||||
|
# System-wide paths — accessible by LocalSystem service account
|
||||||
|
$systemPaths = @(
|
||||||
|
"C:\Program Files\Python313\python.exe",
|
||||||
|
"C:\Program Files\Python312\python.exe",
|
||||||
|
"C:\Program Files\Python311\python.exe",
|
||||||
|
"C:\Program Files\Python310\python.exe",
|
||||||
|
"C:\Program Files\Python39\python.exe",
|
||||||
|
"C:\Python313\python.exe",
|
||||||
|
"C:\Python312\python.exe",
|
||||||
|
"C:\Python311\python.exe",
|
||||||
|
"C:\Python310\python.exe"
|
||||||
|
)
|
||||||
|
|
||||||
|
function Install-PythonSystemWide {
|
||||||
|
# Try winget first (Win 10 1709+ / Win 11)
|
||||||
|
$wingetOk = $false
|
||||||
try {
|
try {
|
||||||
& python "$INSTALL_DIR\jarvis-agent-windows.py" remove 2>$null
|
$null = Get-Command winget -ErrorAction Stop
|
||||||
|
Write-Host " Using winget (system-wide)..." -NoNewline
|
||||||
|
winget install Python.Python.3.12 --silent --scope machine `
|
||||||
|
--accept-package-agreements --accept-source-agreements 2>&1 | Out-Null
|
||||||
|
if ($LASTEXITCODE -eq 0) { $wingetOk = $true; Write-Host " done." -ForegroundColor Green }
|
||||||
} catch {}
|
} catch {}
|
||||||
Write-OK "Existing service removed."
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Check / install Python ────────────────────────────────────────────────────
|
if (-not $wingetOk) {
|
||||||
Write-Step "Checking Python..."
|
# Direct download — works on Win 8.1 without winget
|
||||||
$py = Get-Command python -ErrorAction SilentlyContinue
|
# Python 3.11 explicitly supports Win 8.1+
|
||||||
if (-not $py) {
|
Write-Host " Downloading Python 3.11 (Win 8.1+ compatible)..." -NoNewline
|
||||||
Write-Host " Python not found. Installing via winget..." -ForegroundColor Yellow
|
$pyInstaller = "$env:TEMP\python-installer.exe"
|
||||||
if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
|
$pyUrl = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-amd64.exe"
|
||||||
Write-Fail "winget not available. Please install Python 3.11+ from https://python.org and re-run."
|
|
||||||
}
|
|
||||||
winget install -e --id Python.Python.3.11 --silent --accept-package-agreements --accept-source-agreements
|
|
||||||
$env:PATH = [System.Environment]::GetEnvironmentVariable("PATH","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("PATH","User")
|
|
||||||
$py = Get-Command python -ErrorAction SilentlyContinue
|
|
||||||
if (-not $py) { Write-Fail "Python install failed. Please install manually from https://python.org" }
|
|
||||||
}
|
|
||||||
$pyVersion = & python --version 2>&1
|
|
||||||
Write-OK $pyVersion
|
|
||||||
|
|
||||||
# ── Install pywin32 ───────────────────────────────────────────────────────────
|
|
||||||
Write-Step "Checking pywin32..."
|
|
||||||
$checkWin32 = & python -c "import win32service; print('ok')" 2>&1
|
|
||||||
if ($checkWin32 -ne 'ok') {
|
|
||||||
Write-Host " Installing pywin32..." -ForegroundColor Yellow
|
|
||||||
& python -m pip install --quiet pywin32
|
|
||||||
& python -m pywin32_postinstall -install 2>$null
|
|
||||||
Write-OK "pywin32 installed."
|
|
||||||
} else {
|
|
||||||
Write-OK "pywin32 already installed."
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Create install dir ────────────────────────────────────────────────────────
|
|
||||||
Write-Step "Creating install directory..."
|
|
||||||
New-Item -ItemType Directory -Path $INSTALL_DIR -Force | Out-Null
|
|
||||||
Write-OK $INSTALL_DIR
|
|
||||||
|
|
||||||
# ── Download agent script ─────────────────────────────────────────────────────
|
|
||||||
Write-Step "Downloading JARVIS agent..."
|
|
||||||
try {
|
try {
|
||||||
Invoke-WebRequest -Uri "$JARVIS_URL/agent/jarvis-agent-windows.py" -OutFile $AGENT_SCRIPT -UseBasicParsing
|
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
|
||||||
Write-OK "Agent downloaded to $AGENT_SCRIPT"
|
$wc = New-Object System.Net.WebClient
|
||||||
|
$wc.DownloadFile($pyUrl, $pyInstaller)
|
||||||
|
Write-Host " downloaded." -ForegroundColor Green
|
||||||
} catch {
|
} catch {
|
||||||
Write-Fail "Failed to download agent: $_"
|
Write-Error "Could not download Python. Install from https://python.org choosing 'Install for all users', then re-run."
|
||||||
|
}
|
||||||
|
Write-Host " Installing system-wide (silent)..." -NoNewline
|
||||||
|
$proc = Start-Process -FilePath $pyInstaller `
|
||||||
|
-ArgumentList "/quiet InstallAllUsers=1 PrependPath=1 Include_test=0" `
|
||||||
|
-Wait -PassThru
|
||||||
|
if ($proc.ExitCode -ne 0) {
|
||||||
|
Write-Error "Python installer exited $($proc.ExitCode). Install manually from https://python.org then re-run."
|
||||||
|
}
|
||||||
|
Write-Host " done." -ForegroundColor Green
|
||||||
|
Remove-Item $pyInstaller -ErrorAction SilentlyContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── Get registration key ──────────────────────────────────────────────────────
|
# Refresh PATH after install
|
||||||
$regKey = $env:JARVIS_REG_KEY
|
$env:PATH = [System.Environment]::GetEnvironmentVariable("PATH","Machine") + ";" +
|
||||||
if (-not $regKey -and (Test-Path $CONFIG_FILE)) {
|
[System.Environment]::GetEnvironmentVariable("PATH","User")
|
||||||
$existingCfg = Get-Content $CONFIG_FILE | ConvertFrom-Json
|
|
||||||
$regKey = $existingCfg.registration_key
|
|
||||||
if ($regKey) { Write-OK "Using existing registration key from config." }
|
|
||||||
}
|
|
||||||
if (-not $regKey) {
|
|
||||||
$regKey = Read-Host "`n Enter JARVIS registration key"
|
|
||||||
if (-not $regKey) { Write-Fail "Registration key required." }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── Get hostname ──────────────────────────────────────────────────────────────
|
# ── Search for system-wide Python first ───────────────────────────────────────
|
||||||
$hostname = $env:COMPUTERNAME
|
foreach ($p in $systemPaths) {
|
||||||
$customHostname = $env:JARVIS_HOSTNAME
|
if (Test-Path $p) {
|
||||||
if ($customHostname) { $hostname = $customHostname }
|
try {
|
||||||
|
$ver = & $p --version 2>&1
|
||||||
|
if ("$ver" -match "Python 3") { $pythonPath = $p; break }
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# ── Write config ──────────────────────────────────────────────────────────────
|
# ── Fall back to PATH — but flag if it's per-user ─────────────────────────────
|
||||||
Write-Step "Writing config..."
|
if (-not $pythonPath) {
|
||||||
$cfg = @{
|
foreach ($cmd in @("python", "python3", "py")) {
|
||||||
jarvis_url = $JARVIS_URL
|
try {
|
||||||
registration_key = $regKey
|
$ver = & $cmd --version 2>&1
|
||||||
hostname = $hostname
|
if ("$ver" -match "Python 3") {
|
||||||
agent_type = 'windows'
|
$resolved = (Get-Command $cmd -ErrorAction SilentlyContinue)
|
||||||
|
if ($resolved) { $pythonPath = $resolved.Source; break }
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── If Python is per-user (AppData), install system-wide so LocalSystem can use it ──
|
||||||
|
$needsSystemPython = $false
|
||||||
|
if ($pythonPath -and ($pythonPath -match "AppData")) {
|
||||||
|
Write-Host " Found per-user Python: $pythonPath" -ForegroundColor Yellow
|
||||||
|
Write-Host " LocalSystem service needs system-wide Python. Installing..." -ForegroundColor Yellow
|
||||||
|
$needsSystemPython = $true
|
||||||
|
} elseif (-not $pythonPath) {
|
||||||
|
Write-Host " Python 3 not found. Installing system-wide..." -ForegroundColor Yellow
|
||||||
|
$needsSystemPython = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($needsSystemPython) {
|
||||||
|
Install-PythonSystemWide
|
||||||
|
# Locate the newly installed system-wide Python
|
||||||
|
$pythonPath = $null
|
||||||
|
foreach ($p in $systemPaths) {
|
||||||
|
if (Test-Path $p) {
|
||||||
|
try {
|
||||||
|
$ver = & $p --version 2>&1
|
||||||
|
if ("$ver" -match "Python 3") { $pythonPath = $p; break }
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (-not $pythonPath) {
|
||||||
|
Write-Error "System-wide Python not found after install. Open a new Admin PowerShell and re-run."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host " Python: $pythonPath" -ForegroundColor Green
|
||||||
|
|
||||||
|
# ── Install pywin32 (required for Windows service support) ────────────────────
|
||||||
|
Write-Host "[2/6] Installing pywin32..." -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# pip install
|
||||||
|
$pipResult = & $pythonPath -m pip install --upgrade pywin32 2>&1
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Error "pip install pywin32 failed (exit $LASTEXITCODE).`n$pipResult`nTry manually: $pythonPath -m pip install pywin32"
|
||||||
|
}
|
||||||
|
|
||||||
|
# postinstall registers service runner DLLs — non-fatal if it fails
|
||||||
|
try {
|
||||||
|
$postResult = & $pythonPath -c "import pywin32_postinstall; pywin32_postinstall.install()" 2>&1
|
||||||
|
Write-Host " pywin32 installed." -ForegroundColor Green
|
||||||
|
} catch {
|
||||||
|
Write-Host " pywin32 installed (postinstall skipped — service should still work)." -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Create install directory and download agent ────────────────────────────────
|
||||||
|
Write-Host "[3/6] Downloading agent..." -ForegroundColor Cyan
|
||||||
|
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
|
||||||
|
|
||||||
|
try {
|
||||||
|
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
|
||||||
|
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
|
||||||
|
$wc = New-Object System.Net.WebClient
|
||||||
|
$wc.Headers.Add("User-Agent", "JARVIS-Installer/3.1")
|
||||||
|
$wc.DownloadFile("$JarvisUrl/agent/jarvis-agent-windows.py", $AgentScript)
|
||||||
|
Write-Host " Downloaded to $AgentScript" -ForegroundColor Green
|
||||||
|
} catch {
|
||||||
|
Write-Error "Download failed: $_"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Write config ───────────────────────────────────────────────────────────────
|
||||||
|
Write-Host "[4/6] Writing config..." -ForegroundColor Cyan
|
||||||
|
$agentId = "${AgentName}_windows"
|
||||||
|
$config = [ordered]@{
|
||||||
|
jarvis_url = $JarvisUrl
|
||||||
|
host_header = ""
|
||||||
ssl_verify = $true
|
ssl_verify = $true
|
||||||
|
registration_key = $Key
|
||||||
|
agent_type = "windows"
|
||||||
|
hostname = $AgentName
|
||||||
|
agent_id = $agentId
|
||||||
poll_interval = 30
|
poll_interval = 30
|
||||||
heartbeat_every = 10
|
heartbeat_every = 10
|
||||||
update_check_hours = 24
|
update_check_hours = 24
|
||||||
watch_services = @('WinDefend', 'Spooler', 'wuauserv')
|
watch_services = @("WinDefend", "Spooler")
|
||||||
} | ConvertTo-Json -Depth 5
|
} | ConvertTo-Json -Depth 3
|
||||||
$cfg | Out-File -FilePath $CONFIG_FILE -Encoding utf8
|
|
||||||
Write-OK "Config written to $CONFIG_FILE"
|
|
||||||
|
|
||||||
# ── Install Windows Service ───────────────────────────────────────────────────
|
[System.IO.File]::WriteAllText($ConfigFile, $config, [System.Text.UTF8Encoding]::new($false))
|
||||||
Write-Step "Installing Windows service..."
|
Write-Host " Config: $ConfigFile" -ForegroundColor Green
|
||||||
$pyPath = (Get-Command python).Source
|
|
||||||
& $pyPath "$AGENT_SCRIPT" --startup auto install
|
|
||||||
if ($LASTEXITCODE -ne 0) { Write-Fail "Service install failed." }
|
|
||||||
Write-OK "Service '$SERVICE_NAME' installed."
|
|
||||||
|
|
||||||
# ── Start service ─────────────────────────────────────────────────────────────
|
# ── Remove legacy scheduled task if present ────────────────────────────────────
|
||||||
Write-Step "Starting service..."
|
|
||||||
Start-Service -Name $SERVICE_NAME
|
|
||||||
Start-Sleep 3
|
|
||||||
$svc = Get-Service -Name $SERVICE_NAME
|
|
||||||
if ($svc.Status -ne 'Running') { Write-Fail "Service failed to start. Check C:\ProgramData\jarvis-agent\jarvis-agent.log" }
|
|
||||||
Write-OK "Service is running."
|
|
||||||
|
|
||||||
# ── Test connectivity ─────────────────────────────────────────────────────────
|
|
||||||
Write-Step "Testing JARVIS connection..."
|
|
||||||
try {
|
try {
|
||||||
$ping = Invoke-RestMethod -Uri "$JARVIS_URL/api/ping" -TimeoutSec 10
|
$oldTask = Get-ScheduledTask -TaskName $OldTaskName -ErrorAction SilentlyContinue
|
||||||
Write-OK "JARVIS is online: $($ping.codename)"
|
if ($oldTask) {
|
||||||
} catch {
|
Stop-ScheduledTask -TaskName $OldTaskName -ErrorAction SilentlyContinue
|
||||||
Write-Host " WARNING: Could not reach JARVIS at $JARVIS_URL - check connectivity." -ForegroundColor Yellow
|
Unregister-ScheduledTask -TaskName $OldTaskName -Confirm:$false -ErrorAction SilentlyContinue
|
||||||
|
Write-Host " Removed legacy scheduled task '$OldTaskName'." -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
# ── Register Windows service ───────────────────────────────────────────────────
|
||||||
|
Write-Host "[5/6] Registering Windows service '$ServiceName'..." -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# Stop + remove any existing service first
|
||||||
|
$existing = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
|
||||||
|
if ($existing) {
|
||||||
|
if ($existing.Status -eq "Running") {
|
||||||
|
Write-Host " Stopping existing service..." -NoNewline
|
||||||
|
& $pythonPath $AgentScript stop 2>&1 | Out-Null
|
||||||
|
Start-Sleep -Seconds 3
|
||||||
|
Write-Host " stopped." -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
Write-Host " Removing existing service..." -NoNewline
|
||||||
|
& $pythonPath $AgentScript remove 2>&1 | Out-Null
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
Write-Host " removed." -ForegroundColor Yellow
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host "`n========================================" -ForegroundColor Green
|
# Install the service (--startup auto = start at boot)
|
||||||
Write-Host " JARVIS Agent installed successfully!" -ForegroundColor Green
|
& $pythonPath $AgentScript --startup auto install
|
||||||
Write-Host " Hostname: $hostname" -ForegroundColor Green
|
if ($LASTEXITCODE -ne 0) {
|
||||||
Write-Host " Service: $SERVICE_NAME (auto-start at boot)" -ForegroundColor Green
|
Write-Error "Service registration failed (exit $LASTEXITCODE). Check that pywin32 postinstall completed."
|
||||||
Write-Host " Logs: C:\ProgramData\jarvis-agent\jarvis-agent.log" -ForegroundColor Green
|
}
|
||||||
Write-Host "========================================`n" -ForegroundColor Green
|
|
||||||
|
# Configure failure recovery: restart after 5s, 10s, 30s
|
||||||
|
sc.exe failure $ServiceName reset= 86400 actions= restart/5000/restart/10000/restart/30000 | Out-Null
|
||||||
|
Write-Host " Service registered with auto-restart on failure." -ForegroundColor Green
|
||||||
|
|
||||||
|
# ── Start the service ──────────────────────────────────────────────────────────
|
||||||
|
Write-Host "[6/6] Starting service..." -ForegroundColor Cyan
|
||||||
|
& $pythonPath $AgentScript start
|
||||||
|
Start-Sleep -Seconds 4
|
||||||
|
|
||||||
|
$svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
|
||||||
|
$status = if ($svc) { $svc.Status } else { "NotFound" }
|
||||||
|
$color = if ($status -eq "Running") { "Green" } else { "Yellow" }
|
||||||
|
Write-Host " Service status: $status" -ForegroundColor $color
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " ====================================" -ForegroundColor Green
|
||||||
|
Write-Host " Installation complete! " -ForegroundColor Green
|
||||||
|
Write-Host " ====================================" -ForegroundColor Green
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " Machine : $AgentName ($agentId)" -ForegroundColor White
|
||||||
|
Write-Host " JARVIS : $JarvisUrl" -ForegroundColor White
|
||||||
|
Write-Host " Python : $pythonPath" -ForegroundColor White
|
||||||
|
Write-Host " Logs : $InstallDir\jarvis-agent.log" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " Manage the service:" -ForegroundColor Gray
|
||||||
|
Write-Host " Get-Service JARVISAgent" -ForegroundColor Gray
|
||||||
|
Write-Host " Start-Service JARVISAgent" -ForegroundColor Gray
|
||||||
|
Write-Host " Stop-Service JARVISAgent" -ForegroundColor Gray
|
||||||
|
Write-Host " Restart-Service JARVISAgent" -ForegroundColor Gray
|
||||||
|
Write-Host " Get-Content '$InstallDir\jarvis-agent.log' -Tail 30 -Wait" -ForegroundColor Gray
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " To uninstall:" -ForegroundColor Gray
|
||||||
|
Write-Host " Stop-Service JARVISAgent" -ForegroundColor Gray
|
||||||
|
Write-Host " & '$pythonPath' '$AgentScript' remove" -ForegroundColor Gray
|
||||||
|
Write-Host ""
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ set -e
|
|||||||
|
|
||||||
HOSTNAME_ARG="${1:-$(hostname -s)}"
|
HOSTNAME_ARG="${1:-$(hostname -s)}"
|
||||||
AGENT_TYPE="${2:-linux}"
|
AGENT_TYPE="${2:-linux}"
|
||||||
JARVIS_URL="${JARVIS_URL:-https://jarvis.orbishosting.com}"
|
JARVIS_URL="http://10.48.200.211"
|
||||||
JARVIS_HOST=""
|
JARVIS_HOST=""
|
||||||
INSTALL_DIR="/opt/jarvis-agent"
|
INSTALL_DIR="/opt/jarvis-agent"
|
||||||
CONFIG_DIR="/etc/jarvis-agent"
|
CONFIG_DIR="/etc/jarvis-agent"
|
||||||
@@ -50,7 +50,7 @@ else
|
|||||||
{
|
{
|
||||||
"jarvis_url": "$JARVIS_URL",
|
"jarvis_url": "$JARVIS_URL",
|
||||||
"host_header": "$JARVIS_HOST",
|
"host_header": "$JARVIS_HOST",
|
||||||
"ssl_verify": true,
|
"ssl_verify": false,
|
||||||
"registration_key": "$REG_KEY",
|
"registration_key": "$REG_KEY",
|
||||||
"hostname": "$HOSTNAME_ARG",
|
"hostname": "$HOSTNAME_ARG",
|
||||||
"agent_type": "$AGENT_TYPE",
|
"agent_type": "$AGENT_TYPE",
|
||||||
|
|||||||
@@ -1,236 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
JARVIS HA Poller — pulls entity states from Home Assistant REST API
|
|
||||||
and pushes them to JARVIS as a homeassistant-type agent.
|
|
||||||
Runs on VM211 as a systemd service (jarvis-ha-poller).
|
|
||||||
|
|
||||||
Config: /etc/jarvis-agent/ha-poller.json
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import socket
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import urllib.request
|
|
||||||
import urllib.error
|
|
||||||
import ssl
|
|
||||||
from datetime import datetime, timezone
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
CONFIG_PATH = "/etc/jarvis-agent/ha-poller.json"
|
|
||||||
STATE_PATH = "/var/lib/jarvis-agent/ha-poller-state.json"
|
|
||||||
AGENT_VERSION = "1.0"
|
|
||||||
AGENT_ID = "homeassistant_ha"
|
|
||||||
HOSTNAME = "homeassistant"
|
|
||||||
|
|
||||||
# Domains to skip — don't send to JARVIS (saves DB space, keeps UI clean)
|
|
||||||
SKIP_DOMAINS = {
|
|
||||||
'sensor', 'binary_sensor', 'button', 'update', 'select', 'number',
|
|
||||||
'device_tracker', 'event', 'image', 'person', 'zone', 'tts',
|
|
||||||
'conversation', 'assist_satellite', 'input_button', 'media_player',
|
|
||||||
'scene', 'water_heater', 'alarm_control_panel', 'automation',
|
|
||||||
'script', 'calendar', 'notify', 'weather', 'sun', 'persistent_notification',
|
|
||||||
'tag', 'system_health', 'timer', 'counter',
|
|
||||||
'camera', 'siren', 'remote', 'todo', 'lawn_mower',
|
|
||||||
}
|
|
||||||
|
|
||||||
def log(msg: str):
|
|
||||||
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
||||||
print(f"[{ts}] {msg}", flush=True)
|
|
||||||
|
|
||||||
def load_config() -> dict:
|
|
||||||
if not os.path.exists(CONFIG_PATH):
|
|
||||||
print(f"[ERROR] Config not found at {CONFIG_PATH}", flush=True)
|
|
||||||
sys.exit(1)
|
|
||||||
with open(CONFIG_PATH) as f:
|
|
||||||
return json.load(f)
|
|
||||||
|
|
||||||
def load_state() -> dict:
|
|
||||||
if os.path.exists(STATE_PATH):
|
|
||||||
with open(STATE_PATH) as f:
|
|
||||||
return json.load(f)
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def save_state(state: dict):
|
|
||||||
Path(STATE_PATH).parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
with open(STATE_PATH, "w") as f:
|
|
||||||
json.dump(state, f, indent=2)
|
|
||||||
|
|
||||||
def _ssl_ctx(verify: bool):
|
|
||||||
if not verify:
|
|
||||||
ctx = ssl.create_default_context()
|
|
||||||
ctx.check_hostname = False
|
|
||||||
ctx.verify_mode = ssl.CERT_NONE
|
|
||||||
return ctx
|
|
||||||
return None
|
|
||||||
|
|
||||||
def jarvis_post(url: str, payload: dict, headers: dict, ssl_verify: bool, timeout: int = 15) -> dict:
|
|
||||||
body = json.dumps(payload).encode()
|
|
||||||
req = urllib.request.Request(url, data=body, method="POST")
|
|
||||||
req.add_header("Content-Type", "application/json")
|
|
||||||
for k, v in headers.items():
|
|
||||||
req.add_header(k, v)
|
|
||||||
try:
|
|
||||||
ctx = _ssl_ctx(ssl_verify)
|
|
||||||
with urllib.request.urlopen(req, timeout=timeout, context=ctx) as resp:
|
|
||||||
return json.loads(resp.read().decode())
|
|
||||||
except urllib.error.HTTPError as e:
|
|
||||||
return {"error": f"HTTP {e.code}: {e.read().decode()[:200]}"}
|
|
||||||
except Exception as e:
|
|
||||||
return {"error": str(e)}
|
|
||||||
|
|
||||||
def ha_get(url: str, token: str, timeout: int = 15) -> dict | list | None:
|
|
||||||
req = urllib.request.Request(url)
|
|
||||||
req.add_header("Authorization", f"Bearer {token}")
|
|
||||||
req.add_header("Content-Type", "application/json")
|
|
||||||
try:
|
|
||||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
||||||
return json.loads(resp.read().decode())
|
|
||||||
except Exception as e:
|
|
||||||
log(f"HA API error: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def register(cfg: dict, state: dict) -> str:
|
|
||||||
jarvis_url = cfg["jarvis_url"].rstrip("/")
|
|
||||||
ssl_verify = bool(cfg.get("ssl_verify", False))
|
|
||||||
reg_key = cfg["registration_key"]
|
|
||||||
|
|
||||||
log(f"Registering HA poller with JARVIS at {jarvis_url}...")
|
|
||||||
result = jarvis_post(
|
|
||||||
f"{jarvis_url}/api/agent/register",
|
|
||||||
{
|
|
||||||
"hostname": HOSTNAME,
|
|
||||||
"version": AGENT_VERSION,
|
|
||||||
"agent_type": "homeassistant",
|
|
||||||
"ip_address": cfg.get("ha_url", "").split("//")[-1].split(":")[0],
|
|
||||||
"capabilities": ["ha_entities", "ha_state"],
|
|
||||||
"agent_id": AGENT_ID,
|
|
||||||
},
|
|
||||||
{"X-Registration-Key": reg_key},
|
|
||||||
ssl_verify,
|
|
||||||
)
|
|
||||||
if "error" in result:
|
|
||||||
log(f"Registration failed: {result['error']}")
|
|
||||||
return ""
|
|
||||||
api_key = result.get("api_key", "")
|
|
||||||
if api_key:
|
|
||||||
state["api_key"] = api_key
|
|
||||||
state["agent_id"] = AGENT_ID
|
|
||||||
save_state(state)
|
|
||||||
log(f"Registered. agent_id={AGENT_ID}")
|
|
||||||
return api_key
|
|
||||||
|
|
||||||
def push_entities(cfg: dict, api_key: str, entities: list) -> bool:
|
|
||||||
jarvis_url = cfg["jarvis_url"].rstrip("/")
|
|
||||||
ssl_verify = bool(cfg.get("ssl_verify", False))
|
|
||||||
headers = {"X-Agent-Key": api_key}
|
|
||||||
|
|
||||||
# Send in batches of 200
|
|
||||||
batch_size = 200
|
|
||||||
total = len(entities)
|
|
||||||
ok = True
|
|
||||||
for i in range(0, total, batch_size):
|
|
||||||
batch = entities[i:i+batch_size]
|
|
||||||
result = jarvis_post(
|
|
||||||
f"{jarvis_url}/api/agent/ha_state",
|
|
||||||
{"entities": batch},
|
|
||||||
headers,
|
|
||||||
ssl_verify,
|
|
||||||
)
|
|
||||||
if "error" in result:
|
|
||||||
log(f"Push batch {i//batch_size+1} failed: {result['error']}")
|
|
||||||
ok = False
|
|
||||||
return ok
|
|
||||||
|
|
||||||
def heartbeat(cfg: dict, api_key: str) -> bool:
|
|
||||||
jarvis_url = cfg["jarvis_url"].rstrip("/")
|
|
||||||
ssl_verify = bool(cfg.get("ssl_verify", False))
|
|
||||||
result = jarvis_post(
|
|
||||||
f"{jarvis_url}/api/agent/heartbeat",
|
|
||||||
{"version": AGENT_VERSION},
|
|
||||||
{"X-Agent-Key": api_key},
|
|
||||||
ssl_verify,
|
|
||||||
timeout=10,
|
|
||||||
)
|
|
||||||
return "error" not in result
|
|
||||||
|
|
||||||
def fetch_ha_states(cfg: dict) -> list:
|
|
||||||
ha_url = cfg["ha_url"].rstrip("/")
|
|
||||||
token = cfg["ha_token"]
|
|
||||||
states = ha_get(f"{ha_url}/api/states", token)
|
|
||||||
if not states or not isinstance(states, list):
|
|
||||||
return []
|
|
||||||
|
|
||||||
entities = []
|
|
||||||
for s in states:
|
|
||||||
entity_id = s.get("entity_id", "")
|
|
||||||
domain = entity_id.split(".")[0] if "." in entity_id else ""
|
|
||||||
if domain in SKIP_DOMAINS:
|
|
||||||
continue
|
|
||||||
attrs = s.get("attributes", {})
|
|
||||||
# Convert ISO 8601 (e.g. "2026-06-28T21:26:01.922366+00:00") to MySQL datetime
|
|
||||||
lc = s.get("last_changed", "")
|
|
||||||
try:
|
|
||||||
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")
|
|
||||||
|
|
||||||
entities.append({
|
|
||||||
"entity_id": entity_id,
|
|
||||||
"name": attrs.get("friendly_name") or entity_id,
|
|
||||||
"state": s.get("state", ""),
|
|
||||||
"attributes": attrs,
|
|
||||||
"last_changed": lc,
|
|
||||||
})
|
|
||||||
return entities
|
|
||||||
|
|
||||||
def main():
|
|
||||||
cfg = load_config()
|
|
||||||
state = load_state()
|
|
||||||
|
|
||||||
poll_interval = int(cfg.get("poll_interval", 30))
|
|
||||||
heartbeat_every = int(cfg.get("heartbeat_every", 10))
|
|
||||||
|
|
||||||
api_key = state.get("api_key", "")
|
|
||||||
if 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
|
|
||||||
log(f"HA Poller v{AGENT_VERSION} running. Polling HA every {poll_interval}s, heartbeat every {heartbeat_every}s.")
|
|
||||||
|
|
||||||
while True:
|
|
||||||
now = time.time()
|
|
||||||
|
|
||||||
# Heartbeat
|
|
||||||
if not heartbeat(cfg, api_key):
|
|
||||||
log("Heartbeat failed (401?) — re-registering...")
|
|
||||||
state.clear()
|
|
||||||
save_state(state)
|
|
||||||
api_key = register(cfg, state)
|
|
||||||
if not api_key:
|
|
||||||
time.sleep(60)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Push entity states every poll_interval
|
|
||||||
if now - last_push >= poll_interval:
|
|
||||||
entities = fetch_ha_states(cfg)
|
|
||||||
if entities:
|
|
||||||
ok = push_entities(cfg, api_key, entities)
|
|
||||||
if ok:
|
|
||||||
log(f"Pushed {len(entities)} HA entities to JARVIS.")
|
|
||||||
last_push = now
|
|
||||||
else:
|
|
||||||
log("No HA entities fetched (HA down or token invalid?)")
|
|
||||||
|
|
||||||
time.sleep(heartbeat_every)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
+2
-13
@@ -50,20 +50,9 @@ if (!isset($repoMap[$repo])) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$path = $repoMap[$repo];
|
$path = $repoMap[$repo];
|
||||||
|
$ts = date('Y-m-d H:i:s');
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
file_put_contents(DEPLOY_QUEUE, $path . "\n", FILE_APPEND | LOCK_EX);
|
file_put_contents(DEPLOY_QUEUE, $path . "\n", FILE_APPEND | LOCK_EX);
|
||||||
|
file_put_contents(DEPLOY_LOG, "[$ts] Queued deploy: $repo by $pusher -> $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);
|
|
||||||
|
|
||||||
echo json_encode(['ok' => true, 'queued' => $repo, 'path' => $path]);
|
echo json_encode(['ok' => true, 'queued' => $repo, 'path' => $path]);
|
||||||
|
|||||||
Reference in New Issue
Block a user