first commit

This commit is contained in:
2026-03-02 20:53:28 +01:00
commit d27c205106
63 changed files with 4593 additions and 0 deletions

114
templates/aurora.sh Normal file
View File

@@ -0,0 +1,114 @@
#!/bin/bash
# Version: 2.5.1
# Author: Tomi Eckert
# ==============================================================================
# Aurora Refresh Script
#
# Performs an automated refresh of a SAP HANA schema using hanatool.sh.
# It exports a production schema and re-imports it under a new name ("Aurora")
# to create an up-to-date, non-production environment for testing.
# ==============================================================================
# --- Configuration and Setup ---
# Find the script's own directory to locate the config file and hanatool.sh
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)
CONFIG_FILE="${SCRIPT_DIR}/aurora.conf"
HANATOOL_PATH="${SCRIPT_DIR}/hanatool.sh"
# Check for config file and source it
if [[ -f "$CONFIG_FILE" ]]; then
source "$CONFIG_FILE"
else
echo "❌ Error: Configuration file not found at '${CONFIG_FILE}'"
exit 1
fi
# Check if hanatool.sh executable exists
if [[ ! -x "$HANATOOL_PATH" ]]; then
echo "❌ Error: hanatool.sh not found or not executable at '${HANATOOL_PATH}'"
exit 1
fi
# --- Derived Variables ---
AURORA_SCHEMA="${SOURCE_SCHEMA}_AURORA"
EXPORT_DIR="${BACKUP_BASE_DIR}/${AURORA_SCHEMA}_TEMP_EXPORT"
# --- Main Execution ---
echo "🚀 Starting Aurora Refresh for '${SOURCE_SCHEMA}' using hanatool.sh..."
# 1. Drop the old Aurora schema if it exists.
echo "🗑️ Dropping old schema '${AURORA_SCHEMA}' (if it exists)..."
"$HDBSQL" -U "$DB_ADMIN_KEY" "DROP SCHEMA \"${AURORA_SCHEMA}\" CASCADE" >/dev/null 2>&1 || echo " -> Schema did not exist. Continuing."
# 2. Prepare the temporary export directory.
echo "📁 Preparing temporary export directory..."
rm -rf "$EXPORT_DIR"
mkdir -p "$EXPORT_DIR"
# 3. Export the source schema using hanatool.sh
echo "⬇️ Exporting source schema '${SOURCE_SCHEMA}'..."
"$HANATOOL_PATH" "$DB_ADMIN_KEY" export "$SOURCE_SCHEMA" "$EXPORT_DIR" -t "$THREADS"
if [[ $? -ne 0 ]]; then
echo "❌ Error: Export failed."
exit 1
fi
# 4. Import the data into the new Aurora schema using hanatool.sh
echo "⬆️ Importing data and renaming schema to '${AURORA_SCHEMA}'..."
"$HANATOOL_PATH" "$DB_ADMIN_KEY" import-rename "$SOURCE_SCHEMA" "$AURORA_SCHEMA" "$EXPORT_DIR" -t "$THREADS"
if [[ $? -ne 0 ]]; then
echo "❌ Error: Import failed."
exit 1
fi
# 5. Update company name in CINF and OADM tables.
echo "✍️ Updating company name fields in the new schema..."
# First, get the original company name from the source schema.
echo " -> Fetching original company name from '${SOURCE_SCHEMA}'..."
ORIGINAL_COMPNY_NAME=$("$HDBSQL" -U "$DB_ADMIN_KEY" "SELECT \"CompnyName\" FROM \"${SOURCE_SCHEMA}\".\"CINF\"" | sed -n '2p' | tr -d '"' | xargs)
# Construct the new name in the desired format.
DATE_STAMP=$(date "+%Y-%m-%d")
NEW_COMPNY_NAME="AURORA - ${ORIGINAL_COMPNY_NAME} - ${DATE_STAMP}"
echo " -> New company name set to: '${NEW_COMPNY_NAME}'"
echo " -> Updating CINF table..."
"$HDBSQL" -U "$DB_ADMIN_KEY" "UPDATE \"${AURORA_SCHEMA}\".CINF SET \"CompnyName\" = '${NEW_COMPNY_NAME}';" >/dev/null
echo " -> Updating OADM table..."
"$HDBSQL" -U "$DB_ADMIN_KEY" "UPDATE \"${AURORA_SCHEMA}\".OADM SET \"CompnyName\" = '${NEW_COMPNY_NAME}', \"PrintHeadr\" = '${NEW_COMPNY_NAME}';" >/dev/null
echo " -> Company info updated."
# 6. Grant privileges to the read/write user.
echo "🔑 Granting ALL privileges on '${AURORA_SCHEMA}' to '${AURORA_USER}'..."
"$HDBSQL" -U "$DB_ADMIN_KEY" "GRANT ALL PRIVILEGES ON SCHEMA \"${AURORA_SCHEMA}\" TO \"${AURORA_USER}\";" >/dev/null
echo " -> Privileges granted."
# 7. Run post-import SQL scripts, if any are defined.
if [[ -n "$POST_IMPORT_SQL" ]]; then
echo "⚙️ Running post-import SQL scripts..."
for sql_file in $POST_IMPORT_SQL; do
full_path="${SQL_SCRIPTS_ROOT}/${sql_file}"
if [[ -f "$full_path" ]]; then
echo " -> Executing: ${sql_file}"
"$HDBSQL" -U "$DB_ADMIN_KEY" -I "$full_path"
else
echo " -> ⚠️ WARNING: Script not found: ${full_path}" >&2
fi
done
else
echo " No post-import SQL scripts to run."
fi
# 8. Clean up the temporary export files.
echo "🧹 Cleaning up temporary directory '${EXPORT_DIR}'..."
rm -rf "$EXPORT_DIR"
echo " -> Cleanup complete."
echo "--------------------------------------------------------"
echo "✅ Aurora Refresh finished successfully!"
echo
exit 0

133
templates/backup.sh Normal file
View File

@@ -0,0 +1,133 @@
#!/bin/bash
# Version: 1.1.0
# Author: Tomi Eckert
# ==============================================================================
# SAP HANA Backup Script
#
# Performs schema exports for one or more schemas and/or tenant backups for a
# SAP HANA database using hanatool.sh. Designed to be executed via a cronjob.
# Reads all settings from the backup.conf file in the same directory.
# ==============================================================================
# --- Configuration and Setup ---
# Find the script's own directory to locate the config file and hanatool.sh
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)
CONFIG_FILE="${SCRIPT_DIR}/backup.conf"
HANATOOL_PATH="${SCRIPT_DIR}/hanatool.sh" # Assuming hanatool.sh is in the parent directory
# Check for config file and source it
if [[ -f "$CONFIG_FILE" ]]; then
source "$CONFIG_FILE"
else
echo "❌ Error: Configuration file not found at '${CONFIG_FILE}'"
exit 1
fi
# Check if hanatool.sh executable exists
if [[ ! -x "$HANATOOL_PATH" ]]; then
echo "❌ Error: hanatool.sh not found or not executable at '${HANATOOL_PATH}'"
exit 1
fi
# --- Main Execution ---
echo "⚙️ Starting HANA backup process using hanatool.sh..."
mkdir -p "$BACKUP_BASE_DIR"
SCHEMA_EXPORT_OPTIONS=""
case "$BACKUP_TYPE" in
schema)
if [[ -z "$SCHEMA_NAMES" ]]; then
echo " ⚠️ Warning: SCHEMA_NAMES variable is not set in config. Skipping schema export."
else
echo "🔎 Found schemas to export: ${SCHEMA_NAMES}"
for schema in $SCHEMA_NAMES; do
echo "⬇️ Starting schema export for '${schema}'..."
SCHEMA_EXPORT_OPTIONS="$COMMON_OPTIONS"
if [[ -n "$THREADS" ]]; then
SCHEMA_EXPORT_OPTIONS+=" -t $THREADS"
fi
if [[ "$COMPRESS_SCHEMA" == "true" ]]; then
SCHEMA_EXPORT_OPTIONS+=" --compress"
fi
"$HANATOOL_PATH" "$USER_KEY" export "$schema" "${BACKUP_BASE_DIR}/schema" $SCHEMA_EXPORT_OPTIONS
if [[ $? -ne 0 ]]; then
echo "❌ Error: Schema export for '${schema}' failed."
fi
echo "--------------------------------------------------"
done
fi
;;
tenant)
echo "⬇️ Starting Tenant backup..."
TENANT_BACKUP_OPTIONS="$COMMON_OPTIONS"
if [[ "$COMPRESS_TENANT" == "true" ]]; then
TENANT_BACKUP_OPTIONS+=" --compress"
fi
"$HANATOOL_PATH" "$USER_KEY" backup "${BACKUP_BASE_DIR}/tenant" $TENANT_BACKUP_OPTIONS
if [[ $? -ne 0 ]]; then
echo "❌ Error: Tenant backup failed."
fi
;;
all)
if [[ -z "$SCHEMA_NAMES" ]]; then
echo " ⚠️ Warning: SCHEMA_NAMES variable is not set in config. Skipping schema export."
else
echo "🔎 Found schemas to export: ${SCHEMA_NAMES}"
for schema in $SCHEMA_NAMES; do
echo "⬇️ Starting schema export for '${schema}'..."
SCHEMA_EXPORT_OPTIONS="$COMMON_OPTIONS"
if [[ -n "$THREADS" ]]; then
SCHEMA_EXPORT_OPTIONS+=" -t $THREADS"
fi
if [[ "$COMPRESS_SCHEMA" == "true" ]]; then
SCHEMA_EXPORT_OPTIONS+=" --compress"
fi
"$HANATOOL_PATH" "$USER_KEY" export "$schema" "${BACKUP_BASE_DIR}/schema" $SCHEMA_EXPORT_OPTIONS
if [[ $? -ne 0 ]]; then
echo "❌ Error: Schema export for '${schema}' failed."
fi
echo "--------------------------------------------------"
done
fi
echo "⬇️ Starting Tenant backup..."
TENANT_BACKUP_OPTIONS="$COMMON_OPTIONS"
if [[ "$COMPRESS_TENANT" == "true" ]]; then
TENANT_BACKUP_OPTIONS+=" --compress"
fi
"$HANATOOL_PATH" "$USER_KEY" backup "${BACKUP_BASE_DIR}/tenant" $TENANT_BACKUP_OPTIONS
if [[ $? -ne 0 ]]; then
echo "❌ Error: Tenant backup failed."
fi
;;
*)
echo " ❌ Error: Invalid BACKUP_TYPE '${BACKUP_TYPE}' in config. Use 'schema', 'tenant', or 'all'."
;;
esac
# Check if SYSTEMDB backup is enabled, regardless of BACKUP_TYPE (as long as it's not 'schema' only)
if [[ "$BACKUP_TYPE" == "tenant" || "$BACKUP_TYPE" == "all" ]]; then
if [[ "$BACKUP_SYSTEMDB" == "true" ]]; then
echo "--------------------------------------------------"
if [[ -z "$SYSTEMDB_USER_KEY" ]]; then
echo " ❌ Error: BACKUP_SYSTEMDB is true, but SYSTEMDB_USER_KEY is not set in config."
else
echo "⬇️ Starting SYSTEMDB backup..."
SYSTEMDB_BACKUP_OPTIONS="$COMMON_OPTIONS"
if [[ "$COMPRESS_TENANT" == "true" ]]; then # SYSTEMDB compression uses COMPRESS_TENANT setting
SYSTEMDB_BACKUP_OPTIONS+=" --compress"
fi
"$HANATOOL_PATH" "$SYSTEMDB_USER_KEY" backup "${BACKUP_BASE_DIR}/tenant" $SYSTEMDB_BACKUP_OPTIONS
if [[ $? -ne 0 ]]; then
echo "❌ Error: SYSTEMDB backup failed."
fi
fi
fi
fi
echo "📦 Backup process complete."
echo "👋 Exiting."

