Network Anomaly Agent (secubox-network-anomaly): - 5 detection modules: bandwidth, connection flood, port scan, DNS, protocol - EMA-based baseline comparison - LocalAI integration for threat assessment - network-anomalyctl CLI LocalRecall Memory System (secubox-localrecall): - Persistent memory for AI agents - Categories: threats, decisions, patterns, configs, conversations - EMA-based importance scoring - LocalAI integration for summarization - localrecallctl CLI with 13 commands AI Insights Dashboard (luci-app-ai-insights): - Unified view across all AI agents - Security posture scoring (0-100) - Agent status grid with alert counts - Aggregated alerts from all agents - Run All Agents and AI Analysis actions LuCI Dashboards: - luci-app-network-anomaly with real-time stats - luci-app-localrecall with memory management Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
221 lines
6.4 KiB
Bash
221 lines
6.4 KiB
Bash
#!/bin/sh
|
|
# LocalRecall Memory RPCD Handler
|
|
|
|
. /usr/share/libubox/jshn.sh
|
|
|
|
CONFIG="localrecall"
|
|
STORAGE_DIR="/var/lib/localrecall"
|
|
MEMORIES_FILE="$STORAGE_DIR/memories.json"
|
|
|
|
# Source memory library
|
|
[ -f /usr/lib/localrecall/memory.sh ] && . /usr/lib/localrecall/memory.sh
|
|
[ -f /usr/lib/localrecall/ai.sh ] && . /usr/lib/localrecall/ai.sh
|
|
|
|
log_info() { logger -t localrecall-rpcd "$*"; }
|
|
|
|
uci_get() { uci -q get "${CONFIG}.$1"; }
|
|
|
|
case "$1" in
|
|
list)
|
|
cat <<'EOF'
|
|
{
|
|
"status": {},
|
|
"get_memories": {"category": "string", "limit": 50},
|
|
"search": {"query": "string", "limit": 20},
|
|
"stats": {},
|
|
"add": {"category": "string", "content": "string", "agent": "string", "importance": 5},
|
|
"delete": {"id": "string"},
|
|
"cleanup": {},
|
|
"summarize": {"category": "string"},
|
|
"export": {},
|
|
"import": {"data": "string"}
|
|
}
|
|
EOF
|
|
;;
|
|
|
|
call)
|
|
case "$2" in
|
|
status)
|
|
enabled=$(uci_get main.enabled)
|
|
storage=$(uci_get main.storage_path)
|
|
max_mem=$(uci_get main.max_memories)
|
|
retention=$(uci_get main.retention_days)
|
|
|
|
total=0
|
|
threats=0
|
|
decisions=0
|
|
patterns=0
|
|
configs=0
|
|
convs=0
|
|
|
|
if [ -f "$MEMORIES_FILE" ]; then
|
|
total=$(jsonfilter -i "$MEMORIES_FILE" -e '@[*]' 2>/dev/null | wc -l)
|
|
threats=$(jsonfilter -i "$MEMORIES_FILE" -e "@[@.category='threats']" 2>/dev/null | wc -l)
|
|
decisions=$(jsonfilter -i "$MEMORIES_FILE" -e "@[@.category='decisions']" 2>/dev/null | wc -l)
|
|
patterns=$(jsonfilter -i "$MEMORIES_FILE" -e "@[@.category='patterns']" 2>/dev/null | wc -l)
|
|
configs=$(jsonfilter -i "$MEMORIES_FILE" -e "@[@.category='configs']" 2>/dev/null | wc -l)
|
|
convs=$(jsonfilter -i "$MEMORIES_FILE" -e "@[@.category='conversations']" 2>/dev/null | wc -l)
|
|
fi
|
|
|
|
localai_status="offline"
|
|
localai_url=$(uci_get main.localai_url)
|
|
[ -z "$localai_url" ] && localai_url="http://127.0.0.1:8091"
|
|
wget -q -O /dev/null --timeout=2 "${localai_url}/v1/models" 2>/dev/null && localai_status="online"
|
|
|
|
storage_size="0"
|
|
[ -d "${storage:-/var/lib/localrecall}" ] && storage_size=$(du -s "${storage:-/var/lib/localrecall}" 2>/dev/null | cut -f1)
|
|
|
|
cat <<EOF
|
|
{
|
|
"enabled": $([ "$enabled" = "1" ] && echo "true" || echo "false"),
|
|
"total": $total,
|
|
"threats": $threats,
|
|
"decisions": $decisions,
|
|
"patterns": $patterns,
|
|
"configs": $configs,
|
|
"conversations": $convs,
|
|
"max_memories": ${max_mem:-1000},
|
|
"retention_days": ${retention:-90},
|
|
"storage_size": $storage_size,
|
|
"localai_status": "$localai_status"
|
|
}
|
|
EOF
|
|
;;
|
|
|
|
get_memories)
|
|
read -r input
|
|
category=$(echo "$input" | jsonfilter -e '@.category' 2>/dev/null)
|
|
limit=$(echo "$input" | jsonfilter -e '@.limit' 2>/dev/null)
|
|
[ -z "$limit" ] && limit=50
|
|
|
|
memories='[]'
|
|
if [ -f "$MEMORIES_FILE" ]; then
|
|
if [ -n "$category" ]; then
|
|
memories=$(jsonfilter -i "$MEMORIES_FILE" -e "@[@.category='$category']" 2>/dev/null | head -n "$limit" | tr '\n' ',' | sed 's/,$//')
|
|
[ -z "$memories" ] && memories='[]' || memories="[$memories]"
|
|
else
|
|
# Get all, sorted by timestamp (most recent first)
|
|
memories=$(cat "$MEMORIES_FILE")
|
|
fi
|
|
fi
|
|
|
|
printf '{"memories":%s}' "$memories"
|
|
;;
|
|
|
|
search)
|
|
read -r input
|
|
query=$(echo "$input" | jsonfilter -e '@.query' 2>/dev/null)
|
|
limit=$(echo "$input" | jsonfilter -e '@.limit' 2>/dev/null)
|
|
[ -z "$limit" ] && limit=20
|
|
|
|
results='[]'
|
|
if [ -n "$query" ] && [ -f "$MEMORIES_FILE" ]; then
|
|
results=$(grep -i "$query" "$MEMORIES_FILE" 2>/dev/null | head -n "$limit" | tr '\n' ',' | sed 's/,$//')
|
|
[ -z "$results" ] && results='[]' || results="[$results]"
|
|
fi
|
|
|
|
printf '{"results":%s}' "$results"
|
|
;;
|
|
|
|
stats)
|
|
# Agent breakdown
|
|
agent_stats='{}'
|
|
if [ -f "$MEMORIES_FILE" ]; then
|
|
ta=$(jsonfilter -i "$MEMORIES_FILE" -e "@[@.agent='threat_analyst']" 2>/dev/null | wc -l)
|
|
dg=$(jsonfilter -i "$MEMORIES_FILE" -e "@[@.agent='dns_guard']" 2>/dev/null | wc -l)
|
|
na=$(jsonfilter -i "$MEMORIES_FILE" -e "@[@.agent='network_anomaly']" 2>/dev/null | wc -l)
|
|
ct=$(jsonfilter -i "$MEMORIES_FILE" -e "@[@.agent='cve_triage']" 2>/dev/null | wc -l)
|
|
us=$(jsonfilter -i "$MEMORIES_FILE" -e "@[@.agent='user']" 2>/dev/null | wc -l)
|
|
agent_stats="{\"threat_analyst\":$ta,\"dns_guard\":$dg,\"network_anomaly\":$na,\"cve_triage\":$ct,\"user\":$us}"
|
|
fi
|
|
|
|
printf '{"agents":%s}' "$agent_stats"
|
|
;;
|
|
|
|
add)
|
|
read -r input
|
|
category=$(echo "$input" | jsonfilter -e '@.category' 2>/dev/null)
|
|
content=$(echo "$input" | jsonfilter -e '@.content' 2>/dev/null)
|
|
agent=$(echo "$input" | jsonfilter -e '@.agent' 2>/dev/null)
|
|
importance=$(echo "$input" | jsonfilter -e '@.importance' 2>/dev/null)
|
|
|
|
[ -z "$category" ] || [ -z "$content" ] && {
|
|
echo '{"error":"Category and content required"}'
|
|
exit 0
|
|
}
|
|
|
|
[ -z "$agent" ] && agent="user"
|
|
[ -z "$importance" ] && importance=5
|
|
|
|
mkdir -p "$STORAGE_DIR"
|
|
id=$(add_memory "$category" "$agent" "$content" '{}' "$importance")
|
|
|
|
printf '{"success":true,"id":"%s"}' "$id"
|
|
;;
|
|
|
|
delete)
|
|
read -r input
|
|
id=$(echo "$input" | jsonfilter -e '@.id' 2>/dev/null)
|
|
|
|
[ -z "$id" ] && {
|
|
echo '{"error":"Memory ID required"}'
|
|
exit 0
|
|
}
|
|
|
|
delete_memory "$id"
|
|
echo '{"success":true}'
|
|
;;
|
|
|
|
cleanup)
|
|
retention=$(uci_get main.retention_days)
|
|
keep_imp=$(uci_get cleanup.keep_important)
|
|
|
|
deleted=$(cleanup_old "${retention:-90}" "${keep_imp:-1}")
|
|
printf '{"success":true,"deleted":%d}' "${deleted:-0}"
|
|
;;
|
|
|
|
summarize)
|
|
read -r input
|
|
category=$(echo "$input" | jsonfilter -e '@.category' 2>/dev/null)
|
|
|
|
localai_url=$(uci_get main.localai_url)
|
|
[ -z "$localai_url" ] && localai_url="http://127.0.0.1:8091"
|
|
|
|
if ! curl -s --max-time 2 "${localai_url}/v1/models" >/dev/null 2>&1; then
|
|
echo '{"error":"LocalAI not available"}'
|
|
exit 0
|
|
fi
|
|
|
|
summary=$(summarize_memories "$category" 2>/dev/null)
|
|
summary=$(printf '%s' "$summary" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr '\n' ' ')
|
|
|
|
printf '{"summary":"%s"}' "$summary"
|
|
;;
|
|
|
|
export)
|
|
memories='[]'
|
|
[ -f "$MEMORIES_FILE" ] && memories=$(cat "$MEMORIES_FILE")
|
|
printf '{"data":%s}' "$memories"
|
|
;;
|
|
|
|
import)
|
|
read -r input
|
|
data=$(echo "$input" | jsonfilter -e '@.data' 2>/dev/null)
|
|
|
|
[ -z "$data" ] && {
|
|
echo '{"error":"No data provided"}'
|
|
exit 0
|
|
}
|
|
|
|
mkdir -p "$STORAGE_DIR"
|
|
echo "$data" > "$MEMORIES_FILE"
|
|
echo '{"success":true}'
|
|
;;
|
|
|
|
*)
|
|
echo '{"error":"Unknown method"}'
|
|
;;
|
|
esac
|
|
;;
|
|
esac
|