Files
linux-scripts/grafana-backup.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

397 lines
10 KiB
Bash

#!/bin/bash
################################################
#### Grafana Backup & Restore Script ####
#### Backup dashboards, datasources, alert ####
#### rules, and folders via the HTTP API ####
#### ####
#### Author: Phil Connor ####
#### Contact: contact@mylinux.work ####
#### Version: 1.0.0.20260309 ####
################################################
set -o pipefail
SCRIPT_NAME=$(basename "$0")
readonly SCRIPT_NAME
# Default configuration
readonly DEFAULT_BACKUP_DIR="/var/backups/grafana"
readonly DEFAULT_RETENTION_COUNT=7
readonly DEFAULT_CURL_TIMEOUT=30
# Configuration variables (can be overridden by environment)
GRAFANA_URL=${GRAFANA_URL:-}
GRAFANA_TOKEN=${GRAFANA_TOKEN:-}
BACKUP_DIR=${BACKUP_DIR:-$DEFAULT_BACKUP_DIR}
RETENTION_COUNT=${RETENTION_COUNT:-$DEFAULT_RETENTION_COUNT}
# Runtime
RUN_MODE="backup"
RESTORE_DIR=""
handle_error() {
local exit_code=$1
local line_number=$2
echo "Error: $SCRIPT_NAME failed at line $line_number with exit code $exit_code" >&2
exit "$exit_code"
}
trap 'handle_error $? $LINENO' ERR
show_help() {
cat << EOF
Usage: $SCRIPT_NAME [OPTIONS]
Backup and restore Grafana dashboards, datasources, alert rules, and folders
via the HTTP API. Creates timestamped backup directories with automatic retention.
OPTIONS:
--backup Run a full backup (default)
--restore DIR Restore from the specified backup directory
--list List available backups
--help, -h Show this help message
ENVIRONMENT VARIABLES:
GRAFANA_URL Grafana base URL (required, e.g. http://localhost:3000)
GRAFANA_TOKEN Grafana API token with Admin permissions (required)
BACKUP_DIR Root backup directory (default: $DEFAULT_BACKUP_DIR)
RETENTION_COUNT Number of backups to retain (default: $DEFAULT_RETENTION_COUNT)
EXAMPLES:
GRAFANA_URL=http://localhost:3000 GRAFANA_TOKEN=glsa_xxxx $SCRIPT_NAME --backup
GRAFANA_URL=http://localhost:3000 GRAFANA_TOKEN=glsa_xxxx $SCRIPT_NAME --restore /var/backups/grafana/20260309-143022
GRAFANA_URL=http://localhost:3000 GRAFANA_TOKEN=glsa_xxxx $SCRIPT_NAME --list
EOF
exit 0
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--backup) RUN_MODE="backup"; shift ;;
--restore) RUN_MODE="restore"; RESTORE_DIR="$2"; shift 2 ;;
--list) RUN_MODE="list"; shift ;;
--help|-h) show_help ;;
*) echo "Unknown option: $1" >&2; show_help ;;
esac
done
validate_config() {
if [[ -z "$GRAFANA_URL" ]]; then
echo "Error: GRAFANA_URL is required" >&2
exit 1
fi
if [[ -z "$GRAFANA_TOKEN" ]]; then
echo "Error: GRAFANA_TOKEN is required" >&2
exit 1
fi
# Strip trailing slash
GRAFANA_URL="${GRAFANA_URL%/}"
# Check dependencies
for cmd in curl jq; do
if ! command -v "$cmd" &>/dev/null; then
echo "Error: $cmd is required but not installed" >&2
exit 1
fi
done
}
grafana_api() {
local method="$1"
local endpoint="$2"
local data="${3:-}"
local url="${GRAFANA_URL}${endpoint}"
local args=(
-sf
--max-time "$DEFAULT_CURL_TIMEOUT"
-H "Authorization: Bearer ${GRAFANA_TOKEN}"
-H "Content-Type: application/json"
-H "Accept: application/json"
-X "$method"
)
if [[ -n "$data" ]]; then
args+=(-d "$data")
fi
curl "${args[@]}" "$url"
}
backup_dashboards() {
local dest="$1/dashboards"
mkdir -p "$dest"
echo "Backing up dashboards..."
local search_result
search_result=$(grafana_api GET "/api/search?type=dash-db&limit=5000") || {
echo " Error: Failed to search dashboards" >&2
return 1
}
local count
count=$(echo "$search_result" | jq 'length')
echo " Found $count dashboards"
echo "$search_result" | jq -r '.[].uid' | while IFS= read -r uid; do
local dashboard
dashboard=$(grafana_api GET "/api/dashboards/uid/$uid") || {
echo " Warning: Failed to export dashboard $uid" >&2
continue
}
local title
title=$(echo "$dashboard" | jq -r '.dashboard.title // "unknown"' | tr '/ ' '__')
echo "$dashboard" | jq '.' > "$dest/${uid}_${title}.json"
done
echo " Dashboards saved to $dest"
}
backup_datasources() {
local dest="$1/datasources"
mkdir -p "$dest"
echo "Backing up datasources..."
local result
result=$(grafana_api GET "/api/datasources") || {
echo " Error: Failed to fetch datasources" >&2
return 1
}
local count
count=$(echo "$result" | jq 'length')
echo " Found $count datasources"
echo "$result" | jq -c '.[]' | while IFS= read -r ds; do
local id name
id=$(echo "$ds" | jq -r '.id')
name=$(echo "$ds" | jq -r '.name' | tr '/ ' '__')
echo "$ds" | jq '.' > "$dest/${id}_${name}.json"
done
echo " Datasources saved to $dest"
}
backup_alert_rules() {
local dest="$1/alert_rules"
mkdir -p "$dest"
echo "Backing up alert rules..."
local result
result=$(grafana_api GET "/api/v1/provisioning/alert-rules") || {
echo " Error: Failed to fetch alert rules" >&2
return 1
}
local count
count=$(echo "$result" | jq 'length')
echo " Found $count alert rules"
echo "$result" | jq -c '.[]' | while IFS= read -r rule; do
local uid title
uid=$(echo "$rule" | jq -r '.uid // .id // "unknown"')
title=$(echo "$rule" | jq -r '.title // "unknown"' | tr '/ ' '__')
echo "$rule" | jq '.' > "$dest/${uid}_${title}.json"
done
echo " Alert rules saved to $dest"
}
backup_folders() {
local dest="$1/folders"
mkdir -p "$dest"
echo "Backing up folders..."
local result
result=$(grafana_api GET "/api/folders?limit=1000") || {
echo " Error: Failed to fetch folders" >&2
return 1
}
local count
count=$(echo "$result" | jq 'length')
echo " Found $count folders"
echo "$result" | jq -c '.[]' | while IFS= read -r folder; do
local uid title
uid=$(echo "$folder" | jq -r '.uid')
title=$(echo "$folder" | jq -r '.title' | tr '/ ' '__')
echo "$folder" | jq '.' > "$dest/${uid}_${title}.json"
done
echo " Folders saved to $dest"
}
restore_dashboards() {
local src="$1/dashboards"
if [[ ! -d "$src" ]]; then
echo " No dashboards directory found, skipping" >&2
return 0
fi
echo "Restoring dashboards..."
local count=0
for file in "$src"/*.json; do
[[ -f "$file" ]] || continue
local payload
payload=$(jq '{dashboard: .dashboard, overwrite: true, folderId: (.meta.folderId // 0)}' "$file") || {
echo " Warning: Failed to parse $file" >&2
continue
}
if grafana_api POST "/api/dashboards/db" "$payload" >/dev/null; then
count=$((count + 1))
else
local title
title=$(basename "$file" .json)
echo " Warning: Failed to restore dashboard $title" >&2
fi
done
echo " Restored $count dashboards"
}
restore_datasources() {
local src="$1/datasources"
if [[ ! -d "$src" ]]; then
echo " No datasources directory found, skipping" >&2
return 0
fi
echo "Restoring datasources..."
local count=0
for file in "$src"/*.json; do
[[ -f "$file" ]] || continue
local payload
payload=$(jq 'del(.id, .uid, .readOnly)' "$file") || {
echo " Warning: Failed to parse $file" >&2
continue
}
if grafana_api POST "/api/datasources" "$payload" >/dev/null; then
count=$((count + 1))
else
local name
name=$(basename "$file" .json)
echo " Warning: Failed to restore datasource $name" >&2
fi
done
echo " Restored $count datasources"
}
prune_backups() {
echo "Pruning old backups (retaining $RETENTION_COUNT)..."
local backup_count
backup_count=$(find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 -type d | wc -l)
if [[ $backup_count -le $RETENTION_COUNT ]]; then
echo " No pruning needed ($backup_count backups present)"
return 0
fi
local remove_count=$((backup_count - RETENTION_COUNT))
find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 -type d | sort | head -n "$remove_count" | while IFS= read -r dir; do
echo " Removing $(basename "$dir")"
rm -rf "$dir"
done
echo " Pruned $remove_count old backups"
}
do_backup() {
local timestamp
timestamp=$(date +%Y%m%d-%H%M%S)
local dest="$BACKUP_DIR/$timestamp"
mkdir -p "$dest"
echo "Starting backup to $dest"
backup_dashboards "$dest"
backup_datasources "$dest"
backup_alert_rules "$dest"
backup_folders "$dest"
prune_backups
echo "Backup complete: $dest"
}
do_restore() {
if [[ -z "$RESTORE_DIR" ]]; then
echo "Error: --restore requires a directory argument" >&2
exit 1
fi
if [[ ! -d "$RESTORE_DIR" ]]; then
echo "Error: Restore directory does not exist: $RESTORE_DIR" >&2
exit 1
fi
echo "Restoring from $RESTORE_DIR"
restore_dashboards "$RESTORE_DIR"
restore_datasources "$RESTORE_DIR"
echo "Restore complete"
}
do_list() {
if [[ ! -d "$BACKUP_DIR" ]]; then
echo "No backups found (directory does not exist: $BACKUP_DIR)"
return 0
fi
local count=0
for dir in "$BACKUP_DIR"/*/; do
[[ -d "$dir" ]] || continue
count=$((count + 1))
local name
name=$(basename "$dir")
local dashboards datasources alerts folders
dashboards=$(find "$dir/dashboards" -name '*.json' 2>/dev/null | wc -l)
datasources=$(find "$dir/datasources" -name '*.json' 2>/dev/null | wc -l)
alerts=$(find "$dir/alert_rules" -name '*.json' 2>/dev/null | wc -l)
folders=$(find "$dir/folders" -name '*.json' 2>/dev/null | wc -l)
printf " %s dashboards:%-4s datasources:%-4s alerts:%-4s folders:%-4s\n" \
"$name" "$dashboards" "$datasources" "$alerts" "$folders"
done
if [[ $count -eq 0 ]]; then
echo "No backups found in $BACKUP_DIR"
else
echo "$count backup(s) in $BACKUP_DIR"
fi
}
main() {
validate_config
case "$RUN_MODE" in
backup) do_backup ;;
restore) do_restore ;;
list) do_list ;;
esac
}
main