#!/usr/bin/env bash # # Graylog API Backup # # Backs up Graylog configuration objects via the REST API. # Exports inputs, streams, pipelines, dashboards, alerts, # index sets, users, roles, sidecar configs, content packs, # and lookup tables to individual JSON files in a dated directory. # # Usage: # GRAYLOG_URL="http://graylog.example.com:9000/api" GRAYLOG_TOKEN="abc123" ./graylog-api-backup.sh # GRAYLOG_URL="http://graylog.example.com:9000/api" GRAYLOG_TOKEN="abc123" ./graylog-api-backup.sh --dry-run # GRAYLOG_URL="http://graylog.example.com:9000/api" GRAYLOG_TOKEN="abc123" ./graylog-api-backup.sh --install # # Parameters: # --dry-run Show what would be backed up without writing files # --install Create cron job for daily backup at 3am # --help Show usage # # Environment: # GRAYLOG_URL Graylog API base URL (required, e.g. http://localhost:9000/api) # GRAYLOG_TOKEN API token (required) # BACKUP_DIR Base backup directory (default: /backup/graylog-api) # RETENTION_DAYS Delete backups older than this many days (default: 30) # CURL_TIMEOUT API request timeout in seconds (default: 10) # # Author: Phil Connor # Contact: contact@mylinux.work # Website: https://mylinux.work # License: MIT # Version: 1.01 set -euo pipefail # --- Configuration --- readonly VERSION="1.0" readonly SCRIPT_NAME="$(basename "$0")" GRAYLOG_URL="${GRAYLOG_URL:-}" GRAYLOG_TOKEN="${GRAYLOG_TOKEN:-}" BACKUP_DIR="${BACKUP_DIR:-/backup/graylog-api}" RETENTION_DAYS="${RETENTION_DAYS:-30}" CURL_TIMEOUT="${CURL_TIMEOUT:-10}" DRY_RUN=false # Backup endpoints: "api_path output_filename" readonly ENDPOINTS=( "system/inputs inputs.json" "streams streams.json" "system/pipelines/pipeline pipelines.json" "system/pipelines/rule pipeline-rules.json" "system/pipelines/connections pipeline-connections.json" "views dashboards.json" "alerts/definitions alert-definitions.json" "alerts/notifications alert-notifications.json" "system/indices/index_sets index-sets.json" "users users.json" "roles roles.json" "sidecar/configurations sidecar-configs.json" "system/content_packs content-packs.json" "system/lookup/tables lookup-tables.json" "system/lookup/adapters lookup-adapters.json" "system/lookup/caches lookup-caches.json" ) # --- Functions --- usage() { cat </dev/null; then missing+=("$cmd") fi done if [[ ${#missing[@]} -gt 0 ]]; then echo "ERROR: Missing required commands: ${missing[*]}" >&2 echo "Install with: apt install ${missing[*]} OR dnf install ${missing[*]}" >&2 exit 1 fi } validate_config() { if [[ -z "$GRAYLOG_URL" ]]; then echo "ERROR: GRAYLOG_URL environment variable is required" >&2 exit 1 fi if [[ -z "$GRAYLOG_TOKEN" ]]; then echo "ERROR: GRAYLOG_TOKEN environment variable is required" >&2 exit 1 fi # Strip trailing slash GRAYLOG_URL="${GRAYLOG_URL%/}" } api_get() { local endpoint="$1" curl -sf --max-time "$CURL_TIMEOUT" \ -u "${GRAYLOG_TOKEN}:token" \ -H "Accept: application/json" \ "${GRAYLOG_URL}/${endpoint}" 2>/dev/null || echo "" } backup_endpoint() { local endpoint="$1" local filename="$2" local output_dir="$3" local response if [[ "$DRY_RUN" == true ]]; then printf " %-35s → %s (dry-run)\n" "$endpoint" "$filename" return 0 fi response=$(api_get "$endpoint") if [[ -z "$response" ]]; then printf " %-35s → %-30s FAIL\n" "$endpoint" "$filename" return 1 fi echo "$response" | jq '.' > "${output_dir}/${filename}" local size size=$(du -h "${output_dir}/${filename}" | cut -f1) printf " %-35s → %-30s OK %s\n" "$endpoint" "$filename" "$size" return 0 } cleanup_old_backups() { if [[ ! -d "$BACKUP_DIR" ]]; then return fi local removed=0 while IFS= read -r dir; do rm -rf "$dir" ((removed++)) || true done < <(find "$BACKUP_DIR" -maxdepth 1 -mindepth 1 -type d -mtime +"$RETENTION_DAYS" 2>/dev/null) if [[ $removed -gt 0 ]]; then echo "Retention: removed $removed backup(s) older than ${RETENTION_DAYS} days" fi } install_cron() { if [[ $EUID -ne 0 ]]; then echo "ERROR: --install requires root" >&2 exit 1 fi local script_path script_path=$(readlink -f "$0") cat > /etc/cron.d/graylog-api-backup </dev/null EOF chmod 644 /etc/cron.d/graylog-api-backup echo "Installed cron job: /etc/cron.d/graylog-api-backup" echo "Backups will be written to: ${BACKUP_DIR}/" } # --- Main --- main() { # Parse arguments for arg in "$@"; do case "$arg" in --dry-run) DRY_RUN=true ;; --install) check_dependencies validate_config install_cron exit 0 ;; --help|-h) usage ;; *) echo "Unknown option: $arg" >&2; usage ;; esac done check_dependencies validate_config local today today=$(date +%Y%m%d) local output_dir="${BACKUP_DIR}/${today}" if [[ "$DRY_RUN" == true ]]; then echo "Graylog API Backup v${VERSION} (dry-run)" echo "Target: ${output_dir}" echo "" else mkdir -p "$output_dir" echo "Graylog API Backup v${VERSION}" echo "Target: ${output_dir}" echo "" fi local ok=0 local fail=0 for entry in "${ENDPOINTS[@]}"; do local endpoint filename endpoint="${entry%% *}" filename="${entry##* }" if backup_endpoint "$endpoint" "$filename" "$output_dir"; then ((ok++)) || true else ((fail++)) || true fi done echo "" if [[ "$DRY_RUN" == true ]]; then echo "Dry-run complete: ${#ENDPOINTS[@]} endpoints" else local total_size total_size=$(du -sh "$output_dir" | cut -f1) echo "Complete: ${ok} OK, ${fail} failed, ${total_size} total" cleanup_old_backups fi } main "$@"