#!/bin/bash ################################################################################ # Script Name: samba-exporter.sh # Version: 1.0 # Description: Prometheus exporter for Samba metrics -- active sessions, share # connections, locked files, process status, version info, and # configured share counts # # Author: Phil Connor # Contact: contact@mylinux.work # Website: https://mylinux.work # License: MIT # # Prerequisites: # - Samba installed (smbstatus, testparm, smbd) # - Root or appropriate permissions for smbstatus # - netcat (nc) for HTTP mode # # Usage: # ./samba-exporter.sh # ./samba-exporter.sh --http -p 9588 # ./samba-exporter.sh --textfile # # Metrics Exported: # - samba_up - Exporter status (1=up, 0=down) # - samba_server_info{version} - Samba version info metric # - samba_sessions_active - Number of active SMB sessions # - samba_session_info{user,machine,protocol_version} - Per-session info # - samba_shares_active - Number of active share connections # - samba_share_connections{share,machine,user} - Connections per share # - samba_share_configured_total - Total configured shares from testparm # - samba_locked_files_total - Total number of locked files # - samba_smbd_running - Whether smbd process is running (1/0) # - samba_nmbd_running - Whether nmbd process is running (1/0) # - samba_winbindd_running - Whether winbindd process is running (1/0) # - samba_smbd_pid_count - Number of smbd processes # - samba_exporter_duration_seconds - Script execution time # - samba_exporter_last_run_timestamp - Last successful run time # # Configuration: # Default HTTP port: 9588 # Textfile directory: /var/lib/node_exporter # ################################################################################ set -o pipefail # ============================================================================ # CONFIGURATION VARIABLES # ============================================================================ TEXTFILE_DIR="/var/lib/node_exporter" OUTPUT_FILE="" HTTP_MODE=false HTTP_PORT=9588 LOCK_FILE="/tmp/samba-exporter.lock" # ============================================================================ # LOGGING FUNCTIONS # ============================================================================ RED='\033[0;31m' YELLOW='\033[1;33m' GREEN='\033[0;32m' NC='\033[0m' log() { echo -e "${GREEN}[INFO]${NC} $*" >&2; } warn() { echo -e "${YELLOW}[WARN]${NC} $*" >&2; } error(){ echo -e "${RED}[ERROR]${NC} $*" >&2; } # ============================================================================ # HELPER FUNCTIONS # ============================================================================ show_usage() { cat </dev/null) if [ -n "$lock_pid" ] && kill -0 "$lock_pid" 2>/dev/null; then error "Another instance is running (PID $lock_pid)" return 1 fi rm -f "$LOCK_FILE" fi echo $$ > "$LOCK_FILE" trap 'rm -f "$LOCK_FILE"' EXIT return 0 } # Check if Samba tools are available check_samba() { if ! command -v smbstatus >/dev/null 2>&1; then error "smbstatus not found" return 1 fi if ! smbstatus -b >/dev/null 2>&1; then error "smbstatus failed (insufficient permissions or smbd not running)" return 1 fi return 0 } # Get Samba version string from smbd # Returns: Version string (e.g., "4.18.6") get_samba_version() { local version if command -v smbd >/dev/null 2>&1; then version=$(smbd --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) fi if [ -z "$version" ]; then version=$(smbstatus --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) fi echo "${version:-unknown}" } # Get active session data from smbstatus --processes # Returns: Lines with PID, username, group, machine, protocol_version get_session_data() { smbstatus --processes --numeric 2>/dev/null | awk ' BEGIN { started=0 } /^----/ { started=1; next } started && NF >= 4 && $1 ~ /^[0-9]+/ { pid=$1 user=$2 group=$3 machine=$4 proto="" # Protocol version is typically the last field or near end for (i=5; i<=NF; i++) { if ($i ~ /^SMB[0-9]/ || $i ~ /^NT1/ || $i ~ /^SMB[23]_[0-9]/) { proto=$i } } if (proto == "") proto="unknown" printf "%s|%s|%s|%s|%s\n", pid, user, group, machine, proto } ' } # Get active share connection data from smbstatus --shares # Returns: Lines with share name, machine, user, connection time get_share_data() { smbstatus --shares --numeric 2>/dev/null | awk ' BEGIN { started=0 } /^----/ { started=1; next } started && NF >= 4 && $1 ~ /^[0-9]+/ { # Fields: Service pid machine Connected_at ... share=$1 pid=$2 machine=$3 # Look up from all remaining fields printf "%s|%s|%s\n", share, pid, machine } ' } # Get number of locked files from smbstatus --locks # Returns: Count of locked files get_locked_files_count() { local count count=$(smbstatus --locks --numeric 2>/dev/null | awk ' BEGIN { started=0; count=0 } /^----/ { started=1; next } started && NF >= 2 && $1 ~ /^[0-9]+/ { count++ } END { print count } ') echo "${count:-0}" } # Get total configured shares from testparm # Returns: Number of configured shares (excluding global, printers meta-sections) get_configured_shares() { local count if command -v testparm >/dev/null 2>&1; then count=$(testparm -s 2>/dev/null | grep -cE '^\[' | head -1) # Subtract 1 for [global] section if [ -n "$count" ] && [ "$count" -gt 0 ]; then # Count actual share sections, excluding [global] count=$(testparm -s 2>/dev/null | grep -E '^\[' | grep -vcE '^\[global\]') fi fi echo "${count:-0}" } # Check if a process is running # Args: $1 - process name # Returns: 1 if running, 0 if not check_process_running() { local proc_name="$1" if pgrep -x "$proc_name" >/dev/null 2>&1; then echo "1" else echo "0" fi } # Get count of smbd processes # Returns: Number of smbd processes get_smbd_pid_count() { local count count=$(pgrep -cx smbd 2>/dev/null) echo "${count:-0}" } # Sanitize a label value for Prometheus exposition format # Args: $1 - raw value # Returns: Escaped string safe for label values sanitize_label() { local val="$1" val="${val//\\/\\\\}" val="${val//\"/\\\"}" val="${val//$'\n'/}" echo "$val" } # ============================================================================ # METRIC GENERATION # ============================================================================ generate_metrics() { local script_start script_start=$(date +%s) if ! check_samba; then cat </dev/null 2>&1; then error "netcat (nc) required for HTTP mode" 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" echo "Samba Exporter

Samba Prometheus Exporter

Metrics

" fi } | nc -l -p "$HTTP_PORT" -q 1 2>/dev/null done } # ============================================================================ # MAIN EXECUTION # ============================================================================ main() { parse_args "$@" if ! acquire_lock; then exit 1 fi 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}/.samba_metrics.XXXXXX") if ! generate_metrics > "$temp_file" 2>/dev/null; then rm -f "$temp_file" error "Failed to generate metrics" 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" error "Metrics file too small ($file_lines lines), keeping previous" exit 1 fi chmod 644 "$temp_file" mv -f "$temp_file" "$OUTPUT_FILE" log "Metrics written to $OUTPUT_FILE ($file_lines lines)" else generate_metrics fi } main "$@"