Index: /branches/amp_4_0/platform/README.md
===================================================================
--- /branches/amp_4_0/platform/README.md	(revision 2837)
+++ /branches/amp_4_0/platform/README.md	(working copy)
@@ -1,3 +1,8 @@
-### directory - platform
+# directory - platform
 
-Within this directory, you'll find the scripts and configurations specifically designed to enhance the AMP device platform.
\ No newline at end of file
+Within this directory, you'll find the scripts and configurations specifically designed to enhance the AMP device platform.
+
+The directory is organized into two primary deployment approaches:
+
+- **[tools/container](platform/tools/container)**: Contains the scripts and configurations for the **containerized** deployment approach using Docker and Docker Compose.
+- **[tools/scripts](platform/tools/scripts)**: Contains the scripts for the **traditional** (bare-metal or VM) installation approach, where services are installed directly on the host system.
Index: /branches/amp_4_0/platform/tools/container/.env
===================================================================
--- /branches/amp_4_0/platform/tools/container/.env	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/.env	(working copy)
@@ -0,0 +1,26 @@
+# .env file for AMP Docker Stack
+
+# OpenSearch Credentials
+OPENSEARCH_INITIAL_ADMIN_PASSWORD=Arr@y2050
+OPENSEARCH_JAVA_OPTS="-Xms1g -Xmx1g"
+OPENSEARCH_JWT_SECRET="Arr@y2050"
+
+# TimescaleDB / Postgres Credentials
+POSTGRES_USER=postgres
+POSTGRES_PASSWORD=Arr@y2050
+POSTGRES_DB=postgres
+AMP_DB_NAME=amp_ts
+AMP_DB_USER=amp_ts_user
+AMP_DB_PASSWORD=Array@123$
+
+# Grafana
+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"
+
+# Host IP for Nginx to reach backend on host
+HOST_BACKEND_IP=host.docker.internal
Index: /branches/amp_4_0/platform/tools/container/README.md
===================================================================
--- /branches/amp_4_0/platform/tools/container/README.md	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/README.md	(working copy)
@@ -0,0 +1,158 @@
+# 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>/...`.
Index: /branches/amp_4_0/platform/tools/container/compose/backend.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/compose/backend.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/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/compose/base.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/compose/base.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/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/compose/configurator.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/compose/configurator.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/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/compose/grafana.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/compose/grafana.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/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/compose/logstash.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/compose/logstash.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/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/compose/nginx.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/compose/nginx.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/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/compose/opensearch-dashboards.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/compose/opensearch-dashboards.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/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/compose/opensearch.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/compose/opensearch.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/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/compose/pgbouncer.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/compose/pgbouncer.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/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/compose/setup.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/compose/setup.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/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/compose/telegraf.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/compose/telegraf.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/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/compose/timescaledb.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/compose/timescaledb.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/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/install_prerequisites.sh
===================================================================
--- /branches/amp_4_0/platform/tools/container/install_prerequisites.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/install_prerequisites.sh	(working copy)
@@ -0,0 +1,148 @@
+#!/bin/bash
+
+# install_prerequisites.sh
+# Check and install necessary tools for the Docker validation environment.
+
+echo "--- Checking Prerequisites ---"
+
+OS="$(uname -s)"
+echo "Detected OS: $OS"
+
+check_cmd() {
+    command -v "$1" >/dev/null 2>&1
+}
+
+install_linux() {
+    if [ -f /etc/os-release ]; then
+        . /etc/os-release
+        PKG_MANAGER=""
+        case $ID in
+            debian|ubuntu|kali)
+                PKG_MANAGER="apt-get"
+                ;;
+            centos|fedora|rhel|rocky|almalinux)
+                PKG_MANAGER="dnf"
+                ;;
+            *)
+                echo "⚠️  Unsupported Linux distribution: $ID"
+                exit 1
+                ;;
+        esac
+    else
+        echo "⚠️  Cannot detect Linux distribution."
+        exit 1
+    fi
+
+    echo "Using package manager: $PKG_MANAGER"
+    
+    # Kernel limits for OpenSearch
+    echo "Checking vm.max_map_count..."
+    CURRENT_MAP_COUNT=$(sysctl -n vm.max_map_count)
+    if [ "$CURRENT_MAP_COUNT" -lt 262144 ]; then
+        echo "Updating vm.max_map_count to 262144 (required for OpenSearch)..."
+        echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf
+        sudo sysctl -p
+    else
+        echo "✅ vm.max_map_count is sufficient ($CURRENT_MAP_COUNT)."
+    fi
+
+    # Update Repos
+    echo "Updating package repositories..."
+    if [ "$PKG_MANAGER" == "apt-get" ]; then
+        sudo apt-get update -y
+        
+        # Generic installation for Debian/Ubuntu (custom scripts are RHEL specific)
+        if ! check_cmd python3; then
+            echo "Installing Python3..."
+            sudo apt-get install -y python3
+        fi
+        if ! check_cmd java; then
+            echo "Installing Java (OpenJDK)..."
+            sudo apt-get install -y openjdk-17-jre
+        fi
+        
+    elif [ "$PKG_MANAGER" == "dnf" ]; then
+        # RHEL/Rocky Specific Logic using Custom Installers
+        SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+        SETUP_DIR="$SCRIPT_DIR/services/setup"
+        
+        # Install Python 3.13 via custom script
+        if [ -f "$SETUP_DIR/install_python.sh" ]; then
+             echo "Invoking custom Python installer (install_python.sh)..."
+             sudo bash "$SETUP_DIR/install_python.sh"
+        else
+             echo "⚠️  Custom install_python.sh not found at $SETUP_DIR/install_python.sh. Falling back to dnf."
+             sudo dnf install -y python3
+        fi
+        
+        # Install Java via custom script
+        if [ -f "$SETUP_DIR/install_java.sh" ]; then
+             echo "Invoking custom Java installer (install_java.sh)..."
+             sudo bash "$SETUP_DIR/install_java.sh"
+        else
+             echo "⚠️  Custom install_java.sh not found at $SETUP_DIR/install_java.sh. Falling back to dnf."
+             sudo dnf install -y java-17-openjdk
+        fi
+
+        # Install curl via custom script
+        if [ -f "$SETUP_DIR/install_curl.sh" ]; then
+             echo "Invoking custom curl installer (install_curl.sh)..."
+             sudo bash "$SETUP_DIR/install_curl.sh"
+        else
+             echo "⚠️  Custom install_curl.sh not found at $SETUP_DIR/install_curl.sh. Falling back to dnf."
+             sudo $PKG_MANAGER install -y curl
+        fi
+    fi
+
+    # rsync
+    if ! check_cmd rsync; then
+        echo "Installing rsync..."
+        sudo $PKG_MANAGER install -y rsync
+    else
+        echo "✅ rsync is installed."
+    fi
+
+    # docker
+    if ! check_cmd docker; then
+        echo "Installing Docker..."
+        if [ "$PKG_MANAGER" == "apt-get" ]; then
+             # Update apt repo
+             sudo apt-get update
+             sudo apt-get install -y ca-certificates curl gnupg
+             curl -fsSL https://get.docker.com | sh
+        elif [ "$PKG_MANAGER" == "dnf" ]; then
+             # Install config manager plugin
+             sudo $PKG_MANAGER install -y dnf-plugins-core
+             
+             # Add Docker Repo
+             sudo $PKG_MANAGER config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
+             
+             # Install Docker packages
+             sudo $PKG_MANAGER install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
+             
+             sudo systemctl start docker
+             sudo systemctl enable docker
+        fi
+    else
+        echo "✅ Docker is installed."
+    fi
+    
+    # Check docker compose functionality
+    if docker compose version >/dev/null 2>&1; then
+       echo "✅ Docker Compose Plugin is working."
+    else
+       echo "⚠️  Docker Compose Plugin might be missing or not in PATH."
+    fi
+}
+
+case "$OS" in
+    Linux)
+        install_linux
+        ;;
+    *)
+        echo "❌ Unsupported OS: $OS. This script is designed for Linux."
+        exit 1
+        ;;
+esac
+
+echo "--- Prerequisites Check Complete ---"

