Add home-security-agent with PostgreSQL persistence for dashboard.
The autonomous agent writes all observations to agent.* tables consumed by Homelab Command on port 8765. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env python3
|
||||
"""EL-KADI Home Security Agent — autonome loop."""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
# Package root on path
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv(ROOT / ".env")
|
||||
|
||||
from agent.brain import decide, should_notify
|
||||
from agent.observer import load_yaml, run_observation
|
||||
from agent.pg_store import (
|
||||
persist_incident as pg_record_incident,
|
||||
persist_observation,
|
||||
was_notified_recently_pg,
|
||||
)
|
||||
from agent.state import log_run, record_incident, was_notified_recently
|
||||
from agent.telegram_notify import format_alert, send_message
|
||||
|
||||
|
||||
def _record_incident(
|
||||
run_id,
|
||||
fingerprint: str,
|
||||
severity: str,
|
||||
title: str,
|
||||
body: str,
|
||||
notified: bool,
|
||||
) -> None:
|
||||
record_incident(fingerprint, severity, title, body, notified)
|
||||
pg_record_incident(run_id, fingerprint, severity, title, body, notified)
|
||||
|
||||
|
||||
def run_once() -> int:
|
||||
policies = load_yaml("policies.yaml")
|
||||
report = run_observation()
|
||||
decision = decide(report)
|
||||
log_run(decision.title, report.to_dict())
|
||||
run_id = persist_observation(report, decision)
|
||||
|
||||
print(f"[{report.timestamp}] {decision.severity}: {decision.title} (alert={decision.alert})")
|
||||
if run_id:
|
||||
print(f" → PostgreSQL run #{run_id}")
|
||||
|
||||
if not should_notify(decision, policies):
|
||||
if decision.alert:
|
||||
_record_incident(run_id, decision.fingerprint, decision.severity, decision.title, decision.body, False)
|
||||
return 0
|
||||
|
||||
dedupe = int(policies.get("dedupe_minutes", 30))
|
||||
if was_notified_recently(decision.fingerprint, dedupe) or was_notified_recently_pg(
|
||||
decision.fingerprint, dedupe
|
||||
):
|
||||
print(f" dedupe skip ({decision.fingerprint})")
|
||||
return 0
|
||||
|
||||
text = format_alert(decision.severity, decision.title, decision.body, decision.actions)
|
||||
if send_message(text):
|
||||
_record_incident(run_id, decision.fingerprint, decision.severity, decision.title, decision.body, True)
|
||||
print(" → Telegram verzonden")
|
||||
return 0
|
||||
|
||||
print(" → Telegram mislukt (check TELEGRAM_BOT_TOKEN / TELEGRAM_CHAT_ID)")
|
||||
_record_incident(run_id, decision.fingerprint, decision.severity, decision.title, decision.body, False)
|
||||
return 1
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "once":
|
||||
sys.exit(run_once())
|
||||
|
||||
policies = load_yaml("policies.yaml")
|
||||
interval = int(policies.get("interval_seconds", 300))
|
||||
print(f"EL-KADI Security Agent — loop elke {interval}s (Ctrl+C stop)")
|
||||
while True:
|
||||
try:
|
||||
run_once()
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except Exception as e:
|
||||
print(f"run error: {e}")
|
||||
time.sleep(interval)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user