#!/usr/bin/env bash ######################################################################################### #### freeradius-exporter.sh — Prometheus metrics exporter for FreeRADIUS #### #### Exports authentication, accounting, and proxy statistics from the #### #### FreeRADIUS status server as Prometheus metrics #### #### Requires: bash 4+, radclient #### #### #### #### Author: Phil Connor #### #### Contact: contact@mylinux.work #### #### License: MIT #### #### Version 1.00 #### #### #### #### Usage: #### #### ./freeradius-exporter.sh --http --port 9620 #### #### #### #### See --help for all options. #### ######################################################################################### set -uo pipefail # ============================================================================ # CONFIGURATION VARIABLES # ============================================================================ RADIUS_HOST="${RADIUS_HOST:-localhost}" RADIUS_STATUS_PORT="${RADIUS_STATUS_PORT:-18121}" RADIUS_SECRET="${RADIUS_SECRET:-adminsecret}" TEXTFILE_DIR="/var/lib/node_exporter" OUTPUT_FILE="" HTTP_MODE=false HTTP_PORT=9620 EXPORTER_VERSION="1.00" # ============================================================================ # HELPER FUNCTIONS # ============================================================================ show_usage() { cat <&2; exit 1 ;; esac done } # Query the FreeRADIUS status server via radclient # Returns: raw attribute-value pair output, or empty on failure query_status_server() { echo "Message-Authenticator = 0x00, FreeRADIUS-Statistics-Type = All" \ | radclient "${RADIUS_HOST}:${RADIUS_STATUS_PORT}" status "${RADIUS_SECRET}" 2>/dev/null } # Extract a numeric value from radclient output # Args: $1 - attribute name, $2 - radclient output # Returns: numeric value or 0 if not found extract_value() { local attr="$1" local data="$2" local val val=$(echo "$data" | grep -F "$attr" | awk -F'= ' '{print $2}' | tr -d '[:space:]') echo "${val:-0}" } # ============================================================================ # METRIC GENERATION # ============================================================================ # Generate all Prometheus metrics # Returns: Prometheus text format metrics on stdout generate_metrics() { local script_start script_start=$(date +%s) # Check radclient is available if ! command -v radclient >/dev/null 2>&1; then echo "ERROR: radclient command not found" >&2 cat <&2 if ! command -v nc >/dev/null 2>&1; then echo "ERROR: netcat (nc) required for HTTP mode" >&2 exit 1 fi while true; do { read -r request if [[ "$request" =~ ^GET\ /metrics ]]; then echo -e "HTTP/1.1 200 OK\r\nContent-Type: text/plain; version=0.0.4\r\n\r" generate_metrics else echo -e "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r" cat < FreeRADIUS Exporter v${EXPORTER_VERSION}

FreeRADIUS Prometheus Exporter v${EXPORTER_VERSION}

Metrics

Authentication, accounting, and proxy statistics from the FreeRADIUS status server.

EOF fi } | nc -l -p "$HTTP_PORT" -q 1 2>/dev/null done } # ============================================================================ # MAIN EXECUTION # ============================================================================ main() { parse_args "$@" if [[ "$HTTP_MODE" = true ]]; then run_http_server elif [[ -n "$OUTPUT_FILE" ]]; then local output_dir output_dir="$(dirname "$OUTPUT_FILE")" mkdir -p "$output_dir" local temp_file temp_file=$(mktemp "${output_dir}/.freeradius_metrics.XXXXXX") if ! generate_metrics > "$temp_file" 2>/dev/null; then rm -f "$temp_file" echo "ERROR: Failed to generate metrics" >&2 exit 1 fi local file_lines file_lines=$(wc -l < "$temp_file" 2>/dev/null || echo 0) if [[ "$file_lines" -lt 5 ]]; then rm -f "$temp_file" echo "ERROR: Metrics file too small ($file_lines lines), keeping previous" >&2 exit 1 fi chmod 644 "$temp_file" mv -f "$temp_file" "$OUTPUT_FILE" echo "Metrics written to $OUTPUT_FILE ($file_lines lines)" >&2 else generate_metrics fi } main "$@"