diff --git a/README.md b/README.md index fa67e34..c81fddb 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,15 @@ source .venv/bin/activate pip install -e . ``` +Or use the helper script, which creates/uses `.venv` automatically and runs `pip install -e .`: + +```bash +./start.sh +./start.sh status +./start.sh analyze +./start.sh stop +``` + Analyze local logs: ```bash @@ -24,6 +33,18 @@ Listen for FortiGate syslog locally: fgai listen-syslog --port 5514 --output logs/fg_syslog.jsonl ``` +Run the listener quietly in the background: + +```bash +./start.sh +``` + +Stop the background listener: + +```bash +./start.sh stop +``` + UDP port `514` normally needs root privileges on Linux: ```bash diff --git a/scripts/fgai-listener.sh b/scripts/fgai-listener.sh new file mode 100755 index 0000000..5a1c4a3 --- /dev/null +++ b/scripts/fgai-listener.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +exec "$ROOT_DIR/start.sh" "$@" diff --git a/src/fgai/cli.py b/src/fgai/cli.py index 9d9a99e..b5eb700 100644 --- a/src/fgai/cli.py +++ b/src/fgai/cli.py @@ -102,7 +102,7 @@ def fetch_policies(args: argparse.Namespace) -> int: def listen_syslog(args: argparse.Namespace) -> int: - listen_udp_syslog(args.host, args.port, args.output) + listen_udp_syslog(args.host, args.port, args.output, quiet=args.quiet) return 0 @@ -145,6 +145,7 @@ def build_parser() -> argparse.ArgumentParser: listener.add_argument("--host", default="0.0.0.0", help="Bind address") listener.add_argument("--port", type=int, default=5514, help="UDP port. Use 514 only with sudo/capability.") listener.add_argument("--output", default="logs/fg_syslog.jsonl", help="File to append received logs to") + listener.add_argument("--quiet", action="store_true", help="Do not print each received syslog message") listener.set_defaults(func=listen_syslog) return parser diff --git a/src/fgai/syslog_server.py b/src/fgai/syslog_server.py index fe407cf..1847038 100644 --- a/src/fgai/syslog_server.py +++ b/src/fgai/syslog_server.py @@ -4,7 +4,7 @@ import socket from pathlib import Path -def listen_udp_syslog(host: str, port: int, output: str, *, max_bytes: int = 65535) -> None: +def listen_udp_syslog(host: str, port: int, output: str, *, max_bytes: int = 65535, quiet: bool = False) -> None: output_path = Path(output) output_path.parent.mkdir(parents=True, exist_ok=True) @@ -20,4 +20,5 @@ def listen_udp_syslog(host: str, port: int, output: str, *, max_bytes: int = 655 if not message: continue handle.write(f"{message}\n") - print(f"{address[0]}:{address[1]} {message}") + if not quiet: + print(f"{address[0]}:{address[1]} {message}") diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..8150d80 --- /dev/null +++ b/start.sh @@ -0,0 +1,118 @@ +#!/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}" +PID_FILE="${FGAI_PID_FILE:-$ROOT_DIR/run/fgai-listener.pid}" + +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" +} + +install_deps() { + if [ ! -x "$VENV_DIR/bin/python" ]; then + printf 'Creating venv: %s\n' "$VENV_DIR" + python3 -m venv "$VENV_DIR" + fi + + printf 'Installing/updating fgAI dependencies...\n' + "$VENV_DIR/bin/python" -m pip install --upgrade pip + "$VENV_DIR/bin/python" -m pip install -e "$ROOT_DIR" +} + +is_running() { + [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null +} + +start_listener() { + install_deps + mkdir -p "$(dirname "$LOG_FILE")" "$(dirname "$LISTENER_LOG")" "$(dirname "$PID_FILE")" + touch "$LOG_FILE" + + if is_running; then + printf 'fgAI syslog listener already running, pid %s\n' "$(cat "$PID_FILE")" + return 0 + fi + + nohup "$VENV_DIR/bin/fgai" listen-syslog \ + --host "$HOST" \ + --port "$PORT" \ + --output "$LOG_FILE" \ + --quiet > "$LISTENER_LOG" 2>&1 & + printf '%s\n' "$!" > "$PID_FILE" + + printf 'Started fgAI syslog listener, pid %s\n' "$(cat "$PID_FILE")" + printf 'Input: udp://%s:%s\n' "$HOST" "$PORT" + printf 'Syslog file: %s\n' "$LOG_FILE" + printf 'Process log: %s\n' "$LISTENER_LOG" +} + +stop_listener() { + if ! is_running; then + printf 'fgAI syslog listener is not running\n' + rm -f "$PID_FILE" + return 0 + fi + + kill "$(cat "$PID_FILE")" + rm -f "$PID_FILE" + printf 'Stopped fgAI syslog listener\n' +} + +status_listener() { + if is_running; then + printf 'fgAI syslog listener running, pid %s\n' "$(cat "$PID_FILE")" + printf 'Input: udp://%s:%s\n' "$HOST" "$PORT" + printf 'Syslog file: %s\n' "$LOG_FILE" + return 0 + fi + + printf 'fgAI syslog listener is not running\n' + return 1 +} + +command="${1:-start}" +case "$command" in + start) + start_listener + ;; + stop) + stop_listener + ;; + restart) + stop_listener + start_listener + ;; + status) + status_listener + ;; + tail) + mkdir -p "$(dirname "$LOG_FILE")" + touch "$LOG_FILE" + tail -f "$LOG_FILE" + ;; + analyze) + install_deps + "$VENV_DIR/bin/fgai" analyze-logs --logs "$LOG_FILE" + ;; + install) + install_deps + ;; + -h|--help|help) + usage + ;; + *) + usage + exit 2 + ;; +esac