The Wazuh manager deployment playbook (at radar/roles/wazuh_manager/tasks/main.yml) automates the deployment and configuration of Wazuh Manager instances with RADAR scenario-specific customizations. It supports three deployment modes (local Docker, remote Docker, and remote host) and ensures idempotent, scenario-aware configuration management.
┌─────────────────────────────────────────────────────────────┐
│ Playbook entry point: initialize variables & resolve paths │
└─────────────────────────────────────────────────────────────┘
↓
┌───────────────────┬───────────────────┐
↓ ↓ ↓
DOCKER_LOCAL DOCKER_REMOTE HOST_REMOTE
(Local Dev) (Remote Container) (Bare Metal)
The playbook consists of four logical parts: one handles initial configuration and flow choices, and the rest branches into three mutually exclusive blocks based on the manager_mode variable set in the inventory, which are as follows:
docker_local: Manager runs in Docker on controller machinedocker_remote: Manager runs in Docker on remote hosthost_remote: Manager runs directly on remote host (no containers)START
↓
┌─────────────────────────────┐
│ Load Variables & Resolve │
│ Scenario Path │
└────────────┬────────────────┘
↓
┌────────────────────┐
│ Determine Manager │
│ Mode │
└─┬──────┬───────┬───┤
│ │ │ └─→ host_remote
│ │ └─────→ docker_remote
│ └──────────────→ docker_local
│
├─ (If docker_local)
│ ├─ Check container running
│ ├─ Upload templates to OpenSearch
│ ├─ Copy config files
│ ├─ Append decoders (idempotent)
│ ├─ Append rules (idempotent)
│ ├─ Handle SSH override (conditional)
│ ├─ Modify ossec.conf
│ ├─ Restart Wazuh (if changed)
│ ├─ Configure filebeat
│ └─ Setup filebeat pipelines
│
├─ (If docker_remote)
│ ├─ Create staging dir on remote
│ ├─ Bootstrap manager (if needed)
│ ├─ Check container running
│ ├─ [Same as docker_local, executed on remote]
│
└─ (If host_remote)
├─ Verify target directories
├─ Read snippets on controller
├─ Insert configs via Ansible modules
├─ Copy active response files
├─ Validate XML (optional)
└─ Restart Wazuh (if changed)
↓
END
failed_when: falsedebug taskswazuh-control (not full container)The playbook uses a volume-first architecture where all Wazuh configuration directories are bind-mounted from the host into the container. This eliminates the need for docker cp operations and allows direct host-side manipulation.
Key volume mappings (defined in volumes.yml):
volumes:
- /radar-srv/wazuh/manager/etc:/var/ossec/etc
- /radar-srv/wazuh/manager/active-response/bin:/var/ossec/active-response/bin
- /radar-srv/wazuh/manager/filebeat/etc:/etc/filebeat
The manager playbook was decomposed into small task files, each responsible for a single configuration domain. The key refactor idea is: tasks are mode-agnostic; only the source path changes between docker_local and docker_remote.
The orchestration is centralized in main.yml, which acts as a control plane, not an execution script.
Common task modules (roles/wazuh_manager/tasks/):
| Task file | Responsibility | Modified resources |
|---|---|---|
responses.yml |
Active response scripts & env | /var/ossec/active-response/bin |
lists.yml |
Whitelists / lists | /var/ossec/etc/lists, ossec.conf |
decoders.yml |
Custom decoders | local_decoder.xml, SSH overrides |
rules.yml |
Custom rules | local_rules.xml |
ossec.yml |
Core manager configuration | ossec.conf |
filebeat.yml |
Ingest & indexing logic | Filebeat config, pipelines |
bootstrap.yml |
Manager / webhook bootstrap | Docker Compose stack |
agent_config.yml |
Centralized agent configuration | /var/ossec/etc/shared/<group>/agent.conf |
Each task file:
This keeps business logic identical across modes and prevents divergence.
Here we provide a summarized description of the entire automation pipeline in the form of pseudocode.
FUNCTION DeployRadarScenario(scenario_name, manager_mode, bootstrap_flag):
// ============================================
// PHASE 1: INITIALIZATION
// ============================================
scenario_root ← RESOLVE_SCENARIO_ROOT_DIR() // "scenarios/"
scenario_path ← RESOLVE_scenario_path(scenario_root, scenario_name)
manager_vars ← LOAD_INVENTORY_VARS(manager_mode)
// Load and parse volumes.yml
volumes_yml ← LOAD_YAML_FILE("volumes.yml")
volume_mappings ← EXTRACT_BIND_MOUNTS(volumes_yml)
// Resolve host paths from volume mappings
host_ossec_etc ← EXTRACT_HOST_PATH(volume_mappings, "/var/ossec/etc")
host_active_response_bin ← EXTRACT_HOST_PATH(volume_mappings, "/var/ossec/active-response/bin")
host_filebeat_etc ← EXTRACT_HOST_PATH(volume_mappings, "/etc/filebeat")
IF scenario_root DOES NOT EXIST:
FAIL "scenarios/ root not found"
END IF
// ============================================
// PHASE 2: BRANCH ON DEPLOYMENT MODE
// ============================================
SWITCH manager_mode:
CASE "docker_local":
RETURN DeployLocal(scenario_path, manager_vars)
CASE "docker_remote":
RETURN DeployRemoteContainer(scenario_path, manager_vars, bootstrap_flag)
CASE "host_remote":
RETURN DeployRemoteHost(scenario_path, manager_vars)
END SWITCH
// ============================================
// DOCKER_LOCAL DEPLOYMENT
// ============================================
FUNCTION DeployLocal(scenario_path, manager_vars):
container ← manager_vars.container_name
// Step 1: Verify container running
REPEAT 3 TIMES with 30sec delay:
IF docker.ps(container) == RUNNING:
BREAK
END REPEAT
// Step 2: Upload templates to OpenSearch
template_json ← READ_FILE(scenario_path + "/wazuh-alerts-template.json")
HTTP_PUT("https://indexer:9200/_index_template/wazuh-alerts-*",
auth=(admin_user, admin_pass),
body=template_json,
retries=3)
// Step 3: Copy configuration files
CopyConfigFiles(container, scenario_path)
// Step 4: Ensure host bind-mount directories exist
ENSURE_DIRS_EXIST_LOCAL([
host_ossec_etc + "/decoders",
host_ossec_etc + "/rules",
host_ossec_etc + "/lists",
host_active_response_bin,
host_filebeat_etc
])
// Step 5: Copy decoders into volume mapped to /var/ossec/etc/decoders/
COPY_FILES(scenario_path.decoders_dir + "/*.xml",
host_ossec_etc + "/decoders/")
// Step 6: Copy rules into volume mapped to /var/ossec/etc/rules/
COPY_FILES(scenario_path.rules_dir + "/*.xml",
host_ossec_etc + "/rules/")
// Step 7: Modify ossec.conf in volume mapped to /var/ossec/etc/ossec.conf (idempotent)
ossec_conf ← host_ossec_etc + "/ossec.conf"
IF MarkerNotExists(ossec_conf, "RADAR: " + scenario_name):
snippet ← READ_FILE(scenario_path.ossec_snippet)
InsertBefore(ossec_conf, "</ossec_config>",
markers="RADAR: " + scenario_name,
content=snippet)
END IF
IF scenario_name IN ["suspicious_login", "geoip_detection"]:
AddIfMissing(ossec_conf, "<decoder_exclude>0310-ssh_decoders.xml</decoder_exclude>")
END IF
AddIfMissing(ossec_conf, "<logall>yes</logall>")
AddIfMissing(ossec_conf, "<logall_json>yes</logall_json>")
// Step 8: Restart if config changed
IF any_file_changed:
DOCKER_EXEC(container, "/var/ossec/bin/wazuh-control restart")
END IF
// Step 9: Configure filebeat using volume-mapped paths
ConfigureFilebeat(container, scenario_path)
// Step 10: Setup filebeat pipelines
DOCKER_EXEC(container,
"filebeat setup --pipelines --modules wazuh --strict.ssl=false",
ignore_errors=TRUE) // Non-critical
// Step 11: Update centralized agent configuration (agent.conf) on manager
ENSURE_DIR_EXISTS_LOCAL(host_ossec_etc + "/shared/default")
ENSURE_FILE_EXISTS_LOCAL(agent_conf)
IF MarkerNotExists(agent_conf, "RADAR: " + scenario_name + " agent_config"):
agent_snippet ← READ_FILE(scenario_path.agent_snippet)
AppendBlock(agent_conf,
markers="RADAR: " + scenario_name + " agent_config",
content=agent_snippet)
END IF
DOCKER_EXEC(container, "/var/ossec/bin/verify-agent-conf -f /var/ossec/etc/shared/default/agent.conf")
RETURN SUCCESS
// ============================================
// DOCKER_REMOTE DEPLOYMENT
// ============================================
FUNCTION DeployRemoteContainer(scenario_path, manager_vars, bootstrap_flag):
remote_host ← manager_vars.ansible_host
remote_user ← manager_vars.ansible_user
container ← manager_vars.container_name
// Step 1: Bootstrap if needed
IF bootstrap_flag:
stage_dir ← CREATE_TEMP_DIR_ON_REMOTE(remote_host)
COPY_DOCKER_COMPOSE(stage_dir, remote_host)
SSH_EXEC(remote_host, "docker compose up -d")
WAIT_FOR_CONTAINER(remote_host, container, retries=20)
END IF
// Step 2: Verify container running
REPEAT 3 TIMES with 30sec delay:
IF docker.ps(container, remote_host) == RUNNING:
BREAK
END REPEAT
// Step 3-11: Same as docker_local, but:
// - Operations run on remote_host via SSH
// - Use "become: true" for privilege escalation
RETURN SUCCESS
// ============================================
// HOST_REMOTE DEPLOYMENT
// ============================================
FUNCTION DeployRemoteHost(scenario_path, manager_vars):
remote_host ← manager_vars.ansible_host
remote_user ← manager_vars.ansible_user
// Step 1: Verify target directories
ENSURE_DIRS_EXIST(remote_host, [
"/var/ossec",
"/var/ossec/etc",
"/var/ossec/etc/lists",
"/var/ossec/active-response/bin"
])
// Step 2: Read scenario snippets on controller
ossec_snippet ← READ_FILE(scenario_path + "/radar-ossec-snippet.xml")
decoder_snippet ← READ_FILE(scenario_path + "/local_decoder.xml")
rules_snippet ← READ_FILE(scenario_path + "/local_rules.xml")
// Step 3: Insert into host files via Ansible modules (idempotent)
INSERT_INTO_FILE(remote_host, "/var/ossec/etc/ossec.conf",
marker="RADAR: " + scenario_name,
content=ossec_snippet)
APPEND_TO_FILE(remote_host, "/var/ossec/etc/decoders/local_decoder.xml",
marker="RADAR_DECODERS: " + scenario_name,
content=decoder_snippet)
APPEND_TO_FILE(remote_host, "/var/ossec/etc/rules/local_rules.xml",
marker="RADAR_RULES: " + scenario_name,
content=rules_snippet)
// Step 4: Copy active response files
IF file_exists(scenario_path + "/radar_ar.py"):
SCP(scenario_path + "/radar_ar.py",
remote_host + ":/var/ossec/active-response/bin/")
END IF
// Step 5: Handle SSH decoder (if applicable)
IF scenario_name IN ["suspicious_login", "geoip_detection"]:
CopySshDecoderOverride(remote_host, scenario_path)
END IF
// Step 6: Validate ossec.conf XML (if Python available)
TRY:
SSH_EXEC(remote_host, "python3 -m xml.etree.ElementTree " +
"/var/ossec/etc/ossec.conf")
CATCH:
WARN "Could not validate XML syntax"
END TRY
// Step 7: Restart Wazuh if config changed
IF any_file_changed:
SSH_EXEC(remote_host, "/var/ossec/bin/wazuh-control restart",
become=true)
END IF
RETURN SUCCESS
// ============================================
// HELPER FUNCTIONS
// ============================================
FUNCTION CopyConfigFiles(container, scenario_path):
// Active response env vars
IF file_exists(".env"):
COPY_TO_VOLUME(".env", host_active_response_bin + "/active_responses.env")
END IF
// Whitelist (geoip scenario)
IF file_exists(scenario_path + "/whitelist_countries"):
ENSURE_DIR(host_ossec_etc + "/lists")
COPY_TO_VOLUME(scenario_path + "/whitelist_countries",
host_ossec_etc + "/lists/whitelist_countries")
SET_PERMS(container, "/var/ossec/etc/lists/whitelist_countries",
"root:wazuh", "0644")
AddToOssecConf(container, "<list>etc/lists/whitelist_countries</list>")
END IF
// Unified active response script
IF file_exists(scenario_path + "/active_responses/radar_ar.py"):
COPY_TO_VOLUME(scenario_path + "/active_responses/radar_ar.py",
host_active_response_bin + "/radar_ar.py")
END IF
IF file_exists(scenario_path + "/active_responses/ar.yaml"):
COPY_TO_VOLUME(scenario_path + "/active_responses/ar.yaml",
host_active_response_bin + "ar.yaml")
END IF
IF dir_exists("pyflowintel"):
SCP_RECURSIVE("/pyflowintel",
container + ":/var/ossec/active-response/bin/")
END IF
END FUNCTION
FUNCTION ConfigureFilebeat(container):
filebeat_yml ← host_filebeat_etc + "/filebeat.yml"
// Enable archives
REPLACE_IN_FILE(filebeat_yml, "enabled: false", "enabled: true",
within_section="archives")
REPLACE_IN_FILE(filebeat_yml, "var.paths: []",
"var.paths:\n - /var/ossec/logs/archives/archives.json",
within_section="archives")
IF file_changed:
DOCKER_RESTART(container)
END IF
END FUNCTION
FUNCTION MarkerExists(container, file_path, marker_text):
result ← DOCKER_EXEC(container, "grep -F '" + marker_text + "' " + file_path)
RETURN result.exit_code == 0
END FUNCTION
FUNCTION COPY_IF_DIFFERENT(src, dest_container_path):
src_sum ← HASH_FILE(src)
dest_sum ← DOCKER_EXEC(container, "sha256sum " + dest_container_path)
IF src_sum != dest_sum:
COPY_TO_VOLUME(src, dest_host_path)
RETURN TRUE
END IF
RETURN FALSE
END FUNCTION
Establish common variables used across all blocks.
| Variable | Source | Purpose |
|———-|——–|———|
| _scenario_root | Ansible fact | Root scenarios directory (e.g., radar/scenarios) |
| _scenario_path | Ansible fact (derived) | Resolved per-scenario artifact paths across the new structure (ossec/decoders/rules/lists/filebeat) |
| _mgr_mode | Inventory host var | Deployment mode: docker_local, docker_remote, or host_remote |
| _mgr_container | Inventory (default: wazuh.manager) | Docker container name |
| _list_marker | Hardcoded | Marker for geoip whitelist insertion |
| _radar_ar_dest | Hardcoded | Container path to the generalized active response script (radar_ar.py) |
| _lists_dir | Hardcoded | Container path to Wazuh lists directory |
| _host_ossec_conf | Derived from volumes.yml | Host bind-mount path for ossec.conf (e.g., /radar-srv/wazuh/manager/etc/ossec.conf) |
| _host_decoders_dir | Derived from volumes.yml | Host bind-mount dir for decoders (e.g., /radar-srv/wazuh/manager/etc/decoders) |
| _host_rules_dir | Derived from volumes.yml | Host bind-mount dir for rules (e.g., /radar-srv/wazuh/manager/etc/rules) |
| _host_active_response_bin | Derived from volumes.yml | Host bind-mount dir for active responses (e.g., /radar-srv/wazuh/manager/active-response/bin) |
| _host_filebeat_yml | Derived from volumes.yml | Host bind-mount path for filebeat.yml (e.g., /radar-srv/wazuh/manager/filebeat/etc/filebeat.yml) |
- Check .env file existence on controller (for active response env vars)
- Set scenario paths and destination paths
- Load volumes.yml from controller
- Parse volumes.yml to extract volume mappings
- Derive host paths from volume mappings
- Validate required volume mappings exist
- Debug output current manager mode
when: _mgr_mode == 'docker_local'
Deploy scenario to a local Docker container on the controller machine.
What: Verify manager container is running
How: docker ps with retries (3x, 30sec delay)
Why: Ensure container is ready before modifications
Idempotency: Register variable, use changed_when: false (check-only)
Transfers scenario-specific files into container via volume-mapped directories:
| File | Source | Destination | Purpose |
|---|---|---|---|
.env |
Controller | {host_active_response_bin}/active_responses.env |
Active response environment vars |
whitelist_countries |
Scenario dir | {host_lists_dir}/whitelist_countries |
IP whitelist for geoip scenario |
radar_ar.py |
Scenario dir | {host_active_response_bin}/radar_ar.py |
Unified active response script |
ar.yaml |
Scenario dir | {host_active_response_bin}/ar.yaml |
Risk config for radar_ar |
pyflowintel |
Controller | {host_active_response_bin}/pyflowintel/ |
FlowIntel wrapper |
What: Copy scenario configuration files to volume-mapped host directories
How: Ansible copy module to host paths, then fix permissions via container exec
Why: Volume mapping makes files immediately visible to container without docker cp
When: Files only copied if source exists
Idempotency:
Key changes:
radar_ar.py and ad_context_*.py to single radar_ar.py scriptradar_ar.py expects ar.yaml in the same directory for risk configurationpyflowintel is vendored under active-response/bin for FlowIntel case creationPYFLOWINTEL_PATH environment variable points to /var/ossec/active-response/bin/pyflowintelyaml (PyYAML) and requests (for OpenSearch and FlowIntel HTTP operations)What: Copy scenario decoders as files into /var/ossec/etc/decoders/ via volume
How: Copy *.xml files from scenarios/decoders// to {host_decoders_dir}/, then normalize permissions via container exec
Why: Volume mapping eliminates need for docker cp operations
When: Always executed for each scenario
Idempotency mechanism: Copy only when source exists and content differs (or missing)
Flow:
docker execWhat: Copy scenario rules as files into /var/ossec/etc/rules/ via volume
How: Identical to decoders, but for rules directory
Why: Same volume-mapping benefits as decoders
When: Always executed for each scenario
Idempotency: Same as decoders
What: Modify Wazuh core configuration
How: Complex multi-step process using volume-mapped file
Why: Enable scenario-specific settings (active responses, rule configs)
When: Always executed for each scenario
Complex multi-step process:
{host_ossec_conf} exists on hostblockinfile with scenario-specific markers (idempotent)docker exec to test if 0310-ssh.xml exists<decoder_exclude> if custom override present<logall>yes</logall> and <logall_json>yes</logall_json>Idempotency:
blockinfile with markers prevents duplicatesreplace module is inherently idempotentWhat: Restart Wazuh service if configuration changed
How: docker exec wazuh.manager /var/ossec/bin/wazuh-control restart
Why: Apply configuration changes without full container restart
When: If any of these changed:
What: Enable archives in filebeat.yml for log collection
Steps:
/etc/filebeat/filebeat.yml via volume-mapped fileIdempotency:
What: Upload wazuh-ad-log-volume index template to OpenSearch
How: HTTP PUT request to /_index_template/wazuh-ad-log-volume-*
When: Only for scenario_name == ‘log_volume’
Idempotency: Check HTTP status (200/201 = success)
TLS: validate_certs: no (handles self-signed certs)
Why: This creates a template for index to store log volume metric events
What: Configures dedicated OpenSearch index and routes log_volume_metric events via Wazuh archives ingest pipeline
How: Pull pipeline.json from container, patch with routing logic, push back
When: Only for scenario_name == 'log_volume'
Why: This isolates the log volume metrics into a RADAR-controlled index with correct mappings (e.g., data.log_bytes as numeric), without changing the global wazuh-archives-* schema or impacting existing dashboards. Any future events from the log_volume_metric program are now indexed into the dedicated wazuh-ad-log-volume-* indices, while all other archives events remain under the standard Wazuh index pattern.
Steps:
/usr/share/filebeat/module/wazuh/archives/ingest/pipeline.json via docker cpscenarios/pipelines/log_volume/radar-pipeline.txtdate_index_name processor with two-branch conditional:
predecoder.program_name == "log_volume_metric" → index prefix wazuh-ad-log-volume-*wazuh-archives-*)docker cpNote: Pipeline.json not volume-mapped, requires docker cp operations
agent.confWhat: Deploys agent-side settings centrally from the manager using Wazuh Centralized Configuration (agent.conf)
How: Append a scenario-scoped <agent_config ...> block into /var/ossec/etc/shared/default/agent.conf (volume-mapped via /var/ossec/etc), validate with verify-agent-conf
When: For scenarios that require agent-side log collection
Steps:
scenarios/agent_configs//radar--agent-snippet.xml (must contain a complete <agent_config ...>...</agent_config> block)agent.conf using a stable marker RADAR: agent_configverify-agent-confwhen: _mgr_mode == 'docker_remote'
Deploy scenario to a Docker container on a remote host via SSH.
What: Create temporary directory on remote host
How: Using tempfile module
Why: Clean, isolated staging area for bootstrap files
When: Always created at start of remote deployment
_stage.path = /tmp/radar_mgr_XXXXX/
What: Deploy entire Wazuh stack from scratch
How: Multi-step Docker Compose deployment
Why: Enables greenfield deployments on remote hosts
When: If manager_bootstrap == true (i.e., manager doesn’t exist yet)
Steps:
docker-compose up -dWhat: Deploy webhook container alongside manager
How: Separate Docker Compose stack
Why: Enable Teams/Slack integrations
When: If manager_bootstrap == true AND webhook container not running
Purpose: Deploy webhook container alongside manager
Scope: Separate from manager configuration
What: Deploy scenario decoders and rules
How: Identical logic to docker_local
Why: Same volume-mapping approach works on remote
When: Always executed
Execution context: Remote host via become: true
What: Modify Wazuh configuration
How: Enhanced vs docker_local with additional SSH decoder checks
Why: Support scenarios requiring custom SSH decoders
When: Always executed
Enhanced features:
become: truedocker_local: copy directly to controller bind-mount path.docker_remote: stage to /tmp/pyflowintel/, then privileged copy into bind mount.when: _mgr_mode == 'host_remote'
Deploy to bare-metal Wazuh Manager on remote host (no Docker).
No Docker intermediate → modify host files directly:
/var/ossec/etc/ossec.conf/var/ossec/etc/decoders/local_decoder.xml/var/ossec/etc/rules/local_rules.xmlEnsures target directories exist (parent creation):
/var/ossec
/var/ossec/etc
/var/ossec/etc/lists
/var/ossec/active-response/bin
Uses lookup() to read scenario snippets on controller
Why: Single file transfer, then insert from variable (fewer SSH ops)
Uses Ansible blockinfile module (preferred for idempotency over shell scripts)
Attempts to validate ossec.conf well-formedness using Python XML parser
Why: Catch config errors early
Graceful: Skips if Python not available
blockinfile:
marker: "<!-- RADAR: {mark} -->"
blockinfile tracks content between markerslsum="$(sha256sum "$f" | awk '{print $1}')"
rsum="$(docker exec ... sha256sum ...)"
if [ "$lsum" != "$rsum" ]; then copy; fi
if grep -Fq "$marker" "$file"; then
echo "NOCHANGE"
else
append content
fi
when:
- file_stat.stat.exists
- previous_task is changed
retries: 3
delay: 30
until: result.rc == 0
Used for:
failed_when: false
Used for:
register: _variable_name
changed_when: "'CHANGED' in stdout"
--extra-vars "scenario_name=suspicious_login"
controller:/home/user/radar/scenarios/
├── active_responses/
| ├── ar.yaml
│ └── radar_ar.py
├── lists/
│ └── whitelist_countries
├── ossec/
│ ├── radar-geoip-detection-ossec-snippet.xml
│ ├── radar-log-volume-ossec-snippet.xml
│ └── radar-suspicious-login-ossec-snippet.xml
├── decoders/
│ └── /
│ └── *.xml
├── rules/
│ └── /
│ └── *.xml
├── templates/
│ └── /
│ └── radar-template.json
├── pipelines/
│ └── /
│ └── radar-pipeline.txt
└── agent_configs/
└── /
└── radar--agent-snippet.xml
| Scenario | Conditional Block |
|———-|——————|
| suspicious_login, geoip_detection | 0310 SSH decoder override |
| log_volume | Index template upload + archives pipeline routing + filebeat setup |
| All | Decoders + Rules + ossec.conf modifications |
# From build-radar.sh
--manager local → _mgr_mode = docker_local (block 2)
--manager remote → _mgr_mode = docker_remote (block 3)
--manager host → _mgr_mode = host_remote (block 4)
Each block:
/var/ossec/etc/ossec.conf (via volume: {host_ossec_conf})/var/ossec/etc/decoders/*.xml (via volume: {host_decoders_dir})/var/ossec/etc/rules/*.xml (via volume: {host_rules_dir})/var/ossec/etc/lists/whitelist_countries (via volume: {host_lists_dir}, conditional)/var/ossec/active-response/bin/radar_ar.py (via volume: {host_active_response_bin})/var/ossec/active-response/bin/ar.yaml (via volume: {host_active_response_bin})/var/ossec/active-response/bin/active_responses.env (via volume: {host_active_response_bin})/etc/filebeat/filebeat.yml (via volume: {host_filebeat_yml})/var/ossec/etc/shared/<GROUP_NAME>/agent.conf (via volume: {_host_shared_group_dir})/usr/share/filebeat/module/wazuh/archives/ingest/pipeline.json (via docker cp, conditional for log_volume)/_index_template/wazuh-alerts-* (HTTP PUT)/_index_template/radar-log-volume (conditional, HTTP PUT)radar/scenarios/ossec/radar--ossec-snippet.xml → ossec.conf insertionsdecoders//*.xml → custom decodersrules//*.xml → custom rulesactive_responses/radar_ar.py → unified active response script (if needed)templates//radar-template.json (optional) → index templatepipelines//radar-pipeline.txt (optional) → pipeline routingagent_configs/radar--agent-snippet.xml → agent.conf insertionswhen: _mgr_mode == 'new_mode'