31
templates/cleaner.sh Normal file
View File

@@ -0,0 +1,31 @@
#!/bin/bash
# Version: 1.1.0
# Author: Tomi Eckert
# Check if any arguments were provided
if [ "$#" -eq 0 ]; then
echo "Usage: $0 <retention_days>:<path> [<retention_days>:<path> ...]"
exit 1
fi
# Loop through each argument provided
for ARG in "$@"; do
# Split the argument at the first colon
IFS=':' read -r RETENTION_DAYS TARGET_DIR <<< "$ARG"
# Validate that both a retention period and a path were provided
if [ -z "$RETENTION_DAYS" ] || [ -z "$TARGET_DIR" ]; then
echo "Invalid format for argument: $ARG. Please use the format <retention_days>:<path>"
continue
fi
echo "Starting cleanup of files older than $RETENTION_DAYS days in $TARGET_DIR..."
# Use find to locate and delete files, handling potential errors
find "$TARGET_DIR" -type f -mtime +"$RETENTION_DAYS" -delete -print || echo "Could not process $TARGET_DIR. Check permissions."
echo "Cleanup complete for $TARGET_DIR."
echo "--------------------------------------------------"
done
echo "All cleanup tasks finished."

354
templates/firewalld.sh Normal file
View File

@@ -0,0 +1,354 @@
#!/bin/bash
# ==========================================
# INTERACTIVE FIREWALL CONFIGURATOR FOR SAP B1
# (With Save/Load State)
# ==========================================
# Configuration File
CONFIG_FILE="./firewall_state.conf"
# Colors for formatting
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# ==========================================
# SERVICE DEFINITIONS
# ==========================================
declare -a SVC_NAMES
declare -a SVC_PORTS
SVC_NAMES[0]="SAP Web Client"
SVC_PORTS[0]="443"
SVC_NAMES[1]="SAP HANA Database (System & Company DB)"
SVC_PORTS[1]="30013 30015"
SVC_NAMES[2]="SAP Business One SLD"
SVC_PORTS[2]="40000"
SVC_NAMES[3]="SAP Business One Auth"
SVC_PORTS[3]="40020"
SVC_NAMES[4]="SAP Business One Service Layer, Cockpit"
SVC_PORTS[4]="50000 4300"
SVC_NAMES[5]="SAP Host Agent"
SVC_PORTS[5]="1128 1129"
SVC_NAMES[6]="SSH Remote Access"
SVC_PORTS[6]="22"
SVC_NAMES[7]="SMB / B1_SHR (File Sharing)"
SVC_PORTS[7]="139 445"
# Arrays to store user decisions
declare -a CONFIG_DECISION # "ALL", "IP", "SKIP"
declare -a CONFIG_IPS # Stores the IP string if "IP" is chosen
DO_FLUSH=false
# ==========================================
# HELPER FUNCTIONS
# ==========================================
print_header() {
clear
echo -e "${CYAN}==========================================${NC}"
echo -e "${CYAN} SAP B1 Interactive Firewall Setup ${NC}"
echo -e "${CYAN}==========================================${NC}"
echo ""
}
save_config() {
echo "# SAP B1 Firewall State - Do not edit manually unless you know what you are doing" > "$CONFIG_FILE"
for i in "${!SVC_NAMES[@]}"; do
# Use simple variable assignment format
echo "SAVED_DECISION[$i]=\"${CONFIG_DECISION[$i]}\"" >> "$CONFIG_FILE"
echo "SAVED_IPS[$i]=\"${CONFIG_IPS[$i]}\"" >> "$CONFIG_FILE"
done
echo "Config state saved to $CONFIG_FILE"
}
load_config() {
if [ -f "$CONFIG_FILE" ]; then
echo -e "${GREEN}Found saved configuration file.${NC}"
source "$CONFIG_FILE"
# Map SAVED variables to current CONFIG variables
for i in "${!SVC_NAMES[@]}"; do
CONFIG_DECISION[$i]="${SAVED_DECISION[$i]}"
CONFIG_IPS[$i]="${SAVED_IPS[$i]}"
done
return 0
else
return 1
fi
}
# ==========================================
# INITIAL SETUP
# ==========================================
print_header
# 1. Flush Question (First)
echo -e "${YELLOW}Existing Configuration:${NC}"
echo "Do you want to FLUSH (remove) all currently active firewall rules before starting?"
echo "This ensures a clean slate. (Only affects Runtime, not Permanent config)"
read -p "Flush all current rules? [Y/n]: " flush_choice
flush_choice=${flush_choice:-Y} # Default to Yes
if [[ "$flush_choice" =~ ^[Yy]$ ]]; then
DO_FLUSH=true
echo -e "-> ${RED}Will flush all rules (Clean Slate).${NC}"
else
echo -e "-> Keeping existing rules (Appending new ones)."
fi
echo ""
sleep 1
# 2. Load Configuration (Second)
HAS_CONFIG=false
if load_config; then
HAS_CONFIG=true
echo "Previous settings loaded. You can press ENTER to accept defaults during selection."
else
echo "No previous configuration found. Starting fresh."
fi
echo ""
sleep 1
# ==========================================
# CONFIGURATION LOOP
# ==========================================
print_header
echo -e "This script will help you configure access rules for each service."
echo -e "${YELLOW}Note: Configuration applies to RUNTIME only.${NC}"
echo ""
for i in "${!SVC_NAMES[@]}"; do
NAME="${SVC_NAMES[$i]}"
PORTS="${SVC_PORTS[$i]}"
# Get previous setting if available
PREV_DECISION="${CONFIG_DECISION[$i]}"
PREV_IPS="${CONFIG_IPS[$i]}"
# Determine default option number for UI
DEFAULT_OPT=""
DEFAULT_TXT=""
if [[ "$PREV_DECISION" == "ALL" ]]; then DEFAULT_OPT="1"; DEFAULT_TXT="[Default: 1 - Public]"; fi
if [[ "$PREV_DECISION" == "IP" ]]; then DEFAULT_OPT="2"; DEFAULT_TXT="[Default: 2 - Restricted]"; fi
if [[ "$PREV_DECISION" == "SKIP" ]]; then DEFAULT_OPT="3"; DEFAULT_TXT="[Default: 3 - Skip]"; fi
echo -e "--------------------------------------------------"
echo -e "Configuring Service: ${GREEN}$NAME${NC}"
echo -e "Ports: ${YELLOW}$PORTS${NC}"
echo "--------------------------------------------------"
echo "1) Allow from ANYWHERE (Public)"
echo "2) Restrict to SPECIFIC IPs"
echo "3) Skip / Block (Do not open)"
while true; do
read -p "Select option (1-3) $DEFAULT_TXT: " choice
# Handle Enter key (Default)
if [[ -z "$choice" && -n "$DEFAULT_OPT" ]]; then
choice=$DEFAULT_OPT
fi
case $choice in
1)
CONFIG_DECISION[$i]="ALL"
CONFIG_IPS[$i]=""
echo -e "-> Selected: ${RED}Public Access${NC}"
break
;;
2)
CONFIG_DECISION[$i]="IP"
echo ""
echo -e "Enter IPs or Subnets separated by spaces or commas."
# Show previous IPs as default if they exist
if [[ -n "$PREV_IPS" ]]; then
echo -e "Current Saved IPs: ${CYAN}$PREV_IPS${NC}"
read -p "IPs [Press Enter to keep current]: " ip_input
if [[ -z "$ip_input" ]]; then
ip_input="$PREV_IPS"
fi
else
echo -e "Example: ${CYAN}192.168.1.10, 192.168.1.20${NC}"
read -p "IPs: " ip_input
fi
# Replace commas with spaces to sanitize
CONFIG_IPS[$i]="${ip_input//,/ }"
echo -e "-> Selected: ${GREEN}Restricted to ${CONFIG_IPS[$i]}${NC}"
break
;;
3)
CONFIG_DECISION[$i]="SKIP"
CONFIG_IPS[$i]=""
echo -e "-> Selected: ${YELLOW}Skipping${NC}"
break
;;
*)
echo "Invalid option."
;;
esac
done
echo ""
done
# ==========================================
# SUMMARY & CONFIRMATION
# ==========================================
print_header
echo -e "${YELLOW}SUMMARY OF PENDING CHANGES:${NC}"
echo ""
printf "%-25s | %-15s | %-30s\n" "Service" "Action" "Details"
echo "-------------------------------------------------------------------------------"
for i in "${!SVC_NAMES[@]}"; do
NAME="${SVC_NAMES[$i]}"
ACTION="${CONFIG_DECISION[$i]}"
DETAILS="${CONFIG_IPS[$i]}"
# Shorten name for table
SHORT_NAME=${NAME:0:24}
if [ "$ACTION" == "ALL" ]; then
printf "%-25s | ${RED}%-15s${NC} | %-30s\n" "$SHORT_NAME" "Open Public" "0.0.0.0/0"
elif [ "$ACTION" == "IP" ]; then
printf "%-25s | ${GREEN}%-15s${NC} | %-30s\n" "$SHORT_NAME" "Restricted" "$DETAILS"
else
printf "%-25s | ${YELLOW}%-15s${NC} | %-30s\n" "$SHORT_NAME" "Blocked/Skip" "-"
fi
done
echo ""
echo -e "${CYAN}Global Actions:${NC}"
if [ "$DO_FLUSH" = true ]; then
echo "1. FLUSH ALL current rules (Clean Slate)."
else
echo "1. Remove specific insecure rules (0-65535) and standard SSH service."
fi
echo ""
read -p "Do you want to SAVE config and APPLY changes now? [Y/n]: " confirm
confirm=${confirm:-Y} # Default to Yes
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
echo "Aborted."
exit 0
fi
# ==========================================
# EXECUTION
# ==========================================
echo ""
echo "Saving configuration to $CONFIG_FILE..."
save_config
echo "Applying configurations (RUNTIME ONLY)..."
# 1. Flush or Safety Cleanup
if [ "$DO_FLUSH" = true ]; then
echo "-> Flushing active rules..."
# Flush Services
for service in $(firewall-cmd --list-services); do
firewall-cmd --remove-service="$service" >/dev/null 2>&1
done
# Flush Ports
for port in $(firewall-cmd --list-ports); do
firewall-cmd --remove-port="$port" >/dev/null 2>&1
done
# Flush Rich Rules
firewall-cmd --list-rich-rules | while read -r rule; do
if [ -n "$rule" ]; then
firewall-cmd --remove-rich-rule="$rule" >/dev/null 2>&1
fi
done
else
# Only remove specific conflicting rules if not flushing everything
echo "-> Cleaning up insecure rules..."
firewall-cmd --remove-port=0-65535/tcp >/dev/null 2>&1
firewall-cmd --remove-service=ssh >/dev/null 2>&1
firewall-cmd --remove-port=22/tcp >/dev/null 2>&1
fi
# 2. Loop and Apply
for i in "${!SVC_NAMES[@]}"; do
PORTS="${SVC_PORTS[$i]}"
DECISION="${CONFIG_DECISION[$i]}"
# Convert space-separated ports to array for inner loop
read -ra PORT_ARR <<< "$PORTS"
if [ "$DECISION" == "ALL" ]; then
# Open ports globally
for port in "${PORT_ARR[@]}"; do
echo " Opening $port globally..."
firewall-cmd --add-port=${port}/tcp >/dev/null
done
elif [ "$DECISION" == "IP" ]; then
# Add rich rules for specific IPs
read -ra IP_ARR <<< "${CONFIG_IPS[$i]}"
for ip in "${IP_ARR[@]}"; do
if [[ -z "$ip" ]]; then continue; fi
for port in "${PORT_ARR[@]}"; do
echo " Allowing $ip access to port $port..."
firewall-cmd --add-rich-rule="rule family='ipv4' source address='$ip' port port='$port' protocol='tcp' accept" >/dev/null
done
done
fi
done
echo ""
echo -e "${YELLOW}==========================================${NC}"
echo -e "${YELLOW} TESTING CONNECTIVITY ${NC}"
echo -e "${YELLOW}==========================================${NC}"
echo "The new rules are active. If you are locked out, DO NOTHING."
echo "The firewall will automatically REVERT in 15 seconds."
echo ""
echo -e "${GREEN}If you can read this and your connection works:${NC}"
echo -e "Press ${CYAN}ENTER${NC} now to CONFIRM and KEEP the changes."
echo "OR press Ctrl+C to keep changes without saving (script exits)."
echo ""
if read -t 15 -p "Waiting for confirmation... "; then
# User pressed Enter (Connection is good)
echo ""
echo -e "${GREEN}Connection confirmed!${NC}"
echo ""
read -p "Do you want to save these rules PERMANENTLY now? [Y/n]: " save_choice
save_choice=${save_choice:-Y} # Default to Yes
if [[ "$save_choice" =~ ^[Yy]$ ]]; then
echo "Saving to permanent configuration..."
firewall-cmd --runtime-to-permanent
echo -e "${GREEN}Configuration Saved.${NC}"
else
echo "Rules kept in RUNTIME only. They will be lost on reboot."
fi
else
# Timeout occurred (User likely locked out)
echo ""
echo -e "${RED}Timeout reached! Reverting changes...${NC}"
# Revert Logic
echo "1. Reloading permanent configuration..."
firewall-cmd --reload >/dev/null 2>&1
echo "2. Ensuring SSH is accessible (Failsafe)..."
firewall-cmd --add-service=ssh >/dev/null 2>&1
echo -e "${YELLOW}Firewall reverted to previous state (plus global SSH allow).${NC}"
fi
exit 0

