Index: /branches/amp_4_0/platform/scripts/create_user.sh
===================================================================
--- /branches/amp_4_0/platform/scripts/create_user.sh	(nonexistent)
+++ /branches/amp_4_0/platform/scripts/create_user.sh	(working copy)
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+# Configuration
+NEW_USER="admin"
+NEW_PASS="admin"
+
+# 1. Create the user with a home directory
+useradd -m "$NEW_USER"
+
+# 2. Set the password
+# We use chpasswd for non-interactive password setting
+echo "$NEW_USER:$NEW_PASS" | chpasswd
+
+# 3. Add user to the 'wheel' group for sudo/su access
+usermod -aG wheel "$NEW_USER"
+
+echo "User '$NEW_USER' created successfully and added to the 'wheel' group."
Index: /branches/amp_4_0/platform/tools/README.md
===================================================================
--- /branches/amp_4_0/platform/tools/README.md	(revision 2855)
+++ /branches/amp_4_0/platform/tools/README.md	(nonexistent)
@@ -1,45 +0,0 @@
-### Tools directory contains the scripts to install the AMP tools.
-
-### Tool & Version
-
-* Python -> 3.13
-* Django -> 5
-* Java -> 21
-* PostgresSQL -> 17.4
-* Elastic -> 8.18.0-1
-* Logstash -> 1:8.18.0-1
-* Kibana -> 8.18.0-1
-* MetricBeats -> 8.18.0-1
-* Filebeat -> 8.18.0-1
-* InfluxDB -> 2.7.11-1
-* InfluxDB-CLI -> 2.7.5-1
-* Telegraf -> 1.34.1-1
-* Nginx -> 1.20.1
-
-### Installation order
-
-1. Python
-2. PSQL
-3. ELK
-    1. Configure ELK
-4. INFLUX
-5. TELEGRAF
-
-### ToDo (Best practices)
-
-* Use random passwords or better alternatives for the stored passwords from the script.
-
-### Debug
-
-#### InfluxDB
-
-By default, influxdb GUI is not exposed to the outside network; we can allow the 8086 to access the InfluxDB GUI using
-the following firewall rules—it's strictly for the debugging purpose.
-
-sudo firewall-cmd --permanent --add-port=8086/tcp
-sudo firewall-cmd --permanent --add-port=8086/udp
-
-sudo firewall-cmd --reload
-
-### Configure HyperTables for Device Metrics
-psql "host=localhost port=5432 dbname=amp_ts user=amp_ts_user" -v ON_ERROR_STOP=1 -f /opt/telegraf_snmp_timescale.sql 
Index: /branches/amp_4_0/platform/tools/configure_elk.sh
===================================================================
--- /branches/amp_4_0/platform/tools/configure_elk.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/configure_elk.sh	(nonexistent)
@@ -1,228 +0,0 @@
-#!/bin/bash
-set -e
-
-if [[ "$EUID" -ne 0 ]]; then
-  echo "Please run as root"
-  exit 1
-fi
-
-# --- File Configuration ---
-LOG_FILE="/var/log/configure_elk.log"
-SYSTEM_MODULE_FILE="/etc/filebeat/modules.d/system.yml"
-
-# --- Host & Credentials ---
-KIBANA_HOST="http://localhost:5601"
-
-ELASTIC_SUPER_USER="elastic"
-ELASTIC_PASSWORD="Arr@y2050"
-
-# --- Logging Helpers ---
-log_info() {
-  echo "[INFO] $(date +'%Y-%m-%d %H:%M:%S') $1" | tee -a "$LOG_FILE"
-}
-
-log_error() {
-  echo "[ERROR] $(date +'%Y-%m-%d %H:%M:%S') $1" >&2 | tee -a "$LOG_FILE"
-}
-
-create_dashboard_index_patterns() {
-  local service="$1"
-  log_info "setting up the dashboards & index management for - '$service'..."
-  sudo "$service" setup --dashboards --index-management\
-  -E setup.ilm.overwrite=true \
-  -E setup.kibana.host="$KIBANA_HOST" \
-  -E setup.kibana.username="$ELASTIC_SUPER_USER" \
-  -E setup.kibana.password="$ELASTIC_PASSWORD" \
-  -E output.elasticsearch.username="$ELASTIC_SUPER_USER" \
-  -E output.elasticsearch.password="$ELASTIC_PASSWORD" \
-   | tee -a "$LOG_FILE"
-}
-
-create_data_view_logstash() {
-  log_info "Creating Kibana data view for Logstash..."
-
-  local response
-  response=$(curl -s -X POST "$KIBANA_HOST/api/saved_objects/index-pattern" \
-    -u "$ELASTIC_SUPER_USER:$ELASTIC_PASSWORD" \
-    -H 'Content-Type: application/json' \
-    -H 'kbn-xsrf: true' \
-    -d "{
-      \"attributes\": {
-        \"title\": \"logstash-*\",
-        \"timeFieldName\": \"@timestamp\"
-      }
-    }")
-
-  echo "$response" >> "$LOG_FILE"
-
-  if echo "$response" | jq . >/dev/null 2>&1; then
-    log_info "✅ Successfully created Logstash data view."
-  else
-    log_error "❌ Failed to create Logstash data view. Raw output: $response"
-    exit 1
-  fi
-}
-
-create_data_view_acm() {
-  log_info "Creating Kibana data view for ACM..."
-
-  local response
-  response=$(curl -s -X POST "$KIBANA_HOST/api/saved_objects/index-pattern" \
-    -u "$ELASTIC_SUPER_USER:$ELASTIC_PASSWORD" \
-    -H 'Content-Type: application/json' \
-    -H 'kbn-xsrf: true' \
-    -d "{
-      \"attributes\": {
-        \"title\": \"acm-*\",
-        \"timeFieldName\": \"@timestamp\"
-      }
-    }")
-
-  echo "$response" >> "$LOG_FILE"
-
-  if echo "$response" | jq . >/dev/null 2>&1; then
-    log_info "✅ Successfully created acm data view."
-  else
-    log_error "❌ Failed to create acm data view. Raw output: $response"
-    exit 1
-  fi
-}
-
-make_data_view_acm_default() {
-  CURL_AUTH_ARGS=(-u "${ELASTIC_SUPER_USER}:${ELASTIC_PASSWORD}") # Basic Auth for curl
-  CURL_COMMON_ARGS=(-H "kbn-xsrf: true" -H "Content-Type: application/json" --silent --fail --show-error)
-
-  TARGET_DATAVIEW_NAME="acm-*"
-  TARGET_DATAVIEW_ID="acm-*"
-
-  SET_DEFAULT_RESPONSE=$(curl -X POST "${KIBANA_HOST}/api/data_views/default" \
-    "${CURL_AUTH_ARGS[@]}" \
-    "${CURL_COMMON_ARGS[@]}" \
-    -d '{ "data_view_id": "'"${TARGET_DATAVIEW_ID}"'", "force": true }')
-
-  # Check if curl command itself failed
-  if [ $? -ne 0 ]; then
-    log_error "❌ Failed to set default data view. Check Kibana URL, credentials, and connectivity." >&2
-    log_info "Response: ${SET_DEFAULT_RESPONSE}" >&2
-    exit 1
-  fi
-
-  # Verify success from the response content using jq
-  ACKNOWLEDGED=$(echo "${SET_DEFAULT_RESPONSE}" | jq -r ".acknowledged")
-
-  if [ "${ACKNOWLEDGED}" == "true" ]; then
-    log_info "Success: Default data view set to '${TARGET_DATAVIEW_NAME}' (ID: ${TARGET_DATAVIEW_ID})."
-  else
-    log_error "❌ Kibana API reported failure to set default data view." >&2
-    log_info "Response: ${SET_DEFAULT_RESPONSE}" >&2
-    exit 1
-  fi
-}
-
-clean_start_filebeat() {
-  log_info "Cleaning up and safely starting Filebeat..."
-
-  systemctl stop filebeat || true | tee -a "$LOG_FILE"
-
-  LOCK_FILE="/var/lib/filebeat/filebeat.lock"
-  if [[ -f "$LOCK_FILE" ]]; then
-    log_info "Removing stale lock file: $LOCK_FILE"
-    rm -f "$LOCK_FILE" | tee -a "$LOG_FILE"
-  fi
-
-  SYSTEM_YML="/etc/filebeat/modules.d/system.yml"
-
-  log_info "Overwriting $SYSTEM_YML with valid configuration..."
-  tee "$SYSTEM_YML" > /dev/null <<EOF
-- module: system
-  syslog:
-    enabled: true
-  auth:
-    enabled: true
-EOF
-
-  log_info "Starting Filebeat..."
-  systemctl start filebeat | tee -a "$LOG_FILE"
-  systemctl enable filebeat | tee -a "$LOG_FILE"
-}
-
-
-clean_start_metricbeat() {
-  log_info "Starting Metricbeat..."
-  systemctl enable --now metricbeat | tee -a "$LOG_FILE"
-  sleep 5
-
-  # Just ensure system module is enabled for filebeat-like logs
-  SYSTEM_MODULE_FILE="/etc/filebeat/modules.d/system.yml"
-  if [[ -f "$SYSTEM_MODULE_FILE" ]]; then
-    sed -i 's/enabled: false/enabled: true/g' "$SYSTEM_MODULE_FILE" | tee -a "$LOG_FILE"
-  fi
-
-  systemctl restart metricbeat | tee -a "$LOG_FILE"
-}
-
-enable_start_beats() {
-  log_info "Setting up Filebeat and Metricbeat..."
-
-  # Filebeat
-  sudo filebeat modules enable system | tee -a "$LOG_FILE"
-  clean_start_filebeat
-
-  # Metricbeat
-  clean_start_metricbeat
-
-  create_data_view_logstash
-
-  create_data_view_acm
-
-  make_data_view_acm_default
-}
-
-configure_elk() {
-  # Configure Filebeat
-  create_dashboard_index_patterns "filebeat"
-
-  # Configure Metricbeat
-  create_dashboard_index_patterns "metricbeat"
-
-  # Enable & Start Beats
-  enable_start_beats
-}
-
-restart_elk_services() {
-  local services=("elasticsearch" "logstash" "kibana" "filebeat" "metricbeat")
-
-  for service in "${services[@]}"; do
-    echo "Restarting $service..."
-    systemctl restart "$service"
-
-    # Wait a bit for the service to settle (especially for elasticsearch)
-    sleep 5
-
-    # Check status
-    status=$(systemctl is-active "$service")
-    if [[ "$status" == "active" ]]; then
-      echo "$service restarted successfully ✅"
-    else
-      echo "❌ Failed to restart $service. Status: $status"
-    fi
-  done
-  configure_elk
-}
-
-if systemctl is-active --quiet elasticsearch && \
-   systemctl is-active --quiet logstash && \
-   systemctl is-active --quiet kibana && \
-   systemctl is-active --quiet filebeat && \
-   systemctl is-active --quiet metricbeat; then
-    echo "✅ All ELK services are running. Proceeding with setup..."
-    configure_elk
-else
-    echo "❌ One or more ELK services are not running. Aborting setup."
-    echo "Elasticsearch: $(systemctl is-active elasticsearch)"
-    echo "Logstash:      $(systemctl is-active logstash)"
-    echo "Kibana:        $(systemctl is-active kibana)"
-    echo "Filebeat:        $(systemctl is-active filebeat)"
-    echo "Metricbeat:        $(systemctl is-active metricbeat)"
-    restart_elk_services
-fi
Index: /branches/amp_4_0/platform/tools/configure_opensearch_heap_memory.sh
===================================================================
--- /branches/amp_4_0/platform/tools/configure_opensearch_heap_memory.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/configure_opensearch_heap_memory.sh	(nonexistent)
@@ -1,85 +0,0 @@
-#!/bin/bash
-set -e
-
-# --- Configuration ---
-OPENSEARCH_JVM_OPTS="/etc/opensearch/jvm.options"
-DATAPREPPER_SCRIPT="/usr/share/data-prepper/bin/data-prepper"
-DASHBOARDS_SERVICE="opensearch-dashboards.service"
-MAX_HEAP_MB=31744 # This limit is primarily for Java-based services (OpenSearch, Data Prepper)
-
-# --- Memory Calculation ---
-TOTAL_MEM_MB=$(free -m | awk '/^Mem:/{print $2}')
-OS_HEAP_MB=$(( TOTAL_MEM_MB * 40 / 100 ))
-DP_HEAP_MB=$(( TOTAL_MEM_MB * 25 / 100 ))
-
-# Explicitly set Dashboards memory to 4GB (4096 MB)
-DASHBOARDS_MB=4096
-
-# Apply MAX_HEAP_MB only to OpenSearch and Data Prepper
-(( OS_HEAP_MB > MAX_HEAP_MB )) && OS_HEAP_MB=$MAX_HEAP_MB
-(( DP_HEAP_MB > MAX_HEAP_MB )) && DP_HEAP_MB=$MAX_HEAP_MB
-
-
-echo "🧠 Total RAM: ${TOTAL_MEM_MB}MB"
-echo "📦 OpenSearch heap: ${OS_HEAP_MB}MB"
-echo "🚰 Data Prepper heap: ${DP_HEAP_MB}MB"
-echo "🖥️  Dashboards max-old-space-size: ${DASHBOARDS_MB}MB"
-
-# --- OpenSearch ---
-if [[ -f "$OPENSEARCH_JVM_OPTS" ]]; then
-  cp "$OPENSEARCH_JVM_OPTS" "$OPENSEARCH_JVM_OPTS.bak.$(date +%s)"
-  sed -i "s/^-Xms.*/-Xms${OS_HEAP_MB}m/" "$OPENSEARCH_JVM_OPTS"
-  sed -i "s/^-Xmx.*/-Xmx${OS_HEAP_MB}m/" "$OPENSEARCH_JVM_OPTS"
-  echo "✅ OpenSearch heap updated."
-else
-  echo "❌ OpenSearch JVM config not found."
-fi
-
-# --- Data Prepper ---
-DP_SERVICE=$(systemctl show -p FragmentPath data-prepper | cut -d'=' -f2)
-if [[ -n "$DP_SERVICE" && -f "$DP_SERVICE" ]]; then
-  cp "$DP_SERVICE" "$DP_SERVICE.bak.$(date +%s)"
-  sed -i '/^Environment="DATA_PREPPER_JAVA_OPTS=/d' "$DP_SERVICE"
-  sed -i "/^\[Service\]/a Environment=\"DATA_PREPPER_JAVA_OPTS=-Xms${DP_HEAP_MB}m -Xmx${DP_HEAP_MB}m\"" "$DP_SERVICE"
-  echo "✅ Data Prepper systemd unit updated."
-else
-  echo "❌ Data Prepper service unit not found."
-fi
-
-if [[ -f "$DATAPREPPER_SCRIPT" ]]; then
-  if ! grep -q 'export JAVA_OPTS="$JAVA_OPTS $DATA_PREPPER_JAVA_OPTS"' "$DATAPREPPER_SCRIPT"; then
-    cp "$DATAPREPPER_SCRIPT" "$DATAPREPPER_SCRIPT.bak.$(date +%s)"
-    sed -i '/^#!\/bin\/bash/a export JAVA_OPTS="$JAVA_OPTS $DATA_PREPPER_JAVA_OPTS"' "$DATAPREPPER_SCRIPT"
-    echo "✅ Patched Data Prepper launcher script."
-  else
-    echo "✅ Data Prepper launcher already patched."
-  fi
-else
-  echo "❌ Data Prepper script not found."
-fi
-
-# --- OpenSearch Dashboards ---
-DASHBOARDS_UNIT=$(systemctl show -p FragmentPath "$DASHBOARDS_SERVICE" | cut -d'=' -f2)
-if [[ -n "$DASHBOARDS_UNIT" && -f "$DASHBOARDS_UNIT" ]]; then
-  cp "$DASHBOARDS_UNIT" "$DASHBOARDS_UNIT.bak.$(date +%s)"
-  # The original sed command was sufficient, but we'll ensure it overrides any existing
-  # NODE_OPTIONS for max-old-space-size and adds the new value.
-  # We should also retain other NODE_OPTIONS if they exist, but for --max-old-space-size,
-  # we want to ensure only our new value is present.
-  sed -i '/^Environment="NODE_OPTIONS=/d' "$DASHBOARDS_UNIT" # Remove any existing NODE_OPTIONS
-  sed -i "/^\[Service\]/a Environment=\"NODE_OPTIONS=--max-old-space-size=${DASHBOARDS_MB}\"" "$DASHBOARDS_UNIT"
-  echo "✅ Dashboards memory setting updated."
-else
-  echo "❌ OpenSearch Dashboards service unit not found."
-fi
-
-# --- Reload + Restart ---
-systemctl daemon-reexec
-systemctl daemon-reload
-
-echo "♻️ Restarting services..."
-systemctl restart opensearch && echo "🚀 OpenSearch restarted."
-systemctl restart data-prepper && echo "🚀 Data Prepper restarted."
-systemctl restart "$DASHBOARDS_SERVICE" && echo "🚀 Dashboards restarted."
-
-echo "✅ All heap/memory updates applied successfully."
\ No newline at end of file
Index: /branches/amp_4_0/platform/tools/configure_psql.sh
===================================================================
--- /branches/amp_4_0/platform/tools/configure_psql.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/configure_psql.sh	(nonexistent)
@@ -1,12 +0,0 @@
-#!/bin/bash
-
-# Source custom path changes
-source /etc/profile.d/custom-path.sh
-
-AN_DB_NAME="cm"
-AN_USER="amp_admin"
-AN_USER_PASSWORD="Array@123$"
-
-# Import PSQL Tables for the AN provided database
-PGPASSWORD="$AN_USER_PASSWORD" psql -U "$AN_USER" -d "$AN_DB_NAME" -f ./config/init_db.sql
-
Index: /branches/amp_4_0/platform/tools/configure_telegraf_timescale.sh
===================================================================
--- /branches/amp_4_0/platform/tools/configure_telegraf_timescale.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/configure_telegraf_timescale.sh	(nonexistent)
@@ -1,241 +0,0 @@
-#!/bin/bash
-# configure_telegraf_timescale.sh
-# - No install, no repos, no tokens.
-# - Validates at least one SNMP input exists in telegraf.d (apv/ag/asf).
-# - Writes telegraf.conf with PostgreSQL output.
-# - Restarts Telegraf.
-
-set -euo pipefail
-
-LOG_FILE="/var/log/install_telegraf.log"
-TELEGRAF_CONFIG_DIR="/etc/telegraf/telegraf.d"
-TELEGRAF_CONFIG="/etc/telegraf/telegraf.conf"
-
-# === Timescale/PostgreSQL target (adjust if needed) ===
-PG_HOST="127.0.0.1"
-PG_PORT="5432"
-PG_DB="amp_ts"
-PG_USER="amp_ts_user"
-PG_PASSWORD="Array@123$"   # put this somewhere safer in production
-
-log() { echo "[$(date +'%F %T')] $*" | tee -a "$LOG_FILE" ; }
-need() { command -v "$1" >/dev/null 2>&1 || { log "ERROR: $1 not found"; exit 1; }; }
-
-# --- sanity checks ---
-for c in sudo grep sed awk paste telegraf systemctl; do need "$c"; done
-for d in "/var/log" "/etc/telegraf" "$TELEGRAF_CONFIG_DIR"; do
-  if [[ ! -d "$d" ]]; then
-    log "WARN: $d missing, creating..."
-    sudo mkdir -p "$d"
-  fi
-done
-
-# --- require at least one SNMP input file in telegraf.d ---
-SNMP_FILES=(
-  "/etc/telegraf/telegraf.d/apv.conf"
-  "/etc/telegraf/telegraf.d/ag.conf"
-  "/etc/telegraf/telegraf.d/asf.conf"
-)
-
-FOUND_SNMP=false
-for f in "${SNMP_FILES[@]}"; do
-  if [[ -f "$f" ]]; then
-    FOUND_SNMP=true
-    # quick validation (best-effort)
-    grep -qE '^[[:space:]]*agents[[:space:]]*=' "$f" || { log "ERROR: 'agents = [...]' not found in $f"; exit 1; }
-    grep -qm1 -E '^[[:space:]]*community[[:space:]]*=' "$f" || { log "ERROR: 'community = \"...\"' not found in $f"; exit 1; }
-    grep -qm1 -E '^[[:space:]]*timeout[[:space:]]*=' "$f" || { log "ERROR: 'timeout = \"...\"' not found in $f"; exit 1; }
-    log "$(basename "$f") looks OK (agents/community/timeout present)."
-  fi
-done
-$FOUND_SNMP || { log "ERROR: no SNMP input file found (expected one of: ${SNMP_FILES[*]})"; exit 1; }
-
-# --- backup any existing main config ---
-if [[ -f "$TELEGRAF_CONFIG" ]]; then
-  sudo cp -a "$TELEGRAF_CONFIG" "${TELEGRAF_CONFIG}.bak.$(date +%s)"
-  log "Backed up ${TELEGRAF_CONFIG}."
-fi
-
-# --- write a clean main config that targets Timescale/PostgreSQL ---
-log "Writing ${TELEGRAF_CONFIG}..."
-sudo tee "$TELEGRAF_CONFIG" >/dev/null <<EOF
-[agent]
-  interval = "10s"
-  round_interval = true
-  metric_batch_size = 5000
-  metric_buffer_limit = 50000
-  collection_jitter = "0s"
-  flush_interval = "10s"
-  precision = ""
-  hostname = "AN"
-  omit_hostname = false
-  skip_processors_after_aggregators = false
-
-# === Output: TimescaleDB/PostgreSQL ===
-[[outputs.postgresql]]
-  connection = "host=${PG_HOST} port=${PG_PORT} user=${PG_USER} password=${PG_PASSWORD} dbname=${PG_DB} sslmode=disable"
-  schema     = "public"
-  # Expect tables/columns to exist (no auto-DDL)
-
-# === Optional local host metrics ===
-[[inputs.cpu]]
-  percpu = true
-  totalcpu = true
-
-[[inputs.disk]]
-  mount_points = ["/", "/home", "/var"]
-  ignore_fs = ["tmpfs", "devtmpfs", "overlay", "rootfs"]
-
-[[inputs.mem]]
-
-[[inputs.diskio]]
-
-[[inputs.net]]
-  ignore_protocol_stats = true
-
-[[inputs.system]]
-  fieldinclude = ["load1", "load5", "load15", "uptime"]
-
-[[processors.regex]]
-  order = 1
-  namepass = ["asf_http_service"]
-  [[processors.regex.fields]]
-    key = ".*"
-    pattern = "\\\\x00"
-    replacement = ""
-
-[[processors.starlark]]
-  order = 2
-  namepass = ["asf_http_service"]
-  source = '''
-def apply(metric):
-    for key, value in metric.fields.items():
-        if type(value) == "string" and "\x00" in value:
-            metric.fields[key] = value.replace("\x00", "")
-    if "http_service_index" not in metric.fields or metric.fields["http_service_index"] == None:
-        metric.fields["http_service_index"] = 0
-    return metric
-'''
-
-[[processors.starlark]]
-  order = 3
-  namepass = ["asf_http_service"]
-  source = '''
-def apply(metric):
-    if "host" in metric.fields:
-        metric.fields.pop("host")
-    if "host" in metric.tags:
-        metric.tags.pop("host")
-    if "http_service_anomaly_contentlength" in metric.fields:
-        metric.fields.pop("http_service_anomaly_contentlength")
-    return metric
-'''
-
-[[processors.starlark]]
-  order = 4
-  source = '''
-def apply(metric):
-    if metric.name == 'asf_ssl_host_statistics' and 'ssl_index' not in metric.fields:
-        return None
-    if metric.name == 'asf_syslog_history' and 'idx' not in metric.fields:
-        return None
-    if metric.name not in ['apv_real_stats']:
-        return metric
-    if 'real_server_id' in metric.tags or 'serverid' in metric.fields:
-        return metric
-    return None
-'''
-
-[[processors.starlark]]
-  order = 5
-  namepass = ["apv_virtual_stats"]
-  source = '''
-def apply(metric):
-    total = 0
-    for key, value in metric.fields.items():
-        if (key.endswith("hits") or key.endswith("_hits")) and (type(value) == "int" or type(value) == "float"):
-            total += value
-    metric.fields["total_hits"] = total
-    return metric
-'''
-
-[[processors.starlark]]
-  namepass = ["apv_llb_stats"]
-  source = '''
-def apply(metric):
-    if metric.name != "apv_llb_stats":
-        return metric
-
-    # Fields that must remain as strings
-    string_fields = ["link_resp_time", "link_up_time", "link_down_time",
-                     "link_bandwid_in", "link_bandwid_out", "link_thresh",
-                     "link_status", "link_down_event", "link_name",
-                     "link_gateway", "host"]
-
-    # Fields that must be integers
-    int_fields = ["link_index", "link_hits", "link_conn", "link_usage",
-                  "link_down_count"]
-
-    # Convert string fields safely
-    for f in string_fields:
-        if f in metric.fields:
-            if metric.fields[f] == None:
-                metric.fields[f] = ""
-            else:
-                metric.fields[f] = str(metric.fields[f])
-
-    # Convert int fields safely
-    for f in int_fields:
-        if f in metric.fields:
-            val = metric.fields[f]
-            # Convert numeric strings to int, keep actual int, else 0
-            if val == None:
-                metric.fields[f] = 0
-            elif type(val) == int:
-                metric.fields[f] = val
-            elif type(val) == float:
-                metric.fields[f] = int(val)
-            elif type(val) == str:
-                # remove non-digit characters like 'kbps' or 'ms'
-                digits = ""
-                for c in val:
-                    if c in "0123456789.":
-                        digits += c
-                if digits == "":
-                    metric.fields[f] = 0
-                else:
-                    metric.fields[f] = int(float(digits))
-            else:
-                metric.fields[f] = 0
-
-    if "host" in metric.tags:
-        metric.tags.pop("host")
-
-    return metric
-'''
-
-
-EOF
-
-# --- test config (outputs are skipped in test mode) ---
-log "Testing telegraf config..."
-if ! telegraf --config "$TELEGRAF_CONFIG" --config-directory "$TELEGRAF_CONFIG_DIR" --test >/dev/null 2>&1; then
-  log "ERROR: telegraf --test failed. Inspect output with:"
-  log "       telegraf --config $TELEGRAF_CONFIG --config-directory $TELEGRAF_CONFIG_DIR --test"
-  exit 1
-fi
-log "telegraf --test passed."
-
-# --- restart telegraf ---
-log "Restarting Telegraf..."
-sudo systemctl restart telegraf || true
-sudo systemctl status telegraf --no-pager -l | sed -n '1,40p' || true
-
-if systemctl is-active --quiet telegraf; then
-  log "Telegraf is active."
-else
-  log "ERROR: Telegraf failed to start. See: journalctl -u telegraf -n 200 --no-pager"
-  exit 1
-fi
-
-log "Done."
Index: /branches/amp_4_0/platform/tools/container/.env
===================================================================
--- /branches/amp_4_0/platform/tools/container/.env	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/.env	(working copy)
@@ -1,11 +1,19 @@
-# .env file for AMP Docker Stack
+# ------------------------------------------------------------------
+# Brand:   Array Networks (AN)
+# Product: Array Management Platform (AMP)
+# Purpose: Environment Configuration for Docker Swarm Stack
+# ------------------------------------------------------------------
 
-# OpenSearch Credentials
+# --- OpenSearch (AMP Analytics Store) ---
 OPENSEARCH_INITIAL_ADMIN_PASSWORD=Arr@y2050
-OPENSEARCH_JAVA_OPTS="-Xms1g -Xmx1g"
 OPENSEARCH_JWT_SECRET="Arr@y2050"
 
-# TimescaleDB / Postgres Credentials
+# Memory Allocation:
+# By default, manage_amp.sh calculates this based on 50% of Host RAM.
+# Uncomment to override (e.g. for testing constraints).
+# OPENSEARCH_JAVA_OPTS="-Xms1g -Xmx1g"
+
+# --- TimescaleDB / Postgres (AMP Metrics Store) ---
 POSTGRES_USER=postgres
 POSTGRES_PASSWORD=Arr@y2050
 POSTGRES_DB=postgres
@@ -13,14 +21,30 @@
 AMP_DB_USER=amp_ts_user
 AMP_DB_PASSWORD=Array@123$
 
-# Grafana
+# --- Grafana (AMP Visualization) ---
 GF_SECURITY_ADMIN_USER=admin
 GF_SECURITY_ADMIN_PASSWORD=GArr@y2050
 GF_SERVER_ROOT_URL=https://localhost/monitoring/
 GF_SERVER_SERVE_FROM_SUB_PATH=true
 
-# Logstash
-LOGSTASH_JAVA_OPTS="-Xms1g -Xmx1g"
+# --- Logstash (AMP Ingest Pipeline) ---
+# Memory Allocation:
+# By default, manage_amp.sh calculates this based on 25% of Host RAM.
+# Uncomment to override.
+# LOGSTASH_JAVA_OPTS="-Xms1g -Xmx1g"
 
+# --- Ports Configuration ---
+AMP_NGINX_HTTP_PORT=80
+AMP_NGINX_HTTPS_PORT=443
+AMP_GRAFANA_PORT=3000
+AMP_TIMESCALEDB_PORT=5432
+
+# --- Network Configuration ---
 # Host IP for Nginx to reach backend on host
 HOST_BACKEND_IP=host.docker.internal
+
+# --- General Domain/IP Configuration ---
+# The IP address or Domain Name used to access AMP from the browser.
+# This MUST match the URL in your browser to avoid Grafana auth loops.
+# Uncomment to override auto-detection (e.g. for specific domain or IP).
+# AMP_DOMAIN_OR_IP=192.168.162.139
Index: /branches/amp_4_0/platform/tools/container/ARCHITECTURE.md
===================================================================
--- /branches/amp_4_0/platform/tools/container/ARCHITECTURE.md	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/ARCHITECTURE.md	(working copy)
@@ -0,0 +1,89 @@
+# System Architecture: AMP Platform on Docker Swarm
+
+## 1. Executive Summary
+
+This document details the architecture of the AMP Platform deployed on a 3-Node Docker Swarm cluster. The current design utilizes a **Hybrid High Availability (HA)** model: it provides full redundancy for network ingress and stateless processing, while pinning stateful storage to a designated primary node to ensure data consistency without external shared storage dependencies (NFS/SAN).
+
+## 2. Deployment Topology
+
+The cluster consists of three nodes participating in a Docker Swarm.
+
+### Node Roles
+
+| Node | Hostname | Swarm Role | Labels | Primary Responsibility |
+| :--- | :--- | :--- | :--- | :--- |
+| **Node 1** | `amp-node-1` | **Manager (Leader)** | `type=storage` | **Storage + Compute**. Hosts the database files (OpenSearch, TimescaleDB, Registry) on local high-speed disk. |
+| **Node 2** | `amp-node-2` | **Manager/Worker** | `type=compute` | **Compute Only**. Runs stateless services (Logstash, Nginx, Dashboards). |
+| **Node 3** | `amp-node-3` | **Manager/Worker** | `type=compute` | **Compute Only**. Runs stateless services (Logstash, Nginx, Dashboards). |
+
+### Component Diagram
+
+```mermaid
+graph TD
+    Client["Client / Log Sources"] --> VIP["Virtual IP (Keepalived)"]
+    
+    subgraph cluster_swarm ["Docker Swarm Cluster"]
+        VIP --> Nginx_Service["Nginx (Web Port 443)"]
+        VIP --> Logstash["Logstash (Syslog Port 514)"]
+        
+        subgraph cluster_stateless ["Stateless Layer (Any Node)"]
+            Nginx_Service --> Dashboards
+            Nginx_Service --> Grafana
+        end
+        
+        subgraph cluster_stateful ["Stateful Layer (Node 1 Only)"]
+            Logstash --> OpenSearch[("OpenSearch Data")]
+            Grafana --> Timescale[("TimescaleDB Data")]
+        end
+    end
+```
+
+## 3. High Availability (HA) Strategy
+
+### 3.1 Network Layer (Ingress)
+
+* **Technology**: `Keepalived` (VRRP).
+* **Mechanism**: A floating Virtual IP (VIP) is assigned to the active Leader (Node 1). If Node 1 fails, the VIP automatically migrates to Node 2 or Node 3 within seconds.
+* **Benefit**: External systems (Syslog senders, User Browsers) never need to reconfigure IPs. The "Front Door" is always open.
+
+### 3.2 Compute Layer (Stateless Services)
+
+* **Services**: `nginx`, `logstash`, `opensearch-dashboards`, `grafana`.
+* **Mechanism**: Docker Swarm Orchestration.
+* **Behavior**: These services are not pinned. If a node fails, Swarm reschedules replicas to remaining healthy nodes.
+* **Benefit**: Continuous request processing. Dashboards and Ingestion endpoints remain accessible.
+
+### 3.3 Storage Layer (Stateful Services)
+
+* **Services**: `opensearch`, `timescaledb`.
+* **Mechanism**: Pinned placement (`constraints: - node.labels.type == storage`) using Local Docker Volumes (`driver: local`).
+* **Behavior**: These services MUST run on Node 1 because their data files exist physically on Node 1's disk. They cannot start on other nodes.
+
+## 4. Architectural Analysis
+
+### 4.1 Advantages
+
+1. **High Performance (I/O)**: By using local disks (NVMe/SSD) on Node 1 instead of NFS, database write speeds are maximized. This is critical for high-volume log ingestion.
+2. **Stateless Scalability**: Heavy processing tasks (Log Parsing via Logstash, SSL Termination via Nginx) are distributed across 3 nodes. Node 1 is not overwhelmed by CPU tasks, leaving it free to handle Database I/O.
+3. **Simplicity**: No requirement for external NAS, SAN, or complex Ceph/GlusterFS configurations. Reduced maintenance overhead.
+4. **Partial Failover**: In the event of a Node 1 failure, the UI remains accessible (with connection errors) and VIP remains pingable, preventing "Connection Refused" errors on client side.
+
+### 4.2 Limitations & Risks
+
+1. **Single Point of Failure (Data)**: If Node 1 fails, **OpenSearch and TimescaleDB go offline**. No new logs can be written, and no historical data can be queried.
+2. **Data Loss Risk (Buffer Overflow)**: While Logstash (on surviving nodes) will buffer incoming logs in memory, it will eventually fill up and start dropping data if Node 1 does not recover quickly.
+3. **Manual Recovery**: If Node 1 has a hardware failure, recovering the data requires restoring from backups, as the data does not exist on Nodes 2/3.
+
+## 5. Solution: Path to Full HA
+
+To mitigate the storage limitation and achieve **Zero Downtime**, the cluster can be upgraded to use **Shared Storage**.
+
+### Implementation: NFS Migration
+
+* **Dependency**: An external NFS Server accessible by all 3 nodes.
+* **Configuration**:
+    1. Update `stack.yml` volumes to use `driver_opts: type: nfs`.
+    2. Remove `placement.constraints` from database services.
+* **Result**: Database containers effectively become stateless. If Node 1 fails, Swarm simply restarts OpenSearch on Node 2, which mounts the same NFS path and resumes operations immediately.
+
+This upgrade can be performed without reinstalling the cluster, by modifying the `stack.yml` as detailed in `README.md`.
Index: /branches/amp_4_0/platform/tools/container/README.md
===================================================================
--- /branches/amp_4_0/platform/tools/container/README.md	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/README.md	(working copy)
@@ -1,158 +1,540 @@
-# Container Tools
+# Docker Swarm Deployment Guide
 
-This directory contains the Docker container configurations and tools for the AMP platform.
+## 1. Introduction
 
-## Directory Structure
+This directory contains the configurations and scripts to deploy the AMP platform on **Docker Swarm**.
 
-The environment is modularized to allow for easier management and verification of individual services.
+### Prerequisites
 
-- **`compose/`**: Contains individual Docker Compose files for each service definition (e.g., `opensearch.yml`, `timescaledb.yml`) and a `base.yml` for shared networks and volumes.
-- **`services/`**: Contains configuration files and build contexts for specific services (e.g., `services/opensearch/`, `services/nginx/`).
-- **`manage_service.sh`**: The primary script to manage the lifecycle and health of these services.
-- **`install_prerequisites.sh`**: Script to install host dependencies (`rsync`, `docker`, `python3`, `java`) and configure system limits.
+1. **Docker Engine**: Installed on all nodes.
+2. **Swarm Mode**: Active on the manager node.
+3. **Ports**: Ensure Swarm ports are open between nodes (TCP 2377, 7946; UDP 7946, 4789).
 
-## File Locations & Persistence
+## 2. Architecture Overview
 
-The AMP platform is designed to be stateless in its container definitions, with configuration and persistent data decoupled from the images.
+Unlike standard Docker Compose (which runs everything on a single host with local bind-mounts), this Swarm implementation is designed for **orchestration** across one or more nodes.
 
-### 1. Configuration Files
+- **Stack**: `stack.yml` defines the services, replicating the Compose setup but adapted for Swarm (Overlay networks, Configs, Secrets).
+- **Registry**: A local Docker Registry (`localhost:5000`) is used to share images between nodes.
+- **Configs/Secrets**: Configuration files are immutable and injected into containers managed by Swarm.
+- **Pinning**: Stateful services (`opensearch`, `timescaledb`) are pinned to specific nodes using labels to ensure they can access their data volumes.
 
-All service configurations (e.g., `nginx.conf`, `telegraf.conf`, certificates) are stored in the host repository and mounted into the containers as read-only volumes.
+### Architecture Summary
 
-- **Path**: `platform/tools/container/services/<service_name>/`
-- **Updates**: Changes made to files in these directories are typically picked up by the services after a restart via `./manage_service.sh <service> up`.
+| Feature | OpenSearch (Logs) | TimescaleDB (Metrics) |
+| :--- | :--- | :--- |
+| **Scaling** | Multi-Node (Active-Active) | Single-Node (Active) |
+| **Data Location** | Distributed (Shards on all nodes) | Pinned (Storage Node only) |
+| **HA Strategy** | Application-Level Replication | Storage Node Reliability |
+| **Node Failure** | Logs accessible from other nodes | Metrics unavailable until node recovers |
 
-### 2. Persistent Data (Volumes)
+## 3. Single Node Deployment
 
-Stateful data (databases, logs, indices) is stored in **Named Docker Volumes**. This ensures data survives container updates and accidental removals.
+For a development or single-server production environment:
 
-| Volume Name | Description |
-| :--- | :--- |
-| **`opensearch-data`** | OpenSearch indices and cluster state. |
-| **`timescaledb-data`** | TimescaleDB (PostgreSQL) relational data and logs. |
-| **`grafana-data`** | Grafana sqlite database (dashboards, users). |
-| **`certs-vol`** | Shared SSL certificates generated during `setup`. |
-| **`security-config-vol`** | OpenSearch Security plugin configurations. |
-| **`*-logs`** | Log volumes for each service (e.g., `opensearch-logs`, `nginx-logs`). |
+1. **Prerequisites**:
+    Run the helper script (from parent directory) to install Docker and configure firewall ports:
 
-### 3. Accessing Persistent Data & Logs
+    ```bash
+    ./install_prerequisites.sh
+    ```
 
-Since data and logs are stored in Docker volumes (not directly on the host filesystem), you cannot browse them with a standard file explorer. Instead, use Docker tools or helper containers.
+2. **Initialize Swarm**:
 
-#### Accessing Logs
+    ```bash
+    ./manage_amp.sh init
+    ```
 
-Logs are stored in named volumes (e.g., `opensearch-logs`). To view or export them:
+    *This initializes Swarm and labels the current node as `type=storage` so databases can run on it.*
 
-**Quick View (Last 100 lines):**
+3. **Generate Certificates**:
 
+    ```bash
+    ./manage_amp.sh setup
+    ```
+
+    *This generates self-signed certificates and places them in the `certs-vol` volume required by services.*
+
+4. **Build & Deploy**:
+
+    ```bash
+    ./manage_amp.sh build
+    ./manage_amp.sh deploy
+    ```
+
+    *This builds images, pushes them to the local registry, and deploys the `amp` stack.*
+
+5. **Initialize Security**:
+
+    ```bash
+    ./manage_amp.sh security_init
+    ```
+
+    *This initializes the OpenSearch security index.*
+
+6. **Configure OpenSearch**:
+
+    ```bash
+    ./manage_amp.sh configurator
+    ```
+
+    *This applies security roles and creates index patterns.*
+
+7. **Verify**:
+
+    ```bash
+    ./manage_amp.sh status
+    ```
+
+## 4. Multi-Node Deployment
+
+To run on multiple nodes (e.g., Manager + Workers):
+
+### 1. Prerequisite Setup (All Nodes)
+
+On **Manager** and **all Worker** nodes, ensure Docker is installed and ports are open:
+
 ```bash
-docker run --rm -v opensearch-logs:/logs alpine tail -n 100 /logs/opensearch.log
+# Copy install_prerequisites.sh to the node if needed, then run:
+./install_prerequisites.sh
 ```
 
-**Interactive Shell:**
+### 2. Configure Hostnames (Critical for Swarm)
 
+Docker Swarm uses hostnames to identify nodes. If all nodes are named `localhost`, it will be very confusing.
+
+Run this on **each node** to give it a unique name:
+
 ```bash
-docker run --rm -it -v nginx-logs:/logs alpine sh
-# cd /logs && ls -l
+# On Manager
+hostnamectl set-hostname manager-1
+
+# On Worker 1
+hostnamectl set-hostname worker-1
+
+# On Worker 2
+hostnamectl set-hostname worker-2
 ```
 
-#### Accessing Data
+*Re-login to the shell for the change to take effect.*
 
-Database files are similarly protected. To inspect raw data files (debug only):
+### 3. Manager Setup
 
+On the **Manager** node:
+
 ```bash
-docker run --rm -v timescaledb-data:/data alpine ls -lR /data
+./manage_amp.sh init
 ```
 
-#### Accessing Configuration
+This prints a token to join workers.
 
-Configuration files are **mounted from the host** and are directly editable:
+### 3. Join Workers
 
-- Go to: `platform/tools/container/services/<service_name>/`
-- Edit the files (e.g., `nginx/conf.d/app.conf`).
-- Apply changes: `./manage_service.sh <service_name> up`
+On each **Worker** node:
 
-> [!TIP]
-> To find the physical location of a volume on your host system (Linux only), use:
+```bash
+docker swarm join --token <TOKEN> <MANAGER_IP>:2377
+```
+
+### 4. Configure Storage Node
+
+Choose **one** node (usually the manager or a specific high-disk node) to host the databases. Find its Node ID and label it:
+
+```bash
+docker node ls
+docker node update --label-add type=storage <NODE_ID>
+```
+
+### 5. Generate Certificates
+
+On the **Manager** node:
+
+```bash
+./manage_amp.sh setup
+```
+
+> [!IMPORTANT]
+> **Multi-Node Volume Warning**: Since we are using standard (local) Docker volumes, the `certs-vol` created on the Manager is **not** automatically available on Worker nodes.
+> You must either:
 >
-> ```bash
-> docker volume inspect <volume_name>
-> ```
+> 1. Use a shared volume plugin (NFS, etc.) for `certs-vol`.
+> 2. Or manually copy the contents of `certs-vol` from the Manager to the corresponding volume on every Worker node.
 >
-> On macOS/Windows (Docker Desktop), the physical files are inside the Docker VM and not directly accessible on the host OS without mounting them as shown above.
+> If you skip this, services on Worker nodes will fail to find certificates.
 
-#### Physical Locations (Linux / Docker VM)
+### 6. Deploy
 
-If running on a standard Linux Docker host (or inside the Docker VM), the logs are located at `/var/lib/docker/volumes/<project>_<volume_name>/_data`.
-Assuming the project name is `compose` (default), the locations are:
+On the **Manager** node, specify the **Manager IP** for the registry so workers can reach it:
 
-| Service | Host Path (Linux) |
-| :--- | :--- |
-| **OpenSearch** | `/var/lib/docker/volumes/compose_opensearch-logs/_data/` |
-| **OpenSearch Dashboards** | `/var/lib/docker/volumes/compose_opensearch-dashboards-logs/_data/` |
-| **Nginx** | `/var/lib/docker/volumes/compose_nginx-logs/_data/` |
-| **Grafana** | `/var/lib/docker/volumes/compose_grafana-logs/_data/` |
-| **Telegraf** | `/var/lib/docker/volumes/compose_telegraf-logs/_data/` |
-| **Logstash** | `/var/lib/docker/volumes/compose_logstash-logs/_data/` |
-| **PgBouncer** | `/var/lib/docker/volumes/compose_pgbouncer-logs/_data/` |
-| **TimescaleDB** | `/var/lib/docker/volumes/compose_timescaledb-data/_data/pg_log/` |
+```bash
+export REGISTRY=<MANAGER_IP>:5000
+./manage_amp.sh build
+./manage_amp.sh deploy
+```
 
-## Setup & Prerequisites
+Wait for services to start.
 
-Before running services, ensure your system has the necessary tools. OpenSearch requires `vm.max_map_count` >= 262144.
+### 7. Configure OpenSearch
 
-Run the helper script to attempt automatic host setup:
+Once OpenSearch is running (check `docker service ls`), run:
 
 ```bash
+./manage_amp.sh configurator
+```
+
+Wait for the services to stabilize. Images will be pulled from the manager's local registry by all workers.
+
+## 5. Multi-Node Deployment with Manager and Worker (Small Clusters)
+
+This guide is for small clusters (1-5 nodes) where nodes must act as both **Managers** and **Workers** to maximize resource utilization, often requiring high availability (HA) for the control plane.
+
+### 1. Prerequisite Setup (Small Cluster)
+
+On **every node** (Managers and future Workers), run the installation script:
+
+```bash
+# Copy install_prerequisites.sh to the node
 ./install_prerequisites.sh
 ```
 
-## Usage
+# Copy install_prerequisites.sh to the node
 
-Use `manage_service.sh` to manage and verify services.
+./install_prerequisites.sh
 
-### 1. Basic Usage
+```
 
+### 2. Configure Hostnames
+
+Since all nodes act as both Managers and Workers, give them generic numbered names:
+
 ```bash
-./manage_service.sh [service_name] [action]
+# Node 1
+hostnamectl set-hostname amp-node-1
+
+# Node 2
+hostnamectl set-hostname amp-node-2
+
+# Node 3
+hostnamectl set-hostname amp-node-3
 ```
 
-- **`service_name`**: Name of the service (filename in `compose/` without .yml).
-- **`action`** (optional):
-  - `up` (default): Start service and run health checks.
-  - `down`: Stop and remove the service.
-  - `purge`: Stop service and remove its volumes (fresh start).
-  - `validate`: Run health checks on a running service.
+*Re-login to the shell for the change to take effect.*
 
-### 2. Recommended Startup Order
+### 3. Initialize First Manager
 
-1. **Setup & Core Storage**
+On the **first node** (Node 1):
 
-   ```bash
-   ./manage_service.sh setup
-   ./manage_service.sh opensearch
-   ./manage_service.sh timescaledb
-   ```
+```bash
+./manage_amp.sh init
+```
 
-2. **Backend & Middleware**
+*This initializes the Swarm and generates the join token.*
 
-   ```bash
-   ./manage_service.sh configurator
-   ./manage_service.sh pgbouncer
-   ```
+### 3. Join & Promote Other Nodes
 
-3. **Frontend & Observability**
+1. **Join Node 2 & 3**: Run the join command (output from Step 2) on the other nodes.
 
-   ```bash
-   ./manage_service.sh opensearch-dashboards
-   ./manage_service.sh grafana
-   ./manage_service.sh telegraf
-   ./manage_service.sh logstash
-   ./manage_service.sh nginx
-   ```
+    ```bash
+    docker swarm join --token <TOKEN> <NODE_1_IP>:2377
+    ```
 
-## Development Notes
+2. **Promote to Manager** (On Node 1):
+    To ensure HA for the Swarm control plane, promote the new nodes to Managers.
 
-- **Modifying Configuration**: Service configurations are located in `services/<service_name>`.
-- **Modifying Compose Definitions**: Docker compose definitions are in `compose/<service_name>.yml`.
-- Volumes in `compose/*.yml` reference `../services/<service_name>/...`.
+    ```bash
+    docker node promote <NODE_2_HOSTNAME>
+    docker node promote <NODE_3_HOSTNAME>
+    ```
+
+### 4. Configure Mixed Roles (Active)
+
+By default, we want these managers to also run workloads (containers). Ensure they are set to `Active` availability (not `Drain`).
+
+On a Manager node:
+
+```bash
+# Repeat for all node IDs
+docker node update --availability active <NODE_ID>
+```
+
+### 5. Configure Storage Node
+
+Choose **ONE** specific node to host the database data (pinning).
+
+```bash
+docker node ls
+docker node update --label-add type=storage <NODE_ID>
+```
+
+### 6. High Availability: Traffic Routing (VIP)
+
+Since any node might go down, do not point users to a specific Node IP. Instead, configure a Floating VIP using the provided script.
+
+1. **Run `configure_vip.sh`** on **EACH** node with a unique priority:
+
+    ```bash
+    # Node 1 (Primary)
+    ./configure_vip.sh --vip 192.168.200.100 --priority 102
+
+    # Node 2 (Backup)
+    ./configure_vip.sh --vip 192.168.200.100 --priority 101
+
+    # Node 3 (Backup)
+    ./configure_vip.sh --vip 192.168.200.100 --priority 100
+    ```
+
+    *Replace `192.168.200.100` with your desired VIP.*
+
+2. **DNS**: Point your domain to this VIP.
+
+### 7. Deploy Stack
+
+On **Node 1** (or any Manager):
+
+1. **Generate Certificates**:
+
+    ```bash
+    ./manage_amp.sh setup
+    ```
+
+> [!IMPORTANT]
+    > **Manual Certificate Distribution**: Since `certs-vol` is a local volume, other nodes cannot access certificates generated on Node 1. You must copy them manually.
+
+    **Steps to distribute certificates:**
+
+    1.  **Locate the Volume Path** (On Node 1):
+        ```bash
+        SOURCE_PATH=$(docker volume inspect certs-vol --format '{{.Mountpoint}}')
+        echo "Certs are at: $SOURCE_PATH"
+        ```
+
+    2.  **Copy to Other Nodes** (Run on Node 1):
+        *Replace `amp-node-2`, `amp-node-3` with your other node IPs/Hostnames.*
+
+        ```bash
+        # Loop through other nodes
+        for NODE in amp-node-2 amp-node-3; do
+            echo "--- Processing $NODE ---"
+            
+            # 1. Create volume on destination
+            ssh root@$NODE "docker volume create certs-vol"
+
+            # 2. Get destination path
+            DEST_PATH=$(ssh root@$NODE "docker volume inspect certs-vol --format '{{.Mountpoint}}'")
+            
+            # 3. Copy files
+            scp -r $SOURCE_PATH/* root@$NODE:$DEST_PATH/
+            
+            echo "✅ Copied to $NODE"
+        done
+        ```
+
+2. **Deploy**:
+
+    You cannot use `127.0.0.1` because other nodes will try to pull from *their own* localhost and fail.
+    
+    Use the **Physical IP** of Node 1 (where the registry is running).
+
+    ```bash
+    # Replace with Node 1's actual IP (e.g., 192.168.162.139)
+    export REGISTRY=192.168.162.139:5000 
+    
+    ./manage_amp.sh build
+    ./manage_amp.sh deploy
+    ./manage_amp.sh security_init
+    ```
+
+3. **Configure OpenSearch**:
+
+    The OpenSearch configuration (including admin certificates) is managed in `services/opensearch/opensearch.yml`.
+    If you change environment variables or certificate names, update this file and redeploy.
+
+    ```bash
+    ./manage_amp.sh configurator
+    ```
+
+## 6. Full High Availability (HA) with NFS
+
+To achieve **Full Failover** (where services restart on another node if the leader fails), you must use **Shared Storage** (NFS).
+
+> [!IMPORTANT]
+> Without NFS, database services (`opensearch`, `timescaledb`) are pinned to Node 1 (`type=storage`). If Node 1 fails, these services go down.
+> With NFS, services can float to **any node**.
+
+### 1. Prerequisites (All Nodes)
+
+Install NFS client on **ALL Swarm Nodes**:
+
+```bash
+# RedHat/Rocky/AlmaLinux
+dnf install -y nfs-utils
+
+# Ubuntu/Debian
+apt-get install -y nfs-common
+```
+
+### 2. Configure `stack.yml`
+
+You must edit `stack.yml` to:
+
+1. **Mount NFS Volumes**: Replace `driver: local` with NFS config.
+2. **Unpin Services**: Remove `constraints: - node.labels.type == storage`.
+
+#### Example `stack.yml` Modification
+
+```yaml
+services:
+  opensearch:
+    deploy:
+      # REMOVE constraints for HA
+      # placement:
+      #   constraints:
+      #     - node.labels.type == storage 
+      replicas: 1 
+
+volumes:
+  # Example for OpenSearch Data
+  opensearch-data:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr=192.168.1.100,rw,nfsvers=4
+      device: ":/volume1/nfs/amp/opensearch"
+
+  # Repeat for: timescaledb-data, grafana-data, certs-vol, etc.
+```
+
+### 3. Deploy
+
+1. **Destroy** the old stack (required to remove old volumes):
+
+    ```bash
+    ./manage_amp.sh destroy
+    ```
+
+2. **Prune** old volumes (WARNING: Deletes Data!):
+
+    ```bash
+    docker volume prune -f
+    ```
+
+3. **Deploy New Stack**:
+
+    ```bash
+    ./manage_amp.sh deploy
+    ```
+
+## 7. Limitations and Options
+
+### Data Persistence & Locations
+
+Understanding where your data lives is critical for backup and recovery.
+
+- **Storage Node**:
+  - In a **Single Node** setup, the manager is effectively the storage node.
+  - In a **Multi-Node** setup, only the node labeled `type=storage` holds the data (unless using NFS).
+
+- **Data Locations (Local Volumes)**:
+  `/var/lib/docker/volumes/`
+
+  | Volume Name | Content | Service |
+  | :--- | :--- | :--- |
+  | `amp_opensearch-data` | OpenSearch Indices & Shards | `opensearch` |
+  | `amp_timescaledb-data` | Time-series Metrics Data | `timescaledb` |
+  | `amp_grafana-data` | Dashboards, Users, Alerts | `grafana` |
+
+### Data Lifecycle (Crash vs Delete)
+
+- **Service Crash / Restart**: ✅ **Safe**.
+  If a container crashes or is manually restarted, it re-mounts the **same** volume. No data is lost.
+
+- **Stack Removal (`./manage_swarm.sh rm`)**: ✅ **Safe**.
+  Docker Swarm **does not** delete named volumes when a stack is removed. The volumes (`amp_opensearch-data`, etc.) remain on the disk.
+  When you re-deploy (`./manage_swarm.sh deploy`), the new services will attach to the **existing** volumes and resume with all old data.
+
+- **Node Restart**: ✅ **Safe**.
+  Data persists on the disk. When the node boots up, Docker mounts the volume again.
+
+- **Manual Volume Prune**: ⚠️ **DANGER**.
+  Data is only deleted if you explicitly run `docker volume rm <name>` or `docker volume prune`.
+
+### Addressing TimescaleDB Limitations (HA Options)
+
+Since TimescaleDB runs in a single container, here are 3 ways to improve its reliability:
+
+1. **Option A: Infrastructure HA (Recommended)**
+    - Run the Storage Node as a VM. Rely on Hypervisor (VMware/Hyper-V) to restart the VM if hardware fails.
+2. **Option B: Shared Storage (NFS)**
+    - Decouple data from the node using NFS (see Section 6). Swarm can then reschedule the DB to any node.
+3. **Option C: External Managed Database**
+    - Use AWS RDS or Google Cloud SQL.
+
+## 8. Advanced Config
+
+### Management Commands
+
+Use the `manage_amp.sh` script for common tasks.
+
+| Action | Description |
+| :--- | :--- |
+| `setup` | Generate SSL certificates. |
+| `build` | Build images and push to local registry. |
+| `deploy` | Deploy the stack. |
+| `configurator` | Configure OpenSearch roles/indices. |
+
+### Configs & Secrets
+
+Modifying files in `../services/` requires a **re-deploy**. Swarm Configs are immutable.
+
+## 9. Troubleshooting
+
+### "Volume 'certs-vol' was just created and is empty"
+
+Run `./manage_swarm.sh setup` immediately.
+
+### Connection Refused (ECONNREFUSED)
+
+Normal during startup. Wait for OpenSearch to be ready.
+
+### Services Stuck at 0/1
+
+Use `docker service ps <service_name> --no-trunc`. Common causes: missing secrets, missing certificates, or placement constraints.
+
+## 10. Monitoring & Alerting
+
+### Self-Healing
+
+Docker Swarm automatically restarts containers if they crash. We have configured **Healthchecks** (`HEALTHCHECK`) for critical services:
+
+- **OpenSearch**: Checks cluster health status.
+- **TimescaleDB**: Checks Postgres connectivity.
+- **Nginx**: Checks HTTP responsiveness.
+- **Grafana**: Checks API availability.
+
+If a service becomes unresponsive (even if the process is running), Swarm will mark it as unhealthy and restart it automatically.
+
+### Alerting (Grafana)
+
+The platform uses Grafana for alerting.
+
+1. **Access Grafana**: `https://<host-ip>/monitoring/`.
+2. **Contact Points**: Go to **Alerting > Contact points** to define where to send alerts (Email, Slack, PagerDuty, Webhook).
+3. **Notification Policies**: Route alerts to specific contact points.
+4. **Alert Rules**: Create rules based on Telegraf metrics.
+    - *Example CPU Alert*: `mean(cpu_usage_system) > 90` for 5 minutes.
+    - *Example Disk Alert*: `disk_used_percent > 85`.
+    - *Example OpenSearch*: `opensearch_cluster_status != green`.
+
+Telegraf is pre-configured to collect:
+
+- Host Metrics (CPU, Memory, Disk, Net)
+- Docker Metrics (Container status)
+- Postgres Metrics
+- OpenSearch Metrics (via `logstash` pipeline or configured plugin)
+
+### Logging
+
+Logs are centralized in OpenSearch.
+
+- **View Logs**: Go to **OpenSearch Dashboards** -> **Discover**.
+- **Index Pattern**: Create an index pattern for `logstash-*`.
Index: /branches/amp_4_0/platform/tools/container/compose/backend.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/compose/backend.yml	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/compose/backend.yml	(nonexistent)
@@ -1,23 +0,0 @@
-services:
-  backend:
-    build:
-      context: ..
-      dockerfile: services/backend/Dockerfile
-    container_name: backend
-    environment:
-      - DJANGO_SETTINGS_MODULE=djproject.settings
-      - POSTGRES_HOST=pgbouncer
-      - POSTGRES_PORT=6432
-      - POSTGRES_DB=cm
-      - POSTGRES_USER=amp_admin
-      - POSTGRES_PASSWORD=Array@123$
-      - OPENSEARCH_HOSTS=https://opensearch-node1:9200
-      - OPENSEARCH_INITIAL_ADMIN_PASSWORD=${OPENSEARCH_INITIAL_ADMIN_PASSWORD}
-    volumes:
-      # Mount application source code
-      - ../../../../src/webui/webui/htdocs/new/src:/app:rw
-      - ../../../../extensions:/extensions:rw
-    command: ["python", "manage.py", "runserver", "0.0.0.0:8888"]
-    networks:
-      - amp-network
-      - timescaledb
Index: /branches/amp_4_0/platform/tools/container/compose/base.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/compose/base.yml	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/compose/base.yml	(nonexistent)
@@ -1,19 +0,0 @@
-volumes:
-  opensearch-data:
-  timescaledb-data:
-  grafana-data:
-  certs-vol:
-    external: true
-  security-config-vol:
-  opensearch-logs:
-  nginx-logs:
-  timescaledb-logs:
-  grafana-logs:
-  telegraf-logs:
-  logstash-logs:
-  opensearch-dashboards-logs:
-  pgbouncer-logs:
-
-networks:
-  amp-network:
-    driver: bridge
Index: /branches/amp_4_0/platform/tools/container/compose/configurator.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/compose/configurator.yml	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/compose/configurator.yml	(nonexistent)
@@ -1,16 +0,0 @@
-services:
-  configurator:
-    image: rockylinux:9
-    container_name: amp-configurator
-    environment:
-      - OPENSEARCH_INITIAL_ADMIN_PASSWORD=${OPENSEARCH_INITIAL_ADMIN_PASSWORD}
-    volumes:
-      - certs-vol:/usr/share/opensearch/config/certs:ro
-      - ../services/setup:/setup:ro
-      - ../services/setup/install_curl.sh:/usr/local/bin/install_curl.sh
-      - ../services/setup/configure_opensearch.sh:/usr/local/bin/configure_opensearch.sh
-      - ../services/opensearch/amplog_template.json:/usr/share/opensearch/config/amplog_template.json
-      - ../services/opensearch-dashboards/export.ndjson:/usr/share/opensearch/config/export.ndjson
-    command: ["/bin/sh", "-c", "bash /usr/local/bin/install_curl.sh && bash /usr/local/bin/configure_opensearch.sh"]
-    networks:
-      - amp-network
Index: /branches/amp_4_0/platform/tools/container/compose/grafana.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/compose/grafana.yml	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/compose/grafana.yml	(nonexistent)
@@ -1,20 +0,0 @@
-services:
-  grafana:
-    image: grafana/grafana:latest
-    container_name: grafana
-    environment:
-      GF_SECURITY_ADMIN_USER: ${GF_SECURITY_ADMIN_USER}
-      GF_SECURITY_ADMIN_PASSWORD: ${GF_SECURITY_ADMIN_PASSWORD}
-      GF_SERVER_ROOT_URL: ${GF_SERVER_ROOT_URL}
-      GF_SERVER_SERVE_FROM_SUB_PATH: ${GF_SERVER_SERVE_FROM_SUB_PATH}
-      GF_PLUGINS_PREINSTALL: grafana-opensearch-datasource
-      GF_LOG_MODE: console,file
-      GF_PATHS_LOGS: /var/log/grafana
-    volumes:
-      - grafana-data:/var/lib/grafana
-      - grafana-logs:/var/log/grafana
-      - ../services/grafana/provisioning:/etc/grafana/provisioning
-    ports:
-      - "3000:3000"
-    networks:
-      - amp-network
Index: /branches/amp_4_0/platform/tools/container/compose/logstash.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/compose/logstash.yml	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/compose/logstash.yml	(nonexistent)
@@ -1,40 +0,0 @@
-services:
-  logstash:
-    image: opensearchproject/logstash-oss-with-opensearch-output-plugin:latest
-    container_name: logstash
-    command: >
-      bash -c "
-        if [ ! -f /usr/share/logstash/config/certs/node.pem ]; then
-          echo 'Waiting for certs...'; sleep 5;
-        fi
-        
-        # Auto-download JDBC driver if missing
-        DRIVER_PATH=/usr/share/logstash/drivers/postgresql.jar
-        if [ ! -f $$DRIVER_PATH ]; then
-           echo 'Downloading PostgreSQL JDBC Driver...'
-           curl -L -o $$DRIVER_PATH https://jdbc.postgresql.org/download/postgresql-42.7.3.jar
-        fi
-        
-        logstash-plugin install logstash-integration-jdbc
-        logstash-plugin install logstash-filter-geoip
-        bin/logstash
-      "
-    environment:
-      LS_JAVA_OPTS: ${LOGSTASH_JAVA_OPTS}
-      OPENSEARCH_URL: https://opensearch-node1:9200
-      OPENSEARCH_INITIAL_ADMIN_PASSWORD: ${OPENSEARCH_INITIAL_ADMIN_PASSWORD}
-      POSTGRES_USER: ${POSTGRES_USER}
-      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
-      POSTGRES_DB: ${POSTGRES_DB}
-    volumes:
-      - ../services/logstash/config/logstash.yml:/usr/share/logstash/config/logstash.yml:ro
-      - ../services/logstash/pipeline:/usr/share/logstash/pipeline:ro
-      - ../services/logstash/drivers:/usr/share/logstash/drivers:ro
-      - certs-vol:/usr/share/logstash/config/certs:ro
-      - logstash-logs:/usr/share/logstash/logs
-    ports:
-      - "5044:5044"
-      - "5514:5514/udp"
-      - "5514:5514/tcp"
-    networks:
-      - amp-network
Index: /branches/amp_4_0/platform/tools/container/compose/nginx.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/compose/nginx.yml	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/compose/nginx.yml	(nonexistent)
@@ -1,15 +0,0 @@
-services:
-  nginx:
-    image: nginx:alpine
-    container_name: nginx
-    volumes:
-      - ../services/nginx/conf.d:/etc/nginx/conf.d
-      - certs-vol:/etc/nginx/certs:ro
-      - nginx-logs:/var/log/nginx
-    ports:
-      - "80:80"
-      - "443:443"
-    extra_hosts:
-      - "host.docker.internal:host-gateway"
-    networks:
-      - amp-network
Index: /branches/amp_4_0/platform/tools/container/compose/opensearch-dashboards.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/compose/opensearch-dashboards.yml	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/compose/opensearch-dashboards.yml	(nonexistent)
@@ -1,42 +0,0 @@
-services:
-  opensearch-dashboards:
-    image: opensearchproject/opensearch-dashboards:2.11.0
-    container_name: opensearch-dashboards
-    user: root
-
-    environment:
-      OPENSEARCH_HOSTS: '["https://opensearch-node1:9200"]'
-      OPENSEARCH_SSL_VERIFICATIONMODE: certificate
-      OPENSEARCH_USERNAME: admin
-      OPENSEARCH_PASSWORD: ${OPENSEARCH_INITIAL_ADMIN_PASSWORD}
-      OPENSEARCH_SSL_CERTIFICATEAUTHORITIES: '["/usr/share/opensearch-dashboards/config/certs/root-ca.pem"]'
-
-      SERVER_SSL_ENABLED: "true"
-      SERVER_SSL_KEY: /usr/share/opensearch-dashboards/config/certs/node-key.pem
-      SERVER_SSL_CERTIFICATE: /usr/share/opensearch-dashboards/config/certs/node.pem
-      LOGGING_DEST: /usr/share/opensearch-dashboards/logs/osd.log
-
-      SERVER_BASEPATH: /visualization
-      SERVER_REWRITEBASEPATH: "true"
-
-    ports:
-      - "5601:5601"
-
-    volumes:
-      - certs-vol:/usr/share/opensearch-dashboards/config/certs:ro
-      - /bin/yq:/usr/local/bin/yq:ro
-      - ../services/opensearch-dashboards/assets:/usr/share/opensearch-dashboards/src/core/server/core_app/assets/custom:ro
-      - opensearch-dashboards-logs:/usr/share/opensearch-dashboards/logs
-
-    command: >
-      bash -c "
-        chown -R 1000:1000 /usr/share/opensearch-dashboards/logs &&
-        runuser -u opensearch-dashboards -- /usr/share/opensearch-dashboards/opensearch-dashboards-docker-entrypoint.sh
-      "
-
-    networks:
-      - amp-network
-
-volumes:
-  certs-vol:
-    external: true
Index: /branches/amp_4_0/platform/tools/container/compose/opensearch.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/compose/opensearch.yml	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/compose/opensearch.yml	(nonexistent)
@@ -1,59 +0,0 @@
-services:
-  opensearch:
-    image: opensearchproject/opensearch:2.11.0
-    container_name: opensearch-node1
-    environment:
-      - cluster.name=opensearch-cluster
-      - node.name=opensearch-node1
-      - discovery.seed_hosts=opensearch-node1
-      - cluster.initial_master_nodes=opensearch-node1
-      - bootstrap.memory_lock=true
-      - "OPENSEARCH_JAVA_OPTS=${OPENSEARCH_JAVA_OPTS}"
-      - OPENSEARCH_INITIAL_ADMIN_PASSWORD=${OPENSEARCH_INITIAL_ADMIN_PASSWORD}
-      # Security Plugin Configuration
-      - plugins.security.ssl.http.enabled=true
-      - plugins.security.ssl.http.pemkey_filepath=/usr/share/opensearch/config/certs/node-key.pem
-      - plugins.security.ssl.http.pemcert_filepath=/usr/share/opensearch/config/certs/node.pem
-      - plugins.security.ssl.http.pemtrustedcas_filepath=/usr/share/opensearch/config/certs/root-ca.pem
-      - plugins.security.ssl.transport.enabled=true
-      - plugins.security.ssl.transport.pemkey_filepath=/usr/share/opensearch/config/certs/node-key.pem
-      - plugins.security.ssl.transport.pemcert_filepath=/usr/share/opensearch/config/certs/node.pem
-      - plugins.security.ssl.transport.pemtrustedcas_filepath=/usr/share/opensearch/config/certs/root-ca.pem
-      - plugins.security.restapi.roles_enabled=["all_access", "security_rest_api_access"]
-      - plugins.security.allow_default_init_securityindex=true
-      - plugins.security.nodes_dn=CN=node-1,O=OpenSearchNode,L=Bengaluru,ST=Karnataka,C=IN
-      - plugins.security.authcz.admin_dn=CN=admin,O=OpenSearchAdmin,L=Bengaluru,ST=Karnataka,C=IN
-    ulimits:
-      memlock:
-        soft: -1
-        hard: -1
-      nofile:
-        soft: 65536
-        hard: 65536
-    volumes:
-      - opensearch-data:/usr/share/opensearch/data
-      - certs-vol:/usr/share/opensearch/config/certs:ro
-      - security-config-vol:/usr/share/opensearch/config/opensearch-security-mount:ro
-      - opensearch-logs:/usr/share/opensearch/logs
-    command: >
-      bash -c "
-        set -e
-        # 1. Run demo installer to generate default security files (roles, users, etc.)
-        # This writes to /usr/share/opensearch/config/opensearch-security
-        chmod +x /usr/share/opensearch/plugins/opensearch-security/tools/install_demo_configuration.sh
-        /usr/share/opensearch/plugins/opensearch-security/tools/install_demo_configuration.sh -y -i
-        
-        # 2. Overwrite config.yml with our custom version (JWT, etc.)
-        echo '--- Overwriting config.yml ---'
-        cp /usr/share/opensearch/config/opensearch-security-mount/config.yml /usr/share/opensearch/config/opensearch-security/config.yml
-        
-        echo '--- Overwriting internal_users.yml ---'
-        cp /usr/share/opensearch/config/opensearch-security-mount/internal_users.yml /usr/share/opensearch/config/opensearch-security/internal_users.yml
-        
-        # 3. Start OpenSearch
-        # Disable demo config in entrypoint so it doesn't overwrite our work
-        export DISABLE_INSTALL_DEMO_CONFIG=true
-        ./opensearch-docker-entrypoint.sh
-      "
-    networks:
-      - amp-network
Index: /branches/amp_4_0/platform/tools/container/compose/pgbouncer.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/compose/pgbouncer.yml	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/compose/pgbouncer.yml	(nonexistent)
@@ -1,15 +0,0 @@
-services:
-  pgbouncer:
-    image: edoburu/pgbouncer:latest
-    container_name: pgbouncer
-    environment:
-      - DB_USER=amp_admin
-      - DB_PASSWORD=Array@123$
-      - DB_HOST=timescaledb
-      - DB_NAME=cm
-    volumes:
-      - ../services/pgbouncer/pgbouncer.ini:/etc/pgbouncer/pgbouncer.ini:ro
-      - ../services/pgbouncer/userlist.txt:/etc/pgbouncer/userlist.txt:ro
-      - pgbouncer-logs:/var/log/pgbouncer
-    networks:
-      - amp-network
Index: /branches/amp_4_0/platform/tools/container/compose/setup.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/compose/setup.yml	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/compose/setup.yml	(nonexistent)
@@ -1,13 +0,0 @@
-services:
-  setup:
-    image: rockylinux:9
-    container_name: amp-setup
-    working_dir: /setup
-    environment:
-      - OPENSEARCH_JWT_SECRET=${OPENSEARCH_JWT_SECRET}
-      - OPENSEARCH_INITIAL_ADMIN_PASSWORD=${OPENSEARCH_INITIAL_ADMIN_PASSWORD}
-    volumes:
-      - ../services/setup:/setup:rw
-      - certs-vol:/certs:rw
-      - security-config-vol:/security-config:rw
-    command: ["/bin/sh", "-c", "dnf install -y openssl httpd-tools && chmod +x /setup/setup.sh && /setup/setup.sh /certs /security-config"]
Index: /branches/amp_4_0/platform/tools/container/compose/telegraf.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/compose/telegraf.yml	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/compose/telegraf.yml	(nonexistent)
@@ -1,22 +0,0 @@
-services:
-  telegraf:
-    image: telegraf:latest
-    container_name: telegraf
-    user: "0:${DOCKER_GID:-0}"
-    group_add:
-      - "${DOCKER_GID:-0}"
-    environment:
-      PG_PASSWORD: ${POSTGRES_PASSWORD}
-    volumes:
-      - ../services/telegraf/telegraf.conf:/etc/telegraf/telegraf.conf:ro
-      - ../services/telegraf/telegraf.d:/etc/telegraf/telegraf.d:ro
-      - telegraf-logs:/var/log/telegraf
-      - /var/run/docker.sock:/var/run/docker.sock:ro
-      - /dev:/dev:ro
-      - /sys:/rootfs/sys:ro
-      - /proc:/rootfs/proc:ro
-      - /etc:/rootfs/etc:ro
-      - /run/udev:/run/udev:ro
-    command: ["telegraf", "--config", "/etc/telegraf/telegraf.conf", "--config-directory", "/etc/telegraf/telegraf.d"]
-    networks:
-      - amp-network
Index: /branches/amp_4_0/platform/tools/container/compose/timescaledb.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/compose/timescaledb.yml	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/compose/timescaledb.yml	(nonexistent)
@@ -1,15 +0,0 @@
-services:
-  timescaledb:
-    image: timescale/timescaledb:latest-pg16
-    container_name: timescaledb
-    environment:
-      POSTGRES_USER: ${POSTGRES_USER}
-      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
-      POSTGRES_DB: ${POSTGRES_DB}
-    volumes:
-      - timescaledb-data:/var/lib/postgresql/data
-      - ../services/postgres/initdb.d:/docker-entrypoint-initdb.d
-    ports:
-      - "5432:5432"
-    networks:
-      - amp-network
Index: /branches/amp_4_0/platform/tools/container/configure_vip.sh
===================================================================
--- /branches/amp_4_0/platform/tools/container/configure_vip.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/configure_vip.sh	(working copy)
@@ -0,0 +1,115 @@
+#!/bin/bash
+
+# configure_vip.sh
+# Usage: ./configure_vip.sh --vip <VIP> --priority <PRIORITY> [--interface <IFACE>]
+# Example (Node 1): ./configure_vip.sh --vip 192.168.1.100 --priority 101
+# Example (Node 2): ./configure_vip.sh --vip 192.168.1.100 --priority 100
+
+set -e
+
+VIP=""
+PRIORITY=""
+INTERFACE=""
+ROUTER_ID=51
+AUTH_PASS="array_amp"
+
+usage() {
+    echo "Usage: $0 --vip <IP_ADDRESS> --priority <INT> [--interface <IFACE>]"
+    echo "  --vip       : The Floating Virtual IP (e.g., 192.168.1.100)"
+    echo "  --priority  : Higher number = Higher preference (Master). Use 101, 100, 99..."
+    echo "  --interface : (Optional) Network interface. Auto-detected if omitted."
+    exit 1
+}
+
+# Parse Arguments
+while [[ "$#" -gt 0 ]]; do
+    case $1 in
+        --vip) VIP="$2"; shift ;;
+        --priority) PRIORITY="$2"; shift ;;
+        --interface) INTERFACE="$2"; shift ;;
+        *) echo "Unknown parameter: $1"; usage ;;
+    esac
+    shift
+done
+
+if [ -z "$VIP" ] || [ -z "$PRIORITY" ]; then
+    usage
+fi
+
+# Auto-detect interface if not specified
+if [ -z "$INTERFACE" ]; then
+    # Get the interface used for the default route
+    INTERFACE=$(ip route get 8.8.8.8 | awk '{print $5; exit}')
+    
+    if [ -z "$INTERFACE" ]; then
+        echo "❌ Could not auto-detect network interface. Please specify with --interface."
+        exit 1
+    fi
+    echo "ℹ️  Auto-detected interface: $INTERFACE"
+fi
+
+CONFIG_FILE="/etc/keepalived/keepalived.conf"
+BACKUP_FILE="/etc/keepalived/keepalived.conf.bak.$(date +%s)"
+
+echo "--- Configuring Keepalived ---"
+echo "VIP: $VIP"
+echo "Interface: $INTERFACE"
+echo "Priority: $PRIORITY"
+
+# Backup existing config
+if [ -f "$CONFIG_FILE" ]; then
+    echo "Backing up existing config to $BACKUP_FILE"
+    cp "$CONFIG_FILE" "$BACKUP_FILE"
+fi
+
+# Generate Config
+# We use 'state BACKUP' for all nodes. 
+# The one with the highest priority will be elected Master.
+# This avoids preemption flaps if we used MASTER/BACKUP explicitly without nopreempt.
+
+cat > "$CONFIG_FILE" <<EOF
+! Configuration File for AMP Swarm VIP
+! Generated by configure_vip.sh
+
+global_defs {
+   router_id AMP_NODE_$(hostname)
+}
+
+vrrp_instance VI_1 {
+    state BACKUP
+    interface $INTERFACE
+    virtual_router_id $ROUTER_ID
+    priority $PRIORITY
+    advert_int 1
+    
+    # Preempt: If a higher priority node comes online, it takes over.
+    # Set to 'nopreempt' if you want to minimize failovers.
+    preempt
+    
+    authentication {
+        auth_type PASS
+        auth_pass $AUTH_PASS
+    }
+    
+    virtual_ipaddress {
+        $VIP
+    }
+}
+EOF
+
+echo "✅ Configuration written to $CONFIG_FILE"
+
+# Restart Keepalived
+if systemctl is-active --quiet keepalived; then
+    echo "Restarting Keepalived..."
+    systemctl restart keepalived
+    echo "✅ Keepalived restarted."
+else
+    echo "⚠️  Keepalived is not running. Starting it..."
+    systemctl enable --now keepalived
+    echo "✅ Keepalived started."
+fi
+
+# Status check
+echo "--- Current IP Status ---"
+ip addr show $INTERFACE | grep "$VIP" || echo "Note: VIP '$VIP' is not currently active on this node (it may be on another node)."

Property changes on: platform/tools/container/configure_vip.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: /branches/amp_4_0/platform/tools/container/install_prerequisites.sh
===================================================================
--- /branches/amp_4_0/platform/tools/container/install_prerequisites.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/install_prerequisites.sh	(working copy)
@@ -61,6 +61,11 @@
             sudo apt-get install -y openjdk-17-jre
         fi
         
+        if ! check_cmd keepalived; then
+            echo "Installing Keepalived..."
+            sudo apt-get install -y keepalived
+        fi
+        
     elif [ "$PKG_MANAGER" == "dnf" ]; then
         # RHEL/Rocky Specific Logic using Custom Installers
         SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -92,6 +97,17 @@
              echo "⚠️  Custom install_curl.sh not found at $SETUP_DIR/install_curl.sh. Falling back to dnf."
              sudo $PKG_MANAGER install -y curl
         fi
+        
+        # Install keepalived via custom script or dnf
+        if [ -f "$SETUP_DIR/install_keepalived.sh" ]; then
+             echo "Invoking custom keepalived installer (install_keepalived.sh)..."
+             sudo bash "$SETUP_DIR/install_keepalived.sh"
+        else
+             if ! check_cmd keepalived; then
+                 echo "Installing Keepalived..."
+                 sudo $PKG_MANAGER install -y keepalived
+             fi
+        fi
     fi
 
     # rsync
@@ -145,4 +161,32 @@
         ;;
 esac
 
-echo "--- Prerequisites Check Complete ---"
+    # Swarm Ports Configuration
+    echo "--- Configuring Firewall for Swarm Ports ---"
+    # Swarm requires: TCP 2377, TCP/UDP 7946, UDP 4789
+    if command -v firewall-cmd >/dev/null 2>&1; then
+        echo "Detected firewalld. Opening Swarm ports..."
+        sudo firewall-cmd --permanent --add-port=2377/tcp
+        sudo firewall-cmd --permanent --add-port=7946/tcp
+        sudo firewall-cmd --permanent --add-port=7946/udp
+        sudo firewall-cmd --permanent --add-port=4789/udp
+        # Keepalived VRRP
+        sudo firewall-cmd --permanent --add-protocol=vrrp
+        sudo firewall-cmd --reload
+        echo "✅ Firewalld updated."
+    elif command -v ufw >/dev/null 2>&1; then
+        echo "Detected UFW. Opening Swarm ports..."
+        sudo ufw allow 2377/tcp
+        sudo ufw allow 7946/tcp
+        sudo ufw allow 7946/udp
+        sudo ufw allow 4789/udp
+        # Keepalived VRRP
+        sudo ufw allow vrrp
+        sudo ufw reload
+        echo "✅ UFW updated."
+    else
+        echo "⚠️  No supported firewall manager (firewalld/ufw) detected. Ensure ports 2377/tcp, 7946/tcp+udp, 4789/udp AND protocol VRRP are open manually."
+    fi
+
+    echo "--- Prerequisites Check Complete ---"
+
Index: /branches/amp_4_0/platform/tools/container/legacy/README_old.md
===================================================================
--- /branches/amp_4_0/platform/tools/container/legacy/README_old.md	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/legacy/README_old.md	(working copy)
@@ -0,0 +1,184 @@
+# Container Tools
+
+This directory contains the Docker container configurations and tools for the AMP platform.
+
+## Directory Structure
+
+The environment is modularized to allow for easier management and verification of individual services.
+
+- **`compose/`**: Contains individual Docker Compose files for each service definition (e.g., `opensearch.yml`, `timescaledb.yml`) and a `base.yml` for shared networks and volumes.
+- **`services/`**: Contains configuration files and build contexts for specific services (e.g., `services/opensearch/`, `services/nginx/`).
+- **`manage_service.sh`**: The primary script to manage the lifecycle and health of these services.
+- **`install_prerequisites.sh`**: Script to install host dependencies (`rsync`, `docker`, `python3`, `java`) and configure system limits.
+
+## File Locations & Persistence
+
+The AMP platform is designed to be stateless in its container definitions, with configuration and persistent data decoupled from the images.
+
+### 1. Configuration Files
+
+All service configurations (e.g., `nginx.conf`, `telegraf.conf`, certificates) are stored in the host repository and mounted into the containers as read-only volumes.
+
+- **Path**: `platform/tools/container/services/<service_name>/`
+- **Updates**: Changes made to files in these directories are typically picked up by the services after a restart via `./manage_service.sh <service> up`.
+
+### 2. Persistent Data (Volumes)
+
+Stateful data (databases, logs, indices) is stored in **Named Docker Volumes**. This ensures data survives container updates and accidental removals.
+
+| Volume Name | Description |
+| :--- | :--- |
+| **`opensearch-data`** | OpenSearch indices and cluster state. |
+| **`timescaledb-data`** | TimescaleDB (PostgreSQL) relational data and logs. |
+| **`grafana-data`** | Grafana sqlite database (dashboards, users). |
+| **`certs-vol`** | Shared SSL certificates generated during `setup`. |
+| **`security-config-vol`** | OpenSearch Security plugin configurations. |
+| **`*-logs`** | Log volumes for each service (e.g., `opensearch-logs`, `nginx-logs`). |
+
+### 3. Accessing Persistent Data & Logs
+
+Since data and logs are stored in Docker volumes (not directly on the host filesystem), you cannot browse them with a standard file explorer. Instead, use Docker tools or helper containers.
+
+#### Accessing Logs
+
+Logs are stored in named volumes (e.g., `opensearch-logs`). To view or export them:
+
+**Quick View (Last 100 lines):**
+
+```bash
+docker run --rm -v opensearch-logs:/logs alpine tail -n 100 /logs/opensearch.log
+```
+
+**Interactive Shell:**
+
+```bash
+docker run --rm -it -v nginx-logs:/logs alpine sh
+# cd /logs && ls -l
+```
+
+#### Accessing Data
+
+Database files are similarly protected. To inspect raw data files (debug only):
+
+```bash
+docker run --rm -v timescaledb-data:/data alpine ls -lR /data
+```
+
+#### Accessing Configuration
+
+Configuration files are **mounted from the host** and are directly editable:
+
+- Go to: `platform/tools/container/services/<service_name>/`
+- Edit the files (e.g., `nginx/conf.d/app.conf`).
+- Apply changes: `./manage_service.sh <service_name> up`
+
+> [!TIP]
+> To find the physical location of a volume on your host system (Linux only), use:
+>
+> ```bash
+> docker volume inspect <volume_name>
+> ```
+>
+> On macOS/Windows (Docker Desktop), the physical files are inside the Docker VM and not directly accessible on the host OS without mounting them as shown above.
+
+#### Physical Locations (Linux / Docker VM)
+
+If running on a standard Linux Docker host (or inside the Docker VM), the logs are located at `/var/lib/docker/volumes/<project>_<volume_name>/_data`.
+Assuming the project name is `compose` (default), the locations are:
+
+| Service | Host Path (Linux) |
+| :--- | :--- |
+| **OpenSearch** | `/var/lib/docker/volumes/compose_opensearch-logs/_data/` |
+| **OpenSearch Dashboards** | `/var/lib/docker/volumes/compose_opensearch-dashboards-logs/_data/` |
+| **Nginx** | `/var/lib/docker/volumes/compose_nginx-logs/_data/` |
+| **Grafana** | `/var/lib/docker/volumes/compose_grafana-logs/_data/` |
+| **Telegraf** | `/var/lib/docker/volumes/compose_telegraf-logs/_data/` |
+| **Logstash** | `/var/lib/docker/volumes/compose_logstash-logs/_data/` |
+| **PgBouncer** | `/var/lib/docker/volumes/compose_pgbouncer-logs/_data/` |
+| **TimescaleDB** | `/var/lib/docker/volumes/compose_timescaledb-data/_data/pg_log/` |
+
+## Setup & Prerequisites
+
+Before running services, ensure your system has the necessary tools. OpenSearch requires `vm.max_map_count` >= 262144.
+
+Run the helper script to attempt automatic host setup:
+
+```bash
+./install_prerequisites.sh
+```
+
+## Usage
+
+Use `manage_service.sh` to manage and verify services.
+
+### 1. Basic Usage
+
+```bash
+./manage_service.sh [service_name] [action]
+```
+
+- **`service_name`**: Name of the service (filename in `compose/` without .yml).
+- **`action`** (optional):
+  - `up` (default): Start service and run health checks.
+  - `down`: Stop and remove the service.
+  - `purge`: Stop service and remove its volumes (fresh start).
+  - `validate`: Run health checks on a running service.
+
+### 2. Recommended Startup Order
+
+1. **Setup & Core Storage**
+
+   ```bash
+   ./manage_service.sh setup
+   ./manage_service.sh opensearch
+   ./manage_service.sh timescaledb
+   ```
+
+2. **Backend & Middleware**
+
+   ```bash
+   ./manage_service.sh configurator
+   ./manage_service.sh pgbouncer
+   ```
+
+3. **Frontend & Observability**
+
+   ```bash
+   ./manage_service.sh opensearch-dashboards
+   ./manage_service.sh grafana
+   ./manage_service.sh telegraf
+   ./manage_service.sh logstash
+   ./manage_service.sh nginx
+   ```
+
+## Development Notes
+
+- **Modifying Configuration**: Service configurations are located in `services/<service_name>`.
+- **Modifying Compose Definitions**: Docker compose definitions are in `compose/<service_name>.yml`.
+- Volumes in `compose/*.yml` reference `../services/<service_name>/...`.
+
+## Docker Swarm Support
+
+To run the platform in Docker Swarm mode (Single or Multi-Node):
+
+### 1. Initialization
+
+Initialize Swarm mode and label the current node for storage pinning:
+
+```bash
+./swarm/manage_swarm.sh init
+```
+
+### 2. Build & Deploy
+
+This will start a local registry, build all images, push them, and deploy the stack.
+
+```bash
+./swarm/manage_swarm.sh build
+./swarm/manage_swarm.sh deploy
+```
+
+### 3. Management
+
+- **Status**: `./swarm/manage_swarm.sh status`
+- **Remove**: `./swarm/manage_swarm.sh rm`
Index: /branches/amp_4_0/platform/tools/container/legacy/compose/backend.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/legacy/compose/backend.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/legacy/compose/backend.yml	(working copy)
@@ -0,0 +1,23 @@
+services:
+  backend:
+    build:
+      context: ..
+      dockerfile: services/backend/Dockerfile
+    container_name: backend
+    environment:
+      - DJANGO_SETTINGS_MODULE=djproject.settings
+      - POSTGRES_HOST=pgbouncer
+      - POSTGRES_PORT=6432
+      - POSTGRES_DB=cm
+      - POSTGRES_USER=amp_admin
+      - POSTGRES_PASSWORD=Array@123$
+      - OPENSEARCH_HOSTS=https://opensearch-node1:9200
+      - OPENSEARCH_INITIAL_ADMIN_PASSWORD=${OPENSEARCH_INITIAL_ADMIN_PASSWORD}
+    volumes:
+      # Mount application source code
+      - ../../../../src/webui/webui/htdocs/new/src:/app:rw
+      - ../../../../extensions:/extensions:rw
+    command: ["python", "manage.py", "runserver", "0.0.0.0:8888"]
+    networks:
+      - amp-network
+      - timescaledb
Index: /branches/amp_4_0/platform/tools/container/legacy/compose/base.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/legacy/compose/base.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/legacy/compose/base.yml	(working copy)
@@ -0,0 +1,19 @@
+volumes:
+  opensearch-data:
+  timescaledb-data:
+  grafana-data:
+  certs-vol:
+    external: true
+  security-config-vol:
+  opensearch-logs:
+  nginx-logs:
+  timescaledb-logs:
+  grafana-logs:
+  telegraf-logs:
+  logstash-logs:
+  opensearch-dashboards-logs:
+  pgbouncer-logs:
+
+networks:
+  amp-network:
+    driver: bridge
Index: /branches/amp_4_0/platform/tools/container/legacy/compose/configurator.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/legacy/compose/configurator.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/legacy/compose/configurator.yml	(working copy)
@@ -0,0 +1,16 @@
+services:
+  configurator:
+    image: rockylinux:9
+    container_name: amp-configurator
+    environment:
+      - OPENSEARCH_INITIAL_ADMIN_PASSWORD=${OPENSEARCH_INITIAL_ADMIN_PASSWORD}
+    volumes:
+      - certs-vol:/usr/share/opensearch/config/certs:ro
+      - ../services/setup:/setup:ro
+      - ../services/setup/install_curl.sh:/usr/local/bin/install_curl.sh
+      - ../services/setup/configure_opensearch.sh:/usr/local/bin/configure_opensearch.sh
+      - ../services/opensearch/amplog_template.json:/usr/share/opensearch/config/amplog_template.json
+      - ../services/opensearch-dashboards/export.ndjson:/usr/share/opensearch/config/export.ndjson
+    command: ["/bin/sh", "-c", "bash /usr/local/bin/install_curl.sh && bash /usr/local/bin/configure_opensearch.sh"]
+    networks:
+      - amp-network
Index: /branches/amp_4_0/platform/tools/container/legacy/compose/grafana.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/legacy/compose/grafana.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/legacy/compose/grafana.yml	(working copy)
@@ -0,0 +1,20 @@
+services:
+  grafana:
+    image: grafana/grafana:latest
+    container_name: grafana
+    environment:
+      GF_SECURITY_ADMIN_USER: ${GF_SECURITY_ADMIN_USER}
+      GF_SECURITY_ADMIN_PASSWORD: ${GF_SECURITY_ADMIN_PASSWORD}
+      GF_SERVER_ROOT_URL: ${GF_SERVER_ROOT_URL}
+      GF_SERVER_SERVE_FROM_SUB_PATH: ${GF_SERVER_SERVE_FROM_SUB_PATH}
+      GF_PLUGINS_PREINSTALL: grafana-opensearch-datasource
+      GF_LOG_MODE: console,file
+      GF_PATHS_LOGS: /var/log/grafana
+    volumes:
+      - grafana-data:/var/lib/grafana
+      - grafana-logs:/var/log/grafana
+      - ../services/grafana/provisioning:/etc/grafana/provisioning
+    ports:
+      - "3000:3000"
+    networks:
+      - amp-network
Index: /branches/amp_4_0/platform/tools/container/legacy/compose/logstash.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/legacy/compose/logstash.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/legacy/compose/logstash.yml	(working copy)
@@ -0,0 +1,40 @@
+services:
+  logstash:
+    image: opensearchproject/logstash-oss-with-opensearch-output-plugin:latest
+    container_name: logstash
+    command: >
+      bash -c "
+        if [ ! -f /usr/share/logstash/config/certs/node.pem ]; then
+          echo 'Waiting for certs...'; sleep 5;
+        fi
+        
+        # Auto-download JDBC driver if missing
+        DRIVER_PATH=/usr/share/logstash/drivers/postgresql.jar
+        if [ ! -f $$DRIVER_PATH ]; then
+           echo 'Downloading PostgreSQL JDBC Driver...'
+           curl -L -o $$DRIVER_PATH https://jdbc.postgresql.org/download/postgresql-42.7.3.jar
+        fi
+        
+        logstash-plugin install logstash-integration-jdbc
+        logstash-plugin install logstash-filter-geoip
+        bin/logstash
+      "
+    environment:
+      LS_JAVA_OPTS: ${LOGSTASH_JAVA_OPTS}
+      OPENSEARCH_URL: https://opensearch-node1:9200
+      OPENSEARCH_INITIAL_ADMIN_PASSWORD: ${OPENSEARCH_INITIAL_ADMIN_PASSWORD}
+      POSTGRES_USER: ${POSTGRES_USER}
+      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
+      POSTGRES_DB: ${POSTGRES_DB}
+    volumes:
+      - ../services/logstash/config/logstash.yml:/usr/share/logstash/config/logstash.yml:ro
+      - ../services/logstash/pipeline:/usr/share/logstash/pipeline:ro
+      - ../services/logstash/drivers:/usr/share/logstash/drivers:ro
+      - certs-vol:/usr/share/logstash/config/certs:ro
+      - logstash-logs:/usr/share/logstash/logs
+    ports:
+      - "5044:5044"
+      - "5514:5514/udp"
+      - "5514:5514/tcp"
+    networks:
+      - amp-network
Index: /branches/amp_4_0/platform/tools/container/legacy/compose/nginx.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/legacy/compose/nginx.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/legacy/compose/nginx.yml	(working copy)
@@ -0,0 +1,15 @@
+services:
+  nginx:
+    image: nginx:alpine
+    container_name: nginx
+    volumes:
+      - ../services/nginx/conf.d:/etc/nginx/conf.d
+      - certs-vol:/etc/nginx/certs:ro
+      - nginx-logs:/var/log/nginx
+    ports:
+      - "80:80"
+      - "443:443"
+    extra_hosts:
+      - "host.docker.internal:host-gateway"
+    networks:
+      - amp-network
Index: /branches/amp_4_0/platform/tools/container/legacy/compose/opensearch-dashboards.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/legacy/compose/opensearch-dashboards.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/legacy/compose/opensearch-dashboards.yml	(working copy)
@@ -0,0 +1,42 @@
+services:
+  opensearch-dashboards:
+    image: opensearchproject/opensearch-dashboards:2.11.0
+    container_name: opensearch-dashboards
+    user: root
+
+    environment:
+      OPENSEARCH_HOSTS: '["https://opensearch-node1:9200"]'
+      OPENSEARCH_SSL_VERIFICATIONMODE: certificate
+      OPENSEARCH_USERNAME: admin
+      OPENSEARCH_PASSWORD: ${OPENSEARCH_INITIAL_ADMIN_PASSWORD}
+      OPENSEARCH_SSL_CERTIFICATEAUTHORITIES: '["/usr/share/opensearch-dashboards/config/certs/root-ca.pem"]'
+
+      SERVER_SSL_ENABLED: "true"
+      SERVER_SSL_KEY: /usr/share/opensearch-dashboards/config/certs/node-key.pem
+      SERVER_SSL_CERTIFICATE: /usr/share/opensearch-dashboards/config/certs/node.pem
+      LOGGING_DEST: /usr/share/opensearch-dashboards/logs/osd.log
+
+      SERVER_BASEPATH: /visualization
+      SERVER_REWRITEBASEPATH: "true"
+
+    ports:
+      - "5601:5601"
+
+    volumes:
+      - certs-vol:/usr/share/opensearch-dashboards/config/certs:ro
+      - /bin/yq:/usr/local/bin/yq:ro
+      - ../services/opensearch-dashboards/assets:/usr/share/opensearch-dashboards/src/core/server/core_app/assets/custom:ro
+      - opensearch-dashboards-logs:/usr/share/opensearch-dashboards/logs
+
+    command: >
+      bash -c "
+        chown -R 1000:1000 /usr/share/opensearch-dashboards/logs &&
+        runuser -u opensearch-dashboards -- /usr/share/opensearch-dashboards/opensearch-dashboards-docker-entrypoint.sh
+      "
+
+    networks:
+      - amp-network
+
+volumes:
+  certs-vol:
+    external: true
Index: /branches/amp_4_0/platform/tools/container/legacy/compose/opensearch.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/legacy/compose/opensearch.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/legacy/compose/opensearch.yml	(working copy)
@@ -0,0 +1,59 @@
+services:
+  opensearch:
+    image: opensearchproject/opensearch:2.11.0
+    container_name: opensearch-node1
+    environment:
+      - cluster.name=opensearch-cluster
+      - node.name=opensearch-node1
+      - discovery.seed_hosts=opensearch-node1
+      - cluster.initial_master_nodes=opensearch-node1
+      - bootstrap.memory_lock=true
+      - "OPENSEARCH_JAVA_OPTS=${OPENSEARCH_JAVA_OPTS}"
+      - OPENSEARCH_INITIAL_ADMIN_PASSWORD=${OPENSEARCH_INITIAL_ADMIN_PASSWORD}
+      # Security Plugin Configuration
+      - plugins.security.ssl.http.enabled=true
+      - plugins.security.ssl.http.pemkey_filepath=/usr/share/opensearch/config/certs/node-key.pem
+      - plugins.security.ssl.http.pemcert_filepath=/usr/share/opensearch/config/certs/node.pem
+      - plugins.security.ssl.http.pemtrustedcas_filepath=/usr/share/opensearch/config/certs/root-ca.pem
+      - plugins.security.ssl.transport.enabled=true
+      - plugins.security.ssl.transport.pemkey_filepath=/usr/share/opensearch/config/certs/node-key.pem
+      - plugins.security.ssl.transport.pemcert_filepath=/usr/share/opensearch/config/certs/node.pem
+      - plugins.security.ssl.transport.pemtrustedcas_filepath=/usr/share/opensearch/config/certs/root-ca.pem
+      - plugins.security.restapi.roles_enabled=["all_access", "security_rest_api_access"]
+      - plugins.security.allow_default_init_securityindex=true
+      - plugins.security.nodes_dn=CN=node-1,O=OpenSearchNode,L=Bengaluru,ST=Karnataka,C=IN
+      - plugins.security.authcz.admin_dn=CN=admin,O=OpenSearchAdmin,L=Bengaluru,ST=Karnataka,C=IN
+    ulimits:
+      memlock:
+        soft: -1
+        hard: -1
+      nofile:
+        soft: 65536
+        hard: 65536
+    volumes:
+      - opensearch-data:/usr/share/opensearch/data
+      - certs-vol:/usr/share/opensearch/config/certs:ro
+      - security-config-vol:/usr/share/opensearch/config/opensearch-security-mount:ro
+      - opensearch-logs:/usr/share/opensearch/logs
+    command: >
+      bash -c "
+        set -e
+        # 1. Run demo installer to generate default security files (roles, users, etc.)
+        # This writes to /usr/share/opensearch/config/opensearch-security
+        chmod +x /usr/share/opensearch/plugins/opensearch-security/tools/install_demo_configuration.sh
+        /usr/share/opensearch/plugins/opensearch-security/tools/install_demo_configuration.sh -y -i
+        
+        # 2. Overwrite config.yml with our custom version (JWT, etc.)
+        echo '--- Overwriting config.yml ---'
+        cp /usr/share/opensearch/config/opensearch-security-mount/config.yml /usr/share/opensearch/config/opensearch-security/config.yml
+        
+        echo '--- Overwriting internal_users.yml ---'
+        cp /usr/share/opensearch/config/opensearch-security-mount/internal_users.yml /usr/share/opensearch/config/opensearch-security/internal_users.yml
+        
+        # 3. Start OpenSearch
+        # Disable demo config in entrypoint so it doesn't overwrite our work
+        export DISABLE_INSTALL_DEMO_CONFIG=true
+        ./opensearch-docker-entrypoint.sh
+      "
+    networks:
+      - amp-network
Index: /branches/amp_4_0/platform/tools/container/legacy/compose/pgbouncer.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/legacy/compose/pgbouncer.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/legacy/compose/pgbouncer.yml	(working copy)
@@ -0,0 +1,15 @@
+services:
+  pgbouncer:
+    image: edoburu/pgbouncer:latest
+    container_name: pgbouncer
+    environment:
+      - DB_USER=amp_admin
+      - DB_PASSWORD=Array@123$
+      - DB_HOST=timescaledb
+      - DB_NAME=cm
+    volumes:
+      - ../services/pgbouncer/pgbouncer.ini:/etc/pgbouncer/pgbouncer.ini:ro
+      - ../services/pgbouncer/userlist.txt:/etc/pgbouncer/userlist.txt:ro
+      - pgbouncer-logs:/var/log/pgbouncer
+    networks:
+      - amp-network
Index: /branches/amp_4_0/platform/tools/container/legacy/compose/setup.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/legacy/compose/setup.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/legacy/compose/setup.yml	(working copy)
@@ -0,0 +1,13 @@
+services:
+  setup:
+    image: rockylinux:9
+    container_name: amp-setup
+    working_dir: /setup
+    environment:
+      - OPENSEARCH_JWT_SECRET=${OPENSEARCH_JWT_SECRET}
+      - OPENSEARCH_INITIAL_ADMIN_PASSWORD=${OPENSEARCH_INITIAL_ADMIN_PASSWORD}
+    volumes:
+      - ../services/setup:/setup:rw
+      - certs-vol:/certs:rw
+      - security-config-vol:/security-config:rw
+    command: ["/bin/sh", "-c", "dnf install -y openssl httpd-tools && chmod +x /setup/setup.sh && /setup/setup.sh /certs /security-config"]
Index: /branches/amp_4_0/platform/tools/container/legacy/compose/telegraf.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/legacy/compose/telegraf.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/legacy/compose/telegraf.yml	(working copy)
@@ -0,0 +1,22 @@
+services:
+  telegraf:
+    image: telegraf:latest
+    container_name: telegraf
+    user: "0:${DOCKER_GID:-0}"
+    group_add:
+      - "${DOCKER_GID:-0}"
+    environment:
+      PG_PASSWORD: ${POSTGRES_PASSWORD}
+    volumes:
+      - ../services/telegraf/telegraf.conf:/etc/telegraf/telegraf.conf:ro
+      - ../services/telegraf/telegraf.d:/etc/telegraf/telegraf.d:ro
+      - telegraf-logs:/var/log/telegraf
+      - /var/run/docker.sock:/var/run/docker.sock:ro
+      - /dev:/dev:ro
+      - /sys:/rootfs/sys:ro
+      - /proc:/rootfs/proc:ro
+      - /etc:/rootfs/etc:ro
+      - /run/udev:/run/udev:ro
+    command: ["telegraf", "--config", "/etc/telegraf/telegraf.conf", "--config-directory", "/etc/telegraf/telegraf.d"]
+    networks:
+      - amp-network
Index: /branches/amp_4_0/platform/tools/container/legacy/compose/timescaledb.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/legacy/compose/timescaledb.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/legacy/compose/timescaledb.yml	(working copy)
@@ -0,0 +1,15 @@
+services:
+  timescaledb:
+    image: timescale/timescaledb:latest-pg16
+    container_name: timescaledb
+    environment:
+      POSTGRES_USER: ${POSTGRES_USER}
+      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
+      POSTGRES_DB: ${POSTGRES_DB}
+    volumes:
+      - timescaledb-data:/var/lib/postgresql/data
+      - ../services/postgres/initdb.d:/docker-entrypoint-initdb.d
+    ports:
+      - "5432:5432"
+    networks:
+      - amp-network
Index: /branches/amp_4_0/platform/tools/container/legacy/manage_service.sh
===================================================================
--- /branches/amp_4_0/platform/tools/container/legacy/manage_service.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/legacy/manage_service.sh	(working copy)
@@ -0,0 +1,303 @@
+#!/bin/bash
+
+# validate_service.sh
+# Usage: ./validate_service.sh [service_name]
+
+SERVICE=$1
+
+# REPO_ROOT Resolution
+# Assuming script is in platform/tools/container/
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "$SCRIPT_DIR/../../../" && pwd)"
+
+SPLIT_DIR_NAME="compose"
+COMPOSE_DIR="$SCRIPT_DIR/$SPLIT_DIR_NAME"
+
+# Detect docker-compose command
+if command -v docker-compose &> /dev/null; then
+  DOCKER_COMPOSE_CMD="docker-compose"
+elif docker compose version &> /dev/null; then
+  DOCKER_COMPOSE_CMD="docker compose"
+else
+  echo "Error: neither 'docker-compose' nor 'docker compose' found."
+  exit 1
+fi
+echo "Using Docker Compose command: $DOCKER_COMPOSE_CMD"
+export COMPOSE_IGNORE_ORPHANS=True
+
+# Detect Docker GID for Telegraf and other services needing socket access
+if [ -S /var/run/docker.sock ]; then
+  # Compatibility: stat flags differ between GNU and BSD (macOS)
+  if [[ "$OSTYPE" == "darwin"* ]]; then
+    DOCKER_GID=$(stat -f '%g' /var/run/docker.sock)
+  else
+    DOCKER_GID=$(stat -c '%g' /var/run/docker.sock)
+  fi
+  export DOCKER_GID
+  echo "Detected Docker GID: $DOCKER_GID"
+fi
+
+if [ -z "$SERVICE" ]; then
+  echo "Usage: ./manage_service.sh [service_name] [action]"
+  echo "AMP Service Manager"
+  echo ""
+  echo "Available services:"
+  echo "  base, setup, opensearch, configurator, opensearch-dashboards, timescaledb,"
+  echo "  pgbouncer, grafana, telegraf, logstash, nginx"
+  echo ""
+  echo "Actions:"
+  echo "  up       - Start/Build service (default)"
+  echo "  down     - Stop/Remove service"
+  echo "  purge    - Stop/Remove service AND volumes (Fresh Start)"
+  echo "  validate - Run health checks only"
+  exit 1
+fi
+
+ACTION=${2:-up}
+
+# Check Docker Permissions
+if ! docker ps >/dev/null 2>&1; then
+   echo "❌ Error: Docker permission denied or Docker is not running."
+   exit 1
+fi
+
+echo "--- Processing Service: $SERVICE ($ACTION) ---"
+
+# Ensure external volume 'certs-vol' exists
+if ! docker volume ls -q | grep -q "^certs-vol$"; then
+  echo "Creating external volume: certs-vol"
+  docker volume create certs-vol
+fi
+
+# Move to the Compose Directory
+cd "$COMPOSE_DIR" || exit 1
+
+# Load .env if it exists in the script directory
+if [ -f "$SCRIPT_DIR/.env" ]; then
+  # Note: Docker Compose loads .env automatically if it's in the same dir as the yaml files.
+  # If it's in the parent, we might need to symlink it or specify --env-file
+  if [ ! -f ".env" ]; then
+     ln -s "$SCRIPT_DIR/.env" .env
+     echo "DEBUG: Symlinked .env from parent directory."
+  fi
+fi
+
+if [ "$ACTION" == "down" ] || [ "$ACTION" == "purge" ]; then
+  if [ "$ACTION" == "purge" ]; then
+    echo "Stopping $SERVICE and removing volumes..."
+    $DOCKER_COMPOSE_CMD -f base.yml -f "$SERVICE.yml" down -v
+  else
+    $DOCKER_COMPOSE_CMD -f base.yml -f "$SERVICE.yml" down
+  fi
+  echo "Service $SERVICE stopped."
+  exit 0
+fi
+
+if [ "$SERVICE" == "base" ]; then
+  echo "Validating Base configuration..."
+  # 'base' only has volumes/networks, 'up' fails in v2 if no services.
+  # We trust that subsequent service runs (which include base.yml) will create them.
+  # We just check config validity here.
+  $DOCKER_COMPOSE_CMD -f base.yml config >/dev/null
+  if [ $? -eq 0 ]; then
+     echo "✅ Base configuration is valid. Networks/Volumes will be created when starting services."
+  else
+     echo "❌ Base configuration is invalid."
+     exit 1
+  fi
+  exit 0
+fi
+
+SERVICE_FILE="$SERVICE.yml"
+
+if [ ! -f "$SERVICE_FILE" ]; then
+  echo "Error: Service file $SERVICE_FILE not found in workspace."
+  exit 1
+fi
+
+if [ "$ACTION" == "up" ]; then
+   # Bring up the service (along with base)
+   # We use base.yml from the temp dir as well
+   $DOCKER_COMPOSE_CMD -f base.yml -f "$SERVICE_FILE" up -d --build
+   
+   echo "Waiting for service to initialize..."
+   sleep 5
+fi
+
+# Validation Checks
+case $SERVICE in
+  setup)
+    echo "Checking Setup exit code..."
+    EXIT_CODE=$(docker inspect amp-setup --format='{{.State.ExitCode}}')
+    if [ "$EXIT_CODE" == "0" ]; then
+      echo "✅ Setup finished successfully."
+      echo "--- Certificates in 'certs-vol' ---"
+      # List contents of /certs in the volume using a temporary container (using setup service definition)
+      $DOCKER_COMPOSE_CMD -f base.yml -f setup.yml run --rm --entrypoint ls setup -lR /certs
+      echo "-----------------------------------"
+      echo "Note: Certificates are stored in the Docker volume 'certs-vol'."
+    else
+      echo "❌ Setup failed with exit code $EXIT_CODE."
+    fi
+    ;;
+  opensearch)
+    echo "Checking OpenSearch health..."
+    if docker ps | grep -q opensearch-node1; then
+      echo "✅ OpenSearch container is running."
+      
+      # Retries for Health Check
+      MAX_RETRIES=30
+      COUNT=0
+      PASSWORD=${OPENSEARCH_INITIAL_ADMIN_PASSWORD:-admin}
+      
+      echo "Waiting for OpenSearch API to be ready (up to 60s)..."
+      while [ $COUNT -lt $MAX_RETRIES ]; do
+        docker exec opensearch-node1 curl -k -s -u admin:$PASSWORD https://localhost:9200/_cluster/health > /dev/null
+        if [ $? -eq 0 ]; then
+            echo "✅ OpenSearch API is healthy."
+            break
+        fi
+        echo "   ... waiting for API ($((COUNT*2))s)"
+        sleep 2
+        COUNT=$((COUNT+1))
+      done
+      
+      if [ $COUNT -eq $MAX_RETRIES ]; then
+         echo "❌ OpenSearch API failed to respond after 60 seconds."
+         echo "   Debugging info:"
+         docker logs --tail 20 opensearch-node1
+      fi
+    else
+      echo "❌ OpenSearch container is not running."
+    fi
+    ;;
+  timescaledb)
+    echo "Checking TimescaleDB readiness..."
+    sleep 5
+    if docker exec timescaledb pg_isready; then
+      echo "✅ TimescaleDB is ready."
+    else
+      echo "❌ TimescaleDB is not ready."
+      echo "   Debugging info:"
+      docker logs --tail 20 timescaledb
+    fi
+    ;;
+  pgbouncer)
+    echo "Checking PgBouncer..."
+    if docker ps | grep -q pgbouncer; then
+      echo "✅ PgBouncer container is running."
+      # Need a way to check port inside?
+      # Using /bin/sh to check port open with timeout (nc might not be installed)
+      # docker exec pgbouncer nc -z localhost 6432
+    else
+      echo "❌ PgBouncer container is not running."
+    fi
+    ;;
+  grafana)
+    echo "Checking Grafana..."
+    
+    MAX_RETRIES=30
+    COUNT=0
+    
+    echo "Waiting for Grafana to be ready (up to 60s)..."
+    while [ $COUNT -lt $MAX_RETRIES ]; do
+        curl -s -I http://localhost:3000/login > /dev/null
+        if [ $? -eq 0 ]; then
+             echo "✅ Grafana login page is accessible."
+             break
+        fi
+        echo "   ... waiting for Grafana ($((COUNT*2))s)"
+        sleep 2
+        COUNT=$((COUNT+1))
+    done
+
+    if [ $COUNT -eq $MAX_RETRIES ]; then
+      echo "❌ Grafana login page check failed."
+      echo "   Debugging info:"
+      docker logs --tail 20 grafana
+    fi
+    ;;
+  opensearch-dashboards)
+    echo "Checking OpenSearch Dashboards (waiting for startup)..."
+    
+    MAX_RETRIES=30
+    COUNT=0
+    
+    echo "Waiting for OpenSearch Dashboards to be ready (up to 60s)..."
+    while [ $COUNT -lt $MAX_RETRIES ]; do
+        # 5601 might not be exposed to host in docker-compose.yml (networks only)
+        # So we use docker exec to check inside
+        docker exec opensearch-dashboards curl -s -I -k https://localhost:5601 > /dev/null
+        if [ $? -eq 0 ]; then
+            echo "✅ OpenSearch Dashboards is running."
+            break
+        fi
+        echo "   ... waiting for Dashboards ($((COUNT*2))s)"
+        sleep 2
+        COUNT=$((COUNT+1))
+    done
+    
+    if [ $COUNT -eq $MAX_RETRIES ]; then
+        echo "❌ OpenSearch Dashboards check failed."
+        echo "   Debugging info:"
+        docker logs --tail 20 opensearch-dashboards
+    fi
+    ;;
+
+  nginx)
+    echo "Checking Nginx..."
+    
+    MAX_RETRIES=30
+    COUNT=0
+    
+    echo "Waiting for Nginx to be ready (up to 60s)..."
+    while [ $COUNT -lt $MAX_RETRIES ]; do
+        curl -s -k -I https://localhost > /dev/null
+        if [ $? -eq 0 ]; then
+            echo "✅ Nginx is reachable."
+            break
+        fi
+        echo "   ... waiting for Nginx ($((COUNT*2))s)"
+        sleep 2
+        COUNT=$((COUNT+1))
+    done
+
+    if [ $COUNT -eq $MAX_RETRIES ]; then
+      echo "❌ Nginx check failed."
+      echo "   Debugging info:"
+      docker logs --tail 20 nginx
+    fi
+    ;;
+  configurator)
+    echo "Checking Configurator..."
+    # Configurator is a job that requires OpenSearch. In isolated validation, it will likely fail.
+    # We check if it attempted to run.
+    sleep 5
+    STATUS=$(docker inspect amp-configurator --format='{{.State.Status}}')
+    EXIT_CODE=$(docker inspect amp-configurator --format='{{.State.ExitCode}}')
+    
+    echo "Configurator Status: $STATUS (Exit Code: $EXIT_CODE)"
+    
+    if [ "$STATUS" == "exited" ]; then
+       # It's okay if it fails (non-zero) because OpenSearch isn't here.
+       echo "✅ Configurator container was created and attempted execution."
+       if [ "$EXIT_CODE" != "0" ]; then
+         echo "   (Note: Exit code $EXIT_CODE is expected in isolation as OpenSearch is unreachable)"
+       fi
+    elif [ "$STATUS" == "running" ]; then
+       echo "✅ Configurator is running."
+    else
+       echo "❌ Configurator status is $STATUS."
+    fi
+    ;;
+  *)
+    echo "No specific validation script for $SERVICE, verifying container status..."
+    if docker ps -a | grep -q $SERVICE; then
+       echo "✅ Service $SERVICE appears to be created/running."
+    else
+       echo "⚠️ Service $SERVICE container not found."
+    fi
+    ;;
+esac
+
+echo "--- Done ---"

Property changes on: platform/tools/container/legacy/manage_service.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: /branches/amp_4_0/platform/tools/container/manage_amp.sh
===================================================================
--- /branches/amp_4_0/platform/tools/container/manage_amp.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/manage_amp.sh	(working copy)
@@ -0,0 +1,458 @@
+#!/bin/bash
+
+# manage_swarm.sh
+# Usage: ./manage_swarm.sh [action]
+
+ACTION=${1:-status}
+STACK_NAME="amp"
+REGISTRY="localhost:5000"
+
+# REPO_ROOT Resolution
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+# Resolve SERVICES_DIR relative to the script
+SERVICES_DIR="$(cd "$SCRIPT_DIR/services" && pwd)"
+STACK_FILE="$SCRIPT_DIR/stack.yml"
+
+echo "Using Services Directory: $SERVICES_DIR"
+
+# Load .env file
+ENV_FILE="$SCRIPT_DIR/.env"
+if [ -f "$ENV_FILE" ]; then
+    echo "Loading environment variables from $ENV_FILE"
+    set -a
+    source "$ENV_FILE"
+    set +a
+else
+    echo "⚠️  .env file not found at $ENV_FILE"
+fi
+
+
+# Function: Calculate Dynamic Heap Size (50% of Host RAM)
+calculate_heap_size() {
+    # If explicitly set in .env or shell, skip auto-calc
+    if [ -n "$OPENSEARCH_JAVA_OPTS" ]; then
+        echo "Using Memory Override from .env: $OPENSEARCH_JAVA_OPTS"
+        return
+    fi
+
+    echo "--- Calculating Memory Allocation ---"
+    TOTAL_MEM_MB=0
+
+    # Detect OS to get Total Memory
+    if [[ "$OSTYPE" == "darwin"* ]]; then
+        # macOS
+        TOTAL_MEM_BYTES=$(sysctl -n hw.memsize)
+        TOTAL_MEM_MB=$((TOTAL_MEM_BYTES / 1024 / 1024))
+    else
+        # Linux
+        if [ -f /proc/meminfo ]; then
+            TOTAL_MEM_KB=$(grep MemTotal /proc/meminfo | awk '{print $2}')
+            TOTAL_MEM_MB=$((TOTAL_MEM_KB / 1024))
+        else
+            echo "⚠️  Cannot detect memory (neither macOS nor Linux /proc/meminfo). Defaulting to 1GB."
+            TOTAL_MEM_MB=2048 # Fallback assumes at least 2GB machine -> 1GB heap
+        fi
+    fi
+
+    echo "Host Total Memory: ${TOTAL_MEM_MB}MB"
+
+    # 50% Rule
+    HEAP_SIZE_MB=$((TOTAL_MEM_MB / 2))
+
+    # Min/Max Clamping
+    # Min: 1GB (1024MB)
+    # Max: 32GB (32768MB) - JVM compressed oops limit
+    MIN_HEAP=1024
+    MAX_HEAP=32768
+
+    if [ "$HEAP_SIZE_MB" -lt "$MIN_HEAP" ]; then
+        echo "⚠️  Calculated Heap (${HEAP_SIZE_MB}MB) is below minimum. Setting to 1GB."
+        HEAP_SIZE_MB=$MIN_HEAP
+    elif [ "$HEAP_SIZE_MB" -gt "$MAX_HEAP" ]; then
+        echo "⚠️  Calculated Heap (${HEAP_SIZE_MB}MB) exceeds 32GB. Clamping to 32GB."
+        HEAP_SIZE_MB=$MAX_HEAP
+    fi
+
+    export OPENSEARCH_JAVA_OPTS="-Xms${HEAP_SIZE_MB}m -Xmx${HEAP_SIZE_MB}m"
+    echo "✅ Auto-Allocated OpenSearch Heap: $OPENSEARCH_JAVA_OPTS"
+
+    # --- Logstash Memory (25% of Host RAM) ---
+    if [ -n "$LOGSTASH_JAVA_OPTS" ]; then
+        echo "Using Logstash Memory Override from .env: $LOGSTASH_JAVA_OPTS"
+    else
+        LS_HEAP_SIZE_MB=$((TOTAL_MEM_MB / 4))
+        # Clamp Logstash: Min 1GB, Max 8GB
+        MIN_LS=1024
+        MAX_LS=8192
+
+        if [ "$LS_HEAP_SIZE_MB" -lt "$MIN_LS" ]; then
+             LS_HEAP_SIZE_MB=$MIN_LS
+        elif [ "$LS_HEAP_SIZE_MB" -gt "$MAX_LS" ]; then
+             LS_HEAP_SIZE_MB=$MAX_LS
+        fi
+        export LOGSTASH_JAVA_OPTS="-Xms${LS_HEAP_SIZE_MB}m -Xmx${LS_HEAP_SIZE_MB}m"
+        echo "✅ Auto-Allocated Logstash Heap:  $LOGSTASH_JAVA_OPTS"
+    fi
+}
+
+
+# Function: Generate Dynamic Configuration Files
+generate_configs() {
+    echo "--- Generating Dynamic Configurations ---"
+    
+    # Generate PgBouncer Config from Template
+    PGB_DIR="$SERVICES_DIR/pgbouncer"
+    TEMPLATE="$PGB_DIR/pgbouncer.ini.template"
+    OUTPUT="$PGB_DIR/pgbouncer.ini"
+    USERLIST="$PGB_DIR/userlist.txt"
+
+    if [ -f "$TEMPLATE" ]; then
+        echo "Generating pgbouncer.ini from template..."
+        cp "$TEMPLATE" "$OUTPUT"
+        
+        # Helper for OS-aware sed
+        replace_var() {
+            local search=$1
+            local replace=$2
+            local file=$3
+            if [[ "$OSTYPE" == "darwin"* ]]; then
+                sed -i "" "s|${search}|${replace}|g" "$file"
+            else
+                sed -i "s|${search}|${replace}|g" "$file"
+            fi
+        }
+        
+        replace_var "__AMP_ADMIN_PASSWORD__" "$POSTGRES_PASSWORD" "$OUTPUT"
+        replace_var "__AMP_DB_NAME__" "$AMP_DB_NAME" "$OUTPUT"
+        replace_var "__AMP_DB_USER__" "$AMP_DB_USER" "$OUTPUT"
+        replace_var "__AMP_DB_PASSWORD__" "$AMP_DB_PASSWORD" "$OUTPUT"
+        
+        echo "✅ Generated $OUTPUT"
+    fi
+
+    # Generate Userlist (MD5 format: "md5" + md5(password + username))
+    echo "Generating userlist.txt..."
+    
+    # Helper to calc md5
+    calc_md5() {
+        local user=$1
+        local pass=$2
+        if [[ "$OSTYPE" == "darwin"* ]]; then
+             echo -n "${pass}${user}" | md5 -q
+        else
+             echo -n "${pass}${user}" | md5sum | awk '{print $1}'
+        fi
+    }
+
+    # Admin User (amp_admin) using POSTGRES_PASSWORD
+    MD5_ADMIN=$(calc_md5 "amp_admin" "$POSTGRES_PASSWORD")
+    # App User (amp_ts_user)
+    MD5_APP=$(calc_md5 "$AMP_DB_USER" "$AMP_DB_PASSWORD")
+
+    # Write file
+    echo "\"amp_admin\" \"md5$MD5_ADMIN\"" > "$USERLIST"
+    echo "\"$AMP_DB_USER\" \"md5$MD5_APP\"" >> "$USERLIST"
+    
+    echo "✅ Generated $USERLIST"
+}
+
+cleanup() {
+    echo ""
+    echo "⚠️ Caught Signal. Cleaning up ephemeral containers..."
+    docker rm -f amp-setup-ephemeral amp-configurator-ephemeral 2>/dev/null
+    exit 1
+}
+trap cleanup INT TERM
+
+check_swarm() {
+    if ! docker info | grep -q "Swarm: active"; then
+        echo "❌ Docker Swarm is NOT active."
+        echo "Run './manage_swarm.sh init' to initialize."
+        exit 1
+    fi
+}
+
+init_swarm() {
+    if docker info | grep -q "Swarm: active"; then
+        echo "✅ Swarm is already active."
+    else
+        echo "Initializing Docker Swarm..."
+        docker swarm init
+    fi
+
+    # Label this node as storage node for pinning
+    NODE_ID=$(docker info --format '{{.Swarm.NodeID}}')
+    echo "Labeling node $NODE_ID with type=storage..."
+    docker node update --label-add type=storage $NODE_ID
+}
+
+ensure_registry() {
+    if ! docker ps | grep -q "registry"; then
+        echo "Starting Local Registry ($REGISTRY)..."
+        docker run -d -p 5000:5000 --restart=always --name registry registry:2
+    else
+        echo "✅ Local Registry is running."
+    fi
+}
+
+build_and_push() {
+    ensure_registry
+    echo "--- Mirroring Images (Pull -> Tag -> Push) ---"
+    
+    # Map service names to upstream images
+    # Format: "service_name:upstream_image"
+    IMAGES=(
+        "opensearch:opensearchproject/opensearch:2.11.0"
+        "timescaledb:timescale/timescaledb:latest-pg16"
+        "pgbouncer:edoburu/pgbouncer:latest"
+        "grafana:grafana/grafana:latest"
+        "telegraf:telegraf:latest"
+        "logstash:opensearchproject/logstash-oss-with-opensearch-output-plugin:latest"
+        "nginx:nginx:alpine"
+        "opensearch-dashboards:opensearchproject/opensearch-dashboards:2.11.0"
+    )
+
+    for INFO in "${IMAGES[@]}"; do
+        SERVICE_NAME="${INFO%%:*}"
+        UPSTREAM="${INFO#*:}"
+        LOCAL_TAG="$REGISTRY/amp/$SERVICE_NAME:latest"
+
+        echo "Processing $SERVICE_NAME..."
+        echo "  - Pulling $UPSTREAM"
+        docker pull -q $UPSTREAM
+        
+        echo "  - Tagging as $LOCAL_TAG"
+        docker tag $UPSTREAM $LOCAL_TAG
+        
+        echo "  - Pushing to Registry..."
+        docker push -q $LOCAL_TAG
+        echo "  ✅ Done."
+    done
+}
+
+create_secrets() {
+    echo "--- strict Checking/Creating Secrets ---"
+    
+    # Helper to create secret if missing
+    create_secret_if_missing() {
+        SECRET_NAME=$1
+        SECRET_VALUE=$2
+        if ! docker secret ls | grep -q "$SECRET_NAME"; then
+            printf "$SECRET_VALUE" | docker secret create $SECRET_NAME -
+            echo "Created secret: $SECRET_NAME"
+        else
+            echo "Secret $SECRET_NAME exists."
+        fi
+    }
+
+    # Passwords from .env
+    create_secret_if_missing "opensearch_initial_admin_password" "${OPENSEARCH_INITIAL_ADMIN_PASSWORD:-admin}"
+    create_secret_if_missing "pg_password" "${POSTGRES_PASSWORD:-postgres}"
+    create_secret_if_missing "opensearch_jwt_secret" "${OPENSEARCH_JWT_SECRET:-supersecretjwtkey}"
+}
+
+deploy_stack() {
+    # Detect Host IP for Grafana Domain if not set
+    detect_host_ip() {
+        if [ -n "$AMP_DOMAIN_OR_IP" ]; then
+            echo "Using configured AMP_DOMAIN_OR_IP: $AMP_DOMAIN_OR_IP"
+            return
+        fi
+
+        echo "--- Detecting Host IP ---"
+        if [[ "$OSTYPE" == "darwin"* ]]; then
+             # macOS
+             HOST_IP=$(ipconfig getifaddr en0)
+             if [ -z "$HOST_IP" ]; then HOST_IP=$(ipconfig getifaddr en1); fi
+        else
+             # Linux (hostname -I | first ip)
+             HOST_IP=$(hostname -I | awk '{print $1}')
+        fi
+
+        if [ -z "$HOST_IP" ]; then
+             echo "⚠️  Could not auto-detect IP. Defaulting to localhost."
+             HOST_IP="localhost"
+        fi
+
+        export AMP_DOMAIN_OR_IP="$HOST_IP"
+        echo "✅ Auto-Detected Host IP: $AMP_DOMAIN_OR_IP"
+    }
+
+    check_swarm
+    create_secrets
+    detect_host_ip
+    create_secrets
+    calculate_heap_size
+    generate_configs
+    
+    # Allow user to override REGISTRY for multi-node (e.g. export REGISTRY=192.168.1.5:5000)
+    export REGISTRY=${REGISTRY:-127.0.0.1:5000}
+    echo "--- Deploying Stack: $STACK_NAME (Registry: $REGISTRY) ---"
+    
+    # Create external volumes if missing
+    CERTS_VOL_CREATED=false
+    for vol in certs-vol security-config-vol; do
+        if ! docker volume ls | grep -q "$vol"; then
+            docker volume create $vol
+            echo "Created volume: $vol"
+            if [ "$vol" == "certs-vol" ]; then
+                CERTS_VOL_CREATED=true
+            fi
+        fi
+    done
+    
+    if $CERTS_VOL_CREATED; then
+        echo "⚠️  Volume 'certs-vol' was just created and is empty."
+        echo "⚠️  You MUST run './manage_swarm.sh setup' to generate certificates, otherwise services will fail."
+    fi
+
+    docker stack deploy -c "$STACK_FILE" $STACK_NAME
+}
+
+rm_stack() {
+    echo "Removing Stack $STACK_NAME..."
+    docker stack rm $STACK_NAME
+}
+
+run_setup() {
+    echo "--- Running Setup (Generating Certificates) ---"
+    # Ensure volumes exist
+    if ! docker volume ls | grep -q "certs-vol"; then
+        docker volume create certs-vol
+    fi
+    if ! docker volume ls | grep -q "security-config-vol"; then
+        docker volume create security-config-vol
+    fi
+
+    # Run setup container (ephemeral)
+    # We mount the local services/setup dir to /setup
+    docker run --rm --name amp-setup-ephemeral \
+        -v "$SERVICES_DIR/setup:/setup:rw" \
+        -v "certs-vol:/certs:rw" \
+        -v "security-config-vol:/security-config:rw" \
+        -e OPENSEARCH_INITIAL_ADMIN_PASSWORD="${OPENSEARCH_INITIAL_ADMIN_PASSWORD:-admin}" \
+        -e OPENSEARCH_JWT_SECRET="${OPENSEARCH_JWT_SECRET:-supersecretjwtkey}" \
+        rockylinux:9 \
+        /bin/sh -c "dnf install -y openssl httpd-tools && chmod +x /setup/setup.sh && /setup/setup.sh /certs /security-config && chown -R 1000:1000 /certs && chown -R 1000:1000 /security-config" &
+    
+    PID=$!
+    wait $PID
+
+    
+    echo "✅ Setup complete. Certificates generated in 'certs-vol' (Ownership set to 1000:1000)."
+}
+
+security_init() {
+    echo "--- Initializing OpenSearch Security Index ---"
+    # Find running opensearch container (exclude dashboards)
+    # We use --format to check names but output ID
+    CONTAINER_ID=$(docker ps --format '{{.ID}} {{.Names}}' | grep "amp_opensearch" | grep -v "dashboards" | awk '{print $1}' | head -n 1)
+    
+    if [ -z "$CONTAINER_ID" ]; then
+        echo "❌ OpenSearch container not found. Please run 'deploy' and wait for it to start." 
+        exit 1
+    fi
+
+    echo "Found OpenSearch container: $CONTAINER_ID"
+    echo "Running securityadmin.sh..."
+
+    # 1. Copy default configs to /tmp/sec-config
+    # 2. Overwrite with custom configs from setup (config.yml, internal_users.yml)
+    # 3. Run securityadmin.sh
+    docker exec "$CONTAINER_ID" bash -c "
+      mkdir -p /tmp/sec-config && \
+      cp /usr/share/opensearch/config/opensearch-security/* /tmp/sec-config/ && \
+      cp /usr/share/opensearch/config/opensearch-security-mount/* /tmp/sec-config/ && \
+      chmod +x /usr/share/opensearch/plugins/opensearch-security/tools/securityadmin.sh && \
+      /usr/share/opensearch/plugins/opensearch-security/tools/securityadmin.sh \
+      -cd /tmp/sec-config \
+      -icl \
+      -nhnv \
+      --accept-red-cluster \
+      -h localhost \
+      -p 9200 \
+      -cacert /usr/share/opensearch/config/certs/root-ca.pem \
+      -cert /usr/share/opensearch/config/certs/admin.pem \
+      -key /usr/share/opensearch/config/certs/admin-key.pem
+    " &
+    
+    PID=$!
+    wait $PID
+
+    
+    if [ $? -eq 0 ]; then
+        echo "✅ Security Index Initialized Successfully."
+    else
+        echo "❌ Security Initialization Failed."
+        exit 1
+    fi
+}
+
+run_configurator() {
+    echo "--- Running Configurator (OpenSearch Setup) ---"
+    
+    # Needs to run on the overlay network to reach 'opensearch' service
+    # and mount certs to create the security roles using curl
+    
+    check_swarm
+    
+    # We use docker run attached to the overlay network
+    # Note: overlay networks are only attachable if 'attachable: true' is set in stack.yml (it is).
+    
+    echo "Waiting for OpenSearch to be reachable..."
+    # A simple check before running configurator could go here, or let the script fail if not ready.
+
+    docker run --rm --name amp-configurator-ephemeral \
+        --network=${STACK_NAME}_amp-overlay \
+        -e OPENSEARCH_INITIAL_ADMIN_PASSWORD="${OPENSEARCH_INITIAL_ADMIN_PASSWORD:-admin}" \
+        -e OPENSEARCH_URL="https://opensearch:9200" \
+        -e OPENSEARCH_DASHBOARDS_URL="https://opensearch-dashboards:5601" \
+        -v "certs-vol:/usr/share/opensearch/config/certs:ro" \
+        -v "$SERVICES_DIR/setup:/setup:ro" \
+        -v "$SERVICES_DIR/setup/install_curl.sh:/usr/local/bin/install_curl.sh" \
+        -v "$SERVICES_DIR/setup/configure_opensearch.sh:/usr/local/bin/configure_opensearch.sh" \
+        -v "$SERVICES_DIR/opensearch/amplog_template.json:/usr/share/opensearch/config/amplog_template.json" \
+        -v "$SERVICES_DIR/opensearch-dashboards/export.ndjson:/usr/share/opensearch/config/export.ndjson" \
+        rockylinux:9 \
+        /bin/sh -c "bash /usr/local/bin/install_curl.sh && bash /usr/local/bin/configure_opensearch.sh" &
+        
+    PID=$!
+    wait $PID
+
+        
+    echo "✅ Configurator finished."
+}
+
+case $ACTION in
+    init)
+        init_swarm
+        ensure_registry
+        ;;
+    build)
+        build_and_push
+        ;;
+    registry)
+        ensure_registry
+        ;;
+    setup)
+        run_setup
+        ;;
+    configurator)
+        run_configurator
+        ;;
+    security_init)
+        security_init
+        ;;
+    deploy)
+        deploy_stack
+        ;;
+    rm|remove)
+        rm_stack
+        ;;
+    status)
+        docker stack services $STACK_NAME
+        ;;
+    *)
+        echo "Usage: $0 {init|build|setup|deploy|security_init|status|configurator|rm}"
+        exit 1
+esac

Property changes on: platform/tools/container/manage_amp.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: /branches/amp_4_0/platform/tools/container/manage_service.sh
===================================================================
--- /branches/amp_4_0/platform/tools/container/manage_service.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/manage_service.sh	(nonexistent)
@@ -1,303 +0,0 @@
-#!/bin/bash
-
-# validate_service.sh
-# Usage: ./validate_service.sh [service_name]
-
-SERVICE=$1
-
-# REPO_ROOT Resolution
-# Assuming script is in platform/tools/container/
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-REPO_ROOT="$(cd "$SCRIPT_DIR/../../../" && pwd)"
-
-SPLIT_DIR_NAME="compose"
-COMPOSE_DIR="$SCRIPT_DIR/$SPLIT_DIR_NAME"
-
-# Detect docker-compose command
-if command -v docker-compose &> /dev/null; then
-  DOCKER_COMPOSE_CMD="docker-compose"
-elif docker compose version &> /dev/null; then
-  DOCKER_COMPOSE_CMD="docker compose"
-else
-  echo "Error: neither 'docker-compose' nor 'docker compose' found."
-  exit 1
-fi
-echo "Using Docker Compose command: $DOCKER_COMPOSE_CMD"
-export COMPOSE_IGNORE_ORPHANS=True
-
-# Detect Docker GID for Telegraf and other services needing socket access
-if [ -S /var/run/docker.sock ]; then
-  # Compatibility: stat flags differ between GNU and BSD (macOS)
-  if [[ "$OSTYPE" == "darwin"* ]]; then
-    DOCKER_GID=$(stat -f '%g' /var/run/docker.sock)
-  else
-    DOCKER_GID=$(stat -c '%g' /var/run/docker.sock)
-  fi
-  export DOCKER_GID
-  echo "Detected Docker GID: $DOCKER_GID"
-fi
-
-if [ -z "$SERVICE" ]; then
-  echo "Usage: ./manage_service.sh [service_name] [action]"
-  echo "AMP Service Manager"
-  echo ""
-  echo "Available services:"
-  echo "  base, setup, opensearch, configurator, opensearch-dashboards, timescaledb,"
-  echo "  pgbouncer, grafana, telegraf, logstash, nginx"
-  echo ""
-  echo "Actions:"
-  echo "  up       - Start/Build service (default)"
-  echo "  down     - Stop/Remove service"
-  echo "  purge    - Stop/Remove service AND volumes (Fresh Start)"
-  echo "  validate - Run health checks only"
-  exit 1
-fi
-
-ACTION=${2:-up}
-
-# Check Docker Permissions
-if ! docker ps >/dev/null 2>&1; then
-   echo "❌ Error: Docker permission denied or Docker is not running."
-   exit 1
-fi
-
-echo "--- Processing Service: $SERVICE ($ACTION) ---"
-
-# Ensure external volume 'certs-vol' exists
-if ! docker volume ls -q | grep -q "^certs-vol$"; then
-  echo "Creating external volume: certs-vol"
-  docker volume create certs-vol
-fi
-
-# Move to the Compose Directory
-cd "$COMPOSE_DIR" || exit 1
-
-# Load .env if it exists in the script directory
-if [ -f "$SCRIPT_DIR/.env" ]; then
-  # Note: Docker Compose loads .env automatically if it's in the same dir as the yaml files.
-  # If it's in the parent, we might need to symlink it or specify --env-file
-  if [ ! -f ".env" ]; then
-     ln -s "$SCRIPT_DIR/.env" .env
-     echo "DEBUG: Symlinked .env from parent directory."
-  fi
-fi
-
-if [ "$ACTION" == "down" ] || [ "$ACTION" == "purge" ]; then
-  if [ "$ACTION" == "purge" ]; then
-    echo "Stopping $SERVICE and removing volumes..."
-    $DOCKER_COMPOSE_CMD -f base.yml -f "$SERVICE.yml" down -v
-  else
-    $DOCKER_COMPOSE_CMD -f base.yml -f "$SERVICE.yml" down
-  fi
-  echo "Service $SERVICE stopped."
-  exit 0
-fi
-
-if [ "$SERVICE" == "base" ]; then
-  echo "Validating Base configuration..."
-  # 'base' only has volumes/networks, 'up' fails in v2 if no services.
-  # We trust that subsequent service runs (which include base.yml) will create them.
-  # We just check config validity here.
-  $DOCKER_COMPOSE_CMD -f base.yml config >/dev/null
-  if [ $? -eq 0 ]; then
-     echo "✅ Base configuration is valid. Networks/Volumes will be created when starting services."
-  else
-     echo "❌ Base configuration is invalid."
-     exit 1
-  fi
-  exit 0
-fi
-
-SERVICE_FILE="$SERVICE.yml"
-
-if [ ! -f "$SERVICE_FILE" ]; then
-  echo "Error: Service file $SERVICE_FILE not found in workspace."
-  exit 1
-fi
-
-if [ "$ACTION" == "up" ]; then
-   # Bring up the service (along with base)
-   # We use base.yml from the temp dir as well
-   $DOCKER_COMPOSE_CMD -f base.yml -f "$SERVICE_FILE" up -d --build
-   
-   echo "Waiting for service to initialize..."
-   sleep 5
-fi
-
-# Validation Checks
-case $SERVICE in
-  setup)
-    echo "Checking Setup exit code..."
-    EXIT_CODE=$(docker inspect amp-setup --format='{{.State.ExitCode}}')
-    if [ "$EXIT_CODE" == "0" ]; then
-      echo "✅ Setup finished successfully."
-      echo "--- Certificates in 'certs-vol' ---"
-      # List contents of /certs in the volume using a temporary container (using setup service definition)
-      $DOCKER_COMPOSE_CMD -f base.yml -f setup.yml run --rm --entrypoint ls setup -lR /certs
-      echo "-----------------------------------"
-      echo "Note: Certificates are stored in the Docker volume 'certs-vol'."
-    else
-      echo "❌ Setup failed with exit code $EXIT_CODE."
-    fi
-    ;;
-  opensearch)
-    echo "Checking OpenSearch health..."
-    if docker ps | grep -q opensearch-node1; then
-      echo "✅ OpenSearch container is running."
-      
-      # Retries for Health Check
-      MAX_RETRIES=30
-      COUNT=0
-      PASSWORD=${OPENSEARCH_INITIAL_ADMIN_PASSWORD:-admin}
-      
-      echo "Waiting for OpenSearch API to be ready (up to 60s)..."
-      while [ $COUNT -lt $MAX_RETRIES ]; do
-        docker exec opensearch-node1 curl -k -s -u admin:$PASSWORD https://localhost:9200/_cluster/health > /dev/null
-        if [ $? -eq 0 ]; then
-            echo "✅ OpenSearch API is healthy."
-            break
-        fi
-        echo "   ... waiting for API ($((COUNT*2))s)"
-        sleep 2
-        COUNT=$((COUNT+1))
-      done
-      
-      if [ $COUNT -eq $MAX_RETRIES ]; then
-         echo "❌ OpenSearch API failed to respond after 60 seconds."
-         echo "   Debugging info:"
-         docker logs --tail 20 opensearch-node1
-      fi
-    else
-      echo "❌ OpenSearch container is not running."
-    fi
-    ;;
-  timescaledb)
-    echo "Checking TimescaleDB readiness..."
-    sleep 5
-    if docker exec timescaledb pg_isready; then
-      echo "✅ TimescaleDB is ready."
-    else
-      echo "❌ TimescaleDB is not ready."
-      echo "   Debugging info:"
-      docker logs --tail 20 timescaledb
-    fi
-    ;;
-  pgbouncer)
-    echo "Checking PgBouncer..."
-    if docker ps | grep -q pgbouncer; then
-      echo "✅ PgBouncer container is running."
-      # Need a way to check port inside?
-      # Using /bin/sh to check port open with timeout (nc might not be installed)
-      # docker exec pgbouncer nc -z localhost 6432
-    else
-      echo "❌ PgBouncer container is not running."
-    fi
-    ;;
-  grafana)
-    echo "Checking Grafana..."
-    
-    MAX_RETRIES=30
-    COUNT=0
-    
-    echo "Waiting for Grafana to be ready (up to 60s)..."
-    while [ $COUNT -lt $MAX_RETRIES ]; do
-        curl -s -I http://localhost:3000/login > /dev/null
-        if [ $? -eq 0 ]; then
-             echo "✅ Grafana login page is accessible."
-             break
-        fi
-        echo "   ... waiting for Grafana ($((COUNT*2))s)"
-        sleep 2
-        COUNT=$((COUNT+1))
-    done
-
-    if [ $COUNT -eq $MAX_RETRIES ]; then
-      echo "❌ Grafana login page check failed."
-      echo "   Debugging info:"
-      docker logs --tail 20 grafana
-    fi
-    ;;
-  opensearch-dashboards)
-    echo "Checking OpenSearch Dashboards (waiting for startup)..."
-    
-    MAX_RETRIES=30
-    COUNT=0
-    
-    echo "Waiting for OpenSearch Dashboards to be ready (up to 60s)..."
-    while [ $COUNT -lt $MAX_RETRIES ]; do
-        # 5601 might not be exposed to host in docker-compose.yml (networks only)
-        # So we use docker exec to check inside
-        docker exec opensearch-dashboards curl -s -I -k https://localhost:5601 > /dev/null
-        if [ $? -eq 0 ]; then
-            echo "✅ OpenSearch Dashboards is running."
-            break
-        fi
-        echo "   ... waiting for Dashboards ($((COUNT*2))s)"
-        sleep 2
-        COUNT=$((COUNT+1))
-    done
-    
-    if [ $COUNT -eq $MAX_RETRIES ]; then
-        echo "❌ OpenSearch Dashboards check failed."
-        echo "   Debugging info:"
-        docker logs --tail 20 opensearch-dashboards
-    fi
-    ;;
-
-  nginx)
-    echo "Checking Nginx..."
-    
-    MAX_RETRIES=30
-    COUNT=0
-    
-    echo "Waiting for Nginx to be ready (up to 60s)..."
-    while [ $COUNT -lt $MAX_RETRIES ]; do
-        curl -s -k -I https://localhost > /dev/null
-        if [ $? -eq 0 ]; then
-            echo "✅ Nginx is reachable."
-            break
-        fi
-        echo "   ... waiting for Nginx ($((COUNT*2))s)"
-        sleep 2
-        COUNT=$((COUNT+1))
-    done
-
-    if [ $COUNT -eq $MAX_RETRIES ]; then
-      echo "❌ Nginx check failed."
-      echo "   Debugging info:"
-      docker logs --tail 20 nginx
-    fi
-    ;;
-  configurator)
-    echo "Checking Configurator..."
-    # Configurator is a job that requires OpenSearch. In isolated validation, it will likely fail.
-    # We check if it attempted to run.
-    sleep 5
-    STATUS=$(docker inspect amp-configurator --format='{{.State.Status}}')
-    EXIT_CODE=$(docker inspect amp-configurator --format='{{.State.ExitCode}}')
-    
-    echo "Configurator Status: $STATUS (Exit Code: $EXIT_CODE)"
-    
-    if [ "$STATUS" == "exited" ]; then
-       # It's okay if it fails (non-zero) because OpenSearch isn't here.
-       echo "✅ Configurator container was created and attempted execution."
-       if [ "$EXIT_CODE" != "0" ]; then
-         echo "   (Note: Exit code $EXIT_CODE is expected in isolation as OpenSearch is unreachable)"
-       fi
-    elif [ "$STATUS" == "running" ]; then
-       echo "✅ Configurator is running."
-    else
-       echo "❌ Configurator status is $STATUS."
-    fi
-    ;;
-  *)
-    echo "No specific validation script for $SERVICE, verifying container status..."
-    if docker ps -a | grep -q $SERVICE; then
-       echo "✅ Service $SERVICE appears to be created/running."
-    else
-       echo "⚠️ Service $SERVICE container not found."
-    fi
-    ;;
-esac
-
-echo "--- Done ---"

Property changes on: platform/tools/container/manage_service.sh
___________________________________________________________________
Deleted: svn:executable
## -1 +0,0 ##
-*
\ No newline at end of property
Index: /branches/amp_4_0/platform/tools/container/services/grafana/provisioning/datasources/datasources.yaml
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/grafana/provisioning/datasources/datasources.yaml	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/services/grafana/provisioning/datasources/datasources.yaml	(working copy)
@@ -4,23 +4,35 @@
   - name: PostgreSQL
     type: postgres
     url: timescaledb:5432
-    user: postgres # Using superuser or custom read-only user
+    user: ${DS_POSTGRES_USER}
     secureJsonData:
-      password: postgres # Should match .env
+      password: ${DS_POSTGRES_PASSWORD}
     jsonData:
-      database: postgres
+      database: cm
       sslmode: "disable"
       timescaledb: true
       version: 16
     isDefault: true
 
+  - name: TimescaleDB
+    type: postgres
+    url: timescaledb:5432
+    user: ${DS_AMP_DB_USER}
+    secureJsonData:
+      password: ${DS_AMP_DB_PASSWORD}
+    jsonData:
+      database: ${DS_AMP_DB_NAME}
+      sslmode: "disable"
+      timescaledb: true
+      version: 16
+
   - name: OpenSearch
     type: grafana-opensearch-datasource
     access: proxy
-    url: https://opensearch-node1:9200
+    url: https://opensearch:9200
     basicAuth: true
-    basicAuthUser: admin
-    basicAuthPassword: Arr@y2050 # Should match .env
+    basicAuthUser: ${DS_OS_USER}
+    basicAuthPassword: ${DS_OS_PASSWORD}
     jsonData:
       timeField: "@timestamp"
       tlsSkipVerify: true
Index: /branches/amp_4_0/platform/tools/container/services/nginx/conf.d/app.conf
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/nginx/conf.d/app.conf	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/services/nginx/conf.d/app.conf	(working copy)
@@ -10,9 +10,16 @@
 server {
     listen 80;
     listen [::]:80;
-    server_name localhost;
+    server_name _;
 
-    return 301 https://$host$request_uri;
+    location /health {
+        return 200 "OK\n";
+        add_header Content-Type text/plain;
+    }
+
+    location / {
+        return 301 https://$host$request_uri;
+    }
 }
 
 # --- HTTPS Server Block ---
Index: /branches/amp_4_0/platform/tools/container/services/opensearch/opensearch.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/opensearch/opensearch.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/opensearch/opensearch.yml	(working copy)
@@ -0,0 +1,29 @@
+cluster.name: opensearch-cluster
+network.host: 0.0.0.0
+
+# Bind to all interfaces
+transport.host: 0.0.0.0
+
+# Disable memory lock (swarm usually doesn't allow memlock unless configured)
+bootstrap.memory_lock: false
+
+# Single node discovery for now (as per stack.yml)
+discovery.type: single-node
+
+# --- Security Configuration ---
+plugins.security.ssl.http.enabled: true
+plugins.security.ssl.http.pemkey_filepath: /usr/share/opensearch/config/certs/node-key.pem
+plugins.security.ssl.http.pemcert_filepath: /usr/share/opensearch/config/certs/node.pem
+plugins.security.ssl.http.pemtrustedcas_filepath: /usr/share/opensearch/config/certs/root-ca.pem
+
+plugins.security.ssl.transport.enabled: true
+plugins.security.ssl.transport.pemkey_filepath: /usr/share/opensearch/config/certs/node-key.pem
+plugins.security.ssl.transport.pemcert_filepath: /usr/share/opensearch/config/certs/node.pem
+plugins.security.ssl.transport.pemtrustedcas_filepath: /usr/share/opensearch/config/certs/root-ca.pem
+
+# Allowed Admin DNs (Fixing the parsing issue)
+plugins.security.authcz.admin_dn:
+  - "CN=admin,O=OpenSearchAdmin,L=Bengaluru,ST=Karnataka,C=IN"
+
+plugins.security.restapi.roles_enabled: ["all_access", "security_rest_api_access"]
+plugins.security.allow_default_init_securityindex: true
Index: /branches/amp_4_0/platform/tools/container/services/pgbouncer/pgbouncer.ini
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/pgbouncer/pgbouncer.ini	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/services/pgbouncer/pgbouncer.ini	(nonexistent)
@@ -1,22 +0,0 @@
-[databases]
-cm = host=timescaledb port=5432 dbname=cm user=amp_admin password=Array@123$
-amp_ts = host=timescaledb port=5432 dbname=amp_ts user=amp_ts_user password=Array@123$
-
-[pgbouncer]
-listen_addr = 0.0.0.0
-logfile = /var/log/pgbouncer/pgbouncer.log
-listen_port = 6432
-auth_type = md5
-auth_file = /etc/pgbouncer/userlist.txt
-pool_mode = transaction
-max_client_conn = 1000
-default_pool_size = 50
-reserve_pool_size = 10
-log_connections = 1
-log_disconnections = 1
-log_pooler_errors = 1
-stats_period = 60
-verbose = 0
-admin_users = amp_admin
-stats_users = amp_admin
-ignore_startup_parameters = extra_float_digits
Index: /branches/amp_4_0/platform/tools/container/services/pgbouncer/pgbouncer.ini.template
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/pgbouncer/pgbouncer.ini.template	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/pgbouncer/pgbouncer.ini.template	(working copy)
@@ -0,0 +1,18 @@
+[databases]
+cm = host=timescaledb port=5432 dbname=cm user=amp_admin password=__AMP_ADMIN_PASSWORD__
+amp_ts = host=timescaledb port=5432 dbname=__AMP_DB_NAME__ user=__AMP_DB_USER__ password=__AMP_DB_PASSWORD__
+
+[pgbouncer]
+listen_port = 6432
+listen_addr = *
+auth_type = md5
+auth_file = /etc/pgbouncer/userlist.txt
+pool_mode = session
+max_client_conn = 100
+default_pool_size = 20
+
+# Admin users
+admin_users = amp_admin
+stats_users = amp_admin
+
+ignore_startup_parameters = extra_float_digits
Index: /branches/amp_4_0/platform/tools/container/services/pgbouncer/userlist.txt
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/pgbouncer/userlist.txt	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/services/pgbouncer/userlist.txt	(nonexistent)
@@ -1,2 +0,0 @@
-"amp_admin" "md507b5a1b329433696700cb7444c9b1392"
-"amp_ts_user" "md5dfc2c68ba0156715b3d09d4351336c53"
Index: /branches/amp_4_0/platform/tools/container/services/postgres/initdb.d/01_basics.sh
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/postgres/initdb.d/01_basics.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/postgres/initdb.d/01_basics.sh	(working copy)
@@ -0,0 +1,15 @@
+#!/bin/bash
+set -e
+
+# Dynamically create user and DB using Environment Variables
+# Variables are injected by Docker from .env
+
+psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
+    CREATE USER $AMP_DB_USER WITH PASSWORD '$AMP_DB_PASSWORD';
+    CREATE DATABASE $AMP_DB_NAME;
+    GRANT ALL PRIVILEGES ON DATABASE $AMP_DB_NAME TO $AMP_DB_USER;
+
+    -- Explicitly create 'cm' database for configuration management
+    CREATE DATABASE cm;
+    GRANT ALL PRIVILEGES ON DATABASE cm TO $AMP_DB_USER;
+EOSQL

Property changes on: platform/tools/container/services/postgres/initdb.d/01_basics.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: /branches/amp_4_0/platform/tools/container/services/postgres/initdb.d/01_basics.sql
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/postgres/initdb.d/01_basics.sql	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/services/postgres/initdb.d/01_basics.sql	(nonexistent)
@@ -1,15 +0,0 @@
--- 01_basics.sql
--- Create secondary user and db (Timescale/Postgres image creates 'postgres' user by default)
-CREATE USER amp_ts_user WITH PASSWORD 'Array@123$';
-CREATE DATABASE amp_ts;
-CREATE DATABASE cm;
-GRANT ALL PRIVILEGES ON DATABASE amp_ts TO amp_ts_user;
--- GRANT ALL PRIVILEGES ON DATABASE cm TO amp_ts_user; -- Optional, main user is postgres usually
-
-\c amp_ts
-CREATE EXTENSION IF NOT EXISTS timescaledb;
-
--- Basic schema for Logstash lookup
--- Rely on init_db.sql to create the 'device' table with the correct schema
-\c postgres
--- CREATE TABLE IF NOT EXISTS device ... (REMOVED due to conflict with init_db.sql)
Index: /branches/amp_4_0/platform/tools/container/services/postgres/initdb.d/02_telegraf_snmp.sql
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/postgres/initdb.d/02_telegraf_snmp.sql	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/services/postgres/initdb.d/02_telegraf_snmp.sql	(working copy)
@@ -737,3 +737,8 @@
 SELECT add_retention_policy('asf_http_service',             INTERVAL '180 days');
 SELECT add_retention_policy('asf_https_service',            INTERVAL '180 days');
 COMMIT;
+
+-- Grant permissions to amp_ts_user so Grafana can see the tables
+GRANT USAGE ON SCHEMA public TO amp_ts_user;
+GRANT SELECT ON ALL TABLES IN SCHEMA public TO amp_ts_user;
+ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO amp_ts_user;
Index: /branches/amp_4_0/platform/tools/container/services/postgres/initdb.d/03_docker_metrics.sql
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/postgres/initdb.d/03_docker_metrics.sql	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/postgres/initdb.d/03_docker_metrics.sql	(working copy)
@@ -0,0 +1,162 @@
+-- 03_docker_metrics.sql
+-- Schema for Telegraf Docker Input Plugin
+-- Tables: docker_container_mem, docker_container_cpu, docker_container_net, docker_container_blkio
+
+BEGIN;
+
+\c amp_ts
+
+-- 1) docker_container_mem
+CREATE TABLE IF NOT EXISTS docker_container_mem (
+    time TIMESTAMPTZ NOT NULL,
+    container_name TEXT,
+    container_image TEXT,
+    container_status TEXT,
+    engine_host TEXT,
+    server_address TEXT,
+    usage_percent DOUBLE PRECISION,
+    "usage" DOUBLE PRECISION,
+    "limit" DOUBLE PRECISION,
+    max_usage DOUBLE PRECISION,
+    active_anon DOUBLE PRECISION,
+    active_file DOUBLE PRECISION,
+    cache DOUBLE PRECISION,
+    hierarchical_memory_limit DOUBLE PRECISION,
+    inactive_anon DOUBLE PRECISION,
+    inactive_file DOUBLE PRECISION,
+    mapped_file DOUBLE PRECISION,
+    pgfault DOUBLE PRECISION,
+    pgmajfault DOUBLE PRECISION,
+    pgpgin DOUBLE PRECISION,
+    pgpgout DOUBLE PRECISION,
+    rss DOUBLE PRECISION,
+    rss_huge DOUBLE PRECISION,
+    unevictable DOUBLE PRECISION,
+    total_active_anon DOUBLE PRECISION,
+    total_active_file DOUBLE PRECISION,
+    total_cache DOUBLE PRECISION,
+    total_inactive_anon DOUBLE PRECISION,
+    total_inactive_file DOUBLE PRECISION,
+    total_mapped_file DOUBLE PRECISION,
+    total_pgfault DOUBLE PRECISION,
+    total_pgmajfault DOUBLE PRECISION,
+    total_pgpgin DOUBLE PRECISION,
+    total_pgpgout DOUBLE PRECISION,
+    total_rss DOUBLE PRECISION,
+    total_rss_huge DOUBLE PRECISION,
+    total_unevictable DOUBLE PRECISION,
+    total_writeback DOUBLE PRECISION,
+    writeback DOUBLE PRECISION
+);
+ALTER TABLE docker_container_mem OWNER TO amp_ts_user;
+SELECT create_hypertable('docker_container_mem', 'time', if_not_exists => TRUE);
+CREATE INDEX IF NOT EXISTS idx_docker_mem_container ON docker_container_mem (container_name, time DESC);
+
+-- 2) docker_container_cpu
+CREATE TABLE IF NOT EXISTS docker_container_cpu (
+    time TIMESTAMPTZ NOT NULL,
+    container_name TEXT,
+    container_image TEXT,
+    container_status TEXT,
+    engine_host TEXT,
+    server_address TEXT,
+    usage_percent DOUBLE PRECISION,
+    usage_total DOUBLE PRECISION,
+    usage_system DOUBLE PRECISION,
+    usage_user DOUBLE PRECISION,
+    throttling_periods DOUBLE PRECISION,
+    throttling_throttled_periods DOUBLE PRECISION,
+    throttling_throttled_time DOUBLE PRECISION
+);
+ALTER TABLE docker_container_cpu OWNER TO amp_ts_user;
+SELECT create_hypertable('docker_container_cpu', 'time', if_not_exists => TRUE);
+CREATE INDEX IF NOT EXISTS idx_docker_cpu_container ON docker_container_cpu (container_name, time DESC);
+
+-- 3) docker_container_net
+CREATE TABLE IF NOT EXISTS docker_container_net (
+    time TIMESTAMPTZ NOT NULL,
+    container_name TEXT,
+    container_image TEXT,
+    container_status TEXT,
+    engine_host TEXT,
+    server_address TEXT,
+    network TEXT,
+    rx_bytes DOUBLE PRECISION,
+    rx_dropped DOUBLE PRECISION,
+    rx_errors DOUBLE PRECISION,
+    rx_packets DOUBLE PRECISION,
+    tx_bytes DOUBLE PRECISION,
+    tx_dropped DOUBLE PRECISION,
+    tx_errors DOUBLE PRECISION,
+    tx_packets DOUBLE PRECISION
+);
+ALTER TABLE docker_container_net OWNER TO amp_ts_user;
+SELECT create_hypertable('docker_container_net', 'time', if_not_exists => TRUE);
+CREATE INDEX IF NOT EXISTS idx_docker_net_container ON docker_container_net (container_name, time DESC);
+
+-- 4) docker_container_blkio
+CREATE TABLE IF NOT EXISTS docker_container_blkio (
+    time TIMESTAMPTZ NOT NULL,
+    container_name TEXT,
+    container_image TEXT,
+    container_status TEXT,
+    engine_host TEXT,
+    server_address TEXT,
+    device TEXT,
+    io_service_bytes_recursive_async DOUBLE PRECISION,
+    io_service_bytes_recursive_read DOUBLE PRECISION,
+    io_service_bytes_recursive_sync DOUBLE PRECISION,
+    io_service_bytes_recursive_total DOUBLE PRECISION,
+    io_service_bytes_recursive_write DOUBLE PRECISION,
+    io_serviced_recursive_async DOUBLE PRECISION,
+    io_serviced_recursive_read DOUBLE PRECISION,
+    io_serviced_recursive_sync DOUBLE PRECISION,
+    io_serviced_recursive_total DOUBLE PRECISION,
+    io_serviced_recursive_write DOUBLE PRECISION
+);
+ALTER TABLE docker_container_blkio OWNER TO amp_ts_user;
+SELECT create_hypertable('docker_container_blkio', 'time', if_not_exists => TRUE);
+CREATE INDEX IF NOT EXISTS idx_docker_blkio_container ON docker_container_blkio (container_name, time DESC);
+
+-- 5) docker (summary stats depending on telegraf version/config, sometimes just 'docker')
+CREATE TABLE IF NOT EXISTS docker (
+    time TIMESTAMPTZ NOT NULL,
+    engine_host TEXT,
+    server_address TEXT,
+    n_containers DOUBLE PRECISION,
+    n_containers_paused DOUBLE PRECISION,
+    n_containers_running DOUBLE PRECISION,
+    n_containers_stopped DOUBLE PRECISION,
+    n_images DOUBLE PRECISION,
+    n_goroutines DOUBLE PRECISION,
+    n_listener_events DOUBLE PRECISION,
+    memory_total DOUBLE PRECISION,
+    pool_blocksize DOUBLE PRECISION
+);
+ALTER TABLE docker OWNER TO amp_ts_user;
+SELECT create_hypertable('docker', 'time', if_not_exists => TRUE);
+
+-- Compression Policies (7 days)
+ALTER TABLE docker_container_mem SET (timescaledb.compress, timescaledb.compress_segmentby = 'container_name', timescaledb.compress_orderby = 'time DESC');
+ALTER TABLE docker_container_cpu SET (timescaledb.compress, timescaledb.compress_segmentby = 'container_name', timescaledb.compress_orderby = 'time DESC');
+ALTER TABLE docker_container_net SET (timescaledb.compress, timescaledb.compress_segmentby = 'container_name', timescaledb.compress_orderby = 'time DESC');
+ALTER TABLE docker_container_blkio SET (timescaledb.compress, timescaledb.compress_segmentby = 'container_name', timescaledb.compress_orderby = 'time DESC');
+
+SELECT add_compression_policy('docker_container_mem', INTERVAL '7 days');
+SELECT add_compression_policy('docker_container_cpu', INTERVAL '7 days');
+SELECT add_compression_policy('docker_container_net', INTERVAL '7 days');
+SELECT add_compression_policy('docker_container_blkio', INTERVAL '7 days');
+
+-- Retention Policies (30 days for raw metrics)
+SELECT add_retention_policy('docker_container_mem', INTERVAL '30 days');
+SELECT add_retention_policy('docker_container_cpu', INTERVAL '30 days');
+SELECT add_retention_policy('docker_container_net', INTERVAL '30 days');
+SELECT add_retention_policy('docker_container_blkio', INTERVAL '30 days');
+SELECT add_retention_policy('docker', INTERVAL '30 days');
+
+COMMIT;
+
+-- Grant permissions (Crucial)
+GRANT USAGE, CREATE ON SCHEMA public TO amp_ts_user;
+GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO amp_ts_user;
+ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO amp_ts_user;
Index: /branches/amp_4_0/platform/tools/container/services/setup/configure_opensearch.sh
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/setup/configure_opensearch.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/services/setup/configure_opensearch.sh	(working copy)
@@ -2,18 +2,57 @@
 # configure_opensearch.sh
 # Waits for OpenSearch to be ready, then applies security roles/mappings
 
-OPENSEARCH_URL="https://opensearch-node1:9200"
+OPENSEARCH_URL="${OPENSEARCH_URL:-https://opensearch:9200}"
+OPENSEARCH_DASHBOARDS_URL="${OPENSEARCH_DASHBOARDS_URL:-https://opensearch-dashboards:5601}"
 ADMIN_USER="admin"
 ADMIN_PASS="${OPENSEARCH_INITIAL_ADMIN_PASSWORD}"
 
 log() { echo "[$(date -Iseconds)] $1"; }
 
-log "Waiting for OpenSearch at $OPENSEARCH_URL..."
-until curl -s -k -u "$ADMIN_USER:$ADMIN_PASS" "$OPENSEARCH_URL/_cluster/health" > /dev/null; do
-  log "OpenSearch not ready yet... sleeping 5s"
+MAX_RETRIES=60
+COUNT=0
+log "Waiting for OpenSearch at $OPENSEARCH_URL (timeout: 300s)..."
+
+# --- DNS DEBUGGING ---
+log "🔍 Debugging Network/DNS..."
+log "Contents of /etc/resolv.conf:"
+cat /etc/resolv.conf
+
+log "Testing DNS resolution:"
+for HOST in opensearch amp_opensearch tasks.amp_opensearch tasks.opensearch; do
+    if nslookup "$HOST" >/dev/null 2>&1; then
+        log "  ✅ nslookup $HOST: SUCCESS"
+        nslookup "$HOST"
+    else
+        log "  ❌ nslookup $HOST: FAILED"
+    fi
+done
+# ---------------------
+
+while [ $COUNT -lt $MAX_RETRIES ]; do
+  # Capture output and exit code to debug failure
+  OUTPUT=$(curl -v -k -u "$ADMIN_USER:$ADMIN_PASS" "$OPENSEARCH_URL/_cluster/health" 2>&1)
+  RET=$?
+  
+  if [ $RET -eq 0 ]; then
+      log "✅ OpenSearch is UP."
+      break
+  fi
+  
+  log "OpenSearch not ready yet at $OPENSEARCH_URL... (Exit Code: $RET)"
+  # Print the error message (last few lines where the error usually is)
+  log "Curl Error Output:"
+  echo "$OUTPUT" | tail -n 5 | while read -r line; do log "  $line"; done
+  
   sleep 5
+  COUNT=$((COUNT+1))
 done
 
+if [ $COUNT -eq $MAX_RETRIES ]; then
+    log "❌ Timeout waiting for OpenSearch."
+    exit 1
+fi
+
 log "OpenSearch is UP. Applying configuration..."
 
 # 1. Create jwt_users Role
@@ -53,23 +92,46 @@
 RESPONSE=$(curl -s -k -u "$ADMIN_USER:$ADMIN_PASS" -w "%{http_code}" -X PUT "$OPENSEARCH_URL/_template/acm_template" -H 'Content-Type: application/json' -d @/usr/share/opensearch/config/amplog_template.json)
 log "Done. HTTP Response: $RESPONSE"
 
-log "Waiting for OpenSearch Dashboards (https://opensearch-dashboards:5601/visualization/api/status)..."
-until STATUS_RES=$(curl -s -k -u "$ADMIN_USER:$ADMIN_PASS" "https://opensearch-dashboards:5601/visualization/api/status") && echo "$STATUS_RES" | grep -q '"state":"green"'; do
-  # Fallback for state extraction if jq is missing
-  if command -v jq >/dev/null 2>&1; then
-    CURRENT_STATE=$(echo "$STATUS_RES" | jq -r '.status.overall.state // "Unknown"')
-  else
-    CURRENT_STATE=$(echo "$STATUS_RES" | grep -o '"state":"[^"]*"' | head -1 | cut -d'"' -f4 || echo "Unknown")
+MAX_RETRIES=60
+COUNT=0
+log "Waiting for OpenSearch Dashboards ($OPENSEARCH_DASHBOARDS_URL/visualization/api/status) (timeout: 300s)..."
+while [ $COUNT -lt $MAX_RETRIES ]; do
+  STATUS_RES=$(curl -s -k -u "$ADMIN_USER:$ADMIN_PASS" "$OPENSEARCH_DASHBOARDS_URL/visualization/api/status")
+  if echo "$STATUS_RES" | grep -q '"state":"green"'; then
+      log "✅ Dashboards is Green/Ready."
+      break
   fi
-  log "Dashboards not ready yet (Current: $CURRENT_STATE). Raw response: $STATUS_RES"
-  log "Sleeping 5s..."
+
+  # Fallback for state extraction if jq is missing or response is not JSON
+  # Check if response looks like JSON (starts with {)
+  case "$STATUS_RES" in
+    \{*)
+      if command -v jq >/dev/null 2>&1; then
+        CURRENT_STATE=$(echo "$STATUS_RES" | jq -r '.status.overall.state // "Unknown"')
+      else
+        CURRENT_STATE=$(echo "$STATUS_RES" | grep -o '"state":"[^"]*"' | head -1 | cut -d'"' -f4)
+      fi
+      ;;
+    *)
+      CURRENT_STATE="Not JSON (Starting...)"
+      ;;
+  esac
+
+  if [ -z "$CURRENT_STATE" ]; then CURRENT_STATE="Unknown"; fi
+  log "Dashboards not ready yet (Current: $CURRENT_STATE). Sleeping 5s..."
   sleep 5
+  COUNT=$((COUNT+1))
 done
 
+if [ $COUNT -eq $MAX_RETRIES ]; then
+    log "❌ Timeout waiting for Dashboards."
+    exit 1
+fi
+
 log "Importing Saved Objects (Index Patterns, Dashboards)..."
 # Using -w %{http_code} to catch errors and -o to avoid dumping large JSON to logs
 HTTP_CODE=$(curl -s -k -u "$ADMIN_USER:$ADMIN_PASS" -o /tmp/import_res.json -w "%{http_code}" \
-  -X POST "https://opensearch-dashboards:5601/visualization/api/saved_objects/_import?overwrite=true" \
+  -X POST "$OPENSEARCH_DASHBOARDS_URL/visualization/api/saved_objects/_import?overwrite=true" \
   -H "osd-xsrf: true" \
   --form file=@/usr/share/opensearch/config/export.ndjson)
 
Index: /branches/amp_4_0/platform/tools/container/services/setup/install_curl.sh
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/setup/install_curl.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/services/setup/install_curl.sh	(working copy)
@@ -12,7 +12,7 @@
         echo "Detected RHEL-based distribution: $ID"
         # --allowerasing is key for Rocky Linux 9 to replace curl-minimal
         dnf install -y epel-release
-        dnf install -y --allowerasing curl jq
+        dnf install -y --allowerasing curl jq bind-utils iputils
     elif [[ "$ID" == "debian" || "$ID" == "ubuntu" ]]; then
         echo "Detected Debian-based distribution: $ID"
         apt-get update && apt-get install -y curl
Index: /branches/amp_4_0/platform/tools/container/services/setup/install_python.sh
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/setup/install_python.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/services/setup/install_python.sh	(working copy)
@@ -6,7 +6,7 @@
 exec > >(tee -a "$LOGFILE") 2>&1
 
 echo "------------------------------------------------------------------"
-echo "  Installing Python 3.13.0 on Rocky Linux 9.5 (Non-Interactive)"
+echo "  Installing Python 3.13.0 on Rocky Linux 9.x (Non-Interactive)"
 echo "  Log file: $LOGFILE"
 echo "------------------------------------------------------------------"
 
Index: /branches/amp_4_0/platform/tools/container/services/setup/setup.sh
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/setup/setup.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/services/setup/setup.sh	(working copy)
@@ -18,7 +18,7 @@
 SAN_HOSTS="DNS:localhost,IP:127.0.0.1,DNS:opensearch-node1,DNS:opensearch-dashboards"
 ADMIN_KEY="$OUTPUT_DIR/admin-key.pem"
 ADMIN_CERT="$OUTPUT_DIR/admin.pem"
-ADMIN_DN="/CN=admin/O=OpenSearchAdmin/L=Bengaluru/ST=Karnataka/C=IN"
+ADMIN_DN="/C=IN/ST=Karnataka/L=Bengaluru/O=OpenSearchAdmin/CN=admin"
 
 log() { echo "[$(date -Iseconds)] $1"; }
 
@@ -122,6 +122,69 @@
   description: "Kibana server user"
 EOF
 
+# --- 4. OpenSearch Main Config (opensearch.yml) ---
+# We generate this to avoid Env Var parsing issues with Lists (nodes_dn, admin_dn)
+OS_CONF="$OUTPUT_DIR/opensearch.yml"
+log "Generating opensearch.yml..."
 
+cat > "$OS_CONF" <<EOF
+---
+cluster.name: opensearch-cluster
+network.host: 0.0.0.0
+
+plugins.security.ssl.transport.pemkey_filepath: certs/node-key.pem
+plugins.security.ssl.transport.pemcert_filepath: certs/node.pem
+plugins.security.ssl.transport.pemtrustedcas_filepath: certs/root-ca.pem
+plugins.security.ssl.transport.enforce_hostname_verification: false
+plugins.security.ssl.http.enabled: true
+plugins.security.ssl.http.pemkey_filepath: certs/node-key.pem
+plugins.security.ssl.http.pemcert_filepath: certs/node.pem
+plugins.security.ssl.http.pemtrustedcas_filepath: certs/root-ca.pem
+
+plugins.security.allow_unsafe_democertificates: false
+plugins.security.allow_default_init_securityindex: true
+
+plugins.security.authcz.admin_dn:
+  - "CN=admin,O=OpenSearchAdmin,L=Bengaluru,ST=Karnataka,C=IN"
+
+plugins.security.nodes_dn:
+  - "CN=node-1,O=OpenSearchNode,L=Bengaluru,ST=Karnataka,C=IN"
+
+plugins.security.restapi.roles_enabled: ["all_access", "security_rest_api_access"]
+plugins.security.system_indices.enabled: true
+EOF
+
+chmod 644 "$OS_CONF"
+
+
 chmod 644 "$CONFIG_FILE"
+
+# --- Generate roles_mapping.yml ---
+ROLES_MAPPING="$CONFIG_DIR/roles_mapping.yml"
+log "Generating roles_mapping.yml at $ROLES_MAPPING"
+cat > "$ROLES_MAPPING" <<EOF
+_meta:
+  type: "rolesmapping"
+  config_version: 2
+
+all_access:
+  reserved: false
+  backend_roles:
+  - "admin"
+  description: "Maps admin to all_access"
+
+security_rest_api_access:
+  reserved: false
+  backend_roles:
+  - "admin"
+  description: "Maps admin to security_rest_api_access"
+
+kibana_server:
+  reserved: false
+  users:
+  - "kibanaserver"
+  description: "Maps kibanaserver to kibana_server"
+EOF
+chmod 644 "$ROLES_MAPPING"
+
 log "Setup complete."
Index: /branches/amp_4_0/platform/tools/container/services/telegraf/telegraf.conf
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/telegraf/telegraf.conf	(revision 2855)
+++ /branches/amp_4_0/platform/tools/container/services/telegraf/telegraf.conf	(working copy)
@@ -7,7 +7,7 @@
   flush_interval = "10s"
   precision = ""
   hostname = "amp-docker"
-  logfile = "/var/log/telegraf/telegraf.log"
+  # logfile = "/var/log/telegraf/telegraf.log"
   debug = true
   quiet = false
   omit_hostname = false
@@ -15,7 +15,7 @@
 
 # === Output: TimescaleDB/PostgreSQL ===
 [[outputs.postgresql]]
-  connection = "host=timescaledb port=5432 user=postgres password=$PG_PASSWORD dbname=postgres sslmode=disable"
+  connection = "host=timescaledb port=5432 user=amp_ts_user password=Array@123$$ dbname=amp_ts sslmode=disable"
   schema     = "public"
   # Route metrics to Timescale, configured to drop logs
   namedrop = ["docker_log"]
Index: /branches/amp_4_0/platform/tools/container/stack.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/stack.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/stack.yml	(working copy)
@@ -0,0 +1,290 @@
+networks:
+  amp-overlay:
+    driver: overlay
+    attachable: true
+
+configs:
+  nginx_app_conf:
+    file: services/nginx/conf.d/app.conf
+  telegraf_main_conf:
+    file: services/telegraf/telegraf.conf
+  telegraf_ag_conf:
+    file: services/telegraf/telegraf.d/ag.toml
+  telegraf_apv_conf:
+    file: services/telegraf/telegraf.d/apv.toml
+  telegraf_asf_conf:
+    file: services/telegraf/telegraf.d/asf.toml
+  logstash_pipeline_conf:
+    file: services/logstash/pipeline/syslog.conf
+  logstash_config_yml:
+    file: services/logstash/config/logstash.yml
+  pgbouncer_ini:
+    file: services/pgbouncer/pgbouncer.ini
+  pgbouncer_userlist:
+    file: services/pgbouncer/userlist.txt
+  grafana_datasources:
+    file: services/grafana/provisioning/datasources/datasources.yaml
+  opensearch_config:
+    file: services/opensearch/opensearch.yml
+
+secrets:
+  opensearch_initial_admin_password:
+    external: true
+  pg_password:
+    external: true
+  opensearch_jwt_secret:
+    external: true
+
+services:
+  # --- Core Storage (Pinned to Storage Node) ---
+  opensearch:
+    image: ${REGISTRY:-127.0.0.1:5000}/amp/opensearch:latest
+    deploy:
+      mode: replicated
+      replicas: 1
+      placement:
+        constraints:
+          - node.labels.type == storage
+      restart_policy:
+        condition: on-failure
+    environment:
+      - bootstrap.memory_lock=false
+      - "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m"
+      - OPENSEARCH_INITIAL_ADMIN_PASSWORD_FILE=/run/secrets/opensearch_initial_admin_password
+    configs:
+      - source: opensearch_config
+        target: /usr/share/opensearch/config/opensearch.yml
+    healthcheck:
+      test: ["CMD-SHELL", "curl -f -k -u admin:$$(cat /run/secrets/opensearch_initial_admin_password) https://localhost:9200/_cluster/health || exit 1"]
+      interval: 30s
+      timeout: 10s
+      retries: 3
+      start_period: 60s
+    command: /usr/share/opensearch/opensearch-docker-entrypoint.sh opensearch
+    secrets:
+      - opensearch_initial_admin_password
+    volumes:
+      - opensearch-data:/usr/share/opensearch/data
+      - certs-vol:/usr/share/opensearch/config/certs:ro
+      - security-config-vol:/usr/share/opensearch/config/opensearch-security-mount:ro
+      - opensearch-logs:/usr/share/opensearch/logs
+    networks:
+      - amp-overlay
+
+  timescaledb:
+    image: ${REGISTRY:-127.0.0.1:5000}/amp/timescaledb:latest
+    deploy:
+      mode: replicated
+      replicas: 1
+      placement:
+        constraints:
+          - node.labels.type == storage
+    healthcheck:
+      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"]
+      interval: 10s
+      timeout: 5s
+      retries: 5
+    ports:
+      - "${AMP_TIMESCALEDB_PORT:-5432}:5432"
+    environment:
+      - POSTGRES_PASSWORD_FILE=/run/secrets/pg_password
+      - POSTGRES_USER=${POSTGRES_USER}
+      - POSTGRES_DB=${POSTGRES_DB}
+      - AMP_DB_USER=${AMP_DB_USER}
+      - AMP_DB_PASSWORD=${AMP_DB_PASSWORD}
+      - AMP_DB_NAME=${AMP_DB_NAME}
+    secrets:
+      - pg_password
+    volumes:
+      - timescaledb-data:/var/lib/postgresql/data
+      - ./services/postgres/initdb.d:/docker-entrypoint-initdb.d:ro
+    networks:
+      - amp-overlay
+
+  # --- Middleware ---
+  pgbouncer:
+    image: ${REGISTRY:-127.0.0.1:5000}/amp/pgbouncer:latest
+    deploy:
+      replicas: 1
+    environment:
+      - AGENT_HOSTNAME=amp-docker-agent
+      - HOST_PROC=/host/proc
+      - HOST_SYS=/host/sys
+      - HOST_ETC=/host/etc
+      - AMP_DB_NAME=${AMP_DB_NAME:-amp_ts}
+      - AMP_DB_USER=${AMP_DB_USER:-amp_ts_user}
+      - AMP_DB_PASSWORD=${AMP_DB_PASSWORD:-Array@123$}
+      - DB_HOST=timescaledb
+      - DB_NAME=cm
+    configs:
+      - source: pgbouncer_ini
+        target: /etc/pgbouncer/pgbouncer.ini
+      - source: pgbouncer_userlist
+        target: /etc/pgbouncer/userlist.txt
+    networks:
+      - amp-overlay
+
+  # --- Frontend / Observability ---
+  nginx:
+    image: ${REGISTRY:-127.0.0.1:5000}/amp/nginx:latest
+    deploy:
+      replicas: 1
+      placement:
+        constraints:
+          - node.role == manager # Often helps to run ingress on manager or explicit edge nodes
+    healthcheck:
+      test: ["CMD-SHELL", "wget --spider http://localhost/health || wget --spider http://localhost/ || exit 1"]
+      interval: 30s
+      timeout: 5s
+      retries: 3
+    ports:
+      - "${AMP_NGINX_HTTPS_PORT:-443}:443"
+      - "${AMP_NGINX_HTTP_PORT:-80}:80"
+    configs:
+      - source: nginx_app_conf
+        target: /etc/nginx/conf.d/app.conf
+    volumes:
+      - certs-vol:/etc/nginx/certs:ro
+      - type: bind
+        source: /dev/null
+        target: /etc/nginx/conf.d/default.conf
+    networks:
+      - amp-overlay
+
+  opensearch-dashboards:
+    image: ${REGISTRY:-127.0.0.1:5000}/amp/opensearch-dashboards:latest
+    deploy:
+      replicas: 1
+    environment:
+      OPENSEARCH_HOSTS: '["https://opensearch:9200"]'
+      OPENSEARCH_SSL_VERIFICATIONMODE: certificate
+      OPENSEARCH_USERNAME: admin
+      OPENSEARCH_SSL_CERTIFICATEAUTHORITIES: '["/usr/share/opensearch-dashboards/config/certs/root-ca.pem"]'
+      SERVER_SSL_ENABLED: "true"
+      SERVER_SSL_KEY: /usr/share/opensearch-dashboards/config/certs/node-key.pem
+      SERVER_SSL_CERTIFICATE: /usr/share/opensearch-dashboards/config/certs/node.pem
+      SERVER_BASEPATH: /visualization
+      SERVER_REWRITEBASEPATH: "true"
+    command: >
+      bash -c "export OPENSEARCH_PASSWORD=\$$(cat /run/secrets/opensearch_initial_admin_password) && /usr/share/opensearch-dashboards/opensearch-dashboards-docker-entrypoint.sh opensearch-dashboards"
+    secrets:
+      - opensearch_initial_admin_password
+    volumes:
+      - certs-vol:/usr/share/opensearch-dashboards/config/certs:ro
+    networks:
+      - amp-overlay
+    ports:
+      - "5601:5601"
+
+  grafana:
+    image: ${REGISTRY:-127.0.0.1:5000}/amp/grafana:latest
+    deploy:
+      replicas: 1
+      placement:
+        constraints:
+          - node.labels.type == storage # Pinned for sqlite db
+    healthcheck:
+      test: ["CMD-SHELL", "curl -f http://localhost:3000/api/health || exit 1"]
+      interval: 30s
+      timeout: 10s
+      retries: 3
+    environment:
+      - GF_LOG_LEVEL=debug
+      - GF_SERVER_ROOT_URL=https://${AMP_DOMAIN_OR_IP:-localhost}/monitoring/
+      - GF_SERVER_SERVE_FROM_SUB_PATH=true
+      - GF_SECURITY_COOKIE_SECURE=true
+      - GF_SECURITY_COOKIE_SAMESITE=lax
+      - GF_SERVER_DOMAIN=${AMP_DOMAIN_OR_IP:-localhost}
+      - GF_SERVER_ENFORCE_DOMAIN=false
+      - GF_SECURITY_COOKIE_PATH=/
+      - GF_AUTH_TOKEN_ROTATION_INTERVAL_MINUTES=1440
+      - GF_AUTH_LOGIN_MAXIMUM_INACTIVE_LIFETIME_DAYS=30
+      - GF_AUTH_LOGIN_MAXIMUM_LIFETIME_DAYS=30
+      - GF_SECURITY_ADMIN_USER=admin
+      - GF_SECURITY_ADMIN_PASSWORD=${GF_SECURITY_ADMIN_PASSWORD:-GArr@y2050}
+      - GF_INSTALL_PLUGINS=grafana-opensearch-datasource
+      - DS_POSTGRES_USER=${POSTGRES_USER:-postgres}
+      - DS_POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-Arr@y2050}
+      - DS_AMP_DB_NAME=${AMP_DB_NAME:-amp_ts}
+      - DS_AMP_DB_USER=${AMP_DB_USER:-amp_ts_user}
+      - DS_AMP_DB_PASSWORD=${AMP_DB_PASSWORD:-Array@123$}
+      - DS_OS_USER=admin
+      - DS_OS_PASSWORD=${OPENSEARCH_INITIAL_ADMIN_PASSWORD:-Arr@y2050}
+    configs:
+      - source: grafana_datasources
+        target: /etc/grafana/provisioning/datasources/datasources.yaml
+    volumes:
+      - grafana-data:/var/lib/grafana
+    networks:
+      - amp-overlay
+    ports:
+      - "${AMP_GRAFANA_PORT:-3000}:3000"
+
+  telegraf:
+    image: ${REGISTRY:-127.0.0.1:5000}/amp/telegraf:latest
+    user: root
+    security_opt:
+      - label=disable
+    deploy:
+      mode: global # Run on EVERY node to monitor docker
+    entrypoint: ["telegraf"]
+    environment:
+      PG_PASSWORD_FILE: /run/secrets/pg_password
+    secrets:
+      - pg_password
+    configs:
+      - source: telegraf_main_conf
+        target: /etc/telegraf/telegraf.conf
+      - source: telegraf_ag_conf
+        target: /etc/telegraf/telegraf.d/ag.toml
+      - source: telegraf_apv_conf
+        target: /etc/telegraf/telegraf.d/apv.toml
+      - source: telegraf_asf_conf
+        target: /etc/telegraf/telegraf.d/asf.toml
+    volumes:
+      - /var/run/docker.sock:/var/run/docker.sock
+      - /dev:/dev:ro
+      - /proc:/rootfs/proc:ro
+      - /sys:/rootfs/sys:ro
+      - /etc:/rootfs/etc:ro
+    networks:
+      - amp-overlay
+
+  logstash:
+    image: ${REGISTRY:-127.0.0.1:5000}/amp/logstash:latest
+    deploy:
+      replicas: 1
+    ports:
+      - "514:5514/tcp"
+      - "514:5514/udp"
+    environment:
+      OPENSEARCH_URL: https://opensearch:9200
+      POSTGRES_PASSWORD_FILE: /run/secrets/pg_password
+      POSTGRES_USER: postgres
+      POSTGRES_DB: cm
+    command: >
+      bash -c "export OPENSEARCH_INITIAL_ADMIN_PASSWORD=\$$(cat /run/secrets/opensearch_initial_admin_password) && /usr/share/logstash/bin/logstash"
+
+    secrets:
+      - opensearch_initial_admin_password
+      - pg_password
+    configs:
+      - source: logstash_pipeline_conf
+        target: /usr/share/logstash/pipeline/syslog.conf
+      - source: logstash_config_yml
+        target: /usr/share/logstash/config/logstash.yml
+    volumes:
+      - certs-vol:/usr/share/logstash/config/certs:ro
+      - ./services/logstash/drivers:/usr/share/logstash/drivers:ro
+    networks:
+      - amp-overlay
+
+volumes:
+  opensearch-data:
+  timescaledb-data:
+  grafana-data:
+  certs-vol:
+    external: true
+  security-config-vol:
+    external: true
+  opensearch-logs:
Index: /branches/amp_4_0/platform/tools/enable_opensearch_jwt_auth.sh
===================================================================
--- /branches/amp_4_0/platform/tools/enable_opensearch_jwt_auth.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/enable_opensearch_jwt_auth.sh	(nonexistent)
@@ -1,88 +0,0 @@
-#!/bin/bash
-
-OPENSEARCH_CONFIG_DIR="/etc/opensearch/opensearch-security"
-CONFIG_YML="$OPENSEARCH_CONFIG_DIR/config.yml"
-ROLES_YML="$OPENSEARCH_CONFIG_DIR/roles.yml"
-ROLES_MAPPING_YML="$OPENSEARCH_CONFIG_DIR/roles_mapping.yml"
-CERTS_DIR="/etc/opensearch/certs"
-BACKUP_CONFIG_YML="$CONFIG_YML.bak-$(date +%F-%H%M%S)"
-SECURITY_ADMIN_SCRIPT="/usr/share/opensearch/plugins/opensearch-security/tools/securityadmin.sh"
-
-sudo dnf install epel-release -y
-sudo dnf install yq -y
-
-pip3.13 install pyyaml
-
-echo "🔐 Generating a new JWT signing key..."
-BASE64_SIGNING_KEY=$(openssl rand -base64 32 | tr -d '\n"' | tr -d "'")
-
-if [ -z "$BASE64_SIGNING_KEY" ]; then
-    echo "❌ Failed to generate signing key. Exiting."
-    exit 1
-fi
-
-echo "📝 Creating backup of config.yml..."
-cp "$CONFIG_YML" "$BACKUP_CONFIG_YML"
-
-echo "✅ Updating config.yml using yq..."
-command -v yq >/dev/null 2>&1 || { echo "❌ yq not installed."; exit 1; }
-
-# Enable HTTP for JWT domain and set key
-yq eval -i '.config.dynamic.authc.jwt_auth_domain.http_enabled = true' "$CONFIG_YML"
-yq eval -i '.config.dynamic.authc.jwt_auth_domain.http_authenticator.config.signing_key = "'"$BASE64_SIGNING_KEY"'"' "$CONFIG_YML"
-yq eval -i '.config.dynamic.authc.jwt_auth_domain.http_authenticator.config.jwt_url_parameter = "access_token"' "$CONFIG_YML"
-yq eval -i '.config.dynamic.authc.jwt_auth_domain.http_authenticator.config.roles_key = "roles"' "$CONFIG_YML"
-yq eval -i '.config.dynamic.authc.jwt_auth_domain.http_authenticator.config.subject_key = "sub"' "$CONFIG_YML"
-yq eval -i '.config.dynamic.authc.jwt_auth_domain.http_authenticator.config.issuer = "amp.com"' "$CONFIG_YML"
-yq eval -i '.config.dynamic.authc.jwt_auth_domain.order = 1' "$CONFIG_YML"
-
-echo "✅ Ensuring roles.yml contains jwt_users role..."
-if ! grep -q '^jwt_users:' "$ROLES_YML"; then
-    cat >> "$ROLES_YML" <<EOL
-
-jwt_users:
-  cluster_permissions:
-    - "cluster:monitor/*"
-  index_permissions:
-    - index_patterns:
-        - "*"
-      allowed_actions:
-        - "read"
-  tenant_permissions:
-    - tenant_patterns:
-        - "*"
-      allowed_actions:
-        - "kibana_all_write"
-EOL
-fi
-
-echo "✅ Ensuring roles_mapping.yml contains mapping for JWT users..."
-if ! grep -q '^jwt_users:' "$ROLES_MAPPING_YML"; then
-    cat >> "$ROLES_MAPPING_YML" <<EOL
-
-jwt_users:
-  reserved: false
-  hidden: false
-  description: "All JWT authenticated users"
-  users:
-    - "admin"
-EOL
-fi
-
-echo "🔍 Validating YAML syntax..."
-python3 -c "import yaml; yaml.safe_load(open('$CONFIG_YML'))" || { echo "❌ Invalid YAML syntax"; cp "$BACKUP_CONFIG_YML" "$CONFIG_YML"; exit 1; }
-
-echo "✅ Pushing updated security configuration..."
-$SECURITY_ADMIN_SCRIPT -cd "$OPENSEARCH_CONFIG_DIR" \
-    -cacert "$CERTS_DIR/root-ca.pem" \
-    -cert "$CERTS_DIR/admin.pem" \
-    -key "$CERTS_DIR/admin-key.pem" -nhnv
-
-if [ $? -eq 0 ]; then
-    echo "✅ JWT Authentication enabled and role mapping applied successfully."
-    echo "ℹ️ Restart OpenSearch service if needed."
-else
-    echo "❌ Security admin push failed. Restoring backup."
-    cp "$BACKUP_CONFIG_YML" "$CONFIG_YML"
-    exit 1
-fi
Index: /branches/amp_4_0/platform/tools/install_configure_opensearch.sh
===================================================================
--- /branches/amp_4_0/platform/tools/install_configure_opensearch.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/install_configure_opensearch.sh	(nonexistent)
@@ -1,573 +0,0 @@
-#!/bin/bash
-
-# Script to install OpenSearch 3.x and OpenSearch Dashboards on Rocky Linux with
-# automated security setup (password change) and self-signed SSL certificates.
-
-# --- Configuration ---
-OPENSEARCH_REPO_DOWNLOAD_URL="https://artifacts.opensearch.org/releases/bundle/opensearch/3.x/opensearch-3.x.repo"
-OPENSEARCH_REPO_FILE="/etc/yum.repos.d/opensearch-3.x.repo"
-OPENSEARCH_GPG_KEY_URL="https://artifacts.opensearch.org/publickeys/opensearch-release.pgp"
-DASHBOARDS_REPO_DOWNLOAD_URL="https://artifacts.opensearch.org/releases/bundle/opensearch-dashboards/3.x/opensearch-dashboards-3.x.repo"
-DASHBOARDS_REPO_FILE="/etc/yum.repos.d/opensearch-dashboards-3.x.repo"
-OPENSEARCH_CONFIG_FILE="/etc/opensearch/opensearch.yml"
-OPENSEARCH_DATA_DIR="/var/lib/opensearch"
-OPENSEARCH_LOG_DIR="/var/log/opensearch"
-OPENSEARCH_SERVICE="opensearch"
-OPENSEARCH_SERVICE_FILE="/usr/lib/systemd/system/opensearch.service"
-SECURITY_CONFIG_DIR="/etc/opensearch/opensearch-security"
-
-DASHBOARDS_VERSION="3.0.0"
-DASHBOARDS_CONFIG_FILE="/etc/opensearch-dashboards/opensearch_dashboards.yml"
-DASHBOARDS_SERVICE="opensearch-dashboards"
-DASHBOARDS_PORT=5601
-
-NEW_ADMIN_PASSWORD="Arr@y2050" # WARNING: Hardcoded password, NOT for production!
-
-CERT_DIR="/etc/opensearch/certs"
-CA_KEY="${CERT_DIR}/root-ca-key.pem"
-CA_CERT="${CERT_DIR}/root-ca.pem"
-NODE_KEY="${CERT_DIR}/node-key.pem"
-NODE_CERT="${CERT_DIR}/node.pem"
-ADMIN_KEY="${CERT_DIR}/admin-key.pem"
-ADMIN_CERT="${CERT_DIR}/admin.pem"
-
-# Certificate DNs
-CA_DN="/C=IN/ST=Karnataka/L=Bengaluru/O=OpenSearchSelfSignedCA/CN=OpenSearchCA"
-NODE_DN="/C=IN/ST=Karnataka/L=Bengaluru/O=OpenSearchNode/CN=node-1"
-ADMIN_DN="/CN=admin/O=OpenSearchAdmin/L=Bengaluru/ST=Karnataka/C=IN"
-
-# Subject Alternative Names (SANs) for node certificate
-SAN_HOSTS="DNS:localhost,IP:127.0.0.1,DNS:node-1"
-
-JAVA_HOME_PATH="/usr/lib/jvm/java-21-openjdk-21.0.8.0.9-1.el9.x86_64/"
-
-# --- Functions ---
-
-log_info() {
-    echo -e "\n\033[0;34m[INFO]\033[0m $1"
-}
-
-log_success() {
-    echo -e "\n\033[0;32m[SUCCESS]\033[0m $1"
-}
-
-log_warn() {
-    echo -e "\n\033[0;33m[WARNING]\033[0m $1"
-}
-
-log_error() {
-    echo -e "\n\033[0;31m[ERROR]\033[0m $1"
-    exit 1
-}
-
-# Check if certificate files exist
-check_cert_files() {
-    log_info "Checking certificate files..."
-    for file in "${CA_KEY}" "${CA_CERT}" "${NODE_KEY}" "${NODE_CERT}" "${ADMIN_KEY}" "${ADMIN_CERT}"; do
-        if [ ! -f "${file}" ]; then
-            log_error "Certificate file ${file} does not exist."
-        fi
-    done
-    log_success "All certificate files exist."
-}
-
-# --- Main Script ---
-
-log_info "Starting OpenSearch 3.x and Dashboards installation on Rocky Linux..."
-
-# 1. Check for root privileges
-if [[ $EUID -ne 0 ]]; then
-   log_error "This script must be run as root. Please use 'sudo bash $0'."
-fi
-
-# 2. Check for Java installation
-log_info "Checking for Java installation (OpenJDK 17 or higher recommended)..."
-if command -v java &>/dev/null; then
-    JAVA_VERSION=$(java -version 2>&1 | awk -F '"' '/version/ {print $2}' | cut -d'.' -f1)
-    if (( JAVA_VERSION >= 17 )); then
-        log_success "Java ${JAVA_VERSION} found. Compatible with OpenSearch 3.x."
-        if [ -z "${JAVA_HOME_PATH}" ]; then
-            DETECTED_JAVA_BIN=$(readlink -f $(which java))
-            if [ -n "${DETECTED_JAVA_BIN}" ]; then
-                JAVA_HOME_PATH=$(dirname $(dirname "${DETECTED_JAVA_BIN}"))
-                log_info "Auto-detected JAVA_HOME_PATH: ${JAVA_HOME_PATH}"
-            else
-                log_error "Failed to determine JAVA_HOME_PATH. Please set it manually."
-            fi
-        fi
-    else
-        log_error "Java version ${JAVA_VERSION} found. OpenSearch 3.x requires Java 17 or higher."
-    fi
-else
-    log_error "Java not found. Please install Java (OpenJDK 17 or higher)."
-fi
-
-# 3. Download OpenSearch repository file
-log_info "Downloading OpenSearch 3.x repository file: ${OPENSEARCH_REPO_FILE}..."
-sudo curl -o "${OPENSEARCH_REPO_FILE}" "${OPENSEARCH_REPO_DOWNLOAD_URL}" || log_error "Failed to download OpenSearch repository file."
-
-# Configure OpenSearch repository
-if ! grep -q "gpgcheck=1" "${OPENSEARCH_REPO_FILE}"; then
-    log_warn "Adding 'gpgcheck=1' to ${OPENSEARCH_REPO_FILE}."
-    echo "gpgcheck=1" | sudo tee -a "${OPENSEARCH_REPO_FILE}" > /dev/null
-fi
-if ! grep -q "gpgkey=" "${OPENSEARCH_REPO_FILE}"; then
-    log_warn "Adding 'gpgkey=${OPENSEARCH_GPG_KEY_URL}' to ${OPENSEARCH_REPO_FILE}."
-    echo "gpgkey=${OPENSEARCH_GPG_KEY_URL}" | sudo tee -a "${OPENSEARCH_REPO_FILE}" > /dev/null
-fi
-if ! grep -q "repo_gpgcheck=" "${OPENSEARCH_REPO_FILE}"; then
-    log_info "Adding 'repo_gpgcheck=0' to ${OPENSEARCH_REPO_FILE}."
-    echo "repo_gpgcheck=0" | sudo tee -a "${OPENSEARCH_REPO_FILE}" > /dev/null
-fi
-
-# 4. Download OpenSearch Dashboards repository file
-log_info "Downloading OpenSearch Dashboards 3.x repository file: ${DASHBOARDS_REPO_FILE}..."
-sudo curl -o "${DASHBOARDS_REPO_FILE}" "${DASHBOARDS_REPO_DOWNLOAD_URL}" || log_error "Failed to download OpenSearch Dashboards repository file."
-
-# Configure Dashboards repository
-if ! grep -q "gpgcheck=1" "${DASHBOARDS_REPO_FILE}"; then
-    log_warn "Adding 'gpgcheck=1' to ${DASHBOARDS_REPO_FILE}."
-    echo "gpgcheck=1" | sudo tee -a "${DASHBOARDS_REPO_FILE}" > /dev/null
-fi
-if ! grep -q "gpgkey=" "${DASHBOARDS_REPO_FILE}"; then
-    log_warn "Adding 'gpgkey=${OPENSEARCH_GPG_KEY_URL}' to ${DASHBOARDS_REPO_FILE}."
-    echo "gpgkey=${OPENSEARCH_GPG_KEY_URL}" | sudo tee -a "${DASHBOARDS_REPO_FILE}" > /dev/null
-fi
-if ! grep -q "repo_gpgcheck=" "${DASHBOARDS_REPO_FILE}"; then
-    log_info "Adding 'repo_gpgcheck=0' to ${DASHBOARDS_REPO_FILE}."
-    echo "repo_gpgcheck=0" | sudo tee -a "${DASHBOARDS_REPO_FILE}" > /dev/null
-fi
-
-# 5. Enable SHA1 for OpenSearch repositories (Rocky Linux 9 workaround)
-log_info "Temporarily enabling SHA1 crypto policy for repository verification..."
-sudo update-crypto-policies --set DEFAULT:SHA1 || log_error "Failed to enable SHA1 crypto policy."
-log_success "SHA1 crypto policy enabled."
-
-# 6. Clean DNF cache
-log_info "Cleaning DNF cache..."
-sudo dnf clean all || log_error "Failed to clean DNF cache."
-sudo dnf makecache -y || log_error "Failed to make DNF cache."
-log_success "DNF cache updated."
-
-# 7. Install OpenSearch and OpenSSL
-log_info "Installing OpenSearch 3.x and OpenSSL..."
-sudo dnf -y install opensearch openssl || log_error "Failed to install OpenSearch or OpenSSL."
-log_success "OpenSearch and OpenSSL installed."
-
-# 8. Install OpenSearch Dashboards
-log_info "Installing OpenSearch Dashboards ${DASHBOARDS_VERSION}..."
-sudo dnf -y install opensearch-dashboards || log_error "Failed to install OpenSearch Dashboards."
-log_success "OpenSearch Dashboards installed."
-
-# 9. Revert SHA1 crypto policy
-log_info "Reverting SHA1 crypto policy to default..."
-sudo update-crypto-policies --set DEFAULT || log_error "Failed to revert crypto policy."
-log_success "Crypto policy reverted to default."
-
-# 10. Set vm.max_map_count
-log_info "Setting vm.max_map_count for OpenSearch..."
-if ! grep -q "vm.max_map_count=262144" /etc/sysctl.d/99-opensearch.conf 2>/dev/null; then
-    echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.d/99-opensearch.conf > /dev/null
-    sudo sysctl --system || log_error "Failed to apply sysctl changes."
-    log_success "vm.max_map_count set to 262144."
-else
-    log_info "vm.max_map_count already configured."
-fi
-
-# 11. Create certificate directory
-log_info "Creating certificate directory: ${CERT_DIR}..."
-sudo mkdir -p "${CERT_DIR}" || log_error "Failed to create directory ${CERT_DIR}."
-
-# 12. Generate Self-Signed SSL Certificates
-log_info "Generating self-signed SSL certificates..."
-
-# Generate CA Key
-sudo openssl genrsa -out "${CA_KEY}" 2048 || log_error "Failed to generate CA key."
-sudo chmod 400 "${CA_KEY}"
-
-# Generate CA Certificate
-sudo openssl req -new -x509 -key "${CA_KEY}" -out "${CA_CERT}" -days 3650 -subj "${CA_DN}" || log_error "Failed to generate CA certificate."
-sudo chmod 444 "${CA_CERT}"
-
-# Create SAN config for Node certificate
-NODE_EXTFILE_TEMP="/tmp/node_san.cnf"
-echo "subjectAltName=${SAN_HOSTS}" | sudo tee "${NODE_EXTFILE_TEMP}" > /dev/null || log_error "Failed to create node SAN file."
-sudo chmod 644 "${NODE_EXTFILE_TEMP}"
-
-# Generate Node Key
-sudo openssl genrsa -out "${NODE_KEY}" 2048 || log_error "Failed to generate node key."
-sudo chmod 400 "${NODE_KEY}"
-
-# Generate Node CSR
-sudo openssl req -new -key "${NODE_KEY}" -out "${CERT_DIR}/node.csr" -subj "${NODE_DN}" || log_error "Failed to generate node CSR."
-
-# Sign Node Certificate
-sudo openssl x509 -req -in "${CERT_DIR}/node.csr" -CA "${CA_CERT}" -CAkey "${CA_KEY}" -CAcreateserial -out "${NODE_CERT}" -days 3650 -extfile "${NODE_EXTFILE_TEMP}" || log_error "Failed to sign node certificate."
-sudo chmod 444 "${NODE_CERT}"
-
-# Generate Admin Key
-sudo openssl genrsa -out "${ADMIN_KEY}" 2048 || log_error "Failed to generate admin key."
-sudo chmod 400 "${ADMIN_KEY}"
-
-# Generate Admin CSR
-sudo openssl req -new -key "${ADMIN_KEY}" -out "${CERT_DIR}/admin.csr" -subj "${ADMIN_DN}" || log_error "Failed to generate admin CSR."
-
-# Sign Admin Certificate
-sudo openssl x509 -req -in "${CERT_DIR}/admin.csr" -CA "${CA_CERT}" -CAkey "${CA_KEY}" -CAcreateserial -out "${ADMIN_CERT}" -days 3650 || log_error "Failed to sign admin certificate."
-sudo chmod 444 "${ADMIN_CERT}"
-
-# Clean up
-sudo rm -f "${CERT_DIR}/node.csr" "${CERT_DIR}/admin.csr" "${NODE_EXTFILE_TEMP}"
-log_success "SSL certificates generated."
-
-# 13. Set certificate permissions for OpenSearch
-log_info "Setting certificate permissions for OpenSearch..."
-sudo chown -R opensearch:opensearch "${CERT_DIR}" || log_error "Failed to chown certificate directory."
-sudo chmod 755 "${CERT_DIR}"
-sudo chmod 640 "${NODE_KEY}" "${ADMIN_KEY}"
-sudo chmod 644 "${NODE_CERT}" "${CA_CERT}" "${ADMIN_CERT}"
-log_success "Certificate permissions set for OpenSearch."
-
-# 14. Set certificate permissions for OpenSearch Dashboards
-log_info "Setting certificate permissions for OpenSearch Dashboards..."
-sudo chown opensearch:opensearch "${NODE_KEY}" "${NODE_CERT}" "${CA_CERT}" || log_error "Failed to set certificate ownership for Dashboards."
-log_success "Certificate permissions set for Dashboards."
-
-## Add opensearch-dashboards user to the opensearch group
-sudo usermod -aG opensearch opensearch-dashboards
-
-sudo chown -R opensearch:opensearch /etc/opensearch/
-sudo chmod -R u+rwX,go-w /etc/opensearch/
-
-# 15. Verify certificate files
-check_cert_files
-
-# 16. Configure OpenSearch
-log_info "Configuring OpenSearch..."
-sudo cp "${OPENSEARCH_CONFIG_FILE}" "${OPENSEARCH_CONFIG_FILE}.bak_$(date +%Y%m%d%H%M%S)"
-
-cat << EOF | sudo tee "${OPENSEARCH_CONFIG_FILE}" > /dev/null
-# Managed by OpenSearch installation script
-
-# Basic Configuration
-cluster.name: opensearch-cluster
-node.name: node-1
-network.host: 0.0.0.0
-
-# Single-node discovery
-discovery.seed_hosts: ["127.0.0.1"]
-cluster.initial_master_nodes: ["node-1"]
-
-# Security Plugin (TLS/SSL)
-plugins.security.ssl.http.enabled: true
-plugins.security.ssl.http.pemcert_filepath: ${NODE_CERT}
-plugins.security.ssl.http.pemkey_filepath: ${NODE_KEY}
-plugins.security.ssl.http.pemtrustedcas_filepath: ${CA_CERT}
-plugins.security.ssl.http.clientauth_mode: OPTIONAL
-
-plugins.security.ssl.transport.enabled: true
-plugins.security.ssl.transport.pemcert_filepath: ${NODE_CERT}
-plugins.security.ssl.transport.pemkey_filepath: ${NODE_KEY}
-plugins.security.ssl.transport.pemtrustedcas_filepath: ${CA_CERT}
-plugins.security.ssl.transport.enforce_hostname_verification: false
-
-plugins.security.allow_default_init_securityindex: true
-
-# Allow REST API access to these roles
-plugins.security.restapi.roles_enabled: ["security_rest_api_access", "all_access"]
-plugins.security.restapi.admin.enabled: true
-
-# Admin DN
-plugins.security.authcz.admin_dn:
-  - C=IN,ST=Karnataka,L=Bengaluru,O=OpenSearchAdmin,CN=admin
-
-# Node DN
-plugins.security.nodes_dn:
-  - CN=node-1,O=OpenSearchNode,L=Bengaluru,ST=Karnataka,C=IN
-
-# Disable system call filter for dev/test
-bootstrap.system_call_filter: false
-EOF
-log_success "OpenSearch configuration updated."
-
-# 17. Configure OpenSearch Dashboards
-log_info "Configuring OpenSearch Dashboards..."
-sudo cp "${DASHBOARDS_CONFIG_FILE}" "${DASHBOARDS_CONFIG_FILE}.bak_$(date +%Y%m%d%H%M%S)"
-
-cat << EOF | sudo tee "${DASHBOARDS_CONFIG_FILE}" > /dev/null
-# Managed by OpenSearch installation script
-
-server.host: 0.0.0.0
-server.port: ${DASHBOARDS_PORT}
-opensearch.hosts: ["https://127.0.0.1:9200"]
-opensearch.ssl.verificationMode: certificate
-opensearch.username: "admin"
-opensearch.password: "${NEW_ADMIN_PASSWORD}"
-opensearch.requestHeadersWhitelist: ["securitytenant","Authorization"]
-
-# SSL Configuration
-server.ssl.enabled: true
-server.ssl.certificate: ${NODE_CERT}
-server.ssl.key: ${NODE_KEY}
-opensearch.ssl.certificateAuthorities: ["${CA_CERT}"]
-
-server.basePath: "/visualization"
-server.rewriteBasePath: true
-
-EOF
-
-# Set permissions
-sudo chown -R opensearch-dashboards:opensearch-dashboards "${DASHBOARDS_CONFIG_FILE}" || log_error "Failed to set permissions for Dashboards config."
-sudo chmod 600 "${DASHBOARDS_CONFIG_FILE}"
-log_success "OpenSearch Dashboards configuration updated."
-
-# 18. Configure OpenSearch systemd service
-log_info "Generating OpenSearch systemd service file..."
-sudo rm -f "${OPENSEARCH_SERVICE_FILE}"
-
-cat << EOF | sudo tee "${OPENSEARCH_SERVICE_FILE}" > /dev/null
-[Unit]
-Description=OpenSearch
-Documentation=https://opensearch.org/
-Wants=network-online.target
-After=network-online.target
-
-[Service]
-Environment="JAVA_HOME=${JAVA_HOME_PATH}"
-Environment="OPENSEARCH_INITIAL_ADMIN_PASSWORD=dummy_password"
-Type=notify
-RuntimeDirectory=opensearch
-EnvironmentFile=-/etc/default/opensearch
-EnvironmentFile=-/etc/sysconfig/opensearch
-
-WorkingDirectory=/usr/share/opensearch
-
-User=opensearch
-Group=opensearch
-
-ExecStartPre=/bin/mkdir -p /dev/shm/performanceanalyzer
-ExecStartPre=/bin/chown opensearch:opensearch /dev/shm/performanceanalyzer
-
-ExecStart=/usr/share/opensearch/bin/systemd-entrypoint -p \${PID_DIR}/opensearch.pid --quiet
-
-StandardOutput=journal
-StandardError=inherit
-SyslogIdentifier=opensearch
-
-LimitNOFILE=65535
-LimitNPROC=4096
-LimitAS=infinity
-LimitFSIZE=infinity
-
-TimeoutStopSec=0
-KillSignal=SIGTERM
-KillMode=process
-SendSIGKILL=no
-SuccessExitStatus=143
-
-TimeoutStartSec=75
-
-ProtectControlGroups=true
-ProtectKernelModules=true
-ProtectKernelTunables=true
-DevicePolicy=closed
-ProtectProc=invisible
-ProtectSystem=full
-LockPersonality=true
-NoNewPrivileges=true
-RestrictSUID SGID=true
-RestrictRealtime=true
-ProtectHostname=true
-ProtectKernelLogs=true
-ProtectClock=true
-
-SystemCallFilter=madvise mincore mlock mlock2 munlock get_mempolicy sched_getaffinity sched_setaffinity fcntl @system-service
-SystemCallFilter=~@reboot ~@swap
-SystemCallErrorNumber=EPERM
-
-CapabilityBoundingSet=~CAP_BLOCK_SUSPEND ~CAP_LEASE ~CAP_SYS_PACCT ~CAP_SYS_TTY_CONFIG ~CAP_SYS_ADMIN ~CAP_SYS_PTRACE ~CAP_NET_ADMIN
-
-RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
-
-ReadWritePaths=/var/log/opensearch
-ReadWritePaths=/var/lib/opensearch
-ReadWritePaths=/dev/shm/
-ReadWritePaths=-/etc/opensearch
-ReadWritePaths=-/mnt/snapshots
-
-ReadOnlyPaths=-/etc/os-release -/usr/lib/os-release -/etc/system-release
-ReadOnlyPaths=/proc/self/mountinfo /proc/diskstats
-ReadOnlyPaths=/proc/self/cgroup /sys/fs/cgroup/cpu /sys/fs/cgroup/cpuacct /sys/fs/cgroup/memory
-
-[Install]
-WantedBy=multi-user.target
-EOF
-
-sudo chmod 644 "${OPENSEARCH_SERVICE_FILE}"
-sudo chown root:root "${OPENSEARCH_SERVICE_FILE}"
-log_success "OpenSearch service configured."
-
-# 19. Configure OpenSearch Dashboards systemd service
-log_info "Configuring OpenSearch Dashboards systemd service..."
-DASHBOARDS_SERVICE_FILE="/usr/lib/systemd/system/${DASHBOARDS_SERVICE}.service"
-
-sudo rm -f "${DASHBOARDS_SERVICE_FILE}"
-
-cat << EOF | sudo tee "${DASHBOARDS_SERVICE_FILE}" > /dev/null
-[Unit]
-Description=OpenSearch Dashboards
-Documentation=https://opensearch.org/docs/latest/
-Wants=network-online.target
-After=network-online.target opensearch.service
-
-[Service]
-Type=simple
-User=opensearch-dashboards
-Group=opensearch-dashboards
-ExecStart=/usr/share/opensearch-dashboards/bin/opensearch-dashboards
-Restart=on-failure
-WorkingDirectory=/usr/share/opensearch-dashboards
-StandardOutput=journal
-StandardError=inherit
-SyslogIdentifier=opensearch-dashboards
-Environment=NODE_ENV=production
-Environment=CONFIG_PATH=${DASHBOARDS_CONFIG_FILE}
-
-LimitNOFILE=65535
-LimitNPROC=4096
-LimitAS=infinity
-LimitFSIZE=infinity
-
-TimeoutStopSec=0
-KillSignal=SIGTERM
-KillMode=process
-SendSIGKILL=no
-
-[Install]
-WantedBy=multi-user.target
-EOF
-
-sudo chmod 644 "${DASHBOARDS_SERVICE_FILE}"
-sudo chown root:root "${DASHBOARDS_SERVICE_FILE}"
-log_success "OpenSearch Dashboards service configured."
-
-# 20. Start OpenSearch
-log_info "Enabling and starting OpenSearch service..."
-sudo systemctl daemon-reload || log_error "Failed to reload systemd daemon."
-sudo systemctl enable "${OPENSEARCH_SERVICE}" || log_error "Failed to enable OpenSearch service."
-sudo systemctl restart "${OPENSEARCH_SERVICE}" || log_error "Failed to start OpenSearch service."
-
-# 21. Wait for OpenSearch
-log_info "Waiting for OpenSearch to start..."
-ATTEMPTS=60
-for i in $(seq 1 $ATTEMPTS); do
-    if curl -s -k -u admin:admin "https://localhost:9200/_plugins/_security/health?pretty" | grep -q "status.*UP"; then
-        log_success "OpenSearch service is up!"
-        break
-    else
-        log_info "Attempt $i/$ATTEMPTS: OpenSearch not available. Waiting 10 seconds..."
-        sleep 10
-    fi
-    if [ $i -eq $ATTEMPTS ]; then
-        log_error "OpenSearch did not start. Check logs."
-    fi
-done
-
-# 22. Initialize Security Plugin
-log_info "Initializing OpenSearch Security Plugin..."
-
-# Apply default security configuration with reload
-if JAVA_HOME="${JAVA_HOME_PATH}" sudo -E /usr/share/opensearch/plugins/opensearch-security/tools/securityadmin.sh \
-    -cd "${SECURITY_CONFIG_DIR}" \
-    -icl -nhnv \
-    -cacert "${CA_CERT}" -cert "${ADMIN_CERT}" -key "${ADMIN_KEY}" \
-    -rl; then
-
-    log_success "Security Plugin initialized."
-
-    log_info "Waiting for security index to stabilize..."
-    sleep 10 # Give it a moment after plugin initialization
-
-    # New steps to update password and roles
-    log_info "Updating admin user password and roles based on manual steps..."
-    # Dynamically generate the password hash, filtering out the warning
-    log_info "Dynamically generating hash for the admin password..."
-    ADMIN_HASH=$(sudo -u opensearch /usr/share/opensearch/plugins/opensearch-security/tools/hash.sh -p "${NEW_ADMIN_PASSWORD}" | grep -o '\$2y\$12\$[a-zA-Z0-9./]*' | tr -d '\n')
-    if [ -z "${ADMIN_HASH}" ]; then
-        log_error "Failed to generate password hash. Ensure OpenSearch is running and paths are correct."
-    fi
-    log_success "Password hash generated."
-
-    # Update internal_users.yml with the newly generated hash
-    log_info "Updating internal_users.yml with new admin password hash..."
-    # Using a more robust sed command with a different delimiter '@'
-    sudo sed -i -E "s@^\s*hash:.*@  hash: \"${ADMIN_HASH}\"@" "${SECURITY_CONFIG_DIR}/internal_users.yml" || log_error "Failed to update admin hash in internal_users.yml."
-    log_success "internal_users.yml updated with new admin hash."
-
-    # Re-run securityadmin.sh to apply the new internal_users.yml with the password change.
-    log_info "Applying the updated security configuration..."
-    if JAVA_HOME="${JAVA_HOME_PATH}" sudo -E /usr/share/opensearch/plugins/opensearch-security/tools/securityadmin.sh \
-        -cd "${SECURITY_CONFIG_DIR}" \
-        -icl -nhnv \
-        -cacert "${CA_CERT}" -cert "${ADMIN_CERT}" -key "${ADMIN_KEY}"; then
-        log_success "Updated security configuration applied successfully."
-    else
-        log_error "Failed to apply updated security configuration."
-    fi
-
-    # 23. Verify admin user roles after changes
-    log_info "Verifying admin user roles after password and configuration update..."
-    sleep 10 # A final delay to ensure changes are live
-    ADMIN_ROLES_VERIFY=$(curl -s -k -u admin:"${NEW_ADMIN_PASSWORD}" "https://localhost:9200/_plugins/_security/api/roles?pretty")
-    # This curl command directly verifies the roles that are available and accessible with the new credentials.
-    if echo "$ADMIN_ROLES_VERIFY" | grep -q "all_access" && echo "$ADMIN_ROLES_VERIFY" | grep -q "security_rest_api_full_access"; then
-        log_success "Admin user can access expected roles with the new password. Validation successful."
-    else
-        log_error "Validation failed. Admin user could not access all expected roles with the new password. Response: $ADMIN_ROLES_VERIFY"
-    fi
-
-else
-    log_error "Failed to initialize Security Plugin. Check logs."
-fi
-
-# 24. Start OpenSearch Dashboards
-log_info "Enabling and starting OpenSearch Dashboards service..."
-sudo systemctl daemon-reload || log_error "Failed to reload systemd daemon."
-sudo systemctl enable "${DASHBOARDS_SERVICE}" || log_error "Failed to enable OpenSearch Dashboards service."
-sudo systemctl restart "${DASHBOARDS_SERVICE}" || log_error "Failed to start OpenSearch Dashboards service."
-
-# 25. Wait for OpenSearch Dashboards
-log_info "Waiting for OpenSearch Dashboards to start..."
-ATTEMPTS=60
-for i in $(seq 1 $ATTEMPTS); do
-    if curl -s -k -u admin:"${NEW_ADMIN_PASSWORD}" "https://localhost:${DASHBOARDS_PORT}/api/status" | grep -q "green"; then
-        log_success "OpenSearch Dashboards is up!"
-        break
-    else
-        log_info "Attempt $i/$ATTEMPTS: Dashboards not available. Waiting 10 seconds..."
-        sleep 10
-    fi
-    if [ $i -eq $ATTEMPTS ]; then
-        log_error "OpenSearch Dashboards did not start. Check logs."
-    fi
-done
-
-# 26. Configure Firewall
-log_info "Configuring firewall for ports 9200, 9600, and ${DASHBOARDS_PORT}..."
-if systemctl is-active --quiet firewalld; then
-    sudo firewall-cmd --add-port=9200/tcp --permanent || log_warn "Failed to add port 9200/tcp."
-    sudo firewall-cmd --add-port=9600/tcp --permanent || log_warn "Failed to add port 9600/tcp."
-    sudo firewall-cmd --add-port=${DASHBOARDS_PORT}/tcp --permanent || log_warn "Failed to add port ${DASHBOARDS_PORT}/tcp."
-    sudo firewall-cmd --reload || log_warn "Failed to reload firewall."
-    log_success "Firewall rules updated."
-else
-    log_warn "Firewalld not running. Configure firewall manually for ports 9200, 9600, and ${DASHBOARDS_PORT}."
-fi
-
-# 27. Final Output
-log_success "OpenSearch 3.x and Dashboards installation complete!"
-log_info "Access OpenSearch at: https://$(hostname -I | awk '{print $1}'):9200 or https://127.0.0.1:9200"
-log_info "Access Dashboards at: https://$(hostname -I | awk '{print $1}'):${DASHBOARDS_PORT} or https://127.0.0.1:${DASHBOARDS_PORT}"
-log_info "Username: admin, Password: ${NEW_ADMIN_PASSWORD}"
-log_warn "Import CA certificate (${CA_CERT}) into your browser's trust store."
-log_warn "For production, use trusted CA certificates and secure passwords."
Index: /branches/amp_4_0/platform/tools/install_elk.sh
===================================================================
--- /branches/amp_4_0/platform/tools/install_elk.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/install_elk.sh	(nonexistent)
@@ -1,534 +0,0 @@
-#!/bin/bash
-set -e
-
-if [[ "$EUID" -ne 0 ]]; then
-  echo "Please run as root"
-  exit 1
-fi
-
-# --- Log File Configuration ---
-LOG_FILE="/var/log/install_elk.log"
-
-# --- Version Configuration ---
-ELASTIC_MAJOR="8.x"
-KIBANA_MAJOR="8.x"
-BEATS_MAJOR="8.x"
-LOGSTASH_MAJOR="8.x"
-FILEBEAT_MAJOR="8.x"
-
-# --- Credentials ---
-ELASTIC_SUPER_USER="elastic"
-ELASTIC_PASSWORD="Arr@y2050"
-
-KIBANA_ELASTIC_USERNAME="kibana_internal"
-KIBANA_ELASTIC_PASSWORD="KArr@y2050"
-ES_DATA_READER_ROLE="data_reader"
-ES_USERNAME_1="admin"
-ES_PASSWORD_1="Array@123"
-METRICBEAT_ELASTIC_USERNAME="metricbeat_internal"
-METRICBEAT_ELASTIC_PASSWORD="MArr@y2050"
-FILEBEAT_ELASTIC_USERNAME="filebeat_internal"
-FILEBEAT_ELASTIC_PASSWORD="FArr@y2050"
-LOGSTASH_ELASTIC_USERNAME="logstash_internal"
-LOGSTASH_ELASTIC_PASSWORD="LArr@y2050"
-
-KIBANA_HOST="http://localhost:5601"
-
-# --- Server IP Configuration ---
-# Get the primary IP address
-SERVER_IP=$(hostname -I | awk '{print $1}')
-if [[ -z "$SERVER_IP" ]]; then
-  echo "Failed to automatically determine server IP address.  Please set SERVER_IP manually." | tee -a "$LOG_FILE"
-  exit 1
-fi
-echo "Detected server IP address: $SERVER_IP" | tee -a "$LOG_FILE"
-
-# --- Logging Helpers ---
-log_info() {
-  echo "[INFO] $(date +'%Y-%m-%d %H:%M:%S') $1" | tee -a "$LOG_FILE"
-}
-
-log_error() {
-  echo "[ERROR] $(date +'%Y-%m-%d %H:%M:%S') $1" >&2 | tee -a "$LOG_FILE"
-}
-
-# --- Command Check ---
-check_command() {
-  if ! command -v "$1" &>/dev/null; then
-    log_error "$1 not found. Please install it."
-    exit 1
-  fi
-}
-
-log_info "Verifying required commands..."
-for cmd in curl sudo rpm tee dnf sed systemctl ip firewall-cmd jq; do check_command "$cmd"; done
-
-# --- Add 8.x Repositories ---
-log_info "Adding Elastic 8.x repositories..."
-sudo rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch
-repo_config=$(cat <<EOF
-[elasticsearch]
-name=Elasticsearch 8.x
-baseurl=https://artifacts.elastic.co/packages/8.x/yum
-gpgcheck=1
-enabled=1
-
-[kibana]
-name=Kibana 8.x
-baseurl=https://artifacts.elastic.co/packages/8.x/yum
-gpgcheck=1
-enabled=1
-
-[beats]
-name=Beats 8.x
-baseurl=https://artifacts.elastic.co/packages/8.x/yum
-gpgcheck=1
-enabled=1
-
-[logstash]
-name=Logstash 8.x
-baseurl=https://artifacts.elastic.co/packages/8.x/yum
-gpgcheck=1
-enabled=1
-EOF
-)
-echo "$repo_config" | sudo tee /etc/yum.repos.d/elastic-8.x.repo
-sudo dnf makecache
-
-# --- Determine Latest 8.x Version ---
-log_info "Determining latest Elasticsearch 8.x version..."
-ELASTIC_VERSION=$(dnf --disablerepo="*" --enablerepo=elasticsearch list --showduplicates elasticsearch \
-  | awk '/^elasticsearch\./ {print $2}' | grep "^${ELASTIC_MAJOR%.*}\\." | sort -V | tail -n1)
-
-if [[ -z "$ELASTIC_VERSION" ]]; then
-  log_error "No Elasticsearch ${ELASTIC_MAJOR} version found in repository."
-  exit 1
-fi
-KIBANA_VERSION="$ELASTIC_VERSION"
-BEATS_VERSION="$ELASTIC_VERSION"
-LOGSTASH_VERSION="$ELASTIC_VERSION"
-FILEBEAT_VERSION="$ELASTIC_VERSION"
-log_info "Using version $ELASTIC_VERSION for all components"
-
-# --- Install Components ---
-log_info "Installing Elasticsearch, Kibana, Metricbeat, Logstash, and Filebeat..."
-sudo dnf install -y \
-  elasticsearch-${ELASTIC_VERSION} \
-  kibana-${KIBANA_VERSION} \
-  metricbeat-${BEATS_VERSION} \
-  logstash-${LOGSTASH_VERSION} \
-  filebeat-${FILEBEAT_VERSION} | tee -a "$LOG_FILE"
-
-# --- Configure Elasticsearch ---
-ES_YML="/etc/elasticsearch/elasticsearch.yml"
-log_info "Backing up and cleaning existing Elasticsearch config..."
-sudo cp "$ES_YML" "${ES_YML}.bak" | tee -a "$LOG_FILE"
-
-# Remove any previous conflicting settings or SSL references
-declare -a REMOVE_KEYS=(
-  '^xpack.security\.'
-  '^cluster.initial_master_nodes'
-  '^discovery.type'
-  '^network.host'
-  'certs/'
-  'ssl\.keystore\.path'
-  'ssl\.truststore\.path'
-  '^path\.data'
-  '^path\.logs'
-)
-
-for pattern in "${REMOVE_KEYS[@]}"; do
-  # Escape forward slashes for sed
-  escaped_pattern=$(printf '%s\n' "$pattern" | sed 's/[\/&]/\\&/g')
-  sudo sed -i "/$escaped_pattern/d" "$ES_YML" | tee -a "$LOG_FILE"
-done
-
-# Overwrite/add Elasticsearch settings
-log_info "Applying required Elasticsearch settings..."
-ES_CONFIG_BLOCK=$(cat <<EOF
-# --- Security and Single-Node Setup ---
-xpack.security.enabled: true
-xpack.security.transport.ssl.enabled: false
-xpack.security.http.ssl.enabled: false
-
-# --- Discovery and Networking ---
-discovery.type: single-node
-network.host: 0.0.0.0
-
-# --- Paths ---
-path.data: /var/lib/elasticsearch
-path.logs: /var/log/elasticsearch
-EOF
-)
-
-# Remove existing lines and re-append
-while IFS= read -r line; do
-  setting=$(echo "$line" | sed -e 's/:.*//')
-  [[ -n "$setting" ]] && sudo sed -i "/^$setting:/d" "$ES_YML" | tee -a "$LOG_FILE"
-done <<< "$ES_CONFIG_BLOCK"
-echo "$ES_CONFIG_BLOCK" | sudo tee -a "$ES_YML"
-
-# Check for duplicates
-log_info "Checking for duplicate keys in elasticsearch.yml..."
-DUPLICATES=$(grep -E '^[a-zA-Z0-9_.-]+:' "$ES_YML" | sed 's/:.*//' | sort | uniq -d)
-if [[ -n "$DUPLICATES" ]]; then
-  log_error "Found duplicate keys:" && echo "$DUPLICATES" | tee -a "$LOG_FILE"
-  exit 1
-else
-  log_info "No duplicate keys found."
-fi
-
-cat "$ES_YML" | tee -a "$LOG_FILE"
-
-# --- Remove SSL entries from keystore ---
-log_info "Removing SSL settings from Elasticsearch keystore if present..."
-KEYSTORE_TOOL="/usr/share/elasticsearch/bin/elasticsearch-keystore"
-for KEY in \
-  xpack.security.http.ssl.keystore.path \
-  xpack.security.http.ssl.truststore.path \
-  xpack.security.http.ssl.keystore.password \
-  xpack.security.http.ssl.truststore.password; do
-  if sudo $KEYSTORE_TOOL list | grep -q "$KEY"; then
-    log_info "Removing $KEY"
-    echo y | sudo $KEYSTORE_TOOL remove "$KEY" | tee -a "$LOG_FILE"
-  fi
-done
-
-log_info "Ensuring Elasticsearch keystore exists..."
-if [ ! -f /etc/elasticsearch/elasticsearch.keystore ]; then
-  sudo $KEYSTORE_TOOL create | tee -a "$LOG_FILE"
-else
-  log_info "Elasticsearch keystore already exists. Skipping creation."
-fi
-
-# --- Start Elasticsearch ---
-log_info "Enabling and starting Elasticsearch..."
-sudo systemctl daemon-reexec | tee -a "$LOG_FILE"
-sudo systemctl enable --now elasticsearch | tee -a "$LOG_FILE"
-
-log_info "Waiting an additional 15 seconds for Elasticsearch security to initialize..."
-sleep 15
-
-# Wait for Elasticsearch HTTP endpoint (accepts 200 or 401 as up)
-log_info "Waiting for Elasticsearch HTTP endpoint to respond (200 or 401)..."
-ELASTICSEARCH_UP=0
-for i in {1..20}; do #wait for 60 seconds
-  if curl --connect-timeout 3 --silent --output /dev/null --write-out "%{http_code}" http://localhost:9200/ | grep -E '^(200|401)$'; then
-    ELASTICSEARCH_UP=1
-    break
-  fi
-  echo -n "."; sleep 3
-done
-echo "" | tee -a "$LOG_FILE"
-
-if [[ $ELASTICSEARCH_UP -eq 0 ]]; then
-    log_error "Elasticsearch failed to start after 60 seconds."
-    exit 1
-fi
-log_info "Elasticsearch is up and running."
-
-# Get initial elastic user password
-INITIAL_PW=$(sudo grep "The generated password for the elastic built-in superuser is" "$LOG_FILE" | awk -F': ' '{print $2}')
-
-if [[ -n "$INITIAL_PW" ]]; then
-  log_info "Setting password for $ELASTIC_SUPER_USER user..."
-  ELASTIC_PW_RESULT=$(curl -s -X POST -u $ELASTIC_SUPER_USER:"$INITIAL_PW" -H 'Content-Type: application/json' \
-    -d "{ \"password\": \"$ELASTIC_PASSWORD\" }" \
-    http://localhost:9200/_security/user/$ELASTIC_SUPER_USER/_password)
-  echo "$ELASTIC_PW_RESULT" | tee -a "$LOG_FILE"
-  echo "$ELASTIC_PW_RESULT" | jq . >/dev/null 2>&1 #check the json
-  if [[ $? -ne 0 ]]; then
-    log_error "Failed to set $ELASTIC_SUPER_USER password.  Output: $ELASTIC_PW_RESULT"
-    exit 1
-  fi
-
-  log_info "Creating metricbeat writer role"
-  METRICBEAT_WRITER_ROLE_RESULT=$(curl -s -X PUT "http://localhost:9200/_security/role/metricbeat_writer" \
-  -H "Content-Type: application/json" \
-  -u "$ELASTIC_SUPER_USER:$ELASTIC_PASSWORD" \
-  -d '{
-    "cluster": [ "monitor", "manage_ilm", "manage_ingest_pipelines", "manage_index_templates"],
-    "indices": [
-      {
-        "names": [ "metricbeat-*" ],
-        "privileges": [ "write", "create_index", "auto_configure", "view_index_metadata"]
-      }
-    ]
-  }')
-  echo "$METRICBEAT_WRITER_ROLE_RESULT" | tee -a "$LOG_FILE"
-  echo "$METRICBEAT_WRITER_ROLE_RESULT" | jq . >/dev/null 2>&1 #check the json
-    if [[ $? -ne 0 ]]; then
-    log_error "Failed to create metricbeat_writer. Output: $METRICBEAT_WRITER_ROLE_RESULT"
-    exit 1
-  fi
-
-  log_info "Creating filebeat writer role"
-  FILEBEAT_WRITER_ROLE_RESULT=$(curl -s -X PUT "http://localhost:9200/_security/role/filebeat_writer" \
-  -H "Content-Type: application/json" \
-  -u "$ELASTIC_SUPER_USER:$ELASTIC_PASSWORD" \
-  -d '{
-    "cluster": [ "monitor", "manage_ilm", "manage_ingest_pipelines", "manage_index_templates"],
-    "indices": [
-      {
-        "names": [ "filebeat-*" ],
-        "privileges": [ "write", "create_index", "auto_configure", "view_index_metadata" ]
-      }
-    ]
-  }')
-  echo "$FILEBEAT_WRITER_ROLE_RESULT" | tee -a "$LOG_FILE"
-  echo "$FILEBEAT_WRITER_ROLE_RESULT" | jq . >/dev/null 2>&1
-    if [[ $? -ne 0 ]]; then
-    log_error "Failed to create filebeat_writer. Output: $FILEBEAT_WRITER_ROLE_RESULT"
-    exit 1
-  fi
-
-  log_info "Creating logstash writer role"
-  LOGSTASH_WRITER_ROLE_RESULT=$(curl -s -X PUT "http://localhost:9200/_security/role/logstash_writer" \
-  -H "Content-Type: application/json" \
-  -u "$ELASTIC_SUPER_USER:$ELASTIC_PASSWORD" \
-  -d '{
-    "cluster": [ "monitor", "manage_ilm", "manage_ingest_pipelines", "manage_index_templates", "manage_pipeline" ],
-    "indices": [
-      {
-        "names": [ "logstash-*", "acm-*" ],
-        "privileges": [ "write", "create_index", "auto_configure", "manage", "view_index_metadata" ]
-      },
-      {
-        "names" : [ ".monitoring-logstash-*" ],
-        "privileges" : [ "create", "write", "auto_configure", "manage", "create_index" ]
-      }
-    ]
-  }')
-  echo "$LOGSTASH_WRITER_ROLE_RESULT" | tee -a "$LOG_FILE"
-  echo "$LOGSTASH_WRITER_ROLE_RESULT" | jq . >/dev/null 2>&1
-    if [[ $? -ne 0 ]]; then
-    log_error "Failed to create filebeat_writer. Output: $LOGSTASH_WRITER_ROLE_RESULT"
-    exit 1
-  fi
-
-  log_info "Creating $KIBANA_ELASTIC_USERNAME user..."
-  KIBANA_PW_RESULT=$(curl -s -X PUT -u $ELASTIC_SUPER_USER:"$ELASTIC_PASSWORD" -H 'Content-Type: application/json' \
-    -d "{ \"password\": \"$KIBANA_ELASTIC_PASSWORD\", \"roles\": [\"kibana_system\"]}" \
-    http://localhost:9200/_security/user/$KIBANA_ELASTIC_USERNAME)
-  echo "$KIBANA_PW_RESULT" | tee -a "$LOG_FILE"
-  echo "$KIBANA_PW_RESULT" | jq . >/dev/null 2>&1
-    if [[ $? -ne 0 ]]; then
-    log_error "Failed to set $KIBANA_ELASTIC_USERNAME password. Output: $KIBANA_PW_RESULT"
-    exit 1
-  fi
-
-  log_info "Creating $LOGSTASH_ELASTIC_USERNAME user..."
-  LOGSTASH_PW_RESULT=$(curl -s -X PUT -u $ELASTIC_SUPER_USER:"$ELASTIC_PASSWORD" -H 'Content-Type: application/json' \
-    -d "{ \"password\": \"$LOGSTASH_ELASTIC_PASSWORD\", \"roles\": [\"logstash_writer\"], \"full_name\": \"Internal Logstash User\" }" \
-    http://localhost:9200/_security/user/$LOGSTASH_ELASTIC_USERNAME)
-  echo "$LOGSTASH_PW_RESULT" | tee -a "$LOG_FILE"
-  echo "$LOGSTASH_PW_RESULT" | jq . >/dev/null 2>&1
-    if [[ $? -ne 0 ]]; then
-    log_error "Failed to set $LOGSTASH_ELASTIC_USERNAME password. Output: $LOGSTASH_PW_RESULT"
-    exit 1
-  fi
-
-  log_info "Creating $FILEBEAT_ELASTIC_USERNAME user..."
-  FILEBEAT_PW_RESULT=$(curl -s -X PUT -u $ELASTIC_SUPER_USER:"$ELASTIC_PASSWORD" -H 'Content-Type: application/json' \
-    -d "{ \"password\": \"$FILEBEAT_ELASTIC_PASSWORD\", \"roles\" : [ \"filebeat_writer\" ], \"full_name\" : \"Internal Filebeat User\"}" \
-    http://localhost:9200/_security/user/$FILEBEAT_ELASTIC_USERNAME)
-  echo "$FILEBEAT_PW_RESULT" | tee -a "$LOG_FILE"
-  echo "$FILEBEAT_PW_RESULT" | jq . >/dev/null 2>&1
-    if [[ $? -ne 0 ]]; then
-    log_error "Failed to create $FILEBEAT_ELASTIC_USERNAME user. Output: $FILEBEAT_PW_RESULT"
-    exit 1
-  fi
-
-  log_info "Creating $METRICBEAT_ELASTIC_USERNAME user..."
-  METRICBEAT_PW_RESULT=$(curl -s -X PUT -u $ELASTIC_SUPER_USER:"$ELASTIC_PASSWORD" -H 'Content-Type: application/json' \
-    -d "{ \"password\": \"$METRICBEAT_ELASTIC_PASSWORD\", \"roles\" : [ \"metricbeat_writer\" ], \"full_name\" : \"Internal Metricbeat User\"}" \
-    http://localhost:9200/_security/user/$METRICBEAT_ELASTIC_USERNAME)
-  echo "$METRICBEAT_PW_RESULT" | tee -a "$LOG_FILE"
-  echo "$METRICBEAT_PW_RESULT" | jq . >/dev/null 2>&1
-    if [[ $? -ne 0 ]]; then
-    log_error "Failed to create $METRICBEAT_ELASTIC_USERNAME user. Output: $METRICBEAT_PW_RESULT"
-    exit 1
-  fi
-
-  # --- Create the custom 'data_reader' role with necessary privileges ---
-  log_info "Creating the '$ES_DATA_READER_ROLE' role with necessary data access privileges..."
-  DATA_READER_ROLE_RESULT=$(curl -s -X POST "http://localhost:9200/_security/role/$ES_DATA_READER_ROLE" \
-    -H "Content-Type: application/json" \
-    -u "$ELASTIC_SUPER_USER:$ELASTIC_PASSWORD" \
-    -d "{
-      \"indices\": [
-        {
-          \"names\": [\"*\"],
-          \"privileges\": [\"read\", \"view_index_metadata\"]
-        }
-      ],
-      \"run_as\": [],
-      \"cluster\": [\"monitor\", \"manage_ilm\", \"manage_ingest_pipelines\", \"manage_index_templates\"]
-    }")
-  echo "$DATA_READER_ROLE_RESULT" | tee -a "$LOG_FILE"
-  echo "$DATA_READER_ROLE_RESULT" | jq . >/dev/null 2>&1
-  if [[ $? -ne 0 ]]; then
-    log_error "Failed to create the '$ES_DATA_READER_ROLE' role. Output: $DATA_READER_ROLE_RESULT"
-    exit 1
-  fi
-  log_info "Successfully created the '$ES_DATA_READER_ROLE' role."
-
-  # --- Create the 'admin' user ---
-  log_info "Creating the '$ES_USERNAME_1' user for Kibana..."
-  ADMIN_USER_RESULT=$(curl -s -X POST "http://localhost:9200/_security/user/$ES_USERNAME_1" \
-    -H "Content-Type: application/json" \
-    -u "$ELASTIC_SUPER_USER:$ELASTIC_PASSWORD" \
-    -d "{
-      \"password\" : \"$ES_PASSWORD_1\",
-      \"roles\" : [ \"kibana_admin\", \"$ES_DATA_READER_ROLE\" ],
-      \"full_name\" : \"Admin User\"
-    }")
-  echo "$ADMIN_USER_RESULT" | tee -a "$LOG_FILE"
-  echo "$ADMIN_USER_RESULT" | jq .  >/dev/null 2>&1
-  if [[ $? -ne 0 ]]; then
-    log_error "Failed to create the '$ES_USERNAME_1' user. Output: $ADMIN_USER_RESULT"
-    exit 1
-  fi
-  log_info "Successfully created the '$ES_USERNAME_1' user."
-else
-  log_error "Failed to extract initial password from logs."
-  exit 1
-fi
-
-# --- Configure Kibana ---
-KB_YML="/etc/kibana/kibana.yml"
-log_info "Backing up and updating Kibana config..."
-sudo cp "$KB_YML" "${KB_YML}.bak" | tee -a "$LOG_FILE"
-
-# use sed to remove
-sudo sed -i '/^server.host/d' "$KB_YML" | tee -a "$LOG_FILE"
-sudo sed -i '/^elasticsearch.hosts/d' "$KB_YML" | tee -a "$LOG_FILE"
-sudo sed -i '/^elasticsearch.username/d' "$KB_YML" | tee -a "$LOG_FILE"
-sudo sed -i '/^elasticsearch.password/d' "$KB_YML" | tee -a "$LOG_FILE"
-# Append to the file.
-echo "server.host: 0.0.0.0" | sudo tee -a "$KB_YML"
-echo "elasticsearch.hosts: ['http://localhost:9200']" | sudo tee -a "$KB_YML"
-echo "elasticsearch.username: '$KIBANA_ELASTIC_USERNAME'" | sudo tee -a "$KB_YML"
-echo "elasticsearch.password: '$KIBANA_ELASTIC_PASSWORD'" | sudo tee -a "$KB_YML"
-
-cat "$KB_YML" | tee -a "$LOG_FILE" # Log the content of the file
-
-# --- Configure Metricbeat ---
-MB_YML="/etc/metricbeat/metricbeat.yml"
-log_info "Backing up and updating Metricbeat config..."
-sudo cp "$MB_YML" "${MB_YML}.bak" | tee -a "$LOG_FILE"
-#use sed to remove
-sudo sed -i '/^output.elasticsearch/d' "$MB_YML" | tee -a "$LOG_FILE"
-sudo sed -i '/^  hosts:/d' "$MB_YML" | tee -a "$LOG_FILE"
-sudo sed -i '/^  username:/d' "$MB_YML" | tee -a "$LOG_FILE"
-sudo sed -i '/^  password:/d' "$MB_YML" | tee -a "$LOG_FILE"
-#append
-echo "output.elasticsearch:" | sudo tee -a "$MB_YML"
-echo "  hosts: ['localhost:9200']" | sudo tee -a "$MB_YML"
-echo "  username: '$METRICBEAT_ELASTIC_USERNAME'" | sudo tee -a "$MB_YML"
-echo "  password: '$METRICBEAT_ELASTIC_PASSWORD'" | sudo tee -a "$MB_YML"
-
-# Add systemd metricset
-echo "metricbeat.modules:" | sudo tee -a "$MB_YML"
-echo "- module: system" | sudo tee -a "$MB_YML"
-echo "  metricsets: [cpu, load, memory, network, process, process_summary, socket_summary, uptime, filesystem, diskio]" | sudo tee -a "$MB_YML"
-echo "  enabled: true" | sudo tee -a "$MB_YML"
-echo "  period: 10s" | sudo tee -a "$MB_YML" # Collect every 10 seconds
-
-cat "$MB_YML" | tee -a "$LOG_FILE" # Log the content of the file
-
-# --- Configure Logstash ---
-LS_YML="/etc/logstash/logstash.yml"
-LS_CONF_DIR="/etc/logstash/conf.d"
-LS_PIPELINE_CONF="$LS_CONF_DIR/logstash.conf"
-
-log_info "Backing up and updating Logstash config..."
-sudo cp "$LS_YML" "${LS_YML}.bak" | tee -a "$LOG_FILE"
-
-# use sed to remove monitoring settings
-sudo sed -i '/^xpack.monitoring.enabled/d' "$LS_YML" | tee -a "$LOG_FILE"
-sudo sed -i '/^xpack.monitoring.elasticsearch.hosts/d' "$LS_YML" | tee -a "$LOG_FILE"
-sudo sed -i '/^xpack.monitoring.elasticsearch.username/d' "$LS_YML" | tee -a "$LOG_FILE"
-sudo sed -i '/^xpack.monitoring.elasticsearch.password/d' "$LS_YML"
-
-# Add the configuration settings
-echo "xpack.monitoring.enabled: true" | sudo tee -a "$LS_YML"
-echo "xpack.monitoring.elasticsearch.hosts: ['http://localhost:9200']" | sudo tee -a "$LS_YML"
-echo "xpack.monitoring.elasticsearch.username: '$LOGSTASH_ELASTIC_USERNAME'" | sudo tee -a "$LS_YML"
-echo "xpack.monitoring.elasticsearch.password: '$LOGSTASH_ELASTIC_PASSWORD'" | sudo tee -a "$LS_YML"
-
-cat "$LS_YML" | tee -a "$LOG_FILE"
-
-# --- Create a basic Logstash pipeline configuration ---
-log_info "Creating a basic Logstash pipeline configuration..."
-LS_PIPELINE_CONFIG=$(cat <<EOF
-input {
-  stdin { }
-}
-output {
-  elasticsearch {
-    hosts => ["http://localhost:9200"]
-    index => "logstash-%{+YYYY.MM.dd}"
-    user => "$LOGSTASH_ELASTIC_USERNAME"
-    password => "$LOGSTASH_ELASTIC_PASSWORD"
-  }
-  stdout { codec => rubydebug }
-}
-EOF
-)
-echo "$LS_PIPELINE_CONFIG" | sudo tee "$LS_PIPELINE_CONF" | tee -a "$LOG_FILE"
-
-# --- Configure Filebeat ---
-FB_YML="/etc/filebeat/filebeat.yml"
-log_info "Backing up and updating Filebeat config..."
-sudo cp "$FB_YML" "${FB_YML}.bak" | tee -a "$LOG_FILE"
-
-# use sed to remove
-sudo sed -i '/^output.elasticsearch/d' "$FB_YML" | tee -a "$LOG_FILE"
-sudo sed -i '/^  hosts:/d' "$FB_YML" | tee -a "$LOG_FILE"
-sudo sed -i '/^  username:/d' "$FB_YML" | tee -a "$LOG_FILE"
-sudo sed -i '/^  password:/d' "$FB_YML" | tee -a "$LOG_FILE"
-
-# Add filebeat config
-echo "output.elasticsearch:" | sudo tee -a "$FB_YML"
-echo "  hosts: ['localhost:9200']" | sudo tee -a "$FB_YML"
-echo "  username: '$FILEBEAT_ELASTIC_USERNAME'" | sudo tee -a "$FB_YML"
-echo "  password: '$FILEBEAT_ELASTIC_PASSWORD'" | sudo tee -a "$FB_YML"
-
-cat "$FB_YML" | tee -a "$LOG_FILE"
-
-# --- Open Firewall for Kibana, Logstash, and Filebeat ---
-if systemctl is-active --quiet firewalld; then
-  log_info "Opening firewall for Kibana (port 5601), Logstash (port 9600), and Filebeat (port 5601)..."
-  sudo firewall-cmd --add-port={5601,9200,5044}/tcp --permanent | tee -a "$LOG_FILE"
-  sudo firewall-cmd --reload | tee -a "$LOG_FILE"
-  sudo firewall-cmd --permanent --zone=public \
-  --add-masquerade \
-  --add-port=514/udp \
-  --add-port=5514/udp \
-  --add-port=514/tcp \
-  --add-port=5514/tcp \
-  --add-forward-port=port=514:proto=udp:toport=5514 \
-  --add-forward-port=port=514:proto=tcp:toport=5514  | tee -a "$LOG_FILE"
-  sudo firewall-cmd --reload  | tee -a "$LOG_FILE"
-fi
-
-# --- Start Elasticsearch, Kibana, Metricbeat, Logstash, and Filebeat ---
-log_info "Enabling and starting Elasticsearch, Kibana, Metricbeat, Logstash, and Filebeat..."
-sudo systemctl enable --now elasticsearch kibana metricbeat logstash filebeat | tee -a "$LOG_FILE"
-
-# --- Final Verification ---
-log_info "Verifying services..."
-for svc in elasticsearch kibana metricbeat logstash filebeat; do
-  if systemctl is-active --quiet "$svc"; then
-    log_info "$svc is running."
-  else
-    log_error "$svc failed to start. See logs: journalctl -u $svc"
-  fi
-done
-
-log_info "✅ ELK Stack 8.x single-node setup complete."
-log_info "Installation logs are available in $LOG_FILE."
-log_info "Access Kibana at http://${SERVER_IP}:5601 (admin / ${ES_PASSWORD_1})"
-
-exit 0
Index: /branches/amp_4_0/platform/tools/install_fluentbit_dataprepper.sh
===================================================================
--- /branches/amp_4_0/platform/tools/install_fluentbit_dataprepper.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/install_fluentbit_dataprepper.sh	(nonexistent)
@@ -1,566 +0,0 @@
-#!/bin/bash
-
-# Script to install and configure Data Prepper, Fluent Bit, and rsyslog for OpenSearch.
-# Assumes OpenSearch and OpenSearch Dashboards are already installed and configured
-# (including SSL certificates and the 'admin' user with password) from a previous script run.
-
-# --- Configuration ---
-OPENSEARCH_HOST="localhost"
-OPENSEARCH_PORT=9200
-NEW_ADMIN_PASSWORD="Arr@y2050" # OpenSearch admin password
-CERT_DIR="/etc/opensearch/certs"
-CA_CERT="${CERT_DIR}/root-ca.pem"
-NODE_KEY="${CERT_DIR}/node-key.pem"
-NODE_CERT="${CERT_DIR}/node.pem"
-ADMIN_KEY="${CERT_DIR}/admin-key.pem"
-ADMIN_CERT="${CERT_DIR}/admin.pem"
-KEYSTORE="${CERT_DIR}/dataprepper-keystore.p12" # PKCS12 keystore
-JAVA_HOME_PATH="/usr/lib/jvm/java-21-openjdk-21.0.7.0.6-1.el9.x86_64" # JDK 21
-INDEX_PATTERN="acm-*"
-
-# Data Prepper Configuration
-DATA_PREPPER_INSTALL_DIR="/usr/share/data-prepper"
-DATA_PREPPER_CONFIG_DIR="${DATA_PREPPER_INSTALL_DIR}/config"
-DATA_PREPPER_PIPELINES_DIR="${DATA_PREPPER_INSTALL_DIR}/pipelines"
-DATA_PREPPER_LOG_DIR="/var/log/data-prepper"
-DATA_PREPPER_SERVICE="data-prepper"
-DATA_PREPPER_SERVICE_FILE="/usr/lib/systemd/system/${DATA_PREPPER_SERVICE}.service"
-DATA_PREPPER_USER="data-prepper"
-DATA_PREPPER_GROUP="data-prepper"
-DATA_PREPPER_HTTP_PORT=2021
-DATA_PREPPER_SOURCE_USER="data_prepper_user"
-DATA_PREPPER_SOURCE_PASSWORD="data_prepper_password"
-
-# Fluent Bit Configuration
-FLUENT_BIT_CONFIG_DIR="/etc/fluent-bit"
-FLUENT_BIT_LOG_DIR="/var/log/fluent-bit"
-FLUENT_BIT_SERVICE="fluent-bit"
-SYSLOG_TCP_PORT=514
-SYSLOG_UDP_PORT=514
-
-# rsyslog Configuration
-RSYSLOG_CONFIG_DIR="/etc/rsyslog.d"
-RSYSLOG_SERVICE="rsyslog"
-
-# --- Functions ---
-
-log_info() { echo -e "\n\033[0;34m[INFO]\033[0m $1"; }
-log_success() { echo -e "\n\033[0;32m[SUCCESS]\033[0m $1"; }
-log_warn() { echo -e "\n\033[0;33m[WARNING]\033[0m $1"; }
-log_error() { echo -e "\n\033[0;31m[ERROR]\033[0m $1"; exit 1; }
-
-create_data_prepper_user() {
-    log_info "Creating Data Prepper user and group..."
-    if ! id "${DATA_PREPPER_USER}" &>/dev/null; then
-        sudo groupadd --system "${DATA_PREPPER_GROUP}" || log_error "Failed to create group ${DATA_PREPPER_GROUP}."
-        sudo useradd --system -g "${DATA_PREPPER_GROUP}" -s /sbin/nologin -d "${DATA_PREPPER_INSTALL_DIR}" "${DATA_PREPPER_USER}" || log_error "Failed to create user ${DATA_PREPPER_USER}."
-        log_success "Data Prepper user '${DATA_PREPPER_USER}' and group '${DATA_PREPPER_GROUP}' created."
-    else
-        log_info "Data Prepper user '${DATA_PREPPER_USER}' already exists."
-    fi
-}
-
-convert_pem_to_keystore() {
-    log_info "Converting PEM certificates to PKCS12 keystore..."
-    if [ ! -f "${KEYSTORE}" ]; then
-        sudo openssl pkcs12 -export -in "${NODE_CERT}" -inkey "${NODE_KEY}" -out "${KEYSTORE}" -name dataprepper -passout pass: || log_error "Failed to create PKCS12 keystore."
-        sudo chown ${DATA_PREPPER_USER}:${DATA_PREPPER_GROUP} "${KEYSTORE}"
-        sudo chmod 640 "${KEYSTORE}"
-        log_success "PKCS12 keystore created at ${KEYSTORE}."
-    else
-        log_info "PKCS12 keystore already exists at ${KEYSTORE}."
-    fi
-}
-
-configure_data_prepper_server_config() {
-    log_info "Configuring Data Prepper server settings..."
-    sudo mkdir -p "${DATA_PREPPER_CONFIG_DIR}" "${DATA_PREPPER_LOG_DIR}" || log_error "Failed to create Data Prepper directories."
-    sudo chown -R ${DATA_PREPPER_USER}:${DATA_PREPPER_GROUP} "${DATA_PREPPER_CONFIG_DIR}" "${DATA_PREPPER_LOG_DIR}"
-    sudo chmod 750 "${DATA_PREPPER_LOG_DIR}"
-    sudo usermod -aG opensearch ${DATA_PREPPER_USER}
-    cat << EOF | sudo tee "${DATA_PREPPER_CONFIG_DIR}/data-prepper-config.yaml" > /dev/null
-ssl: true
-key_store_file_path: "${KEYSTORE}"
-key_store_password: ""
-private_key_password: ""
-server_port: 4900
-authentication:
-  http_basic:
-    username: "data_prepper_server_user"
-    password: "data_prepper_server_password"
-EOF
-    sudo chown ${DATA_PREPPER_USER}:${DATA_PREPPER_GROUP} "${DATA_PREPPER_CONFIG_DIR}/data-prepper-config.yaml"
-    sudo chmod 640 "${DATA_PREPPER_CONFIG_DIR}/data-prepper-config.yaml"
-    log_success "Data Prepper server configuration created."
-}
-
-configure_data_prepper_pipeline() {
-    log_info "Creating Data Prepper pipeline configuration..."
-    sudo mkdir -p "${DATA_PREPPER_PIPELINES_DIR}" || log_error "Failed to create ${DATA_PREPPER_PIPELINES_DIR}."
-    sudo chown -R ${DATA_PREPPER_USER}:${DATA_PREPPER_GROUP} "${DATA_PREPPER_PIPELINES_DIR}"
-    cat << EOF | sudo tee "${DATA_PREPPER_PIPELINES_DIR}/opensearch-pipeline.yaml" > /dev/null
-version: "2"
-opensearch-pipeline:
-  workers: 2
-  delay: 100
-  source:
-    http:
-      ssl: true
-      ssl_certificate_file: "${NODE_CERT}"
-      ssl_key_file: "${NODE_KEY}"
-      port: ${DATA_PREPPER_HTTP_PORT}
-      path: /
-  processor:
-    - date:
-        from_time_received: true
-  sink:
-    - opensearch:
-        hosts: ["https://localhost:9200"]
-        insecure: true
-        cert: "${CA_CERT}"
-        username: "${DATA_PREPPER_SOURCE_USER}"
-        password: "${DATA_PREPPER_SOURCE_PASSWORD}"
-        index: "acm-%{yyyy.MM.dd}"
-EOF
-    sudo chown ${DATA_PREPPER_USER}:${DATA_PREPPER_GROUP} "${DATA_PREPPER_PIPELINES_DIR}/opensearch-pipeline.yaml"
-    sudo chmod 640 "${DATA_PREPPER_PIPELINES_DIR}/opensearch-pipeline.yaml"
-    log_success "Data Prepper pipeline configuration created."
-}
-
-configure_data_prepper_service() {
-    log_info "Configuring Data Prepper systemd service..."
-    sudo rm -f "${DATA_PREPPER_SERVICE_FILE}"
-    cat << EOF | sudo tee "${DATA_PREPPER_SERVICE_FILE}" > /dev/null
-[Unit]
-Description=OpenSearch Data Prepper
-Documentation=https://opensearch.org/docs/latest/data-prepper/
-Wants=network-online.target
-After=network-online.target opensearch.service
-
-[Service]
-Environment="JAVA_HOME=${JAVA_HOME_PATH}"
-Type=simple
-User=${DATA_PREPPER_USER}
-Group=${DATA_PREPPER_GROUP}
-WorkingDirectory=${DATA_PREPPER_INSTALL_DIR}
-ExecStart=${DATA_PREPPER_INSTALL_DIR}/bin/data-prepper
-Restart=on-failure
-StandardOutput=append:${DATA_PREPPER_LOG_DIR}/data-prepper-service.log
-StandardError=append:${DATA_PREPPER_LOG_DIR}/data-prepper-service.log
-SyslogIdentifier=data-prepper
-LimitNOFILE=65535
-LimitNPROC=4096
-LimitAS=infinity
-LimitFSIZE=infinity
-TimeoutStopSec=0
-KillSignal=SIGTERM
-KillMode=process
-SendSIGKILL=no
-
-[Install]
-WantedBy=multi-user.target
-EOF
-    sudo chmod 644 "${DATA_PREPPER_SERVICE_FILE}"
-    sudo chown root:root "${DATA_PREPPER_SERVICE_FILE}"
-    log_success "Data Prepper service configured."
-}
-
-configure_rsyslog() {
-    [ "$EUID" -eq 0 ] || { log_error "Must run as root."; return 1; }
-    : "${RSYSLOG_CONFIG_DIR:=/etc/rsyslog.d}"
-    : "${SYSLOG_UDP_PORT:=514}"
-    : "${SYSLOG_TCP_PORT:=514}"
-    : "${RSYSLOG_SERVICE:=rsyslog}"
-    log_info "Configuring rsyslog to forward to Fluent Bit..."
-    sudo mkdir -p "${RSYSLOG_CONFIG_DIR}" || { log_error "Failed to create rsyslog config directory."; return 1; }
-    cat << EOF | sudo tee "${RSYSLOG_CONFIG_DIR}/fluent-bit.conf" > /dev/null || { log_error "Failed to write config."; return 1; }
-module(load="omfwd")
-*.* @127.0.0.1:${SYSLOG_UDP_PORT}
-*.* @@127.0.0.1:${SYSLOG_TCP_PORT}
-EOF
-    sudo chmod 644 "${RSYSLOG_CONFIG_DIR}/fluent-bit.conf" || { log_error "Failed to set permissions."; return 1; }
-    sudo cp /etc/rsyslog.conf /etc/rsyslog.conf.bak || log_error "Failed to backup rsyslog.conf."
-    sudo sed -i '/module(load="imudp")/s/^/#/' /etc/rsyslog.conf || { log_error "Failed to disable imudp module."; return 1; }
-    sudo sed -i '/module(load="imtcp")/s/^/#/' /etc/rsyslog.conf || { log_error "Failed to disable imtcp module."; return 1; }
-    sudo sed -i '/input(type="imudp" port="514")/s/^/#/' /etc/rsyslog.conf || { log_error "Failed to disable imudp input."; return 1; }
-    sudo sed -i '/input(type="imtcp" port="514")/s/^/#/' /etc/rsyslog.conf || { log_error "Failed to disable imtcp input."; return 1; }
-    sudo systemctl is-active --quiet "${RSYSLOG_SERVICE}" || { log_error "rsyslog service not running."; return 1; }
-    sudo systemctl restart "${RSYSLOG_SERVICE}" || { log_error "Failed to restart rsyslog service."; return 1; }
-    log_info "Ensuring firewall ports are open..."
-    if systemctl is-active --quiet firewalld; then
-        sudo firewall-cmd --add-port=514/tcp --permanent || log_error "Failed to open TCP 514."
-        sudo firewall-cmd --add-port=514/udp --permanent || log_error "Failed to open UDP 514."
-        sudo firewall-cmd --reload || log_error "Failed to reload firewall."
-    else
-        log_warn "Firewalld not running. Ensure ports 514 TCP/UDP are open."
-    fi
-    log_info "Testing rsyslog to Fluent Bit connectivity..."
-    sudo systemctl start fluent-bit || { log_error "Failed to start Fluent Bit service."; return 1; }
-    sudo systemctl is-active --quiet fluent-bit || { log_error "Fluent Bit service not running."; return 1; }
-    if ! command -v nc >/dev/null 2>&1; then
-        log_error "netcat (nc) not installed. Install with 'dnf install nc' and rerun."
-        return 1
-    fi
-    echo "Test rsyslog message" | logger -t "test_rsyslog"
-    sleep 2
-    for attempt in {1..3}; do
-        if timeout 2 nc -zv 127.0.0.1 ${SYSLOG_TCP_PORT} &>/dev/null && (echo -n "test" | timeout 2 nc -u -w 1 127.0.0.1 ${SYSLOG_UDP_PORT} &>/dev/null); then
-            log_success "rsyslog can connect to Fluent Bit on TCP/UDP 514."
-            break
-        elif [ $attempt -eq 3 ]; then
-            log_error "rsyslog cannot connect to Fluent Bit after $attempt attempts. Check firewall, SELinux, or Fluent Bit service."
-            return 1
-        fi
-        log_info "Attempt $attempt failed. Retrying in 2 seconds..."
-        sleep 2
-    done
-    log_success "rsyslog configured to forward to Fluent Bit."
-}
-
-create_index_pattern() {
-    log_info "Creating index pattern '${INDEX_PATTERN}' in OpenSearch..."
-    local INDEX_PATTERN_JSON=$(cat <<EOT
-{
-  "attributes": {
-    "title": "${INDEX_PATTERN}",
-    "timeFieldName": "@timestamp"
-  }
-}
-EOT
-)
-    local RESPONSE=$(curl -s -k -u admin:"${NEW_ADMIN_PASSWORD}" -X POST "https://${OPENSEARCH_HOST}:5601/api/saved_objects/index-pattern" -H "Content-Type: application/json" -H "osd-xsrf: true" -d "${INDEX_PATTERN_JSON}")
-    if echo "$RESPONSE" | grep -q '"id"'; then
-        log_success "Index pattern '${INDEX_PATTERN}' created."
-    else
-        log_error "Failed to create index pattern. Response: $RESPONSE"
-    fi
-}
-
-ensure_index_exists() {
-    local INDEX_NAME="acm-$(date +%Y.%m.%d)"
-    log_info "Ensuring index '${INDEX_NAME}' exists..."
-    local INDEX_RESPONSE=$(curl -s -k -u admin:"${NEW_ADMIN_PASSWORD}" -X PUT "https://${OPENSEARCH_HOST}:${OPENSEARCH_PORT}/${INDEX_NAME}" -H "Content-Type: application/json")
-
-    if echo "$INDEX_RESPONSE" | grep -q '"acknowledged":true'; then
-        log_success "Index '${INDEX_NAME}' created or already exists."
-    elif echo "$INDEX_RESPONSE" | grep -q 'resource_already_exists_exception'; then
-        log_info "Index '${INDEX_NAME}' already exists. Skipping creation."
-    else
-        log_error "Failed to create index. Response: $INDEX_RESPONSE"
-    fi
-}
-
-install_fluent_bit() {
-    log_info "Installing Fluent Bit..."
-    cat <<EOF | sudo tee /etc/yum.repos.d/fluent-bit.repo
-[fluent-bit]
-name=Fluent Bit
-baseurl=https://packages.fluentbit.io/centos/9/$basearch/
-gpgcheck=1
-gpgkey=https://packages.fluentbit.io/fluentbit.key
-enabled=1
-EOF
-    sudo dnf -y install fluent-bit-3.2.* || log_error "Failed to install Fluent Bit."
-    log_info "Relocating Fluent Bit to /usr/share/fluent-bit..."
-    sudo mkdir -p /usr/share/fluent-bit
-    sudo mv /opt/fluent-bit/* /usr/share/fluent-bit/ || log_error "Failed to relocate Fluent Bit."
-    sudo rmdir /opt/fluent-bit || log_warn "Failed to remove /opt/fluent-bit directory."
-    sudo sed -i 's|/opt/fluent-bit/bin/fluent-bit|/usr/share/fluent-bit/bin/fluent-bit|' /usr/lib/systemd/system/fluent-bit.service || log_error "Failed to update Fluent Bit service file."
-    log_success "Fluent Bit installed and relocated."
-}
-
-configure_fluent_bit() {
-    log_info "Configuring Fluent Bit..."
-    sudo mkdir -p "${FLUENT_BIT_CONFIG_DIR}" "${FLUENT_BIT_LOG_DIR}" || log_error "Failed to create Fluent Bit config directory."
-    sudo chown -R fluent-bit:fluent-bit "${FLUENT_BIT_CONFIG_DIR}" "${FLUENT_BIT_LOG_DIR}"
-    sudo chmod 750 "${FLUENT_BIT_LOG_DIR}"
-    cat <<EOF | sudo tee "${FLUENT_BIT_CONFIG_DIR}/fluent-bit.conf" > /dev/null
-[SERVICE]
-    flush        1
-    log_level    debug
-    log_file     ${FLUENT_BIT_LOG_DIR}/fluent-bit.log
-    daemon       off
-    parsers_file  parsers.conf
-    http_server  on
-    http_listen  0.0.0.0
-    http_port    2020
-
-[INPUT]
-    Name         syslog
-    Mode         tcp
-    Port         ${SYSLOG_TCP_PORT}
-    Parser       syslog-rfc5424
-    Listen       0.0.0.0
-
-[INPUT]
-    Name         syslog
-    Mode         udp
-    Port         ${SYSLOG_UDP_PORT}
-    Parser       syslog-rfc5424
-    Listen       0.0.0.0
-
-[OUTPUT]
-    Name          http
-    Match         *
-    Host          localhost
-    Port          ${DATA_PREPPER_HTTP_PORT}
-    URI           /
-    Format        json
-    tls           On
-    tls.verify    Off
-    tls.ca_file   ${CA_CERT}
-    header        Content-Type application/json
-    http_user     ${DATA_PREPPER_SOURCE_USER}
-    http_passwd   ${DATA_PREPPER_SOURCE_PASSWORD}
-EOF
-    cat <<EOF | sudo tee "${FLUENT_BIT_CONFIG_DIR}/parsers.conf" > /dev/null
-[PARSER]
-    Name        syslog-rfc5424
-    Format      regex
-    Regex       /^\<(?<pri>[0-9]+)\>(?<time>[^ ]+ [^ ]+ [^ ]+) (?<host>[^ ]+) (?<ident>[a-zA-Z0-9_\/\.\-]+)(?:\[(?<pid>[0-9]+)\])?\: (?<message>.*)$/
-    Time_Key    time
-    Time_Format %b %d %H:%M:%S
-    Time_Keep   On
-EOF
-    sudo chown -R fluent-bit:fluent-bit "${FLUENT_BIT_CONFIG_DIR}" "${FLUENT_BIT_LOG_DIR}"
-    sudo chmod 640 "${FLUENT_BIT_CONFIG_DIR}/fluent-bit.conf" "${FLUENT_BIT_CONFIG_DIR}/parsers.conf"
-    log_success "Fluent Bit configured."
-}
-
-configure_fluent_bit_service() {
-    log_info "Configuring Fluent Bit systemd service..."
-    sudo rm -f /usr/lib/systemd/system/fluent-bit.service
-    cat <<EOF | sudo tee /usr/lib/systemd/system/fluent-bit.service > /dev/null
-[Unit]
-Description=Fluent Bit - Lightweight Data Collector and Forwarder
-Documentation=https://fluentbit.io/
-After=network.target
-
-[Service]
-ExecStart=/usr/share/fluent-bit/bin/fluent-bit -c ${FLUENT_BIT_CONFIG_DIR}/fluent-bit.conf
-Restart=on-failure
-StandardOutput=append:${FLUENT_BIT_LOG_DIR}/fluent-bit-service.log
-StandardError=append:${FLUENT_BIT_LOG_DIR}/fluent-bit-service.log
-
-[Install]
-WantedBy=multi-user.target
-EOF
-    sudo systemctl daemon-reload
-    sudo systemctl enable fluent-bit
-    log_success "Fluent Bit service configured."
-}
-
-create_opensearch_data_prepper_security_user() {
-    ALL_ACCESS_ADMIN_ROLE_NAME="all_access"
-    log_info "Attempting to re-map admin certificate user to '${ALL_ACCESS_ADMIN_ROLE_NAME}' security role."
-    MAPPING_RESPONSE=$(curl -s -k --cert "${ADMIN_CERT}" --key "${ADMIN_KEY}" -X PUT "https://${OPENSEARCH_HOST}:${OPENSEARCH_PORT}/_plugins/_security/api/rolesmapping/${ALL_ACCESS_ADMIN_ROLE_NAME}" -H 'Content-Type: application/json' -d "{\"users\": [\"${ADMIN_CERT_SUBJECT}\"], \"backend_roles\": [\"${ALL_ACCESS_ADMIN_ROLE_NAME}\"]}")
-    if echo "$MAPPING_RESPONSE" | grep -q "\"status\":\"OK\"" || echo "$MAPPING_RESPONSE" | grep -q "\"status\":\"CREATED\""; then
-        log_success "Successfully re-mapped admin certificate user to '${ALL_ACCESS_ADMIN_ROLE_NAME}' role."
-    else
-        log_error "Failed to re-map admin certificate user. Response: $MAPPING_RESPONSE"
-    fi
-    sleep 5
-
-    log_info "Creating/updating Data Prepper user in OpenSearch..."
-    local DATA_PREPPER_ROLES_JSON=$(cat <<EOT
-{
-  "cluster_permissions": [
-    "cluster_composite_ops_ro",
-    "cluster_monitor",
-    "cluster_manage_pipeline"
-  ],
-  "index_permissions": [
-    {
-      "index_patterns": ["acm-*"],
-      "allowed_actions": [
-        "indices:data/write/bulk",
-        "indices:data/write/bulk[s]",
-        "indices:data/write/index",
-        "indices:data/read/search",
-        "indices:admin/create",
-        "indices:admin/mappings/get",
-        "indices:admin/mapping/put",
-        "indices:admin/template/*",
-        "indices:admin/ilm/put",
-        "indices:admin/get",
-        "indices:monitor/settings/get",
-        "indices:monitor/stats"
-      ]
-    },
-    {
-      "index_patterns": ["*"],
-      "allowed_actions": ["indices:admin/aliases/get"]
-    }
-  ]
-}
-EOT
-)
-    local DATA_PREPPER_ROLES_NAME="data_prepper_writer"
-    log_info "Creating/updating OpenSearch security role '${DATA_PREPPER_ROLES_NAME}'..."
-    local ROLE_RESPONSE=$(curl -s -k -X PUT "https://${OPENSEARCH_HOST}:${OPENSEARCH_PORT}/_plugins/_security/api/roles/${DATA_PREPPER_ROLES_NAME}" --cert "${ADMIN_CERT}" --key "${ADMIN_KEY}" -H "Content-Type: application/json" -d "${DATA_PREPPER_ROLES_JSON}")
-    if echo "$ROLE_RESPONSE" | grep -q "\"status\":\"OK\"" || echo "$ROLE_RESPONSE" | grep -q "\"status\":\"CREATED\""; then
-        log_success "OpenSearch security role '${DATA_PREPPER_ROLES_NAME}' created/updated."
-    else
-        log_error "Failed to create/update role '${DATA_PREPPER_ROLES_NAME}'. Response: $ROLE_RESPONSE"
-    fi
-    sleep 2
-    log_info "Creating/updating OpenSearch internal user '${DATA_PREPPER_SOURCE_USER}'..."
-    local USER_RESPONSE=$(curl -s -k -X PUT "https://${OPENSEARCH_HOST}:${OPENSEARCH_PORT}/_plugins/_security/api/internalusers/${DATA_PREPPER_SOURCE_USER}" --cert "${ADMIN_CERT}" --key "${ADMIN_KEY}" -H "Content-Type: application/json" -d "{\"password\":\"${DATA_PREPPER_SOURCE_PASSWORD}\",\"backend_roles\":[\"${DATA_PREPPER_ROLES_NAME}\"]}")
-    if echo "$USER_RESPONSE" | grep -q "\"status\":\"OK\"" || echo "$USER_RESPONSE" | grep -q "\"status\":\"CREATED\""; then
-        log_success "OpenSearch internal user '${DATA_PREPPER_SOURCE_USER}' created/updated."
-    else
-        log_error "Failed to create/update user '${DATA_PREPPER_SOURCE_USER}'. Response: $USER_RESPONSE"
-    fi
-    sleep 2
-    log_info "Mapping backend role '${DATA_PREPPER_ROLES_NAME}' to security role..."
-    local MAPPING_RESPONSE=$(curl -s -k --cert "${ADMIN_CERT}" --key "${ADMIN_KEY}" -X PUT "https://${OPENSEARCH_HOST}:${OPENSEARCH_PORT}/_plugins/_security/api/rolesmapping/${DATA_PREPPER_ROLES_NAME}" -H 'Content-Type: application/json' -d "{\"backend_roles\": [\"${DATA_PREPPER_ROLES_NAME}\"]}")
-    if echo "$MAPPING_RESPONSE" | grep -q "\"status\":\"OK\"" || echo "$MAPPING_RESPONSE" | grep -q "\"status\":\"CREATED\""; then
-        log_success "Successfully mapped backend role '${DATA_PREPPER_ROLES_NAME}'."
-    else
-        log_warn "Failed to map backend role '${DATA_PREPPER_ROLES_NAME}'. Response: $MAPPING_RESPONSE"
-    fi
-    sleep 5
-}
-
-wait_for_opensearch_health() {
-    log_info "Waiting for OpenSearch to be healthy..."
-    ATTEMPTS=20
-    for i in $(seq 1 $ATTEMPTS); do
-        if curl -s -k -u admin:"${NEW_ADMIN_PASSWORD}" "https://${OPENSEARCH_HOST}:${OPENSEARCH_PORT}/_plugins/_security/health?pretty" | grep -q "status.*UP"; then
-            log_success "OpenSearch is up and healthy."
-            break
-        else
-            log_info "Attempt $i/$ATTEMPTS: OpenSearch not ready. Waiting 5 seconds..."
-            sleep 5
-        fi
-        if [ $i -eq $ATTEMPTS ]; then
-            log_error "OpenSearch did not become healthy."
-        fi
-    done
-}
-
-create_fluent_bit_user() {
-    log_info "Creating Fluent Bit user and group..."
-    if ! id "fluent-bit" &>/dev/null; then
-        sudo groupadd --system fluent-bit || log_error "Failed to create group fluent-bit."
-        sudo useradd --system -g fluent-bit -s /sbin/nologin -d /usr/share/fluent-bit fluent-bit || log_error "Failed to create user fluent-bit."
-        log_success "Fluent Bit user and group created."
-    else
-        log_info "Fluent Bit user already exists."
-    fi
-}
-
-# --- Main Script ---
-
-log_info "Starting installation and configuration of Data Prepper, Fluent Bit, and rsyslog..."
-
-if [[ $EUID -ne 0 ]]; then
-   log_error "This script must be run as root."
-fi
-
-sudo dnf -y install nc net-tools
-wait_for_opensearch_health
-create_opensearch_data_prepper_security_user
-create_data_prepper_user
-convert_pem_to_keystore
-
-log_info "Beginning Data Prepper installation..."
-DATA_PREPPER_DOWNLOAD_PATH="/tmp/data-prepper.tar.gz"
-DATA_PREPPER_VERSION="2.11.0"
-DATA_PREPPER_DOWNLOAD_URL="https://artifacts.opensearch.org/data-prepper/${DATA_PREPPER_VERSION}/opensearch-data-prepper-jdk-${DATA_PREPPER_VERSION}-linux-x64.tar.gz"
-log_info "Downloading Data Prepper from ${DATA_PREPPER_DOWNLOAD_URL}..."
-if ! sudo curl -L --fail --silent --show-error -o "${DATA_PREPPER_DOWNLOAD_PATH}" "${DATA_PREPPER_DOWNLOAD_URL}"; then
-    log_error "Failed to download Data Prepper tarball."
-fi
-if [ ! -s "${DATA_PREPPER_DOWNLOAD_PATH}" ]; then
-    log_error "Downloaded Data Prepper tarball is empty or corrupted."
-fi
-log_success "Data Prepper tarball downloaded."
-log_info "Extracting Data Prepper to ${DATA_PREPPER_INSTALL_DIR}..."
-sudo mkdir -p "${DATA_PREPPER_INSTALL_DIR}" || log_error "Failed to create Data Prepper install directory."
-if ! sudo tar -xzf "${DATA_PREPPER_DOWNLOAD_PATH}" -C "${DATA_PREPPER_INSTALL_DIR}" --strip-components=1; then
-    log_error "Failed to extract Data Prepper tarball."
-fi
-sudo rm -f "${DATA_PREPPER_DOWNLOAD_PATH}"
-log_success "Data Prepper extracted."
-
-sudo chown -R ${DATA_PREPPER_USER}:${DATA_PREPPER_GROUP} "${DATA_PREPPER_INSTALL_DIR}" || log_error "Failed to set ownership."
-log_success "Data Prepper directory ownership set."
-
-configure_data_prepper_server_config
-configure_data_prepper_pipeline
-configure_data_prepper_service
-install_fluent_bit
-create_fluent_bit_user
-configure_fluent_bit
-configure_fluent_bit_service
-configure_rsyslog
-
-log_info "Enabling and starting services..."
-sudo systemctl daemon-reload || log_error "Failed to reload systemd daemon."
-sudo systemctl enable "${DATA_PREPPER_SERVICE}" || log_error "Failed to enable Data Prepper service."
-sudo systemctl restart "${DATA_PREPPER_SERVICE}" || log_error "Failed to start Data Prepper service."
-log_info "Data Prepper service started. Check journalctl -u ${DATA_PREPPER_SERVICE} for status."
-sleep 15
-
-sudo systemctl enable "${FLUENT_BIT_SERVICE}" || log_error "Failed to enable Fluent Bit service."
-sudo systemctl restart "${FLUENT_BIT_SERVICE}" || log_error "Failed to start Fluent Bit service."
-log_info "Fluent Bit service started. Check journalctl -u ${FLUENT_BIT_SERVICE} for status."
-sleep 5
-
-sudo systemctl enable "${RSYSLOG_SERVICE}" || log_error "Failed to enable rsyslog service."
-sudo systemctl restart "${RSYSLOG_SERVICE}" || log_error "Failed to start rsyslog service."
-log_info "rsyslog service started. Check journalctl -u ${RSYSLOG_SERVICE} for status."
-sleep 5
-
-log_info "Configuring firewall rules..."
-if systemctl is-active --quiet firewalld; then
-    sudo firewall-cmd --add-port=${DATA_PREPPER_HTTP_PORT}/tcp --permanent || log_warn "Failed to add Data Prepper HTTP port."
-    sudo firewall-cmd --add-port=${SYSLOG_TCP_PORT}/tcp --permanent || log_warn "Failed to add syslog TCP port."
-    sudo firewall-cmd --add-port=${SYSLOG_UDP_PORT}/udp --permanent || log_warn "Failed to add syslog UDP port."
-    sudo firewall-cmd --reload || log_warn "Failed to reload firewall."
-    log_success "Firewall rules updated."
-else
-    log_warn "Firewalld not running. Configure firewall manually."
-fi
-
-ensure_index_exists
-create_index_pattern
-
-log_info "Sending a sample syslog message to Fluent Bit..."
-logger -t "test_syslog" "This is a test message from Fluent Bit via Data Prepper."
-sleep 15
-
-log_info "Verifying data in OpenSearch..."
-
-
-SEARCH_QUERY='{"query": {"match": {"message": "This is a test message from Fluent Bit via Data Prepper."}}}'
-SEARCH_RESPONSE=$(curl -k -u admin:"${NEW_ADMIN_PASSWORD}" \
-  "https://${OPENSEARCH_HOST}:${OPENSEARCH_PORT}/${INDEX_PATTERN}/_search?pretty" \
-  -H "Content-Type: application/json" \
-  -d "$SEARCH_QUERY")
-
-echo "--- RAW SEARCH RESPONSE ---"
-echo "$SEARCH_RESPONSE"
-echo "---------------------------"
-
-if echo "$SEARCH_RESPONSE" | grep -q "error"; then
-    log_error "OpenSearch search returned an error. Response: ${SEARCH_RESPONSE}"
-    exit 1
-fi
-
-TOTAL_HITS=$(echo "$SEARCH_RESPONSE" | jq -r '.hits.total.value')
-
-# Check if TOTAL_HITS is a number and greater than 0
-if [[ "$TOTAL_HITS" =~ ^[0-9]+$ ]] && (( TOTAL_HITS > 0 )); then
-    log_success "Found ${TOTAL_HITS} matching documents in '${INDEX_PATTERN}'."
-    echo "Sample matching document (first 2):"
-    echo "$SEARCH_RESPONSE" | jq '.hits.hits[:2] | .[] | {index: ._index, id: ._id, message: ._source.message, timestamp: ._source."@timestamp", host: ._source.host}'
-    log_success "Data ingestion pipeline working!"
-else
-    log_error "Failed to find test syslog message or total hits is 0. Total Hits: ${TOTAL_HITS}. Response: ${SEARCH_RESPONSE}."
-fi
-
-log_success "Fluent Bit, Data Prepper, and rsyslog installation complete!"
-log_info "Configure systems to send syslog data to TCP/UDP ${SYSLOG_TCP_PORT}."
-log_info "Check OpenSearch Dashboards (admin/${NEW_ADMIN_PASSWORD}) to visualize '${INDEX_PATTERN}'."
Index: /branches/amp_4_0/platform/tools/install_grafana.sh
===================================================================
--- /branches/amp_4_0/platform/tools/install_grafana.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/install_grafana.sh	(nonexistent)
@@ -1,228 +0,0 @@
-#!/bin/bash
-
-#---------------------------
-# Configuration Variables
-#---------------------------
-GRAFANA_USER="admin"
-GRAFANA_PASSWORD="GArr@y2050"
-INFLUXDB_HOST="localhost"
-INFLUXDB_PORT="8086"
-INFLUXDB_ORG="AN"
-INFLUXDB_BUCKET="AMP"
-INFLUXDB_TOKEN_FILE="/opt/influxdb3_token.toml"
-LOG_FILE="/var/log/install_grafana.log"
-
-#---------------------------
-# Logging and Helpers
-#---------------------------
-log() {
-  echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
-}
-
-info() { log "[INFO] $1"; }
-error() { log "[ERROR] $1"; exit 1; }
-command_exists() { command -v "$1" >/dev/null 2>&1; }
-
-#---------------------------
-# Read InfluxDB Token
-#---------------------------
-read_influxdb_token() {
-  [[ -f "$INFLUXDB_TOKEN_FILE" ]] || error "InfluxDB token file not found: $INFLUXDB_TOKEN_FILE"
-  INFLUXDB_TOKEN=$(sed -nE 's/^[[:space:]]*token[[:space:]]*=[[:space:]]*"([^"]*)".*$/\1/p' "$INFLUXDB_TOKEN_FILE")
-  [[ -n "$INFLUXDB_TOKEN" ]] || error "Failed to read InfluxDB token from file."
-  info "Read InfluxDB token."
-}
-
-#---------------------------
-# Install Grafana
-#---------------------------
-install_grafana() {
-  if ! command_exists grafana-server; then
-    info "Installing Grafana..."
-    cat <<EOF | tee /etc/yum.repos.d/grafana.repo >/dev/null
-[grafana]
-name=Grafana OSS
-baseurl=https://packages.grafana.com/oss/rpm
-enabled=1
-gpgcheck=1
-gpgkey=https://packages.grafana.com/gpg.key
-sslverify=1
-EOF
-    rpm --import https://packages.grafana.com/gpg.key || error "Failed to import GPG key."
-    dnf install -y grafana || error "Failed to install Grafana"
-    systemctl enable --now grafana-server || error "Failed to start Grafana"
-    info "Grafana installed and started."
-  else
-    info "Grafana is already installed."
-  fi
-}
-
-#---------------------------
-# Reset Admin Password via CLI
-#---------------------------
-reset_admin_password() {
-  if command_exists grafana-cli; then
-    info "Resetting Grafana admin password via grafana-cli..."
-    grafana-cli admin reset-admin-password "$GRAFANA_PASSWORD" >> "$LOG_FILE" 2>&1 || error "Failed to reset admin password via CLI."
-    info "Grafana admin password set to '$GRAFANA_PASSWORD'"
-  else
-    log "[WARN] grafana-cli not found, skipping admin password reset."
-  fi
-}
-
-#---------------------------
-# Create API Token
-#---------------------------
-create_api_token() {
-  info "Creating Grafana service account and API token..."
-
-  # Create a service account
-  RESPONSE=$(curl -s -w "%{http_code}" -o /tmp/grafana_serviceaccount_response.txt \
-    -X POST http://localhost:3000/api/serviceaccounts \
-    -H "Content-Type: application/json" \
-    -H "Authorization: Basic $(printf '%s' "$GRAFANA_USER:$GRAFANA_PASSWORD" | base64)" \
-    -d '{"name": "InfluxDB Service Account", "role": "Admin"}')
-
-  BODY=$(cat /tmp/grafana_serviceaccount_response.txt)
-  CODE=$(echo "$RESPONSE" | tail -n1)
-
-  if [[ ! "$CODE" =~ 20[01] ]]; then
-    echo "$BODY" >> "$LOG_FILE"
-    error "Failed to create service account (HTTP $CODE). Response: $BODY"
-  fi
-
-  # Extract service account ID
-  SERVICE_ACCOUNT_ID=$(echo "$BODY" | jq -r '.id')
-  [[ -n "$SERVICE_ACCOUNT_ID" && "$SERVICE_ACCOUNT_ID" != "null" ]] || error "Failed to extract service account ID. Response: $BODY"
-
-  info "Service account created with ID: $SERVICE_ACCOUNT_ID"
-
-  # Create a token for the service account
-  RESPONSE=$(curl -s -w "%{http_code}" -o /tmp/grafana_token_response.txt \
-    -X POST http://localhost:3000/api/serviceaccounts/"$SERVICE_ACCOUNT_ID"/tokens \
-    -H "Content-Type: application/json" \
-    -H "Authorization: Basic $(printf '%s' "$GRAFANA_USER:$GRAFANA_PASSWORD" | base64)" \
-    -d '{"name": "InfluxDB API Token"}')
-
-  BODY=$(cat /tmp/grafana_token_response.txt)
-  CODE=$(echo "$RESPONSE" | tail -n1)
-
-  if [[ ! "$CODE" =~ 20[01] ]]; then
-    echo "$BODY" >> "$LOG_FILE"
-    error "Failed to create API token (HTTP $CODE). Response: $BODY"
-  fi
-
-  # Extract the token
-  TOKEN=$(echo "$BODY" | jq -r '.key')
-  if [[ "$TOKEN" == "null" || -z "$TOKEN" ]]; then
-    error "Failed to extract API token. Response: $BODY"
-  fi
-
-  echo "$TOKEN" | tee /opt/grafana_token >> "$LOG_FILE"
-  chmod 600 /opt/grafana_token
-  chown root:root /opt/grafana_token
-  info "API token created and stored in /opt/grafana_token."
-}
-
-#---------------------------
-# Add or Update InfluxDB Datasource
-#---------------------------
-add_influxdb_datasource() {
-  command_exists curl || error "curl is required but not installed."
-  command_exists jq || error "jq is required but not installed."
-  read_influxdb_token
-
-  # Validate token format
-  if [[ ! "$INFLUXDB_TOKEN" =~ ^[A-Za-z0-9\+\/_-]+=*$ ]]; then
-    error "Invalid InfluxDB token format: $INFLUXDB_TOKEN"
-  fi
-  info "InfluxDB token validated."
-
-  # Wait for Grafana API with timeout (60 seconds)
-  info "Waiting for Grafana API to be ready (timeout 60s)..."
-  TIMEOUT=60
-  COUNT=0
-  until curl -s http://localhost:3000/api/health | grep -q "database"; do
-    sleep 2
-    ((COUNT+=2))
-    echo -n "."
-    if [[ $COUNT -ge $TIMEOUT ]]; then
-      error "Grafana API not ready after ${TIMEOUT}s. Check Grafana service and port 3000."
-    fi
-  done
-  echo
-  info "Grafana API is ready."
-
-  # Use API token for authentication
-  GRAFANA_TOKEN=$(cat /opt/grafana_token 2>/dev/null) || error "Failed to read Grafana API token from /opt/grafana_token."
-
-  # Delete existing datasource
-  info "Deleting existing InfluxDB datasource if it exists..."
-  RESPONSE=$(curl -s -w "%{http_code}" -o /tmp/grafana_response.txt \
-    -H "Authorization: Bearer $GRAFANA_TOKEN" \
-    -X DELETE http://localhost:3000/api/datasources/name/InfluxDB)
-  CODE=$(echo "$RESPONSE" | tail -n1)
-  if [[ "$CODE" =~ 20[0-4] ]]; then
-    info "Existing InfluxDB datasource deleted (HTTP $CODE)."
-  else
-    log "[WARN] Failed to delete existing datasource (HTTP $CODE). Continuing..."
-  fi
-
-  # Prepare JSON
-  DATA_SOURCE_JSON=$(cat <<EOF
-{
-  "name": "InfluxDB",
-  "type": "influxdb",
-  "access": "proxy",
-  "url": "http://$INFLUXDB_HOST:$INFLUXDB_PORT",
-  "isDefault": true,
-  "jsonData": {
-    "version": "Flux",
-    "organization": "$INFLUXDB_ORG",
-    "defaultBucket": "$INFLUXDB_BUCKET",
-    "httpMode": "POST",
-    "httpHeaderName1": "Authorization"
-  },
-  "secureJsonData": {
-    "httpHeaderValue1": "Token $INFLUXDB_TOKEN"
-  }
-}
-EOF
-)
-
-  # Create datasource
-  info "Creating InfluxDB datasource via API..."
-  RESPONSE=$(curl -s -w "%{http_code}" -o /tmp/grafana_response.txt \
-    -H "Content-Type: application/json" \
-    -H "Authorization: Bearer $GRAFANA_TOKEN" \
-    -X POST http://localhost:3000/api/datasources \
-    -d "$DATA_SOURCE_JSON")
-
-  BODY=$(cat /tmp/grafana_response.txt)
-  CODE=$(echo "$RESPONSE" | tail -n1)
-
-  if [[ "$CODE" =~ 20[01] ]]; then
-    info "InfluxDB datasource created (HTTP $CODE)."
-  else
-    echo "$BODY" >> "$LOG_FILE"
-    error "Failed to create datasource (HTTP $CODE). Response: $BODY"
-  fi
-
-  # Configure firewall
-  info "Configuring firewall to allow access to Grafana on port 3000..."
-  firewall-cmd --permanent --add-port=3000/tcp >> "$LOG_FILE" 2>&1 || error "Failed to add firewall rule."
-  firewall-cmd --reload >> "$LOG_FILE" 2>&1 || error "Failed to reload firewall."
-  info "Firewall configured."
-}
-
-#---------------------------
-# Main
-#---------------------------
-[[ $EUID -ne 0 ]] && error "This script must be run as root."
-for cmd in curl systemctl rpm awk dnf tee firewall-cmd jq; do command_exists "$cmd" || error "$cmd is required."; done
-mkdir -p "$(dirname "$LOG_FILE")"
-install_grafana
-reset_admin_password
-create_api_token
-add_influxdb_datasource
-info "Script completed successfully."
Index: /branches/amp_4_0/platform/tools/install_influx.sh
===================================================================
--- /branches/amp_4_0/platform/tools/install_influx.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/install_influx.sh	(nonexistent)
@@ -1,170 +0,0 @@
-#!/bin/bash
-
-set -e
-
-# --- Configuration ---
-LOG_FILE="/var/log/install_influxdb.log"
-INFLUXDB_USERNAME="array"
-INFLUXDB_PASSWORD="Arr@y2050"
-INFLUXDB_ORG="AN"
-INFLUXDB_BUCKET="AMP"
-INFLUXDB_TOKEN_DESCRIPTION="Admin Token - Initial Setup"
-CONFIG_FILE="/opt/influxdb2_token.toml"
-INFLUXDB_HOST="http://localhost:8086"
-
-log_info() {
-  echo "[INFO] $(date +'%Y-%m-%d %H:%M:%S') $1" | tee -a "$LOG_FILE"
-}
-
-log_error() {
-  echo "[ERROR] $(date +'%Y-%m-%d %H:%M:%S') $1" >&2 | tee -a "$LOG_FILE"
-}
-
-check_command() {
-  if ! command -v "$1" &> /dev/null; then
-    log_error "$1 command not found. Please install it."
-    exit 1
-  fi
-}
-
-# --- Check directory existence and writable ---
-for dir in "/var/log" "/opt"; do
-  if [[ ! -d "$dir" ]]; then
-    log_error "$dir directory does not exist."
-    exit 1
-  fi
-  if [[ ! -w "$dir" ]]; then
-    log_error "$dir directory is not writable. Please check permissions."
-    exit 1
-  fi
-done
-
-# --- Prerequisites Check ---
-log_info "Checking for required commands..."
-check_command curl
-check_command sudo
-check_command dnf
-check_command jq
-
-# --- Add InfluxData Repository for InfluxDB 2.x ---
-log_info "Adding InfluxData repository for InfluxDB 2.x..."
-REPO_URL="https://repos.influxdata.com/centos/9/x86_64/stable"
-REPO_FILE="/etc/yum.repos.d/influxdata.repo"
-
-cat <<EOF | sudo tee "$REPO_FILE"
-[influxdata]
-name = InfluxData Repository - Stable
-baseurl = $REPO_URL
-gpgcheck = 1
-gpgkey = https://repos.influxdata.com/influxdata-archive_compat.key
-enabled = 1
-EOF
-
-sudo dnf update -y
-
-# --- Install InfluxDB 2.x and the CLI (influx) ---
-log_info "Installing influxdb2 and influxdb2-cli (influx)..."
-sudo dnf install -y influxdb2 influxdb2-cli
-
-# --- Check if the influxdb.service file exists ---
-if [[ ! -f "/usr/lib/systemd/system/influxdb.service" ]]; then
-  log_error "InfluxDB 2.x service unit file not found at /usr/lib/systemd/system/influxdb.service. Installation may have failed."
-  exit 1
-fi
-
-# --- Start and Enable InfluxDB 2.x Service (using influxdb.service) ---
-log_info "Starting and enabling influxdb service (assuming influxdb.service for 2.x)..."
-sudo systemctl enable --now influxdb
-
-# Check if the enable command was successful
-if [ $? -ne 0 ]; then
-  log_error "Failed to enable the influxdb service. Please check the output above."
-  exit 1
-fi
-
-sudo systemctl start influxdb
-
-# Check if the start command was successful
-if [ $? -ne 0 ]; then
-  log_error "Failed to start the influxdb service. Please check the output."
-  exit 1
-fi
-
-# --- Wait for InfluxDB 2.x to start ---
-log_info "Waiting for InfluxDB 2.x service to start..."
-sleep 15
-
-# --- Find Influx CLI Path ---
-INFLUX_CLI_PATH=$(which influx)
-if [[ -z "$INFLUX_CLI_PATH" ]]; then
-  log_warning "influx command not found in PATH. Checking common locations..."
-  if [[ -f "/usr/local/bin/influx" ]]; then
-    INFLUX_CLI_PATH="/usr/local/bin/influx"
-  elif [[ -f "/usr/bin/influx" ]]; then
-    INFLUX_CLI_PATH="/usr/bin/influx"
-  elif [[ -f "/opt/influxdb2/usr/bin/influx" ]]; then
-    INFLUX_CLI_PATH="/opt/influxdb2/usr/bin/influx"
-  else
-    log_error "influx command not found. Installation might be incomplete or PATH is not set correctly."
-    exit 1
-  fi
-  log_info "Found influx at: $INFLUX_CLI_PATH"
-fi
-
-# --- Initialize InfluxDB 2.x Non-Interactively ---
-log_info "Initializing InfluxDB 2.x non-interactively..."
-sudo "$INFLUX_CLI_PATH" setup --host "$INFLUXDB_HOST" \
-  --username "$INFLUXDB_USERNAME" \
-  --password "$INFLUXDB_PASSWORD" \
-  --org "$INFLUXDB_ORG" \
-  --bucket "$INFLUXDB_BUCKET" \
-  --force
-
-if [ $? -eq 0 ]; then
-  log_info "InfluxDB 2.x initialized successfully..."
-else
-  log_error "Failed to initialize InfluxDB 2.x."
-  exit 1
-fi
-
-# --- Create an Initial Admin Token Non-Interactively and Store in TOML File ---
-log_info "Creating an initial admin token non-interactively and storing in $CONFIG_FILE..."
-AUTH_OUTPUT=$(sudo "$INFLUX_CLI_PATH" auth create --host "$INFLUXDB_HOST" \
-  --org "$INFLUXDB_ORG" \
-  --user "$INFLUXDB_USERNAME" \
-  --all-access \
-  --description "$INFLUXDB_TOKEN_DESCRIPTION" \
-  --json)
-echo "Output of influx auth create (JSON):"
-echo "$AUTH_OUTPUT"
-ADMIN_TOKEN=$(echo "$AUTH_OUTPUT" | jq -r '.token')
-
-if [ -n "$ADMIN_TOKEN" ]; then
-  log_info "Successfully created initial admin token."
-  echo "[influxdb]" > "$CONFIG_FILE"
-  echo "token = \"$ADMIN_TOKEN\"" >> "$CONFIG_FILE"
-  chmod 600 "$CONFIG_FILE"
-  log_info "Admin token stored securely in $CONFIG_FILE."
-else
-  log_error "Failed to create initial admin token."
-fi
-
-# --- Verify Installation ---
-log_info "Verifying InfluxDB 2.x installation..."
-sudo "$INFLUX_CLI_PATH" version
-
-# Attempt to list buckets
-log_info "Listing buckets using the token from $CONFIG_FILE..."
-STORED_TOKEN=$(grep "^token = " "$CONFIG_FILE" | cut -d '"' -f 2)
-sudo "$INFLUX_CLI_PATH" bucket list --host "$INFLUXDB_HOST" --org "$INFLUXDB_ORG" --token "$STORED_TOKEN"
-
-if [ $? -eq 0 ]; then
-  log_info "Successfully listed buckets."
-else
-  log_warning "Failed to list buckets. Ensure InfluxDB 2.x is running and the token is correct."
-fi
-
-log_info "InfluxDB 2.x and InfluxDB CLI installed and configured!"
-log_info "Installation logs are available in $LOG_FILE."
-
-exit 0
\ No newline at end of file
Index: /branches/amp_4_0/platform/tools/install_influxdb3.sh
===================================================================
--- /branches/amp_4_0/platform/tools/install_influxdb3.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/install_influxdb3.sh	(nonexistent)
@@ -1,214 +0,0 @@
-#!/bin/bash
-
-# This script installs InfluxDB 3.x on a Rocky Linux 9 system,
-# sets up an initial configuration, and creates a token.
-
-set -e
-
-# --- Configuration ---
-LOG_FILE="/var/log/install_influxdb3.log"
-CONFIG_FILE="/opt/influxdb3_token.toml"
-INFLUXDB_HOST="http://localhost:8181" # Correct port for InfluxDB 3.x
-INFLUXDB_DATABASE="amp_database"
-
-log_info() {
-  echo "[INFO] $(date +'%Y-%m-%d %H:%M:%S') $1" | tee -a "$LOG_FILE"
-}
-
-log_error() {
-  echo "[ERROR] $(date +'%Y-%m-%d %H:%M:%S') $1" >&2 | tee -a "$LOG_FILE"
-}
-
-check_command() {
-  if ! command -v "$1" &> /dev/null; then
-    log_error "$1 command not found. Please install it."
-    exit 1
-  fi
-}
-
-# --- Prerequisites Check ---
-log_info "Checking for required commands..."
-check_command curl
-check_command sudo
-
-# --- InfluxDB 3.x Automated Installer (Embedded) ---
-log_info "Using the embedded InfluxDB 3.x quick installer script..."
-
-# Define the installer script content to be executed non-interactively
-INFLUXDB_INSTALLER_SCRIPT=$(cat << 'EOF'
-#!/bin/sh -e
-
-readonly GREEN='\033[0;32m'
-readonly BOLD='\033[1m'
-readonly BOLDGREEN='\033[1;32m'
-readonly DIM='\033[2m'
-readonly NC='\033[0m' # No Color
-
-ARCHITECTURE=$(uname -m)
-ARTIFACT=""
-OS=""
-INSTALL_LOC=/usr/local/bin
-BINARY_NAME="influxdb3"
-PORT=8181
-
-INFLUXDB_VERSION="3.3.0"
-EDITION="Core"
-EDITION_TAG="core"
-
-### OS AND ARCHITECTURE DETECTION ###
-case "$(uname -s)" in
-    Linux*)     OS="Linux";;
-    Darwin*)    OS="Darwin";;
-    *)          OS="UNKNOWN";;
-esac
-
-if [ "${OS}" = "Linux" ]; then
-    if [ "${ARCHITECTURE}" = "x86_64" ] || [ "${ARCHITECTURE}" = "amd64" ]; then
-        ARTIFACT="linux_amd64"
-    elif [ "${ARCHITECTURE}" = "aarch64" ] || [ "${ARCHITECTURE}" = "arm64" ]; then
-        ARTIFACT="linux_arm64"
-    fi
-elif [ "${OS}" = "Darwin" ]; then
-    ARTIFACT="darwin_arm64"
-fi
-
-[ -n "${ARTIFACT}" ] || {
-    printf "Unfortunately this script doesn't support your '${OS}' | '${ARCHITECTURE}' setup.\n"
-    exit 1
-}
-
-URL="https://dl.influxdata.com/influxdb/releases/influxdb3-${EDITION_TAG}-${INFLUXDB_VERSION}_${ARTIFACT}.tar.gz"
-
-printf "${BOLD}Downloading InfluxDB 3 %s to %s${NC}\n" "$EDITION" "$INSTALL_LOC"
-mkdir -p "$INSTALL_LOC"
-curl -sSL "${URL}" -o "$INSTALL_LOC/influxdb3-${EDITION_TAG}.tar.gz"
-
-printf "${BOLD}Verifying '%s/influxdb3-${EDITION_TAG}.tar.gz'${NC}\n" "$INSTALL_LOC"
-curl -sSL "${URL}.sha256" -o "$INSTALL_LOC/influxdb3-${EDITION_TAG}.tar.gz.sha256"
-dl_sha=$(cut -d ' ' -f 1 "$INSTALL_LOC/influxdb3-${EDITION_TAG}.tar.gz.sha256" | grep -E '^[0-9a-f]{64}$')
-if [ -z "$dl_sha" ]; then
-    printf "Could not find properly formatted SHA256 in '%s/influxdb3-${EDITION_TAG}.tar.gz.sha256'. Aborting.\n" "$INSTALL_LOC"
-    exit 1
-fi
-ch_sha=$(sha256sum "$INSTALL_LOC/influxdb3-${EDITION_TAG}.tar.gz" | cut -d ' ' -f 1)
-if [ "$ch_sha" = "$dl_sha" ]; then
-    printf " (OK: %s = %s)${NC}\n" "$ch_sha" "$dl_sha"
-else
-    printf " (ERROR: %s != %s). Aborting.${NC}\n" "$ch_sha" "$dl_sha"
-    exit 1
-fi
-rm "$INSTALL_LOC/influxdb3-${EDITION_TAG}.tar.gz.sha256"
-
-printf "${BOLD}Extracting and Processing${NC}\n"
-TAR_LEVEL=0
-if tar -tf "$INSTALL_LOC/influxdb3-${EDITION_TAG}.tar.gz" | grep -q '[a-zA-Z0-9]/influxdb3$' ; then
-    TAR_LEVEL=1
-fi
-tar -xf "$INSTALL_LOC/influxdb3-${EDITION_TAG}.tar.gz" --strip-components="${TAR_LEVEL}" -C "$INSTALL_LOC"
-rm "$INSTALL_LOC/influxdb3-${EDITION_TAG}.tar.gz"
-
-printf "${BOLDGREEN}✓ InfluxDB 3 ${EDITION} is now installed. Nice!${NC}\n"
-EOF
-)
-
-# Execute the installer script as root
-echo "$INFLUXDB_INSTALLER_SCRIPT" | sudo bash
-
-# --- InfluxDB 3.x Systemd Service Configuration ---
-log_info "Creating and configuring systemd service for InfluxDB 3.x..."
-SERVICE_FILE="/etc/systemd/system/influxdb3.service"
-sudo bash -c "cat > $SERVICE_FILE" <<EOF
-[Unit]
-Description=InfluxDB 3.x Service
-After=network-online.target
-
-[Service]
-ExecStart=/usr/local/bin/influxdb3 serve --node-id=node0 --object-store=file --data-dir /var/lib/influxdb --http-bind=0.0.0.0:8181
-Restart=always
-User=influxdb
-Group=influxdb
-
-[Install]
-WantedBy=multi-user.target
-EOF
-
-# Create the user and group for the service
-if ! getent group influxdb >/dev/null; then
-  sudo groupadd --system influxdb
-fi
-if ! getent passwd influxdb >/dev/null; then
-  sudo useradd --system -g influxdb -d /var/lib/influxdb -s /sbin/nologin -c "InfluxDB User" influxdb
-fi
-
-# Ensure necessary directories and permissions
-sudo mkdir -p /var/lib/influxdb
-sudo chown -R influxdb:influxdb /var/lib/influxdb
-
-# Reload systemd, start, and enable the service
-sudo systemctl daemon-reload
-log_info "Starting and enabling influxdb3 service..."
-sudo systemctl enable --now influxdb3
-
-if [ $? -ne 0 ]; then
-  log_error "Failed to enable and start the influxdb3 service."
-  exit 1
-fi
-
-# --- Wait for InfluxDB 3.x to start ---
-log_info "Waiting for InfluxDB 3.x service to start..."
-sleep 15
-
-# --- Create an Initial Admin Token ---
-log_info "Creating an initial admin token and storing it..."
-INFLUXDB3_CLI_PATH="/usr/local/bin/influxdb3"
-
-# Provide an empty line (pressing Enter) to the create token command to bypass interactive prompt.
-RAW_AUTH_OUTPUT=$(echo "" | sudo "$INFLUXDB3_CLI_PATH" create token --admin)
-
-# Filter the output to find the line with "Token:" and extract the token value cleanly.
-# - Remove ANSI escape codes
-# - Trim leading/trailing spaces
-# - Remove carriage returns/newlines
-ADMIN_TOKEN=$(echo "$RAW_AUTH_OUTPUT" \
-  | grep 'Token:' \
-  | cut -d ':' -f 2- \
-  | sed 's/\x1b\[[0-9;]*m//g' \
-  | tr -d '\r\n' \
-  | xargs)
-
-if [ -n "$ADMIN_TOKEN" ]; then
-  log_info "Successfully created initial admin token."
-  {
-    echo "[influxdb]"
-    echo "token = \"$ADMIN_TOKEN\""
-  } | sudo tee "$CONFIG_FILE" > /dev/null
-  sudo chmod 600 "$CONFIG_FILE"
-  log_info "Admin token stored securely in $CONFIG_FILE."
-else
-  log_error "Failed to create initial admin token."
-  log_error "Raw output from token creation command: $RAW_AUTH_OUTPUT"
-  exit 1
-fi
-
-# --- Verify Installation and Token ---
-log_info "Verifying InfluxDB 3.x installation..."
-log_info "Attempting to create the database using the new token."
-
-STORED_TOKEN=$(grep '^token = ' "$CONFIG_FILE" \
-  | cut -d '"' -f 2 \
-  | sed 's/\x1b\[[0-9;]*m//g' \
-  | tr -d '\r\n' \
-  | xargs)
-
-# Use the InfluxDB 3 CLI to create the database
-if sudo "$INFLUXDB3_CLI_PATH" create database "$INFLUXDB_DATABASE" --token "$STORED_TOKEN"; then
-  log_info "Successfully created/verified database '$INFLUXDB_DATABASE' with the new token."
-else
-  log_error "Failed to create or verify database '$INFLUXDB_DATABASE'."
-  exit 1
-fi
-
-log_info "InfluxDB 3.x installed and configured!"
-log_info "Installation logs are available in $LOG_FILE."
-
-exit 0
Index: /branches/amp_4_0/platform/tools/install_java.sh
===================================================================
--- /branches/amp_4_0/platform/tools/install_java.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/install_java.sh	(nonexistent)
@@ -1,100 +0,0 @@
-#!/bin/bash
-
-# Script to install OpenJDK 21 on Rocky Linux using DNF (RPM repository)
-# This version aims for a more robust JAVA_HOME detection.
-
-# --- Configuration ---
-JAVA_PACKAGE="java-21-openjdk-devel" # Installs JDK (including JRE)
-                                     # 'java-21-openjdk' would install only the JRE
-PROFILE_SCRIPT="/etc/profile.d/java.sh" # For setting JAVA_HOME
-
-# --- Functions ---
-
-log_info() {
-    echo -e "\n\033[0;34m[INFO]\033[0m $1"
-}
-
-log_success() {
-    echo -e "\n\033[0;32m[SUCCESS]\033[0m $1"
-}
-
-log_error() {
-    echo -e "\n\033[0;31m[ERROR]\033[0m $1"
-    exit 1
-}
-
-# --- Main Script ---
-
-log_info "Starting OpenJDK 21 installation on Rocky Linux via DNF..."
-
-# 1. Check for root privileges
-if [[ $EUID -ne 0 ]]; then
-   log_error "This script must be run as root. Please use 'sudo bash $0'."
-fi
-
-# 2. Update system packages
-log_info "Updating system packages and metadata..."
-sudo dnf -y update --refresh || log_error "Failed to update system packages or refresh metadata."
-
-# 3. Search for available OpenJDK 21 packages
-log_info "Searching for available OpenJDK 21 packages in repositories..."
-# This is just for user information, not a direct check for installation flow
-dnf search "${JAVA_PACKAGE}" &>/dev/null
-
-# 4. Install OpenJDK 21 if not already installed
-if ! dnf list installed "${JAVA_PACKAGE}" &>/dev/null; then
-    log_info "Installing OpenJDK 21 (${JAVA_PACKAGE}) from official repositories..."
-    sudo dnf -y install "${JAVA_PACKAGE}" || log_error "Failed to install OpenJDK 21. It might not be available in your enabled repositories, or there's a network issue."
-else
-    log_info "OpenJDK 21 (${JAVA_PACKAGE}) is already installed."
-fi
-
-# 5. Determine JAVA_HOME path more robustly
-log_info "Determining JAVA_HOME path..."
-
-# First, ensure java is in PATH for the current script execution context
-# This is crucial so that 'which java' works reliably
-source /etc/profile # Load system-wide path settings
-
-JAVA_BIN_PATH=$(which java)
-if [ -z "${JAVA_BIN_PATH}" ]; then
-    log_error "Could not find 'java' executable in PATH after installation. This is unexpected."
-fi
-
-# Use readlink -f to get the absolute, resolved path to the actual java executable
-# Then remove the '/bin/java' part to get JAVA_HOME
-JAVA_HOME_PATH=$(readlink -f "${JAVA_BIN_PATH}" | sed 's:/bin/java::')
-
-if [ -z "${JAVA_HOME_PATH}" ]; then
-    log_error "Failed to determine JAVA_HOME path using 'readlink -f'. Manual check might be required."
-else
-    log_info "Detected JAVA_HOME: ${JAVA_HOME_PATH}"
-    # 6. Set up JAVA_HOME and PATH environment variables
-    log_info "Setting up JAVA_HOME and PATH environment variables in ${PROFILE_SCRIPT}..."
-
-    # Clear any previous Java settings in the profile script related to OpenJDK 21
-    # This prevents duplicate or incorrect entries if the script is run multiple times
-    sudo sed -i '/^export JAVA_HOME=.*openjdk-21/,+2d' "${PROFILE_SCRIPT}" 2>/dev/null
-    sudo sed -i '/^export PATH=.*\$JAVA_HOME\/bin/,d' "${PROFILE_SCRIPT}" 2>/dev/null
-    sudo sed -i '/^export JDK_HOME=.*\$JAVA_HOME/,d' "${PROFILE_SCRIPT}" 2>/dev/null
-
-    echo "export JAVA_HOME=${JAVA_HOME_PATH}" | sudo tee "${PROFILE_SCRIPT}" > /dev/null
-    echo "export PATH=\$PATH:\$JAVA_HOME/bin" | sudo tee -a "${PROFILE_SCRIPT}" > /dev/null
-    echo "export JDK_HOME=\$JAVA_HOME" | sudo tee -a "${PROFILE_SCRIPT}" > /dev/null
-
-    log_success "Environment variables for JAVA_HOME set in ${PROFILE_SCRIPT}."
-    log_info "You may need to log out and log back in, or run 'source ${PROFILE_SCRIPT}' to apply changes for new sessions."
-fi
-
-# 7. Verify installation
-log_info "Verifying OpenJDK 21 installation..."
-source "${PROFILE_SCRIPT}" # Apply changes for the current session to verify
-java -version
-
-if [ $? -eq 0 ]; then
-    log_success "OpenJDK 21 installed successfully via DNF and JAVA_HOME configured!"
-    log_info "JAVA_HOME is set to: ${JAVA_HOME}"
-    log_info "To ensure changes persist, please log out and log back in, or reboot your system."
-else
-    log_error "OpenJDK 21 installation verification failed."
-fi
Index: /branches/amp_4_0/platform/tools/install_logstash_oss.sh
===================================================================
--- /branches/amp_4_0/platform/tools/install_logstash_oss.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/install_logstash_oss.sh	(nonexistent)
@@ -1,795 +0,0 @@
-#!/bin/bash
-
-# Script to install Logstash OSS with OpenSearch output plugin on Rocky Linux,
-# configure a Logstash user for authentication, install PostgreSQL JDBC driver,
-# set up a syslog pipeline with jdbc_streaming filter to write output to OpenSearch acm-%{+YYYY.MM.dd} indices,
-# configure firewall rules for syslog traffic with port forwarding,
-# and create an index pattern for acm-* based on @timestamp.
-
-# --- Configuration ---
-LOGSTASH_VERSION="8.15.0"
-LOGSTASH_RPM_URL="https://artifacts.elastic.co/downloads/logstash/logstash-oss-${LOGSTASH_VERSION}-x86_64.rpm"
-LOGSTASH_SERVICE="logstash"
-LOGSTASH_CONFIG_DIR="/etc/logstash"
-LOGSTASH_CERT_DIR="${LOGSTASH_CONFIG_DIR}/certs"
-LOGSTASH_PIPELINE_CONF="${LOGSTASH_CONFIG_DIR}/conf.d/syslog.conf"
-OPENSEARCH_HOST="https://127.0.0.1:9200"
-NEW_ADMIN_PASSWORD="Arr@y2050"
-LOGSTASH_USER="logstash"
-LOGSTASH_PASSWORD="Array@123"
-FIREWALL_ZONE="public"
-JAVA_HEAP_SIZE="2g" # Heap size for plugin installations and runtime
-LOGSTASH_ROLE="logstash_custom" # Custom role to avoid static role conflicts
-
-# Certificate paths (aligned with previous OpenSearch script)
-CERT_DIR="/etc/opensearch/certs"
-CA_KEY="${CERT_DIR}/root-ca-key.pem"
-CA_CERT="${CERT_DIR}/root-ca.pem"
-LOGSTASH_KEY="${CERT_DIR}/logstash-key.pem"
-LOGSTASH_CERT="${CERT_DIR}/logstash.pem"
-LOGSTASH_PKCS8_KEY="${CERT_DIR}/logstash-key-pkcs8.pem"
-ADMIN_KEY="${CERT_DIR}/admin-key.pem"
-ADMIN_CERT="${CERT_DIR}/admin.pem"
-
-# Logstash Certificate DN (not used for authentication but kept for potential future mTLS support)
-LOGSTASH_DN="/C=IN/ST=Karnataka/L=Bengaluru/O=OpenSearchLogstash/CN=logstash"
-LOGSTASH_DN_MAPPING="CN=logstash,O=OpenSearchLogstash,L=Bengaluru,ST=Karnataka,C=IN"
-ADMIN_DN_MAPPING="C=IN,ST=Karnataka,L=Bengaluru,O=OpenSearchAdmin,CN=admin"
-
-# Java path (aligned with previous script)
-JAVA_HOME_PATH="/usr/lib/jvm/java-21-openjdk-21.0.8.0.9-1.el9.x86_64/"
-
-# JDBC driver configuration
-JDBC_DRIVER_DIR="/etc/logstash/jdbc_drivers"
-JDBC_DRIVER_FILE="${JDBC_DRIVER_DIR}/postgresql-42.7.5.jar"
-JDBC_DRIVER_URL="https://jdbc.postgresql.org/download/postgresql-42.7.5.jar"
-
-# --- Functions ---
-
-log_info() {
-    echo -e "\n\033[0;34m[INFO]\033[0m $1"
-}
-
-log_success() {
-    echo -e "\n\033[0;32m[SUCCESS]\033[0m $1"
-}
-
-log_warn() {
-    echo -e "\n\033[0;33m[WARNING]\033[0m $1"
-}
-
-log_error() {
-    echo -e "\n\033[0;31m[ERROR]\033[0m $1"
-    exit 1
-}
-
-# Check if certificate files exist
-check_cert_files() {
-    log_info "Checking CA certificate file..."
-    for file in "${CA_CERT}" "${ADMIN_KEY}" "${ADMIN_CERT}"; do
-        if [ ! -f "${file}" ]; then
-            log_error "Certificate file ${file} does not exist. Ensure OpenSearch is installed and certificates are generated."
-        fi
-    done
-    log_success "Required certificate files exist."
-}
-
-# Check for Java installation
-check_java() {
-    log_info "Checking for Java installation (OpenJDK 17 or higher required)..."
-    if command -v java &>/dev/null; then
-        JAVA_VERSION=$(java -version 2>&1 | awk -F '"' '/version/ {print $2}' | cut -d'.' -f1)
-        if (( JAVA_VERSION >= 17 )); then
-            log_success "Java ${JAVA_VERSION} found. Compatible with Logstash 8.x."
-            if [ -z "${JAVA_HOME_PATH}" ]; then
-                DETECTED_JAVA_BIN=$(readlink -f $(which java))
-                if [ -n "${DETECTED_JAVA_BIN}" ]; then
-                    JAVA_HOME_PATH=$(dirname $(dirname "${DETECTED_JAVA_BIN}"))
-                    log_info "Auto-detected JAVA_HOME_PATH: ${JAVA_HOME_PATH}"
-                else
-                    log_error "Failed to determine JAVA_HOME_PATH. Please set it manually."
-                fi
-            fi
-        else
-            log_error "Java version ${JAVA_VERSION} found. Logstash 8.x requires Java 17 or higher."
-        fi
-    else
-        log_error "Java not found. Please install Java (OpenJDK 17 or higher)."
-    fi
-}
-
-# Check and create security_admin role if missing
-check_security_admin_role() {
-    log_info "Checking for security_admin role..."
-    ROLE_RESPONSE=$(curl -s -k -X GET "${OPENSEARCH_HOST}/_plugins/_security/api/roles/security_admin" \
-        -u admin:${NEW_ADMIN_PASSWORD})
-    if echo "$ROLE_RESPONSE" | grep -q "\"security_admin\""; then
-        log_success "security_admin role exists."
-    else
-        log_info "security_admin role not found. Creating role..."
-        CREATE_ROLE_RESPONSE=$(curl -s -k -X PUT "${OPENSEARCH_HOST}/_plugins/_security/api/roles/security_admin" \
-            -u admin:${NEW_ADMIN_PASSWORD} \
-            -H "Content-Type: application/json" \
-            -d '{
-              "cluster_permissions": [
-                "cluster:admin/opendistro_security/*"
-              ]
-            }')
-        if echo "$CREATE_ROLE_RESPONSE" | grep -q "\"status\":\"CREATED\"\|\"status\":\"OK\""; then
-            log_success "security_admin role created."
-        else
-            log_error "Failed to create security_admin role. Response: $CREATE_ROLE_RESPONSE"
-        fi
-    fi
-}
-
-# Check admin certificate permissions
-check_admin_permissions() {
-    log_info "Checking admin certificate permissions..."
-    ADMIN_MAPPING=$(curl -s -k -X GET "${OPENSEARCH_HOST}/_plugins/_security/api/rolesmapping/security_admin" \
-        -u admin:${NEW_ADMIN_PASSWORD})
-    if echo "$ADMIN_MAPPING" | grep -q "${ADMIN_DN_MAPPING}"; then
-        log_success "Admin certificate has security_admin role."
-    else
-        log_info "Mapping admin DN to security_admin role..."
-        MAPPING_RESPONSE=$(curl -s -k -X PUT "${OPENSEARCH_HOST}/_plugins/_security/api/rolesmapping/security_admin" \
-            -u admin:${NEW_ADMIN_PASSWORD} \
-            -H "Content-Type: application/json" \
-            -d "{\"backend_roles\": [], \"hosts\": [], \"users\": [\"${ADMIN_DN_MAPPING}\"]}")
-        if echo "$MAPPING_RESPONSE" | grep -q "\"status\":\"CREATED\"\|\"status\":\"OK\""; then
-            log_success "Admin DN mapped to security_admin role."
-        else
-            log_error "Failed to map admin DN to security_admin role. Response: $MAPPING_RESPONSE"
-        fi
-    fi
-}
-
-# Create Logstash user in OpenSearch
-create_logstash_user() {
-    log_info "Creating Logstash user in OpenSearch..."
-    USER_RESPONSE=$(curl -s -k -X PUT "${OPENSEARCH_HOST}/_plugins/_security/api/internalusers/${LOGSTASH_USER}" \
-        -u admin:${NEW_ADMIN_PASSWORD} \
-        -H "Content-Type: application/json" \
-        -d "{\"password\": \"${LOGSTASH_PASSWORD}\"}")
-    if echo "$USER_RESPONSE" | grep -q "\"status\":\"CREATED\"\|\"status\":\"OK\""; then
-        log_success "Logstash user created."
-    else
-        log_error "Failed to create Logstash user. Response: $USER_RESPONSE"
-    fi
-}
-
-# Map Logstash user to logstash_custom role
-map_logstash_user() {
-    log_info "Mapping Logstash user to '${LOGSTASH_ROLE}' role..."
-    MAPPING_RESPONSE=$(curl -s -k -X PUT "${OPENSEARCH_HOST}/_plugins/_security/api/rolesmapping/${LOGSTASH_ROLE}" \
-        -u admin:${NEW_ADMIN_PASSWORD} \
-        -H "Content-Type: application/json" \
-        -d "{\"backend_roles\": [], \"hosts\": [], \"users\": [\"${LOGSTASH_USER}\"]}")
-    if echo "$MAPPING_RESPONSE" | grep -q "\"status\":\"CREATED\"\|\"status\":\"OK\""; then
-        log_success "Logstash user mapped to ${LOGSTASH_ROLE} role."
-    else
-        log_error "Failed to map Logstash user to ${LOGSTASH_ROLE}. Response: $MAPPING_RESPONSE"
-    fi
-
-    log_info "Verifying Logstash role mapping..."
-    MAPPING_CHECK=$(curl -s -k -X GET "${OPENSEARCH_HOST}/_plugins/_security/api/rolesmapping/${LOGSTASH_ROLE}" \
-        -u admin:${NEW_ADMIN_PASSWORD})
-    if echo "$MAPPING_CHECK" | grep -q "${LOGSTASH_USER}"; then
-        log_success "Logstash user mapping to ${LOGSTASH_ROLE} verified."
-    else
-        log_error "Logstash user mapping not found. Response: $MAPPING_CHECK"
-    fi
-}
-
-# Ensure logstash_custom role has sufficient permissions
-configure_logstash_role() {
-    log_info "Configuring ${LOGSTASH_ROLE} role in OpenSearch for acm-* indices..."
-    ROLE_RESPONSE=$(curl -s -k -X PUT "${OPENSEARCH_HOST}/_plugins/_security/api/roles/${LOGSTASH_ROLE}" \
-        -u admin:${NEW_ADMIN_PASSWORD} \
-        -H "Content-Type: application/json" \
-        -d '{
-          "cluster_permissions": ["cluster_composite_ops", "cluster_monitor"],
-          "index_permissions": [{
-            "index_patterns": ["acm-*"],
-            "allowed_actions": ["write", "create_index", "manage"]
-          }]
-        }')
-    if echo "$ROLE_RESPONSE" | grep -q "\"status\":\"CREATED\"\|\"status\":\"OK\""; then
-        log_success "${LOGSTASH_ROLE} role configured with acm-* index permissions."
-    else
-        log_error "Failed to configure ${LOGSTASH_ROLE} role. Response: $ROLE_RESPONSE"
-    fi
-}
-
-# Create index pattern for acm-* based on @timestamp
-create_index_pattern() {
-    OPENSEARCH_DASHBOARDS_HOST="https://127.0.0.1:5601"
-    log_info "Checking if OpenSearch Dashboards is running on ${OPENSEARCH_DASHBOARDS_HOST}..."
-    if curl -s -k -X GET "${OPENSEARCH_DASHBOARDS_HOST}/api/status" -u admin:${NEW_ADMIN_PASSWORD} | grep -q "overall"; then
-        log_success "OpenSearch Dashboards is running."
-    else
-        log_warn "OpenSearch Dashboards is not running, inaccessible, or authentication failed on ${OPENSEARCH_DASHBOARDS_HOST}. Skipping index pattern creation. Please ensure OpenSearch Dashboards is installed, running, and accessible with the admin user (username: admin, password: ${NEW_ADMIN_PASSWORD}), then create the index pattern manually in the Dashboards UI (Stack Management -> Index Patterns -> Create Index Pattern) with pattern 'acm-*' and time field '@timestamp'."
-        return
-    fi
-
-    log_info "Verifying admin user authentication for OpenSearch Dashboards..."
-    AUTH_RESPONSE=$(curl -s -k -X GET "${OPENSEARCH_DASHBOARDS_HOST}/api/status" -u admin:${NEW_ADMIN_PASSWORD})
-    if echo "$AUTH_RESPONSE" | grep -q "overall"; then
-        log_success "Admin user authentication verified."
-    else
-        log_error "Admin user authentication failed. Response: $AUTH_RESPONSE. Please verify the admin user credentials (username: admin, password: ${NEW_ADMIN_PASSWORD}) in OpenSearch Dashboards and ensure the user has the 'kibana_admin' role."
-    fi
-
-    log_info "Ensuring admin user has kibana_admin role for saved objects..."
-    ROLE_MAPPING_RESPONSE=$(curl -s -k -X GET "${OPENSEARCH_HOST}/_plugins/_security/api/rolesmapping/kibana_admin" \
-        -u admin:${NEW_ADMIN_PASSWORD})
-    if echo "$ROLE_MAPPING_RESPONSE" | grep -q "${ADMIN_DN_MAPPING}\|admin"; then
-        log_success "Admin user or DN already mapped to kibana_admin role."
-    else
-        log_info "Mapping admin user to kibana_admin role..."
-        MAPPING_RESPONSE=$(curl -s -k -X PUT "${OPENSEARCH_HOST}/_plugins/_security/api/rolesmapping/kibana_admin" \
-            -u admin:${NEW_ADMIN_PASSWORD} \
-            -H "Content-Type: application/json" \
-            -d "{\"backend_roles\": [], \"hosts\": [], \"users\": [\"admin\", \"${ADMIN_DN_MAPPING}\"]}")
-        if echo "$MAPPING_RESPONSE" | grep -q "\"status\":\"CREATED\"\|\"status\":\"OK\""; then
-            log_success "Admin user mapped to kibana_admin role."
-        else
-            log_error "Failed to map admin user to kibana_admin role. Response: $MAPPING_RESPONSE. Please map the role manually and retry."
-        fi
-    fi
-
-    log_info "Creating index pattern for acm-* with @timestamp as time field (ignoring SSL verification)..."
-    INDEX_PATTERN_RESPONSE=$(curl -s -k -X POST "${OPENSEARCH_DASHBOARDS_HOST}/api/saved_objects/index-pattern/acm-index-pattern" \
-        -u admin:${NEW_ADMIN_PASSWORD} \
-        -H "Content-Type: application/json" \
-        -H "osd-xsrf: true" \
-        -d '{
-          "attributes": {
-            "title": "acm-*",
-            "timeFieldName": "@timestamp"
-          }
-        }')
-    if echo "$INDEX_PATTERN_RESPONSE" | grep -q "\"type\":\"index-pattern\""; then
-        log_success "Index pattern 'acm-*' created with @timestamp as time field."
-    else
-        log_error "Failed to create index pattern. Response: $INDEX_PATTERN_RESPONSE. Please create the index pattern manually in OpenSearch Dashboards (Stack Management -> Index Patterns -> Create Index Pattern) with pattern 'acm-*' and time field '@timestamp'."
-    fi
-}
-
-# Configure firewall rules for syslog traffic
-configure_firewall() {
-    log_info "Configuring firewall for Logstash syslog input in zone ${FIREWALL_ZONE}..."
-    if systemctl is-active --quiet firewalld; then
-        # Verify firewall zone exists
-        if ! firewall-cmd --get-zones | grep -qw "${FIREWALL_ZONE}"; then
-            log_error "Firewall zone ${FIREWALL_ZONE} does not exist. Available zones: $(firewall-cmd --get-zones)"
-        fi
-
-        # Check if zone is active
-        ACTIVE_ZONES=$(firewall-cmd --get-active-zones | grep -B1 "${FIREWALL_ZONE}" | grep -v "${FIREWALL_ZONE}")
-        if [ -z "${ACTIVE_ZONES}" ]; then
-            log_warn "Firewall zone ${FIREWALL_ZONE} is not active. Ensure it is assigned to an interface."
-        fi
-
-        # Enable IP masquerading
-        if ! firewall-cmd --query-masquerade --zone="${FIREWALL_ZONE}" &>/dev/null; then
-            sudo firewall-cmd --permanent --zone="${FIREWALL_ZONE}" --add-masquerade || {
-                log_warn "Failed to enable IP masquerading in zone ${FIREWALL_ZONE}."
-            }
-            log_success "IP masquerading enabled in zone ${FIREWALL_ZONE}."
-        else
-            log_info "IP masquerading already enabled in zone ${FIREWALL_ZONE}."
-        fi
-
-        # Add port 5514 for syslog traffic (pipeline listens on 5514)
-        for PROTO in udp tcp; do
-            if ! firewall-cmd --query-port=5514/${PROTO} --zone="${FIREWALL_ZONE}" &>/dev/null; then
-                sudo firewall-cmd --permanent --zone="${FIREWALL_ZONE}" --add-port=5514/${PROTO} || {
-                    log_warn "Failed to open port 5514/${PROTO} in zone ${FIREWALL_ZONE}."
-                }
-                log_success "Opened port 5514/${PROTO} in zone ${FIREWALL_ZONE}."
-            else
-                log_info "Port 5514/${PROTO} already open in zone ${FIREWALL_ZONE}."
-            fi
-        done
-
-        # Add port forwarding from 514 to 5514
-        for PROTO in udp tcp; do
-            if ! firewall-cmd --query-forward-port=port=514:proto=${PROTO}:toport=5514 --zone="${FIREWALL_ZONE}" &>/dev/null; then
-                sudo firewall-cmd --permanent --zone="${FIREWALL_ZONE}" --add-forward-port=port=514:proto=${PROTO}:toport=5514 || {
-                    log_warn "Failed to add port forwarding from 514/${PROTO} to 5514 in zone ${FIREWALL_ZONE}."
-                }
-                log_success "Added port forwarding from 514/${PROTO} to 5514 in zone ${FIREWALL_ZONE}."
-            else
-                log_info "Port forwarding from 514/${PROTO} to 5514 already exists in zone ${FIREWALL_ZONE}."
-            fi
-        done
-
-        # Reload firewall
-        sudo firewall-cmd --reload || log_error "Failed to reload firewall."
-        log_success "Firewall reloaded successfully."
-    else
-        log_warn "Firewalld not running. Configure firewall manually to allow UDP/TCP port 5514 and forwarding from 514 to 5514."
-    fi
-}
-
-# --- Main Script ---
-
-log_info "Starting Logstash 8.15.0 installation and configuration for OpenSearch 3.x..."
-
-# 1. Check for root privileges
-if [[ $EUID -ne 0 ]]; then
-    log_error "This script must be run as root. Please use 'sudo bash $0'."
-fi
-
-# 2. Check for Java
-check_java
-
-# 3. Install Logstash OSS
-log_info "Installing Logstash OSS ${LOGSTASH_VERSION}..."
-sudo dnf install -y "${LOGSTASH_RPM_URL}" || log_error "Failed to install Logstash."
-log_success "Logstash installed."
-
-# 4. Install OpenSearch output, JDBC integration, and GeoIP plugins
-log_info "Installing logstash-output-opensearch plugin..."
-if sudo /usr/share/logstash/bin/logstash-plugin list | grep -q "logstash-output-opensearch"; then
-    log_success "logstash-output-opensearch plugin already installed."
-else
-    log_info "Attempting to install logstash-output-opensearch with ${JAVA_HEAP_SIZE} heap..."
-    export LS_JAVA_OPTS="-Xmx${JAVA_HEAP_SIZE} -Xms${JAVA_HEAP_SIZE}"
-    if sudo -E /usr/share/logstash/bin/logstash-plugin install logstash-output-opensearch; then
-        log_success "OpenSearch output plugin installed."
-    else
-        log_info "Retrying with 4g heap..."
-        export LS_JAVA_OPTS="-Xmx4g -Xms4g"
-        if sudo -E /usr/share/logstash/bin/logstash-plugin install logstash-output-opensearch; then
-            log_success "OpenSearch output plugin installed with 4g heap."
-        else
-            log_error "Failed to install logstash-output-opensearch plugin even with 4g heap."
-        fi
-    fi
-    unset LS_JAVA_OPTS
-fi
-
-log_info "Checking for logstash-integration-jdbc plugin..."
-if sudo /usr/share/logstash/bin/logstash-plugin list | grep -q "logstash-integration-jdbc"; then
-    log_success "logstash-integration-jdbc plugin already installed, providing jdbc_streaming filter."
-else
-    log_info "Attempting to install logstash-integration-jdbc with ${JAVA_HEAP_SIZE} heap..."
-    export LS_JAVA_OPTS="-Xmx${JAVA_HEAP_SIZE} -Xms${JAVA_HEAP_SIZE}"
-    if sudo -E /usr/share/logstash/bin/logstash-plugin install logstash-integration-jdbc; then
-        log_success "logstash-integration-jdbc plugin installed."
-    else
-        log_info "Retrying with 4g heap..."
-        export LS_JAVA_OPTS="-Xmx4g -Xms4g"
-        if sudo -E /usr/share/logstash/bin/logstash-plugin install logstash-integration-jdbc; then
-            log_success "logstash-integration-jdbc plugin installed with 4g heap."
-        else
-            log_error "Failed to install logstash-integration-jdbc plugin even with 4g heap."
-        fi
-    fi
-    unset LS_JAVA_OPTS
-fi
-
-log_info "Checking for logstash-filter-geoip plugin..."
-if sudo /usr/share/logstash/bin/logstash-plugin list | grep -q "logstash-filter-geoip"; then
-    log_success "logstash-filter-geoip plugin already installed."
-else
-    log_info "Attempting to install logstash-filter-geoip with ${JAVA_HEAP_SIZE} heap..."
-    export LS_JAVA_OPTS="-Xmx${JAVA_HEAP_SIZE} -Xms${JAVA_HEAP_SIZE}"
-    if sudo -E /usr/share/logstash/bin/logstash-plugin install logstash-filter-geoip; then
-        log_success "GeoIP plugin installed."
-    else
-        log_info "Retrying with 4g heap..."
-        export LS_JAVA_OPTS="-Xmx4g -Xms4g"
-        if sudo -E /usr/share/logstash/bin/logstash-plugin install logstash-filter-geoip; then
-            log_success "GeoIP plugin installed with 4g heap."
-        else
-            log_warn "Failed to install GeoIP plugin. Pipeline will function without GeoIP enrichment. To retry, run: sudo -E LS_JAVA_OPTS=\"-Xmx4g -Xms4g\" /usr/share/logstash/bin/logstash-plugin install logstash-filter-geoip"
-        fi
-    fi
-    unset LS_JAVA_OPTS
-fi
-
-# 5. Create Logstash certificate directory
-log_info "Creating Logstash certificate directory: ${LOGSTASH_CERT_DIR}..."
-sudo mkdir -p "${LOGSTASH_CERT_DIR}" || log_error "Failed to create directory ${LOGSTASH_CERT_DIR}."
-
-# 6. Copy CA certificate to Logstash directory and set permissions
-log_info "Copying CA certificate to Logstash and set permissions..."
-sudo cp "${CA_CERT}" "${LOGSTASH_CERT_DIR}/" || log_error "Failed to copy CA certificate."
-sudo chown -R logstash:logstash "${LOGSTASH_CERT_DIR}" || log_error "Failed to set ownership for Logstash certificates."
-sudo chmod 755 "${LOGSTASH_CERT_DIR}"
-sudo chmod 644 "${LOGSTASH_CERT_DIR}/root-ca.pem"
-log_success "CA certificate copied and permissions set."
-
-# 7. Install PostgreSQL JDBC driver
-log_info "Installing PostgreSQL JDBC driver..."
-sudo mkdir -p "${JDBC_DRIVER_DIR}" || log_error "Failed to create JDBC driver directory ${JDBC_DRIVER_DIR}."
-sudo curl -o "${JDBC_DRIVER_FILE}" "${JDBC_DRIVER_URL}" || log_error "Failed to download PostgreSQL JDBC driver."
-sudo chown logstash:logstash "${JDBC_DRIVER_FILE}" || log_error "Failed to set ownership for JDBC driver."
-sudo chmod 644 "${JDBC_DRIVER_FILE}" || log_error "Failed to set permissions for JDBC driver."
-sudo chown -R logstash:logstash "${JDBC_DRIVER_DIR}" || log_error "Failed to set ownership for JDBC driver directory."
-sudo chmod -R u+rwX "${JDBC_DRIVER_DIR}" || log_error "Failed to set permissions for JDBC driver directory."
-log_success "PostgreSQL JDBC driver installed."
-
-# 8. Verify existing CA and admin certificates
-check_cert_files
-
-# 9. Check and create security_admin role if missing
-check_security_admin_role
-
-# 10. Verify admin permissions
-check_admin_permissions
-
-# 11. Configure OpenSearch Security for Logstash
-log_info "Configuring OpenSearch security for Logstash authentication..."
-
-# Configure logstash_custom role with necessary permissions
-configure_logstash_role
-
-# Create Logstash user and map to role
-create_logstash_user
-map_logstash_user
-
-# 12. Create index pattern for acm-*
-create_index_pattern
-
-# 13. Configure Logstash Pipeline
-log_info "Configuring syslog pipeline for OpenSearch output to acm-%{+YYYY.MM.dd} indices..."
-
-# Backup existing pipeline config if it exists
-if [ -f "${LOGSTASH_PIPELINE_CONF}" ]; then
-    sudo cp "${LOGSTASH_PIPELINE_CONF}" "${LOGSTASH_PIPELINE_CONF}.bak_$(date +%Y%m%d%H%M%S)" || log_warn "Failed to backup existing pipeline config."
-fi
-
-cat << 'EOF' | sudo tee "${LOGSTASH_PIPELINE_CONF}" > /dev/null
-input {
-  udp {
-    port => 5514
-    type => "syslog"
-    codec => plain { charset => "UTF-8" }
-  }
-  tcp {
-    port => 5514
-    type => "syslog"
-    codec => plain { charset => "UTF-8" }
-  }
-  beats {
-    port => 5044
-    type => "beats"
-  }
-}
-
-filter {
-  # ============================================================
-  # Stage 1: Cleaning & Strict Device Authorization
-  # ============================================================
-  
-  if [message] {
-    ruby {
-      code => '
-        if event.get("message").is_a?(String)
-          event.set("message", event.get("message").gsub(/\\x00/, ""))
-        elsif event.get("message").is_a?(Array)
-          cleaned = event.get("message").map { |m| m.gsub(/\\x00/, "") if m }
-          event.set("message", cleaned.join(" "))
-        end
-      '
-    }
-  }
-
-  # --- Step 1: Aggressive IP Extraction ---
-  ruby {
-    code => "
-      host_data = event.get('host')
-      device_ip = nil
-
-      # Case A: Syslog (String)
-      if host_data.is_a?(String)
-        device_ip = host_data
-      
-      # Case B: Beats (Hash)
-      elsif host_data.is_a?(Hash) && host_data.key?('ip')
-        ip_val = host_data['ip']
-        if ip_val.is_a?(Array)
-          device_ip = ip_val[0]
-        else
-          device_ip = ip_val
-        end
-      end
-
-      # Trim whitespace if found
-      if device_ip
-        event.set('device_ip', device_ip.strip)
-      else
-        # Critical: If no IP found, tag for immediate drop
-        event.tag('_missing_source_ip')
-      end
-    "
-  }
-
-  # --- Step 2: Database Lookup (No Cache) ---
-  if [device_ip] {
-    jdbc_streaming {
-      jdbc_driver_library => "/etc/logstash/jdbc_drivers/postgresql-42.7.5.jar"
-      jdbc_driver_class => "org.postgresql.Driver"
-      jdbc_connection_string => "jdbc:postgresql://127.0.0.1:5432/cm"
-      jdbc_user => "amp_admin"
-      jdbc_password => "Array@123$"
-      # FIX: Cast to text to prevent Type Mismatch (INET vs String)
-      statement => "SELECT name, type, device_group FROM device WHERE ip_address::text = :device_ip"
-      parameters => { "device_ip" => "device_ip" }
-      target => "device_info"
-      # FIX: Disable caching to ensure immediate updates apply
-      use_cache => false
-      tag_on_failure => ["_device_lookup_failure"]
-    }
-  }
-
-  # --- Step 3: The Gatekeeper (Fail Closed) ---
-  ruby {
-    code => "
-      # 1. Check if IP was missing
-      if event.get('tags') && event.get('tags').include?('_missing_source_ip')
-        event.cancel # DROP EVENT IMMEDIATELY
-      
-      # 2. Check DB Lookup Result
-      elsif event.get('device_ip')
-        info = event.get('device_info')
-        
-        # Validation: Must be an array, and NOT empty
-        if info && info.is_a?(Array) && !info.empty?
-          # AUTHORIZED
-          device = info[0]
-          event.set('device_name', device['name'])
-          event.set('device_type', device['type'])
-          event.set('device_group', device['device_group'])
-          event.set('auth_status', 'authorized') # Debug flag
-        else
-          # UNAUTHORIZED -> DROP
-          event.cancel # DROP EVENT IMMEDIATELY
-        end
-        
-        # Cleanup
-        event.remove('device_info')
-      end
-    "
-  }
-  
-  # --- Step 4: Final Safety Net ---
-  # If the Ruby script failed to cancel for some reason, this catches it.
-  if ![device_name] {
-    drop {}
-  }
-
-  # Clean up host field now that validation is done
-  if [host] {
-    ruby {
-      code => "
-        if event.get('host').is_a?(String)
-          event.set('[host][name]', event.get('host'))
-        end
-      "
-    }
-  }
-
-  # ============================================================
-  # Stage 2: Parsing Strategy (Unchanged)
-  # ============================================================
-
-  if [type] == "syslog" {
-    # Attempt 1: RFC 5424
-    grok {
-      match => { "message" => "^<%{NUMBER:syslog_pri}>%{NUMBER:syslog_protocol_version} %{TIMESTAMP_ISO8601:syslog_timestamp} %{HOSTNAME:syslog_hostname} %{DATA:syslog_appname} %{DATA:syslog_procid} %{DATA:syslog_msgid} (?<syslog_structured_data>-|\\[.*?\\])(?:\\s+)?%{GREEDYDATA:syslog_message}" }
-      tag_on_failure => ["_grokparsefailure_rfc5424"]
-      add_tag => ["rfc5424_header_parsed"]
-    }
-
-    if [syslog_message] { mutate { strip => ["syslog_message"] } }
-
-    # Parser A: HTTP Security / Access
-    if [syslog_message] =~ /^(HTTP security:|HTTP access:)/ {
-      mutate { gsub => [ "syslog_message", "^HTTP security:", "", "syslog_message", "^HTTP access:", "", "syslog_message", "\\\"", "" ] strip => ["syslog_message"] }
-      kv { source => "syslog_message" field_split => " " value_split => "=" target => "security_details" add_tag => ["security_kv_parsed"] }
-      if "security_kv_parsed" in [tags] {
-        mutate {
-          rename => { "[security_details][time]" => "event_time" }
-          rename => { "[security_details][severity]" => "security_severity" }
-          rename => { "[security_details][sip]" => "client_ip" }
-          rename => { "[security_details][sport]" => "source_port" }
-          rename => { "[security_details][dip]" => "destination_ip" }
-          rename => { "[security_details][dport]" => "destination_port" }
-          rename => { "[security_details][atkType]" => "attack_type" }
-          rename => { "[security_details][description]" => "attack_description" }
-          rename => { "[security_details][atkData]" => "attack_data_raw" }
-          rename => { "[security_details][reqMethod]" => "http_request_method" }
-          rename => { "[security_details][statusCode]" => "http_status_code" }
-          rename => { "[security_details][protocol]" => "network_protocol" }
-          rename => { "[security_details][reqURL]" => "http_request_url" }
-          rename => { "[security_details][sessionID]" => "session_id" }
-          rename => { "[security_details][usr]" => "user_name" }
-          rename => { "[security_details][srv]" => "server_name" }
-          rename => { "[security_details][host]" => "requested_host" }
-          convert => { "source_port" => "integer" "destination_port" => "integer" "http_status_code" => "integer" }
-        }
-        if [attack_data_raw] {
-          grok { match => { "attack_data_raw" => "Matched Data: %{DATA:matched_data} found within REQUEST_FILENAME: %{GREEDYDATA:request_filename}" } tag_on_failure => ["_grokparsefailure_attack_data"] }
-        }
-        date { match => ["event_time", "YYYY-MM-dd HH:mm:ss"] target => "event_time" }
-        mutate { remove_field => ["security_details", "attack_data_raw"] add_tag => ["an_http_log_parsed"] }
-      }
-    }
-
-    # Parser B: APP-HTTP
-    if [syslog_message] =~ /^APP-HTTP/ or [message] =~ /^APP-HTTP/ {
-      if [syslog_message] { mutate { add_field => { "[@metadata][parse_source]" => "%{syslog_message}" } } } else { mutate { add_field => { "[@metadata][parse_source]" => "%{message}" } } }
-      grok { match => { "[@metadata][parse_source]" => "^APP-HTTP %{IP:client_ip} %{WORD:http_request_method} %{URI:http_request_url} %{NUMBER:http_status_code:int} %{NUMBER:bytes_sent:int} %{NUMBER:bytes_received:int} %{NUMBER:http_version} %{IPORHOST:destination_ip} %{QUOTEDSTRING:user_agent} %{QUOTEDSTRING:http_referrer} %{WORD:cache_status} %{IPORHOST:next_hop_ip} %{WORD:virtual_server} %{NUMBER:duration_seconds:float} %{INT:unknown_field1} %{INT:unknown_field2} %{INT:unknown_field3}$" } add_tag => ["app_http_log_parsed"] tag_on_failure => ["_grokparsefailure_app_http"] }
-      if "app_http_log_parsed" in [tags] { mutate { gsub => [ "user_agent", "\"", "", "http_referrer", "\"", "" ] remove_field => ["unknown_field1", "unknown_field2", "unknown_field3"] } }
-    }
-
-    # Fallback Parsers
-    if "_grokparsefailure_rfc5424" in [tags] {
-      grok { match => { "message" => "^<%{POSINT:syslog_pri}>%{POSINT:syslog_version} %{TIMESTAMP_ISO8601:syslog_timestamp} %{HOSTNAME:syslog_hostname} %{DATA:syslog_appname} %{DATA:syslog_procid} %{INT:event_id} - AN_WELF_LOG:id=%{WORD:log_id} time=\"%{YEAR:year}-%{MONTHNUM:month}-%{MONTHDAY:day} %{HOUR:hour}:%{MINUTE:minute}:%{SECOND:second}\" fw=%{IP:virtual_ip} pri=%{POSINT:priority} proto=%{WORD:protocol} src=%{IP:client_ip} dstname=%{IP:destination_ip} arg=%{URIPATH:arg} op=%{WORD:operation} agent=\"%{DATA:user_agent}\" result=%{INT:http_result_code} sent=%{INT:bytes_sent} duration=%{NUMBER:duration_seconds} msg=\"%{GREEDYDATA:destination_data_raw}\"" } tag_on_failure => ["_grokparsefailure_an_welf_log"] }
-      if !("_grokparsefailure_an_welf_log" in [tags]) { mutate { add_field => { "log_time" => "%{year}-%{month}-%{day} %{hour}:%{minute}:%{second}" } remove_field => ["year", "month", "day", "hour", "minute", "second"] gsub => ["destination_data_raw", "\\s+", " "] } }
-    }
-    if "_grokparsefailure_rfc5424" in [tags] and "_grokparsefailure_an_welf_log" in [tags] {
-      grok { match => { "message" => "^AN_WELF_LOG:id=%{WORD:log_id} time=\"%{YEAR:year}-%{MONTHNUM:month}-%{MONTHDAY:day} %{HOUR:hour}:%{MINUTE:minute}:%{SECOND:second}\" fw=%{IP:virtual_ip} pri=%{POSINT:priority} proto=%{WORD:protocol} src=%{IP:client_ip} dstname=%{IP:destination_ip} arg=%{URIPATH:arg} op=%{WORD:operation} agent=\"%{DATA:user_agent}\" result=%{INT:http_result_code} sent=%{INT:bytes_sent} duration=%{NUMBER:duration_seconds} msg=\"%{GREEDYDATA:message_detail_raw}\"" } tag_on_failure => ["_grokparsefailure_an_welf_log_no_header"] }
-      if !("_grokparsefailure_an_welf_log_no_header" in [tags]) { mutate { add_field => { "log_time" => "%{year}-%{month}-%{day} %{hour}:%{minute}:%{second}" } add_field => { "syslog_priority" => "%{priority}" } add_field => { "syslog_timestamp" => "%{log_time}" } remove_field => ["year", "month", "day", "hour", "minute", "second"] gsub => ["message_detail_raw", "\\s+", " "] } }
-    }
-    if "_grokparsefailure_rfc5424" in [tags] and "_grokparsefailure_an_welf_log" in [tags] and "_grokparsefailure_an_welf_log_no_header" in [tags] {
-      grok { match => { "message" => "^<%{POSINT:syslog_pri}>%{MONTH:syslog_month} +%{MONTHDAY:syslog_day} %{YEAR:syslog_year} %{TIME:syslog_time} %{IPORHOST:syslog_hostname} %{GREEDYDATA:syslog_content_kv}" } tag_on_failure => ["_grokparsefailure_custom_nonstandard"] }
-      if !("_grokparsefailure_custom_nonstandard" in [tags]) {
-        date { match => ["syslog_month syslog_day syslog_year syslog_time", "MMM ddYYYY HH:mm:ss"] target => "syslog_timestamp" }
-        kv { source => "syslog_content_kv" field_split => " " value_split => "=" target => "syslog_kv_data" }
-        mutate { add_field => { "syslog_appname" => "%{[syslog_kv_data][id]}" } add_field => { "syslog_msgid" => "%{[syslog_kv_data][type]}" } add_field => { "syslog_message" => "%{[syslog_kv_data][msg]}" } rename => { "[syslog_kv_data][fw]" => "virtual_ip" } rename => { "[syslog_kv_data][src]" => "client_ip" } rename => { "[syslog_kv_data][dst]" => "destination_ip" } rename => { "[syslog_kv_data][dport]" => "destination_port" } convert => { "destination_port" => "integer" } remove_field => ["syslog_month", "syslog_day", "syslog_year", "syslog_time", "syslog_content_kv"] }
-      }
-    }
-    if "_grokparsefailure_rfc5424" in [tags] and "_grokparsefailure_an_welf_log" in [tags] and "_grokparsefailure_an_welf_log_no_header" in [tags] and "_grokparsefailure_custom_nonstandard" in [tags] {
-      grok { match => { "message" => "^<%{POSINT:syslog_pri}>%{SYSLOGTIMESTAMP:syslog_timestamp} %{IPORHOST:syslog_hostname} %{DATA:syslog_appname}(?:\\[%{POSINT:syslog_procid}\\])?:(?: %{DATA:syslog_msgid})? %{GREEDYDATA:syslog_message}" } tag_on_failure => ["_grokparsefailure_bsd"] add_tag => ["bsd_attempt"] }
-    }
-    if "_grokparsefailure_bsd" in [tags] and "_grokparsefailure_rfc5424" in [tags] { syslog_pri { } mutate { add_tag => ["fallback_parsed"] } }
-  }
-
-  # ============================================================
-  # Stage 3: Normalization
-  # ============================================================
-  if [client_ip] { mutate { copy => { "client_ip" => "src_ip" } } }
-  if [user_agent] { useragent { source => "user_agent" target => "useragent" remove_field => ["user_agent"] } }
-  
-  ruby {
-    code => "
-      if event.get('syslog_priority')
-        priority = event.get('syslog_priority').to_i
-        level = priority % 8
-        facility = priority / 8
-        level_map = { 0=>'Emergency', 1=>'Alert', 2=>'Critical', 3=>'Error', 4=>'Warning', 5=>'Notice', 6=>'Informational', 7=>'Debug' }
-        facility_map = { 0=>'Kernel', 1=>'User', 2=>'Mail', 3=>'System Daemons', 4=>'Security/Auth', 5=>'Syslog', 16=>'Local0', 23=>'Local7' }
-        event.set('severity_numeric', level)
-        event.set('severity', level_map[level] || 'Unknown')
-        event.set('log_facility', facility_map[facility] || 'Unknown')
-      end
-    "
-  }
-  
-  if [security_severity] { ruby { code => "orig = event.get('security_severity'); event.set('severity', orig.capitalize) if orig" } }
-
-  # GeoIP
-  if [client_ip] { geoip { source => "client_ip" target => "client_geoip" } }
-  if [client_ip] and ![client_geoip][location] {
-    mutate { add_field => { "[client_geoip][city_name]" => "Unknown" "[client_geoip][country_name]" => "Unresolved" } }
-    ruby { code => "event.set('[client_geoip][location]', {'lat' => 12.925415, 'lon' => 77.631492})" }
-    mutate { add_tag => ["_geoip_client_unresolved"] }
-  }
-  if [destination_ip] { geoip { source => "destination_ip" target => "destination_geoip" } }
-  if [destination_ip] and ![destination_geoip][location] {
-    mutate { add_field => { "[destination_geoip][city_name]" => "Unknown" "[destination_geoip][country_name]" => "Unresolved" } }
-    ruby { code => "event.set('[destination_geoip][location]', {'lat' => 0.0, 'lon' => 0.0})" }
-    mutate { add_tag => ["_geoip_destination_unresolved"] }
-  }
-
-  if "rfc5424_header_parsed" in [tags] or "an_http_log_parsed" in [tags] or "app_http_log_parsed" in [tags] {
-    mutate { remove_tag => ["_grokparsefailure_rfc5424", "_grokparsefailure_an_welf_log", "_grokparsefailure_an_welf_log_no_header", "_grokparsefailure_custom_nonstandard", "_grokparsefailure_bsd"] add_tag => ["log_fully_parsed"] }
-  }
-}
-
-output {
-  stdout { codec => rubydebug }
-  opensearch {
-    hosts => ["https://127.0.0.1:9200"]
-    index => "acm-%{+YYYY.MM.dd}"
-    user => "logstash"
-    password => "Array@123"
-    ssl => true
-    ssl_certificate_verification => true
-    cacert => "/etc/logstash/certs/root-ca.pem"
-    manage_template => false
-  }
-}
-EOF
-
-sudo chown logstash:logstash "${LOGSTASH_PIPELINE_CONF}"
-sudo chmod 644 "${LOGSTASH_PIPELINE_CONF}"
-log_success "Logstash syslog pipeline configured to write to acm-%{+YYYY.MM.dd} indices. Customize ${LOGSTASH_PIPELINE_CONF} for production (e.g., update jdbc_connection_string, jdbc_user, jdbc_password)."
-
-# 14. Ensure Logstash data directory exists and has correct permissions
-log_info "Configuring Logstash data directory..."
-LOGSTASH_DATA_DIR="/usr/share/logstash/data"
-sudo mkdir -p "${LOGSTASH_DATA_DIR}" || log_error "Failed to create Logstash data directory ${LOGSTASH_DATA_DIR}."
-sudo chown -R logstash:logstash "${LOGSTASH_DATA_DIR}" || log_error "Failed to set ownership for Logstash data directory."
-sudo chmod -R u+rwX "${LOGSTASH_DATA_DIR}" || log_error "Failed to set permissions for Logstash data directory."
-log_success "Logstash data directory configured."
-
-# 15. Configure Logstash systemd service
-log_info "Configuring Logstash systemd service..."
-LOGSTASH_SERVICE_FILE="/usr/lib/systemd/system/${LOGSTASH_SERVICE}.service"
-
-# Backup existing service file if it exists
-if [ -f "${LOGSTASH_SERVICE_FILE}" ]; then
-    sudo cp "${LOGSTASH_SERVICE_FILE}" "${LOGSTASH_SERVICE_FILE}.bak_$(date +%Y%m%d%H%M%S)" || log_warn "Failed to backup Logstash service file."
-fi
-
-cat << EOF | sudo tee "${LOGSTASH_SERVICE_FILE}" > /dev/null
-[Unit]
-Description=Logstash
-Documentation=https://www.elastic.co/guide/en/logstash/current/index.html
-Wants=network-online.target
-After=network-online.target
-
-[Service]
-Type=simple
-User=logstash
-Group=logstash
-Environment="JAVA_HOME=${JAVA_HOME_PATH}"
-Environment="LS_JAVA_OPTS=-Xmx${JAVA_HEAP_SIZE} -Xms${JAVA_HEAP_SIZE}"
-ExecStart=/usr/share/logstash/bin/logstash --path.settings ${LOGSTASH_CONFIG_DIR}
-Restart=on-failure
-StandardOutput=journal
-StandardError=inherit
-SyslogIdentifier=logstash
-LimitNOFILE=65535
-LimitNPROC=4096
-LimitAS=infinity
-LimitFSIZE=infinity
-TimeoutStopSec=0
-KillSignal=SIGTERM
-KillMode=process
-SendSIGKILL=no
-
-[Install]
-WantedBy=multi-user.target
-EOF
-
-sudo chmod 644 "${LOGSTASH_SERVICE_FILE}"
-sudo chown root:root "${LOGSTASH_SERVICE_FILE}"
-log_success "Logstash systemd service configured."
-
-# 16. Start Logstash
-log_info "Enabling and starting Logstash service..."
-sudo systemctl daemon-reload || log_error "Failed to reload systemd daemon."
-sudo systemctl enable "${LOGSTASH_SERVICE}" || log_error "Failed to enable Logstash service."
-sudo systemctl restart "${LOGSTASH_SERVICE}" || log_error "Failed to start Logstash service."
-
-# 17. Wait for Logstash
-log_info "Waiting for Logstash to start..."
-ATTEMPTS=60
-for i in $(seq 1 $ATTEMPTS); do
-    if sudo journalctl -u logstash -n 10 | grep -q "Pipelines running"; then
-        log_success "Logstash service is up!"
-        break
-    else
-        log_info "Attempt $i/$ATTEMPTS: Logstash not ready. Waiting 10 seconds..."
-        sleep 10
-    fi
-    if [ $i -eq $ATTEMPTS ]; then
-        log_error "Logstash did not start. Check logs with 'journalctl -u logstash'."
-    fi
-done
-
-# 18. Configure Firewall
-configure_firewall
-
-# 19. Final Output
-log_success "Logstash 8.15.0 installation and configuration for OpenSearch 3.x complete!"
-log_info "Logstash is configured to process syslog messages on port 5514 (with forwarding from 514) and write to OpenSearch acm-%{+YYYY.MM.dd} indices at ${OPENSEARCH_HOST}."
-log_info "Index pattern 'acm-*' is configured with @timestamp as the time field for OpenSearch Dashboards."
\ No newline at end of file
Index: /branches/amp_4_0/platform/tools/install_nginx.sh
===================================================================
--- /branches/amp_4_0/platform/tools/install_nginx.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/install_nginx.sh	(nonexistent)
@@ -1,551 +0,0 @@
-#!/bin/bash
-
-# Script to configure Nginx on Rocky Linux 9.5 as a reverse proxy for:
-# - Custom app (backend at http://127.0.0.1:3033 or static page) on HTTPS (port 443) at root (e.g., /, /login)
-# - Opensearch-Dashboards (backend at http://127.0.0.1:5601) accessible via /visualization on HTTPS (port 443)
-# - Grafana (backend at http://127.0.0.1:3000) accessible via /monitoring on HTTPS (port 443)
-# Uses self-signed certificates and the server's IP address instead of a domain name.
-# Opensearch-Dashboards paths are scoped under /visualization/ and Grafana under /monitoring/ to avoid conflicts with custom app paths (e.g., /login).
-# Fixes duplicate server.host and handles redirects to stay under respective paths.
-
-# --- Variables ---
-INSTALL_LOG_FILE="/var/log/nginx_installation.log"
-NGINX_CONF_D_DIR="/etc/nginx/conf.d"
-NGINX_DEFAULT_CONF="${NGINX_CONF_D_DIR}/default.conf"
-NGINX_APP_CONF="${NGINX_CONF_D_DIR}/app.conf"
-NGINX_TEMPLATE_CONF="/ca/webui/conf/nginx_template.conf"
-DEFAULT_SSL_KEY="/ca/webui/conf/server.key"
-DEFAULT_SSL_CRT="/ca/webui/conf/server.crt"
-SSL_DIR="/etc/nginx/ssl"
-SSL_KEY="${SSL_DIR}/server.key"
-SSL_CRT="${SSL_DIR}/server.crt"
-SSL_COMMON_NAME="${SERVER_IP:-$(ip addr show | grep -oE 'inet [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | awk '{print $2}' | grep -v '127.0.0.1' | head -1)}"
-SSL_DAYS=365
-BACKEND_URL="http://127.0.0.1:8000" # Backend for custom app
-OPENSEARCH_BACKEND_URL="https://127.0.0.1:5601" # Opensearch backend
-#GRAFANA_BACKEND_URL="http://127.0.0.1:3000" # Grafana backend
-
-# --- Functions ---
-log_info() {
-    echo -e "\e[32m[INFO] $1\e[0m" | tee -a "${INSTALL_LOG_FILE}"
-}
-
-log_warning() {
-    echo -e "\e[33m[WARN] $1\e[0m" | tee -a "${INSTALL_LOG_FILE}"
-}
-
-log_error() {
-    echo -e "\e[31m[ERROR] $1\e[0m" | tee -a "${INSTALL_LOG_FILE}"
-    exit 1
-}
-
-check_service() {
-    local url=$1
-    local name=$2
-    log_info "Checking if ${name} is running at ${url}..."
-    if ! curl -s -k --connect-timeout 5 "${url}" &>/dev/null; then
-        log_error "${name} is not running at ${url}. Start the service or update the configuration."
-    fi
-    log_info "${name} is accessible at ${url}."
-}
-
-# --- Pre-installation Setup ---
-> "${INSTALL_LOG_FILE}"
-sudo chmod 640 "${INSTALL_LOG_FILE}"
-sudo chown root:adm "${INSTALL_LOG_FILE}"
-log_info "Starting Nginx installation script. Logs are saved to ${INSTALL_LOG_FILE}"
-
-if [[ $EUID -ne 0 ]]; then
-    log_error "This script must be run as root. Please use sudo."
-fi
-
-if ! grep -q "Rocky Linux 9" /etc/os-release; then
-    log_error "This script is intended for Rocky Linux 9.x. Exiting."
-fi
-
-if [ -z "${SSL_COMMON_NAME}" ]; then
-    log_error "Could not determine server IP address. Set SERVER_IP environment variable."
-fi
-log_info "Using server IP: ${SSL_COMMON_NAME}"
-
-if rpm -q nginx &>/dev/null; then
-    log_info "Nginx is already installed. Proceeding with configuration."
-else
-    # --- Remove or Disable nginx-stable Repository ---
-    if [ -f "/etc/yum.repos.d/nginx.repo" ]; then
-        log_info "Removing existing nginx-stable repository to avoid conflicts..."
-        sudo rm /etc/yum.repos.d/nginx.repo &>> "${INSTALL_LOG_FILE}"
-        if [ $? -ne 0 ]; then
-            log_error "Failed to remove nginx-stable repository. Check ${INSTALL_LOG_FILE}."
-        fi
-    fi
-
-    # --- Install Nginx from AppStream ---
-    log_info "Installing Nginx from Rocky Linux AppStream repository..."
-    sudo dnf install -y nginx policycoreutils-python-utils &>> "${INSTALL_LOG_FILE}"
-    if [ $? -ne 0 ]; then
-        log_error "Failed to install Nginx. Check ${INSTALL_LOG_FILE}."
-    fi
-    log_info "Nginx installed successfully."
-fi
-
-# --- Check Backend Services ---
-check_service "${OPENSEARCH_BACKEND_URL}" "Opensearch-Dashboards"
-#check_service "${GRAFANA_BACKEND_URL}" "Grafana"
-if curl -s --connect-timeout 5 "${BACKEND_URL}" &>/dev/null; then
-    log_info "Main application is accessible at ${BACKEND_URL}."
-else
-    log_warning "Main application is not running at ${BACKEND_URL}. Using static page instead."
-    USE_STATIC_PAGE=true
-    if [ ! -f "/usr/share/nginx/html/index.html" ]; then
-        log_warning "Static index.html not found. Creating a default page."
-        echo "<html><body><h1>Welcome to Your Application</h1></body></html>" | sudo tee /usr/share/nginx/html/index.html &>> "${INSTALL_LOG_FILE}"
-    fi
-fi
-
-# --- Create SSL Directory and Self-Signed Certificate ---
-log_info "Creating SSL directory: ${SSL_DIR}"
-sudo mkdir -p "${SSL_DIR}" &>> "${INSTALL_LOG_FILE}"
-sudo chmod 700 "${SSL_DIR}" &>> "${INSTALL_LOG_FILE}"
-
-if [ ! -f "${SSL_CRT}" ] || [ ! -f "${SSL_KEY}" ]; then
-    log_info "Generating self-signed SSL certificate for ${SSL_COMMON_NAME}..."
-    if ! command -v openssl &>/dev/null; then
-        log_warning "openssl not found. Installing openssl..."
-        sudo dnf install -y openssl &>> "${INSTALL_LOG_FILE}"
-        if [ $? -ne 0 ]; then
-            log_error "Failed to install openssl. Cannot generate certificate."
-        fi
-    fi
-
-    sudo openssl req -x509 -nodes -days "${SSL_DAYS}" -newkey rsa:2048 \
-      -keyout "${SSL_KEY}" \
-      -out "${SSL_CRT}" \
-      -subj "/C=IN/ST=Karnataka/L=BLR/O=Array Networks, Inc/OU=AMP/CN=${SSL_COMMON_NAME}" &>> "${INSTALL_LOG_FILE}"
-    if [ $? -ne 0 ]; then
-        log_error "Failed to generate self-signed certificate. Check ${INSTALL_LOG_FILE}."
-    fi
-    sudo chmod 600 "${SSL_KEY}"
-    log_info "Self-signed certificate generated: ${SSL_CRT} and ${SSL_KEY}"
-    sudo cp "${SSL_KEY}" "${DEFAULT_SSL_KEY}"
-    sudo cp "${SSL_CRT}" "${DEFAULT_SSL_CRT}"
-    log_info "Copied the generated certificates to default cert path: ${DEFAULT_SSL_CRT}"
-else
-    log_info "Using existing SSL certificate: ${SSL_CRT} and ${SSL_KEY}"
-fi
-
-# --- Configure SELinux ---
-log_info "Configuring SELinux for Nginx..."
-if command -v semanage &>/dev/null; then
-    sudo semanage fcontext -a -t httpd_config_t "${SSL_DIR}(/.*)?" &>> "${INSTALL_LOG_FILE}"
-    sudo restorecon -R -v "${SSL_DIR}" &>> "${INSTALL_LOG_FILE}"
-    sudo setsebool -P httpd_can_network_connect 1 &>> "${INSTALL_LOG_FILE}"
-    sudo semanage port -a -t http_port_t -p tcp 8000 2>/dev/null || sudo semanage port -m -t http_port_t -p tcp 8000 &>> "${INSTALL_LOG_FILE}"
-    sudo semanage port -a -t http_port_t -p tcp 5601 2>/dev/null || sudo semanage port -m -t http_port_t -p tcp 5601 &>> "${INSTALL_LOG_FILE}"
-    sudo semanage port -a -t http_port_t -p tcp 3000 2>/dev/null || sudo semanage port -m -t http_port_t -p tcp 3000 &>> "${INSTALL_LOG_FILE}"
-else
-    log_warning "SELinux tools not found. Skipping SELinux configuration. Set SELinux to permissive mode or manually configure."
-    log_info "To set SELinux to permissive: sudo setenforce 0"
-fi
-
-# --- Configure FirewallD ---
-log_info "Configuring FirewallD for HTTP (80) and HTTPS (443)..."
-if ! systemctl is-active --quiet firewalld; then
-    log_warning "FirewallD is not running. Starting and enabling it."
-    sudo systemctl start firewalld &>> "${INSTALL_LOG_FILE}"
-    sudo systemctl enable firewalld &>> "${INSTALL_LOG_FILE}"
-    if [ $? -ne 0 ]; then
-        log_error "Failed to start or enable FirewallD. Check ${INSTALL_LOG_FILE}."
-    fi
-fi
-
-sudo firewall-cmd --permanent --add-service=http &>> "${INSTALL_LOG_FILE}"
-sudo firewall-cmd --permanent --add-service=https &>> "${INSTALL_LOG_FILE}"
-sudo firewall-cmd --reload &>> "${INSTALL_LOG_FILE}"
-if [ $? -ne 0 ]; then
-    log_error "Failed to configure FirewallD. Check ${INSTALL_LOG_FILE}."
-fi
-log_info "FirewallD configured to allow HTTP and HTTPS traffic."
-
-## --- Configure Grafana Base Path ---
-#log_info "Configuring Grafana with base path /monitoring..."
-#GRAFANA_CONF="/etc/grafana/grafana.ini"
-#if [ -f "${GRAFANA_CONF}" ]; then
-#    # Update Grafana configuration for base path
-#    sudo sed -i 's|^;\?\s*root_url\s*=.*|root_url = https://'${SSL_COMMON_NAME}'/monitoring/|' "${GRAFANA_CONF}" &>> "${INSTALL_LOG_FILE}"
-#    sudo sed -i 's|^;\?\s*serve_from_sub_path\s*=.*|serve_from_sub_path = true|' "${GRAFANA_CONF}" &>> "${INSTALL_LOG_FILE}"
-#    log_info "Restarting Grafana to apply configuration..."
-#    sudo systemctl restart grafana-server &>> "${INSTALL_LOG_FILE}"
-#    if [ $? -ne 0 ]; then
-#        log_error "Failed to restart Grafana. Check 'sudo systemctl status grafana-server' or ${INSTALL_LOG_FILE}."
-#    fi
-#    log_info "Grafana configured with base path /monitoring."
-#else
-#    log_error "Grafana configuration file not found at ${GRAFANA_CONF}. Ensure Grafana is installed and configured."
-#fi
-
-# --- Configure Nginx ---
-log_info "Configuring Nginx for custom app, Opensearch-Dashboards, and Grafana."
-if [ -f "${NGINX_DEFAULT_CONF}" ]; then
-    log_info "Removing default Nginx configuration file: ${NGINX_DEFAULT_CONF}"
-    sudo rm "${NGINX_DEFAULT_CONF}" &>> "${INSTALL_LOG_FILE}"
-fi
-
-if [ -f "${NGINX_APP_CONF}" ]; then
-    log_info "Backing up existing Nginx configuration: ${NGINX_APP_CONF}"
-    sudo mv "${NGINX_APP_CONF}" "${NGINX_APP_CONF}.backup-$(date +%F_%T)" &>> "${INSTALL_LOG_FILE}"
-fi
-
-log_info "Creating Nginx configuration file: ${NGINX_APP_CONF}"
-cat <<EOF | sudo tee "${NGINX_APP_CONF}" &>> "${INSTALL_LOG_FILE}"
-# /etc/nginx/conf.d/app.conf
-
-# Extract access_token from cookie, fallback to empty string
-map \$http_cookie \$jwt_token {
-    default "";
-    "~access_token=([^;]+)" \$1;
-}
-
-# --- HTTP Server Block (Redirects to HTTPS) ---
-server {
-    listen 80;
-    listen [::]:80;
-    server_name ${SSL_COMMON_NAME};
-
-    return 301 https://\$host\$request_uri;
-}
-
-# --- HTTPS Server Block ---
-server {
-    listen 443 ssl;
-    listen [::]:443 ssl;
-    server_name ${SSL_COMMON_NAME};
-
-    ssl_certificate ${SSL_CRT};
-    ssl_certificate_key ${SSL_KEY};
-
-    ssl_session_cache shared:SSL:10m;
-    ssl_session_timeout 10m;
-    ssl_protocols TLSv1.2 TLSv1.3;
-    ssl_prefer_server_ciphers on;
-    ssl_ciphers 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305';
-
-    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
-
-    # Enable access logging for debugging
-    access_log /var/log/nginx/access.log;
-
-    # Set access_token cookie and redirect to clean URL without query param
-    location /visualization {
-        if (\$arg_access_token) {
-            add_header Set-Cookie "access_token=\$arg_access_token; Path=/visualization/; HttpOnly";
-            return 302 /visualization/;
-        }
-        proxy_pass https://127.0.0.1:5601;
-        proxy_http_version 1.1;
-        proxy_set_header Host \$host;
-        proxy_set_header X-Real-IP \$remote_addr;
-        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
-        proxy_set_header X-Forwarded-Proto \$scheme;
-        proxy_set_header Authorization "Bearer \$jwt_token";
-        proxy_set_header Upgrade \$http_upgrade;
-        proxy_set_header Connection "upgrade";
-        proxy_cache_bypass \$http_upgrade;
-        proxy_read_timeout 60s;
-        proxy_connect_timeout 60s;
-    }
-
-    location ^~ /visualization/ {
-        proxy_pass https://127.0.0.1:5601;
-        proxy_http_version 1.1;
-        proxy_set_header Host \$host;
-        proxy_set_header X-Real-IP \$remote_addr;
-        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
-        proxy_set_header X-Forwarded-Proto \$scheme;
-        proxy_set_header Authorization "Bearer \$jwt_token";
-        proxy_set_header Upgrade \$http_upgrade;
-        proxy_set_header Connection "upgrade";
-        proxy_cache_bypass \$http_upgrade;
-        proxy_read_timeout 60s;
-        proxy_connect_timeout 60s;
-    }
-
-    location ~* ^/visualization/(app|api|public|built_assets)/ {
-        proxy_pass https://127.0.0.1:5601;
-        proxy_http_version 1.1;
-        proxy_set_header Host \$host;
-        proxy_set_header X-Real-IP \$remote_addr;
-        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
-        proxy_set_header X-Forwarded-Proto \$scheme;
-        proxy_set_header Authorization "Bearer \$jwt_token";
-        proxy_set_header Upgrade \$http_upgrade;
-        proxy_set_header Connection "upgrade";
-        proxy_cache_bypass \$http_upgrade;
-        proxy_read_timeout 60s;
-        proxy_connect_timeout 60s;
-    }
-
-    # Grafana at /monitoring/
-    location /monitoring/ {
-        proxy_pass http://127.0.0.1:3000;  # No trailing slash!
-        proxy_http_version 1.1;
-        proxy_set_header Host \$host;
-        proxy_set_header X-Real-IP \$remote_addr;
-        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
-        proxy_set_header X-Forwarded-Proto \$scheme;
-        proxy_set_header Upgrade \$http_upgrade;
-        proxy_set_header Connection "upgrade";
-        proxy_cache_bypass \$http_upgrade;
-    }
-
-    # --- Backend API Proxy at /api/v2/ ---
-    location ^~ /api/v2/ {
-        # Proxy to the backend service running on port 8000
-        proxy_pass ${BACKEND_URL}/;
-
-        proxy_http_version 1.1;
-        proxy_set_header Host \$host;
-        proxy_set_header X-Real-IP \$remote_addr;
-        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
-        proxy_set_header X-Forwarded-Proto \$scheme;
-        proxy_set_header Authorization "Bearer \$jwt_token";
-        proxy_set_header Upgrade \$http_upgrade;
-        proxy_set_header Connection "upgrade";
-        proxy_read_timeout 60s;
-        proxy_connect_timeout 60s;
-    }
-
-    # Main application (GUI)
-    location / {
-        root /usr/share/amp-gui;
-        index index.html;
-        try_files \$uri \$uri/ /index.html;
-    }
-
-    location ~ /\. {
-        deny all;
-    }
-
-    error_page 404 /404.html;
-    location = /404.html {
-        root /usr/share/nginx/html;
-        internal;
-    }
-
-    error_page 500 502 503 504 /50x.html;
-    location = /50x.html {
-        root /usr/share/nginx/html;
-        internal;
-    }
-}
-EOF
-
-if [ $? -ne 0 ]; then
-    log_error "Failed to create Nginx configuration file. Check ${INSTALL_LOG_FILE}."
-fi
-log_info "Nginx configuration created: ${NGINX_APP_CONF}"
-
-cat <<EOF | sudo tee "${NGINX_TEMPLATE_CONF}" &>> "${INSTALL_LOG_FILE}"
-# /etc/nginx/conf.d/app.conf
-
-# Extract access_token from cookie, fallback to empty string
-map \$\$http_cookie \$\$jwt_token {
-    default "";
-    "~access_token=([^;]+)" \$\$1;
-}
-
-# --- HTTP Server Block (Redirects to HTTPS) ---
-server {
-    listen 80;
-    listen [::]:80;
-    server_name ${SSL_COMMON_NAME};
-
-    return 301 https://\$\$host\$\$request_uri;
-}
-
-# --- HTTPS Server Block ---
-server {
-    listen \$webui_listen ssl;
-    listen [::]:\$webui_listen ssl;
-    server_name ${SSL_COMMON_NAME};
-
-    ssl_certificate \$webui_ssl_cert;
-    ssl_certificate_key \$webui_ssl_key;
-
-    ssl_session_cache shared:SSL:10m;
-    ssl_session_timeout 10m;
-    ssl_protocols TLSv1.2 TLSv1.3;
-    ssl_prefer_server_ciphers on;
-    ssl_ciphers 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305';
-
-    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
-
-    # Enable access logging for debugging
-    access_log /var/log/nginx/access.log;
-
-    # Opensearch-Dashboards at /visualization/
-    # Set access_token cookie and redirect to clean URL without query param
-    location /visualization {
-        if (\$\$arg_access_token) {
-            add_header Set-Cookie "access_token=\$\$arg_access_token; Path=/visualization/; HttpOnly";
-            return 302 /visualization/;
-        }
-        proxy_pass https://127.0.0.1:5601;
-        proxy_http_version 1.1;
-        proxy_set_header Host \$\$host;
-        proxy_set_header X-Real-IP \$\$remote_addr;
-        proxy_set_header X-Forwarded-For \$\$proxy_add_x_forwarded_for;
-        proxy_set_header X-Forwarded-Proto \$\$scheme;
-        proxy_set_header Authorization "Bearer \$\$jwt_token";
-        proxy_set_header Upgrade \$\$http_upgrade;
-        proxy_set_header Connection "upgrade";
-        proxy_cache_bypass \$\$http_upgrade;
-        proxy_read_timeout 60s;
-        proxy_connect_timeout 60s;
-    }
-
-    location ^~ /visualization/ {
-        proxy_pass https://127.0.0.1:5601;
-        proxy_http_version 1.1;
-        proxy_set_header Host \$\$host;
-        proxy_set_header X-Real-IP \$\$remote_addr;
-        proxy_set_header X-Forwarded-For \$\$proxy_add_x_forwarded_for;
-        proxy_set_header X-Forwarded-Proto \$\$scheme;
-        proxy_set_header Authorization "Bearer \$\$jwt_token";
-        proxy_set_header Upgrade \$\$http_upgrade;
-        proxy_set_header Connection "upgrade";
-        proxy_cache_bypass \$\$http_upgrade;
-        proxy_read_timeout 60s;
-        proxy_connect_timeout 60s;
-    }
-
-    location ~* ^/visualization/(app|api|public|built_assets)/ {
-        proxy_pass https://127.0.0.1:5601;
-        proxy_http_version 1.1;
-        proxy_set_header Host \$\$host;
-        proxy_set_header X-Real-IP \$\$remote_addr;
-        proxy_set_header X-Forwarded-For \$\$proxy_add_x_forwarded_for;
-        proxy_set_header X-Forwarded-Proto \$\$scheme;
-        proxy_set_header Authorization "Bearer \$\$jwt_token";
-        proxy_set_header Upgrade \$\$http_upgrade;
-        proxy_set_header Connection "upgrade";
-        proxy_cache_bypass \$\$http_upgrade;
-        proxy_read_timeout 60s;
-        proxy_connect_timeout 60s;
-    }
-
-    # Grafana at /monitoring/
-    location /monitoring/ {
-        proxy_pass http://127.0.0.1:3000;  # No trailing slash!
-        proxy_http_version 1.1;
-        proxy_set_header Host \$\$host;
-        proxy_set_header X-Real-IP \$\$remote_addr;
-        proxy_set_header X-Forwarded-For \$\$proxy_add_x_forwarded_for;
-        proxy_set_header X-Forwarded-Proto \$\$scheme;
-        proxy_set_header Upgrade \$\$http_upgrade;
-        proxy_set_header Connection "upgrade";
-        proxy_cache_bypass \$\$http_upgrade;
-    }
-
-    # --- Backend API Proxy at /api/v2/ ---
-    location ^~ /api/v2/ {
-        # Proxy to the backend service running on port 8000
-        proxy_pass ${BACKEND_URL}/;
-
-        proxy_http_version 1.1;
-        proxy_set_header Host \$host;
-        proxy_set_header X-Real-IP \$remote_addr;
-        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
-        proxy_set_header X-Forwarded-Proto \$scheme;
-        proxy_set_header Authorization "Bearer \$jwt_token";
-        proxy_set_header Upgrade \$http_upgrade;
-        proxy_set_header Connection "upgrade";
-        proxy_read_timeout 60s;
-        proxy_connect_timeout 60s;
-    }
-
-    # Main application (GUI)
-    location / {
-        root /usr/share/amp-gui;
-        index index.html;
-        try_files \$uri \$uri/ /index.html;
-    }
-
-    location ~ /\. {
-        deny all;
-    }
-
-    error_page 404 /404.html;
-    location = /404.html {
-        root /usr/share/nginx/html;
-        internal;
-    }
-
-    error_page 500 502 503 504 /50x.html;
-    location = /50x.html {
-        root /usr/share/nginx/html;
-        internal;
-    }
-}
-EOF
-# --- Test Nginx configuration ---
-log_info "Testing Nginx configuration syntax..."
-sudo nginx -t &>> "${INSTALL_LOG_FILE}"
-if [ $? -ne 0 ]; then
-    log_error "Nginx configuration test failed! Check ${INSTALL_LOG_FILE} for syntax errors."
-fi
-log_info "Nginx configuration syntax is OK."
-
-# --- Start and Enable Nginx Service ---
-log_info "Starting and enabling Nginx service..."
-sudo systemctl start nginx &>> "${INSTALL_LOG_FILE}"
-sudo systemctl enable nginx &>> "${INSTALL_LOG_FILE}"
-if [ $? -ne 0 ]; then
-    log_error "Failed to start or enable Nginx. Check 'sudo systemctl status nginx' or ${INSTALL_LOG_FILE}."
-fi
-log_info "Nginx service started and enabled successfully."
-
-# --- Verify Accessibility ---
-log_info "Verifying Opensearch-Dashboards accessibility at https://${SSL_COMMON_NAME}/visualization..."
-if ! curl -s --connect-timeout 5 --insecure "https://${SSL_COMMON_NAME}/visualization" &>/dev/null; then
-    log_warning "Opensearch-Dashboards is not accessible at https://${SSL_COMMON_NAME}/visualization. Checking backend directly..."
-    if ! curl -s --connect-timeout 5 "${OPENSEARCH_BACKEND_URL}" &>/dev/null; then
-        log_error "Opensearch-Dashboards backend (${OPENSEARCH_BACKEND_URL}) is not responding. Check 'sudo systemctl status opensearch-dashboards' or ${INSTALL_LOG_FILE}."
-    fi
-    log_warning "Opensearch-Dashboards backend is responding, but proxying failed. Check Nginx logs: /var/log/nginx/error.log and /var/log/nginx/access.log"
-fi
-log_info "Opensearch-Dashboards is accessible at https://${SSL_COMMON_NAME}/visualization."
-
-#log_info "Verifying Grafana accessibility at https://${SSL_COMMON_NAME}/monitoring..."
-#if ! curl -s --connect-timeout 5 --insecure "https://${SSL_COMMON_NAME}/monitoring" &>/dev/null; then
-#    log_warning "Grafana is not accessible at https://${SSL_COMMON_NAME}/monitoring. Checking backend directly..."
-#    if ! curl -s --connect-timeout 5 "${GRAFANA_BACKEND_URL}" &>/dev/null; then
-#        log_error "Grafana backend (${GRAFANA_BACKEND_URL}) is not responding. Check 'sudo systemctl status grafana-server' or ${INSTALL_LOG_FILE}."
-#    fi
-#    log_warning "Grafana backend is responding, but proxying failed. Check Nginx logs: /var/log/nginx/error.log and /var/log/nginx/access.log"
-#fi
-#log_info "Grafana is accessible at https://${SSL_COMMON_NAME}/monitoring."
-
-# --- Verify Path Accessibility ---
-log_info "Verifying path accessibility for Opensearch-Dashboards, Grafana, and custom app..."
-if ! curl -s --connect-timeout 5 --insecure "https://${SSL_COMMON_NAME}/visualization/login" &>/dev/null; then
-    log_warning "Opensearch-Dashboards login page not accessible at https://${SSL_COMMON_NAME}/visualization/login. Check Opensearch-Dashboards status."
-fi
-#if ! curl -s --connect-timeout 5 --insecure "https://${SSL_COMMON_NAME}/monitoring/login" &>/dev/null; then
-#    log_warning "Grafana login page not accessible at https://${SSL_COMMON_NAME}/monitoring/login. Check Grafana status."
-#fi
-if ! curl -s --connect-timeout 5 --insecure "https://${SSL_COMMON_NAME}/login" &>/dev/null; then
-    log_warning "Custom app login page not accessible at https://${SSL_COMMON_NAME}/login. Check app backend or Nginx configuration."
-fi
-log_info "Path accessibility check completed."
-
-log_info "Nginx configuration complete. Serving custom app, Opensearch-Dashboards, and Grafana on port 443."
-log_info "Ensure your firewall allows inbound traffic to ports 80 and 443."
-log_info "************************************************************************************************"
-log_info "** IMPORTANT ACCESS INFORMATION **"
-log_info "************************************************************************************************"
-log_info "- Custom application: https://${SSL_COMMON_NAME} (e.g., /, /login)"
-log_info "- Opensearch-Dashboards: https://${SSL_COMMON_NAME}/visualization (e.g., /visualization/login)"
-#log_info "- Grafana: https://${SSL_COMMON_NAME}/monitoring (e.g., /monitoring/login)"
-log_warning "Browsers will show security warnings for the self-signed certificate. For production, obtain a trusted certificate from Let's Encrypt: https://letsencrypt.org/"
-log_info "Full installation log: ${INSTALL_LOG_FILE}"
Index: /branches/amp_4_0/platform/tools/install_pgbouncer.sh
===================================================================
--- /branches/amp_4_0/platform/tools/install_pgbouncer.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/install_pgbouncer.sh	(nonexistent)
@@ -1,121 +0,0 @@
-#!/bin/bash
-
-PG_HOST="127.0.0.1"
-PG_PORT="5432"
-PG_DB="cm"
-PG_USER="amp_admin"
-PG_PASS="Array@123$"
-
-PGBOUNCER_PORT="6432"
-PGBOUNCER_LISTEN_ADDR="0.0.0.0"
-POOL_MODE="transaction"
-DEFAULT_POOL_SIZE="50"
-RESERVE_POOL_SIZE="10"
-MAX_CLIENT_CONN="1000"
-
-# Paths
-PGBOUNCER_INI="/etc/pgbouncer/pgbouncer.ini"
-USERLIST="/etc/pgbouncer/userlist.txt"
-PGBOUNCER_LOG_DIR="/var/log/pgbouncer"
-LOG_FILE="${PGBOUNCER_LOG_DIR}/pgbouncer.log"
-
-# --- Script Start ---
-echo "[+] Starting PgBouncer setup script (single user mode)..."
-
-# Ensure script is run as root
-if [ "$EUID" -ne 0 ]; then
-    echo "ERROR: This script must be run as root."
-    exit 1
-fi
-
-# Install PgBouncer and Firewalld
-echo "[+] Installing PgBouncer and Firewalld..."
-dnf install -y pgbouncer firewalld || { echo "ERROR: Installation failed. Exiting."; exit 1; }
-
-# Create log directory and set permissions
-echo "[+] Creating PgBouncer log directory: ${PGBOUNCER_LOG_DIR}"
-mkdir -p "$PGBOUNCER_LOG_DIR"
-chown pgbouncer:pgbouncer "$PGBOUNCER_LOG_DIR"
-chmod 750 "$PGBOUNCER_LOG_DIR"
-touch "$LOG_FILE"
-chown pgbouncer:pgbouncer "$LOG_FILE"
-chmod 640 "$LOG_FILE"
-
-# --- Create pgbouncer.ini ---
-echo "[+] Creating pgbouncer.ini..."
-cat > "$PGBOUNCER_INI" <<EOF
-[databases]
-${PG_DB} = host=${PG_HOST} port=${PG_PORT} dbname=${PG_DB} user=${PG_USER} password=${PG_PASS}
-
-[pgbouncer]
-listen_addr = ${PGBOUNCER_LISTEN_ADDR}
-listen_port = ${PGBOUNCER_PORT}
-auth_type = md5
-auth_file = ${USERLIST}
-pool_mode = ${POOL_MODE}
-max_client_conn = ${MAX_CLIENT_CONN}
-default_pool_size = ${DEFAULT_POOL_SIZE}
-reserve_pool_size = ${RESERVE_POOL_SIZE}
-log_connections = 1
-log_disconnections = 1
-log_pooler_errors = 1
-stats_period = 60
-verbose = 0
-logfile = ${LOG_FILE}
-pidfile = /run/pgbouncer/pgbouncer.pid
-admin_users = ${PG_USER}
-stats_users = ${PG_USER}
-; Timeout settings (in seconds)
-server_check_delay = 10
-server_connect_timeout = 5
-server_lifetime = 3600
-server_idle_timeout = 60
-client_idle_timeout = 300
-query_timeout = 30
-EOF
-
-# --- Generate MD5 passwords for userlist.txt ---
-echo "[+] Generating MD5 password for userlist.txt (single user: ${PG_USER})..."
-# Format: "username" "md5hash"
-MD5_HASH_APP=$(echo -n "${PG_PASS}${PG_USER}" | md5sum | awk '{print "md5"$1}')
-
-echo "[+] Creating userlist.txt..."
-cat > "$USERLIST" <<EOF
-"${PG_USER}" "${MD5_HASH_APP}"
-EOF
-
-# --- Set secure permissions ---
-echo "[+] Setting secure permissions for configuration files..."
-chown pgbouncer:pgbouncer "$PGBOUNCER_INI" "$USERLIST"
-chmod 600 "$PGBOUNCER_INI" "$USERLIST"
-
-# --- Configure firewalld ---
-echo "[+] Configuring firewalld to allow PgBouncer traffic on port ${PGBOUNCER_PORT}..."
-systemctl enable --now firewalld || { echo "WARNING: Failed to enable/start firewalld. Check status manually."; }
-firewall-cmd --permanent --add-port=${PGBOUNCER_PORT}/tcp || { echo "WARNING: Failed to add firewall rule. Check status manually."; }
-firewall-cmd --reload || { echo "WARNING: Failed to reload firewalld. Check status manually."; }
-
-# --- Enable and start PgBouncer ---
-echo "[+] Enabling and starting PgBouncer service..."
-systemctl enable pgbouncer || { echo "ERROR: Failed to enable pgbouncer service. Exiting."; exit 1; }
-systemctl restart pgbouncer || { echo "ERROR: Failed to restart pgbouncer service. Exiting."; exit 1; }
-
-# --- Verify PgBouncer status ---
-echo "[+] Verifying PgBouncer status..."
-systemctl status pgbouncer --no-pager
-
-echo "[+] PgBouncer log file: $LOG_FILE"
-echo "  Use: journalctl -u pgbouncer -f        (for real-time system log)"
-echo "  Or:  tail -f $LOG_FILE                   (for dedicated log file)"
-
-echo "[+] Monitoring commands:"
-echo "  To connect to PgBouncer (for both app and admin access): psql -h 127.0.0.1 -p ${PGBOUNCER_PORT} -U ${PG_USER} -d ${PG_DB}"
-echo "  Then run (inside psql to pgbouncer):"
-echo "    SHOW POOLS;"
-echo "    SHOW STATS;"
-echo "    SHOW CLIENTS;"
-echo "    SHOW SERVERS;"
-echo "    -- For admin commands, you might need to connect to the special 'pgbouncer' database:"
-echo "    -- psql -h 127.0.0.1 -p ${PGBOUNCER_PORT} -U ${PG_USER} -d pgbouncer"
-
-echo "[✅] PgBouncer installation and setup complete (single user mode). Remember to secure your passwords!"
Index: /branches/amp_4_0/platform/tools/install_postfix.sh
===================================================================
--- /branches/amp_4_0/platform/tools/install_postfix.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/install_postfix.sh	(nonexistent)
@@ -1,87 +0,0 @@
-#!/bin/bash
-
-# === SETUP LOGGING ===
-LOG_FILE="/var/log/install_postfix.log"
-mkdir -p "$(dirname "$LOG_FILE")"
-exec >> "$LOG_FILE" 2>&1
-
-# === DATABASE CONNECTION CONFIG ===
-DB_NAME="cm"
-DB_USER="amp_admin"
-DB_HOST="127.0.0.1"
-DB_PORT="5432"
-
-# === Extract SMTP settings from SETTINGS table ===
-SMTP_JSON=$(psql -qtA -U "$DB_USER" -d "$DB_NAME" -h "$DB_HOST" -p "$DB_PORT" \
-  -c "SELECT attribute_value FROM SETTINGS WHERE attribute_name = 'smtp'" | tr -d '[:space:]')
-
-# === Extract EMAIL_TO from NOTIFICATION table ===
-EMAIL_TO_JSON=$(psql -qtA -U "$DB_USER" -d "$DB_NAME" -h "$DB_HOST" -p "$DB_PORT" \
-  -c "SELECT setting FROM NOTIFICATION WHERE type = 'email' LIMIT 1" | tr -d '[:space:]')
-
-# === Parse using jq ===
-SMTP_SERVER=$(echo "$SMTP_JSON" | jq -r '.Host')
-SMTP_PORT=$(echo "$SMTP_JSON" | jq -r '.Port')
-EMAIL_FROM=$(echo "$SMTP_JSON" | jq -r '.from_address')
-EMAIL_USER=$(echo "$SMTP_JSON" | jq -r '.User')
-EMAIL_PASS=$(echo "$SMTP_JSON" | jq -r '.Password')
-
-
-# === Function: Install if missing ===
-install_if_missing() {
-    local pkg="$1"
-    if ! rpm -q "$pkg" &>/dev/null; then
-        echo "Installing missing package: $pkg"
-        dnf install -y "$pkg"
-    else
-        echo "Package already installed: $pkg"
-    fi
-}
-
-# === Install Required Packages ===
-echo "Checking and installing required packages..."
-install_if_missing postfix
-install_if_missing s-nail
-install_if_missing cyrus-sasl
-install_if_missing cyrus-sasl-plain
-install_if_missing sysstat
-
-# === Enable and Start Postfix ===
-echo "Enabling and starting Postfix..."
-systemctl enable --now postfix
-
-# Generate mapping database
-postmap /etc/postfix/generic
-
-# === Configure Postfix ===
-echo "Configuring Postfix for SMTP relay..."
-postconf -e "relayhost = [$SMTP_SERVER]:$SMTP_PORT"
-postconf -e "smtp_use_tls = yes"
-postconf -e "smtp_sasl_auth_enable = yes"
-postconf -e "smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd"
-postconf -e "smtp_sasl_security_options = noanonymous"
-postconf -e "smtp_tls_CAfile = /etc/ssl/certs/ca-bundle.crt"
-postconf -e "myhostname = $(hostname)"
-postconf -e "inet_interfaces = loopback-only"
-postconf -e "mydestination ="
-postconf -e "smtp_generic_maps = hash:/etc/postfix/generic"
-
-# === Create SASL Password File ===
-echo "Creating /etc/postfix/sasl_passwd..."
-mkdir -p /etc/postfix
-cat <<EOF > /etc/postfix/sasl_passwd
-[$SMTP_SERVER]:$SMTP_PORT $EMAIL_USER:$EMAIL_PASS
-EOF
-
-chmod 600 /etc/postfix/sasl_passwd
-postmap /etc/postfix/sasl_passwd
-
-# === Restart Postfix ===
-echo "Restarting Postfix..."
-systemctl restart postfix
-
-chmod +x send_cpu_memory_stats.sh
-
-# === Add Cron Job ===
-echo "Adding cron job to check every 30 minute..."
-(crontab -l 2>/dev/null; echo "*/30 * * * * ../scripts/send_cpu_memory_stats.sh >> /var/log/send_stats_cron.log 2>&1") | sort -u | crontab -
Index: /branches/amp_4_0/platform/tools/install_psql.sh
===================================================================
--- /branches/amp_4_0/platform/tools/install_psql.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/install_psql.sh	(nonexistent)
@@ -1,139 +0,0 @@
-#!/bin/bash
-# Script to install and configure PostgreSQL on Rocky Linux 9.5 (Non-Interactive)
-# ===============================================================================
-
-set -Eeuo pipefail
-
-# --- Configuration ---
-LOG_FILE="/var/log/install_psql.log"
-DB_USER="postgres"
-DB_PASSWORD="Arr@y2050"
-PG_VERSION="17"
-PGDATA="/var/lib/pgsql/${PG_VERSION}/data"
-CONF_FILE="${PGDATA}/postgresql.conf"
-HBA_FILE="${PGDATA}/pg_hba.conf"
-NEW_DB_NAME="cm"
-NEW_USER="amp_admin"
-NEW_USER_PASSWORD="Array@123$"
-PG_LOG_DIR="$PGDATA/log"
-
-# --- Helper Functions ---
-log() {
-  local message="$1"
-  echo "$(date +'%Y-%m-%d %H:%M:%S') - $message" | tee -a "$LOG_FILE"
-}
-
-execute() {
-  local command="$1"
-  log "Executing: $command"
-  bash -c "$command"
-  if [ $? -ne 0 ]; then
-    log "ERROR: Command '$command' failed with exit code $?"
-    exit 1
-  fi
-}
-
-# --- Main Script ---
-if [[ "$EUID" -ne 0 ]]; then
-  log "ERROR: This script must be run as root."
-  exit 1
-fi
-
-exec > >(tee -a "$LOG_FILE") 2>&1
-
-log ">>> Starting PostgreSQL installation on Rocky Linux 9.5..."
-
-# Add PostgreSQL repository
-execute "dnf -y install https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm"
-
-# Disable built-in PostgreSQL module
-execute "dnf -y module disable postgresql"
-
-# Install PostgreSQL server and client
-execute "dnf -y install postgresql${PG_VERSION}-server postgresql${PG_VERSION}"
-
-# Initialize PostgreSQL database
-execute "/usr/pgsql-${PG_VERSION}/bin/postgresql-${PG_VERSION}-setup initdb"
-
-# Start PostgreSQL temporarily to set initial password
-execute "systemctl start postgresql-${PG_VERSION}"
-
-# Set password for 'postgres' using UNIX socket (peer auth)
-execute "su - postgres -c \"/usr/pgsql-${PG_VERSION}/bin/psql -c \\\"ALTER USER postgres WITH PASSWORD '$DB_PASSWORD';\\\"\""
-
-# Stop PostgreSQL to apply configuration
-execute "systemctl stop postgresql-${PG_VERSION}"
-
-# Configure PostgreSQL for remote access and scram auth
-execute "sed -i 's/^#\\?listen_addresses\\s*=.*/listen_addresses = '\''*'\''/' \"$CONF_FILE\""
-execute "sed -i 's/^#\\?password_encryption\\s*=.*/password_encryption = '\''scram-sha-256'\''/' \"$CONF_FILE\""
-
-# Update pg_hba.conf to restrict external access
-# Ensure local connections use scram-sha-256
-execute "sed -i 's/^\(local\\s\\+all\\s\\+all\\s\\+\\).*$/\\1scram-sha-256/' \"$HBA_FILE\""
-execute "sed -i 's/^\(host\\s\\+all\\s\\+all\\s\\+127\\.0\\.0\\.1\\/32\\s\\+\\).*$/\\1scram-sha-256/' \"$HBA_FILE\""
-execute "sed -i 's/^\(host\\s\\+all\\s\\+all\\s\\+::1\\/128\\s\\+\\).*$/\\1scram-sha-256/' \"$HBA_FILE\""
-execute "sed -i 's/^\(local\\s\\+replication\\s\\+all\\s\\+\\).*$/\\1scram-sha-256/' \"$HBA_FILE\""
-
-# Disable external host access by commenting out lines for 0.0.0.0/0 and ::/0
-# This finds lines starting with 'host' and containing either 0.0.0.0/0 or ::/0 and prepends '#'
-execute "sed -i -E '/^host\\s+.*(0\\.0\\.0\\.0\\/0|::\\/0)/ s/^/#/' \"$HBA_FILE\""
-
-# Removed the grep/echo lines that added external access rules (0.0.0.0/0 and ::/0)
-# grep -q "0.0.0.0/0" "$HBA_FILE" || echo "host    all             all             0.0.0.0/0               scram-sha-256" >> "$HBA_FILE"
-# grep -q "::/0" "$HBA_FILE" || echo "host    all             all             ::/0                    scram-sha-256" >> "$HBA_FILE"
-
-# Enable and start PostgreSQL service
-execute "systemctl enable postgresql-${PG_VERSION}"
-execute "systemctl start postgresql-${PG_VERSION}"
-
-# Wait for PostgreSQL to accept connections
-log ">>> Waiting for PostgreSQL to accept connections..."
-CONNECTION_CHECK_ATTEMPTS=20
-for ((i=1; i<=CONNECTION_CHECK_ATTEMPTS; i++)); do
-  sleep 2
-  PGPASSWORD="$DB_PASSWORD" /usr/pgsql-${PG_VERSION}/bin/psql -U $DB_USER -h 127.0.0.1 -c "SELECT 1" > /dev/null 2>&1
-  CONNECTION_CHECK_RESULT=$?
-  log ">>> Connection attempt $i: result=$CONNECTION_CHECK_RESULT"
-  if [ $CONNECTION_CHECK_RESULT -eq 0 ]; then
-    log ">>> PostgreSQL is accepting connections."
-    break
-  elif [ $i -eq $CONNECTION_CHECK_ATTEMPTS ]; then
-    log "ERROR: PostgreSQL failed to accept connections after $CONNECTION_CHECK_ATTEMPTS attempts."
-    PGPASSWORD="$DB_PASSWORD" /usr/pgsql-${PG_VERSION}/bin/psql -U $DB_USER -h 127.0.0.1 -c "SELECT 1" 2>>"$LOG_FILE"
-    log ">>> Performing diagnostic checks..."
-    execute "systemctl status postgresql-${PG_VERSION}"
-    execute "netstat -tulnp | grep 5432"
-    if [ -d "$PG_LOG_DIR" ]; then
-      execute "tail -n 20 \"$PG_LOG_DIR\"/postgresql-*.log"
-    else
-      log "WARNING: PostgreSQL log directory $PG_LOG_DIR does not exist."
-    fi
-    execute "firewall-cmd --state"
-    execute "firewall-cmd --list-ports"
-    exit 1
-  else
-    log ">>> Connection attempt $i failed. Retrying..."
-  fi
-done
-
-# Create database and user
-export PGPASSWORD="$DB_PASSWORD"
-execute "/usr/pgsql-${PG_VERSION}/bin/psql -U $DB_USER -h 127.0.0.1 -c \"CREATE DATABASE $NEW_DB_NAME;\""
-execute "/usr/pgsql-${PG_VERSION}/bin/psql -U $DB_USER -h 127.0.0.1 -c \"CREATE USER $NEW_USER WITH PASSWORD '$NEW_USER_PASSWORD';\""
-execute "/usr/pgsql-${PG_VERSION}/bin/psql -U $DB_USER -h 127.0.0.1 -c \"GRANT ALL PRIVILEGES ON DATABASE $NEW_DB_NAME TO $NEW_USER;\""
-execute "/usr/pgsql-${PG_VERSION}/bin/psql -U $DB_USER -h 127.0.0.1 -d $NEW_DB_NAME -c \"GRANT USAGE ON SCHEMA public TO $NEW_USER;\""
-execute "/usr/pgsql-${PG_VERSION}/bin/psql -U $DB_USER -h 127.0.0.1 -d $NEW_DB_NAME -c \"GRANT CREATE ON SCHEMA public TO $NEW_USER;\""
-unset PGPASSWORD
-
-# Open PostgreSQL port
-if ! firewall-cmd --list-ports --permanent | grep -q "5432/tcp"; then
-  execute "firewall-cmd --add-port=5432/tcp --permanent"
-fi
-execute "firewall-cmd --reload"
-
-log ">>> PostgreSQL installation complete!"
-log ">>> Database '$NEW_DB_NAME' and user '$NEW_USER' created."
-log ">>> Log saved to $LOG_FILE"
-
-exit 0
Index: /branches/amp_4_0/platform/tools/install_python.sh
===================================================================
--- /branches/amp_4_0/platform/tools/install_python.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/install_python.sh	(nonexistent)
@@ -1,104 +0,0 @@
-#!/bin/bash
-
-set -e
-
-LOGFILE="/var/log/install_python.log"
-exec > >(tee -a "$LOGFILE") 2>&1
-
-echo "------------------------------------------------------------------"
-echo "  Installing Python 3.13.0 on Rocky Linux 9.5 (Non-Interactive)"
-echo "  Log file: $LOGFILE"
-echo "------------------------------------------------------------------"
-
-# Ensure we are root
-if [[ "$EUID" -ne 0 ]]; then
-  echo "Error: This script must be run as root."
-  exit 1
-fi
-
-echo "Step 1: Updating system packages..."
-dnf update -y
-
-echo "Step 2: Installing necessary build dependencies..."
-dnf install -y \
-    tar \
-    wget \
-    gcc \
-    make \
-    zlib-devel \
-    bzip2-devel \
-    openssl-devel \
-    ncurses-devel \
-    sqlite-devel \
-    readline-devel \
-    tk-devel \
-    libffi-devel \
-    xz-devel
-
-PYTHON_VERSION="3.13.0"
-PYTHON_SRC_DIR="/tmp/Python-${PYTHON_VERSION}"
-INSTALL_PREFIX="/usr/local"
-
-echo "Step 3: Downloading and extracting Python ${PYTHON_VERSION} source..."
-cd /tmp
-if [ ! -d "$PYTHON_SRC_DIR" ]; then
-  wget "https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz"
-  tar -xf "Python-${PYTHON_VERSION}.tgz"
-else
-  echo "Python source directory already exists: $PYTHON_SRC_DIR"
-fi
-cd "$PYTHON_SRC_DIR"
-
-echo "Step 4: Configuring the build..."
-./configure --enable-optimizations --enable-shared --prefix="$INSTALL_PREFIX"
-
-echo "Step 5: Building Python..."
-make -j "$(nproc)"
-
-echo "Step 6: Installing Python (using altinstall to avoid overwriting system python)..."
-make altinstall
-
-echo "Step 7: Configuring shared library path for Python 3.13..."
-echo "/usr/local/lib" > /etc/ld.so.conf.d/python3.13.conf
-ldconfig
-
-echo "Step 8: Installing pip and creating symbolic links..."
-
-"$INSTALL_PREFIX/bin/python3.13" -m ensurepip --upgrade
-
-# Confirm pip3.13 exists before symlinking
-if [ -f "$INSTALL_PREFIX/bin/pip3.13" ]; then
-  ln -sf "$INSTALL_PREFIX/bin/pip3.13" /usr/local/bin/pip3
-else
-  echo "Error: pip3.13 was not found after ensurepip. Aborting."
-  exit 1
-fi
-
-# Symlink python3 if not already present
-if [ ! -f "/usr/local/bin/python3" ]; then
-  ln -s "$INSTALL_PREFIX/bin/python3.13" /usr/local/bin/python3
-fi
-
-echo "Step 9: Adding /usr/local/bin to PATH (system-wide)..."
-echo "export PATH=$INSTALL_PREFIX/bin:\$PATH" > /etc/profile.d/custom-path.sh
-chmod +x /etc/profile.d/custom-path.sh
-source /etc/profile.d/custom-path.sh
-
-echo "Step 10: Verifying Python installation..."
-echo "Python path: $(which python3 || echo 'Not found')"
-python3 --version || echo "python3 failed"
-
-echo "pip path: $(which pip3 || echo 'Not found')"
-pip3 --version || echo "pip3 failed"
-
-echo "Step 11: Cleaning up source files..."
-rm -rf "/tmp/Python-${PYTHON_VERSION}" "/tmp/Python-${PYTHON_VERSION}.tgz"
-
-echo "------------------------------------------------------------------"
-echo "  Python 3.13 installation completed successfully!"
-echo "  Python 3 executable: /usr/local/bin/python3"
-echo "  pip3 executable: /usr/local/bin/pip3"
-echo "  Log file: $LOGFILE"
-echo "------------------------------------------------------------------"
-
-exit 0
Index: /branches/amp_4_0/platform/tools/install_system_dependencies.sh
===================================================================
--- /branches/amp_4_0/platform/tools/install_system_dependencies.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/install_system_dependencies.sh	(nonexistent)
@@ -1,12 +0,0 @@
-#!/bin/bash
-
-# Script to install sshpass and dmidecode 
-
-log_error() {
-    echo -e "\e[31m[ERROR] $1\e[0m"
-}
-
-sudo dnf -y install sshpass || log_error "Failed to install sshpass"
-sudo dnf -y install dmidecode || log_error "Failed to install dmidecode"
-sudo dnf -y install net-tools || log_error "Failed to install net-tools"
-sudo dnf -y install tftp || log_error "Failed to install tftp"
Index: /branches/amp_4_0/platform/tools/install_telegraf.sh
===================================================================
--- /branches/amp_4_0/platform/tools/install_telegraf.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/install_telegraf.sh	(nonexistent)
@@ -1,65 +0,0 @@
-#!/bin/bash
-# install_telegraf.sh
-# - Installs Telegraf from system repos.
-# - Leaves ALL configuration to configure_telegraf_timescale.sh
-
-set -euo pipefail
-
-LOG_FILE="/var/log/install_telegraf.log"
-TELEGRAF_CONFIG_DIR="/etc/telegraf/telegraf.d"
-TELEGRAF_CONFIG="/etc/telegraf/telegraf.conf"
-TELEGRAF_VERSION="1.34.1"
-
-# --- Logging helpers ---
-log() { echo "[$(date +'%F %T')] $*"; echo "[$(date +'%F %T')] $*" >> "$LOG_FILE"; }
-need() { command -v "$1" >/dev/null 2>&1 || { log "ERROR: $1 not found"; exit 1; }; }
-
-# --- Check directories ---
-log "Checking directory permissions..."
-for dir in "/var/log" "/etc/telegraf" "$TELEGRAF_CONFIG_DIR"; do
-  if [[ ! -d "$dir" ]]; then
-    log "WARN: $dir does not exist. Creating..."
-    sudo mkdir -p "$dir"
-  fi
-  [[ ! -w "$dir" ]] && { log "ERROR: $dir not writable"; exit 1; }
-done
-
-# --- Prereqs ---
-log "Checking required commands..."
-for cmd in sudo dnf grep sed; do need "$cmd"; done
-log "All required commands are present."
-
-# --- Add InfluxData Repo ---
-log "Adding InfluxData repository..."
-REPO_URL="https://repos.influxdata.com/centos/9/x86_64/stable"
-REPO_FILE="/etc/yum.repos.d/influxdata.repo"
-
-if [[ ! -f "$REPO_FILE" ]]; then
-  cat <<EOF | sudo tee "$REPO_FILE"
-[influxdata]
-name = InfluxData Repository - Stable
-baseurl = $REPO_URL
-gpgcheck = 1
-gpgkey = https://repos.influxdata.com/influxdata-archive_compat.key
-enabled = 1
-EOF
-  sudo dnf update -y
-  [[ $? -ne 0 ]] && log_error "Failed to add repo or update system." && exit 1
-  log "InfluxData repo added and system updated."
-else
-  log "InfluxData repo already exists."
-fi
-
-# --- Install Telegraf ---
-log "Installing Telegraf version $TELEGRAF_VERSION..."
-if ! sudo dnf install -y "telegraf-${TELEGRAF_VERSION}"; then
-  log "ERROR: Failed to install Telegraf"
-  exit 1
-fi
-log "Telegraf $TELEGRAF_VERSION installed successfully."
-
-log "Enabling Telegraf service (will start after configuration)…"
-sudo systemctl enable telegraf
-
-log "Install complete. Next run: configure_telegraf_timescale.sh"
-exit 0
Index: /branches/amp_4_0/platform/tools/install_timescaledb.sh
===================================================================
--- /branches/amp_4_0/platform/tools/install_timescaledb.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/install_timescaledb.sh	(nonexistent)
@@ -1,117 +0,0 @@
-#!/bin/bash
-# Minimal TimescaleDB CE enable script for Rocky Linux 9 + PostgreSQL 17
-# Assumes PostgreSQL 17 is already installed and running.
-set -euo pipefail
-
-### Config (edit as needed)
-PG_VERSION="17"
-DB_SUPERUSER="postgres"                 # local superuser
-DB_SUPERUSER_PASSWORD="Arr@y2050"       # password for the postgres user (local auth)
-NEW_DB_NAME="amp_ts"
-NEW_USER="amp_ts_user"
-NEW_USER_PASSWORD="Array@123$"
-
-# Derived paths
-PGDATA="/var/lib/pgsql/${PG_VERSION}/data"
-CONF_FILE="${PGDATA}/postgresql.conf"
-PG_SERVICE="postgresql-${PG_VERSION}"
-PG_BIN_DIR="/usr/pgsql-${PG_VERSION}/bin"
-
-log()   { echo "[$(date +'%F %T')] $*"; }
-error() { echo "[ERROR $(date +'%F %T')] $*" >&2; }
-
-require() { command -v "$1" &>/dev/null || { error "Missing command: $1"; exit 1; } }
-
-[[ $EUID -eq 0 ]] || { error "Run as root (sudo)."; exit 1; }
-require dnf
-require systemctl
-require sed
-require tee
-
-log "Adding TimescaleDB repository…"
-tee /etc/yum.repos.d/timescale_timescaledb.repo > /dev/null <<'EOF'
-[timescale_timescaledb]
-name=timescale_timescaledb
-baseurl=https://packagecloud.io/timescale/timescaledb/el/9/$basearch
-repo_gpgcheck=1
-enabled=1
-gpgcheck=0
-gpgkey=https://packagecloud.io/timescale/timescaledb/gpgkey
-sslverify=1
-metadata_expire=300
-EOF
-
-dnf clean all -y
-dnf makecache -y
-
-log "Installing TimescaleDB CE for PostgreSQL ${PG_VERSION}…"
-dnf -y install "timescaledb-2-postgresql-${PG_VERSION}"
-dnf -y install "timescaledb-toolkit-postgresql-${PG_VERSION}"
-
-# Optional tuning tool
-dnf -y install timescaledb-tools || true
-
-# Ensure shared_preload_libraries includes timescaledb
-if ! grep -q "^shared_preload_libraries.*timescaledb" "$CONF_FILE"; then
-  if grep -q "^shared_preload_libraries" "$CONF_FILE"; then
-    sed -i "s/^shared_preload_libraries.*/shared_preload_libraries = 'timescaledb'/" "$CONF_FILE"
-  else
-    echo "shared_preload_libraries = 'timescaledb'" >> "$CONF_FILE"
-  fi
-fi
-
-# Run timescaledb-tune if available (non-interactive)
-if command -v timescaledb-tune &>/dev/null; then
-  log "Running timescaledb-tune…"
-  timescaledb-tune --quiet --yes --pg-config "${PG_BIN_DIR}/pg_config" || true
-fi
-
-log "Restarting ${PG_SERVICE}…"
-systemctl restart "${PG_SERVICE}"
-
-# Wait for server
-log "Waiting for PostgreSQL to accept connections…"
-for i in {1..20}; do
-  sleep 2
-  PGPASSWORD="$DB_SUPERUSER_PASSWORD" "${PG_BIN_DIR}/psql" -U "$DB_SUPERUSER" -h 127.0.0.1 -c "SELECT 1" &>/dev/null && break
-  [[ $i -eq 20 ]] && { error "PostgreSQL not accepting connections."; exit 1; }
-done
-
-# Create role if missing
-export PGPASSWORD="$DB_SUPERUSER_PASSWORD"
-sudo -E -u postgres "${PG_BIN_DIR}/psql" -v ON_ERROR_STOP=1 <<SQL
-DO \$\$ BEGIN
-  IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '${NEW_USER}') THEN
-    CREATE ROLE ${NEW_USER} LOGIN PASSWORD '${NEW_USER_PASSWORD}';
-  END IF;
-END \$\$;
-SQL
-
-# Create DB if missing
-DB_EXISTS="$(sudo -E -u postgres "${PG_BIN_DIR}/psql" -tAc "SELECT 1 FROM pg_database WHERE datname='${NEW_DB_NAME}'" 2>/dev/null || true)"
-if [[ "${DB_EXISTS:-}" != "1" ]]; then
-  log "Creating database ${NEW_DB_NAME}…"
-  sudo -E -u postgres "${PG_BIN_DIR}/psql" -c "CREATE DATABASE ${NEW_DB_NAME}"
-else
-  log "Database ${NEW_DB_NAME} already exists."
-fi
-
-# Enable extension + grants
-sudo -E -u postgres "${PG_BIN_DIR}/psql" -v ON_ERROR_STOP=1 <<SQL
-\c ${NEW_DB_NAME}
-CREATE EXTENSION IF NOT EXISTS timescaledb;
-CREATE EXTENSION IF NOT EXISTS timescaledb_toolkit;
-GRANT ALL PRIVILEGES ON DATABASE ${NEW_DB_NAME} TO ${NEW_USER};
-ALTER DATABASE ${NEW_DB_NAME} OWNER TO ${NEW_USER};
-SQL
-unset PGPASSWORD
-
-log "Done. TimescaleDB CE enabled on PostgreSQL ${PG_VERSION}."
-
-echo
-echo "psql test:"
-echo "  PGPASSWORD='${NEW_USER_PASSWORD}' psql -h 127.0.0.1 -U ${NEW_USER} -d ${NEW_DB_NAME} -c '\dx timescaledb'"
-echo
-echo "Next steps:"
-echo "  - Create hypertables and (optionally) continuous aggregates for your metrics."
-echo "  - Point Telegraf PostgreSQL output plugin to DB=${NEW_DB_NAME}, user=${NEW_USER}."
Index: /branches/amp_4_0/platform/tools/install_tools_dependencies.sh
===================================================================
--- /branches/amp_4_0/platform/tools/install_tools_dependencies.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/install_tools_dependencies.sh	(nonexistent)
@@ -1,135 +0,0 @@
-#!/bin/bash
-
-# Configuration
-DOWNLOAD_DIR="/tmp" # Temporary directory for downloading the JAR
-TARGET_DIR="/etc/logstash/jdbc_drivers" # Where Logstash expects the drivers
-LOGSTASH_USER="admin" # User Logstash runs as
-LOGSTASH_GROUP="admin" # Group Logstash runs as
-# --- HARDCODED URL ---
-# IMPORTANT: Manually update this URL to the latest PostgreSQL JDBC driver .jar file
-# You can find it at: https://jdbc.postgresql.org/download/
-LATEST_DRIVER_URL="https://repo1.maven.org/maven2/org/postgresql/postgresql/42.7.5/postgresql-42.7.5.jar" # <--- UPDATE THIS LINK WHEN A NEW DRIVER IS RELEASED
-# --- END HARDCODED URL ---
-PG_JDBC_URL="https://jdbc.postgresql.org/download/" # Keep this for logging context if desired
-LOG_FILE="/var/log/install_logstash_dependencies.log" # Log file for script output
-
-# Redirect all stdout and stderr to the log file
-exec > >(tee -a "$LOG_FILE") 2>&1
-
-# --- Functions ---
-
-log_info() {
-  echo "[INFO] $(date +'%Y-%m-%d %H:%M:%S') $1"
-}
-
-log_error() {
-  echo "[ERROR] $(date +'%Y-%m-%d %H:%M:%S') $1"
-}
-
-# --- Main Script ---
-
-log_info "Starting PostgreSQL JDBC driver setup script..."
-log_info "All script output is being logged to: ${LOG_FILE}"
-
-# Check for required commands
-if ! command -v curl &> /dev/null; then
-    log_error "Error: 'curl' is not installed. Please install it (e.g., sudo apt install curl or sudo yum install curl)."
-    exit 1
-fi
-if ! command -v grep &> /dev/null; then # Grep is still needed for user/group check, even if not for URL parsing
-    log_error "Error: 'grep' is not installed. Please install it (e.g., sudo apt install grep or sudo yum install grep)."
-    exit 1
-fi
-
-# Check if the specified user and group exist
-if ! id -u "$LOGSTASH_USER" &> /dev/null; then
-    log_error "User '$LOGSTASH_USER' does not exist. Please create it or adjust LOGSTASH_USER in the script."
-    exit 1
-fi
-if ! getent group "$LOGSTASH_GROUP" &> /dev/null; then
-    log_error "Group '$LOGSTASH_GROUP' does not exist. Please create it or adjust LOGSTASH_GROUP in the script."
-    exit 1
-fi
-
-log_info "Direct download URL is used: ${LATEST_DRIVER_URL}"
-
-DRIVER_FILENAME=$(basename "$LATEST_DRIVER_URL")
-TEMP_DRIVER_PATH="${DOWNLOAD_DIR}/${DRIVER_FILENAME}"
-
-log_info "Expected filename: ${DRIVER_FILENAME}"
-
-# 2. Download the driver
-log_info "Downloading ${DRIVER_FILENAME} to ${DOWNLOAD_DIR}..."
-curl -L -o "$TEMP_DRIVER_PATH" "$LATEST_DRIVER_URL"
-
-if [ $? -ne 0 ]; then
-  log_error "Failed to download the driver from ${LATEST_DRIVER_URL}."
-  log_error "Please check your internet connection or the URL."
-  exit 1
-fi
-
-if [ ! -f "$TEMP_DRIVER_PATH" ]; then
-  log_error "Downloaded file not found at ${TEMP_DRIVER_PATH}. Download might have failed silently."
-  exit 1
-fi
-
-log_info "Download complete."
-
-# 3. Create target directory
-log_info "Ensuring target directory ${TARGET_DIR} exists..."
-sudo mkdir -p "$TARGET_DIR"
-if [ $? -ne 0 ]; then
-  log_error "Failed to create target directory ${TARGET_DIR}. Do you have sufficient permissions?"
-  exit 1
-fi
-log_info "Target directory is ready."
-
-# 4. Copy the driver to the target directory
-log_info "Copying ${DRIVER_FILENAME} to ${TARGET_DIR}..."
-sudo cp "$TEMP_DRIVER_PATH" "$TARGET_DIR/"
-if [ $? -ne 0 ]; then
-  log_error "Failed to copy the driver to ${TARGET_DIR}. Do you have sufficient permissions?"
-  exit 1
-fi
-log_info "Driver copied successfully."
-
-# 5. Set permissions
-FINAL_DRIVER_PATH="${TARGET_DIR}/${DRIVER_FILENAME}"
-log_info "Setting ownership to ${LOGSTASH_USER}:${LOGSTASH_GROUP} for ${FINAL_DRIVER_PATH}..."
-sudo chown "${LOGSTASH_USER}:${LOGSTASH_GROUP}" "$FINAL_DRIVER_PATH"
-if [ $? -ne 0 ]; then
-  log_error "Failed to change ownership of ${FINAL_DRIVER_PATH}. Check if user/group '${LOGSTASH_USER}:${LOGSTASH_GROUP}' exist and you have permissions."
-  exit 1
-fi
-
-log_info "Setting permissions to 644 for ${FINAL_DRIVER_PATH}..."
-sudo chmod 644 "$FINAL_DRIVER_PATH"
-if [ $? -ne 0 ]; then
-  log_error "Failed to set permissions for ${FINAL_DRIVER_PATH}."
-  exit 1
-fi
-log_info "Permissions set successfully."
-
-# 6. Clean up temporary file
-log_info "Cleaning up temporary downloaded file ${TEMP_DRIVER_PATH}..."
-rm "$TEMP_DRIVER_PATH"
-log_info "Temporary file removed."
-
-log_info "PostgreSQL JDBC driver setup complete!"
-log_info "Remember to update your Logstash configuration to use this path:"
-log_info "  jdbc_driver_library => \"${FINAL_DRIVER_PATH}\""
-
-# 7. Restart Logstash Service (NEW STEP)
-log_info "Restarting Logstash service..."
-sudo systemctl restart logstash
-
-if [ $? -eq 0 ]; then
-  log_info "Logstash service restarted successfully."
-  log_info "Please check Logstash logs for any startup errors: sudo journalctl -u logstash -f"
-else
-  log_error "Failed to restart Logstash service. Please check service status and logs manually."
-fi
-
-log_info "Script finished."
-
-exit 0
Index: /branches/amp_4_0/platform/tools/telegraf_snmp_timescale.sql
===================================================================
--- /branches/amp_4_0/platform/tools/telegraf_snmp_timescale.sql	(revision 2855)
+++ /branches/amp_4_0/platform/tools/telegraf_snmp_timescale.sql	(nonexistent)
@@ -1,737 +0,0 @@
--- telegraf_snmp_timescale_fixed.sql
--- TimescaleDB schema for Telegraf SNMP inputs (Array Networks)
--- Compatible with TimescaleDB OSS; uses standard row compression (NOT columnstore).
--- Creates hypertables, indexes, compression + retention, and 5-minute CAs (WITH NO DATA).
-
-BEGIN;
-
--- 0) Enable TimescaleDB (first run requires superuser)
-CREATE EXTENSION IF NOT EXISTS timescaledb;
-
--- 1) MEASUREMENT: an_device_metrics
-CREATE TABLE IF NOT EXISTS an_device_metrics (
-    time TIMESTAMPTZ NOT NULL,
-    agent_host TEXT NOT NULL,
-    cpu_usage DOUBLE PRECISION,
-    mem_usage DOUBLE PRECISION,
-    net_mem_usage DOUBLE PRECISION,
-    total_openssl_conns BIGINT,
-    connections BIGINT,
-    requests BIGINT,
-    total_in BIGINT,
-    total_out BIGINT,
-    PRIMARY KEY (time, agent_host)
-);
-SELECT create_hypertable('an_device_metrics','time','agent_host', number_partitions => 4, if_not_exists => TRUE);
-CREATE INDEX IF NOT EXISTS idx_metrics_agent_time ON an_device_metrics (agent_host, time DESC);
-
--- 2) MEASUREMENT: an_device_performance
-CREATE TABLE IF NOT EXISTS an_device_performance (
-    time TIMESTAMPTZ NOT NULL,
-    agent_host TEXT NOT NULL,
-    ssl_ae_core_utilization DOUBLE PRECISION,
-    ssl_se_core_utilization DOUBLE PRECISION,
-    PRIMARY KEY (time, agent_host)
-);
-SELECT create_hypertable('an_device_performance','time','agent_host', number_partitions => 4, if_not_exists => TRUE);
-CREATE INDEX IF NOT EXISTS idx_perf_agent_time ON an_device_performance (agent_host, time DESC);
-
--- 3) TABLE: an_device_storage (HOST-RESOURCES-MIB hrStorageTable)
--- hrStorage values are in allocation units; store alloc_unit to compute bytes later.
-CREATE TABLE IF NOT EXISTS an_device_storage (
-    time TIMESTAMPTZ NOT NULL,
-    agent_host TEXT NOT NULL,
-    prefix TEXT NOT NULL,
-    size BIGINT,          -- hrStorageSize
-    used BIGINT,          -- hrStorageUsed
-    alloc_unit BIGINT,    -- hrStorageAllocationUnits
-    PRIMARY KEY (time, agent_host, prefix)
-);
-SELECT create_hypertable('an_device_storage','time','agent_host', number_partitions => 4, if_not_exists => TRUE);
-CREATE INDEX IF NOT EXISTS idx_storage_prefix ON an_device_storage (prefix);
-CREATE INDEX IF NOT EXISTS idx_storage_agent_time ON an_device_storage (agent_host, time DESC);
-
--- 4) TABLE: apv_virtual_stats
-CREATE TABLE IF NOT EXISTS apv_virtual_stats (
-    time TIMESTAMPTZ NOT NULL,
-    agent_host TEXT NOT NULL,
-    serverid TEXT,
-    addr TEXT NOT NULL,
-    port TEXT NOT NULL,
-    protocol TEXT NOT NULL,
-    url_hits BIGINT,
-    hostname_hits BIGINT,
-    perstnt_cookie_hits BIGINT,
-    qos_cookie_hits BIGINT,
-    default_hits BIGINT,
-    perstnt_url_hits BIGINT,
-    static_hits BIGINT,
-    qos_network_hits BIGINT,
-    qos_url_hits BIGINT,
-    backup_hits BIGINT,
-    cache_hits BIGINT,
-    regex_hits BIGINT,
-    rcookie_hits BIGINT,
-    icookie_hits BIGINT,
-    conn_cnt BIGINT,
-    qos_client_port_hits BIGINT,
-    qos_body_hits BIGINT,
-    header_hits BIGINT,
-    hash_url_hits BIGINT,
-    redirect_hits BIGINT,
-    conn_per_sec DOUBLE PRECISION,
-    in_byte_per_sec DOUBLE PRECISION,
-    out_byte_per_sec DOUBLE PRECISION,
-    in_packet_per_sec DOUBLE PRECISION,
-    out_packet_per_sec DOUBLE PRECISION,
-    health_status TEXT,
-    total_hits BIGINT,
-    PRIMARY KEY (time, agent_host, serverid, addr, port, protocol)
-);
-SELECT create_hypertable('apv_virtual_stats','time','agent_host', number_partitions => 8, if_not_exists => TRUE);
-CREATE INDEX IF NOT EXISTS idx_virtual_serverid ON apv_virtual_stats (serverid);
-CREATE INDEX IF NOT EXISTS idx_virtual_agent_time ON apv_virtual_stats (agent_host, time DESC);
-CREATE INDEX IF NOT EXISTS idx_virtual_server_time ON apv_virtual_stats (serverid, time DESC);
-
--- 5) TABLE: apv_real_stats
-CREATE TABLE IF NOT EXISTS apv_real_stats (
-    time TIMESTAMPTZ NOT NULL,
-    agent_host TEXT NOT NULL,
-    real_server_id TEXT,
-    addr TEXT NOT NULL,
-    port TEXT NOT NULL,
-    protocol TEXT NOT NULL,
-    status TEXT NOT NULL,
-    rs_cnt_of_req BIGINT,
-    rs_conn_cnt BIGINT,
-    rs_total_hits BIGINT,
-    rs_conn_per_sec DOUBLE PRECISION,
-    rs_in_byte_per_sec DOUBLE PRECISION,
-    rs_out_byte_per_sec DOUBLE PRECISION,
-    rs_in_packet_per_sec DOUBLE PRECISION,
-    rs_out_packet_per_sec DOUBLE PRECISION,
-    PRIMARY KEY (time, agent_host, real_server_id, addr, port, protocol)
-);
-SELECT create_hypertable('apv_real_stats','time','agent_host', number_partitions => 8, if_not_exists => TRUE);
-CREATE INDEX IF NOT EXISTS idx_real_realserverid ON apv_real_stats (real_server_id);
-CREATE INDEX IF NOT EXISTS idx_real_agent_time ON apv_real_stats (agent_host, time DESC);
-CREATE INDEX IF NOT EXISTS idx_real_server_time ON apv_real_stats (real_server_id, time DESC);
-
--- 6) TABLE: apv_llb_stats
--- Note: several fields arrive as strings (e.g., "7.502ms", "7+00:33:52"); keep TEXT.
-CREATE TABLE public.apv_llb_stats (
-    "time" timestamptz NOT NULL,
-    agent_host text NOT NULL,
-    host text NULL,
-    link_gateway text NULL,
-    link_resp_time text NULL,
-    link_down_event text NULL,
-    link_hits int8 NULL,
-    link_index int8 NULL,
-    link_name text NOT NULL,
-    link_up_time int8 NULL,
-    link_down_count int8 NULL,
-    link_bandwid_out int8 NULL,
-    link_conn int8 NULL,
-    link_usage int8 NULL,
-    link_status text NOT NULL,
-    link_bandwid_in int8 NULL,
-    link_thresh int8 NULL,
-    link_down_time int8 NULL
-);
-SELECT create_hypertable('apv_llb_stats','time','agent_host', number_partitions => 4, if_not_exists => TRUE);
-CREATE INDEX IF NOT EXISTS idx_llb_linkname ON apv_llb_stats (link_name);
-CREATE INDEX IF NOT EXISTS idx_llb_agent_time ON apv_llb_stats (agent_host, time DESC);
-
--- 7) Ensure NOT using columnstore policies (ignore if function/policy doesn’t exist)
-DO $$ BEGIN PERFORM remove_columnstore_policy('apv_virtual_stats');     EXCEPTION WHEN undefined_function THEN NULL; WHEN others THEN NULL; END $$;
-DO $$ BEGIN PERFORM remove_columnstore_policy('apv_real_stats');        EXCEPTION WHEN undefined_function THEN NULL; WHEN others THEN NULL; END $$;
-DO $$ BEGIN PERFORM remove_columnstore_policy('apv_llb_stats');         EXCEPTION WHEN undefined_function THEN NULL; WHEN others THEN NULL; END $$;
-DO $$ BEGIN PERFORM remove_columnstore_policy('an_device_metrics');     EXCEPTION WHEN undefined_function THEN NULL; WHEN others THEN NULL; END $$;
-DO $$ BEGIN PERFORM remove_columnstore_policy('an_device_performance'); EXCEPTION WHEN undefined_function THEN NULL; WHEN others THEN NULL; END $$;
-DO $$ BEGIN PERFORM remove_columnstore_policy('an_device_storage');     EXCEPTION WHEN undefined_function THEN NULL; WHEN others THEN NULL; END $$;
-
--- 8) Standard row-compression (NOT columnstore)
-ALTER TABLE IF EXISTS apv_virtual_stats     SET (timescaledb.compress, timescaledb.compress_segmentby = 'agent_host, serverid',         timescaledb.compress_orderby = 'time DESC');
-ALTER TABLE IF EXISTS apv_real_stats        SET (timescaledb.compress, timescaledb.compress_segmentby = 'agent_host, real_server_id',   timescaledb.compress_orderby = 'time DESC');
-ALTER TABLE IF EXISTS apv_llb_stats         SET (timescaledb.compress, timescaledb.compress_segmentby = 'agent_host, link_name',        timescaledb.compress_orderby = 'time DESC');
-ALTER TABLE IF EXISTS an_device_metrics     SET (timescaledb.compress, timescaledb.compress_segmentby = 'agent_host',                   timescaledb.compress_orderby = 'time DESC');
-ALTER TABLE IF EXISTS an_device_performance SET (timescaledb.compress, timescaledb.compress_segmentby = 'agent_host',                   timescaledb.compress_orderby = 'time DESC');
-ALTER TABLE IF EXISTS an_device_storage     SET (timescaledb.compress, timescaledb.compress_segmentby = 'agent_host, prefix',           timescaledb.compress_orderby = 'time DESC');
-
--- 9) Compression policies (compress after 7 days)
-SELECT add_compression_policy('apv_virtual_stats',     INTERVAL '7 days');
-SELECT add_compression_policy('apv_real_stats',        INTERVAL '7 days');
-SELECT add_compression_policy('apv_llb_stats',         INTERVAL '7 days');
-SELECT add_compression_policy('an_device_metrics',     INTERVAL '7 days');
-SELECT add_compression_policy('an_device_performance', INTERVAL '7 days');
-SELECT add_compression_policy('an_device_storage',     INTERVAL '7 days');
-
--- 10) Retention policies (drop after 180 days)
-SELECT add_retention_policy('apv_virtual_stats',     INTERVAL '180 days');
-SELECT add_retention_policy('apv_real_stats',        INTERVAL '180 days');
-SELECT add_retention_policy('apv_llb_stats',         INTERVAL '180 days');
-SELECT add_retention_policy('an_device_metrics',     INTERVAL '180 days');
-SELECT add_retention_policy('an_device_performance', INTERVAL '180 days');
-SELECT add_retention_policy('an_device_storage',     INTERVAL '180 days');
-
--- 11) Continuous aggregates (5-minute) — WITH NO DATA
--- 11.1) Virtual stats
-CREATE MATERIALIZED VIEW IF NOT EXISTS cag_apv_virtual_stats_5m
-WITH (timescaledb.continuous) AS
-SELECT
-  time_bucket('5 minutes', time) AS bucket,
-  agent_host,
-  serverid,
-  avg(conn_per_sec)     AS avg_connps,
-  avg(in_byte_per_sec)  AS avg_in_bps,
-  avg(out_byte_per_sec) AS avg_out_bps,
-  avg(in_packet_per_sec)  AS avg_in_pps,
-  avg(out_packet_per_sec) AS avg_out_pps
-FROM apv_virtual_stats
-GROUP BY bucket, agent_host, serverid
-WITH NO DATA;
-
--- 11.2) Real stats
-CREATE MATERIALIZED VIEW IF NOT EXISTS cag_apv_real_stats_5m
-WITH (timescaledb.continuous) AS
-SELECT
-  time_bucket('5 minutes', time) AS bucket,
-  agent_host,
-  real_server_id,
-  avg(rs_conn_per_sec)      AS avg_connps,
-  avg(rs_in_byte_per_sec)   AS avg_in_bps,
-  avg(rs_out_byte_per_sec)  AS avg_out_bps,
-  avg(rs_in_packet_per_sec)  AS avg_in_pps,
-  avg(rs_out_packet_per_sec) AS avg_out_pps
-FROM apv_real_stats
-GROUP BY bucket, agent_host, real_server_id
-WITH NO DATA;
-
--- 11.3) LLB stats (link_resp_time is TEXT → parse number before averaging, assume milliseconds)
-CREATE MATERIALIZED VIEW IF NOT EXISTS cag_apv_llb_stats_5m
-WITH (timescaledb.continuous) AS
-SELECT
-  time_bucket('5 minutes', time) AS bucket,
-  agent_host,
-  link_name,
-  avg( NULLIF(regexp_replace(link_resp_time, '[^0-9.]', '', 'g'), '')::double precision ) AS avg_resp_time_ms,
-  avg(link_usage)      AS avg_usage,
-  avg(link_bandwid_in)  AS avg_bandwid_in,
-  avg(link_bandwid_out) AS avg_bandwid_out,
-  max(link_down_count)  AS max_down_count
-FROM apv_llb_stats
-GROUP BY bucket, agent_host, link_name
-WITH NO DATA;
-
--- 12) Refresh policies (remove if present, then add)
-DO $$ BEGIN PERFORM remove_continuous_aggregate_policy('cag_apv_virtual_stats_5m'); EXCEPTION WHEN undefined_function THEN NULL; WHEN others THEN NULL; END $$;
-DO $$ BEGIN PERFORM remove_continuous_aggregate_policy('cag_apv_real_stats_5m');    EXCEPTION WHEN undefined_function THEN NULL; WHEN others THEN NULL; END $$;
-DO $$ BEGIN PERFORM remove_continuous_aggregate_policy('cag_apv_llb_stats_5m');     EXCEPTION WHEN undefined_function THEN NULL; WHEN others THEN NULL; END $$;
-
-SELECT add_continuous_aggregate_policy('cag_apv_virtual_stats_5m', start_offset => INTERVAL '30 days', end_offset => INTERVAL '1 hour', schedule_interval => INTERVAL '15 minutes');
-SELECT add_continuous_aggregate_policy('cag_apv_real_stats_5m',    start_offset => INTERVAL '30 days', end_offset => INTERVAL '1 hour', schedule_interval => INTERVAL '15 minutes');
-SELECT add_continuous_aggregate_policy('cag_apv_llb_stats_5m',     start_offset => INTERVAL '30 days', end_offset => INTERVAL '1 hour', schedule_interval => INTERVAL '15 minutes');
-
-COMMIT;
-
--- 13) Optional immediate backfill (outside tx)
-CALL refresh_continuous_aggregate('cag_apv_virtual_stats_5m', now() - INTERVAL '30 days', now());
-CALL refresh_continuous_aggregate('cag_apv_real_stats_5m',    now() - INTERVAL '30 days', now());
-CALL refresh_continuous_aggregate('cag_apv_llb_stats_5m',     now() - INTERVAL '30 days', now());
-
-
-/* =========================
-   AG (Array Gateway) schema
-   ========================= */
-
-BEGIN;
-
--- 1) MEASUREMENT: ag_device_metrics  (from [[inputs.snmp]] name="snmp_system")
-CREATE TABLE IF NOT EXISTS ag_device_metrics (
-    time TIMESTAMPTZ NOT NULL,
-    agent_host TEXT NOT NULL,
-    cpu_usage DOUBLE PRECISION,
-    net_mem_usage DOUBLE PRECISION,
-    total_openssl_conns BIGINT,
-    connections BIGINT,
-    requests BIGINT,
-    total_in BIGINT,
-    total_out BIGINT,
-    PRIMARY KEY (time, agent_host)
-);
-SELECT create_hypertable('ag_device_metrics','time','agent_host',
-                         number_partitions => 4, if_not_exists => TRUE);
-CREATE INDEX IF NOT EXISTS idx_ag_metrics_agent_time ON ag_device_metrics (agent_host, time DESC);
-
--- 2) TABLE: ag_virtual_site_stats  (from [[inputs.snmp.table]] name="virtualSiteStats")
-CREATE TABLE IF NOT EXISTS ag_virtual_site_stats (
-    time TIMESTAMPTZ NOT NULL,
-    agent_host TEXT NOT NULL,
-    id TEXT NOT NULL,
-    ip TEXT NULL,
-    active_sessions BIGINT,
-    success_login BIGINT,
-    failure_login BIGINT,
-    error_login BIGINT,
-    success_logout BIGINT,
-    client_bytes_in BIGINT,
-    client_bytes_out BIGINT,
-    locked_login BIGINT,
-    rejected_login BIGINT,
-    server_bytes_in BIGINT,
-    server_bytes_out BIGINT,
-    PRIMARY KEY (time, agent_host, id)
-);
-SELECT create_hypertable('ag_virtual_site_stats','time','agent_host',
-                         number_partitions => 8, if_not_exists => TRUE);
-CREATE INDEX IF NOT EXISTS idx_ag_vsite_id ON ag_virtual_site_stats (id);
-CREATE INDEX IF NOT EXISTS idx_ag_vsite_agent_time ON ag_virtual_site_stats (agent_host, time DESC);
-CREATE INDEX IF NOT EXISTS idx_ag_vsite_id_time ON ag_virtual_site_stats (id, time DESC);
-
--- 3) TABLE: ag_vpn_stats  (from [[inputs.snmp.table]] name="vpnStats")
-CREATE TABLE IF NOT EXISTS ag_vpn_stats (
-    time TIMESTAMPTZ NOT NULL,
-    agent_host TEXT NOT NULL,
-    id TEXT NOT NULL,
-    tunnels_open BIGINT,
-    tunnels_est BIGINT,
-    tunnels_rejected BIGINT,
-    tunnels_terminated BIGINT,
-    bytes_in BIGINT,
-    bytes_out BIGINT,
-    unauth_packets_in BIGINT,
-    client_app_bytes_in BIGINT,
-    client_app_bytes_out BIGINT,
-    PRIMARY KEY (time, agent_host, id)
-);
-SELECT create_hypertable('ag_vpn_stats','time','agent_host',
-                         number_partitions => 8, if_not_exists => TRUE);
-CREATE INDEX IF NOT EXISTS idx_ag_vpn_id ON ag_vpn_stats (id);
-CREATE INDEX IF NOT EXISTS idx_ag_vpn_agent_time ON ag_vpn_stats (agent_host, time DESC);
-CREATE INDEX IF NOT EXISTS idx_ag_vpn_id_time ON ag_vpn_stats (id, time DESC);
-
--- 4) TABLE: ag_web_stats  (from [[inputs.snmp.table]] name="webStats")
-CREATE TABLE IF NOT EXISTS ag_web_stats (
-    time TIMESTAMPTZ NOT NULL,
-    agent_host TEXT NOT NULL,
-    id TEXT NOT NULL,
-    authorized_req BIGINT,
-    unauthorized_req BIGINT,
-    client_bytes_in BIGINT,
-    client_bytes_out BIGINT,
-    server_bytes_in BIGINT,
-    server_bytes_out BIGINT,
-    PRIMARY KEY (time, agent_host, id)
-);
-SELECT create_hypertable('ag_web_stats','time','agent_host',
-                         number_partitions => 8, if_not_exists => TRUE);
-CREATE INDEX IF NOT EXISTS idx_ag_web_id ON ag_web_stats (id);
-CREATE INDEX IF NOT EXISTS idx_ag_web_agent_time ON ag_web_stats (agent_host, time DESC);
-CREATE INDEX IF NOT EXISTS idx_ag_web_id_time ON ag_web_stats (id, time DESC);
-
--- 5) Compression (row compression; segment by identity tags, order by time desc)
-ALTER TABLE IF EXISTS ag_device_metrics      SET (timescaledb.compress,
-    timescaledb.compress_segmentby = 'agent_host',
-    timescaledb.compress_orderby   = 'time DESC');
-
-ALTER TABLE IF EXISTS ag_virtual_site_stats  SET (timescaledb.compress,
-    timescaledb.compress_segmentby = 'agent_host, id',
-    timescaledb.compress_orderby   = 'time DESC');
-
-ALTER TABLE IF EXISTS ag_vpn_stats          SET (timescaledb.compress,
-    timescaledb.compress_segmentby = 'agent_host, id',
-    timescaledb.compress_orderby   = 'time DESC');
-
-ALTER TABLE IF EXISTS ag_web_stats          SET (timescaledb.compress,
-    timescaledb.compress_segmentby = 'agent_host, id',
-    timescaledb.compress_orderby   = 'time DESC');
-
--- 6) Compression policies (compress after 7 days)
-SELECT add_compression_policy('ag_device_metrics',      INTERVAL '7 days');
-SELECT add_compression_policy('ag_virtual_site_stats',  INTERVAL '7 days');
-SELECT add_compression_policy('ag_vpn_stats',           INTERVAL '7 days');
-SELECT add_compression_policy('ag_web_stats',           INTERVAL '7 days');
-
--- 7) Retention policies (drop after 180 days)
-SELECT add_retention_policy('ag_device_metrics',      INTERVAL '180 days');
-SELECT add_retention_policy('ag_virtual_site_stats',  INTERVAL '180 days');
-SELECT add_retention_policy('ag_vpn_stats',           INTERVAL '180 days');
-SELECT add_retention_policy('ag_web_stats',           INTERVAL '180 days');
-
--- 8) Continuous aggregates (5-minute) 
--- NOTE: Most values look like gauges/counters sampled by Telegraf.
--- For simplicity we expose AVG over the bucket. If you later switch to true counters,
--- use Timescale Toolkit or DERIVATIVE in a view to compute rates.
-CREATE MATERIALIZED VIEW IF NOT EXISTS cag_ag_device_metrics_5m
-WITH (timescaledb.continuous) AS
-SELECT
-  time_bucket('5 minutes', time) AS bucket,
-  agent_host,
-  avg(cpu_usage)            AS avg_cpu_pct,
-  avg(net_mem_usage)        AS avg_net_mem_pct,
-  avg(total_openssl_conns)  AS avg_total_openssl_conns,
-  avg(connections)          AS avg_connections,
-  avg(requests)             AS avg_requests,
-  avg(total_in)             AS avg_total_in,
-  avg(total_out)            AS avg_total_out
-FROM ag_device_metrics
-GROUP BY bucket, agent_host
-WITH NO DATA;
-
-CREATE MATERIALIZED VIEW IF NOT EXISTS cag_ag_virtual_site_stats_5m
-WITH (timescaledb.continuous) AS
-SELECT
-  time_bucket('5 minutes', time) AS bucket,
-  agent_host,
-  id,
-  avg(active_sessions)    AS avg_active_sessions,
-  avg(success_login)      AS avg_success_login,
-  avg(failure_login)      AS avg_failure_login,
-  avg(error_login)        AS avg_error_login,
-  avg(success_logout)     AS avg_success_logout,
-  avg(client_bytes_in)    AS avg_client_bytes_in,
-  avg(client_bytes_out)   AS avg_client_bytes_out,
-  avg(locked_login)       AS avg_locked_login,
-  avg(rejected_login)     AS avg_rejected_login,
-  avg(server_bytes_in)    AS avg_server_bytes_in,
-  avg(server_bytes_out)   AS avg_server_bytes_out
-FROM ag_virtual_site_stats
-GROUP BY bucket, agent_host, id
-WITH NO DATA;
-
-CREATE MATERIALIZED VIEW IF NOT EXISTS cag_ag_vpn_stats_5m
-WITH (timescaledb.continuous) AS
-SELECT
-  time_bucket('5 minutes', time) AS bucket,
-  agent_host,
-  id,
-  avg(tunnels_open)         AS avg_tunnels_open,
-  avg(tunnels_est)          AS avg_tunnels_est,
-  avg(tunnels_rejected)     AS avg_tunnels_rejected,
-  avg(tunnels_terminated)   AS avg_tunnels_terminated,
-  avg(bytes_in)             AS avg_bytes_in,
-  avg(bytes_out)            AS avg_bytes_out,
-  avg(unauth_packets_in)    AS avg_unauth_packets_in,
-  avg(client_app_bytes_in)  AS avg_client_app_bytes_in,
-  avg(client_app_bytes_out) AS avg_client_app_bytes_out
-FROM ag_vpn_stats
-GROUP BY bucket, agent_host, id
-WITH NO DATA;
-
-CREATE MATERIALIZED VIEW IF NOT EXISTS cag_ag_web_stats_5m
-WITH (timescaledb.continuous) AS
-SELECT
-  time_bucket('5 minutes', time) AS bucket,
-  agent_host,
-  id,
-  avg(authorized_req)    AS avg_authorized_req,
-  avg(unauthorized_req)  AS avg_unauthorized_req,
-  avg(client_bytes_in)   AS avg_client_bytes_in,
-  avg(client_bytes_out)  AS avg_client_bytes_out,
-  avg(server_bytes_in)   AS avg_server_bytes_in,
-  avg(server_bytes_out)  AS avg_server_bytes_out
-FROM ag_web_stats
-GROUP BY bucket, agent_host, id
-WITH NO DATA;
-
--- 9) Refresh policies for CAs (drop if present, then add)
-DO $$ BEGIN PERFORM remove_continuous_aggregate_policy('cag_ag_device_metrics_5m');     EXCEPTION WHEN undefined_function THEN NULL; WHEN others THEN NULL; END $$;
-DO $$ BEGIN PERFORM remove_continuous_aggregate_policy('cag_ag_virtual_site_stats_5m'); EXCEPTION WHEN undefined_function THEN NULL; WHEN others THEN NULL; END $$;
-DO $$ BEGIN PERFORM remove_continuous_aggregate_policy('cag_ag_vpn_stats_5m');          EXCEPTION WHEN undefined_function THEN NULL; WHEN others THEN NULL; END $$;
-DO $$ BEGIN PERFORM remove_continuous_aggregate_policy('cag_ag_web_stats_5m');          EXCEPTION WHEN undefined_function THEN NULL; WHEN others THEN NULL; END $$;
-
-SELECT add_continuous_aggregate_policy('cag_ag_device_metrics_5m',
-  start_offset => INTERVAL '30 days', end_offset => INTERVAL '1 hour', schedule_interval => INTERVAL '15 minutes');
-SELECT add_continuous_aggregate_policy('cag_ag_virtual_site_stats_5m',
-  start_offset => INTERVAL '30 days', end_offset => INTERVAL '1 hour', schedule_interval => INTERVAL '15 minutes');
-SELECT add_continuous_aggregate_policy('cag_ag_vpn_stats_5m',
-  start_offset => INTERVAL '30 days', end_offset => INTERVAL '1 hour', schedule_interval => INTERVAL '15 minutes');
-SELECT add_continuous_aggregate_policy('cag_ag_web_stats_5m',
-  start_offset => INTERVAL '30 days', end_offset => INTERVAL '1 hour', schedule_interval => INTERVAL '15 minutes');
-
-COMMIT;
-
--- 10) Optional immediate backfill (same window as APV)
-CALL refresh_continuous_aggregate('cag_ag_device_metrics_5m',      now() - INTERVAL '30 days', now());
-CALL refresh_continuous_aggregate('cag_ag_virtual_site_stats_5m',  now() - INTERVAL '30 days', now());
-CALL refresh_continuous_aggregate('cag_ag_vpn_stats_5m',           now() - INTERVAL '30 days', now());
-CALL refresh_continuous_aggregate('cag_ag_web_stats_5m',           now() - INTERVAL '30 days', now());
-
-
-/* =========================
-   ASF (Array Gateway) schema
-   ========================= */
-
-
-BEGIN;
-CREATE EXTENSION IF NOT EXISTS timescaledb;
-
--- 1) asf_device_metrics (single OIDs above)
-CREATE TABLE IF NOT EXISTS asf_device_metrics (
-  time TIMESTAMPTZ NOT NULL,
-  agent_host TEXT NOT NULL,
-  cpu_usage DOUBLE PRECISION,
-  mem_usage DOUBLE PRECISION,
-  net_mem_usage DOUBLE PRECISION,
-  total_openssl_conns BIGINT,
-  connections BIGINT,
-  requests BIGINT,
-  total_in BIGINT,
-  total_out BIGINT,
-  PRIMARY KEY (time, agent_host)
-);
-SELECT create_hypertable('asf_device_metrics','time','agent_host', number_partitions => 4, if_not_exists => TRUE);
-CREATE INDEX IF NOT EXISTS idx_asf_devmetrics_agent_time ON asf_device_metrics (agent_host, time DESC);
-
--- 2) asf_device_storage
-CREATE TABLE IF NOT EXISTS asf_device_storage (
-  time TIMESTAMPTZ NOT NULL,
-  agent_host TEXT NOT NULL,
-  prefix TEXT NOT NULL,
-  size BIGINT,
-  used BIGINT,
-  alloc_unit BIGINT,
-  PRIMARY KEY (time, agent_host, prefix)
-);
-SELECT create_hypertable('asf_device_storage','time','agent_host', number_partitions => 4, if_not_exists => TRUE);
-CREATE INDEX IF NOT EXISTS idx_asf_storage_prefix ON asf_device_storage (prefix);
-CREATE INDEX IF NOT EXISTS idx_asf_storage_agent_time ON asf_device_storage (agent_host, time DESC);
-
--- 3) asf_ssl_statistics (totals)
-CREATE TABLE IF NOT EXISTS asf_ssl_statistics (
-  time TIMESTAMPTZ NOT NULL,
-  agent_host TEXT NOT NULL,
-  total_openssl_conns BIGINT,
-  total_accepted_conns BIGINT,
-  total_requested_conns BIGINT,
-  PRIMARY KEY (time, agent_host)
-);
-SELECT create_hypertable('asf_ssl_statistics','time','agent_host', number_partitions => 4, if_not_exists => TRUE);
-CREATE INDEX IF NOT EXISTS idx_asf_sslstats_agent_time ON asf_ssl_statistics (agent_host, time DESC);
-
--- 4) asf_ssl_host_statistics
-CREATE TABLE IF NOT EXISTS asf_ssl_host_statistics (
-  time TIMESTAMPTZ NOT NULL,
-  agent_host TEXT NOT NULL,
-  ssl_index BIGINT,
-  vhost_name TEXT,
-  open_ssl_conns BIGINT,
-  accepted_conns BIGINT,
-  requested_conns BIGINT,
-  resumed_sess BIGINT,
-  resumable_sess BIGINT,
-  miss_sess BIGINT,
-  conns_per_sec DOUBLE PRECISION,
-  PRIMARY KEY (time, agent_host, ssl_index)
-);
-SELECT create_hypertable('asf_ssl_host_statistics','time','agent_host', number_partitions => 4, if_not_exists => TRUE);
-CREATE INDEX IF NOT EXISTS idx_asf_sslhost_vhost_time ON asf_ssl_host_statistics (vhost_name, time DESC);
-CREATE INDEX IF NOT EXISTS idx_asf_sslhost_agent_time ON asf_ssl_host_statistics (agent_host, time DESC);
-
--- 5) asf_vip_group_statistics
-CREATE TABLE IF NOT EXISTS asf_vip_group_statistics (
-  time TIMESTAMPTZ NOT NULL,
-  agent_host TEXT NOT NULL,
-  vip_status BIGINT,
-  host_name TEXT,
-  current_tme TEXT,
-  total_ip_pkts_in BIGINT,
-  total_ip_pkts_out BIGINT,
-  total_ip_bytes_in BIGINT,
-  total_ip_bytes_out BIGINT,
-  PRIMARY KEY (time, agent_host)
-);
-SELECT create_hypertable('asf_vip_group_statistics','time','agent_host', number_partitions => 4, if_not_exists => TRUE);
-CREATE INDEX IF NOT EXISTS idx_asf_vipgrp_agent_time ON asf_vip_group_statistics (agent_host, time DESC);
-
--- 6) asf_vip_statistics (per IP)
-CREATE TABLE IF NOT EXISTS asf_vip_statistics (
-  time TIMESTAMPTZ NOT NULL,
-  agent_host TEXT NOT NULL,
-  ip_index BIGINT NOT NULL,
-  ip_address TEXT,
-  ip_pkts_in BIGINT,
-  ip_bytes_in BIGINT,
-  ip_pkts_out BIGINT,
-  ip_bytes_out BIGINT,
-  start_time TEXT,
-  ip_addr_type TEXT,
-  PRIMARY KEY (time, agent_host, ip_index)
-);
-SELECT create_hypertable('asf_vip_statistics','time','agent_host', number_partitions => 4, if_not_exists => TRUE);
-CREATE INDEX IF NOT EXISTS idx_asf_vip_ip_time ON asf_vip_statistics (ip_address, time DESC);
-CREATE INDEX IF NOT EXISTS idx_asf_vip_agent_time ON asf_vip_statistics (agent_host, time DESC);
-
--- 7) asf_syslog_history
-CREATE TABLE IF NOT EXISTS asf_syslog_history (
-  time TIMESTAMPTZ NOT NULL,
-  agent_host TEXT NOT NULL,
-  idx BIGINT NOT NULL,
-  severity BIGINT,
-  msg_text TEXT,
-  PRIMARY KEY (time, agent_host, idx)
-);
-SELECT create_hypertable('asf_syslog_history','time','agent_host', number_partitions => 4, if_not_exists => TRUE);
-CREATE INDEX IF NOT EXISTS idx_asf_syslog_agent_time ON asf_syslog_history (agent_host, time DESC);
-
--- 8) asf_performance_statistics
-CREATE TABLE IF NOT EXISTS asf_performance_statistics (
-  time TIMESTAMPTZ NOT NULL,
-  agent_host TEXT NOT NULL,
-  cpu_utilization DOUBLE PRECISION,
-  connections_per_sec DOUBLE PRECISION,
-  requests_per_sec DOUBLE PRECISION,
-  ssl_core_utilization DOUBLE PRECISION,
-  ssl_ae_core_utilization DOUBLE PRECISION,
-  ssl_se_core_utilization DOUBLE PRECISION,
-  PRIMARY KEY (time, agent_host)
-);
-SELECT create_hypertable('asf_performance_statistics','time','agent_host', number_partitions => 4, if_not_exists => TRUE);
-CREATE INDEX IF NOT EXISTS idx_asf_perf_agent_time ON asf_performance_statistics (agent_host, time DESC);
-
--- 9) asf_http_service
-CREATE TABLE IF NOT EXISTS asf_http_service (
-  time TIMESTAMPTZ NOT NULL,
-  agent_host TEXT NOT NULL,
-  http_service_index BIGINT NOT NULL,
-  http_service_id TEXT,
-  http_service_cc BIGINT,
-  http_service_cps BIGINT,
-  http_service_rps_get BIGINT,
-  http_service_rps_post BIGINT,
-  http_service_rps_head BIGINT,
-  http_service_rps_put BIGINT,
-  http_service_rps_delete BIGINT,
-  http_service_rps_total BIGINT,
-  http_service_anomaly_method BIGINT,
-  http_service_anomaly_requestline BIGINT,
-  http_service_anomaly_host BIGINT,
-  http_service_anomaly_connection BIGINT,
-  http_service_anomaly_contentlength BIGINT,
-  http_service_anomaly_range BIGINT,
-  http_service_traffic_inbound_in_byte BIGINT,
-  http_service_traffic_inbound_in_packet BIGINT,
-  http_service_traffic_inbound_out_byte BIGINT,
-  http_service_traffic_inbound_out_packet BIGINT,
-  http_service_traffic_outbound_in_byte BIGINT,
-  http_service_traffic_outbound_in_packet BIGINT,
-  http_service_traffic_outbound_out_byte BIGINT,
-  http_service_traffic_outbound_out_packet BIGINT,
-  http_service_drop_total BIGINT,
-  http_service_drop_type_source BIGINT,
-  http_service_drop_type_man_bl BIGINT,
-  http_service_drop_type_dyn_bl BIGINT,
-  http_service_drop_type_acl BIGINT,
-  http_service_drop_type_ddos BIGINT,
-  http_service_drop_type_waf BIGINT,
-  http_service_drop_type_filter BIGINT,
-  http_service_drop_type_anomaly BIGINT,
-  http_service_drop_type_parse_fail BIGINT,
-  http_service_drop_type_resource BIGINT,
-  http_service_drop_type_profile BIGINT,
-  PRIMARY KEY (time, agent_host, http_service_index)
-);
-SELECT create_hypertable('asf_http_service','time','agent_host', number_partitions => 8, if_not_exists => TRUE);
-CREATE INDEX IF NOT EXISTS idx_asf_http_id_time ON asf_http_service (http_service_id, time DESC);
-CREATE INDEX IF NOT EXISTS idx_asf_http_agent_time ON asf_http_service (agent_host, time DESC);
-
--- 10) asf_https_service
-CREATE TABLE IF NOT EXISTS asf_https_service (
-  time TIMESTAMPTZ NOT NULL,
-  agent_host TEXT NOT NULL,
-  https_service_index BIGINT NOT NULL,
-  https_service_id TEXT,
-  https_service_cc BIGINT,
-  https_service_cps BIGINT,
-  https_service_rps_get BIGINT,
-  https_service_rps_post BIGINT,
-  https_service_rps_head BIGINT,
-  https_service_rps_put BIGINT,
-  https_service_rps_delete BIGINT,
-  https_service_rps_total BIGINT,
-  https_service_anomaly_method BIGINT,
-  https_service_anomaly_requestline BIGINT,
-  https_service_anomaly_host BIGINT,
-  https_service_anomaly_connection BIGINT,
-  https_service_anomaly_contentlength BIGINT,
-  https_service_anomaly_range BIGINT,
-  https_service_traffic_inbound_in_byte BIGINT,
-  https_service_traffic_inbound_in_packets BIGINT,
-  https_service_traffic_inbound_out_byte BIGINT,
-  https_service_traffic_inbound_out_packets BIGINT,
-  https_service_traffic_outbound_in_byte BIGINT,
-  https_service_traffic_outbound_in_packets BIGINT,
-  https_service_traffic_outbound_out_byte BIGINT,
-  https_service_traffic_outbound_out_packets BIGINT,
-  https_service_ssl_traffic_inbound_in_byte BIGINT,
-  https_service_ssl_traffic_inbound_in_packets BIGINT,
-  https_service_ssl_traffic_inbound_out_byte BIGINT,
-  https_service_ssl_traffic_inbound_out_packets BIGINT,
-  https_service_ssl_traffic_outbound_in_byte BIGINT,
-  https_service_ssl_traffic_outbound_in_packets BIGINT,
-  https_service_ssl_traffic_outbound_out_byte BIGINT,
-  https_service_ssl_traffic_outbound_out_packets BIGINT,
-  https_service_http_drop_total BIGINT,
-  https_service_http_drop_type_source BIGINT,
-  https_service_http_drop_type_man_bl BIGINT,
-  https_service_http_drop_type_dyn_bl BIGINT,
-  https_service_http_drop_type_acl BIGINT,
-  https_service_http_drop_type_ddos BIGINT,
-  https_service_http_drop_type_waf BIGINT,
-  https_service_http_drop_type_filter BIGINT,
-  https_service_http_drop_type_anomaly BIGINT,
-  https_service_http_drop_type_parse_fail BIGINT,
-  https_service_http_drop_type_resource BIGINT,
-  https_service_http_drop_type_profile BIGINT,
-  PRIMARY KEY (time, agent_host, https_service_index)
-);
-SELECT create_hypertable('asf_https_service','time','agent_host', number_partitions => 8, if_not_exists => TRUE);
-CREATE INDEX IF NOT EXISTS idx_asf_https_id_time ON asf_https_service (https_service_id, time DESC);
-CREATE INDEX IF NOT EXISTS idx_asf_https_agent_time ON asf_https_service (agent_host, time DESC);
-
--- Compression (row), 7d; Retention 180d (mirror AG/APV style)
-DO $$ BEGIN PERFORM remove_columnstore_policy('asf_device_metrics');           EXCEPTION WHEN undefined_function THEN NULL; WHEN others THEN NULL; END $$;
-DO $$ BEGIN PERFORM remove_columnstore_policy('asf_device_storage');           EXCEPTION WHEN undefined_function THEN NULL; WHEN others THEN NULL; END $$;
-DO $$ BEGIN PERFORM remove_columnstore_policy('asf_ssl_statistics');           EXCEPTION WHEN undefined_function THEN NULL; WHEN others THEN NULL; END $$;
-DO $$ BEGIN PERFORM remove_columnstore_policy('asf_ssl_host_statistics');      EXCEPTION WHEN undefined_function THEN NULL; WHEN others THEN NULL; END $$;
-DO $$ BEGIN PERFORM remove_columnstore_policy('asf_vip_group_statistics');     EXCEPTION WHEN undefined_function THEN NULL; WHEN others THEN NULL; END $$;
-DO $$ BEGIN PERFORM remove_columnstore_policy('asf_vip_statistics');           EXCEPTION WHEN undefined_function THEN NULL; WHEN others THEN NULL; END $$;
-DO $$ BEGIN PERFORM remove_columnstore_policy('asf_syslog_history');           EXCEPTION WHEN undefined_function THEN NULL; WHEN others THEN NULL; END $$;
-DO $$ BEGIN PERFORM remove_columnstore_policy('asf_performance_statistics');   EXCEPTION WHEN undefined_function THEN NULL; WHEN others THEN NULL; END $$;
-DO $$ BEGIN PERFORM remove_columnstore_policy('asf_http_service');             EXCEPTION WHEN undefined_function THEN NULL; WHEN others THEN NULL; END $$;
-DO $$ BEGIN PERFORM remove_columnstore_policy('asf_https_service');            EXCEPTION WHEN undefined_function THEN NULL; WHEN others THEN NULL; END $$;
-
-ALTER TABLE IF EXISTS asf_device_metrics           SET (timescaledb.compress, timescaledb.compress_segmentby='agent_host', timescaledb.compress_orderby='time DESC');
-ALTER TABLE IF EXISTS asf_device_storage           SET (timescaledb.compress, timescaledb.compress_segmentby='agent_host, prefix', timescaledb.compress_orderby='time DESC');
-ALTER TABLE IF EXISTS asf_ssl_statistics           SET (timescaledb.compress, timescaledb.compress_segmentby='agent_host', timescaledb.compress_orderby='time DESC');
-ALTER TABLE IF EXISTS asf_ssl_host_statistics      SET (timescaledb.compress, timescaledb.compress_segmentby='agent_host, ssl_index', timescaledb.compress_orderby='time DESC');
-ALTER TABLE IF EXISTS asf_vip_group_statistics     SET (timescaledb.compress, timescaledb.compress_segmentby='agent_host', timescaledb.compress_orderby='time DESC');
-ALTER TABLE IF EXISTS asf_vip_statistics           SET (timescaledb.compress, timescaledb.compress_segmentby='agent_host, ip_index', timescaledb.compress_orderby='time DESC');
-ALTER TABLE IF EXISTS asf_syslog_history           SET (timescaledb.compress, timescaledb.compress_segmentby='agent_host', timescaledb.compress_orderby='time DESC');
-ALTER TABLE IF EXISTS asf_performance_statistics   SET (timescaledb.compress, timescaledb.compress_segmentby='agent_host', timescaledb.compress_orderby='time DESC');
-ALTER TABLE IF EXISTS asf_http_service             SET (timescaledb.compress, timescaledb.compress_segmentby='agent_host, http_service_index', timescaledb.compress_orderby='time DESC');
-ALTER TABLE IF EXISTS asf_https_service            SET (timescaledb.compress, timescaledb.compress_segmentby='agent_host, https_service_index', timescaledb.compress_orderby='time DESC');
-
-SELECT add_compression_policy('asf_device_metrics',           INTERVAL '7 days');
-SELECT add_compression_policy('asf_device_storage',           INTERVAL '7 days');
-SELECT add_compression_policy('asf_ssl_statistics',           INTERVAL '7 days');
-SELECT add_compression_policy('asf_ssl_host_statistics',      INTERVAL '7 days');
-SELECT add_compression_policy('asf_vip_group_statistics',     INTERVAL '7 days');
-SELECT add_compression_policy('asf_vip_statistics',           INTERVAL '7 days');
-SELECT add_compression_policy('asf_syslog_history',           INTERVAL '7 days');
-SELECT add_compression_policy('asf_performance_statistics',   INTERVAL '7 days');
-SELECT add_compression_policy('asf_http_service',             INTERVAL '7 days');
-SELECT add_compression_policy('asf_https_service',            INTERVAL '7 days');
-
-SELECT add_retention_policy('asf_device_metrics',           INTERVAL '180 days');
-SELECT add_retention_policy('asf_device_storage',           INTERVAL '180 days');
-SELECT add_retention_policy('asf_ssl_statistics',           INTERVAL '180 days');
-SELECT add_retention_policy('asf_ssl_host_statistics',      INTERVAL '180 days');
-SELECT add_retention_policy('asf_vip_group_statistics',     INTERVAL '180 days');
-SELECT add_retention_policy('asf_vip_statistics',           INTERVAL '180 days');
-SELECT add_retention_policy('asf_syslog_history',           INTERVAL '180 days');
-SELECT add_retention_policy('asf_performance_statistics',   INTERVAL '180 days');
-SELECT add_retention_policy('asf_http_service',             INTERVAL '180 days');
-SELECT add_retention_policy('asf_https_service',            INTERVAL '180 days');
-COMMIT;
Index: /branches/amp_4_0/platform/tools/update_opensearch_branding.sh
===================================================================
--- /branches/amp_4_0/platform/tools/update_opensearch_branding.sh	(revision 2855)
+++ /branches/amp_4_0/platform/tools/update_opensearch_branding.sh	(nonexistent)
@@ -1,152 +0,0 @@
-#!/bin/bash
-
-# Script to update OpenSearch Dashboards branding configuration
-# Usage: sudo bash update_opensearch_branding.sh
-
-CONFIG_FILE="/etc/opensearch-dashboards/opensearch_dashboards.yml"
-BACKUP_FILE="${CONFIG_FILE}.bak_$(date +%Y%m%d%H%M%S)"
-
-# Colors for output
-GREEN='\033[0;32m'
-RED='\033[0;31m'
-NC='\033[0m' # No Color
-
-log_info() {
-    echo -e "${GREEN}[INFO]${NC} $1"
-}
-
-log_error() {
-    echo -e "${RED}[ERROR]${NC} $1"
-    exit 1
-}
-
-# Check for root privileges
-if [[ $EUID -ne 0 ]]; then
-   log_error "This script must be run as root. Please use 'sudo bash $0'."
-fi
-
-# Ensure yq is installed
-if ! command -v yq &> /dev/null; then
-    log_info "yq not found. Installing yq..."
-    # Attempt to install via dnf if available, otherwise suggest manual install
-    if command -v dnf &> /dev/null; then
-         # Ensure epel-release is installed for yq
-        if ! rpm -q epel-release &> /dev/null; then
-             log_info "Installing epel-release..."
-             dnf install -y epel-release || log_error "Failed to install epel-release."
-        fi
-        dnf install -y yq || log_error "Failed to install yq via dnf."
-    else
-        log_error "dnf not found. Please install 'yq' manually."
-    fi
-fi
-
-if [ ! -f "$CONFIG_FILE" ]; then
-    log_error "Configuration file not found: $CONFIG_FILE"
-fi
-
-log_info "Backing up configuration file..."
-cp "$CONFIG_FILE" "$BACKUP_FILE" || log_error "Failed to backup config file."
-
-log_info "Updating branding configuration..."
-
-# Check if branding already exists
-if yq eval '.opensearchDashboards.branding' "$CONFIG_FILE" | grep -q "null"; then
-    # Block doesn't exist, create it
-    yq eval -i '.opensearchDashboards.branding.applicationTitle = "Array Networks Analytics"' "$CONFIG_FILE"
-else
-    # Block exists, update title
-    yq eval -i '.opensearchDashboards.branding.applicationTitle = "Array Networks Analytics"' "$CONFIG_FILE"
-fi
-
-# Handle Favicon
-CUSTOM_FAVICON="/opt/images/favicon.ico"
-DASHBOARDS_FAVICON_DIR="/usr/share/opensearch-dashboards/src/core/server/core_app/assets/favicons"
-DASHBOARDS_FAVICON_PATH="${DASHBOARDS_FAVICON_DIR}/favicon.ico"
-
-if [ -f "$CUSTOM_FAVICON" ]; then
-    log_info "Custom favicon found at $CUSTOM_FAVICON"
-    
-    if [ -d "$DASHBOARDS_FAVICON_DIR" ]; then
-        log_info "Backing up original favicon..."
-        cp "$DASHBOARDS_FAVICON_PATH" "${DASHBOARDS_FAVICON_PATH}.bak_$(date +%Y%m%d%H%M%S)"
-        
-        log_info "Copying custom favicon..."
-        cp -f "$CUSTOM_FAVICON" "$DASHBOARDS_FAVICON_PATH"
-        
-        # Ensure ownership is correct (usually opensearch-dashboards user)
-        if id "opensearch-dashboards" &>/dev/null; then
-             chown opensearch-dashboards:opensearch-dashboards "$DASHBOARDS_FAVICON_PATH"
-        fi
-
-        log_info "Updating favicon configuration..."
-        # Set faviconUrl
-        yq eval -i '.opensearchDashboards.branding.faviconUrl = "/visualization/ui/favicons/favicon.ico"' "$CONFIG_FILE"
-    else
-        log_error "OpenSearch Dashboards favicon directory not found: $DASHBOARDS_FAVICON_DIR"
-    fi
-else
-    log_info "No custom favicon found at $CUSTOM_FAVICON. Skipping favicon update."
-fi
-
-# Handle Logos (Default Logo, Mark, Loading Logo)
-IMAGES_DIR="/opt/images"
-CUSTOM_ASSETS_DIR="/usr/share/opensearch-dashboards/src/core/server/core_app/assets/custom"
-ARRAY_LOGO="array_logo.svg"
-MARK_LOGO="mark_array_logo.png"
-LOADING_LOGO="loading_array_logo.png"
-
-# Check if any of the logo files exist
-if [ -f "$IMAGES_DIR/$ARRAY_LOGO" ] || [ -f "$IMAGES_DIR/$MARK_LOGO" ] || [ -f "$IMAGES_DIR/$LOADING_LOGO" ]; then
-    log_info "Custom logos found in $IMAGES_DIR"
-
-    # Create custom assets directory if it doesn't exist
-    if [ ! -d "$CUSTOM_ASSETS_DIR" ]; then
-        log_info "Creating custom assets directory: $CUSTOM_ASSETS_DIR"
-        mkdir -p "$CUSTOM_ASSETS_DIR"
-        # Set ownership
-        if id "opensearch-dashboards" &>/dev/null; then
-             chown opensearch-dashboards:opensearch-dashboards "$CUSTOM_ASSETS_DIR"
-        fi
-    fi
-
-    # Function to process a single logo file
-    process_logo() {
-        local filename=$1
-        local config_key=$2
-        local source_path="$IMAGES_DIR/$filename"
-        local target_path="$CUSTOM_ASSETS_DIR/$filename"
-        local url_path="/visualization/ui/custom/$filename"
-
-        if [ -f "$source_path" ]; then
-            log_info "Copying $filename..."
-            cp -f "$source_path" "$target_path"
-            
-            # Ensure ownership
-            if id "opensearch-dashboards" &>/dev/null; then
-                 chown opensearch-dashboards:opensearch-dashboards "$target_path"
-            fi
-            
-            log_info "Updating config for $filename..."
-            yq eval -i ".opensearchDashboards.branding.${config_key} = \"${url_path}\"" "$CONFIG_FILE"
-        else
-            log_info "$filename not found, skipping..."
-        fi
-    }
-
-    process_logo "$ARRAY_LOGO" "logo.defaultUrl"
-    process_logo "$MARK_LOGO" "mark.defaultUrl"
-    process_logo "$LOADING_LOGO" "loadingLogo.defaultUrl"
-
-else
-    log_info "No custom logos found in $IMAGES_DIR. Skipping logo updates."
-fi
-
-if [ $? -eq 0 ]; then
-    log_info "Configuration updated successfully."
-    log_info "Please restart OpenSearch Dashboards to apply changes:"
-    log_info "sudo systemctl restart opensearch-dashboards"
-else
-    log_error "Failed to update configuration. Restoring backup..."
-    cp "$BACKUP_FILE" "$CONFIG_FILE"
-fi
