a1a17e81a1
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
277 lines
8.3 KiB
Bash
277 lines
8.3 KiB
Bash
#!/usr/bin/env bash
|
|
# directory-size-exporter.sh — Prometheus exporter for directory sizes
|
|
#
|
|
# Monitors directory disk usage that node_exporter can't see.
|
|
# Node exporter only reports mounted filesystem totals — this script
|
|
# tracks individual directories like /var/log, /home, /opt, or any
|
|
# path you care about.
|
|
#
|
|
# Author: Phil Connor
|
|
# Contact: contact@mylinux.work
|
|
# License: MIT
|
|
# Version: 1.0.1
|
|
|
|
set -euo pipefail
|
|
|
|
EXPORTER_NAME="directory_size"
|
|
DEFAULT_PORT=9101
|
|
OUTPUT_MODE="stdout"
|
|
OUTPUT_FILE=""
|
|
PORT="${DIRECTORY_SIZE_PORT:-$DEFAULT_PORT}"
|
|
TIMEOUT="${DIRECTORY_SIZE_TIMEOUT:-300}"
|
|
VERBOSE=false
|
|
QUIET=false
|
|
DRY_RUN=false
|
|
TARGET_DIRECTORIES=()
|
|
|
|
# ── Metrics Collection ──────────────────────────────────────────────
|
|
|
|
log_verbose() {
|
|
[[ "$VERBOSE" == true ]] && echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >&2 || true
|
|
}
|
|
|
|
log_info() {
|
|
[[ "$QUIET" == false ]] && echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >&2 || true
|
|
}
|
|
|
|
collect_metrics() {
|
|
local start_time
|
|
start_time=$(date +%s%N)
|
|
|
|
local success=1
|
|
local size_lines="" pct_lines=""
|
|
|
|
for directory in "${TARGET_DIRECTORIES[@]}"; do
|
|
log_verbose "Running du for: $directory"
|
|
|
|
local du_output
|
|
du_output=$(timeout "$TIMEOUT" du --block-size=1 --summarize "$directory" 2>/dev/null) || {
|
|
log_info "WARNING: du failed for $directory"
|
|
success=0
|
|
continue
|
|
}
|
|
|
|
local size_bytes
|
|
size_bytes=$(echo "$du_output" | awk '{print $1}')
|
|
size_lines+="node_directory_size_bytes{directory=\"${directory}\"} ${size_bytes}"$'\n'
|
|
|
|
local pct
|
|
pct=$(df --output=pcent "$directory" 2>/dev/null | tail -n 1 | tr -d ' %')
|
|
if [[ "$pct" =~ ^[0-9]+$ ]]; then
|
|
pct_lines+="node_directory_filesystem_usage_percent{directory=\"${directory}\"} ${pct}"$'\n'
|
|
fi
|
|
done
|
|
|
|
echo "# HELP node_directory_size_bytes Disk space used by directory"
|
|
echo "# TYPE node_directory_size_bytes gauge"
|
|
printf "%s" "$size_lines"
|
|
|
|
echo ""
|
|
echo "# HELP node_directory_filesystem_usage_percent Filesystem usage percentage for the directory mount point"
|
|
echo "# TYPE node_directory_filesystem_usage_percent gauge"
|
|
printf "%s" "$pct_lines"
|
|
|
|
# ── Script runtime ──
|
|
local end_time runtime
|
|
end_time=$(date +%s%N)
|
|
runtime=$(awk "BEGIN {printf \"%.3f\", ($end_time - $start_time) / 1000000000}")
|
|
|
|
echo ""
|
|
echo "# HELP ${EXPORTER_NAME}_duration_seconds Script execution time"
|
|
echo "# TYPE ${EXPORTER_NAME}_duration_seconds gauge"
|
|
echo "${EXPORTER_NAME}_duration_seconds ${runtime}"
|
|
|
|
echo ""
|
|
echo "# HELP ${EXPORTER_NAME}_last_run_timestamp Last successful run"
|
|
echo "# TYPE ${EXPORTER_NAME}_last_run_timestamp gauge"
|
|
echo "${EXPORTER_NAME}_last_run_timestamp $(date +%s)"
|
|
|
|
echo ""
|
|
echo "# HELP ${EXPORTER_NAME}_success Whether the exporter ran successfully"
|
|
echo "# TYPE ${EXPORTER_NAME}_success gauge"
|
|
echo "${EXPORTER_NAME}_success ${success}"
|
|
}
|
|
|
|
# ── HTTP Request Handler ────────────────────────────────────────────
|
|
|
|
handle_request() {
|
|
read -r method path version
|
|
|
|
while IFS= read -r header; do
|
|
[[ "$header" == $'\r' || -z "$header" ]] && break
|
|
done
|
|
|
|
if [[ "$path" == "/metrics" ]]; then
|
|
local metrics length
|
|
metrics=$(collect_metrics)
|
|
length=${#metrics}
|
|
|
|
printf "HTTP/1.1 200 OK\r\n"
|
|
printf "Content-Type: text/plain; version=0.0.4; charset=utf-8\r\n"
|
|
printf "Content-Length: %d\r\n" "$length"
|
|
printf "Connection: close\r\n"
|
|
printf "\r\n"
|
|
printf "%s" "$metrics"
|
|
else
|
|
local body="404 Not Found"
|
|
printf "HTTP/1.1 404 Not Found\r\n"
|
|
printf "Content-Type: text/plain\r\n"
|
|
printf "Content-Length: %d\r\n" "${#body}"
|
|
printf "Connection: close\r\n"
|
|
printf "\r\n"
|
|
printf "%s" "$body"
|
|
fi
|
|
}
|
|
|
|
# ── Help ─────────────────────────────────────────────────────────────
|
|
|
|
show_help() {
|
|
cat <<EOF
|
|
Usage: $0 [OPTIONS] <directory> [directory2 ...]
|
|
|
|
Monitor directory sizes for Prometheus. Node exporter only reports
|
|
mounted filesystem totals — this script tracks individual directories.
|
|
|
|
Output modes:
|
|
(default) Print metrics to stdout
|
|
--textfile Write to node_exporter textfile collector
|
|
-o FILE Write to a specific file
|
|
--http Run as HTTP server (default port: ${DEFAULT_PORT})
|
|
|
|
Options:
|
|
--port PORT HTTP listen port (default: ${DEFAULT_PORT})
|
|
--timeout SECS du command timeout (default: 300)
|
|
--dry-run Show what would be written without writing
|
|
--verbose, -v Enable verbose debug output
|
|
--quiet, -q Suppress non-error output
|
|
-h, --help Show this help message
|
|
|
|
Environment variables:
|
|
DIRECTORY_SIZE_PORT HTTP listen port (default: ${DEFAULT_PORT})
|
|
DIRECTORY_SIZE_TIMEOUT du command timeout in seconds (default: 300)
|
|
|
|
Examples:
|
|
$0 /var/log /home /opt
|
|
$0 --textfile /var/log /var/lib/mysql
|
|
$0 --http --port 9101 /var/log /home
|
|
$0 -o /tmp/dir_sizes.prom /var/log
|
|
EOF
|
|
}
|
|
|
|
# ── Argument Parsing ────────────────────────────────────────────────
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--textfile)
|
|
OUTPUT_MODE="textfile"
|
|
shift
|
|
;;
|
|
-o)
|
|
OUTPUT_MODE="file"
|
|
OUTPUT_FILE="$2"
|
|
shift 2
|
|
;;
|
|
--http)
|
|
OUTPUT_MODE="http"
|
|
shift
|
|
;;
|
|
--port)
|
|
PORT="$2"
|
|
shift 2
|
|
;;
|
|
--timeout)
|
|
TIMEOUT="$2"
|
|
shift 2
|
|
;;
|
|
--dry-run)
|
|
DRY_RUN=true
|
|
shift
|
|
;;
|
|
--verbose|-v)
|
|
VERBOSE=true
|
|
shift
|
|
;;
|
|
--quiet|-q)
|
|
QUIET=true
|
|
shift
|
|
;;
|
|
--handle-request)
|
|
OUTPUT_MODE="handle-request"
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
show_help
|
|
exit 0
|
|
;;
|
|
-*)
|
|
echo "Unknown option: $1" >&2
|
|
exit 1
|
|
;;
|
|
*)
|
|
TARGET_DIRECTORIES+=("$1")
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Validate directories
|
|
if [[ ${#TARGET_DIRECTORIES[@]} -eq 0 ]]; then
|
|
echo "Error: at least one directory argument is required" >&2
|
|
echo "Run with --help for usage" >&2
|
|
exit 1
|
|
fi
|
|
|
|
for dir in "${TARGET_DIRECTORIES[@]}"; do
|
|
if [[ ! -d "$dir" ]]; then
|
|
echo "Error: directory does not exist: $dir" >&2
|
|
exit 1
|
|
fi
|
|
if [[ ! -r "$dir" ]]; then
|
|
echo "Error: directory is not readable: $dir" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
# ── Output ──────────────────────────────────────────────────────────
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
log_info "DRY RUN — metrics that would be written:"
|
|
collect_metrics
|
|
exit 0
|
|
fi
|
|
|
|
case "$OUTPUT_MODE" in
|
|
handle-request)
|
|
handle_request
|
|
exit 0
|
|
;;
|
|
stdout)
|
|
collect_metrics
|
|
;;
|
|
textfile)
|
|
output_dir="/var/lib/node_exporter"
|
|
OUTPUT_FILE="${output_dir}/${EXPORTER_NAME}.prom"
|
|
mkdir -p "$output_dir"
|
|
temp_file=$(mktemp "${output_dir}/.${EXPORTER_NAME}.XXXXXX")
|
|
collect_metrics > "$temp_file"
|
|
chmod 644 "$temp_file"
|
|
mv -f "$temp_file" "$OUTPUT_FILE"
|
|
;;
|
|
file)
|
|
temp_file=$(mktemp "${OUTPUT_FILE}.XXXXXX")
|
|
collect_metrics > "$temp_file"
|
|
chmod 644 "$temp_file"
|
|
mv -f "$temp_file" "$OUTPUT_FILE"
|
|
;;
|
|
http)
|
|
if ! command -v socat &>/dev/null; then
|
|
echo "ERROR: socat is required for --http mode" >&2
|
|
echo "Install it: apt install socat or dnf install socat" >&2
|
|
exit 1
|
|
fi
|
|
echo "${EXPORTER_NAME} listening on port ${PORT}..."
|
|
echo "Monitoring directories: ${TARGET_DIRECTORIES[*]}"
|
|
socat TCP-LISTEN:"$PORT",reuseaddr,fork EXEC:"$0 --handle-request ${TARGET_DIRECTORIES[*]}"
|
|
;;
|
|
esac
|