471
templates/hanatool.sh Normal file
View File

@@ -0,0 +1,471 @@
#!/bin/bash
# Version: 1.5.8
# Author: Tomi Eckert
# ==============================================================================
# SAP HANA Schema and Tenant Management Tool (hanatool.sh)
#
# A command-line utility to quickly export/import schemas or backup a tenant.
# ==============================================================================
# --- Default Settings ---
# Define potential HDB client paths
HDB_CLIENT_PATH_1="/usr/sap/hdbclient"
HDB_CLIENT_PATH_2="/usr/sap/NDB/HDB00/exe"
# Determine the correct HDB_CLIENT_PATH
if [ -d "$HDB_CLIENT_PATH_1" ]; then
HDB_CLIENT_PATH="$HDB_CLIENT_PATH_1"
elif [ -d "$HDB_CLIENT_PATH_2" ]; then
HDB_CLIENT_PATH="$HDB_CLIENT_PATH_2"
else
echo "❌ Error: Neither '$HDB_CLIENT_PATH_1' nor '$HDB_CLIENT_PATH_2' found."
echo "Please install the SAP HANA client or adjust the paths in the script."
exit 1
fi
HDBSQL_PATH="${HDB_CLIENT_PATH}/hdbsql"
COMPRESS=false
THREADS=0 # 0 means auto-calculate later
DRY_RUN=false
NTFY_TOKEN=""
IMPORT_REPLACE=false
# Detect pigz for parallel compression
if command -v pigz &>/dev/null; then
USE_PIGZ=true
else
USE_PIGZ=false
fi
# --- Help/Usage Function ---
usage() {
echo "SAP HANA Schema and Tenant Management Tool"
echo ""
echo "Usage (Schema): $0 [USER_KEY] export|import [SCHEMA_NAME] [PATH] [OPTIONS]"
echo " (Schema): $0 [USER_KEY] import-rename [SCHEMA_NAME] [NEW_SCHEMA_NAME] [PATH] [OPTIONS]"
echo " (Tenant): $0 [USER_KEY] backup [PATH] [OPTIONS]"
echo ""
echo "Actions:"
echo " export Export a schema to a specified path."
echo " import Import a schema from a specified path."
echo " import-rename Import a schema from a path to a new schema name."
echo " backup Perform a full backup of the tenant."
echo ""
echo "Arguments:"
echo " USER_KEY The user key from hdbuserstore for DB connection."
echo " SCHEMA_NAME The name of the source schema."
echo " NEW_SCHEMA_NAME (Required for import-rename only) The target schema name."
echo " PATH The file system path for the export/import/backup data."
echo ""
echo "Options:"
echo " -t, --threads N Specify the number of threads (not used for 'backup')."
echo " -c, --compress Enable tar.gz compression for exports and backups."
echo " -n, --dry-run Show what commands would be executed without running them."
echo " --ntfy <token> Send a notification via ntfy.sh upon completion/failure."
echo " --replace Use the 'REPLACE' option for imports instead of 'IGNORE EXISTING'."
echo " --hdbsql <path> Specify a custom path for the hdbsql executable."
echo " -h, --help Show this help message."
echo ""
echo "Examples:"
echo " # Backup the tenant determined by MY_TENANT_KEY and compress the result"
echo " $0 MY_TENANT_KEY backup /hana/backups -c --ntfy tk_xxxxxxxxxxxx"
echo ""
echo " # Import MYSCHEMA from a compressed archive"
echo " $0 MY_SCHEMA_KEY import MYSCHEMA /hana/backups/MYSCHEMA_20240101.tar.gz -c"
echo ""
echo " # Import MYSCHEMA as MYSCHEMA_TEST, replacing any existing objects"
echo " $0 MY_SCHEMA_KEY import-rename MYSCHEMA MYSCHEMA_TEST /hana/backups/temp_export --replace"
}
# --- Notification Function ---
send_notification() {
local message="$1"
if [[ -n "$NTFY_TOKEN" && "$DRY_RUN" == "false" ]]; then
echo " Sending notification..."
curl -s -H "Authorization: Bearer $NTFY_TOKEN" -d "$message" https://ntfy.technopunk.space/sap > /dev/null
elif [[ -n "$NTFY_TOKEN" && "$DRY_RUN" == "true" ]]; then
echo "[DRY RUN] Would send notification: curl -H \"Authorization: Bearer ...\" -d \"$message\" https://ntfy.technopunk.space/sap"
fi
}
# --- Function to get HANA tenant name ---
get_hana_tenant_name() {
local user_key="$1"
local hdbsql_path="$2"
local dry_run="$3"
local query="SELECT DATABASE_NAME FROM SYS.M_DATABASES;"
local tenant_name=""
if [[ "$dry_run" == "true" ]]; then
echo "[DRY RUN] Would execute hdbsql to get tenant name: \"$hdbsql_path\" -U \"$user_key\" \"$query\""
tenant_name="DRYRUN_TENANT"
else
tenant_name=$("$hdbsql_path" -U "$user_key" "$query" | tail -n +2 | head -n 1 | tr -d '[:space:]' | tr -d '"')
if [[ -z "$tenant_name" ]]; then
echo "❌ Error: Could not retrieve HANA tenant name using user key '${user_key}'."
exit 1
fi
fi
echo "$tenant_name"
}
# --- Argument Parsing ---
POSITIONAL_ARGS=()
while [[ $# -gt 0 ]]; do
case $1 in
-t|--threads)
THREADS="$2"
shift 2
;;
-c|--compress)
COMPRESS=true
shift
;;
-n|--dry-run)
DRY_RUN=true
shift
;;
--ntfy)
NTFY_TOKEN="$2"
shift 2
;;
--replace)
IMPORT_REPLACE=true
shift
;;
--hdbsql)
HDBSQL_PATH="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
POSITIONAL_ARGS+=("$1") # save positional arg
shift
;;
esac
done
set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters
# Assign common positional arguments
USER_KEY="$1"
ACTION="$2"
# --- Main Logic ---
if [[ "$DRY_RUN" == "true" ]]; then
echo "⚠️ --- DRY RUN MODE ENABLED --- ⚠️"
echo "No actual commands will be executed."
echo "-------------------------------------"
fi
# Check for hdbsql executable
if [[ ! -x "$HDBSQL_PATH" ]]; then
echo "❌ Error: hdbsql not found or not executable at '${HDBSQL_PATH}'"
exit 1
fi
# Calculate default threads if not specified and action is not backup
if [[ "$THREADS" -eq 0 && "$ACTION" != "backup" ]]; then
TOTAL_THREADS=$(nproc --all)
THREADS=$((TOTAL_THREADS / 2))
if [[ "$THREADS" -eq 0 ]]; then
THREADS=1
fi
echo " Auto-detected threads to use: ${THREADS}"
fi
# Execute action based on user input
case "$ACTION" in
backup)
TARGET_PATH="$3"
if [[ -z "$USER_KEY" || -z "$TARGET_PATH" ]]; then
echo "❌ Error: Missing arguments for 'backup' action."
usage
exit 1
fi
echo "⬇️ Starting tenant backup..."
echo " - User Key: ${USER_KEY}"
echo " - Path: ${TARGET_PATH}"
echo " - Compress: ${COMPRESS}"
TENANT_NAME=$(get_hana_tenant_name "$USER_KEY" "$HDBSQL_PATH" "$DRY_RUN")
echo " - Tenant Name: ${TENANT_NAME}"
timestamp=$(date +%Y%m%d_%H%M%S)
backup_target_dir="$TARGET_PATH" # Initialize with TARGET_PATH
backup_path_prefix=""
if [[ "$COMPRESS" == "true" ]]; then
if [[ "$DRY_RUN" == "true" ]]; then
backup_target_dir="${TARGET_PATH}/${TENANT_NAME}_backup_DRYRUN_TEMP" # Use TARGET_PATH
else
backup_target_dir=$(mktemp -d "${TARGET_PATH}/${TENANT_NAME}_backup_${timestamp}_XXXXXXXX") # Use TARGET_PATH
fi
echo " Using temporary backup directory: ${backup_target_dir}"
fi
if [[ "$DRY_RUN" == "true" ]]; then
echo "[DRY RUN] Would create directory: mkdir -p \"$backup_target_dir\""
else
mkdir -p "$backup_target_dir"
fi
backup_path_prefix="${backup_target_dir}/backup_${TENANT_NAME}_${timestamp}"
QUERY="BACKUP DATA USING FILE ('${backup_path_prefix}')"
EXIT_CODE=0
if [[ "$DRY_RUN" == "true" ]]; then
echo "[DRY RUN] Would execute hdbsql: \"$HDBSQL_PATH\" -U \"$USER_KEY\" \"$QUERY\""
else
"$HDBSQL_PATH" -U "$USER_KEY" "$QUERY" > /dev/null 2>&1
EXIT_CODE=$?
fi
if [[ "$EXIT_CODE" -eq 0 ]]; then
echo "✅ Successfully initiated tenant backup with prefix '${backup_path_prefix}'."
if [[ "$COMPRESS" == "true" ]]; then
ARCHIVE_FILE="${TARGET_PATH}/${TENANT_NAME}_backup_${timestamp}.tar.gz"
echo "🗜️ Compressing backup files to '${ARCHIVE_FILE}'..."
TAR_EXIT_CODE=0
if [[ "$DRY_RUN" == "true" ]]; then
if [[ "$USE_PIGZ" == "true" ]]; then
echo "[DRY RUN] Would execute tar (pigz): tar -I \"pigz -p $THREADS\" -cf \"$ARCHIVE_FILE\" -C \"$backup_target_dir\" ."
else
echo "[DRY RUN] Would execute tar: tar -czf \"$ARCHIVE_FILE\" -C \"$backup_target_dir\" ."
fi
else
if [[ "$USE_PIGZ" == "true" ]]; then
tar -I "pigz -p $THREADS" -cf "$ARCHIVE_FILE" -C "$backup_target_dir" .
else
tar -czf "$ARCHIVE_FILE" -C "$backup_target_dir" .
fi
TAR_EXIT_CODE=$?
fi
if [[ "$TAR_EXIT_CODE" -eq 0 ]]; then
echo "✅ Successfully created archive."
echo "🧹 Cleaning up temporary directory..."
if [[ "$DRY_RUN" == "true" ]]; then
echo "[DRY RUN] Would remove temp directory: rm -rf \"$backup_target_dir\""
else
rm -rf "$backup_target_dir"
fi
else
echo "❌ Error: Failed to create archive from '${backup_target_dir}'."
fi
fi
send_notification "✅ HANA tenant '${TENANT_NAME}' backup completed successfully."
else
echo "❌ Error: Failed to initiate tenant backup (hdbsql exit code: ${EXIT_CODE})."
send_notification "❌ HANA tenant '${TENANT_NAME}' backup FAILED."
if [[ "$COMPRESS" == "true" && "$DRY_RUN" == "false" ]]; then rm -rf "$backup_target_dir"; fi
fi
;;
export)
SCHEMA_NAME="$3"
TARGET_PATH="$4"
if [[ -z "$USER_KEY" || -z "$SCHEMA_NAME" || -z "$TARGET_PATH" ]]; then
echo "❌ Error: Missing arguments for 'export' action."
usage
exit 1
fi
echo "⬇️ Starting schema export..."
echo " - User Key: ${USER_KEY}"
echo " - Schema: ${SCHEMA_NAME}"
echo " - Path: ${TARGET_PATH}"
echo " - Compress: ${COMPRESS}"
echo " - Threads: ${THREADS}"
EXPORT_DIR="$TARGET_PATH"
if [[ "$COMPRESS" == "true" ]]; then
if [[ "$DRY_RUN" == "true" ]]; then
EXPORT_DIR="${TARGET_PATH}/export_${SCHEMA_NAME}_DRYRUN_TEMP"
else
EXPORT_DIR=$(mktemp -d "${TARGET_PATH}/export_${SCHEMA_NAME}_XXXXXXXX")
fi
echo " Using temporary export directory: ${EXPORT_DIR}"
fi
if [[ "$DRY_RUN" == "true" ]]; then
echo "[DRY RUN] Would create directory: mkdir -p \"$EXPORT_DIR\""
else
mkdir -p "$EXPORT_DIR"
fi
QUERY="EXPORT \"${SCHEMA_NAME}\".\"*\" AS BINARY INTO '${EXPORT_DIR}' WITH REPLACE THREADS ${THREADS} NO DEPENDENCIES;"
EXIT_CODE=0
if [[ "$DRY_RUN" == "true" ]]; then
echo "[DRY RUN] Would execute hdbsql: \"$HDBSQL_PATH\" -U \"$USER_KEY\" \"$QUERY\""
else
"$HDBSQL_PATH" -U "$USER_KEY" "$QUERY" > /dev/null 2>&1
EXIT_CODE=$?
fi
if [[ "$EXIT_CODE" -eq 0 ]]; then
echo "✅ Successfully exported schema '${SCHEMA_NAME}' to '${EXPORT_DIR}'."
if [[ "$COMPRESS" == "true" ]]; then
ARCHIVE_FILE="${TARGET_PATH}/${SCHEMA_NAME}_$(date +%Y%m%d_%H%M%S).tar.gz"
echo "🗜️ Compressing files to '${ARCHIVE_FILE}'..."
TAR_EXIT_CODE=0
if [[ "$DRY_RUN" == "true" ]]; then
if [[ "$USE_PIGZ" == "true" ]]; then
echo "[DRY RUN] Would execute tar (pigz): tar -I \"pigz -p $THREADS\" -cf \"$ARCHIVE_FILE\" -C \"$(dirname "$EXPORT_DIR")\" \"$(basename "$EXPORT_DIR")\""
else
echo "[DRY RUN] Would execute tar: tar -czf \"$ARCHIVE_FILE\" -C \"$(dirname "$EXPORT_DIR")\" \"$(basename "$EXPORT_DIR")\""
fi
else
if [[ "$USE_PIGZ" == "true" ]]; then
tar -I "pigz -p $THREADS" -cf "$ARCHIVE_FILE" -C "$(dirname "$EXPORT_DIR")" "$(basename "$EXPORT_DIR")"
else
tar -czf "$ARCHIVE_FILE" -C "$(dirname "$EXPORT_DIR")" "$(basename "$EXPORT_DIR")"
fi
TAR_EXIT_CODE=$?
fi
if [[ "$TAR_EXIT_CODE" -eq 0 ]]; then
echo "✅ Successfully created archive."
echo "🧹 Cleaning up temporary directory..."
if [[ "$DRY_RUN" == "true" ]]; then
echo "[DRY RUN] Would remove temp directory: rm -rf \"$EXPORT_DIR\""
else
rm -rf "$EXPORT_DIR"
fi
else
echo "❌ Error: Failed to create archive from '${EXPORT_DIR}'."
fi
fi
send_notification "✅ Export of schema '${SCHEMA_NAME}' completed successfully."
else
echo "❌ Error: Failed to export schema '${SCHEMA_NAME}' (hdbsql exit code: ${EXIT_CODE})."
send_notification "❌ Export of schema '${SCHEMA_NAME}' FAILED."
if [[ "$COMPRESS" == "true" && "$DRY_RUN" == "false" ]]; then rm -rf "$EXPORT_DIR"; fi
fi
;;
import|import-rename)
SCHEMA_NAME="$3"
if [[ "$ACTION" == "import" ]]; then
SOURCE_PATH="$4"
NEW_SCHEMA_NAME=""
if [[ -z "$USER_KEY" || -z "$SCHEMA_NAME" || -z "$SOURCE_PATH" ]]; then
echo "❌ Error: Missing arguments for 'import' action."
usage
exit 1
fi
else # import-rename
NEW_SCHEMA_NAME="$4"
SOURCE_PATH="$5"
if [[ -z "$USER_KEY" || -z "$SCHEMA_NAME" || -z "$NEW_SCHEMA_NAME" || -z "$SOURCE_PATH" ]]; then
echo "❌ Error: Missing arguments for 'import-rename' action."
usage
exit 1
fi
fi
echo "⬆️ Starting schema import..."
echo " - User Key: ${USER_KEY}"
echo " - Source Schema: ${SCHEMA_NAME}"
if [[ -n "$NEW_SCHEMA_NAME" ]]; then
echo " - Target Schema: ${NEW_SCHEMA_NAME}"
fi
echo " - Path: ${SOURCE_PATH}"
echo " - Compress: ${COMPRESS}"
echo " - Threads: ${THREADS}"
IMPORT_DIR="$SOURCE_PATH"
if [[ "$COMPRESS" == "true" ]]; then
if [[ ! -f "$SOURCE_PATH" && "$DRY_RUN" == "false" ]]; then
echo "❌ Error: Source path '${SOURCE_PATH}' is not a valid file for compressed import."
exit 1
fi
if [[ "$DRY_RUN" == "true" ]]; then
IMPORT_DIR="/tmp/import_${SCHEMA_NAME}_DRYRUN_TEMP"
else
IMPORT_DIR=$(mktemp -d "/tmp/import_${SCHEMA_NAME}_XXXXXXXX")
fi
echo " Decompressing to temporary directory: ${IMPORT_DIR}"
TAR_EXIT_CODE=0
if [[ "$DRY_RUN" == "true" ]]; then
echo "[DRY RUN] Would decompress archive: tar -xzf \"$SOURCE_PATH\" -C \"$IMPORT_DIR\" --strip-components=1"
else
tar -xzf "$SOURCE_PATH" -C "$IMPORT_DIR" --strip-components=1
TAR_EXIT_CODE=$?
fi
if [[ "$TAR_EXIT_CODE" -ne 0 ]]; then
echo "❌ Error: Failed to decompress '${SOURCE_PATH}'."
if [[ "$DRY_RUN" == "false" ]]; then rm -rf "$IMPORT_DIR"; fi
exit 1
fi
fi
if [[ ! -d "$IMPORT_DIR" && "$DRY_RUN" == "false" ]]; then
echo "❌ Error: Import directory '${IMPORT_DIR}' does not exist."
exit 1
fi
import_options=""
if [[ "$IMPORT_REPLACE" == "true" ]]; then
import_options="REPLACE"
echo " - Mode: REPLACE"
else
import_options="IGNORE EXISTING"
echo " - Mode: IGNORE EXISTING (default)"
fi
if [[ "$ACTION" == "import-rename" ]]; then
import_options="${import_options} RENAME SCHEMA \"${SCHEMA_NAME}\" TO \"${NEW_SCHEMA_NAME}\""
fi
QUERY="IMPORT \"${SCHEMA_NAME}\".\"*\" AS BINARY FROM '${IMPORT_DIR}' WITH ${import_options} THREADS ${THREADS};"
EXIT_CODE=0
if [[ "$DRY_RUN" == "true" ]]; then
echo "[DRY RUN] Would execute hdbsql: \"$HDBSQL_PATH\" -U \"$USER_KEY\" \"$QUERY\""
else
"$HDBSQL_PATH" -U "$USER_KEY" "$QUERY" > /dev/null 2>&1
EXIT_CODE=$?
fi
target_schema_name="${NEW_SCHEMA_NAME:-$SCHEMA_NAME}"
if [[ "$EXIT_CODE" -eq 0 ]]; then
echo "✅ Successfully imported schema."
send_notification "${ACTION} of schema '${SCHEMA_NAME}' to '${target_schema_name}' completed successfully."
else
echo "❌ Error: Failed to import schema (hdbsql exit code: ${EXIT_CODE})."
send_notification "${ACTION} of schema '${SCHEMA_NAME}' to '${target_schema_name}' FAILED."
fi
if [[ "$COMPRESS" == "true" ]]; then
echo "🧹 Cleaning up temporary directory..."
if [[ "$DRY_RUN" == "true" ]]; then
echo "[DRY RUN] Would remove temp directory: rm -rf \"$IMPORT_DIR\""
else
rm -rf "$IMPORT_DIR"
fi
fi
;;
*)
echo "❌ Error: Invalid action '${ACTION}'."
usage
exit 1
;;
esac
echo "✅ Process complete."

