Files
linux-scripts/nextcloud-exporter.sh
T
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

490 lines
16 KiB
Bash

#!/usr/bin/env bash
#########################################################################################
#### nextcloud-exporter.sh — Prometheus metrics exporter for Nextcloud ####
#### Exports server info, storage stats, share counts, active users, ####
#### and system health as Prometheus metrics ####
#### Requires: bash 4+, curl, jq ####
#### ####
#### Author: Phil Connor ####
#### Contact: contact@mylinux.work ####
#### License: MIT ####
#### Version 1.00 ####
#### ####
#### Usage: ####
#### ./nextcloud-exporter.sh --http --port 9618 ####
#### ####
#### See --help for all options. ####
#########################################################################################
set -uo pipefail
# ============================================================================
# CONFIGURATION VARIABLES
# ============================================================================
NEXTCLOUD_URL="${NEXTCLOUD_URL:-}"
NEXTCLOUD_USER="${NEXTCLOUD_USER:-}"
NEXTCLOUD_PASS="${NEXTCLOUD_PASS:-}"
TEXTFILE_DIR="/var/lib/node_exporter"
OUTPUT_FILE=""
HTTP_MODE=false
HTTP_PORT="${HTTP_PORT:-9618}"
# ============================================================================
# HELPER FUNCTIONS
# ============================================================================
show_usage() {
cat <<EOF
Usage: $0 [OPTIONS]
Export Nextcloud server info as Prometheus metrics.
MODES:
--textfile Write to node_exporter textfile collector
--http Run HTTP server on port $HTTP_PORT
OPTIONS:
-p, --port HTTP port (default: 9618)
-o, --output Output file path
--url Nextcloud base URL (or set NEXTCLOUD_URL)
--user Nextcloud username (or set NEXTCLOUD_USER)
--pass Nextcloud app password (or set NEXTCLOUD_PASS)
EXAMPLES:
$0 --http --port 9618
$0 --textfile
$0 --url https://cloud.example.com --user admin --pass app-password
$0 -o /tmp/nextcloud.prom
ENVIRONMENT VARIABLES:
NEXTCLOUD_URL Nextcloud base URL (e.g., https://cloud.example.com)
NEXTCLOUD_USER Nextcloud username
NEXTCLOUD_PASS Nextcloud app password
HTTP_PORT HTTP server port (default: 9618)
EOF
exit 0
}
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help) show_usage ;;
--textfile) OUTPUT_FILE="$TEXTFILE_DIR/nextcloud.prom"; shift ;;
--http) HTTP_MODE=true; shift ;;
-p|--port) HTTP_PORT="$2"; shift 2 ;;
-o|--output) OUTPUT_FILE="$2"; shift 2 ;;
--url) NEXTCLOUD_URL="$2"; shift 2 ;;
--user) NEXTCLOUD_USER="$2"; shift 2 ;;
--pass) NEXTCLOUD_PASS="$2"; shift 2 ;;
*) echo "Unknown option: $1" >&2; exit 1 ;;
esac
done
}
check_prerequisites() {
if ! command -v curl >/dev/null 2>&1; then
echo "ERROR: curl not found" >&2
return 1
fi
if ! command -v jq >/dev/null 2>&1; then
echo "ERROR: jq not found (required for JSON parsing)" >&2
return 1
fi
if [[ -z "$NEXTCLOUD_URL" ]]; then
echo "ERROR: NEXTCLOUD_URL not set. Use --url or set NEXTCLOUD_URL" >&2
return 1
fi
if [[ -z "$NEXTCLOUD_USER" || -z "$NEXTCLOUD_PASS" ]]; then
echo "ERROR: NEXTCLOUD_USER and NEXTCLOUD_PASS required. Use --user/--pass or environment variables" >&2
return 1
fi
return 0
}
# Escape special characters in Prometheus label values
# Args: $1 - string to escape
# Returns: escaped string safe for Prometheus labels
prom_escape() {
local val="$1"
val="${val//\\/\\\\}"
val="${val//\"/\\\"}"
val="${val//$'\n'/}"
echo "$val"
}
# Fetch Nextcloud server info from OCS API
# Returns: JSON response on stdout, or empty on error
fetch_server_info() {
local api_url="${NEXTCLOUD_URL%/}/ocs/v2.php/apps/serverinfo/api/v1/info?format=json"
curl -sf --max-time 15 \
-u "${NEXTCLOUD_USER}:${NEXTCLOUD_PASS}" \
-H "OCS-APIRequest: true" \
"$api_url" 2>/dev/null
}
# Safely extract a numeric value from JSON
# Args: $1 - JSON string, $2 - jq path
# Returns: numeric value or 0
jq_num() {
local result
result=$(echo "$1" | jq -r "$2" 2>/dev/null)
if [[ -z "$result" || "$result" == "null" ]]; then
echo "0"
else
echo "$result"
fi
}
# Safely extract a string value from JSON
# Args: $1 - JSON string, $2 - jq path
# Returns: string value or empty
jq_str() {
local result
result=$(echo "$1" | jq -r "$2" 2>/dev/null)
if [[ "$result" == "null" ]]; then
echo ""
else
echo "$result"
fi
}
# ============================================================================
# METRIC GENERATION
# ============================================================================
generate_metrics() {
local script_start
script_start=$(date +%s%N 2>/dev/null || date +%s)
if ! check_prerequisites; then
cat <<EOF
# HELP nextcloud_up Whether the Nextcloud API is reachable
# TYPE nextcloud_up gauge
nextcloud_up 0
EOF
return
fi
local api_response
api_response=$(fetch_server_info)
if [[ -z "$api_response" ]]; then
cat <<EOF
# HELP nextcloud_up Whether the Nextcloud API is reachable
# TYPE nextcloud_up gauge
nextcloud_up 0
EOF
return
fi
local data
data=$(echo "$api_response" | jq '.ocs.data' 2>/dev/null)
if [[ -z "$data" || "$data" == "null" ]]; then
cat <<EOF
# HELP nextcloud_up Whether the Nextcloud API is reachable
# TYPE nextcloud_up gauge
nextcloud_up 0
EOF
return
fi
cat <<EOF
# HELP nextcloud_up Whether the Nextcloud API is reachable
# TYPE nextcloud_up gauge
nextcloud_up 1
EOF
echo ""
# ========================================================================
# VERSION INFO
# ========================================================================
local version
version=$(jq_str "$data" '.nextcloud.system.version')
cat <<EOF
# HELP nextcloud_info Nextcloud version information
# TYPE nextcloud_info gauge
nextcloud_info{version="$(prom_escape "$version")"} 1
EOF
echo ""
# ========================================================================
# STORAGE METRICS
# ========================================================================
local num_users num_files num_storages
num_users=$(jq_num "$data" '.nextcloud.storage.num_users')
num_files=$(jq_num "$data" '.nextcloud.storage.num_files')
num_storages=$(jq_num "$data" '.nextcloud.storage.num_storages')
cat <<EOF
# HELP nextcloud_users_total Total number of users
# TYPE nextcloud_users_total gauge
nextcloud_users_total $num_users
# HELP nextcloud_files_total Total number of files
# TYPE nextcloud_files_total gauge
nextcloud_files_total $num_files
# HELP nextcloud_storages_total Total number of storages
# TYPE nextcloud_storages_total gauge
nextcloud_storages_total $num_storages
EOF
echo ""
# ========================================================================
# ACTIVE USERS
# ========================================================================
local active_5m active_1h active_24h
active_5m=$(jq_num "$data" '.activeUsers.last5minutes')
active_1h=$(jq_num "$data" '.activeUsers.last1hour')
active_24h=$(jq_num "$data" '.activeUsers.last24hours')
cat <<EOF
# HELP nextcloud_active_users Number of active users by period
# TYPE nextcloud_active_users gauge
nextcloud_active_users{period="5m"} $active_5m
nextcloud_active_users{period="1h"} $active_1h
nextcloud_active_users{period="24h"} $active_24h
EOF
echo ""
# ========================================================================
# SHARES
# ========================================================================
local shares_user shares_group shares_link shares_mail shares_fed_sent shares_fed_recv
shares_user=$(jq_num "$data" '.nextcloud.shares.num_shares_user')
shares_group=$(jq_num "$data" '.nextcloud.shares.num_shares_groups')
shares_link=$(jq_num "$data" '.nextcloud.shares.num_shares_link')
shares_mail=$(jq_num "$data" '.nextcloud.shares.num_shares_mail')
shares_fed_sent=$(jq_num "$data" '.nextcloud.shares.num_fed_shares_sent')
shares_fed_recv=$(jq_num "$data" '.nextcloud.shares.num_fed_shares_received')
cat <<EOF
# HELP nextcloud_shares_total Number of shares by type
# TYPE nextcloud_shares_total gauge
nextcloud_shares_total{type="user"} $shares_user
nextcloud_shares_total{type="group"} $shares_group
nextcloud_shares_total{type="link"} $shares_link
nextcloud_shares_total{type="mail"} $shares_mail
nextcloud_shares_total{type="federated_sent"} $shares_fed_sent
nextcloud_shares_total{type="federated_received"} $shares_fed_recv
EOF
echo ""
# ========================================================================
# SYSTEM METRICS
# ========================================================================
local freespace mem_total mem_free swap_total swap_free
freespace=$(jq_num "$data" '.nextcloud.system.freespace')
mem_total=$(jq_num "$data" '.nextcloud.system.mem_total')
mem_free=$(jq_num "$data" '.nextcloud.system.mem_free')
swap_total=$(jq_num "$data" '.nextcloud.system.swap_total')
swap_free=$(jq_num "$data" '.nextcloud.system.swap_free')
# mem_total and mem_free from Nextcloud are in KB, convert to bytes
local mem_total_bytes mem_free_bytes swap_total_bytes swap_free_bytes
mem_total_bytes=$((mem_total * 1024))
mem_free_bytes=$((mem_free * 1024))
swap_total_bytes=$((swap_total * 1024))
swap_free_bytes=$((swap_free * 1024))
cat <<EOF
# HELP nextcloud_free_space_bytes Free disk space in bytes
# TYPE nextcloud_free_space_bytes gauge
nextcloud_free_space_bytes $freespace
# HELP nextcloud_system_mem_total_bytes Total system memory in bytes
# TYPE nextcloud_system_mem_total_bytes gauge
nextcloud_system_mem_total_bytes $mem_total_bytes
# HELP nextcloud_system_mem_free_bytes Free system memory in bytes
# TYPE nextcloud_system_mem_free_bytes gauge
nextcloud_system_mem_free_bytes $mem_free_bytes
# HELP nextcloud_system_swap_total_bytes Total swap space in bytes
# TYPE nextcloud_system_swap_total_bytes gauge
nextcloud_system_swap_total_bytes $swap_total_bytes
# HELP nextcloud_system_swap_free_bytes Free swap space in bytes
# TYPE nextcloud_system_swap_free_bytes gauge
nextcloud_system_swap_free_bytes $swap_free_bytes
EOF
echo ""
# CPU load averages
local cpu_1 cpu_5 cpu_15
cpu_1=$(jq_num "$data" '.nextcloud.system.cpuload[0]')
cpu_5=$(jq_num "$data" '.nextcloud.system.cpuload[1]')
cpu_15=$(jq_num "$data" '.nextcloud.system.cpuload[2]')
cat <<EOF
# HELP nextcloud_system_cpuload System CPU load averages
# TYPE nextcloud_system_cpuload gauge
nextcloud_system_cpuload{core="1"} $cpu_1
nextcloud_system_cpuload{core="5"} $cpu_5
nextcloud_system_cpuload{core="15"} $cpu_15
EOF
echo ""
# ========================================================================
# DATABASE METRICS
# ========================================================================
local db_size
db_size=$(jq_num "$data" '.server.database.size')
cat <<EOF
# HELP nextcloud_database_size_bytes Database size in bytes
# TYPE nextcloud_database_size_bytes gauge
nextcloud_database_size_bytes $db_size
EOF
echo ""
# ========================================================================
# PHP METRICS
# ========================================================================
local php_mem_limit php_upload_max
php_mem_limit=$(jq_num "$data" '.server.php.memory_limit')
php_upload_max=$(jq_num "$data" '.server.php.upload_max_filesize')
cat <<EOF
# HELP nextcloud_php_memory_limit_bytes PHP memory limit in bytes
# TYPE nextcloud_php_memory_limit_bytes gauge
nextcloud_php_memory_limit_bytes $php_mem_limit
# HELP nextcloud_php_upload_max_bytes PHP upload max filesize in bytes
# TYPE nextcloud_php_upload_max_bytes gauge
nextcloud_php_upload_max_bytes $php_upload_max
EOF
echo ""
# ========================================================================
# EXPORTER RUNTIME
# ========================================================================
local script_end script_duration
script_end=$(date +%s)
if [[ "$script_start" =~ ^[0-9]{10}[0-9]+$ ]]; then
local end_ns
end_ns=$(date +%s%N 2>/dev/null || date +%s)
script_duration=$(awk "BEGIN {printf \"%.3f\", ($end_ns - $script_start) / 1000000000}")
else
script_duration=$(( $(date +%s) - script_start ))
fi
cat <<EOF
# HELP nextcloud_exporter_duration_seconds Time to generate all metrics
# TYPE nextcloud_exporter_duration_seconds gauge
nextcloud_exporter_duration_seconds $script_duration
# HELP nextcloud_exporter_last_run_timestamp Unix timestamp of last successful run
# TYPE nextcloud_exporter_last_run_timestamp gauge
nextcloud_exporter_last_run_timestamp $script_end
EOF
echo ""
}
# ============================================================================
# HTTP SERVER MODE
# ============================================================================
run_http_server() {
echo "Starting Nextcloud exporter on port $HTTP_PORT..." >&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 <<EOF
<!DOCTYPE html>
<html>
<head><title>Nextcloud Exporter v1.00</title></head>
<body>
<h1>Nextcloud Prometheus Exporter v1.00</h1>
<p><a href="/metrics">Metrics</a></p>
<p>Exports Nextcloud server info, storage stats, share counts, active users, and system health.</p>
</body>
</html>
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}/.nextcloud_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 10 ]]; 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 "$@"