- CLI: aigatewayctl login [provider] - validates credentials before saving - Rollback on auth failure (preserves previous credentials) - Format warnings for provider-specific API key patterns - RPCD: login method for LuCI frontend integration - ACL: Added write permission for login method docs: Refactor WIP.md and update HISTORY.md - WIP.md: 1470 → 108 lines (keep only March 2026 items) - HISTORY.md: Add entries #74-75 (Feb 2026 milestones) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
348 lines
8.7 KiB
Bash
348 lines
8.7 KiB
Bash
#!/bin/sh
|
|
# SecuBox AI Gateway - RPCD Backend for LuCI
|
|
# Copyright (C) 2026 CyberMind.fr
|
|
|
|
. /usr/share/libubox/jshn.sh
|
|
. /lib/functions.sh
|
|
|
|
CONFIG="ai-gateway"
|
|
LIB_DIR="/usr/lib/ai-gateway"
|
|
|
|
# JSON output helpers
|
|
json_init_object() {
|
|
json_init
|
|
json_add_object "result"
|
|
}
|
|
|
|
json_finish_object() {
|
|
json_close_object
|
|
json_dump
|
|
}
|
|
|
|
# Method: status
|
|
method_status() {
|
|
json_init_object
|
|
|
|
# Service status
|
|
local running="false"
|
|
pgrep -f "ai-gateway" >/dev/null 2>&1 && running="true"
|
|
json_add_boolean "running" "$running"
|
|
|
|
local enabled=$(uci -q get ${CONFIG}.main.enabled || echo "0")
|
|
json_add_boolean "enabled" "$([ "$enabled" = "1" ] && echo "true" || echo "false")"
|
|
|
|
local port=$(uci -q get ${CONFIG}.main.proxy_port || echo "4000")
|
|
json_add_string "port" "$port"
|
|
|
|
local offline=$(uci -q get ${CONFIG}.main.offline_mode || echo "0")
|
|
json_add_boolean "offline_mode" "$([ "$offline" = "1" ] && echo "true" || echo "false")"
|
|
|
|
# Provider count
|
|
local enabled_count=0
|
|
for p in localai mistral claude openai gemini xai; do
|
|
local e=$(uci -q get ${CONFIG}.${p}.enabled)
|
|
[ "$e" = "1" ] && enabled_count=$((enabled_count + 1))
|
|
done
|
|
json_add_int "providers_enabled" "$enabled_count"
|
|
|
|
json_finish_object
|
|
}
|
|
|
|
# Method: get_config
|
|
method_get_config() {
|
|
json_init_object
|
|
|
|
json_add_boolean "enabled" "$([ "$(uci -q get ${CONFIG}.main.enabled)" = "1" ] && echo "true" || echo "false")"
|
|
json_add_string "proxy_port" "$(uci -q get ${CONFIG}.main.proxy_port || echo "4000")"
|
|
json_add_string "proxy_host" "$(uci -q get ${CONFIG}.main.proxy_host || echo "127.0.0.1")"
|
|
json_add_string "default_classification" "$(uci -q get ${CONFIG}.main.default_classification || echo "local_only")"
|
|
json_add_boolean "offline_mode" "$([ "$(uci -q get ${CONFIG}.main.offline_mode)" = "1" ] && echo "true" || echo "false")"
|
|
json_add_boolean "audit_enabled" "$([ "$(uci -q get ${CONFIG}.audit.enabled)" = "1" ] && echo "true" || echo "false")"
|
|
|
|
json_finish_object
|
|
}
|
|
|
|
# Method: get_providers
|
|
method_get_providers() {
|
|
json_init
|
|
json_add_array "providers"
|
|
|
|
for provider in localai mistral claude openai gemini xai; do
|
|
json_add_object ""
|
|
|
|
json_add_string "name" "$provider"
|
|
json_add_boolean "enabled" "$([ "$(uci -q get ${CONFIG}.${provider}.enabled)" = "1" ] && echo "true" || echo "false")"
|
|
json_add_int "priority" "$(uci -q get ${CONFIG}.${provider}.priority || echo 99)"
|
|
json_add_string "classification" "$(uci -q get ${CONFIG}.${provider}.classification || echo "-")"
|
|
json_add_string "model" "$(uci -q get ${CONFIG}.${provider}.model || echo "-")"
|
|
|
|
# Check availability
|
|
local status="disabled"
|
|
if [ "$(uci -q get ${CONFIG}.${provider}.enabled)" = "1" ]; then
|
|
if [ "$provider" = "localai" ]; then
|
|
local endpoint=$(uci -q get ${CONFIG}.localai.endpoint || echo "http://127.0.0.1:8081")
|
|
wget -q -O /dev/null --timeout=2 "${endpoint}/readyz" 2>/dev/null && status="available" || status="unavailable"
|
|
else
|
|
local api_key=$(uci -q get ${CONFIG}.${provider}.api_key)
|
|
[ -n "$api_key" ] && status="configured" || status="no_api_key"
|
|
fi
|
|
fi
|
|
json_add_string "status" "$status"
|
|
|
|
json_close_object
|
|
done
|
|
|
|
json_close_array
|
|
json_dump
|
|
}
|
|
|
|
# Method: get_audit_stats
|
|
method_get_audit_stats() {
|
|
. "$LIB_DIR/audit.sh" 2>/dev/null
|
|
get_audit_stats
|
|
}
|
|
|
|
# Method: classify
|
|
method_classify() {
|
|
local text="$1"
|
|
|
|
. "$LIB_DIR/classifier.sh"
|
|
init_patterns
|
|
classify_with_reason "$text"
|
|
}
|
|
|
|
# Method: set_provider
|
|
method_set_provider() {
|
|
local provider="$1"
|
|
local enabled="$2"
|
|
local api_key="$3"
|
|
|
|
if [ -n "$enabled" ]; then
|
|
uci set ${CONFIG}.${provider}.enabled="$enabled"
|
|
fi
|
|
|
|
if [ -n "$api_key" ]; then
|
|
uci set ${CONFIG}.${provider}.api_key="$api_key"
|
|
fi
|
|
|
|
uci commit ${CONFIG}
|
|
|
|
json_init
|
|
json_add_boolean "success" "true"
|
|
json_dump
|
|
}
|
|
|
|
# Method: set_offline_mode
|
|
method_set_offline_mode() {
|
|
local mode="$1"
|
|
|
|
uci set ${CONFIG}.main.offline_mode="$mode"
|
|
uci commit ${CONFIG}
|
|
|
|
json_init
|
|
json_add_boolean "success" "true"
|
|
json_add_boolean "offline_mode" "$([ "$mode" = "1" ] && echo "true" || echo "false")"
|
|
json_dump
|
|
}
|
|
|
|
# Method: test_provider
|
|
method_test_provider() {
|
|
local provider="$1"
|
|
|
|
local adapter="$LIB_DIR/providers/${provider}.sh"
|
|
if [ ! -f "$adapter" ]; then
|
|
json_init
|
|
json_add_boolean "success" "false"
|
|
json_add_string "error" "Provider not found"
|
|
json_dump
|
|
return
|
|
fi
|
|
|
|
. "$adapter"
|
|
local result=$(provider_test 2>&1)
|
|
local success="false"
|
|
echo "$result" | grep -q "AVAILABLE\|CONFIGURED" && success="true"
|
|
|
|
json_init
|
|
json_add_boolean "success" "$success"
|
|
json_add_string "output" "$result"
|
|
json_dump
|
|
}
|
|
|
|
# Method: login - validate credentials and save on success
|
|
method_login() {
|
|
local provider="$1"
|
|
local api_key="$2"
|
|
|
|
# Validate provider name
|
|
case "$provider" in
|
|
localai|mistral|claude|openai|gemini|xai)
|
|
;;
|
|
*)
|
|
json_init
|
|
json_add_boolean "success" "false"
|
|
json_add_string "error" "Unknown provider: $provider"
|
|
json_dump
|
|
return
|
|
;;
|
|
esac
|
|
|
|
# LocalAI doesn't need API key
|
|
if [ "$provider" = "localai" ]; then
|
|
uci set ${CONFIG}.localai.enabled='1'
|
|
uci commit ${CONFIG}
|
|
|
|
json_init
|
|
json_add_boolean "success" "true"
|
|
json_add_string "provider" "localai"
|
|
json_add_string "message" "LocalAI enabled (on-device)"
|
|
json_dump
|
|
return
|
|
fi
|
|
|
|
# API key required for cloud providers
|
|
if [ -z "$api_key" ]; then
|
|
json_init
|
|
json_add_boolean "success" "false"
|
|
json_add_string "error" "API key required"
|
|
json_dump
|
|
return
|
|
fi
|
|
|
|
# Save old values for rollback on failure
|
|
local old_key=$(uci -q get ${CONFIG}.${provider}.api_key)
|
|
local old_enabled=$(uci -q get ${CONFIG}.${provider}.enabled)
|
|
|
|
# Temporarily set credentials for testing
|
|
uci set ${CONFIG}.${provider}.api_key="$api_key"
|
|
uci set ${CONFIG}.${provider}.enabled='1'
|
|
|
|
# Test the credentials
|
|
local adapter="$LIB_DIR/providers/${provider}.sh"
|
|
local test_failed="false"
|
|
|
|
if [ -f "$adapter" ]; then
|
|
. "$adapter"
|
|
local result=$(provider_test 2>&1)
|
|
|
|
if echo "$result" | grep -qi "error\|fail\|unauthorized\|invalid"; then
|
|
test_failed="true"
|
|
|
|
# Rollback
|
|
if [ -n "$old_key" ]; then
|
|
uci set ${CONFIG}.${provider}.api_key="$old_key"
|
|
else
|
|
uci -q delete ${CONFIG}.${provider}.api_key
|
|
fi
|
|
uci set ${CONFIG}.${provider}.enabled="${old_enabled:-0}"
|
|
uci commit ${CONFIG}
|
|
|
|
json_init
|
|
json_add_boolean "success" "false"
|
|
json_add_string "error" "Authentication failed"
|
|
json_add_string "details" "$result"
|
|
json_dump
|
|
return
|
|
fi
|
|
fi
|
|
|
|
# Commit working credentials
|
|
uci commit ${CONFIG}
|
|
|
|
local class=$(uci -q get ${CONFIG}.${provider}.classification)
|
|
local model=$(uci -q get ${CONFIG}.${provider}.model)
|
|
|
|
json_init
|
|
json_add_boolean "success" "true"
|
|
json_add_string "provider" "$provider"
|
|
json_add_string "model" "$model"
|
|
json_add_string "classification" "$class"
|
|
json_dump
|
|
}
|
|
|
|
# Method: start
|
|
method_start() {
|
|
/etc/init.d/ai-gateway start >/dev/null 2>&1
|
|
|
|
json_init
|
|
json_add_boolean "success" "true"
|
|
json_dump
|
|
}
|
|
|
|
# Method: stop
|
|
method_stop() {
|
|
/etc/init.d/ai-gateway stop >/dev/null 2>&1
|
|
|
|
json_init
|
|
json_add_boolean "success" "true"
|
|
json_dump
|
|
}
|
|
|
|
# Method: restart
|
|
method_restart() {
|
|
/etc/init.d/ai-gateway restart >/dev/null 2>&1
|
|
|
|
json_init
|
|
json_add_boolean "success" "true"
|
|
json_dump
|
|
}
|
|
|
|
# RPCD interface
|
|
case "$1" in
|
|
list)
|
|
echo '{
|
|
"status": {},
|
|
"get_config": {},
|
|
"get_providers": {},
|
|
"get_audit_stats": {},
|
|
"classify": {"text": "string"},
|
|
"login": {"provider": "string", "api_key": "string"},
|
|
"set_provider": {"provider": "string", "enabled": "string", "api_key": "string"},
|
|
"set_offline_mode": {"mode": "string"},
|
|
"test_provider": {"provider": "string"},
|
|
"start": {},
|
|
"stop": {},
|
|
"restart": {}
|
|
}'
|
|
;;
|
|
call)
|
|
case "$2" in
|
|
status) method_status ;;
|
|
get_config) method_get_config ;;
|
|
get_providers) method_get_providers ;;
|
|
get_audit_stats) method_get_audit_stats ;;
|
|
classify)
|
|
read -r input
|
|
text=$(echo "$input" | jsonfilter -e '@.text' 2>/dev/null)
|
|
method_classify "$text"
|
|
;;
|
|
login)
|
|
read -r input
|
|
provider=$(echo "$input" | jsonfilter -e '@.provider' 2>/dev/null)
|
|
api_key=$(echo "$input" | jsonfilter -e '@.api_key' 2>/dev/null)
|
|
method_login "$provider" "$api_key"
|
|
;;
|
|
set_provider)
|
|
read -r input
|
|
provider=$(echo "$input" | jsonfilter -e '@.provider' 2>/dev/null)
|
|
enabled=$(echo "$input" | jsonfilter -e '@.enabled' 2>/dev/null)
|
|
api_key=$(echo "$input" | jsonfilter -e '@.api_key' 2>/dev/null)
|
|
method_set_provider "$provider" "$enabled" "$api_key"
|
|
;;
|
|
set_offline_mode)
|
|
read -r input
|
|
mode=$(echo "$input" | jsonfilter -e '@.mode' 2>/dev/null)
|
|
method_set_offline_mode "$mode"
|
|
;;
|
|
test_provider)
|
|
read -r input
|
|
provider=$(echo "$input" | jsonfilter -e '@.provider' 2>/dev/null)
|
|
method_test_provider "$provider"
|
|
;;
|
|
start) method_start ;;
|
|
stop) method_stop ;;
|
|
restart) method_restart ;;
|
|
esac
|
|
;;
|
|
esac
|