#!/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 <&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 </dev/null) if [[ -z "$data" || "$data" == "null" ]]; then cat </dev/null || date +%s) script_duration=$(awk "BEGIN {printf \"%.3f\", ($end_ns - $script_start) / 1000000000}") else script_duration=$(( $(date +%s) - script_start )) fi 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 < Nextcloud Exporter v1.00

Nextcloud Prometheus Exporter v1.00

Metrics

Exports Nextcloud server info, storage stats, share counts, active users, and system health.

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 "$@"