Files
linux-scripts/directory-size-exporter.sh
chiefgeek a1a17e81a1 Sync all scripts from website downloads — 352 scripts total
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.
2026-05-25 03:31:08 +02:00

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