#!/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 <<EOF
Usage: $SCRIPT_NAME [OPTIONS]

Graylog API Backup

Options:
  --dry-run     Show what would be backed up without writing files
  --install     Create cron job for daily backup at 3am
  --help        Show this help message

Environment Variables:
  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 N days (default: 30)
  CURL_TIMEOUT      Request timeout in seconds (default: 10)

Examples:
  GRAYLOG_URL="http://graylog.example.com:9000/api" GRAYLOG_TOKEN="abc123" $SCRIPT_NAME
  GRAYLOG_URL="http://graylog.example.com:9000/api" GRAYLOG_TOKEN="abc123" $SCRIPT_NAME --dry-run
  GRAYLOG_URL="http://graylog.example.com:9000/api" GRAYLOG_TOKEN="abc123" $SCRIPT_NAME --install
EOF
    exit 0
}

check_dependencies() {
    local missing=()
    for cmd in curl jq; do
        if ! command -v "$cmd" &>/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 <<EOF
# Graylog API Backup — runs daily at 3am
GRAYLOG_URL=${GRAYLOG_URL}
GRAYLOG_TOKEN=${GRAYLOG_TOKEN}
BACKUP_DIR=${BACKUP_DIR}
RETENTION_DAYS=${RETENTION_DAYS}
0 3 * * * root ${script_path} 2>/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}/<date>"
}

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