#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" VENV_DIR="${FGAI_VENV_DIR:-$ROOT_DIR/.venv}" PORT="${FGAI_SYSLOG_PORT:-5514}" HOST="${FGAI_SYSLOG_HOST:-0.0.0.0}" LOG_FILE="${FGAI_SYSLOG_FILE:-$ROOT_DIR/logs/fg_syslog.jsonl}" LISTENER_LOG="${FGAI_LISTENER_LOG:-$ROOT_DIR/logs/fgai-listener.log}" POLICY_FILE="${FGAI_POLICY_FILE:-$ROOT_DIR/exports/policies.json}" STATE_FILE="${FGAI_STATE_FILE:-$ROOT_DIR/state/fgai-status.json}" MONITOR_INTERVAL="${FGAI_MONITOR_INTERVAL:-10}" DASHBOARD_HOST="${FGAI_DASHBOARD_HOST:-127.0.0.1}" DASHBOARD_PORT="${FGAI_DASHBOARD_PORT:-8088}" LISTENER_PID_FILE="${FGAI_LISTENER_PID_FILE:-$ROOT_DIR/run/fgai-listener.pid}" MONITOR_PID_FILE="${FGAI_MONITOR_PID_FILE:-$ROOT_DIR/run/fgai-monitor.pid}" DASHBOARD_PID_FILE="${FGAI_DASHBOARD_PID_FILE:-$ROOT_DIR/run/fgai-dashboard.pid}" MONITOR_LOG="${FGAI_MONITOR_LOG:-$ROOT_DIR/logs/fgai-monitor.log}" DASHBOARD_LOG="${FGAI_DASHBOARD_LOG:-$ROOT_DIR/logs/fgai-dashboard.log}" usage() { printf 'Usage: %s [start|stop|restart|status|tail|analyze|install]\n' "$0" printf '\nDefault action: start\n' printf '\nEnvironment overrides:\n' printf ' FGAI_SYSLOG_PORT=%s\n' "$PORT" printf ' FGAI_SYSLOG_HOST=%s\n' "$HOST" printf ' FGAI_SYSLOG_FILE=%s\n' "$LOG_FILE" printf ' FGAI_LISTENER_LOG=%s\n' "$LISTENER_LOG" printf ' FGAI_DASHBOARD_PORT=%s\n' "$DASHBOARD_PORT" } activate_venv_for_script() { # Activation here affects this script process. It cannot modify the parent shell prompt. # Use "source .venv/bin/activate" manually if you want your interactive prompt to show it. # shellcheck disable=SC1091 source "$VENV_DIR/bin/activate" } install_deps() { if [ ! -x "$VENV_DIR/bin/python" ]; then printf 'Creating venv: %s\n' "$VENV_DIR" python3 -m venv "$VENV_DIR" fi activate_venv_for_script printf 'Installing/updating fgAI dependencies...\n' python -m pip install --upgrade pip python -m pip install -e "$ROOT_DIR" } is_pid_running() { [ -f "$1" ] && ps -p "$(cat "$1")" >/dev/null 2>&1 } needs_privileged_port() { [ "$PORT" -lt 1024 ] && [ "${EUID:-$(id -u)}" -ne 0 ] } listener_command() { "$VENV_DIR/bin/fgai" listen-syslog \ --host "$HOST" \ --port "$PORT" \ --output "$LOG_FILE" \ --quiet } start_listener() { install_deps mkdir -p "$(dirname "$LOG_FILE")" "$(dirname "$LISTENER_LOG")" "$(dirname "$LISTENER_PID_FILE")" "$(dirname "$STATE_FILE")" touch "$LOG_FILE" if is_pid_running "$LISTENER_PID_FILE"; then printf 'fgAI syslog listener already running, pid %s\n' "$(cat "$LISTENER_PID_FILE")" return 0 fi if needs_privileged_port; then printf 'Port %s needs elevated privileges; starting listener with sudo.\n' "$PORT" sudo nohup "$VENV_DIR/bin/fgai" listen-syslog \ --host "$HOST" \ --port "$PORT" \ --output "$LOG_FILE" \ --quiet > "$LISTENER_LOG" 2>&1 & else nohup "$VENV_DIR/bin/fgai" listen-syslog \ --host "$HOST" \ --port "$PORT" \ --output "$LOG_FILE" \ --quiet > "$LISTENER_LOG" 2>&1 & fi printf '%s\n' "$!" > "$LISTENER_PID_FILE" printf 'Started fgAI syslog listener, pid %s\n' "$(cat "$LISTENER_PID_FILE")" printf 'Input: udp://%s:%s\n' "$HOST" "$PORT" printf 'Syslog file: %s\n' "$LOG_FILE" printf 'Process log: %s\n' "$LISTENER_LOG" } start_monitor() { install_deps mkdir -p "$(dirname "$STATE_FILE")" "$(dirname "$MONITOR_LOG")" "$(dirname "$MONITOR_PID_FILE")" if is_pid_running "$MONITOR_PID_FILE"; then printf 'fgAI monitor already running, pid %s\n' "$(cat "$MONITOR_PID_FILE")" return 0 fi monitor_args=(monitor --logs "$LOG_FILE" --output "$STATE_FILE" --interval "$MONITOR_INTERVAL") if [ -f "$POLICY_FILE" ]; then monitor_args+=(--policies "$POLICY_FILE") fi nohup "$VENV_DIR/bin/fgai" "${monitor_args[@]}" > "$MONITOR_LOG" 2>&1 & printf '%s\n' "$!" > "$MONITOR_PID_FILE" printf 'Started fgAI monitor, pid %s\n' "$(cat "$MONITOR_PID_FILE")" printf 'Status file: %s\n' "$STATE_FILE" } start_dashboard() { install_deps mkdir -p "$(dirname "$DASHBOARD_LOG")" "$(dirname "$DASHBOARD_PID_FILE")" if is_pid_running "$DASHBOARD_PID_FILE"; then printf 'fgAI dashboard already running, pid %s\n' "$(cat "$DASHBOARD_PID_FILE")" return 0 fi nohup "$VENV_DIR/bin/fgai" dashboard \ --host "$DASHBOARD_HOST" \ --port "$DASHBOARD_PORT" \ --status-file "$STATE_FILE" \ --image-dir "$ROOT_DIR/images" > "$DASHBOARD_LOG" 2>&1 & printf '%s\n' "$!" > "$DASHBOARD_PID_FILE" printf 'Started fgAI dashboard, pid %s\n' "$(cat "$DASHBOARD_PID_FILE")" printf 'Dashboard: http://%s:%s\n' "$DASHBOARD_HOST" "$DASHBOARD_PORT" } start_all() { start_listener start_monitor start_dashboard } stop_pid() { label="$1" pid_file="$2" if ! is_pid_running "$pid_file"; then printf '%s is not running\n' "$label" rm -f "$pid_file" return 0 fi if ! kill "$(cat "$pid_file")" 2>/dev/null; then sudo kill "$(cat "$pid_file")" fi rm -f "$pid_file" printf 'Stopped %s\n' "$label" } stop_all() { stop_pid "fgAI dashboard" "$DASHBOARD_PID_FILE" stop_pid "fgAI monitor" "$MONITOR_PID_FILE" stop_pid "fgAI syslog listener" "$LISTENER_PID_FILE" } status_one() { label="$1" pid_file="$2" if is_pid_running "$pid_file"; then printf '%s running, pid %s\n' "$label" "$(cat "$pid_file")" return 0 fi printf '%s is not running\n' "$label" return 1 } status_all() { status_one "fgAI syslog listener" "$LISTENER_PID_FILE" || true if is_pid_running "$LISTENER_PID_FILE"; then printf 'Input: udp://%s:%s\n' "$HOST" "$PORT" printf 'Syslog file: %s\n' "$LOG_FILE" if command -v ss >/dev/null 2>&1; then ss -lunp 2>/dev/null | awk -v port=":$PORT" '$0 ~ port {print}' fi fi status_one "fgAI monitor" "$MONITOR_PID_FILE" || true status_one "fgAI dashboard" "$DASHBOARD_PID_FILE" || true printf 'Dashboard: http://%s:%s\n' "$DASHBOARD_HOST" "$DASHBOARD_PORT" printf 'Status file: %s\n' "$STATE_FILE" } command="${1:-start}" case "$command" in start) start_all ;; stop) stop_all ;; restart) stop_all start_all ;; status) status_all ;; tail) mkdir -p "$(dirname "$LOG_FILE")" touch "$LOG_FILE" tail -f "$LOG_FILE" ;; analyze) install_deps fgai analyze-logs --logs "$LOG_FILE" ;; install) install_deps ;; -h|--help|help) usage ;; *) usage exit 2 ;; esac