Property changes on: platform/tools/container/install_prerequisites.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	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/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/manage_service.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: /branches/amp_4_0/platform/tools/container/services/backend/Dockerfile
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/backend/Dockerfile	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/backend/Dockerfile	(working copy)
@@ -0,0 +1,63 @@
+FROM rockylinux:9
+
+# 1. Install system dependencies and build tools (matching install_python.sh)
+RUN dnf update -y && \
+    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 \
+    findutils \
+    libpq-devel \
+    && dnf clean all
+
+# 2. Build Python 3.13 from source
+ENV PYTHON_VERSION=3.13.0
+ENV PYTHON_SRC_DIR=/tmp/Python-${PYTHON_VERSION}
+ENV INSTALL_PREFIX=/usr/local
+
+WORKDIR /tmp
+RUN wget "https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz" && \
+    tar -xf "Python-${PYTHON_VERSION}.tgz" && \
+    cd "$PYTHON_SRC_DIR" && \
+    ./configure --enable-optimizations --enable-shared --prefix="$INSTALL_PREFIX" && \
+    make -j "$(nproc)" && \
+    make altinstall && \
+    # Config shared lib path
+    echo "/usr/local/lib" > /etc/ld.so.conf.d/python3.13.conf && \
+    ldconfig && \
+    # Create symlinks
+    ln -sf "$INSTALL_PREFIX/bin/pip3.13" /usr/local/bin/pip3 && \
+    ln -s "$INSTALL_PREFIX/bin/python3.13" /usr/local/bin/python3 && \
+    ln -s "$INSTALL_PREFIX/bin/python3.13" /usr/local/bin/python && \
+    # Cleanup
+    cd /tmp && \
+    rm -rf "$PYTHON_SRC_DIR" "Python-${PYTHON_VERSION}.tgz"
+
+# 3. Setup Application
+WORKDIR /app
+
+# Copy requirements
+COPY Requirements.txt /app/requirements.txt
+
+# Install dependencies
+RUN pip3 install --no-cache-dir -r requirements.txt
+
+# Environment variables
+ENV PYTHONUNBUFFERED=1
+ENV DJANGO_SETTINGS_MODULE=djproject.settings
+
+# Expose port
+EXPOSE 8888
+
+# Command
+CMD ["python3", "manage.py", "runserver", "0.0.0.0:8888"]
Index: /branches/amp_4_0/platform/tools/container/services/backend/Requirements.txt
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/backend/Requirements.txt	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/backend/Requirements.txt	(working copy)
@@ -0,0 +1,96 @@
+anyio==4.9.0
+APScheduler==3.11.0
+asgiref==3.8.1
+backports.ssl-match-hostname==3.5.0.1
+configobj==4.7.2
+certifi==2025.6.15
+chardet==5.2.0
+charset-normalizer==3.4.2
+cssselect2==0.8.0
+decorator==3.4.0
+dicttoxml==1.7.16
+Django==5.2.3
+django-revproxy==0.10.0
+dnspython==2.7.0
+dynuipv4update==0.12
+elasticsearch==9.0.2
+elastic-transport==8.17.1
+flup==1.0.2
+funcsigs==1.0.2
+futures==3.1.1
+fonttools==4.58.2
+getpublicipv4==0.12
+h11==0.16.0
+httpcore==1.0.9
+httpx==0.28.1
+idna==3.10
+influxdb==5.3.1
+influxdb-client==1.49.0
+iniparse==0.4
+ipaddress==1.0.16
+javapackages==1.0.0
+Jinja2==2.7.2
+joblib==1.5.0
+kthread==0.2.3
+kthread-sleep==0.11
+lxml==3.2.1
+M2Crypto==0.25.1
+MarkupSafe==3.0.2
+msgpack==1.0.4
+numpy==2.2.6
+ordered-set==4.1.0
+perf==0.1
+Pillow==4.0.0
+pfehler==0.10
+pillow==11.2.1
+prettytable==3.16.0
+psutil==5.0.0
+psycopg2==2.7.5
+psycopg2-binary==2.9.10
+pyasynchat==1.0.4
+pyasyncore==1.0.4
+pycurl==7.19.0
+pyecharts==2.0.8
+pyftpdlib==1.5.2
+pygobject==3.22.0
+pygpgme==0.3
+pymongo==4.13.2
+pyliblzma==0.5.3
+python-linux-procfs==0.4.9
+python-dateutil==2.9.0.post0
+pytz==2017.2
+pyudev==0.15
+pyxattr==0.5.1
+reactivex==4.0.4
+reportlab==4.4.1
+requests==2.32.4
+rpm==0.4.0
+scikit-learn==1.6.1
+scipy==1.15.3
+schedule==0.6.0
+schedutils==0.4
+setuptools==80.9.0
+simplejson==3.20.1
+six==1.17.0
+slip==0.4.0
+slip.dbus==0.4.0
+sniffio==1.3.1
+sqlparse==0.5.3
+stderrstdoutcapture==0.10
+tinycss2==1.4.0
+tinyhtml5==2.0.0
+thread==2.0.5
+threadpoolctl==3.6.0
+toml==0.10.2
+touchtouch==0.11
+tftpy==0.8.5
+typing_extensions==4.14.0
+tzlocal==5.3.1
+urlgrabber==3.10
+urllib3==2.4.0
+Wand==0.6.13
+webencodings==0.5.1
+wcwidth==0.2.13
+Whoosh==2.7.4
+yum-metadata-parser==1.1.4
+opensearch-py==3.0.0
\ No newline at end of file
Index: /branches/amp_4_0/platform/tools/container/services/grafana/provisioning/dashboards/dashboard.yaml
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/grafana/provisioning/dashboards/dashboard.yaml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/grafana/provisioning/dashboards/dashboard.yaml	(working copy)
@@ -0,0 +1,12 @@
+apiVersion: 1
+
+providers:
+  - name: 'Default'
+    orgId: 1
+    folder: ''
+    type: file
+    disableDeletion: false
+    updateIntervalSeconds: 10
+    allowUiUpdates: true
+    options:
+      path: /etc/grafana/provisioning/dashboards/json
Index: /branches/amp_4_0/platform/tools/container/services/grafana/provisioning/dashboards/json/docker_stats.json
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/grafana/provisioning/dashboards/json/docker_stats.json	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/grafana/provisioning/dashboards/json/docker_stats.json	(working copy)
@@ -0,0 +1,404 @@
+{
+  "annotations": {
+    "list": [
+      {
+        "builtIn": 1,
+        "datasource": "-- Grafana --",
+        "enable": true,
+        "hide": true,
+        "iconColor": "rgba(0, 211, 255, 1)",
+        "name": "Annotations & Alerts",
+        "target": {
+          "limit": 100,
+          "matchAny": false,
+          "tags": [],
+          "type": "dashboard"
+        },
+        "type": "dashboard"
+      }
+    ]
+  },
+  "editable": true,
+  "fiscalYearStartMonth": 0,
+  "graphTooltip": 0,
+  "id": null,
+  "links": [],
+  "liveNow": false,
+  "panels": [
+    {
+      "datasource": "PostgreSQL",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisAt": "auto",
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "auto",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "percent"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 12,
+        "x": 0,
+        "y": 0
+      },
+      "id": 2,
+      "options": {
+        "legend": {
+          "calcs": [],
+          "displayMode": "list",
+          "placement": "bottom",
+          "showLegend": true
+        },
+        "tooltip": {
+          "mode": "single",
+          "sort": "none"
+        }
+      },
+      "targets": [
+        {
+          "datasource": "PostgreSQL",
+          "format": "time_series",
+          "group": [],
+          "metricColumn": "container_name",
+          "rawQuery": true,
+          "rawSql": "SELECT\n  \"time\" AS \"time\",\n  container_name AS metric,\n  usage_percent\nFROM docker_container_cpu\nWHERE\n  $__timeFilter(\"time\")\nORDER BY 1",
+          "refId": "A",
+          "select": [
+            [
+              {
+                "params": [
+                  "usage_percent"
+                ],
+                "type": "column"
+              }
+            ]
+          ],
+          "table": "docker_container_cpu",
+          "timeColumn": "\"time\"",
+          "timeColumnType": "timestamp",
+          "where": [
+            {
+              "name": "$__timeFilter",
+              "params": [],
+              "type": "macro"
+            }
+          ]
+        }
+      ],
+      "title": "Container CPU Usage",
+      "type": "timeseries"
+    },
+    {
+      "datasource": "PostgreSQL",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisAt": "auto",
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "auto",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "bytes"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 12,
+        "x": 12,
+        "y": 0
+      },
+      "id": 4,
+      "options": {
+        "legend": {
+          "calcs": [],
+          "displayMode": "list",
+          "placement": "bottom",
+          "showLegend": true
+        },
+        "tooltip": {
+          "mode": "single",
+          "sort": "none"
+        }
+      },
+      "targets": [
+        {
+          "datasource": "PostgreSQL",
+          "format": "time_series",
+          "group": [],
+          "metricColumn": "container_name",
+          "rawQuery": true,
+          "rawSql": "SELECT\n  \"time\" AS \"time\",\n  container_name AS metric,\n  usage\nFROM docker_container_mem\nWHERE\n  $__timeFilter(\"time\")\nORDER BY 1",
+          "refId": "A",
+          "select": [
+            [
+              {
+                "params": [
+                  "usage"
+                ],
+                "type": "column"
+              }
+            ]
+          ],
+          "table": "docker_container_mem",
+          "timeColumn": "\"time\"",
+          "timeColumnType": "timestamp",
+          "where": [
+            {
+              "name": "$__timeFilter",
+              "params": [],
+              "type": "macro"
+            }
+          ]
+        }
+      ],
+      "title": "Container Memory Usage",
+      "type": "timeseries"
+    },
+    {
+      "datasource": "PostgreSQL",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisAt": "auto",
+            "axisCenteredZero": false,
+            "axisColorMode": "text",
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "auto",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "bytes"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 24,
+        "x": 0,
+        "y": 8
+      },
+      "id": 6,
+      "options": {
+        "legend": {
+          "calcs": [],
+          "displayMode": "list",
+          "placement": "bottom",
+          "showLegend": true
+        },
+        "tooltip": {
+          "mode": "single",
+          "sort": "none"
+        }
+      },
+      "targets": [
+        {
+          "datasource": "PostgreSQL",
+          "format": "time_series",
+          "group": [],
+          "metricColumn": "container_name",
+          "rawQuery": true,
+          "rawSql": "SELECT\n  \"time\" AS \"time\",\n  container_name || ' RX' AS metric,\n  rx_bytes\nFROM docker_container_net\nWHERE\n  $__timeFilter(\"time\")\nORDER BY 1",
+          "refId": "A",
+          "select": [
+            [
+              {
+                "params": [
+                  "rx_bytes"
+                ],
+                "type": "column"
+              }
+            ]
+          ],
+          "table": "docker_container_net",
+          "timeColumn": "\"time\"",
+          "timeColumnType": "timestamp",
+          "where": [
+            {
+              "name": "$__timeFilter",
+              "params": [],
+              "type": "macro"
+            }
+          ]
+        },
+        {
+          "datasource": "PostgreSQL",
+          "format": "time_series",
+          "group": [],
+          "metricColumn": "none",
+          "rawQuery": true,
+          "rawSql": "SELECT\n  \"time\" AS \"time\",\n  container_name || ' TX' AS metric,\n  tx_bytes\nFROM docker_container_net\nWHERE\n  $__timeFilter(\"time\")\nORDER BY 1",
+          "refId": "B",
+          "select": [
+            [
+              {
+                "params": [
+                  "tx_bytes"
+                ],
+                "type": "column"
+              }
+            ]
+          ],
+          "table": "docker_container_net",
+          "timeColumn": "\"time\"",
+          "timeColumnType": "timestamp",
+          "where": [
+            {
+              "name": "$__timeFilter",
+              "params": [],
+              "type": "macro"
+            }
+          ]
+        }
+      ],
+      "title": "Network I/O",
+      "type": "timeseries"
+    }
+  ],
+  "refresh": "5s",
+  "schemaVersion": 38,
+  "style": "dark",
+  "tags": [
+    "docker"
+  ],
+  "templating": {
+    "list": []
+  },
+  "time": {
+    "from": "now-6h",
+    "to": "now"
+  },
+  "timepicker": {},
+  "timezone": "",
+  "title": "Docker Stats",
+  "uid": "docker-monitoring-details",
+  "version": 1,
+  "weekStart": ""
+}
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	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/grafana/provisioning/datasources/datasources.yaml	(working copy)
@@ -0,0 +1,28 @@
+apiVersion: 1
+
+datasources:
+  - name: PostgreSQL
+    type: postgres
+    url: timescaledb:5432
+    user: postgres # Using superuser or custom read-only user
+    secureJsonData:
+      password: postgres # Should match .env
+    jsonData:
+      database: postgres
+      sslmode: "disable"
+      timescaledb: true
+      version: 16
+    isDefault: true
+
+  - name: OpenSearch
+    type: grafana-opensearch-datasource
+    access: proxy
+    url: https://opensearch-node1:9200
+    basicAuth: true
+    basicAuthUser: admin
+    basicAuthPassword: Arr@y2050 # Should match .env
+    jsonData:
+      timeField: "@timestamp"
+      tlsSkipVerify: true
+      flavor: opensearch
+      version: 2.11.0
Index: /branches/amp_4_0/platform/tools/container/services/logstash/config/logstash.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/logstash/config/logstash.yml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/logstash/config/logstash.yml	(working copy)
@@ -0,0 +1,4 @@
+http.host: "0.0.0.0"
+
+pipeline.ecs_compatibility: disabled
+log.level: info
Index: /branches/amp_4_0/platform/tools/container/services/logstash/drivers/postgresql.jar
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: platform/tools/container/services/logstash/drivers/postgresql.jar
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: /branches/amp_4_0/platform/tools/container/services/logstash/pipeline/syslog.conf
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/logstash/pipeline/syslog.conf	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/logstash/pipeline/syslog.conf	(working copy)
@@ -0,0 +1,320 @@
+input {
+  udp {
+    port => 5514
+    type => "syslog"
+    codec => plain {
+      charset => "UTF-8"
+    }
+  }
+  tcp {
+    port => 5514
+    type => "syslog"
+    codec => plain {
+      charset => "UTF-8"
+    }
+  }
+}
+
+filter {
+  # Stage 1: Initial Cleaning
+  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
+      '
+    }
+  }
+
+  if [type] == "syslog" {
+    # Stage 2: Parsing Attempts (Cascading Grok)
+
+    # 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_attempt"]
+    }
+
+    # Parse syslog_message as AN_WELF_LOG
+    if !("_grokparsefailure_rfc5424" in [tags]) and [syslog_message] {
+      mutate {
+        strip => ["syslog_message"]
+      }
+      grok {
+        match => {
+          "syslog_message" => "^AN_WELF_LOG:id=%{WORD:log_id} time=\"%{TIMESTAMP_ISO8601:log_time}\" fw=%{IP:virtual_ip} pri=%{POSINT:priority} proto=%{WORD:protocol} src=%{IP:src_ip} dstname=%{IP:destination_name} arg=%{URIPATH:arg} op=%{WORD:http_method} agent=\"%{DATA:user_agent}\" result=%{INT:http_status_code} sent=%{INT:bytes_sent} duration=%{NUMBER:duration} msg=\"%{GREEDYDATA:message_detail_raw}\""
+        }
+        tag_on_failure => ["_grokparsefailure_an_welf_log_rfc5424"]
+        add_tag => ["an_welf_log_rfc5424_subparsed"]
+      }
+      if !("_grokparsefailure_an_welf_log_rfc5424" in [tags]) and [message_detail_raw] {
+        mutate {
+          gsub => ["message_detail_raw", "\s+", " "]
+        }
+        grok {
+          match => {
+            "message_detail_raw" => "^cache:%{WORD:cache_status} peer:%{WORD:peer_type}/%{IP:peer_ip}$"
+          }
+          tag_on_failure => ["_grokparsefailure_msg"]
+          add_tag => ["msg_subparsed"]
+        }
+      }
+    }
+
+    # Attempt 2: AN_WELF_LOG (WITH SYSLOG HEADER)
+    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:src_ip} dstname=%{IP:destination_name} arg=%{URIPATH:arg} op=%{WORD:http_method} agent=\"%{DATA:user_agent}\" result=%{INT:http_status_code} sent=%{INT:bytes_sent} duration=%{NUMBER:duration} msg=\"%{GREEDYDATA:destination_data_raw}\""
+        }
+        tag_on_failure => ["_grokparsefailure_an_welf_log"]
+        add_tag => ["an_welf_log_attempt"]
+      }
+      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"]
+        }
+        mutate {
+          gsub => ["destination_data_raw", "\s+", " "]
+        }
+      }
+    }
+
+    # Attempt 3: AN_WELF_LOG (without SYSLOG HEADER)
+    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:src_ip} dstname=%{IP:destination_name} arg=%{URIPATH:arg} op=%{WORD:http_method} agent=\"%{DATA:user_agent}\" result=%{INT:http_status_code} sent=%{INT:bytes_sent} duration=%{NUMBER:duration} msg=\"%{GREEDYDATA:message_detail_raw}\""
+        }
+        tag_on_failure => ["_grokparsefailure_an_welf_log_no_header"]
+        add_tag => ["an_welf_log_no_header_attempt"]
+      }
+      if !("_grokparsefailure_an_welf_log_no_header" in [tags]) {
+        mutate {
+          add_field => { "log_time" => "%{year}-%{month}-%{day} %{hour}:%{minute}:%{second}" }
+          remove_field => ["year", "month", "day", "hour", "minute", "second"]
+        }
+        mutate {
+          gsub => ["message_detail_raw", "\s+", " "]
+        }
+        mutate {
+          add_field => { "syslog_priority" => "%{priority}" }
+          add_field => { "syslog_timestamp" => "%{log_time}" }
+        }
+      }
+    }
+
+    # Attempt 4: Custom/Non-Standard
+    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"]
+        add_tag => ["custom_nonstandard_attempt"]
+      }
+      if !("_grokparsefailure_custom_nonstandard" in [tags]) {
+        date {
+          match => ["syslog_month syslog_day syslog_year syslog_time", "MMM ddYYYY HH:mm:ss"]
+          target => "syslog_timestamp"
+          add_tag => ["_dateparseok"]
+          tag_on_failure => ["_dateparsefailure"]
+        }
+        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]}" }
+          remove_field => ["syslog_month", "syslog_day", "syslog_year", "syslog_time", "syslog_content_kv"]
+        }
+
+        # Map fields from syslog_kv_data to common field names
+        if [syslog_kv_data][fw] {
+          mutate { add_field => { "virtual_ip" => "%{[syslog_kv_data][fw]}" } }
+        }
+        if [syslog_kv_data][src] {
+          mutate { add_field => { "client_ip" => "%{[syslog_kv_data][src]}" } }
+        }
+        if [syslog_kv_data][dst] {
+          mutate { add_field => { "destination_ip" => "%{[syslog_kv_data][dst]}" } }
+        }
+        if [syslog_kv_data][dport] {
+          mutate {
+            add_field => { "destination_port" => "%{[syslog_kv_data][dport]}" }
+            convert => { "destination_port" => "integer" }
+          }
+        }
+        if [syslog_kv_data][time] {
+          date {
+            match => ["syslog_kv_data.time", "YYYY-M-d HH:mm:ss"]
+            target => "log_message_timestamp"
+            tag_on_failure => ["_kvtimeparsefailure"]
+          }
+        }
+      }
+    }
+
+    # Attempt 5: Traditional BSD Syslog
+    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"]
+      }
+    }
+
+    # Stage 3: Common Post-Parsing Processing
+    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] and "_grokparsefailure_bsd" in [tags]) {
+      # USERAGENT FILTER
+      if [user_agent] {
+        useragent {
+          source => "user_agent"
+          target => "useragent"
+          remove_field => ["user_agent"]
+        }
+      }
+
+      if [syslog_appname] == "-" { mutate { remove_field => ["syslog_appname"] } }
+      if [syslog_procid] == "-"  { mutate { remove_field => ["syslog_procid"]  } }
+      if [syslog_structured_data] == "-" {
+        mutate { remove_field => ["syslog_structured_data"] }
+      }
+
+      mutate {
+        rename => {
+          "syslog_pri" => "syslog_priority"
+          "syslog_hostname" => "device_hostname"
+          "syslog_appname" => "application_name"
+          "syslog_procid" => "process_id"
+          "syslog_msgid" => "message_id"
+        }
+        rename => { "syslog_protocol_version" => "syslog_version" }
+      }
+
+      ruby {
+        code => "
+          if event.get('syslog_priority')
+            level = event.get('syslog_priority').to_i % 8
+            level_map = {
+              0 => 'Emergency', 1 => 'Alert', 2 => 'Critical', 3 => 'Error',
+              4 => 'Warning', 5 => 'Notice', 6 => 'Informational', 7 => 'Debug'
+            }
+            event.set('severity_numeric', level)
+            event.set('severity', level_map[level] || 'Unknown')
+          end
+        "
+      }
+
+      ruby {
+        code => "
+          if event.get('syslog_priority')
+            facility = event.get('syslog_priority').to_i / 8
+            facility_map = {
+              0 => 'Kernel', 1 => 'User', 2 => 'Mail', 3 => 'System Daemons',
+              4 => 'Security/Authorization', 5 => 'Syslog', 6 => 'LPR Subsystem',
+              7 => 'NNTP Subsystem', 8 => 'UUCP Subsystem', 9 => 'Clock Daemon',
+              10 => 'Security/Authorization', 11 => 'FTP Daemon', 12 => 'NTP Subsystem',
+              13 => 'Log Audit', 14 => 'Log Alert', 15 => 'Clock Daemon',
+              16 => 'Local0', 17 => 'Local1', 18 => 'Local2', 19 => 'Local3',
+              20 => 'Local4', 21 => 'Local5', 22 => 'Local6', 23 => 'Local7'
+            }
+            event.set('log_facility_numeric', facility)
+            event.set('log_facility', facility_map[facility] || 'Unknown')
+          end
+        "
+      }
+
+      # IP RENAMING LOGIC
+      mutate {
+        copy => { "[host][ip]" => "device_ip" }
+      }
+
+      if [src_ip] {
+        mutate {
+          rename => { "src_ip" => "client_ip" }
+        }
+      }
+
+      if [client_ip] {
+        geoip {
+          source => "client_ip"
+          target => "client_geoip"
+          tag_on_failure => ["_geoip_client_failed"]
+        }
+      }
+
+      if [destination_ip] {
+        geoip {
+          source => "destination_ip"
+          target => "destination_geoip"
+          tag_on_failure => ["_geoip_destination_failed"]
+        }
+      }
+
+      if [device_ip] {
+        jdbc_streaming {
+          jdbc_driver_library => "/usr/share/logstash/drivers/postgresql.jar"
+          jdbc_driver_class => "org.postgresql.Driver"
+          jdbc_connection_string => "jdbc:postgresql://timescaledb:5432/amp_ts"
+          jdbc_user => "amp_ts_user"
+          jdbc_password => "Array@123$"
+          statement => "SELECT name, type, device_group FROM device WHERE ip_address = :device_ip"
+          parameters => { "device_ip" => "device_ip" }
+          target => "device_info"
+          tag_on_failure => ["_device_lookup_failure"]
+        }
+
+        if [device_info] {
+          ruby {
+            code => "
+              if event.get('device_info') && event.get('device_info')[0]
+                device = event.get('device_info')[0]
+                event.set('device_name', device['name'])
+                event.set('device_type', device['type'])
+                event.set('device_group', device['device_group'])
+              else
+                event.tag('_device_info_not_found')
+              end
+              event.remove('device_info')
+            "
+          }
+        }
+      }
+
+      if !("_grokparsefailure_rfc5424" in [tags]) {
+        mutate {
+          remove_tag => ["_grokparsefailure_an_welf_log", "_grokparsefailure_an_welf_log_no_header", "_grokparsefailure_custom_nonstandard", "_grokparsefailure_bsd"]
+          add_tag => ["syslog_parsed", "rfc5424"]
+        }
+      }
+    }
+  }
+}
+
+output {
+  opensearch {
+    hosts => ["https://opensearch:9200"]
+    index => "acm-%{+YYYY.MM.dd}"
+    user => "admin"
+    password => "${OPENSEARCH_INITIAL_ADMIN_PASSWORD}"
+    ssl => true
+    ssl_certificate_verification => false
+    manage_template => false
+  }
+  stdout { codec => rubydebug }
+}
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	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/nginx/conf.d/app.conf	(working copy)
@@ -0,0 +1,137 @@
+# /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 localhost;
+
+    return 301 https://$host$request_uri;
+}
+
+# --- HTTPS Server Block ---
+server {
+    listen 443 ssl;
+    listen [::]:443 ssl;
+    server_name localhost;
+
+    ssl_certificate /etc/nginx/certs/node.pem;
+    ssl_certificate_key /etc/nginx/certs/node-key.pem;
+
+    ssl_session_cache shared:SSL:10m;
+    ssl_session_timeout 10m;
+    ssl_protocols TLSv1.2 TLSv1.3;
+    ssl_prefer_server_ciphers on;
+    # Modern cipher suite
+    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;
+
+    # Resolver for lazy resolution (Docker DNS)
+    resolver 127.0.0.11 valid=30s;
+
+    # 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/;
+        }
+        set $opensearch_dashboards "https://opensearch-dashboards:5601";
+        proxy_pass $opensearch_dashboards;
+        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/ {
+        set $opensearch_dashboards "https://opensearch-dashboards:5601";
+        proxy_pass $opensearch_dashboards;
+        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)/ {
+        set $opensearch_dashboards "https://opensearch-dashboards:5601";
+        proxy_pass $opensearch_dashboards;
+        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/ {
+        set $grafana "http://grafana:3000";
+        proxy_pass $grafana;  # 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/ ---
+    # Assuming backend runs on host or another container. 
+    # For Docker, if backend is on host, use host.docker.internal or extra_hosts
+    location ^~ /api/v2/ {
+        # Using host.docker.internal to reach host service from inside container (Docker Desktop/standard setup)
+        set $backend "http://backend:8888";
+        proxy_pass $backend;
+
+        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) - Served as static files or proxied?
+    # Original script mounted /usr/share/amp-gui
+    # We will mount a volume to /usr/share/nginx/html/amp-gui if needed, or serve from root
+    location / {
+        root /usr/share/nginx/html;
+        index index.html;
+        try_files $uri $uri/ /index.html;
+    }
+}
Index: /branches/amp_4_0/platform/tools/container/services/opensearch-dashboards/assets/array_logo.svg
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/opensearch-dashboards/assets/array_logo.svg	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/opensearch-dashboards/assets/array_logo.svg	(working copy)
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="404px" height="120px" viewBox="0 0 401 120" version="1.1">
+<g id="surface1">
+<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,47.058824%,2.745098%);fill-opacity:1;" d="M 10.457031 69.511719 C 6.980469 69.484375 3.496094 69.597656 0 69.847656 C 3.289062 75.28125 6.25 80.90625 8.867188 86.691406 C 12.140625 86.003906 15.410156 85.453125 18.675781 85.039062 C 16.300781 79.679688 13.554688 74.492188 10.457031 69.511719 Z M 10.457031 69.511719 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,47.058824%,2.745098%);fill-opacity:1;" d="M 15.273438 69.648438 C 18.292969 74.394531 20.945312 79.367188 23.203125 84.523438 C 26.371094 84.222656 29.539062 83.964844 32.703125 83.753906 C 30.507812 79.144531 28.050781 74.660156 25.347656 70.328125 C 22.003906 69.988281 18.644531 69.761719 15.273438 69.648438 Z M 15.273438 69.648438 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,47.058824%,2.745098%);fill-opacity:1;" d="M 12.859375 91.855469 C 15.296875 97.085938 17.433594 102.445312 19.265625 107.917969 C 22.183594 106.949219 25.109375 106 28.039062 105.070312 C 26.222656 99.90625 24.03125 94.882812 21.480469 90.042969 C 18.363281 90.320312 15.945312 91.53125 12.859375 91.855469 Z M 12.859375 91.855469 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,47.058824%,2.745098%);fill-opacity:1;" d="M 166.363281 53.449219 C 163.195312 53.757812 160.027344 54.015625 156.867188 54.222656 C 159.0625 58.835938 161.515625 63.316406 164.21875 67.652344 C 167.566406 67.964844 170.90625 68.199219 174.289062 68.328125 C 171.273438 63.578125 168.621094 58.605469 166.363281 53.449219 Z M 166.363281 53.449219 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,47.058824%,2.745098%);fill-opacity:1;" d="M 144.027344 54.703125 L 144.027344 39.703125 C 144.035156 35.875 143.082031 32.109375 141.253906 28.746094 L 135.707031 30.902344 C 135.105469 27.660156 134.722656 24.375 134.441406 21.042969 C 133.085938 20.066406 131.628906 19.238281 130.09375 18.578125 C 130.324219 23.226562 130.878906 27.851562 131.753906 32.421875 C 129.03125 33.453125 126.3125 34.523438 123.597656 35.636719 C 122.601562 31.082031 121.96875 26.453125 121.714844 21.796875 C 123.773438 20.433594 125.875 19.109375 127.957031 17.78125 C 125.738281 17.089844 123.421875 16.742188 121.097656 16.75 L 63.949219 16.75 C 51.296875 16.75 41.035156 27.019531 41.027344 39.695312 L 41.027344 74.164062 C 42.914062 77.078125 44.625 80.101562 46.140625 83.226562 C 44.4375 83.265625 42.734375 83.328125 41.027344 83.382812 L 41.027344 88.265625 C 43.386719 88.027344 45.75 87.789062 48.105469 87.589844 C 50.023438 92.164062 51.570312 96.886719 52.734375 101.707031 C 49.847656 102.585938 46.960938 103.472656 44.078125 104.371094 C 43.171875 100.695312 42.160156 97.199219 41.03125 93.769531 L 41.03125 96.957031 C 41.03125 101.769531 42.546875 106.457031 45.363281 110.355469 C 48.199219 109.242188 51.035156 108.144531 53.871094 107.058594 C 54.53125 110.742188 54.988281 114.460938 55.246094 118.199219 C 56.609375 118.765625 58.027344 119.195312 59.476562 119.480469 C 59.253906 114.800781 58.695312 110.140625 57.8125 105.539062 C 60.554688 104.507812 63.273438 103.433594 65.96875 102.320312 C 66.972656 106.875 67.601562 111.503906 67.851562 116.15625 C 65.953125 117.410156 64.042969 118.621094 62.132812 119.835938 C 62.738281 119.882812 63.339844 119.929688 63.953125 119.929688 L 121.105469 119.929688 C 133.769531 119.929688 144.035156 109.644531 144.027344 96.957031 L 144.027344 65.113281 L 136.5625 64.082031 C 134.40625 61.203125 132.449219 58.171875 130.714844 55.015625 C 133.574219 54.933594 136.429688 54.878906 139.285156 54.828125 C 140.699219 57.652344 142.285156 60.390625 144.027344 63.023438 L 144.027344 55.898438 C 145.78125 59.390625 147.777344 62.753906 150.003906 65.960938 C 153.203125 66.40625 156.402344 66.816406 159.613281 67.171875 C 157.003906 63.070312 154.632812 58.816406 152.515625 54.441406 C 149.683594 54.550781 146.855469 54.636719 144.027344 54.703125 Z M 125.125 51.863281 C 122.515625 52.140625 119.902344 52.476562 117.296875 52.894531 C 115.699219 50.046875 114.394531 47.050781 113.394531 43.941406 C 116.332031 42.800781 117.867188 42.261719 121 41.210938 C 122.050781 44.878906 123.429688 48.441406 125.113281 51.863281 Z M 117.964844 24.300781 C 118.375 28.625 119.023438 32.925781 119.902344 37.183594 C 116.816406 38.496094 115.273438 39.160156 112.34375 40.550781 C 111.335938 36.996094 110.648438 33.359375 110.285156 29.679688 C 112.832031 27.8125 115.402344 26.015625 118 24.300781 Z M 113.78125 53.515625 C 111.382812 53.953125 109.007812 54.519531 106.667969 55.21875 C 105.273438 53.105469 104.089844 50.863281 103.132812 48.519531 C 105.367188 47.332031 107.65625 46.257812 109.996094 45.304688 C 110.984375 48.152344 112.25 50.898438 113.78125 53.496094 Z M 94.492188 60.472656 C 92.558594 61.644531 90.761719 63.027344 89.136719 64.597656 C 88.195312 63.582031 87.308594 62.515625 86.484375 61.402344 C 88 59.558594 89.683594 57.863281 91.507812 56.324219 C 92.390625 57.78125 93.386719 59.160156 94.492188 60.449219 Z M 94.097656 54.1875 C 96.003906 52.714844 98.011719 51.378906 100.109375 50.195312 C 101.050781 52.296875 102.191406 54.304688 103.519531 56.1875 C 101.34375 56.902344 99.234375 57.808594 97.21875 58.898438 C 96.039062 57.421875 94.992188 55.847656 94.085938 54.191406 Z M 87.503906 73.90625 C 88.824219 75.035156 90.046875 76.273438 91.160156 77.609375 C 89.089844 78.585938 86.9375 79.371094 84.722656 79.953125 C 83.480469 78.269531 82.082031 76.707031 80.546875 75.285156 C 82.90625 75.066406 85.234375 74.617188 87.503906 73.9375 Z M 92.335938 79.0625 C 93.515625 80.535156 94.566406 82.109375 95.46875 83.765625 C 93.566406 85.238281 91.558594 86.574219 89.460938 87.757812 C 88.515625 85.660156 87.375 83.652344 86.050781 81.765625 C 88.222656 81.050781 90.328125 80.144531 92.34375 79.0625 Z M 95.074219 77.515625 C 97.007812 76.347656 98.804688 74.964844 100.429688 73.394531 C 101.371094 74.410156 102.257812 75.476562 103.082031 76.59375 C 101.566406 78.433594 99.882812 80.128906 98.058594 81.664062 C 97.175781 80.195312 96.179688 78.800781 95.078125 77.496094 Z M 102.066406 64.070312 C 100.742188 62.945312 99.519531 61.707031 98.40625 60.371094 C 100.476562 59.40625 102.632812 58.628906 104.84375 58.050781 C 106.085938 59.738281 107.484375 61.304688 109.019531 62.726562 C 106.664062 62.945312 104.332031 63.394531 102.066406 64.070312 Z M 106.839844 32.257812 C 107.214844 35.640625 107.921875 38.980469 108.957031 42.226562 C 106.113281 43.671875 104.746094 44.421875 102.121094 46.015625 C 101.113281 43.386719 100.367188 40.664062 99.886719 37.886719 C 102.558594 35.578125 103.953125 34.464844 106.84375 32.257812 Z M 96.832031 40.582031 C 97.355469 43.09375 98.117188 45.542969 99.113281 47.90625 C 97.023438 49.269531 95.027344 50.773438 93.140625 52.40625 C 92.191406 50.566406 91.402344 48.648438 90.785156 46.675781 C 92.699219 44.542969 94.71875 42.507812 96.839844 40.582031 Z M 88.1875 49.65625 C 88.839844 51.40625 89.636719 53.097656 90.566406 54.714844 C 88.757812 56.386719 87.097656 58.210938 85.597656 60.171875 C 84.722656 58.957031 83.917969 57.695312 83.179688 56.394531 C 84.699219 54.039062 86.371094 51.789062 88.1875 49.65625 Z M 81.117188 59.738281 C 82.070312 61.125 82.554688 61.765625 83.527344 63.007812 C 82.046875 65.121094 80.765625 67.363281 79.695312 69.707031 C 78.796875 68.78125 78.332031 68.308594 77.332031 67.328125 C 78.402344 64.703125 79.667969 62.164062 81.125 59.730469 Z M 77.089844 75.542969 C 78.726562 77.128906 80.207031 78.863281 81.515625 80.730469 C 79.121094 81.273438 76.699219 81.675781 74.261719 81.941406 C 72.835938 79.652344 71.191406 77.507812 69.347656 75.542969 C 71.929688 75.679688 74.515625 75.679688 77.097656 75.542969 Z M 65.558594 75.316406 C 67.496094 77.476562 69.21875 79.824219 70.703125 82.324219 C 68.046875 82.566406 65.390625 82.726562 62.746094 82.839844 C 61.113281 79.871094 59.21875 77.058594 57.085938 74.429688 C 59.890625 74.785156 62.707031 75.089844 65.527344 75.304688 Z M 56.695312 100.519531 C 55.558594 95.980469 54.058594 91.542969 52.203125 87.25 C 55.019531 87.011719 57.828125 86.769531 60.640625 86.492188 C 62.265625 90.242188 63.6875 94.082031 64.890625 97.988281 C 62.148438 98.859375 59.421875 99.699219 56.695312 100.527344 Z M 64.457031 86.089844 C 67.058594 85.8125 69.679688 85.476562 72.28125 85.058594 C 73.875 87.90625 75.183594 90.90625 76.179688 94.015625 C 73.242188 95.152344 71.714844 95.691406 68.582031 96.742188 C 67.527344 93.078125 66.152344 89.519531 64.46875 86.101562 Z M 71.609375 113.660156 C 71.199219 109.332031 70.554688 105.03125 69.667969 100.777344 C 72.753906 99.460938 74.296875 98.796875 77.234375 97.40625 C 78.238281 100.960938 78.925781 104.601562 79.292969 108.277344 C 76.746094 110.132812 74.171875 111.925781 71.578125 113.648438 Z M 75.792969 84.445312 C 78.195312 84.019531 80.570312 83.457031 82.910156 82.765625 C 84.300781 84.863281 85.484375 87.09375 86.441406 89.425781 C 84.210938 90.609375 81.917969 91.679688 79.574219 92.632812 C 78.585938 89.785156 77.316406 87.042969 75.785156 84.445312 Z M 82.734375 105.71875 C 82.359375 102.335938 81.652344 98.996094 80.621094 95.75 C 83.460938 94.308594 84.832031 93.558594 87.449219 91.960938 C 88.457031 94.589844 89.207031 97.3125 89.683594 100.089844 C 87.011719 102.394531 85.621094 103.511719 82.730469 105.71875 Z M 92.738281 97.363281 C 92.214844 94.855469 91.453125 92.402344 90.464844 90.039062 C 92.550781 88.675781 94.546875 87.171875 96.433594 85.542969 C 97.386719 87.382812 98.175781 89.308594 98.789062 91.289062 C 96.875 93.421875 94.851562 95.453125 92.734375 97.378906 Z M 101.386719 88.289062 C 100.730469 86.542969 99.933594 84.851562 99.003906 83.234375 C 100.8125 81.558594 102.476562 79.734375 103.972656 77.773438 C 104.847656 78.988281 105.65625 80.25 106.390625 81.554688 C 104.871094 83.90625 103.195312 86.15625 101.378906 88.289062 Z M 108.453125 78.210938 C 107.5 76.824219 107.023438 76.179688 106.046875 74.9375 C 107.527344 72.828125 108.808594 70.585938 109.878906 68.238281 C 110.773438 69.167969 111.242188 69.640625 112.238281 70.621094 C 111.171875 73.242188 109.902344 75.78125 108.449219 78.210938 Z M 112.476562 62.433594 C 110.839844 60.847656 109.359375 59.109375 108.058594 57.238281 C 110.449219 56.699219 112.871094 56.292969 115.308594 56.027344 C 116.734375 58.316406 118.378906 60.464844 120.222656 62.433594 C 117.640625 62.289062 115.054688 62.289062 112.476562 62.433594 Z M 132.449219 63.546875 C 129.644531 63.191406 126.832031 62.886719 124.007812 62.667969 C 122.070312 60.503906 120.347656 58.15625 118.863281 55.65625 C 121.519531 55.414062 124.171875 55.257812 126.820312 55.140625 C 128.445312 58.097656 130.324219 60.90625 132.4375 63.535156 Z M 128.941406 51.488281 C 127.316406 47.746094 125.894531 43.914062 124.691406 40.015625 C 127.417969 39.136719 130.148438 38.296875 132.882812 37.441406 C 134.019531 41.976562 135.519531 46.414062 137.371094 50.710938 C 134.5625 50.945312 131.753906 51.195312 128.941406 51.464844 Z M 128.941406 51.488281 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,47.058824%,2.745098%);fill-opacity:1;" d="M 157.402344 22.175781 C 156.414062 14.851562 155.875 7.472656 155.792969 0.0820312 C 152.808594 2.039062 149.820312 3.972656 146.828125 5.886719 C 146.882812 12.59375 147.421875 19.289062 148.441406 25.917969 C 151.425781 24.703125 154.410156 23.453125 157.402344 22.175781 Z M 157.402344 22.175781 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.509805%,14.509805%,14.509805%);fill-opacity:1;" d="M 232.15625 84.488281 L 209.4375 84.488281 L 205.105469 97.277344 L 191.65625 97.277344 L 212.957031 38.8125 L 229.070312 38.8125 L 250.421875 97.277344 L 236.535156 97.277344 Z M 228.738281 74.363281 L 220.914062 50.992188 L 220.757812 50.992188 L 212.859375 74.363281 Z M 228.738281 74.363281 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.509805%,14.509805%,14.509805%);fill-opacity:1;" d="M 255.175781 56.394531 L 266.878906 56.394531 L 267.183594 62.871094 L 267.335938 62.871094 C 269.617188 58.449219 272.960938 55.65625 277.523438 55.65625 C 278.839844 55.671875 280.148438 55.902344 281.394531 56.339844 L 281.394531 66.847656 C 280.113281 66.652344 278.820312 66.550781 277.523438 66.542969 C 271.210938 66.542969 267.789062 70.347656 267.789062 74.839844 L 267.789062 97.296875 L 255.175781 97.296875 Z M 255.175781 56.394531 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.509805%,14.509805%,14.509805%);fill-opacity:1;" d="M 287.316406 56.394531 L 299.023438 56.394531 L 299.328125 62.871094 L 299.476562 62.871094 C 301.753906 58.449219 305.101562 55.65625 309.660156 55.65625 C 310.980469 55.671875 312.292969 55.902344 313.539062 56.339844 L 313.539062 66.847656 C 312.253906 66.65625 310.957031 66.558594 309.660156 66.554688 C 303.355469 66.554688 299.933594 70.359375 299.933594 74.851562 L 299.933594 97.308594 L 287.316406 97.308594 Z M 287.316406 56.394531 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.509805%,14.509805%,14.509805%);fill-opacity:1;" d="M 342.109375 97.277344 L 341.800781 91.644531 L 341.648438 91.644531 C 339.21875 95.765625 335.039062 98.117188 329.714844 98.117188 C 321.667969 98.117188 316.496094 92.859375 316.496094 85.632812 C 316.496094 80.832031 318.699219 76.800781 324.324219 74.898438 C 328.578125 73.449219 334.660156 73.066406 341.269531 72.835938 L 341.269531 70.398438 C 341.269531 66.972656 338.914062 64.6875 334.808594 64.6875 C 330.703125 64.6875 328.425781 66.824219 327.289062 69.789062 L 317.890625 65.074219 C 320.929688 58.679688 326.402344 55.558594 335.519531 55.558594 C 347.757812 55.558594 353.757812 61.117188 353.757812 70.101562 L 353.757812 97.277344 Z M 341.269531 82.054688 L 341.269531 79.921875 C 338.914062 80.070312 336.125 80.304688 333.671875 80.679688 C 331.21875 81.054688 328.882812 81.976562 328.882812 84.945312 C 328.882812 87.382812 330.703125 89.363281 334.203125 89.363281 C 338.53125 89.363281 341.269531 86.238281 341.269531 82.054688 Z M 341.269531 82.054688 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.509805%,14.509805%,14.509805%);fill-opacity:1;" d="M 401 56.394531 L 384.882812 99.714844 C 381.921875 107.707031 377.972656 112.046875 369.230469 112.046875 C 367.179688 112.042969 365.136719 111.761719 363.15625 111.210938 L 363.15625 102.449219 C 364.3125 102.800781 365.515625 102.972656 366.726562 102.964844 C 370.070312 102.964844 372.050781 101.75 373.039062 98.476562 L 357.304688 56.371094 L 370.449219 56.371094 L 379.34375 84.464844 L 379.492188 84.464844 L 388.363281 56.394531 Z M 401 56.394531 "/>
+</g>
+</svg>
Index: /branches/amp_4_0/platform/tools/container/services/opensearch-dashboards/assets/favicon.ico
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: platform/tools/container/services/opensearch-dashboards/assets/favicon.ico
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: /branches/amp_4_0/platform/tools/container/services/opensearch-dashboards/assets/loading_array_logo.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: platform/tools/container/services/opensearch-dashboards/assets/loading_array_logo.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: /branches/amp_4_0/platform/tools/container/services/opensearch-dashboards/assets/mark_array_logo.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: platform/tools/container/services/opensearch-dashboards/assets/mark_array_logo.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: /branches/amp_4_0/platform/tools/container/services/opensearch-dashboards/export.ndjson
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/opensearch-dashboards/export.ndjson	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/opensearch-dashboards/export.ndjson	(working copy)
@@ -0,0 +1,17 @@
+{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"},"title":"Traffic","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Traffic\",\"type\":\"metrics\",\"aggs\":[],\"params\":{\"id\":\"61ca57f0-469d-11e7-af02-69e470af7417\",\"type\":\"timeseries\",\"series\":[{\"id\":\"61ca57f1-469d-11e7-af02-69e470af7417\",\"color\":\"#54B399\",\"split_mode\":\"filters\",\"split_color_mode\":\"opensearchDashboards\",\"metrics\":[{\"id\":\"61ca57f2-469d-11e7-af02-69e470af7417\",\"type\":\"count\"}],\"separate_axis\":0,\"axis_position\":\"right\",\"axis_scale\":\"normal\",\"formatter\":\"number\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\",\"label\":\"Traffic\",\"split_filters\":[{\"filter\":{\"query\":\"(device_type=vasf or device_type=asf) and client_ip:*\",\"language\":\"kuery\"},\"label\":\"Traffic\",\"color\":\"#54B399\",\"id\":\"814bdc10-b61b-11f0-8d78-a3f3eecdb8aa\"}],\"type\":\"timeseries\"}],\"time_field\":\"\",\"index_pattern\":\"\",\"interval\":\"\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"axis_scale\":\"normal\",\"show_legend\":1,\"show_grid\":1,\"tooltip_mode\":\"show_all\",\"default_index_pattern\":\"acm-*\",\"default_timefield\":\"@timestamp\",\"isModelInvalid\":false}}"},"id":"c2cd6be0-b61b-11f0-8a1e-3d69a609ed1d","migrationVersion":{"visualization":"7.10.0"},"references":[],"type":"visualization","updated_at":"2025-10-31T05:37:59.710Z","version":"WzIyNCw1XQ=="}
+{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"},"title":"Attacks","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Attacks\",\"type\":\"metrics\",\"aggs\":[],\"params\":{\"id\":\"61ca57f0-469d-11e7-af02-69e470af7417\",\"type\":\"timeseries\",\"series\":[{\"id\":\"61ca57f1-469d-11e7-af02-69e470af7417\",\"color\":\"#54B399\",\"split_mode\":\"filters\",\"split_color_mode\":\"opensearchDashboards\",\"metrics\":[{\"id\":\"61ca57f2-469d-11e7-af02-69e470af7417\",\"type\":\"count\"}],\"separate_axis\":0,\"axis_position\":\"right\",\"axis_scale\":\"normal\",\"formatter\":\"number\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\",\"label\":\"Attacks\",\"split_filters\":[{\"filter\":{\"query\":\"client_ip:* and severity: \\\"critical\\\" and (device_type=asf or device_type=vasf)\",\"language\":\"kuery\"},\"label\":\"Attacks\",\"color\":\"#54B399\",\"id\":\"1e689260-b61a-11f0-8d78-a3f3eecdb8aa\"}],\"type\":\"timeseries\"}],\"time_field\":\"\",\"index_pattern\":\"\",\"interval\":\"\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"axis_scale\":\"normal\",\"show_legend\":1,\"show_grid\":1,\"tooltip_mode\":\"show_all\",\"default_index_pattern\":\"acm-*\",\"default_timefield\":\"@timestamp\",\"isModelInvalid\":false}}"},"id":"3919ea50-b61a-11f0-8a1e-3d69a609ed1d","migrationVersion":{"visualization":"7.10.0"},"references":[],"type":"visualization","updated_at":"2025-10-31T05:26:59.189Z","version":"WzgyLDVd"}
+{"attributes":{"fields":"[{\"count\":0,\"name\":\"@timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"@version\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"@version.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"@version\"}}},{\"count\":0,\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_score\",\"type\":\"number\",\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_type\",\"type\":\"string\",\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"attack_description\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"attack_description.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"attack_description\"}}},{\"count\":0,\"name\":\"attack_type\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"attack_type.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"attack_type\"}}},{\"count\":0,\"name\":\"client_geoip.city_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"client_geoip.city_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"client_geoip.city_name\"}}},{\"count\":0,\"name\":\"client_geoip.country_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"client_geoip.country_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"client_geoip.country_name\"}}},{\"count\":0,\"name\":\"client_geoip.location.lat\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"client_geoip.location.lat.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"client_geoip.location.lat\"}}},{\"count\":0,\"name\":\"client_geoip.location.lon\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"client_geoip.location.lon.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"client_geoip.location.lon\"}}},{\"count\":0,\"name\":\"client_ip\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"client_ip.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"client_ip\"}}},{\"count\":0,\"name\":\"destination_geoip.city_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"destination_geoip.city_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"destination_geoip.city_name\"}}},{\"count\":0,\"name\":\"destination_geoip.country_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"destination_geoip.country_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"destination_geoip.country_name\"}}},{\"count\":0,\"name\":\"destination_geoip.location.lat\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"destination_geoip.location.lat.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"destination_geoip.location.lat\"}}},{\"count\":0,\"name\":\"destination_geoip.location.lon\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"destination_geoip.location.lon.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"destination_geoip.location.lon\"}}},{\"count\":0,\"name\":\"destination_ip\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"destination_ip.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"destination_ip\"}}},{\"count\":0,\"name\":\"destination_port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"device_group\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"device_group.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"device_group\"}}},{\"count\":0,\"name\":\"device_hostname\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"device_hostname.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"device_hostname\"}}},{\"count\":0,\"name\":\"device_ip\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"device_ip.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"device_ip\"}}},{\"count\":0,\"name\":\"device_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"device_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"device_name\"}}},{\"count\":0,\"name\":\"device_type\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"device_type.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"device_type\"}}},{\"count\":0,\"name\":\"event.original\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"event.original.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"event.original\"}}},{\"count\":0,\"name\":\"event_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"host.ip\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"host.ip.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"host.ip\"}}},{\"count\":0,\"name\":\"http_request_method\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"http_request_method.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"http_request_method\"}}},{\"count\":0,\"name\":\"http_request_url\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"http_request_url.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"http_request_url\"}}},{\"count\":0,\"name\":\"http_status_code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"log_facility\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"log_facility.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"log_facility\"}}},{\"count\":0,\"name\":\"log_facility_numeric\",\"type\":\"number\",\"esTypes\":[\"long\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"matched_data\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"matched_data.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"matched_data\"}}},{\"count\":0,\"name\":\"message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"message.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"message\"}}},{\"count\":0,\"name\":\"message_id\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"message_id.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"message_id\"}}},{\"count\":0,\"name\":\"network_protocol\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"network_protocol.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"network_protocol\"}}},{\"count\":0,\"name\":\"request_filename\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"request_filename.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"request_filename\"}}},{\"count\":0,\"name\":\"requested_host\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"requested_host.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"requested_host\"}}},{\"count\":0,\"name\":\"security_severity\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"security_severity.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"security_severity\"}}},{\"count\":0,\"name\":\"server_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"server_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"server_name\"}}},{\"count\":0,\"name\":\"session_id\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"session_id.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"session_id\"}}},{\"count\":0,\"name\":\"severity\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"severity.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"severity\"}}},{\"count\":0,\"name\":\"severity_numeric\",\"type\":\"number\",\"esTypes\":[\"long\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"source_ip\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"source_ip.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"source_ip\"}}},{\"count\":0,\"name\":\"source_port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"syslog_message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"syslog_message.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"syslog_message\"}}},{\"count\":0,\"name\":\"syslog_priority\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"syslog_priority.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"syslog_priority\"}}},{\"count\":0,\"name\":\"syslog_timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"syslog_version\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"syslog_version.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"syslog_version\"}}},{\"count\":0,\"name\":\"tags\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"tags.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"tags\"}}},{\"count\":0,\"name\":\"type\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"type.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"type\"}}},{\"count\":0,\"name\":\"user_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"user_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"user_name\"}}}]","timeFieldName":"@timestamp","title":"acm-*"},"id":"076aec30-b470-11f0-8a1e-3d69a609ed1d","migrationVersion":{"index-pattern":"7.6.0"},"references":[],"type":"index-pattern","updated_at":"2025-10-31T05:47:26.806Z","version":"WzIyNiw1XQ=="}
+{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[{\"$state\":{\"store\":\"appState\"},\"meta\":{\"alias\":null,\"disabled\":false,\"key\":\"query\",\"negate\":false,\"type\":\"custom\",\"value\":\"{\\\"bool\\\":{\\\"should\\\":[{\\\"match_phrase\\\":{\\\"device_type\\\":\\\"vasf\\\"}},{\\\"match_phrase\\\":{\\\"device_type\\\":\\\"asf\\\"}}],\\\"minimum_should_match\\\":1}}\",\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\"},\"query\":{\"bool\":{\"minimum_should_match\":1,\"should\":[{\"match_phrase\":{\"device_type\":\"vasf\"}},{\"match_phrase\":{\"device_type\":\"asf\"}}]}}},{\"$state\":{\"store\":\"appState\"},\"exists\":{\"field\":\"device_ip\"},\"meta\":{\"alias\":null,\"disabled\":false,\"key\":\"device_ip\",\"negate\":false,\"type\":\"exists\",\"value\":\"exists\",\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.filter[1].meta.index\"}}],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Total Requests","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Total Requests\",\"type\":\"metric\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{\"customLabel\":\"Requests\"},\"schema\":\"metric\"}],\"params\":{\"addTooltip\":true,\"addLegend\":false,\"type\":\"metric\",\"metric\":{\"percentageMode\":false,\"useRanges\":false,\"colorSchema\":\"Green to Red\",\"metricColorMode\":\"None\",\"colorsRange\":[{\"from\":0,\"to\":10000}],\"labels\":{\"show\":true},\"invertColors\":false,\"style\":{\"bgFill\":\"#000\",\"bgColor\":false,\"labelColor\":false,\"subText\":\"\",\"fontSize\":60}}}}"},"id":"0b071d60-b47e-11f0-8a1e-3d69a609ed1d","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"076aec30-b470-11f0-8a1e-3d69a609ed1d","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"},{"id":"076aec30-b470-11f0-8a1e-3d69a609ed1d","name":"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index","type":"index-pattern"},{"id":"076aec30-b470-11f0-8a1e-3d69a609ed1d","name":"kibanaSavedObjectMeta.searchSourceJSON.filter[1].meta.index","type":"index-pattern"}],"type":"visualization","updated_at":"2025-10-29T04:17:05.879Z","version":"WzYsNV0="}
+{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[{\"$state\":{\"store\":\"appState\"},\"meta\":{\"alias\":null,\"disabled\":false,\"key\":\"query\",\"negate\":false,\"type\":\"custom\",\"value\":\"{\\\"bool\\\":{\\\"should\\\":[{\\\"match_phrase\\\":{\\\"device_type\\\":\\\"vasf\\\"}},{\\\"match_phrase\\\":{\\\"device_type\\\":\\\"asf\\\"}}],\\\"minimum_should_match\\\":1}}\",\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\"},\"query\":{\"bool\":{\"minimum_should_match\":1,\"should\":[{\"match_phrase\":{\"device_type\":\"vasf\"}},{\"match_phrase\":{\"device_type\":\"asf\"}}]}}},{\"$state\":{\"store\":\"appState\"},\"meta\":{\"alias\":null,\"disabled\":false,\"key\":\"severity\",\"negate\":false,\"params\":{\"query\":\"critical\"},\"type\":\"phrase\",\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.filter[1].meta.index\"},\"query\":{\"match_phrase\":{\"severity\":\"critical\"}}},{\"$state\":{\"store\":\"appState\"},\"exists\":{\"field\":\"device_ip\"},\"meta\":{\"alias\":null,\"disabled\":false,\"key\":\"device_ip\",\"negate\":false,\"type\":\"exists\",\"value\":\"exists\",\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.filter[2].meta.index\"}}],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"ASF Devices","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"ASF Devices\",\"type\":\"pie\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"device_ip.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"ASF Device IP\"},\"schema\":\"segment\"}],\"params\":{\"type\":\"pie\",\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"isDonut\":true,\"labels\":{\"show\":false,\"values\":true,\"last_level\":true,\"truncate\":100}}}"},"id":"8717a520-b481-11f0-8a1e-3d69a609ed1d","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"076aec30-b470-11f0-8a1e-3d69a609ed1d","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"},{"id":"076aec30-b470-11f0-8a1e-3d69a609ed1d","name":"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index","type":"index-pattern"},{"id":"076aec30-b470-11f0-8a1e-3d69a609ed1d","name":"kibanaSavedObjectMeta.searchSourceJSON.filter[1].meta.index","type":"index-pattern"},{"id":"076aec30-b470-11f0-8a1e-3d69a609ed1d","name":"kibanaSavedObjectMeta.searchSourceJSON.filter[2].meta.index","type":"index-pattern"}],"type":"visualization","updated_at":"2025-10-31T05:23:20.961Z","version":"WzE3LDVd"}
+{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"(device_type: \\\"vasf\\\" or device_type: \\\"asf\\\") and severity: \\\"critical\\\"\",\"language\":\"kuery\"},\"filter\":[{\"$state\":{\"store\":\"appState\"},\"meta\":{\"alias\":null,\"disabled\":false,\"key\":\"security_severity\",\"negate\":false,\"params\":{\"query\":\"critical\"},\"type\":\"phrase\",\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\"},\"query\":{\"match_phrase\":{\"security_severity\":\"critical\"}}},{\"$state\":{\"store\":\"appState\"},\"meta\":{\"alias\":null,\"disabled\":false,\"key\":\"query\",\"negate\":false,\"type\":\"custom\",\"value\":\"{\\\"bool\\\":{\\\"minimum_should_match\\\":1,\\\"should\\\":[{\\\"match_phrase\\\":{\\\"device_type\\\":\\\"vasf\\\"}},{\\\"match_phrase\\\":{\\\"device_type\\\":\\\"asf\\\"}}]}}\",\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.filter[1].meta.index\"},\"query\":{\"bool\":{\"minimum_should_match\":1,\"should\":[{\"match_phrase\":{\"device_type\":\"vasf\"}},{\"match_phrase\":{\"device_type\":\"asf\"}}]}}}],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Attacks Blocked","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Attacks Blocked\",\"type\":\"metric\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{\"customLabel\":\"Blocked\"},\"schema\":\"metric\"}],\"params\":{\"addTooltip\":true,\"addLegend\":false,\"type\":\"metric\",\"metric\":{\"percentageMode\":false,\"useRanges\":false,\"colorSchema\":\"Green to Red\",\"metricColorMode\":\"None\",\"colorsRange\":[{\"from\":0,\"to\":10000}],\"labels\":{\"show\":true},\"invertColors\":false,\"style\":{\"bgFill\":\"#000\",\"bgColor\":false,\"labelColor\":false,\"subText\":\"\",\"fontSize\":60}}}}"},"id":"874efe40-b480-11f0-8a1e-3d69a609ed1d","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"076aec30-b470-11f0-8a1e-3d69a609ed1d","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"},{"id":"076aec30-b470-11f0-8a1e-3d69a609ed1d","name":"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index","type":"index-pattern"},{"id":"076aec30-b470-11f0-8a1e-3d69a609ed1d","name":"kibanaSavedObjectMeta.searchSourceJSON.filter[1].meta.index","type":"index-pattern"}],"type":"visualization","updated_at":"2025-10-31T05:21:11.256Z","version":"WzE2LDVd"}
+{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Most Used HTTP Methods","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Most Used HTTP Methods\",\"type\":\"horizontal_bar\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{\"customLabel\":\"Requests\"},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"significant_terms\",\"params\":{\"field\":\"http_request_method.keyword\",\"size\":5,\"customLabel\":\"Request Method\"},\"schema\":\"group\"}],\"params\":{\"type\":\"histogram\",\"grid\":{\"categoryLines\":false},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":200},\"title\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":75,\"filter\":true,\"truncate\":100},\"title\":{\"text\":\"Requests\"}}],\"seriesParams\":[{\"show\":true,\"type\":\"histogram\",\"mode\":\"normal\",\"data\":{\"label\":\"Requests\",\"id\":\"1\"},\"valueAxis\":\"ValueAxis-1\",\"drawLinesBetweenPoints\":true,\"lineWidth\":2,\"showCircles\":true}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false,\"labels\":{},\"thresholdLine\":{\"show\":false,\"value\":10,\"width\":1,\"style\":\"full\",\"color\":\"#E7664C\"}}}"},"id":"90eb6c60-b61d-11f0-8a1e-3d69a609ed1d","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"076aec30-b470-11f0-8a1e-3d69a609ed1d","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2025-10-31T05:50:55.014Z","version":"WzIyOCw1XQ=="}
+{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"(device_type: \\\"vasf\\\" or device_type: \\\"asf\\\") \",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Status Code for Traffic","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Status Code for Traffic\",\"type\":\"horizontal_bar\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{\"customLabel\":\"Requests\"},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"http_status_code\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":5,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"Status Code\"},\"schema\":\"group\"}],\"params\":{\"type\":\"histogram\",\"grid\":{\"categoryLines\":false},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":200},\"title\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":75,\"filter\":true,\"truncate\":100},\"title\":{\"text\":\"Requests\"}}],\"seriesParams\":[{\"show\":true,\"type\":\"histogram\",\"mode\":\"normal\",\"data\":{\"label\":\"Requests\",\"id\":\"1\"},\"valueAxis\":\"ValueAxis-1\",\"drawLinesBetweenPoints\":true,\"lineWidth\":2,\"showCircles\":true}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false,\"labels\":{},\"thresholdLine\":{\"show\":false,\"value\":10,\"width\":1,\"style\":\"full\",\"color\":\"#E7664C\"}}}"},"id":"145f7640-b61e-11f0-8a1e-3d69a609ed1d","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"076aec30-b470-11f0-8a1e-3d69a609ed1d","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2025-10-31T05:54:35.556Z","version":"WzIyOSw1XQ=="}
+{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"(device_type: \\\"vasf\\\" or device_type: \\\"asf\\\") and severity: \\\"critical\\\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Top Applications by Attacks","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Top Applications by Attacks\",\"type\":\"horizontal_bar\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{\"customLabel\":\"Attacks\"},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"significant_terms\",\"params\":{\"field\":\"server_name.keyword\",\"size\":10},\"schema\":\"group\"}],\"params\":{\"type\":\"histogram\",\"grid\":{\"categoryLines\":false},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":200},\"title\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":75,\"filter\":true,\"truncate\":100},\"title\":{\"text\":\"Attacks\"}}],\"seriesParams\":[{\"show\":true,\"type\":\"histogram\",\"mode\":\"normal\",\"data\":{\"label\":\"Attacks\",\"id\":\"1\"},\"valueAxis\":\"ValueAxis-1\",\"drawLinesBetweenPoints\":true,\"lineWidth\":2,\"showCircles\":true}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false,\"labels\":{},\"thresholdLine\":{\"show\":false,\"value\":10,\"width\":1,\"style\":\"full\",\"color\":\"#E7664C\"}}}"},"id":"eb992c00-b623-11f0-8a1e-3d69a609ed1d","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"076aec30-b470-11f0-8a1e-3d69a609ed1d","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2025-10-31T06:36:24.128Z","version":"WzIzNSw1XQ=="}
+{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"language\":\"kuery\",\"query\":\"(device_type: \\\"vasf\\\" or device_type: \\\"asf\\\") \"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Top Applications by Traffic","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Top Applications by Traffic\",\"type\":\"horizontal_bar\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{\"customLabel\":\"Requests\"},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"significant_terms\",\"params\":{\"field\":\"server_name.keyword\",\"size\":10,\"customLabel\":\"Applications\"},\"schema\":\"group\"}],\"params\":{\"type\":\"histogram\",\"grid\":{\"categoryLines\":false},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":200},\"title\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":75,\"filter\":true,\"truncate\":100},\"title\":{\"text\":\"Requests\"}}],\"seriesParams\":[{\"show\":true,\"type\":\"histogram\",\"mode\":\"normal\",\"data\":{\"label\":\"Requests\",\"id\":\"1\"},\"valueAxis\":\"ValueAxis-1\",\"drawLinesBetweenPoints\":true,\"lineWidth\":2,\"showCircles\":true}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false,\"labels\":{},\"thresholdLine\":{\"show\":false,\"value\":10,\"width\":1,\"style\":\"full\",\"color\":\"#E7664C\"}}}"},"id":"06217c50-b61d-11f0-8a1e-3d69a609ed1d","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"076aec30-b470-11f0-8a1e-3d69a609ed1d","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2025-10-31T05:48:11.833Z","version":"WzIyNyw1XQ=="}
+{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"(device_type: \\\"vasf\\\" or device_type: \\\"asf\\\") and severity: \\\"critical\\\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Top 10 Attack IP Addresses","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Top 10 Attack IP Addresses\",\"type\":\"horizontal_bar\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{\"customLabel\":\"Number of Attacks\"},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"significant_terms\",\"params\":{\"field\":\"client_ip.keyword\",\"size\":10,\"customLabel\":\"IPs\"},\"schema\":\"group\"}],\"params\":{\"type\":\"histogram\",\"grid\":{\"categoryLines\":false},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":200},\"title\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":75,\"filter\":true,\"truncate\":100},\"title\":{\"text\":\"Number of Attacks\"}}],\"seriesParams\":[{\"show\":true,\"type\":\"histogram\",\"mode\":\"normal\",\"data\":{\"label\":\"Number of Attacks\",\"id\":\"1\"},\"valueAxis\":\"ValueAxis-1\",\"drawLinesBetweenPoints\":true,\"lineWidth\":2,\"showCircles\":true}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false,\"labels\":{},\"thresholdLine\":{\"show\":false,\"value\":10,\"width\":1,\"style\":\"full\",\"color\":\"#E7664C\"}}}"},"id":"6592a9b0-b61e-11f0-8a1e-3d69a609ed1d","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"076aec30-b470-11f0-8a1e-3d69a609ed1d","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2025-10-31T05:57:06.806Z","version":"WzIzMSw1XQ=="}
+{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"(device_type: \\\"vasf\\\" or device_type: \\\"asf\\\")\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Top 10 User IP addresses","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Top 10 User IP addresses\",\"type\":\"horizontal_bar\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{\"customLabel\":\"Requests\"},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"significant_terms\",\"params\":{\"field\":\"client_ip.keyword\",\"size\":10,\"customLabel\":\"IPs\"},\"schema\":\"group\"}],\"params\":{\"type\":\"histogram\",\"grid\":{\"categoryLines\":false},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":200},\"title\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":75,\"filter\":true,\"truncate\":100},\"title\":{\"text\":\"Requests\"}}],\"seriesParams\":[{\"show\":true,\"type\":\"histogram\",\"mode\":\"normal\",\"data\":{\"label\":\"Requests\",\"id\":\"1\"},\"valueAxis\":\"ValueAxis-1\",\"drawLinesBetweenPoints\":true,\"lineWidth\":2,\"showCircles\":true}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false,\"labels\":{},\"thresholdLine\":{\"show\":false,\"value\":10,\"width\":1,\"style\":\"full\",\"color\":\"#E7664C\"}}}"},"id":"a4ebf060-b620-11f0-8a1e-3d69a609ed1d","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"076aec30-b470-11f0-8a1e-3d69a609ed1d","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2025-10-31T06:22:02.086Z","version":"WzIzNCw1XQ=="}
+{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"},"title":"DDoS Attacks","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"DDoS Attacks\",\"type\":\"metrics\",\"aggs\":[],\"params\":{\"id\":\"61ca57f0-469d-11e7-af02-69e470af7417\",\"type\":\"timeseries\",\"series\":[{\"id\":\"61ca57f1-469d-11e7-af02-69e470af7417\",\"color\":\"#54B399\",\"split_mode\":\"filters\",\"split_color_mode\":\"opensearchDashboards\",\"metrics\":[{\"id\":\"61ca57f2-469d-11e7-af02-69e470af7417\",\"type\":\"count\"}],\"separate_axis\":0,\"axis_position\":\"right\",\"axis_scale\":\"normal\",\"formatter\":\"number\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\",\"split_filters\":[{\"filter\":{\"query\":\"severity : \\\"critical\\\" and attack_type : (\\\"SYN_FLOOD\\\" or \\\"SYNACK_FLOOD\\\" or \\\"ACK_FLOOD\\\" or \\\"FINRST_FLOOD\\\" or \\\"CONN_FLOOD\\\" or \\\"SLOWCONN\\\" or \\\"ABNCONN\\\" or \\\"FRAG_FLOOD\\\" or \\\"FLOOD\\\" or \\\"FINGERPRINT\\\" or \\\"GET_FLOOD\\\" or \\\"POST_FLOOD\\\" or \\\"SLOW_LORIS\\\" or \\\"SLOW_POST\\\") and (device_type=asf or device_type=vasf)\",\"language\":\"kuery\"},\"label\":\"Attacks\",\"color\":\"#54B399\",\"id\":\"c94f2ea0-b61a-11f0-8d78-a3f3eecdb8aa\"}],\"label\":\"\",\"type\":\"timeseries\"}],\"time_field\":\"\",\"index_pattern\":\"\",\"interval\":\"\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"axis_scale\":\"normal\",\"show_legend\":1,\"show_grid\":1,\"tooltip_mode\":\"show_all\",\"default_index_pattern\":\"acm-*\",\"default_timefield\":\"@timestamp\",\"isModelInvalid\":false}}"},"id":"eba41790-b61a-11f0-8a1e-3d69a609ed1d","migrationVersion":{"visualization":"7.10.0"},"references":[],"type":"visualization","updated_at":"2025-10-31T05:31:58.729Z","version":"WzEzOSw1XQ=="}
+{"attributes":{"columns":["_source"],"description":"","hits":0,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"(device_type=asf or device_type=vasf) and client_ip:*\",\"language\":\"kuery\"},\"highlightAll\":true,\"version\":true,\"aggs\":{\"2\":{\"date_histogram\":{\"field\":\"@timestamp\",\"fixed_interval\":\"30m\",\"time_zone\":\"Asia/Calcutta\",\"min_doc_count\":1}}},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"sort":[],"title":"ASF Request Logs","version":1},"id":"6b2d6e30-b625-11f0-8a1e-3d69a609ed1d","migrationVersion":{"search":"7.9.3"},"references":[{"id":"076aec30-b470-11f0-8a1e-3d69a609ed1d","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"search","updated_at":"2025-10-31T06:47:07.667Z","version":"WzI0MCw1XQ=="}
+{"attributes":{"description":"","hits":0,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"},"optionsJSON":"{\"useMargins\":true,\"hidePanelTitles\":false}","panelsJSON":"[{\"version\":\"3.2.0\",\"gridData\":{\"x\":0,\"y\":0,\"w\":24,\"h\":15,\"i\":\"f8340ef7-b0fd-4d35-9498-4d088fbe367e\"},\"panelIndex\":\"f8340ef7-b0fd-4d35-9498-4d088fbe367e\",\"embeddableConfig\":{},\"panelRefName\":\"panel_0\"},{\"version\":\"3.2.0\",\"gridData\":{\"x\":24,\"y\":0,\"w\":24,\"h\":15,\"i\":\"15c8e455-87d5-4aa2-9142-4199c6352cfe\"},\"panelIndex\":\"15c8e455-87d5-4aa2-9142-4199c6352cfe\",\"embeddableConfig\":{},\"panelRefName\":\"panel_1\"},{\"version\":\"3.2.0\",\"gridData\":{\"x\":0,\"y\":15,\"w\":16,\"h\":13,\"i\":\"91b28ab2-94ef-4b06-bb44-0430fbe3260c\"},\"panelIndex\":\"91b28ab2-94ef-4b06-bb44-0430fbe3260c\",\"embeddableConfig\":{},\"panelRefName\":\"panel_2\"},{\"version\":\"3.2.0\",\"gridData\":{\"x\":16,\"y\":15,\"w\":16,\"h\":13,\"i\":\"73c449be-cddf-4e25-819d-353e8a9f56b2\"},\"panelIndex\":\"73c449be-cddf-4e25-819d-353e8a9f56b2\",\"embeddableConfig\":{},\"panelRefName\":\"panel_3\"},{\"version\":\"3.2.0\",\"gridData\":{\"x\":32,\"y\":15,\"w\":16,\"h\":13,\"i\":\"39c68898-535a-48c4-8ce9-57146b67d599\"},\"panelIndex\":\"39c68898-535a-48c4-8ce9-57146b67d599\",\"embeddableConfig\":{},\"panelRefName\":\"panel_4\"},{\"version\":\"3.2.0\",\"gridData\":{\"x\":0,\"y\":28,\"w\":24,\"h\":15,\"i\":\"4ab15d93-0d08-4baa-bf5c-5cc9bd15ec19\"},\"panelIndex\":\"4ab15d93-0d08-4baa-bf5c-5cc9bd15ec19\",\"embeddableConfig\":{},\"panelRefName\":\"panel_5\"},{\"version\":\"3.2.0\",\"gridData\":{\"x\":24,\"y\":28,\"w\":24,\"h\":15,\"i\":\"316967b0-ecfd-4f7e-bf16-cf527f01b9f6\"},\"panelIndex\":\"316967b0-ecfd-4f7e-bf16-cf527f01b9f6\",\"embeddableConfig\":{},\"panelRefName\":\"panel_6\"},{\"version\":\"3.2.0\",\"gridData\":{\"x\":0,\"y\":43,\"w\":24,\"h\":15,\"i\":\"0e4b1b5e-90d7-4d96-9e48-4a22dfe3cdd5\"},\"panelIndex\":\"0e4b1b5e-90d7-4d96-9e48-4a22dfe3cdd5\",\"embeddableConfig\":{},\"panelRefName\":\"panel_7\"},{\"version\":\"3.2.0\",\"gridData\":{\"x\":24,\"y\":43,\"w\":24,\"h\":15,\"i\":\"aea3f707-5729-4f30-8962-d2d86cf89e77\"},\"panelIndex\":\"aea3f707-5729-4f30-8962-d2d86cf89e77\",\"embeddableConfig\":{},\"panelRefName\":\"panel_8\"},{\"version\":\"3.2.0\",\"gridData\":{\"x\":0,\"y\":58,\"w\":24,\"h\":15,\"i\":\"0938b7cd-8e30-4531-bbd0-5b32771b80cb\"},\"panelIndex\":\"0938b7cd-8e30-4531-bbd0-5b32771b80cb\",\"embeddableConfig\":{},\"panelRefName\":\"panel_9\"},{\"version\":\"3.2.0\",\"gridData\":{\"x\":24,\"y\":58,\"w\":24,\"h\":15,\"i\":\"258538be-9945-49bf-a70a-801f15547efb\"},\"panelIndex\":\"258538be-9945-49bf-a70a-801f15547efb\",\"embeddableConfig\":{},\"panelRefName\":\"panel_10\"},{\"version\":\"3.2.0\",\"gridData\":{\"x\":0,\"y\":73,\"w\":48,\"h\":15,\"i\":\"fccb7850-a810-4fb4-ab88-0a91ec8aa90c\"},\"panelIndex\":\"fccb7850-a810-4fb4-ab88-0a91ec8aa90c\",\"embeddableConfig\":{},\"panelRefName\":\"panel_11\"},{\"version\":\"3.2.0\",\"gridData\":{\"x\":0,\"y\":88,\"w\":48,\"h\":20,\"i\":\"fbe81922-8c7b-42b9-a26a-516f356181b4\"},\"panelIndex\":\"fbe81922-8c7b-42b9-a26a-516f356181b4\",\"embeddableConfig\":{},\"panelRefName\":\"panel_12\"}]","timeRestore":false,"title":"ASF","version":1},"id":"bd692d70-b624-11f0-8a1e-3d69a609ed1d","migrationVersion":{"dashboard":"7.9.3"},"references":[{"id":"c2cd6be0-b61b-11f0-8a1e-3d69a609ed1d","name":"panel_0","type":"visualization"},{"id":"3919ea50-b61a-11f0-8a1e-3d69a609ed1d","name":"panel_1","type":"visualization"},{"id":"0b071d60-b47e-11f0-8a1e-3d69a609ed1d","name":"panel_2","type":"visualization"},{"id":"8717a520-b481-11f0-8a1e-3d69a609ed1d","name":"panel_3","type":"visualization"},{"id":"874efe40-b480-11f0-8a1e-3d69a609ed1d","name":"panel_4","type":"visualization"},{"id":"90eb6c60-b61d-11f0-8a1e-3d69a609ed1d","name":"panel_5","type":"visualization"},{"id":"145f7640-b61e-11f0-8a1e-3d69a609ed1d","name":"panel_6","type":"visualization"},{"id":"eb992c00-b623-11f0-8a1e-3d69a609ed1d","name":"panel_7","type":"visualization"},{"id":"06217c50-b61d-11f0-8a1e-3d69a609ed1d","name":"panel_8","type":"visualization"},{"id":"6592a9b0-b61e-11f0-8a1e-3d69a609ed1d","name":"panel_9","type":"visualization"},{"id":"a4ebf060-b620-11f0-8a1e-3d69a609ed1d","name":"panel_10","type":"visualization"},{"id":"eba41790-b61a-11f0-8a1e-3d69a609ed1d","name":"panel_11","type":"visualization"},{"id":"6b2d6e30-b625-11f0-8a1e-3d69a609ed1d","name":"panel_12","type":"search"}],"type":"dashboard","updated_at":"2025-10-31T06:48:35.383Z","version":"WzI0Niw1XQ=="}
+{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"(device_type: \\\"vasf\\\" or device_type: \\\"asf\\\") and severity: \\\"critical\\\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Top Attack Types","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Top Attack Types\",\"type\":\"pie\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"attack_description.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":5,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"Attack Types\"},\"schema\":\"segment\"}],\"params\":{\"type\":\"pie\",\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"isDonut\":true,\"labels\":{\"show\":false,\"values\":true,\"last_level\":true,\"truncate\":100}}}"},"id":"9c040310-b621-11f0-8a1e-3d69a609ed1d","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"076aec30-b470-11f0-8a1e-3d69a609ed1d","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2025-10-31T06:19:51.617Z","version":"WzIzMyw1XQ=="}
+{"exportedCount":16,"missingRefCount":0,"missingReferences":[]}
Index: /branches/amp_4_0/platform/tools/container/services/opensearch/amplog_template.json
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/opensearch/amplog_template.json	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/opensearch/amplog_template.json	(working copy)
@@ -0,0 +1,60 @@
+{
+    "index_patterns": [
+        "acm-*"
+    ],
+    "template": {
+        "settings": {
+            "index": {
+                "number_of_shards": "1",
+                "number_of_replicas": "0",
+                "refresh_interval": "5s"
+            }
+        },
+        "mappings": {
+            "dynamic_templates": [
+                {
+                    "strings": {
+                        "match_mapping_type": "string",
+                        "mapping": {
+                            "type": "text",
+                            "fields": {
+                                "keyword": {
+                                    "type": "keyword",
+                                    "ignore_above": 256
+                                }
+                            }
+                        }
+                    }
+                }
+            ],
+            "properties": {
+                "@timestamp": {
+                    "type": "date"
+                },
+                "client_geoip": {
+                    "properties": {
+                        "location": {
+                            "type": "geo_point"
+                        }
+                    }
+                },
+                "destination_geoip": {
+                    "properties": {
+                        "location": {
+                            "type": "geo_point"
+                        }
+                    }
+                },
+                "syslog_timestamp": {
+                    "type": "date",
+                    "format": "MMM  d HH:mm:ss||MMM dd HH:mm:ss"
+                },
+                "event_time": {
+                    "type": "date",
+                    "format": "yyyy-MM-dd HH:mm:ss"
+                }
+            }
+        },
+        "aliases": {}
+    }
+}
\ No newline at end of file
Index: /branches/amp_4_0/platform/tools/container/services/pgbouncer/pgbouncer.ini
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/pgbouncer/pgbouncer.ini	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/pgbouncer/pgbouncer.ini	(working copy)
@@ -0,0 +1,22 @@
+[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/userlist.txt
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/pgbouncer/userlist.txt	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/pgbouncer/userlist.txt	(working copy)
@@ -0,0 +1,2 @@
+"amp_admin" "md507b5a1b329433696700cb7444c9b1392"
+"amp_ts_user" "md5dfc2c68ba0156715b3d09d4351336c53"
Index: /branches/amp_4_0/platform/tools/container/services/postgres/initdb.d/00_enable_logging.sh
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/postgres/initdb.d/00_enable_logging.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/postgres/initdb.d/00_enable_logging.sh	(working copy)
@@ -0,0 +1,13 @@
+#!/bin/bash
+echo "Configuring persistent logging..."
+# Append config to postgresql.conf
+# This ensures logs are written to the 'pg_log' directory inside the data volume.
+cat >> "$PGDATA/postgresql.conf" <<EOF
+
+# --- AMP Persistent Logging ---
+logging_collector = on
+log_directory = 'pg_log'
+log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
+log_rotation_age = 1d
+log_rotation_size = 10MB
+EOF
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	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/postgres/initdb.d/01_basics.sql	(working copy)
@@ -0,0 +1,15 @@
+-- 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	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/postgres/initdb.d/02_telegraf_snmp.sql	(working copy)
@@ -0,0 +1,739 @@
+-- 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;
+
+\c amp_ts
+
+-- 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/container/services/postgres/initdb.d/init_db.sql
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/postgres/initdb.d/init_db.sql	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/postgres/initdb.d/init_db.sql	(working copy)
@@ -0,0 +1,451 @@
+-- 'cm' should be created as part of the install_psql.sh script.
+\c cm
+
+
+-- Create a cm_role table
+CREATE TABLE IF NOT EXISTS cm_role
+(
+    name        varchar(64) primary key,
+    description varchar(32) NOT NULL,
+    priority    integer       DEFAULT 1,
+    status      integer       DEFAULT 0,
+    time        varchar(64) NOT NULL,
+    device_list varchar(3328) DEFAULT NULL,
+    extend      jsonb
+);
+
+-- Create a zone table
+CREATE TABLE IF NOT EXISTS zone
+(
+    name varchar(32) NOT NULL,
+    PRIMARY KEY (name)
+);
+
+-- Create a cm_acl_resource table
+CREATE TABLE IF NOT EXISTS cm_acl_resource
+(
+    id          serial primary key,
+    group_name  varchar(64)  NOT NULL,
+    resource    varchar(512) NOT NULL,
+    description varchar(32)  NOT NULL,
+    status      integer       DEFAULT 0,
+    time        varchar(64)  NOT NULL,
+    device_list varchar(3328) DEFAULT NULL,
+    extend      jsonb
+);
+
+-- Create a cm_vpn_resource table
+CREATE TABLE IF NOT EXISTS cm_vpn_resource
+(
+    id          serial primary key,
+    group_name  varchar(64)  NOT NULL,
+    resource    varchar(128) NOT NULL,
+    type        integer       default 1,
+    group_type  varchar(32)   DEFAULT 'included',
+    description varchar(32)  NOT NULL,
+    status      integer       DEFAULT 0,
+    time        varchar(64)  NOT NULL,
+    device_list varchar(3328) DEFAULT NULL,
+    extend      jsonb
+);
+
+-- Create a device_group table
+CREATE TABLE IF NOT EXISTS device_group
+(
+    name character varying(128) COLLATE pg_catalog."default" NOT NULL,
+    CONSTRAINT device_group_pkey PRIMARY KEY (name)
+);
+
+-- Create a ha_cluster table
+CREATE TABLE IF NOT EXISTS ha_cluster
+(
+    id            serial primary key,
+    name          varchar(64) NOT NULL,
+    device_type   varchar(16)   default NULL,
+    extend_fields varchar(2048) default null
+);
+
+-- Create a vsite_list table
+CREATE TABLE IF NOT EXISTS vsite_list
+(
+    id          serial primary key,
+    vs_name     varchar(64)  NOT NULL,
+    device_name varchar(32)  NOT NULL,
+    site_FQDM   varchar(128) DEFAULT NULL,
+    ip          varchar(128) NOT NULL,
+    site_type   varchar(32)  NOT NULL,
+    description varchar(128) DEFAULT NULL,
+    parent_site varchar(64)  DEFAULT NULL,
+    extend      jsonb
+);
+
+-- Create a tar_file table
+CREATE TABLE IF NOT EXISTS tar_file
+(
+    id         serial primary key,
+    name       varchar(64) NOT NULL,
+    vsite_name varchar(64) DEFAULT NULL,
+    extend     jsonb
+);
+
+-- Create a proxy_cm table
+CREATE TABLE IF NOT EXISTS proxy_cm
+(
+    name   varchar(32) NOT NULL,
+    ip     varchar(64) NOT NULL,
+    PRIMARY KEY (name),
+    extend jsonb
+);
+
+-- Insert default values for the proxy_cm table
+INSERT INTO proxy_cm(name, ip) values ('default', '0.0.0.0');
+
+-- Create a device table
+CREATE TABLE IF NOT EXISTS device
+(
+    id varchar(64) NOT NULL,
+    zone varchar(32) NOT NULL,
+    name varchar(32) NOT NULL UNIQUE,
+    ip_address varchar(64) NOT NULL,
+    restapi_port INTEGER DEFAULT 9997,
+    restapi_username varchar(16) DEFAULT NULL,
+    restapi_password varchar(256) DEFAULT NULL,
+    console_username varchar(16) DEFAULT NULL,
+    console_password varchar(256) DEFAULT NULL,
+    connection varchar(16) NOT NULL DEFAULT 'unconnected',
+    status varchar(16) NOT NULL DEFAULT 'new',
+    version varchar(8192) DEFAULT NULL,
+    license_key varchar(128) DEFAULT NULL,
+    gateway_domain varchar(128) DEFAULT NULL,
+    location varchar(128) NOT NULL,
+    firewall_ip varchar(64) NOT NULL,
+    intranet_ip varchar(64) NOT NULL,
+    type varchar(16) NOT NULL,
+    extend_fields varchar(8192) DEFAULT NULL,
+    own varchar(64) DEFAULT NULL,
+    log_enable INTEGER DEFAULT 0,
+    webui_port INTEGER DEFAULT 8888,
+    device_group varchar(64) NOT NULL,
+    extend jsonb,
+    snmp_general varchar(256) NOT NULL DEFAULT '{"snmp_enable": false}',
+    enable_password varchar(32) DEFAULT NULL,
+    create_time varchar(40) DEFAULT (now()),
+    protocol varchar(16) DEFAULT 'restapi',
+    PRIMARY KEY (id),
+    FOREIGN KEY (zone) REFERENCES proxy_cm(name) ON DELETE CASCADE,
+    FOREIGN KEY (device_group) REFERENCES device_group(name) ON DELETE CASCADE
+);
+
+-- Create a hc_device table
+CREATE TABLE IF NOT EXISTS hc_device
+(
+    g_id serial references ha_cluster (id) on delete cascade,
+    d_id varchar(64) references device (id) on delete cascade,
+    unique (d_id, g_id)
+);
+
+-- Create a service table
+CREATE TABLE IF NOT EXISTS service
+(
+    id           serial primary key,
+    name         varchar(64),
+    device_type  varchar(32)  NOT NULL,
+    service_name varchar(128) NOT NULL,
+    service_type varchar(32)  NOT NULL,
+    extend       jsonb
+);
+
+-- Create an ip_pool table
+CREATE TABLE IF NOT EXISTS ip_pool
+(
+    id            serial primary key,
+    ip_pool_range varchar(128) NOT NULL,
+    device_id     varchar(64)  NOT NULL REFERENCES device (id) ON DELETE CASCADE,
+    extend        jsonb
+);
+
+-- Create an update_list table
+CREATE TABLE IF NOT EXISTS update_list
+(
+    id            serial primary key,
+    app_name      varchar(32) NOT NULL,
+    build_version varchar(64) NOT NULL,
+    file_size     INTEGER      DEFAULT 0,
+    md5_value     varchar(64)  DEFAULT NULL,
+    download_link varchar(128) DEFAULT NULL,
+    location      varchar(128) DEFAULT NULL,
+    extend        jsonb
+);
+
+-- Create a task table
+CREATE TABLE IF NOT EXISTS task
+(
+    id            serial primary key,
+    name          varchar(256) NOT NULL,
+    type          varchar(16)  NOT NULL,
+    trigger       varchar(16)  NOT NULL,
+    state         varchar(16)  NOT NULL,
+    next_run_time varchar(64)  NOT NULL,
+    description   text,
+    failed_times  integer        default 3,
+    scheduler     varchar(10240) default NULL,
+    custom_fields varchar(1024)  default NULL,
+    result_field  varchar(1024)  default NULL,
+    device_list   varchar(1024)  default NULL,
+    extend        jsonb
+);
+
+-- Create a file_type table
+CREATE TABLE IF NOT EXISTS file_type
+(
+    id SERIAL primary key,
+    name   varchar(32),
+    extend jsonb
+);
+
+-- Insert supported file types
+INSERT INTO file_type (name)
+VALUES ('device'),
+       ('command'),
+       ('diff'),
+       ('system'),
+       ('backup'),
+       ('customize'),
+       ('template'),
+       ('vs');
+
+-- Create a file_list table
+CREATE TABLE IF NOT EXISTS file_list
+(
+    name        varchar(128) primary key,
+    create_time varchar(64) NOT NULL,
+    modify_time varchar(64) DEFAULT NULL,
+    type        varchar(32) NOT NULL,
+    file_type_id int NOT NULL,
+    device_type varchar(32) DEFAULT NULL,
+    device_id varchar(64),
+    comment     text,
+    FOREIGN KEY (file_type_id) REFERENCES file_type(id) ON DELETE CASCADE,
+    extend      jsonb
+);
+
+-- Create an audit_user table
+CREATE TABLE IF NOT EXISTS audit_user
+(
+    id         serial primary key,
+    user_name  varchar(16) NOT NULL,
+    vsite_name varchar(64) NOT NULL,
+    host_name  varchar(64) NOT NULL,
+    extend     jsonb,
+    lasted     timestamp with time zone
+);
+
+-- Create an ext_log table
+CREATE TABLE IF NOT EXISTS ext_log
+(
+    id     BIGSERIAL,
+    name   varchar(64) NOT NULL,
+    action varchar(20) NOT NULL,
+    time   varchar(30) NOT NULL,
+    result varchar(20) NOT NULL,
+    PRIMARY KEY (id)
+);
+
+-- Create a role table
+CREATE TABLE IF NOT EXISTS role
+(
+    id         integer NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
+    role_name  varchar(128),
+    created_at timestamp without time zone,
+    CONSTRAINT role_primary_key PRIMARY KEY (id)
+);
+
+-- Create a user_authorization table
+CREATE TABLE IF NOT EXISTS user_authorization
+(
+    id        serial primary key,
+    username  varchar(128) NOT NULL UNIQUE,
+    auth      varchar(8192) DEFAULT NULL,
+    root      INTEGER       DEFAULT 0,
+    role_id   INTEGER,
+    user_type varchar(32),
+    FOREIGN KEY (role_id) REFERENCES role (id)
+);
+
+-- Create a operation_log table
+CREATE TABLE IF NOT EXISTS operation_log
+(
+    id        serial primary key,
+    username  varchar(128) NOT NULL,
+    client_ip varchar(64)  NOT NULL,
+    time      varchar(30)  NOT NULL,
+    level     varchar(20),
+    module    varchar(20),
+    operation varchar(1024)
+);
+
+-- Create a schedule_backup_all table
+CREATE TABLE IF NOT EXISTS schedule_backup_all
+(
+    task_name   varchar(128) NOT NULL UNIQUE,
+    device_name varchar(128) NOT NULL,
+    interval    INTEGER      NOT NULL,
+    unit        varchar(10)  NOT NULL
+);
+
+-- Create a log_settings table
+CREATE TABLE IF NOT EXISTS log_settings
+(
+    enable integer default 1,
+    level  INTEGER default 2
+);
+
+-- Create a log_host table
+CREATE TABLE IF NOT EXISTS log_host
+(
+    host     varchar(128),
+    port     integer NOT NULL,
+    protocol integer default 1,
+    PRIMARY KEY (host, port, protocol)
+);
+
+-- Create a config_template table
+CREATE TABLE IF NOT EXISTS config_template
+(
+    key           varchar(128) primary key,
+    default_value varchar(128) NOT NULL,
+    description   varchar(256)
+);
+
+-- Create a device_config_template table
+CREATE TABLE IF NOT EXISTS device_config_template
+(
+    id        serial      NOT NULL,
+    device_id varchar(64) NOT NULL,
+    key       varchar(64) NOT NULL,
+    value     varchar(64) NOT NULL,
+    PRIMARY KEY (id)
+);
+
+-- Create a role_device_group table
+CREATE TABLE IF NOT EXISTS role_device_group
+(
+    id                serial primary key,
+    role_id           integer,
+    device_group_name varchar(128),
+    created_at        timestamp without time zone,
+    FOREIGN KEY (role_id) REFERENCES role (id) ON DELETE CASCADE,
+    FOREIGN KEY (device_group_name) REFERENCES device_group (name) ON DELETE CASCADE
+);
+
+-- Create a backup_schedule table
+CREATE TABLE IF NOT EXISTS backup_schedule
+(
+    id               SERIAL PRIMARY KEY,
+    frequency        varchar(64) NOT NULL,
+    time             TIME        NOT NULL,
+    day_of_the_week  integer DEFAULT NULL,
+    day_of_the_month integer DEFAULT NULL,
+    month            integer DEFAULT NULL CHECK (month BETWEEN 1 AND 12),
+    destination varchar(64) DEFAULT NULL
+);
+
+-- Create a remote_storage table
+CREATE TABLE IF NOT EXISTS remote_storage
+(
+    id       SERIAL PRIMARY KEY,
+    ip       varchar(64) NOT NULL,
+    username varchar(64) NOT NULL,
+    password varchar(64) NOT NULL,
+    path     varchar(64) DEFAULT NULL
+);
+
+-- Create a backups table
+CREATE TABLE IF NOT EXISTS backups
+(
+    id          SERIAL PRIMARY KEY,
+    filename    varchar(64) NOT NULL,
+    status      varchar(64)          DEFAULT NULL,
+    destination varchar(64)          DEFAULT NULL,
+    time        TIMESTAMP   NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS settings
+(
+    id SERIAL PRIMARY KEY,
+    attribute_name varchar(64) NOT NULL,
+    attribute_value TEXT          DEFAULT NULL,
+    time TIMESTAMP   NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS notification
+(
+    id serial  PRIMARY KEY,
+    name varchar(64) NOT NULL,
+    type varchar(64) NOT NULL,
+    setting varchar(64)          DEFAULT NULL,
+    time        TIMESTAMP   NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+-- Create a adc_vs_ssl_info table
+CREATE TABLE IF NOT EXISTS adc_vs_ssl_info (
+    id BIGSERIAL PRIMARY KEY,
+    device_id VARCHAR(64),
+    device_name VARCHAR(32) NOT NULL,
+    vs_name VARCHAR(512) NOT NULL,
+    vhost_name VARCHAR(512) NOT NULL,
+    cert_type VARCHAR(16) NOT NULL,
+    expiration INT,
+    CONSTRAINT unique_adc_vs_ssl_cert UNIQUE (device_id, device_name, vs_name, vhost_name, cert_type),
+    CONSTRAINT fk_device_id FOREIGN KEY (device_id)
+        REFERENCES device (id) MATCH SIMPLE
+        ON UPDATE NO ACTION
+        ON DELETE NO ACTION
+);
+
+CREATE TABLE IF NOT EXISTS REPORT
+(
+    id serial PRIMARY KEY,
+    name varchar(64) NOT NULL,
+    device_ip varchar(64) DEFAULT NULL,
+    device_type varchar(64) DEFAULT NULL,
+    from_time varchar(64) NOT NULL,
+    to_time varchar(64) DEFAULT NULL,
+    timeout integer DEFAULT 0,
+    subject_type varchar(64) DEFAULT NULL,
+    subject_name varchar(64) DEFAULT NULL,
+    send_to varchar(64) DEFAULT NULL,
+    status integer DEFAULT 0,
+    create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS REPORT_LOG
+(
+    id serial  PRIMARY KEY,
+    report_id integer NOT NULL,
+    subject_type varchar(64) DEFAULT NULL,
+    subject_name varchar(64) DEFAULT NULL,
+    start_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    end_time TIMESTAMP DEFAULT NULL,
+    status integer DEFAULT 0,
+    result TEXT DEFAULT NULL,
+    FOREIGN KEY (report_id) REFERENCES REPORT(id) ON DELETE CASCADE
+);
+
+CREATE TABLE public.host (
+    id               VARCHAR(64)   NOT NULL,
+    name             VARCHAR(32)   NOT NULL,
+    ip               VARCHAR(64)   NOT NULL,
+    restapi_port     INTEGER       NOT NULL DEFAULT 9997,
+    restapi_username VARCHAR(16)   NOT NULL,
+    restapi_password VARCHAR(256)  NOT NULL,
+    connection       VARCHAR(16)   NOT NULL DEFAULT 'Unconnected',
+    version          VARCHAR(8192) DEFAULT NULL,
+    vm_number        INTEGER       NOT NULL DEFAULT 0,
+    cpu_usage        NUMERIC       NOT NULL DEFAULT 0.0,
+    mem_usage        NUMERIC       NOT NULL DEFAULT 0.0,
+    disk_usage       NUMERIC       NOT NULL DEFAULT 0.0,
+    CONSTRAINT host_pkey PRIMARY KEY (id),
+    CONSTRAINT host_name_key UNIQUE (name)
+);
+-- psql -U amp_admin -d cm -f /path/to/your/init_db.sql
\ No newline at end of file
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	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/setup/configure_opensearch.sh	(working copy)
@@ -0,0 +1,86 @@
+#!/bin/sh
+# configure_opensearch.sh
+# Waits for OpenSearch to be ready, then applies security roles/mappings
+
+OPENSEARCH_URL="https://opensearch-node1:9200"
+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"
+  sleep 5
+done
+
+log "OpenSearch is UP. Applying configuration..."
+
+# 1. Create jwt_users Role
+curl -s -k -X PUT "$OPENSEARCH_URL/_plugins/_security/api/roles/jwt_users" \
+  --cert /usr/share/opensearch/config/certs/admin.pem \
+  --key /usr/share/opensearch/config/certs/admin-key.pem \
+  -H "Content-Type: application/json" \
+  -d '{
+    "cluster_permissions": ["cluster:monitor/*"],
+    "index_permissions": [
+      {
+        "index_patterns": ["*"],
+        "allowed_actions": ["read"]
+      }
+    ],
+    "tenant_permissions": [
+      {
+        "tenant_patterns": ["*"],
+        "allowed_actions": ["kibana_all_write"]
+      }
+    ]
+  }'
+log "Role 'jwt_users' created."
+
+# 2. Create roles mapping for jwt_users
+curl -s -k -X PUT "$OPENSEARCH_URL/_plugins/_security/api/rolesmapping/jwt_users" \
+  --cert /usr/share/opensearch/config/certs/admin.pem \
+  --key /usr/share/opensearch/config/certs/admin-key.pem \
+  -H "Content-Type: application/json" \
+  -d '{
+    "users": ["admin"],
+    "backend_roles": ["jwt_users"] 
+  }'
+log "Role mapping 'jwt_users' updated."
+
+log "Applying Index Template..."
+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")
+  fi
+  log "Dashboards not ready yet (Current: $CURRENT_STATE). Raw response: $STATUS_RES"
+  log "Sleeping 5s..."
+  sleep 5
+done
+
+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" \
+  -H "osd-xsrf: true" \
+  --form file=@/usr/share/opensearch/config/export.ndjson)
+
+log "Import finished. HTTP Response: $HTTP_CODE"
+if [ "$HTTP_CODE" -eq 200 ]; then
+  log "Saved Objects imported successfully."
+  cat /tmp/import_res.json
+else
+  log "❌ Saved Objects import FAILED with code $HTTP_CODE"
+  cat /tmp/import_res.json
+fi
+
+log "Configuration Complete!"
+log "Exiting."
Index: /branches/amp_4_0/platform/tools/container/services/setup/generate_certs.sh
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/setup/generate_certs.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/setup/generate_certs.sh	(working copy)
@@ -0,0 +1,65 @@
+#!/bin/sh
+
+# Script to generate OpenSearch certificates
+# Usage: ./generate_certs.sh <output_dir>
+
+OUTPUT_DIR=${1:-/certs}
+mkdir -p "$OUTPUT_DIR"
+
+# CA Config
+CA_KEY="$OUTPUT_DIR/root-ca-key.pem"
+CA_CERT="$OUTPUT_DIR/root-ca.pem"
+CA_DN="/C=IN/ST=Karnataka/L=Bengaluru/O=OpenSearchSelfSignedCA/CN=OpenSearchCA"
+
+# Node Config
+NODE_KEY="$OUTPUT_DIR/node-key.pem"
+NODE_CERT="$OUTPUT_DIR/node.pem"
+NODE_DN="/C=IN/ST=Karnataka/L=Bengaluru/O=OpenSearchNode/CN=node-1"
+SAN_HOSTS="DNS:localhost,IP:127.0.0.1,DNS:opensearch-node1,DNS:opensearch-dashboards"
+
+# Admin Config
+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"
+
+log() { echo "[$(date -Iseconds)] $1"; }
+
+if [ -f "$CA_CERT" ]; then
+    log "Certificates already exist in $OUTPUT_DIR. Skipping generation."
+    # Ensure correct permissions even if skipping
+    chmod 755 "$OUTPUT_DIR"
+    chmod 644 "$OUTPUT_DIR"/*.pem 2>/dev/null
+    chmod 640 "$OUTPUT_DIR"/*-key.pem 2>/dev/null
+    exit 0
+fi
+
+log "Generating CA..."
+openssl genrsa -out "$CA_KEY" 2048
+openssl req -new -x509 -key "$CA_KEY" -out "$CA_CERT" -days 3650 -subj "$CA_DN"
+
+log "Generating Node Cert..."
+openssl genrsa -out "$NODE_KEY" 2048
+openssl req -new -key "$NODE_KEY" -out "$OUTPUT_DIR/node.csr" -subj "$NODE_DN"
+
+# Create SAN extension file properly
+echo "subjectAltName=${SAN_HOSTS}" > "$OUTPUT_DIR/node_san.cnf"
+
+openssl x509 -req -in "$OUTPUT_DIR/node.csr" -CA "$CA_CERT" -CAkey "$CA_KEY" -CAcreateserial -out "$NODE_CERT" -days 3650 -extfile "$OUTPUT_DIR/node_san.cnf"
+
+log "Generating Admin Cert..."
+openssl genrsa -out "$ADMIN_KEY" 2048
+openssl req -new -key "$ADMIN_KEY" -out "$OUTPUT_DIR/admin.csr" -subj "$ADMIN_DN"
+openssl x509 -req -in "$OUTPUT_DIR/admin.csr" -CA "$CA_CERT" -CAkey "$CA_KEY" -CAcreateserial -out "$ADMIN_CERT" -days 3650
+
+# Cleanup
+rm -f "$OUTPUT_DIR"/*.csr "$OUTPUT_DIR"/*.cnf "$OUTPUT_DIR"/*.srl
+
+# Permissions (Readable by group 0/1000 for containers)
+chmod 755 "$OUTPUT_DIR"
+chmod 644 "$OUTPUT_DIR"/*.pem
+chmod 640 "$OUTPUT_DIR"/*-key.pem
+# In a real scenario you might want specific ownership, but for docker bind mounts 
+# often we rely on the container user having read access.
+# OpenSearch container runs as uid 1000 usually.
+
+log "Certificates generated successfully."
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	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/setup/install_curl.sh	(working copy)
@@ -0,0 +1,29 @@
+#!/bin/bash
+# install_curl.sh
+# Installs curl, resolving potential conflicts with curl-minimal on Rocky Linux.
+
+set -e
+
+echo "--- Installing curl ---"
+
+if [ -f /etc/os-release ]; then
+    . /etc/os-release
+    if [[ "$ID" == "rocky" || "$ID" == "rhel" || "$ID" == "almalinux" || "$ID" == "centos" || "$ID" == "fedora" ]]; then
+        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
+    elif [[ "$ID" == "debian" || "$ID" == "ubuntu" ]]; then
+        echo "Detected Debian-based distribution: $ID"
+        apt-get update && apt-get install -y curl
+    else
+        echo "⚠️  Unsupported distribution: $ID"
+        exit 1
+    fi
+else
+    echo "⚠️  Cannot detect OS."
+    exit 1
+fi
+
+echo "✅ curl installation complete."
+curl --version
Index: /branches/amp_4_0/platform/tools/container/services/setup/install_java.sh
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/setup/install_java.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/setup/install_java.sh	(working copy)
@@ -0,0 +1,100 @@
+#!/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..."
+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..."
+    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
+    sed -i '/^export JAVA_HOME=.*openjdk-21/,+2d' "${PROFILE_SCRIPT}" 2>/dev/null
+    sed -i '/^export PATH=.*\$JAVA_HOME\/bin/,d' "${PROFILE_SCRIPT}" 2>/dev/null
+    sed -i '/^export JDK_HOME=.*\$JAVA_HOME/,d' "${PROFILE_SCRIPT}" 2>/dev/null
+
+    echo "export JAVA_HOME=${JAVA_HOME_PATH}" | tee "${PROFILE_SCRIPT}" > /dev/null
+    echo "export PATH=\$PATH:\$JAVA_HOME/bin" | tee -a "${PROFILE_SCRIPT}" > /dev/null
+    echo "export JDK_HOME=\$JAVA_HOME" | 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/container/services/setup/install_python.sh
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/setup/install_python.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/setup/install_python.sh	(working copy)
@@ -0,0 +1,104 @@
+#!/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/container/services/setup/setup.sh
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/setup/setup.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/setup/setup.sh	(working copy)
@@ -0,0 +1,127 @@
+#!/bin/sh
+
+# setup.sh - Generates Certs and Configs
+
+OUTPUT_DIR=${1:-/certs}
+CONFIG_DIR=${2:-/security-config}
+
+mkdir -p "$OUTPUT_DIR"
+mkdir -p "$CONFIG_DIR"
+
+# --- 1. Cert Generation (Idempotent) ---
+CA_KEY="$OUTPUT_DIR/root-ca-key.pem"
+CA_CERT="$OUTPUT_DIR/root-ca.pem"
+CA_DN="/C=IN/ST=Karnataka/L=Bengaluru/O=OpenSearchSelfSignedCA/CN=OpenSearchCA"
+NODE_KEY="$OUTPUT_DIR/node-key.pem"
+NODE_CERT="$OUTPUT_DIR/node.pem"
+NODE_DN="/C=IN/ST=Karnataka/L=Bengaluru/O=OpenSearchNode/CN=node-1"
+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"
+
+log() { echo "[$(date -Iseconds)] $1"; }
+
+if [ ! -f "$CA_CERT" ]; then
+    log "Generating CA..."
+    openssl genrsa -out "$CA_KEY" 2048
+    openssl req -new -x509 -key "$CA_KEY" -out "$CA_CERT" -days 3650 -subj "$CA_DN"
+
+    log "Generating Node Cert..."
+    openssl genrsa -out "$NODE_KEY" 2048
+    openssl req -new -key "$NODE_KEY" -out "$OUTPUT_DIR/node.csr" -subj "$NODE_DN"
+    echo "subjectAltName=${SAN_HOSTS}" > "$OUTPUT_DIR/node_san.cnf"
+    openssl x509 -req -in "$OUTPUT_DIR/node.csr" -CA "$CA_CERT" -CAkey "$CA_KEY" -CAcreateserial -out "$NODE_CERT" -days 3650 -extfile "$OUTPUT_DIR/node_san.cnf"
+
+    log "Generating Admin Cert..."
+    openssl genrsa -out "$ADMIN_KEY" 2048
+    openssl req -new -key "$ADMIN_KEY" -out "$OUTPUT_DIR/admin.csr" -subj "$ADMIN_DN"
+    openssl x509 -req -in "$OUTPUT_DIR/admin.csr" -CA "$CA_CERT" -CAkey "$CA_KEY" -CAcreateserial -out "$ADMIN_CERT" -days 3650
+
+    # Cleanup & Perms
+    rm -f "$OUTPUT_DIR"/*.csr "$OUTPUT_DIR"/*.cnf "$OUTPUT_DIR"/*.srl
+    chmod 755 "$OUTPUT_DIR"
+    chmod 600 "$OUTPUT_DIR"/*.pem
+    chmod 600 "$OUTPUT_DIR"/*-key.pem
+    
+    # Critical: Change ownership to 1000:1000 so OpenSearch container (non-root) can access
+    # Since we are likely running as root or a different user in setup, we force the ownership.
+    chown -R 1000:1000 "$OUTPUT_DIR"
+    chown -R 1000:1000 "$CONFIG_DIR"
+else
+    log "Certificates exist."
+fi
+
+# --- 2. Config Generation (config.yml) ---
+CONFIG_FILE="$CONFIG_DIR/config.yml"
+log "Generating config.yml with JWT secret..."
+
+cat > "$CONFIG_FILE" <<EOF
+_meta:
+  type: "config"
+  config_version: 2
+
+config:
+  dynamic:
+    http:
+      anonymous_auth_enabled: false
+    authc:
+      jwt_auth_domain:
+        http_enabled: true
+        transport_enabled: false
+        order: 0
+        http_authenticator:
+          type: jwt
+          challenge: false
+          config:
+            signing_key: "$(echo -n "${OPENSEARCH_JWT_SECRET}" | base64)"
+            jwt_header: "Authorization"
+            jwt_url_parameter: "access_token"
+            roles_key: "roles"
+            subject_key: "sub"
+            issuer: "amp.com"
+        authentication_backend:
+          type: noop
+
+      basic_internal_auth_domain:
+        description: "Authenticate via HTTP Basic against internal users database"
+        http_enabled: true
+        transport_enabled: true
+        order: 1
+        http_authenticator:
+          type: basic
+          challenge: true
+        authentication_backend:
+          type: intern
+EOF
+
+# --- 3. Internal Users Generation (internal_users.yml) ---
+USERS_FILE="$CONFIG_DIR/internal_users.yml"
+ADMIN_PASS="${OPENSEARCH_INITIAL_ADMIN_PASSWORD:-Arr@y2050}"
+log "Generating internal_users.yml with custom admin password..."
+
+# OpenSearch uses BCrypt hashes. htpasswd -B -C 12 generates this.
+# We strip the username part (admin:) to just get the hash.
+HASH=$(htpasswd -b -n -B -C 12 admin "$ADMIN_PASS" | cut -d ":" -f 2)
+
+cat > "$USERS_FILE" <<EOF
+_meta:
+  type: "internalusers"
+  config_version: 2
+
+admin:
+  hash: "$HASH"
+  reserved: true
+  backend_roles:
+  - "admin"
+  description: "Admin user"
+
+kibanaserver:
+  hash: "\$2y\$12\$4XM548fXZrKKqwBj9IURxcKy.4364rUPH.0J.MvmqOMO.hQ.J0W.i"
+  reserved: true
+  description: "Kibana server user"
+EOF
+
+
+chmod 644 "$CONFIG_FILE"
+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	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/telegraf/telegraf.conf	(working copy)
@@ -0,0 +1,178 @@
+[agent]
+  interval = "10s"
+  round_interval = true
+  metric_batch_size = 10000
+  metric_buffer_limit = 100000
+  collection_jitter = "0s"
+  flush_interval = "10s"
+  precision = ""
+  hostname = "amp-docker"
+  logfile = "/var/log/telegraf/telegraf.log"
+  debug = true
+  quiet = false
+  omit_hostname = false
+  skip_processors_after_aggregators = false
+
+# === Output: TimescaleDB/PostgreSQL ===
+[[outputs.postgresql]]
+  connection = "host=timescaledb port=5432 user=postgres password=$PG_PASSWORD dbname=postgres sslmode=disable"
+  schema     = "public"
+  # Route metrics to Timescale, configured to drop logs
+  namedrop = ["docker_log"]
+
+# === Output: Logstash (Socket Writer) ===
+# [[outputs.socket_writer]]
+#   address = "tcp://logstash:5514"
+#   # Route logs/events only to Logstash
+#   namepass = ["docker_log"]
+#   data_format = "json"
+
+# === Inputs ===
+[[inputs.cpu]]
+  percpu = true
+  totalcpu = true
+[[inputs.disk]]
+  mount_points = ["/"]
+  ignore_fs = ["tmpfs", "devtmpfs", "overlay", "rootfs"]
+[[inputs.mem]]
+[[inputs.diskio]]
+[[inputs.net]]
+
+[[inputs.system]]
+  fieldinclude = ["load1", "load5", "load15", "uptime"]
+
+# Using Docker input for container stats (Metrics -> TimescaleDB)
+[[inputs.docker]]
+  endpoint = "unix:///var/run/docker.sock"
+  gather_services = false
+  timeout = "5s"
+
+  docker_label_include = []
+  tag_env = ["JAVA_HOME", "HEAP_SIZE"]
+
+# Using Docker logs (Logs -> Logstash)
+[[inputs.docker_log]]
+  endpoint = "unix:///var/run/docker.sock"
+  container_name_include = [] # All containers
+  timeout = "5s"
+
+# === Processors (Ported from configure_telegraf_timescale.sh) ===
+
+[[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
+'''
+
Index: /branches/amp_4_0/platform/tools/container/services/telegraf/telegraf.d/ag.toml
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/telegraf/telegraf.d/ag.toml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/telegraf/telegraf.d/ag.toml	(working copy)
@@ -0,0 +1,166 @@
+[[inputs.snmp]]
+agents = []
+community = "public"
+name = "an_device_metrics"
+timeout = "2s"
+
+[[inputs.snmp.field]]
+name = "cpu_usage"
+oid = ".1.3.6.1.4.1.7564.30.1.0"
+
+[[inputs.snmp.field]]
+name = "net_mem_usage"
+oid = ".1.3.6.1.4.1.7564.30.4.0"
+
+[[inputs.snmp.field]]
+name = "total_openssl_conns"
+oid = ".1.3.6.1.4.1.7564.20.2.1.0"
+
+[[inputs.snmp.field]]
+name = "connections"
+oid = ".1.3.6.1.4.1.7564.30.2.0"
+
+[[inputs.snmp.field]]
+name = "requests"
+oid = ".1.3.6.1.4.1.7564.30.3.0"
+
+[[inputs.snmp.field]]
+name = "total_in"
+oid = ".1.3.6.1.4.1.7564.23.2.0"
+
+[[inputs.snmp.field]]
+name = "total_out"
+oid = ".1.3.6.1.4.1.7564.23.3.0"
+
+[[inputs.snmp.table]]
+name = "ag_virtual_site_stats"
+
+[[inputs.snmp.table.field]]
+is_tag = true
+name = "id"
+oid = ".1.3.6.1.4.1.7564.31.1.2.1.2"
+
+[[inputs.snmp.table.field]]
+is_tag = true
+name = "ip"
+oid = ".1.3.6.1.4.1.7564.31.1.2.1.17"
+
+[[inputs.snmp.table.field]]
+name = "active_sessions"
+oid = ".1.3.6.1.4.1.7564.31.1.2.1.3"
+
+[[inputs.snmp.table.field]]
+name = "success_login"
+oid = ".1.3.6.1.4.1.7564.31.1.2.1.4"
+
+[[inputs.snmp.table.field]]
+name = "failure_login"
+oid = ".1.3.6.1.4.1.7564.31.1.2.1.5"
+
+[[inputs.snmp.table.field]]
+name = "error_login"
+oid = ".1.3.6.1.4.1.7564.31.1.2.1.6"
+
+[[inputs.snmp.table.field]]
+name = "success_logout"
+oid = ".1.3.6.1.4.1.7564.31.1.2.1.7"
+
+[[inputs.snmp.table.field]]
+name = "client_bytes_in"
+oid = ".1.3.6.1.4.1.7564.31.1.2.1.8"
+
+[[inputs.snmp.table.field]]
+name = "client_bytes_out"
+oid = ".1.3.6.1.4.1.7564.31.1.2.1.9"
+
+[[inputs.snmp.table.field]]
+name = "locked_login"
+oid = ".1.3.6.1.4.1.7564.31.1.2.1.15"
+
+[[inputs.snmp.table.field]]
+name = "rejected_login"
+oid = ".1.3.6.1.4.1.7564.31.1.2.1.16"
+
+[[inputs.snmp.table.field]]
+name = "server_bytes_in"
+oid = ".1.3.6.1.4.1.7564.31.1.2.1.19"
+
+[[inputs.snmp.table.field]]
+name = "server_bytes_out"
+oid = ".1.3.6.1.4.1.7564.31.1.2.1.20"
+
+[[inputs.snmp.table]]
+name = "ag_vpn_stats"
+
+[[inputs.snmp.table.field]]
+is_tag = true
+name = "id"
+oid = ".1.3.6.1.4.1.7564.32.1.2.1.2"
+
+[[inputs.snmp.table.field]]
+name = "tunnels_open"
+oid = ".1.3.6.1.4.1.7564.32.1.2.1.3"
+
+[[inputs.snmp.table.field]]
+name = "tunnels_est"
+oid = ".1.3.6.1.4.1.7564.32.1.2.1.4"
+
+[[inputs.snmp.table.field]]
+name = "tunnels_rejected"
+oid = ".1.3.6.1.4.1.7564.32.1.2.1.5"
+
+[[inputs.snmp.table.field]]
+name = "tunnels_terminated"
+oid = ".1.3.6.1.4.1.7564.32.1.2.1.6"
+
+[[inputs.snmp.table.field]]
+name = "bytes_in"
+oid = ".1.3.6.1.4.1.7564.32.1.2.1.7"
+
+[[inputs.snmp.table.field]]
+name = "bytes_out"
+oid = ".1.3.6.1.4.1.7564.32.1.2.1.8"
+
+[[inputs.snmp.table.field]]
+name = "unauth_packets_in"
+oid = ".1.3.6.1.4.1.7564.32.1.2.1.9"
+
+[[inputs.snmp.table.field]]
+name = "client_app_bytes_in"
+oid = ".1.3.6.1.4.1.7564.32.1.2.1.10"
+
+[[inputs.snmp.table.field]]
+name = "client_app_bytes_out"
+oid = ".1.3.6.1.4.1.7564.32.1.2.1.11"
+
+[[inputs.snmp.table]]
+name = "ag_web_stats"
+
+[[inputs.snmp.table.field]]
+is_tag = true
+name = "id"
+oid = ".1.3.6.1.4.1.7564.33.1.2.1.2"
+
+[[inputs.snmp.table.field]]
+name = "authorized_req"
+oid = ".1.3.6.1.4.1.7564.33.1.2.1.3"
+
+[[inputs.snmp.table.field]]
+name = "unauthorized_req"
+oid = ".1.3.6.1.4.1.7564.33.1.2.1.4"
+
+[[inputs.snmp.table.field]]
+name = "client_bytes_in"
+oid = ".1.3.6.1.4.1.7564.33.1.2.1.5"
+
+[[inputs.snmp.table.field]]
+name = "client_bytes_out"
+oid = ".1.3.6.1.4.1.7564.33.1.2.1.6"
+
+[[inputs.snmp.table.field]]
+name = "server_bytes_in"
+oid = ".1.3.6.1.4.1.7564.33.1.2.1.7"
+
+[[inputs.snmp.table.field]]
+name = "server_bytes_out"
+oid = ".1.3.6.1.4.1.7564.33.1.2.1.8"
Index: /branches/amp_4_0/platform/tools/container/services/telegraf/telegraf.d/apv.toml
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/telegraf/telegraf.d/apv.toml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/telegraf/telegraf.d/apv.toml	(working copy)
@@ -0,0 +1,332 @@
+[[inputs.snmp]]
+agents = []
+community = "public"
+name = "an_device_metrics"
+timeout = "2s"
+
+[[inputs.snmp.field]]
+name = "cpu_usage"
+oid = ".1.3.6.1.4.1.7564.30.1.0"
+
+[[inputs.snmp.field]]
+name = "mem_usage"
+oid = ".1.3.6.1.4.1.7564.4.5.0"
+
+[[inputs.snmp.field]]
+name = "net_mem_usage"
+oid = ".1.3.6.1.4.1.7564.4.2.0"
+
+[[inputs.snmp.field]]
+name = "total_openssl_conns"
+oid = ".1.3.6.1.4.1.7564.20.2.1.0"
+
+[[inputs.snmp.field]]
+name = "connections"
+oid = ".1.3.6.1.4.1.7564.30.2.0"
+
+[[inputs.snmp.field]]
+name = "requests"
+oid = ".1.3.6.1.4.1.7564.30.3.0"
+
+[[inputs.snmp.field]]
+name = "total_in"
+oid = ".1.3.6.1.4.1.7564.23.2.0"
+
+[[inputs.snmp.field]]
+name = "total_out"
+oid = ".1.3.6.1.4.1.7564.23.3.0"
+
+
+[[inputs.snmp]]
+agents = []
+community = "public"
+name = "an_device_performance"
+timeout = "2s"
+
+[[inputs.snmp.field]]
+name = "ssl_ae_core_utilization"
+oid = ".1.3.6.1.4.1.7564.30.9.0"
+
+[[inputs.snmp.field]]
+name = "ssl_se_core_utilization"
+oid = ".1.3.6.1.4.1.7564.30.10.0"
+
+
+[[inputs.snmp.table]]
+name = "an_device_storage"
+
+[[inputs.snmp.table.field]]
+is_tag = true
+name = "prefix"
+oid = ".1.3.6.1.2.1.25.2.3.1.3"
+
+[[inputs.snmp.table.field]]
+name = "size"
+oid = ".1.3.6.1.2.1.25.2.3.1.5"
+
+[[inputs.snmp.table.field]]
+name = "used"
+oid = ".1.3.6.1.2.1.25.2.3.1.6"
+
+
+[[inputs.snmp.table]]
+name = "apv_virtual_stats"
+
+[[inputs.snmp.table.field]]
+is_tag = true
+name = "serverid"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.2"
+
+[[inputs.snmp.table.field]]
+is_tag = true
+name = "addr"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.3"
+
+[[inputs.snmp.table.field]]
+is_tag = true
+name = "port"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.4"
+
+[[inputs.snmp.table.field]]
+is_tag = true
+name = "protocol"
+oid = ".1.3.6.1.4.1.7564.19.1.2.2.1.3"
+
+[[inputs.snmp.table.field]]
+is_tag = true
+name = "health_status"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.36"
+
+[[inputs.snmp.table.field]]
+name = "url_hits"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.5"
+
+[[inputs.snmp.table.field]]
+name = "hostname_hits"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.6"
+
+[[inputs.snmp.table.field]]
+name = "perstnt_cookie_hits"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.7"
+
+[[inputs.snmp.table.field]]
+name = "qos_cookie_hits"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.8"
+
+[[inputs.snmp.table.field]]
+name = "default_hits"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.9"
+
+[[inputs.snmp.table.field]]
+name = "perstnt_url_hits"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.10"
+
+[[inputs.snmp.table.field]]
+name = "static_hits"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.11"
+
+[[inputs.snmp.table.field]]
+name = "qos_network_hits"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.12"
+
+[[inputs.snmp.table.field]]
+name = "qos_url_hits"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.13"
+
+[[inputs.snmp.table.field]]
+name = "backup__hits"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.14"
+
+[[inputs.snmp.table.field]]
+name = "cache_hits"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.15"
+
+[[inputs.snmp.table.field]]
+name = "regex_hits"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.16"
+
+[[inputs.snmp.table.field]]
+name = "rcookie_hits"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.17"
+
+[[inputs.snmp.table.field]]
+name = "icookie_hits"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.18"
+
+[[inputs.snmp.table.field]]
+name = "conn_cnt"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.19"
+
+[[inputs.snmp.table.field]]
+name = "qos_client_port_hits"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.22"
+
+[[inputs.snmp.table.field]]
+name = "qos_body_hits"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.23"
+
+[[inputs.snmp.table.field]]
+name = "header_hits"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.24"
+
+[[inputs.snmp.table.field]]
+name = "hashurl_hits"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.25"
+
+[[inputs.snmp.table.field]]
+name = "redirect_hits"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.26"
+
+[[inputs.snmp.table.field]]
+name = "connpersec"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.31"
+
+[[inputs.snmp.table.field]]
+name = "in_byte_per_sec"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.32"
+
+[[inputs.snmp.table.field]]
+name = "out_byte_per_sec"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.33"
+
+[[inputs.snmp.table.field]]
+name = "in_packet_per_sec"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.34"
+
+[[inputs.snmp.table.field]]
+name = "out_packet_per_sec"
+oid = ".1.3.6.1.4.1.7564.19.2.2.1.1.35"
+
+
+# --- apv_real_stats table ---
+[[inputs.snmp.table]]
+name = "apv_real_stats"
+
+[[inputs.snmp.table.field]]
+is_tag = true
+name = "index"
+oid = ".1.3.6.1.4.1.7564.19.2.1.1.1.1"
+
+[[inputs.snmp.table.field]]
+is_tag = true
+name = "real_server_id"
+oid = ".1.3.6.1.4.1.7564.19.2.1.1.1.2"
+
+[[inputs.snmp.table.field]]
+is_tag = true
+name = "addr"
+oid = ".1.3.6.1.4.1.7564.19.2.1.1.1.3"
+
+[[inputs.snmp.table.field]]
+is_tag = true
+name = "port"
+oid = ".1.3.6.1.4.1.7564.19.2.1.1.1.4"
+
+[[inputs.snmp.table.field]]
+is_tag = true
+name = "protocol"
+oid = ".1.3.6.1.4.1.7564.19.1.1.2.1.3"
+
+[[inputs.snmp.table.field]]
+is_tag = true
+name = "status"
+oid = ".1.3.6.1.4.1.7564.19.2.1.1.1.8"
+
+[[inputs.snmp.table.field]]
+name = "rs_cnt_of_req"
+oid = ".1.3.6.1.4.1.7564.19.2.1.1.1.5"
+
+[[inputs.snmp.table.field]]
+name = "rs_conn_cnt"
+oid = ".1.3.6.1.4.1.7564.19.2.1.1.1.6"
+
+[[inputs.snmp.table.field]]
+name = "rs_total__hits"
+oid = ".1.3.6.1.4.1.7564.19.2.1.1.1.7"
+
+[[inputs.snmp.table.field]]
+name = "rs_conn_per_sec"
+oid = ".1.3.6.1.4.1.7564.19.2.1.1.1.13"
+
+[[inputs.snmp.table.field]]
+name = "rs_in_byte_per_sec"
+oid = ".1.3.6.1.4.1.7564.19.2.1.1.1.14"
+
+[[inputs.snmp.table.field]]
+name = "rs_out_byte_per_sec"
+oid = ".1.3.6.1.4.1.7564.19.2.1.1.1.15"
+
+[[inputs.snmp.table.field]]
+name = "rs_in_packet_per_sec"
+oid = ".1.3.6.1.4.1.7564.19.2.1.1.1.16"
+
+[[inputs.snmp.table.field]]
+name = "rs_out_packet_per_sec"
+oid = ".1.3.6.1.4.1.7564.19.2.1.1.1.17"
+
+
+# --- apv_llb_stats table ---
+[[inputs.snmp.table]]
+name = "apv_llb_stats"
+
+[[inputs.snmp.table.field]]
+name = "link_index"
+oid = ".1.3.6.1.4.1.7564.34.2.1.2.1.1"
+
+[[inputs.snmp.table.field]]
+is_tag = true
+name = "link_name"
+oid = ".1.3.6.1.4.1.7564.34.2.1.2.1.2"
+
+[[inputs.snmp.table.field]]
+name = "link_gateway"
+oid = ".1.3.6.1.4.1.7564.34.2.1.2.1.3"
+
+[[inputs.snmp.table.field]]
+is_tag = true
+name = "link_status"
+oid = ".1.3.6.1.4.1.7564.34.2.1.2.1.4"
+
+[[inputs.snmp.table.field]]
+name = "link_resp_time"
+oid = ".1.3.6.1.4.1.7564.34.2.1.2.1.5"
+
+[[inputs.snmp.table.field]]
+name = "link_up_time"
+oid = ".1.3.6.1.4.1.7564.34.2.1.2.1.6"
+
+[[inputs.snmp.table.field]]
+name = "link_down_time"
+oid = ".1.3.6.1.4.1.7564.34.2.1.2.1.7"
+
+[[inputs.snmp.table.field]]
+name = "link_down_count"
+oid = ".1.3.6.1.4.1.7564.34.2.1.2.1.8"
+
+[[inputs.snmp.table.field]]
+name = "link_down_event"
+oid = ".1.3.6.1.4.1.7564.34.2.1.2.1.9"
+
+[[inputs.snmp.table.field]]
+name = "link_bandwid_in"
+oid = ".1.3.6.1.4.1.7564.34.2.1.2.1.10"
+
+[[inputs.snmp.table.field]]
+name = "link_bandwid_out"
+oid = ".1.3.6.1.4.1.7564.34.2.1.2.1.11"
+
+[[inputs.snmp.table.field]]
+name = "link_thresh"
+oid = ".1.3.6.1.4.1.7564.34.2.1.2.1.12"
+
+[[inputs.snmp.table.field]]
+name = "link_hits"
+oid = ".1.3.6.1.4.1.7564.34.2.1.2.1.13"
+
+[[inputs.snmp.table.field]]
+name = "link_conn"
+oid = ".1.3.6.1.4.1.7564.34.2.1.2.1.14"
+
+[[inputs.snmp.table.field]]
+name = "link_usage"
+oid = ".1.3.6.1.4.1.7564.34.2.1.2.1.15"
Index: /branches/amp_4_0/platform/tools/container/services/telegraf/telegraf.d/asf.toml
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/telegraf/telegraf.d/asf.toml	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/telegraf/telegraf.d/asf.toml	(working copy)
@@ -0,0 +1,598 @@
+[[inputs.snmp]]
+agents = []
+community = "public"
+name = "an_device_metrics"
+timeout = "2s"
+
+[[inputs.snmp.field]]
+name = "cpu_usage"
+oid = ".1.3.6.1.4.1.7564.30.1.0"
+
+[[inputs.snmp.field]]
+name = "mem_usage"
+oid = ".1.3.6.1.4.1.7564.4.5.0"
+
+[[inputs.snmp.field]]
+name = "net_mem_usage"
+oid = ".1.3.6.1.4.1.7564.4.2.0"
+
+[[inputs.snmp.field]]
+name = "total_openssl_conns"
+oid = ".1.3.6.1.4.1.7564.20.2.1.0"
+
+[[inputs.snmp.field]]
+name = "connections"
+oid = ".1.3.6.1.4.1.7564.30.2.0"
+
+[[inputs.snmp.field]]
+name = "requests"
+oid = ".1.3.6.1.4.1.7564.30.3.0"
+
+[[inputs.snmp.field]]
+name = "total_in"
+oid = ".1.3.6.1.4.1.7564.23.2.0"
+
+[[inputs.snmp.field]]
+name = "total_out"
+oid = ".1.3.6.1.4.1.7564.23.3.0"
+
+[[inputs.snmp.table]]
+name = "an_device_storage"
+
+[[inputs.snmp.table.field]]
+is_tag = true
+name = "prefix"
+oid = ".1.3.6.1.2.1.25.2.3.1.3"
+
+[[inputs.snmp.table.field]]
+name = "size"
+oid = ".1.3.6.1.2.1.25.2.3.1.5"
+
+[[inputs.snmp.table.field]]
+name = "used"
+oid = ".1.3.6.1.2.1.25.2.3.1.6"
+
+[[inputs.snmp.table.field]]
+name = "alloc_unit"
+oid = ".1.3.6.1.2.1.25.2.3.1.4"
+
+[[inputs.snmp.table]]
+name = "asf_ssl_statistics"
+
+[[inputs.snmp.table.field]]
+name = "total_openssl_conns"
+oid = ".1.3.6.1.4.1.7564.20.2.1"
+
+[[inputs.snmp.table.field]]
+name = "total_accepted_conns"
+oid = ".1.3.6.1.4.1.7564.20.2.2"
+
+[[inputs.snmp.table.field]]
+name = "total_requested_conns"
+oid = ".1.3.6.1.4.1.7564.20.2.3"
+
+[[inputs.snmp.table]]
+name = "asf_ssl_host_statistics"
+
+[[inputs.snmp.table.field]]
+name = "ssl_index"
+oid = ".1.3.6.1.4.1.7564.20.2.4.1.1"
+
+[[inputs.snmp.table.field]]
+name = "vhost_name"
+oid = ".1.3.6.1.4.1.7564.20.2.4.1.2"
+
+[[inputs.snmp.table.field]]
+name = "open_ssl_conns"
+oid = ".1.3.6.1.4.1.7564.20.2.4.1.3"
+
+[[inputs.snmp.table.field]]
+name = "accepted_conns"
+oid = ".1.3.6.1.4.1.7564.20.2.4.1.4"
+
+[[inputs.snmp.table.field]]
+name = "requested_conns"
+oid = ".1.3.6.1.4.1.7564.20.2.4.1.5"
+
+[[inputs.snmp.table.field]]
+name = "resumed_sess"
+oid = ".1.3.6.1.4.1.7564.20.2.4.1.6"
+
+[[inputs.snmp.table.field]]
+name = "resumable_sess"
+oid = ".1.3.6.1.4.1.7564.20.2.4.1.7"
+
+[[inputs.snmp.table.field]]
+name = "miss_sess"
+oid = ".1.3.6.1.4.1.7564.20.2.4.1.8"
+
+[[inputs.snmp.table.field]]
+name = "conns_per_sec"
+oid = ".1.3.6.1.4.1.7564.20.2.4.1.9"
+
+[[inputs.snmp.table]]
+name = "asf_vip_group_statistics"
+
+[[inputs.snmp.table.field]]
+name = "vip_status"
+oid = ".1.3.6.1.4.1.7564.22.1"
+
+[[inputs.snmp.table.field]]
+name = "host_name"
+oid = ".1.3.6.1.4.1.7564.22.2"
+
+[[inputs.snmp.table.field]]
+name = "current_tme"
+oid = ".1.3.6.1.4.1.7564.22.3"
+
+[[inputs.snmp.table.field]]
+name = "total_ip_pkts_in"
+oid = ".1.3.6.1.4.1.7564.22.4"
+
+[[inputs.snmp.table.field]]
+name = "total_ip_pkts_out"
+oid = ".1.3.6.1.4.1.7564.22.5"
+
+[[inputs.snmp.table.field]]
+name = "total_ip_bytes_in"
+oid = ".1.3.6.1.4.1.7564.22.6"
+
+[[inputs.snmp.table.field]]
+name = "total_ip_bytes_out"
+oid = ".1.3.6.1.4.1.7564.22.7"
+
+[[inputs.snmp.table]]
+name = "asf_vip_statistics"
+
+[[inputs.snmp.table.field]]
+name = "ip_index"
+oid = ".1.3.6.1.4.1.7564.22.8.1.1"
+
+[[inputs.snmp.table.field]]
+name = "ip_address"
+oid = ".1.3.6.1.4.1.7564.22.8.1.2"
+
+[[inputs.snmp.table.field]]
+name = "ip_pkts_in"
+oid = ".1.3.6.1.4.1.7564.22.8.1.3"
+
+[[inputs.snmp.table.field]]
+name = "ip_bytes_in"
+oid = ".1.3.6.1.4.1.7564.22.8.1.4"
+
+[[inputs.snmp.table.field]]
+name = "ip_pkts_out"
+oid = ".1.3.6.1.4.1.7564.22.8.1.5"
+
+[[inputs.snmp.table.field]]
+name = "ip_bytes_out"
+oid = ".1.3.6.1.4.1.7564.22.8.1.6"
+
+[[inputs.snmp.table.field]]
+name = "start_time"
+oid = ".1.3.6.1.4.1.7564.22.8.1.7"
+
+[[inputs.snmp.table.field]]
+name = "ip_addr_type"
+oid = ".1.3.6.1.4.1.7564.22.8.1.8"
+
+[[inputs.snmp.table]]
+name = "asf_performance_statistics"
+
+[[inputs.snmp.table.field]]
+name = "cpu_utilization"
+oid = ".1.3.6.1.4.1.7564.30.1"
+
+[[inputs.snmp.table.field]]
+name = "connections_per_sec"
+oid = ".1.3.6.1.4.1.7564.30.2"
+
+[[inputs.snmp.table.field]]
+name = "requests_per_sec"
+oid = ".1.3.6.1.4.1.7564.30.3"
+
+[[inputs.snmp.table.field]]
+name = "ssl_core_utilization"
+oid = ".1.3.6.1.4.1.7564.30.4"
+
+[[inputs.snmp.table.field]]
+name = "ssl_ae_core_utilization"
+oid = ".1.3.6.1.4.1.7564.30.5"
+
+[[inputs.snmp.table.field]]
+name = "ssl_se_core_utilization"
+oid = ".1.3.6.1.4.1.7564.30.6"
+
+[[inputs.snmp.table]]
+name = "asf_http_service"
+
+[[inputs.snmp.table.field]]
+name = "http_service_index"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.1"
+
+[[inputs.snmp.table.field]]
+name = "http_service_id"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.2"
+
+[[inputs.snmp.table.field]]
+name = "http_service_cc"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.3"
+
+[[inputs.snmp.table.field]]
+name = "http_service_cps"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.4"
+
+[[inputs.snmp.table.field]]
+name = "http_service_rps_get"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.5"
+
+[[inputs.snmp.table.field]]
+name = "http_service_rps_post"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.6"
+
+[[inputs.snmp.table.field]]
+name = "http_service_rps_head"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.7"
+
+[[inputs.snmp.table.field]]
+name = "http_service_rps_put"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.8"
+
+[[inputs.snmp.table.field]]
+name = "http_service_rps_delete"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.9"
+
+[[inputs.snmp.table.field]]
+name = "http_service_rps_total"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.10"
+
+[[inputs.snmp.table.field]]
+name = "http_service_anomaly_method"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.11"
+
+[[inputs.snmp.table.field]]
+name = "http_service_anomaly_requestline"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.12"
+
+[[inputs.snmp.table.field]]
+name = "http_service_anomaly_host"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.13"
+
+[[inputs.snmp.table.field]]
+name = "http_service_anomaly_connection"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.14"
+
+[[inputs.snmp.table.field]]
+name = "http_service_anomaly_contentlength"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.15"
+
+[[inputs.snmp.table.field]]
+name = "http_service_anomaly_range"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.16"
+
+[[inputs.snmp.table.field]]
+name = "http_service_traffic_inbound_in_byte"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.17"
+
+[[inputs.snmp.table.field]]
+name = "http_service_traffic_inbound_in_packet"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.18"
+
+[[inputs.snmp.table.field]]
+name = "http_service_traffic_inbound_out_byte"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.19"
+
+[[inputs.snmp.table.field]]
+name = "http_service_traffic_inbound_out_packet"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.20"
+
+[[inputs.snmp.table.field]]
+name = "http_service_traffic_outbound_in_byte"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.21"
+
+[[inputs.snmp.table.field]]
+name = "http_service_traffic_outbound_in_packet"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.22"
+
+[[inputs.snmp.table.field]]
+name = "http_service_traffic_outbound_out_byte"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.23"
+
+[[inputs.snmp.table.field]]
+name = "http_service_traffic_outbound_out_packet"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.24"
+
+[[inputs.snmp.table.field]]
+name = "http_service_drop_total"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.25"
+
+[[inputs.snmp.table.field]]
+name = "http_service_drop_type_source"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.26"
+
+[[inputs.snmp.table.field]]
+name = "http_service_drop_type_man_bl"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.27"
+
+[[inputs.snmp.table.field]]
+name = "http_service_drop_type_dyn_bl"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.28"
+
+[[inputs.snmp.table.field]]
+name = "http_service_drop_type_acl"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.29"
+
+[[inputs.snmp.table.field]]
+name = "http_service_drop_type_ddos"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.30"
+
+[[inputs.snmp.table.field]]
+name = "http_service_drop_type_waf"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.31"
+
+[[inputs.snmp.table.field]]
+name = "http_service_drop_type_filter"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.32"
+
+[[inputs.snmp.table.field]]
+name = "http_service_drop_type_anomaly"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.33"
+
+[[inputs.snmp.table.field]]
+name = "http_service_drop_type_parse_fail"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.34"
+
+[[inputs.snmp.table.field]]
+name = "http_service_drop_type_resource"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.35"
+
+[[inputs.snmp.table.field]]
+name = "http_service_drop_type_profile"
+oid = ".1.3.6.1.4.1.7564.33.1.2.2.1.36"
+
+[[inputs.snmp.table]]
+name = "asf_https_service"
+
+[[inputs.snmp.table.field]]
+name = "https_service_index"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.1"
+
+[[inputs.snmp.table.field]]
+name = "https_service_id"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.2"
+
+[[inputs.snmp.table.field]]
+name = "https_service_cc"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.3"
+
+[[inputs.snmp.table.field]]
+name = "https_service_cps"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.4"
+
+[[inputs.snmp.table.field]]
+name = "https_service_rps_get"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.5"
+
+[[inputs.snmp.table.field]]
+name = "https_service_rps_post"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.6"
+
+[[inputs.snmp.table.field]]
+name = "https_service_rps_head"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.7"
+
+[[inputs.snmp.table.field]]
+name = "https_service_rps_put"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.8"
+
+[[inputs.snmp.table.field]]
+name = "https_service_rps_delete"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.9"
+
+[[inputs.snmp.table.field]]
+name = "https_service_rps_total"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.10"
+
+[[inputs.snmp.table.field]]
+name = "https_service_anomaly_method"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.11"
+
+[[inputs.snmp.table.field]]
+name = "https_service_anomaly_requestline"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.12"
+
+[[inputs.snmp.table.field]]
+name = "https_service_anomaly_host"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.13"
+
+[[inputs.snmp.table.field]]
+name = "https_service_anomaly_connection"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.14"
+
+[[inputs.snmp.table.field]]
+name = "https_service_anomaly_contentlength"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.15"
+
+[[inputs.snmp.table.field]]
+name = "https_service_anomaly_range"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.16"
+
+[[inputs.snmp.table.field]]
+name = "https_service_traffic_inbound_in_byte"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.17"
+
+[[inputs.snmp.table.field]]
+name = "https_service_traffic_inbound_in_packets"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.18"
+
+[[inputs.snmp.table.field]]
+name = "https_service_traffic_inbound_out_byte"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.19"
+
+[[inputs.snmp.table.field]]
+name = "https_service_traffic_inbound_out_packets"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.20"
+
+[[inputs.snmp.table.field]]
+name = "https_service_traffic_outbound_in_byte"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.21"
+
+[[inputs.snmp.table.field]]
+name = "https_service_traffic_outbound_in_packets"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.22"
+
+[[inputs.snmp.table.field]]
+name = "https_service_traffic_outbound_out_byte"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.23"
+
+[[inputs.snmp.table.field]]
+name = "https_service_traffic_outbound_out_packets"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.24"
+
+[[inputs.snmp.table.field]]
+name = "https_service_ssl_traffic_inbound_in_byte"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.25"
+
+[[inputs.snmp.table.field]]
+name = "https_service_ssl_traffic_inbound_in_packets"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.26"
+
+[[inputs.snmp.table.field]]
+name = "https_service_ssl_traffic_inbound_out_byte"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.27"
+
+[[inputs.snmp.table.field]]
+name = "https_service_ssl_traffic_inbound_out_packets"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.28"
+
+[[inputs.snmp.table.field]]
+name = "https_service_ssl_traffic_outbound_in_byte"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.29"
+
+[[inputs.snmp.table.field]]
+name = "https_service_ssl_traffic_outbound_in_packets"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.30"
+
+[[inputs.snmp.table.field]]
+name = "https_service_ssl_traffic_outbound_out_byte"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.31"
+
+[[inputs.snmp.table.field]]
+name = "https_service_ssl_traffic_outbound_out_packets"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.32"
+
+[[inputs.snmp.table.field]]
+name = "https_service_http_drop_total"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.33"
+
+[[inputs.snmp.table.field]]
+name = "https_service_http_drop_type_source"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.34"
+
+[[inputs.snmp.table.field]]
+name = "https_service_http_drop_type_man_bl"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.35"
+
+[[inputs.snmp.table.field]]
+name = "https_service_http_drop_type_dyn_bl"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.36"
+
+[[inputs.snmp.table.field]]
+name = "https_service_http_drop_type_acl"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.37"
+
+[[inputs.snmp.table.field]]
+name = "https_service_http_drop_type_ddos"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.38"
+
+[[inputs.snmp.table.field]]
+name = "https_service_http_drop_type_waf"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.39"
+
+[[inputs.snmp.table.field]]
+name = "https_service_http_drop_type_filter"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.40"
+
+[[inputs.snmp.table.field]]
+name = "https_service_http_drop_type_anomaly"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.41"
+
+[[inputs.snmp.table.field]]
+name = "https_service_http_drop_type_parse_fail"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.42"
+
+[[inputs.snmp.table.field]]
+name = "https_service_http_drop_type_resource"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.43"
+
+[[inputs.snmp.table.field]]
+name = "https_service_http_drop_type_profile"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.44"
+
+[[inputs.snmp.table.field]]
+name = "https_service_ssl_drop_total"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.45"
+
+[[inputs.snmp.table.field]]
+name = "https_service_ssl_drop_resource"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.46"
+
+[[inputs.snmp.table.field]]
+name = "https_service_ssl_drop_dyn_bl"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.47"
+
+[[inputs.snmp.table.field]]
+name = "https_service_ssl_drop_anomaly_total"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.48"
+
+[[inputs.snmp.table.field]]
+name = "https_service_ssl_drop_mismatch"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.49"
+
+[[inputs.snmp.table.field]]
+name = "https_service_ssl_drop_handshake_version_mismatch"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.50"
+
+[[inputs.snmp.table.field]]
+name = "https_service_ssl_drop_record_version"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.51"
+
+[[inputs.snmp.table.field]]
+name = "https_service_ssl_drop_record_type"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.52"
+
+[[inputs.snmp.table.field]]
+name = "https_service_ssl_drop_handshake_type"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.53"
+
+[[inputs.snmp.table.field]]
+name = "https_service_ssl_drop_handshake_len"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.54"
+
+[[inputs.snmp.table.field]]
+name = "https_service_ssl_drop_encrypt_decrypt"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.55"
+
+[[inputs.snmp.table.field]]
+name = "https_service_ssl_drop_host_stop"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.56"
+
+[[inputs.snmp.table.field]]
+name = "https_service_ssl_drop_send_data"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.57"
+
+[[inputs.snmp.table.field]]
+name = "https_service_ssl_drop_bad_cipher"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.58"
+
+[[inputs.snmp.table.field]]
+name = "https_service_ssl_drop_send_card"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.59"
+
+[[inputs.snmp.table.field]]
+name = "https_service_ssl_drop_no_random"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.60"
+
+[[inputs.snmp.table.field]]
+name = "https_service_ssl_drop_big_number_operation_failed"
+oid = ".1.3.6.1.4.1.7564.33.1.3.2.1.61"
Index: /branches/amp_4_0/platform/tools/scripts/README.md
===================================================================
--- /branches/amp_4_0/platform/tools/scripts/README.md	(nonexistent)
+++ /branches/amp_4_0/platform/tools/scripts/README.md	(working copy)
@@ -0,0 +1,45 @@
+### 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/scripts/configure_elk.sh
===================================================================
--- /branches/amp_4_0/platform/tools/scripts/configure_elk.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/scripts/configure_elk.sh	(working copy)
@@ -0,0 +1,228 @@
+#!/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/scripts/configure_opensearch_heap_memory.sh
===================================================================
--- /branches/amp_4_0/platform/tools/scripts/configure_opensearch_heap_memory.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/scripts/configure_opensearch_heap_memory.sh	(working copy)
@@ -0,0 +1,85 @@
+#!/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/scripts/configure_psql.sh
===================================================================
--- /branches/amp_4_0/platform/tools/scripts/configure_psql.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/scripts/configure_psql.sh	(working copy)
@@ -0,0 +1,12 @@
+#!/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/scripts/configure_telegraf_timescale.sh
===================================================================
--- /branches/amp_4_0/platform/tools/scripts/configure_telegraf_timescale.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/scripts/configure_telegraf_timescale.sh	(working copy)
@@ -0,0 +1,241 @@
+#!/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/scripts/enable_opensearch_jwt_auth.sh
===================================================================
--- /branches/amp_4_0/platform/tools/scripts/enable_opensearch_jwt_auth.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/scripts/enable_opensearch_jwt_auth.sh	(working copy)
@@ -0,0 +1,88 @@
+#!/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/scripts/install_configure_opensearch.sh
===================================================================
--- /branches/amp_4_0/platform/tools/scripts/install_configure_opensearch.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/scripts/install_configure_opensearch.sh	(working copy)
@@ -0,0 +1,573 @@
+#!/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/scripts/install_elk.sh
===================================================================
--- /branches/amp_4_0/platform/tools/scripts/install_elk.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/scripts/install_elk.sh	(working copy)
@@ -0,0 +1,534 @@
+#!/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/scripts/install_fluentbit_dataprepper.sh
===================================================================
--- /branches/amp_4_0/platform/tools/scripts/install_fluentbit_dataprepper.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/scripts/install_fluentbit_dataprepper.sh	(working copy)
@@ -0,0 +1,566 @@
+#!/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/scripts/install_grafana.sh
===================================================================
--- /branches/amp_4_0/platform/tools/scripts/install_grafana.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/scripts/install_grafana.sh	(working copy)
@@ -0,0 +1,228 @@
+#!/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/scripts/install_influx.sh
===================================================================
--- /branches/amp_4_0/platform/tools/scripts/install_influx.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/scripts/install_influx.sh	(working copy)
@@ -0,0 +1,170 @@
+#!/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/scripts/install_influxdb3.sh
===================================================================
--- /branches/amp_4_0/platform/tools/scripts/install_influxdb3.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/scripts/install_influxdb3.sh	(working copy)
@@ -0,0 +1,214 @@
+#!/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/scripts/install_java.sh
===================================================================
--- /branches/amp_4_0/platform/tools/scripts/install_java.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/scripts/install_java.sh	(working copy)
@@ -0,0 +1,100 @@
+#!/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/scripts/install_logstash_oss.sh
===================================================================
--- /branches/amp_4_0/platform/tools/scripts/install_logstash_oss.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/scripts/install_logstash_oss.sh	(working copy)
@@ -0,0 +1,795 @@
+#!/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/scripts/install_nginx.sh
===================================================================
--- /branches/amp_4_0/platform/tools/scripts/install_nginx.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/scripts/install_nginx.sh	(working copy)
@@ -0,0 +1,551 @@
+#!/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/scripts/install_pgbouncer.sh
===================================================================
--- /branches/amp_4_0/platform/tools/scripts/install_pgbouncer.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/scripts/install_pgbouncer.sh	(working copy)
@@ -0,0 +1,121 @@
+#!/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/scripts/install_postfix.sh
===================================================================
--- /branches/amp_4_0/platform/tools/scripts/install_postfix.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/scripts/install_postfix.sh	(working copy)
@@ -0,0 +1,87 @@
+#!/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/scripts/install_psql.sh
===================================================================
--- /branches/amp_4_0/platform/tools/scripts/install_psql.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/scripts/install_psql.sh	(working copy)
@@ -0,0 +1,139 @@
+#!/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/scripts/install_python.sh
===================================================================
--- /branches/amp_4_0/platform/tools/scripts/install_python.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/scripts/install_python.sh	(working copy)
@@ -0,0 +1,104 @@
+#!/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/scripts/install_system_dependencies.sh
===================================================================
--- /branches/amp_4_0/platform/tools/scripts/install_system_dependencies.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/scripts/install_system_dependencies.sh	(working copy)
@@ -0,0 +1,12 @@
+#!/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/scripts/install_telegraf.sh
===================================================================
--- /branches/amp_4_0/platform/tools/scripts/install_telegraf.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/scripts/install_telegraf.sh	(working copy)
@@ -0,0 +1,65 @@
+#!/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/scripts/install_timescaledb.sh
===================================================================
--- /branches/amp_4_0/platform/tools/scripts/install_timescaledb.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/scripts/install_timescaledb.sh	(working copy)
@@ -0,0 +1,117 @@
+#!/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/scripts/install_tools_dependencies.sh
===================================================================
--- /branches/amp_4_0/platform/tools/scripts/install_tools_dependencies.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/scripts/install_tools_dependencies.sh	(working copy)
@@ -0,0 +1,135 @@
+#!/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/scripts/telegraf_snmp_timescale.sql
===================================================================
--- /branches/amp_4_0/platform/tools/scripts/telegraf_snmp_timescale.sql	(nonexistent)
+++ /branches/amp_4_0/platform/tools/scripts/telegraf_snmp_timescale.sql	(working copy)
@@ -0,0 +1,737 @@
+-- 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/scripts/update_opensearch_branding.sh
===================================================================
--- /branches/amp_4_0/platform/tools/scripts/update_opensearch_branding.sh	(nonexistent)
+++ /branches/amp_4_0/platform/tools/scripts/update_opensearch_branding.sh	(working copy)
@@ -0,0 +1,152 @@
+#!/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