241
templates/install.sh Normal file
View File

@@ -0,0 +1,241 @@
#!/bin/bash
# Author: Tomi Eckert
# --- Main Script ---
# This script presents a menu of software packages, or installs them
# non-interactively via command-line arguments. It downloads files from a
# remote configuration, shows a diff for config updates, and checks versions.
# --- Functions ---
# Get the version from a local script file.
get_local_version() {
local file_path="$1"
if [[ -f "${file_path}" ]]; then
head -n 5 "${file_path}" | grep -m 1 "^# Version:" | awk '{print $NF}'
else
echo "0.0.0" # Return a base version if file doesn't exist.
fi
}
# Compare two version strings. Returns 0 if v1 is newer.
is_version_greater() {
local v1=$1
local v2=$2
if [[ "$(printf '%s\n' "$v1" "$v2" | sort -V | head -n 1)" != "$v1" ]]; then
return 0 # v1 is greater
else
return 1 # v1 is not greater (equal or less)
fi
}
# Process a single selected package.
process_package() {
local choice_key="$1"
local force_overwrite="$2" # Expects "true" or "false"
if [[ -z "${SCRIPT_PACKAGES[$choice_key]}" ]]; then
echo "[❌] Invalid package name provided: '${choice_key}'"
return
fi
echo
echo "[⬇️] Processing package: '${choice_key}'..."
# Parse the new config format
config_value="${SCRIPT_PACKAGES[$choice_key]}"
display_name=$(echo "${config_value}" | cut -d'|' -f1)
remote_version=$(echo "${config_value}" | cut -d'|' -f2)
description=$(echo "${config_value}" | cut -d'|' -f3)
urls_to_download=$(echo "${config_value}" | cut -d'|' -f4)
install_script=$(echo "${config_value}" | cut -d'|' -f5) # Optional install script
read -r -a urls_to_download_array <<< "$urls_to_download"
for url in "${urls_to_download_array[@]}"; do
filename=$(basename "${url}")
# Handle config file overwrites
if [[ "${filename}" == *.conf && -f "${filename}" ]]; then
if [[ "$force_overwrite" == "true" ]]; then
echo "[⚠️] Overwriting '${filename}' due to --overwrite-config flag."
if ! curl -fsSL -o "${filename}" "${url}"; then
echo "[❌] Error: Failed to download '${filename}'."
fi
continue
fi
echo "[->] Found existing config file: '${filename}'."
tmp_file=$(mktemp)
if curl -fsSL -o "${tmp_file}" "${url}"; then
echo "[🔎] Comparing versions..."
echo "-------------------- DIFF START --------------------"
if command -v colordiff &> /dev/null; then
colordiff -u "${filename}" "${tmp_file}"
else
diff --color=always -u "${filename}" "${tmp_file}" 2>/dev/null || diff -u "${filename}" "${tmp_file}"
fi
echo "--------------------- DIFF END ---------------------"
read -p "Do you want to overwrite '${filename}'? (y/N) " -n 1 -r REPLY
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
mv "${tmp_file}" "${filename}"
echo "[✅] Updated '${filename}'."
else
rm "${tmp_file}"
echo "[🤷] Kept existing version of '${filename}'."
fi
else
echo "[❌] Error downloading new version of '${filename}' for comparison."
rm -f "${tmp_file}"
fi
else
# Original download logic for all other files.
echo "[->] Downloading '${filename}'..."
if curl -fsSL -o "${filename}" "${url}"; then
echo "[✅] Successfully downloaded '${filename}'."
if [[ "${filename}" == *.sh || "${filename}" == *.bash ]]; then
chmod +x "${filename}"
echo "[🤖] Made '${filename}' executable."
fi
else
echo "[❌] Error: Failed to download '${filename}'."
fi
fi
done
if [[ -n "${install_script}" ]]; then
echo "[⚙️] Running install script for '${choice_key}'..."
#eval "${install_script}"
bash -c "$(curl -sSL $install_script)"
if [ $? -eq 0 ]; then
echo "[✅] Install script completed successfully."
else
echo "[❌] Install script failed with exit code $?."
fi
fi
echo "[📦] Package processing complete for '${choice_key}'."
}
# --- Main Logic ---
conf_file="packages.conf.$(date +%Y%m%d%H%M%S)"
trap 'rm -f "${conf_file}"' EXIT
echo "[🔄] Downloading configuration file..."
if ! curl -fsSL -o "${conf_file}" "https://git.technopunk.space/tomi/Scripts/raw/branch/main/packages.conf"; then
echo "[❌] Error: Failed to download packages.conf. Exiting."
exit 1
fi
echo "[✅] Configuration file downloaded successfully."
source "${conf_file}"
# --- Argument Parsing for Non-Interactive Mode ---
if [ "$#" -gt 0 ]; then
declare -a packages_to_install
overwrite_configs=false
for arg in "$@"; do
case $arg in
--overwrite-config)
overwrite_configs=true
;;
-*)
echo "[❌] Unknown flag: $arg" >&2
exit 1
;;
*)
packages_to_install+=("$arg")
;;
esac
done
if [ ${#packages_to_install[@]} -eq 0 ]; then
echo "[❌] Flag provided with no package names. Exiting."
exit 1
fi
echo "[🚀] Running in non-interactive mode."
for pkg_key in "${packages_to_install[@]}"; do
if [[ -n "${SCRIPT_PACKAGES[$pkg_key]}" ]]; then
process_package "$pkg_key" "$overwrite_configs"
else
echo "[⚠️] Unknown package: '$pkg_key'. Skipping."
fi
done
echo "[🏁] Non-interactive run complete."
exit 0
fi
# --- Interactive Mode ---
declare -a ordered_keys
package_keys_sorted=($(for k in "${!SCRIPT_PACKAGES[@]}"; do echo $k; done | sort))
ordered_keys=("${package_keys_sorted[@]}")
# --- Display Menu ---
echo
echo "-------------------------------------"
echo " Script Downloader "
echo "-------------------------------------"
echo "[🔎] Checking for updates..."
echo
for i in "${!ordered_keys[@]}"; do
key="${ordered_keys[$i]}"
config_value="${SCRIPT_PACKAGES[$key]}"
display_name=$(echo "${config_value}" | cut -d'|' -f1)
remote_version=$(echo "${config_value}" | cut -d'|' -f2)
description=$(echo "${config_value}" | cut -d'|' -f3)
urls=$(echo "${config_value}" | cut -d'|' -f4)
# install_script=$(echo "${config_value}" | cut -d'|' -f5) # Not used for display in menu
read -r -a url_array <<< "$urls"
main_script_filename=$(basename "${url_array[0]}")
local_version=$(get_local_version "${main_script_filename}")
# Print main package line
echo -e "\033[1m$((i+1))) $key - $display_name (v$remote_version)\033[0m"
# Print description
echo " $description"
# Print status
if [[ -f "${main_script_filename}" ]]; then
if is_version_greater "$remote_version" "$local_version"; then
echo -e " \033[33m[Update available: v${local_version} -> v${remote_version}]\033[0m"
else
echo -e " \033[32m[Installed: v${local_version}]\033[0m"
fi
fi
echo
done
quit_num=$((${#ordered_keys[@]} + 1))
echo -e "\033[1m${quit_num}) Quit\033[0m"
echo
# --- Handle User Input ---
read -p "Please enter your choice(s) (e.g., 1 3 4), or press Enter to quit: " -r -a user_choices
if [ ${#user_choices[@]} -eq 0 ]; then
echo "[👋] No selection made. Exiting."
exit 0
fi
for choice_num in "${user_choices[@]}"; do
if ! [[ "$choice_num" =~ ^[0-9]+$ ]]; then
echo "[⚠️] Skipping invalid input: '${choice_num}'. Not a number."
continue
fi
if [ "$choice_num" -eq "$quit_num" ]; then
echo "[👋] Quit selected. Exiting."
exit 0
fi
index=$((choice_num - 1))
if [[ -z "${ordered_keys[$index]}" ]]; then
echo "[⚠️] Skipping invalid choice: '${choice_num}'. Out of range."
continue
fi
choice_key="${ordered_keys[$index]}"
process_package "$choice_key" "false" # Never force overwrite in interactive mode
done
echo
echo "[🏁] All selected packages have been processed."

208
templates/keymanager.sh Normal file
View File

@@ -0,0 +1,208 @@
#!/bin/bash
# Version: 1.2.3
# Author: Tomi Eckert
# A script to interactively manage SAP HANA hdbuserstore keys, with testing.
# --- Style Definitions ---
COLOR_BLUE='\033[1;34m'
COLOR_GREEN='\033[1;32m'
COLOR_YELLOW='\033[1;33m'
COLOR_RED='\033[1;31m'
COLOR_NC='\033[0m' # No Color
# --- Configuration ---
# Adjust these paths if your HANA client is installed elsewhere.
# Define potential HDB client paths
HDB_CLIENT_PATH_1="/usr/sap/hdbclient"
HDB_CLIENT_PATH_2="/usr/sap/NDB/HDB00/exe"
# Check which path exists and set HDB_CLIENT_PATH accordingly
if [ -d "$HDB_CLIENT_PATH_1" ]; then
HDB_CLIENT_PATH="$HDB_CLIENT_PATH_1"
elif [ -d "$HDB_CLIENT_PATH_2" ]; then
HDB_CLIENT_PATH="$HDB_CLIENT_PATH_2"
else
echo -e "${COLOR_RED}❌ Error: Neither '$HDB_CLIENT_PATH_1' nor '$HDB_CLIENT_PATH_2' found.${COLOR_NC}"
echo -e "${COLOR_RED}Please install the SAP HANA client or adjust the paths in the script.${COLOR_NC}"
exit 1
fi
HDB_USERSTORE_EXEC="${HDB_CLIENT_PATH}/hdbuserstore"
HDB_SQL_EXEC="${HDB_CLIENT_PATH}/hdbsql"
# --- Function: Test Key Connection ---
# @param $1: The key name to test.
# @return: 0 for success, 1 for failure.
test_key() {
local key_to_test=$1
if [ -z "$key_to_test" ]; then
echo -e "${COLOR_RED} ❌ Error: No key name provided for testing.${COLOR_NC}"
return 1
fi
echo -e "\n${COLOR_YELLOW}🧪 Testing connection for key '${key_to_test}'...${COLOR_NC}"
# Execute hdbsql, capturing both stdout and stderr.
# The query is simple and lightweight, designed just to validate the connection.
test_output=$("$HDB_SQL_EXEC" -U "$key_to_test" "SELECT 'Connection successful' FROM DUMMY" 2>&1)
local exit_code=$?
if [ $exit_code -eq 0 ] && [[ "$test_output" == *"Connection successful"* ]]; then
echo -e "${COLOR_GREEN} ✅ Connection test successful!${COLOR_NC}"
return 0
else
echo -e "${COLOR_RED} ❌ Connection test failed for key '${key_to_test}'.${COLOR_NC}"
echo -e "${COLOR_RED} Error details:${COLOR_NC}"
# Indent the error message for better readability.
echo "$test_output" | sed 's/^/ /'
return 1
fi
}
# --- Function: Create New Key ---
create_new_key() {
current_hostname=$(hostname)
echo -e "\n${COLOR_BLUE}🔑 --- Create New Secure Key ---${COLOR_NC}"
read -p "Enter the Key Name [CRONKEY]: " key_name
read -p "Enter the HANA Host [${current_hostname}]: " hdb_host
read -p "Enter the Instance Number [00]: " hdb_instance
# Ask if connecting to SYSTEMDB to format the connection string correctly.
read -p "Is this for the SYSTEMDB tenant? (y/n) [n]: " is_systemdb
# Set default values for prompts
key_name=${key_name:-"CRONKEY"}
hdb_host=${hdb_host:-$current_hostname}
hdb_instance=${hdb_instance:-"00"}
is_systemdb=${is_systemdb:-"n"}
# Conditionally build the connection string
if [[ "$is_systemdb" =~ ^[Yy]$ ]]; then
CONNECTION_STRING="${hdb_host}:3${hdb_instance}13"
echo -e "${COLOR_YELLOW}💡 Connecting to SYSTEMDB. Tenant name will be omitted from the connection string.${COLOR_NC}"
else
read -p "Enter the Tenant DB [NDB]: " hdb_tenant
hdb_tenant=${hdb_tenant:-"NDB"}
CONNECTION_STRING="${hdb_host}:3${hdb_instance}15@${hdb_tenant}"
fi
read -p "Enter the Database User [SYSTEM]: " hdb_user
read -sp "Enter the Database Password: " hdb_pass
echo ""
hdb_user=${hdb_user:-"SYSTEM"}
echo -e "\n${COLOR_YELLOW}📝 Review the command below (password is hidden):"
echo "------------------------------------------------------"
printf "${HDB_USERSTORE_EXEC} SET \"%s\" \"%s\" \"%s\" \"<password>\"\n" "$key_name" "$CONNECTION_STRING" "$hdb_user"
echo -e "------------------------------------------------------${COLOR_NC}"
read -p "❓ Execute this command? (y/n): " execute_now
if [[ "$execute_now" =~ ^[Yy]$ ]]; then
echo -e "\n${COLOR_GREEN}⚙️ Executing command...${COLOR_NC}"
# Create the key first
if "$HDB_USERSTORE_EXEC" SET "$key_name" "$CONNECTION_STRING" "$hdb_user" "$hdb_pass"; then
echo -e "${COLOR_GREEN} ✅ Success! Key '${key_name}' stored locally.${COLOR_NC}"
# Immediately test the new key
if ! test_key "$key_name"; then
# If the test fails, roll back by deleting the key
echo -e "\n${COLOR_YELLOW} 롤 Rolling back: Deleting the newly created key '${key_name}' due to connection failure.${COLOR_NC}"
if "$HDB_USERSTORE_EXEC" DELETE "$key_name"; then
echo -e "${COLOR_GREEN} ✅ Key '${key_name}' successfully deleted.${COLOR_NC}"
else
echo -e "${COLOR_RED} ❌ Error: Failed to automatically delete the key '${key_name}'. Please remove it manually.${COLOR_NC}"
fi
fi
else
echo -e "${COLOR_RED} ❌ Error: Failed to store key '${key_name}'. Please check details and credentials.${COLOR_NC}"
fi
else
echo -e "\n${COLOR_YELLOW}🛑 Execution aborted by user.${COLOR_NC}"
fi
}
# --- Function: Delete Key ---
delete_key() {
echo -e "\n${COLOR_BLUE}🗑️ --- Delete Existing Secure Key ---${COLOR_NC}"
keys=$("$HDB_USERSTORE_EXEC" list 2>/dev/null | tail -n +3 | grep '^KEY ' | awk '{print $2}')
if [ -z "$keys" ]; then
echo -e "${COLOR_YELLOW}🤷 No keys found to delete.${COLOR_NC}"
return
fi
PS3=$'\nPlease select a key to delete (or Ctrl+C to cancel): '
select key_to_delete in $keys; do
if [ -n "$key_to_delete" ]; then
read -p "❓ PERMANENTLY delete the key '$key_to_delete'? (y/n): " confirm
if [[ "$confirm" =~ ^[Yy]$ ]]; then
echo -e "\n${COLOR_GREEN}⚙️ Deleting key '$key_to_delete'...${COLOR_NC}"
if "$HDB_USERSTORE_EXEC" DELETE "$key_to_delete"; then
echo -e "${COLOR_GREEN} ✅ Success! Key '$key_to_delete' has been deleted.${COLOR_NC}"
else
echo -e "${COLOR_RED} ❌ Error: Failed to delete the key.${COLOR_NC}"
fi
else
echo -e "\n${COLOR_YELLOW}🛑 Deletion aborted by user.${COLOR_NC}"
fi
break
else
echo -e "${COLOR_RED}❌ Invalid selection. Try again.${COLOR_NC}"
fi
done
}
# --- Function: List and Test a Key ---
list_and_test_key() {
echo -e "\n${COLOR_BLUE}🧪 --- Test an Existing Secure Key ---${COLOR_NC}"
keys=$("$HDB_USERSTORE_EXEC" list 2>/dev/null | tail -n +3 | grep '^KEY ' | awk '{print $2}')
if [ -z "$keys" ]; then
echo -e "${COLOR_YELLOW}🤷 No keys found to test.${COLOR_NC}"
return
fi
PS3=$'\nPlease select a key to test (or Ctrl+C to cancel): '
select key_to_test in $keys; do
if [ -n "$key_to_test" ]; then
test_key "$key_to_test"
break
else
echo -e "${COLOR_RED}❌ Invalid selection. Try again.${COLOR_NC}"
fi
done
}
# --- Main Menu ---
while true; do
echo -e "\n${COLOR_BLUE}🔐 ========== SAP HANA Secure User Store Key Manager ==========${COLOR_NC}"
echo "1) Create a New Key"
echo "2) Delete an Existing Key"
echo "3) Test an Existing Key"
echo "4) Exit"
read -p $'\nPlease select an option: ' choice
case $choice in
1)
create_new_key
;;
2)
delete_key
;;
3)
list_and_test_key
;;
4)
echo "👋 Exiting."
exit 0
;;
*)
echo -e "${COLOR_RED}❌ Invalid option '$choice'. Please try again.${COLOR_NC}"
;;
esac
done

244
templates/monitor.sh Normal file
View File

@@ -0,0 +1,244 @@
#!/bin/bash
# Version: 1.3.1
# Author: Tomi Eckert
# =============================================================================
# SAP HANA Monitoring Script
#
# Checks HANA processes, disk usage, log segments, and statement queue.
# Sends ntfy.sh notifications if thresholds are exceeded.
# =============================================================================
# --- Lock File Implementation ---
LOCK_FILE="/tmp/hana_monitor.lock"
if [ -e "$LOCK_FILE" ]; then
echo "▶️ Script is already running. Exiting."
exit 1
fi
touch "$LOCK_FILE"
# Ensure lock file is removed on script exit
trap 'rm -f "$LOCK_FILE"' EXIT
# --- Configuration and Setup ---
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
CONFIG_FILE="${SCRIPT_DIR}/monitor.conf"
if [ ! -f "$CONFIG_FILE" ]; then
echo "❌ Error: Configuration file not found at ${CONFIG_FILE}" >&2
rm -f "$LOCK_FILE"
exit 1
fi
source "$CONFIG_FILE"
STATE_DIR="${SCRIPT_DIR}/monitor_state"
mkdir -p "${STATE_DIR}"
# Helper functions for state management
get_state() {
local key="$1"
if [ -f "${STATE_DIR}/${key}.state" ]; then
cat "${STATE_DIR}/${key}.state"
else
echo ""
fi
}
set_state() {
local key="$1"
local value="$2"
echo "$value" > "${STATE_DIR}/${key}.state"
}
HOSTNAME=$(hostname)
SQL_QUERY="SELECT b.host, b.service_name, a.state, count(*) FROM PUBLIC.M_LOG_SEGMENTS a JOIN PUBLIC.M_SERVICES b ON (a.host = b.host AND a.port = b.port) GROUP BY b.host, b.service_name, a.state;"
send_notification_if_changed() {
local alert_key="$1"
local title_prefix="$2" # e.g., "HANA Process"
local current_message="$3"
local is_alert_condition="$4" # "true" or "false"
local current_value="$5" # The value to store as state (e.g., "85%", "GREEN", "ALERT")
local previous_value=$(get_state "${alert_key}")
if [ "$current_value" != "$previous_value" ]; then
local full_title=""
local full_message=""
if [ "$is_alert_condition" == "true" ]; then
full_title="${title_prefix} Alert"
full_message="🚨 Critical: ${current_message}"
else
# Check if it was previously an alert (i.e., previous_value was not "OK")
if [ -n "$previous_value" ] && [ "$previous_value" != "OK" ]; then
full_title="${title_prefix} Resolved"
full_message="✅ Resolved: ${current_message}"
else
# No alert, and no previous alert to resolve, so just update state silently
set_state "${alert_key}" "$current_value"
return
fi
fi
local final_message="[${COMPANY_NAME} | ${HOSTNAME}] ${full_message}"
curl -H "Authorization: Bearer ${NTFY_TOKEN}" -H "Title: ${full_title}" -d "${final_message}" "${NTFY_TOPIC_URL}" > /dev/null 2>&1
set_state "${alert_key}" "$current_value"
echo "🔔 Notification sent for ${alert_key}: ${full_message}"
fi
}
# --- HANA Process Status ---
echo "⚙️ Checking HANA process status..."
if [ ! -x "$SAPCONTROL_PATH" ]; then
echo "❌ Error: sapcontrol not found or not executable at ${SAPCONTROL_PATH}" >&2
send_notification_if_changed "hana_sapcontrol_path" "HANA Monitor Error" "sapcontrol not found or not executable at ${SAPCONTROL_PATH}" "true" "SAPCONTROL_ERROR"
exit 1
fi
non_green_processes=$("${SAPCONTROL_PATH}" -nr "${HANA_INSTANCE_NR}" -function GetProcessList | tail -n +6 | grep -v 'GREEN')
if [ -n "$non_green_processes" ]; then
echo "🚨 Alert: One or more HANA processes are not running!" >&2
echo "$non_green_processes" >&2
send_notification_if_changed "hana_processes" "HANA Process" "One or more HANA processes are not GREEN. Problem processes: ${non_green_processes}" "true" "PROCESS_ALERT:${non_green_processes}"
exit 1 # Exit early as other checks might fail
else
send_notification_if_changed "hana_processes" "HANA Process" "All HANA processes are GREEN." "false" "OK"
echo "✅ Success! All HANA processes are GREEN."
fi
# --- Disk Space Monitoring ---
echo " Checking disk usage..."
for dir in "${DIRECTORIES_TO_MONITOR[@]}"; do
if [ ! -d "$dir" ]; then
echo "⚠️ Warning: Directory '$dir' not found. Skipping." >&2
send_notification_if_changed "disk_dir_not_found_${dir//\//_}" "HANA Disk Warning" "Directory '$dir' not found." "true" "DIR_NOT_FOUND"
continue
fi
usage=$(df -h "$dir" | awk 'NR==2 {print $5}' | sed 's/%//')
echo " - ${dir} is at ${usage}%"
if (( $(echo "$usage > $DISK_USAGE_THRESHOLD" | bc -l) )); then
echo "🚨 Alert: ${dir} usage is at ${usage}% which is above the ${DISK_USAGE_THRESHOLD}% threshold." >&2
send_notification_if_changed "disk_usage_${dir//\//_}" "HANA Disk" "Disk usage for ${dir} is at ${usage}%." "true" "${usage}%"
else
send_notification_if_changed "disk_usage_${dir//\//_}" "HANA Disk" "Disk usage for ${dir} is at ${usage}% (below threshold)." "false" "OK"
fi
done
# --- HANA Log Segment Monitoring ---
echo "⚙️ Executing HANA SQL query..."
if [ ! -x "$HDBSQL_PATH" ]; then
echo "❌ Error: hdbsql not found or not executable at ${HDBSQL_PATH}" >&2
send_notification_if_changed "hana_hdbsql_path" "HANA Monitor Error" "hdbsql not found or not executable at ${HDBSQL_PATH}" "true" "HDBSQL_ERROR"
exit 1
fi
readarray -t sql_output < <("$HDBSQL_PATH" -U "$HANA_USER_KEY" -c ";" "$SQL_QUERY" 2>&1)
if [ $? -ne 0 ]; then
echo "❌ Failure! The hdbsql command failed. Please check logs." >&2
error_message=$(printf '%s\n' "${sql_output[@]}")
send_notification_if_changed "hana_hdbsql_command" "HANA Monitor Error" "The hdbsql command failed. Details: ${error_message}" "true" "HDBSQL_COMMAND_FAILED"
exit 1
fi
total_segments=0
truncated_segments=0
free_segments=0
for line in "${sql_output[@]}"; do
if [[ -z "$line" || "$line" == *"STATE"* ]]; then continue; fi
cleaned_line=$(echo "$line" | tr -d '"')
state=$(echo "$cleaned_line" | awk -F',' '{print $3}')
count=$(echo "$cleaned_line" | awk -F',' '{print $4}')
total_segments=$((total_segments + count))
if [[ "$state" == "Truncated" ]]; then
truncated_segments=$((truncated_segments + count))
elif [[ "$state" == "Free" ]]; then
free_segments=$((free_segments + count))
fi
done
echo " Total Segments: ${total_segments}"
echo " Truncated Segments: ${truncated_segments}"
echo " Free Segments: ${free_segments}"
if [ $total_segments -eq 0 ]; then
echo "⚠️ Warning: No log segments found. Skipping percentage checks." >&2
send_notification_if_changed "hana_log_segments_total" "HANA Log Segment Warning" "No log segments found. Skipping percentage checks." "true" "NO_LOG_SEGMENTS"
else
send_notification_if_changed "hana_log_segments_total" "HANA Log Segment" "Log segments found." "false" "OK"
truncated_percentage=$((truncated_segments * 100 / total_segments))
if (( $(echo "$truncated_percentage > $TRUNCATED_PERCENTAGE_THRESHOLD" | bc -l) )); then
echo "🚨 Alert: ${truncated_percentage}% of log segments are 'Truncated'." >&2
send_notification_if_changed "hana_log_truncated" "HANA Log Segment" "${truncated_percentage}% of HANA log segments are in 'Truncated' state." "true" "${truncated_percentage}%"
else
send_notification_if_changed "hana_log_truncated" "HANA Log Segment" "${truncated_percentage}% of HANA log segments are in 'Truncated' state (below threshold)." "false" "OK"
fi
free_percentage=$((free_segments * 100 / total_segments))
if (( $(echo "$free_percentage < $FREE_PERCENTAGE_THRESHOLD" | bc -l) )); then
echo "🚨 Alert: Only ${free_percentage}% of log segments are 'Free'." >&2
send_notification_if_changed "hana_log_free" "HANA Log Segment" "Only ${free_percentage}% of HANA log segments are in 'Free' state." "true" "${free_percentage}%"
else
send_notification_if_changed "hana_log_free" "HANA Log Segment" "Only ${free_percentage}% of HANA log segments are in 'Free' state (above threshold)." "false" "OK"
fi
fi
# --- HANA Statement Queue Monitoring ---
echo "⚙️ Checking HANA statement queue..."
STATEMENT_QUEUE_SQL="SELECT COUNT(*) FROM M_SERVICE_THREADS WHERE THREAD_TYPE = 'SqlExecutor' AND THREAD_STATE = 'Queueing';"
queue_count=$("$HDBSQL_PATH" -U "$HANA_USER_KEY" -j -a -x "$STATEMENT_QUEUE_SQL" 2>/dev/null | tr -d '"')
if ! [[ "$queue_count" =~ ^[0-9]+$ ]]; then
echo "⚠️ Warning: Could not retrieve HANA statement queue count. Skipping check." >&2
send_notification_if_changed "hana_statement_queue_check_fail" "HANA Monitor Warning" "Could not retrieve statement queue count." "true" "QUEUE_CHECK_FAIL"
else
send_notification_if_changed "hana_statement_queue_check_fail" "HANA Monitor Warning" "Statement queue check is working." "false" "OK"
echo " Current statement queue length: ${queue_count}"
breach_count=$(get_state "statement_queue_breach_count")
breach_count=${breach_count:-0}
if (( queue_count > STATEMENT_QUEUE_THRESHOLD )); then
breach_count=$((breach_count + 1))
echo "📈 Statement queue is above threshold. Consecutive breach count: ${breach_count}/${STATEMENT_QUEUE_CONSECUTIVE_RUNS}."
else
breach_count=0
fi
set_state "statement_queue_breach_count" "$breach_count"
if (( breach_count >= STATEMENT_QUEUE_CONSECUTIVE_RUNS )); then
message="Statement queue has been over ${STATEMENT_QUEUE_THRESHOLD} for ${breach_count} checks. Current count: ${queue_count}."
send_notification_if_changed "hana_statement_queue_status" "HANA Statement Queue" "${message}" "true" "ALERT:${queue_count}"
else
message="Statement queue is normal. Current count: ${queue_count}."
send_notification_if_changed "hana_statement_queue_status" "HANA Statement Queue" "${message}" "false" "OK"
fi
fi
# --- HANA Backup Status Monitoring ---
echo " Checking last successful data backup status..."
last_backup_date=$("$HDBSQL_PATH" -U "$HANA_USER_KEY" -j -a -x \
"SELECT TOP 1 SYS_START_TIME FROM M_BACKUP_CATALOG WHERE ENTRY_TYPE_NAME = 'complete data backup' AND STATE_NAME = 'successful' ORDER BY SYS_START_TIME DESC" 2>/dev/null | tr -d "\"" | sed 's/\..*//')
if [[ -z "$last_backup_date" ]]; then
message="No successful complete data backup found for ${COMPANY_NAME} HANA."
echo "🚨 Critical: ${message}"
send_notification_if_changed "hana_backup_status" "HANA Backup" "${message}" "true" "NO_BACKUP"
else
last_backup_epoch=$(date -d "$last_backup_date" +%s)
current_epoch=$(date +%s)
threshold_seconds=$((BACKUP_THRESHOLD_HOURS * 3600))
age_seconds=$((current_epoch - last_backup_epoch))
age_hours=$((age_seconds / 3600))
if (( age_seconds > threshold_seconds )); then
message="Last successful HANA backup for ${COMPANY_NAME} is ${age_hours} hours old, which exceeds the threshold of ${BACKUP_THRESHOLD_HOURS} hours. Last backup was on: ${last_backup_date}."
echo "🚨 Critical: ${message}"
send_notification_if_changed "hana_backup_status" "HANA Backup" "${message}" "true" "${age_hours}h"
else
message="Last successful backup is ${age_hours} hours old (Threshold: ${BACKUP_THRESHOLD_HOURS} hours)."
echo "✅ Success! ${message}"
send_notification_if_changed "hana_backup_status" "HANA Backup" "${message}" "false" "OK"
fi
fi
echo "✅ Success! HANA monitoring check complete."