# Launch Galera Cluster from Site 1 to Site 2 [TOC] ## Pre-requisites - Each site has an Uptime Kuma instance locally available - Execution environments on AWX/Ansible: - [`xolorg.opnsense`](https://galaxy.ansible.com/ui/repo/published/oxlorg/opnsense/docs/)- used to make API calls to OPNSense to register new host overrides. This is because with Kea DHCP, the automatic registration of static reservations into Unbound DNS is currently missing in OPNSense 25.7+ and so it does not work reliably as in ISC DHCP, from which OPNSense is moving away. - `awx.awx` - in order to interact with the inventory in AWX - `community.mysql` - to set up new Galera nodes (to modify config files). - `community.proxmox` - to spin up new LXCs. - Each site needs a unique (sub)-domain with pre-defined host overrides (if using Unbound DNS), so that the AWX server on Site 1 can reach a hostname such as `galera5.site2.mydomain.com`. In my case, the sites are as follows: - Site 1: `uvody.bakalar.priv` - an example host: `galera-A1.uvody.bakalar.priv` - Site 2: `tusarka.bakalar.priv` - an example host: `web3.tusarka.bakalar.priv` - Site 3: hetzner-witness.bachelor-tech.com - no nodes are installed there, acts as an Arbitrator ## Workflow Explained - This will be triggered on demand when needed by an admin. - A survey will pop up asking for a predefined lists of items, such as: - IP (drop-down) - hostname (drop-down) - segment ID (which site) - this will determine on which Proxmox server is the LXC spun up. - quorum weight - default 1, smaller sites may have lower weight to keep a balance - hostname (drop-down) - used in the inventory to run jobs against ## Modify galera-template’s firewall rule + script - Ensure that ufw rules are set up as follows: ``` root@galera--template:/opt/scripts# ufw status numbered Status: active To Action From -- ------ ---- [ 1] 80/tcp DENY IN Anywhere [ 2] 443 DENY IN Anywhere [ 3] 21/tcp DENY IN Anywhere [ 4] 4567/tcp ALLOW IN 10.10.10.0/24 # Cluster Replication / Multicast (heartbeat, actual writes) [ 5] 4567/udp ALLOW IN 10.10.10.0/24 # Cluster Replication / Multicast (heartbeat, actual writes) [ 6] 3306/tcp ALLOW IN 10.10.10.0/24 # MariaDB SQL client connection [ 7] 3306/tcp ALLOW IN 192.168.0.0/16 # MariaDB SQL client connection [ 8] 4567/tcp ALLOW IN 192.168.0.0/16 # Cluster Replication / Multicast (heartbeat, actual writes) [ 9] 4567/udp ALLOW IN 192.168.0.0/16 # Cluster Replication / Multicast (heartbeat, actual writes) [10] 2222/tcp ALLOW IN 192.168.0.0/16 # SSH in [11] 4444/tcp ALLOW IN 192.168.0.0/16 # Galera full data copy from donor [12] 4568/tcp ALLOW IN 192.168.0.0/16 # IST - Incremental State Transfer - last 10s [13] 4444/tcp ALLOW IN 10.10.10.0/24 # Galera full data copy from donor [14] 4568/tcp ALLOW IN 10.10.10.0/24 # IST - Incremental State Transfer - last 10s [15] 80/tcp (v6) DENY IN Anywhere (v6) [16] 443 (v6) DENY IN Anywhere (v6) [17] 21/tcp (v6) DENY IN Anywhere (v6) ``` - You can run the following commands: ```bash sudo ufw allow from 192.168.0.0/16 to any port 4444 proto tcp comment "Galera full data copy from donor" sudo ufw allow from 10.10.10.0/24 to any port 4444 proto tcp comment "Galera full data copy from donor" sudo ufw allow from 10.10.10.0/24 to any port 3306 proto tcp comment "MariaDB SQL client connection" sudo ufw allow from 192.168.0.0/16 to any port 3306 proto tcp comment "MariaDB SQL client connection" sudo ufw allow from 10.10.10.0/24 to any port 4568 proto tcp comment "IST - Incremental State Transfer - last 10s" sudo ufw allow from 192.168.0.0/16 to any port 4568 proto tcp comment "IST - Incremental State Transfer - last 10s" ``` - Or to simplify it in a table: | **Port** | **Protocol** | **Status** | **Scope** | **Comment** | | --- | --- | --- | --- | --- | | **2222** | TCP | ALLOW | LAN (192.168.0.0/16) | SSH Management | | **3306** | TCP | ALLOW | LAN + VPN | **Client Connections** (Apps + Uptime Kuma) | | **4567** | TCP/UDP | ALLOW | LAN + VPN | **Replication Traffic** (Heartbeats & Write Sets) | | **4568** | TCP | ALLOW | LAN + VPN | **IST** (Incremental State Transfer - "The catch-up") | | **4444** | TCP | ALLOW | LAN + VPN | **SST** (Snapshot Transfer - "The full backup") | | **80/443** | TCP | DENY | Anywhere | Explicitly block Web traffic (Good practice) | - To explain in terms of bandwidth/latency terms: - **Port 4567 (Live Traffic):** This is sensitive to **Latency**. If your VPN latency spikes, this port causes the cluster to pause writes (Flow Control). - **Port 4444 (SST):** This is sensitive to **Bandwidth**. If a new node joins, it will max out your VPN upload speed on the donor side, potentially choking the VPN for other services (like your web traffic). - **Tip:** If you ever find a node restore kills your VPN, you can limit the bandwidth in the `mariadb-backup` options in the config file later. - Before converting the LXC into a template again, ensure that by default, incoming traffic is blocked and outgoing is allowed: ```bash root@galera--template:/opt/scripts# ufw status verbose Status: active Logging: on (low) Default: deny (**incoming**), allow (**outgoing**), disabled (routed) ``` - Add a script for Uptime Kuma: ```bash root@galera--template:/# cat /opt/scripts/check_galera.sh #!/bin/bash # --- CONFIGURATION --- # Argument 1: The Kuma Push URL (including the unique code) PUSH_URL="$1" # Credentials (Local check doesn't necessarily need network IP, can use localhost) # Recommendation: Use a .my.cnf file for the user running the script to avoid passwords in plain text in this file. # If hardcoding is necessary: DB_USER="uptimekuma" DB_PASS="YourStrongPasswordHere" if [ -z "$PUSH_URL" ]; then echo "Usage: $0 " exit 1 fi # --- CHECK 1: Is Process Running? --- if ! systemctl is-active --quiet mariadb; then curl -fsS --retry 3 "${PUSH_URL}?status=down&msg=Service_Stopped&ping=" > /dev/null exit 0 fi # --- CHECK 2: Galera Status --- # We check 'wsrep_local_state_comment'. # Valid states for serving traffic: 'Synced'. # 'Donor/Desynced' means it's up but taking a backup or catching up (technically up, but maybe not ready for writes). STATUS=$(mysql -u"$DB_USER" -p"$DB_PASS" -N -B -e "SHOW STATUS LIKE 'wsrep_local_state_comment';" 2>/dev/null | awk '{print $2}') if [ "$STATUS" == "Synced" ]; then curl -fsS --retry 3 "${PUSH_URL}?status=up&msg=Synced&ping=" > /dev/null elif [ "$STATUS" == "Donor/Desynced" ]; then # Optional: Mark as UP but with a warning message, or DOWN depending on your preference. curl -fsS --retry 3 "${PUSH_URL}?status=up&msg=Donor_Mode&ping=" > /dev/null else # Joined, Connected, etc., but not Synced usually means issues. curl -fsS --retry 3 "${PUSH_URL}?status=down&msg=State_${STATUS}&ping=" > /dev/null fi ``` - Once done, shut down the LXC and turn it into a template. Then back it up to your PBS to make it reachable from other Proxmox hosts that are not on the same cluster. As you make the backup, tick the ‘Protect’ box to ensure the template’s backup is protected, as we will rely on it. ## Add a Credential Type in AWX for SST replication - In AWX, go to **Administration** -> **Credential Types** -> **Add**. - **Name:** `Galera Credentials` - Enter the following for the input & injector configuration: ```json **# Input configuration (YAML):** fields: - id: wsrep_sst_password label: Galera SST Password type: string secret: true required: - wsrep_sst_password **# Injector configuration (YAML)** extra_vars: galera_sst_pass: '{{ wsrep_sst_password }}' ``` - Then add your Credential into it called **`galera_sst_pass`** with the replication password. ## Create the Galera config to deploy - In your Gitea repository, create a `60-galera.cnf` file that will be saved on the node once deployed from the template. ```sql # 60-galera.cnf.j2 [mysqld] # MySQL-related settings bind-address = 0.0.0.0 binlog_format = ROW default_storage_engine = InnoDB innodb_autoinc_lock_mode = 2 # Durability vs Performance innodb_flush_log_at_trx_commit = 2 innodb_log_file_size = 256M innodb_log_buffer_size = 64M innodb_lock_wait_timeout = 60 innodb_buffer_pool_size = 800M # Network & Safety skip-name-resolve max_connect_errors = 1000 log_warnings = 1 wait_timeout = 1800 interactive_timeout = 1800 max_allowed_packet = 1G [galera] # Galera Provider Configuration wsrep_on=ON wsrep_provider=/usr/lib/galera/libgalera_smm.so # Provider Options # gmcast.segment is injected via Ansible Inventory/Group Vars wsrep_provider_options="gcache.size=512M;gcs.fc_limit=128;gcs.fc_factor=0.8;gmcast.segment={{ galera_segment }};pc.weight={{ galera_weight }}" # Cluster Configuration - clusterA, clusterB, etc. wsrep_cluster_name="{{ galera_cluster_name }}" # This variable should be defined in your Inventory "all" group vars wsrep_cluster_address="{{ galera_cluster_address_string }}" # SST Configuration wsrep_sst_method = mariabackup # The user is hardcoded (usually safe), the password comes from AWX Secret wsrep_sst_auth = sst_user:{{ galera_sst_pass }} wsrep_node_address = "{{ ansible_ip | default(ansible_host) }}" wsrep_sst_receive_address = "{{ ansible_ip | default(ansible_host) }}" wsrep_node_name = "{{ inventory_hostname }}" ``` - Create a single node deployment script. What does this script do? - On the destination Proxmox host (or cluster), we find the next available VM ID. - Restore the latest template copy from Proxmox Backup Server (PBS), which is physically located on Site 1 but reachable by Site 2 via an existing S2S VPN tunnel. The naming by which it is mapped is a bit different, so the variables differ. - Create a full (not linked!) copy of the template on the same Proxmox host after identifying the next available VM ID and then the template gets removed. The final container gets turned on. - A DNS override is created in OPNSense to ensure it is reachable between the networks. ```yaml # 1-provision_one_galera_node.yml --- - name: Provision Galera Container (Clone Method) hosts: localhost gather_facts: no connection: local vars: template_id: 150 target_disk_storage: "local-lvm" storage_id_site1: "pbs-rpi" storage_id_site2: "rpi_backups" tasks: # --------------------------------------------------------- # 1. DYNAMIC INVENTORY LOOKUP # --------------------------------------------------------- - name: Identify Site Group Name set_fact: target_site_group: "{{ 'uvody' if galera_segment|int == 1 else 'tusarka' }}" - name: Find Target Proxmox Host in Inventory set_fact: pve_inventory_name: >- {{ groups.get(target_site_group, []) | intersect(groups.get('proxmox_nodes', [])) | first | default('') }} - name: Find Target OPNsense Host in Inventory set_fact: # Use .get() to return empty list [] if group is missing, preventing crash opnsense_inventory_name: >- {{ groups.get(target_site_group, []) | intersect(groups.get('opnsense_hosts', [])) | first | default('') }} - name: Validate Inventory Lookups fail: msg: > Could not find required infrastructure hosts in the selected inventory. Target Site: {{ target_site_group }} Found Hypervisor: '{{ pve_inventory_name }}' (Expected a host in 'proxmox_nodes') Found Firewall: '{{ opnsense_inventory_name }}' (Expected a host in 'opnsense_hosts') when: pve_inventory_name == '' or opnsense_inventory_name == '' - name: Extract Variables set_fact: opnsense_ip: "{{ hostvars[opnsense_inventory_name]['ansible_ip'] | default(hostvars[opnsense_inventory_name]['ansible_host']) }}" pbs_storage_id: "{{ storage_id_site1 if galera_segment|int == 1 else storage_id_site2 }}" short_hostname: "{{ target_host.split('.')[0] }}" dns_domain: "{{ target_host.split('.')[1:] | join('.') }}" - name: DEBUG - Verify Target debug: msg: "Delegating to Inventory Host: {{ pve_inventory_name }}" # --------------------------------------------------------- # 1.5 PRE-FLIGHT CHECK (Prevent Clashes) # --------------------------------------------------------- - name: "Check if host '{{ short_hostname }}' already exists" command: "/usr/bin/pvesh get /cluster/resources --type vm --output-format json" register: cluster_resources delegate_to: "{{ pve_inventory_name }}" become: true vars: ansible_connection: ssh - name: Parse Existing Hosts set_fact: existing_host_conflict: >- {{ cluster_resources.stdout | from_json | selectattr('name', 'equalto', short_hostname) | list }} - name: "FAIL: Host '{{ short_hostname }}' already exists!" fail: msg: > A container with the name '{{ short_hostname }}' already exists! VMID: {{ existing_host_conflict[0].vmid }} Status: {{ existing_host_conflict[0].status }} Node: {{ existing_host_conflict[0].node }} Please delete it manually or choose a different hostname. when: existing_host_conflict | length > 0 # --------------------------------------------------------- # 2. GET TEMP VMID (For the Intermediate Restore) # --------------------------------------------------------- - name: Find First Free VMID (For Template Restore) command: "/usr/bin/pvesh get /cluster/nextid" register: temp_vmid_out delegate_to: "{{ pve_inventory_name }}" vars: ansible_connection: ssh become: true - name: Set Temp VMID set_fact: temp_vmid: "{{ temp_vmid_out.stdout | trim }}" # --------------------------------------------------------- # 3. FIND BACKUP # --------------------------------------------------------- - name: "Fetch raw backup list for {{ template_id }}" command: "/usr/sbin/pvesm list {{ pbs_storage_id }} --content backup" register: backup_list_raw delegate_to: "{{ pve_inventory_name }}" vars: ansible_connection: ssh become: true - name: Parse and Select Latest Backup set_fact: latest_backup_volid: >- {{ backup_list_raw.stdout_lines | select('search', 'ct/' ~ template_id) | sort(reverse=True) | first | split(' ') | first }} - name: Fail if backup not found fail: msg: "Backup for ID {{ template_id }} not found on storage '{{ pbs_storage_id }}'" when: latest_backup_volid is undefined or latest_backup_volid == "" or latest_backup_volid == "Volid" # --------------------------------------------------------- # 4. RESTORE (INTERMEDIATE STEP) # --------------------------------------------------------- - name: "Restore Backup to Temp ID {{ temp_vmid }}" # We restore with the final network settings here so the Clone inherits them command: > /usr/sbin/pct restore {{ temp_vmid }} {{ latest_backup_volid }} --hostname "temp-restore-galera-template" --storage {{ target_disk_storage }} --net0 name=eth0,bridge=vmbr0,firewall=1,ip={{ target_ip }}/24,gw={{ opnsense_ip }} --searchdomain {{ dns_domain }} delegate_to: "{{ pve_inventory_name }}" vars: ansible_connection: ssh become: true # --------------------------------------------------------- # 5. GET FINAL VMID (For the Production Container) # --------------------------------------------------------- - name: Find Second Free VMID (For Final Container) command: "/usr/bin/pvesh get /cluster/nextid" register: final_vmid_out delegate_to: "{{ pve_inventory_name }}" vars: ansible_connection: ssh become: true - name: Set Final VMID set_fact: final_vmid: "{{ final_vmid_out.stdout | trim }}" # --------------------------------------------------------- # 6. CLONE & CONFIGURE # --------------------------------------------------------- - name: "Clone Temp ID {{ temp_vmid }} to Final ID {{ final_vmid }}" # We use --full 1 to ensure it is a standalone container (not dependent on the temp one) # We verify the hostname is set correctly on the final unit command: > /usr/sbin/pct clone {{ temp_vmid }} {{ final_vmid }} --hostname {{ short_hostname }} --full 1 delegate_to: "{{ pve_inventory_name }}" vars: ansible_connection: ssh become: true - name: "Set Proxmox Tags on Final ID" command: "pct set {{ final_vmid }} --tags {{ target_ip }},2222,{{ target_site_group }},galera,database,{{ galera_cluster_name | lower }}" delegate_to: "{{ pve_inventory_name }}" vars: ansible_connection: ssh become: true # --------------------------------------------------------- # 7. CLEANUP & START # --------------------------------------------------------- - name: "Destroy Intermediate Template {{ temp_vmid }}" command: "/usr/sbin/pct destroy {{ temp_vmid }}" delegate_to: "{{ pve_inventory_name }}" vars: ansible_connection: ssh become: true - name: "Start Final Container {{ final_vmid }}" command: "/usr/sbin/pct start {{ final_vmid }}" delegate_to: "{{ pve_inventory_name }}" vars: ansible_connection: ssh become: true # --------------------------------------------------------- # 8. REGISTER DNS # --------------------------------------------------------- - name: Register DNS in OPNsense oxlorg.opnsense.unbound_host: firewall: "{{ opnsense_ip }}" api_key: "{{ lookup('env', 'OPNSENSE_SITE' + galera_segment|string + '_API_KEY') }}" api_secret: "{{ lookup('env', 'OPNSENSE_SITE' + galera_segment|string + '_API_SECRET') }}" ssl_verify: false hostname: "{{ short_hostname }}" domain: "{{ dns_domain }}" value: "{{ target_ip }}" description: "Auto-provisioned via Ansible" record_type: A enabled: true state: present match_fields: [hostname, domain] delegate_to: localhost ``` - If you do not have it yet, you will need to join the inventories (Hetzner + Bachelor Home Lab) using a smart group, in order to run a playbook that will include both inventories. - In AWX, go to **Inventories** -> **Add** -> **Smart Inventory**. - **Name:** `Galera Cluster-A Production`. - **Smart Host Filter:** Essentially, we need to use the ‘Advanced’ filter with ‘or’ operator to add each group. The result should be `groups__name__icontains=witness or groups__name__icontains=cluster_A`. - This creates an inventory that contains **only** the relevant hosts from **both** parent inventories.

1 create the galera config to

- Create a new template for the copying and configuring of a new template. - Name: `G1 - Deploy a Single Galera Node` - Description: `Copy & configure a node from a PBS-stored template` - Inventory: `Galera Cluster-A Production (tick prompt on launch)` - Project: `Ansible Playbooks - Jan's Gitea` - EE: `bachelor-2.18.11-ee` - Playbook: `deploy-galera-node/1-deploy_single_galera_node.yml` - Credentials: `Ansible SSH key` - Privilege Escalation: **tick**

2 create the galera config to

- Save it and go into the ‘Survey’ section and add variables. - Flip the switch to enable the survey! - All questions will be required - tick the ‘Required’ box as you add them (below). - **Q1 - Target Cluster Name** - Description: Cluster name in the config is without _ or - signs - Answer variable name: `galera_cluster_name` - Answer type: multiple choice - Choice options: clusterA, clusterB, clusterC (enter after each) - **Q2 - Target Node Hostname** - Description: Which Galera node would you like to deploy? - Answer variable name: `target_host` - Answer type: multiple choice - Choice options: galera-A1….galera-A10 (enter after each) - **Q3 - Galera Segment ID** - Description: 1 = Uvody, 2 = Tusarka, 3 = Hetzner, 4 = New location - Answer variable name: `target_host` - Answer type: integer - Minimum: 1 - Maximum: 10 - Default: 2 - **Q4 - Quorum Weight** - Description: Standard is 1. Increase for fallback sites. - Answer variable name: `galera_weight` - Answer type: integer - Minimum: 1 - Maximum: 2 - Default: 1 - **Q5 - Target IP** - Description: Specify the IP address of the node - Answer variable name: `target_ip` - Answer type: Multiple choice - Choice options: 192.168.8.71 … 192.168.8.74

3 create the galera config to

## Test G1 deployment - Launch the job and fill in the survey!

4 test g1 deployment

- If it goes well, then during the restoration, you will observe that a container is being restored onto the right Proxmox host:

5 test g1 deployment

- Ensure that the Unbound DNS override was created as well:

6 test g1 deployment

- Then we will need a job that modifies configuration on other nodes to update the `60-galera.cnf`. ```yaml # 2-configure_one_galera_node.yml --- - name: Configure Single Galera Node # Wildcard allows matching "galera-a5" even if inventory has "galera-a5.domain.com" hosts: "{{ target_host.split('.')[0] }}*" become: yes gather_facts: no # Do not SSH in as the LXC will not be ready straight after it is switched on vars: # Default fallbacks galera_segment: 2 galera_weight: 1 galera_sst_user: "sst_user" galera_cluster_name: "{{ galera_cluster_name | default('clusterA') }}" # ROBUST CLUSTER STRING LOGIC # 1. We define the candidate groups: The Galera Cluster + The Witness Group. # (We accept both 'cluster_a' and 'clustera' to handle tag variations). # 2. We Loop through only these hosts. # 3. We use .get() to safely retrieve IPs, defaulting to the hostname if IP is missing. galera_cluster_address_string: >- gcomm://{% for host in ( groups.get(galera_cluster_name | lower, []) + groups.get((galera_cluster_name | lower) | replace('a', '_a'), []) + groups.get('witness_group', []) ) | unique -%} {{ hostvars[host].get('ansible_ip', hostvars[host].get('ansible_host', host)) }} {%- if not loop.last -%},{%- endif -%} {%- endfor %} pre_tasks: - name: Wait for SSH Connection to be available wait_for_connection: delay: 5 timeout: 300 # Wait up to 5 minutes connect_timeout: 5 sleep: 5 - name: Gather Facts (Manual) setup: tasks: - name: Fail if target_host is not defined fail: msg: "You must specify a target_host variable!" when: target_host is not defined - name: Stop MariaDB (to apply new config) systemd: name: mariadb state: stopped - name: "Deploy Galera Config (Segment: {{ galera_segment }}, Weight: {{ galera_weight }})" template: src: 60-galera.cnf.j2 dest: /etc/mysql/mariadb.conf.d/60-galera.cnf owner: root group: root mode: '0644' register: config_pushed - name: Start MariaDB Service systemd: name: mariadb state: started enabled: yes - name: Check Sync Status command: mysql -e "SHOW STATUS LIKE 'wsrep_local_state_comment';" register: node_state changed_when: false failed_when: "'Synced' not in node_state.stdout" retries: 20 delay: 5 # --------------------------------------------------------- # STATUS CHECKS # --------------------------------------------------------- - name: Query Cluster Size and Weight command: mysql -e "SHOW STATUS LIKE '{{ item }}';" loop: - wsrep_cluster_size - wsrep_cluster_weight register: cluster_status changed_when: false - name: Display Cluster Status debug: msg: "{{ item.stdout_lines }}" loop: "{{ cluster_status.results }}" loop_control: label: "{{ item.item }}" ``` ## 3rd script to configure other nodes + Galera witness ```yaml # 3-update_other_nodes_config_after_change.yml --- - name: Update Galera Cluster Configuration (Rolling) hosts: "{{ galera_cluster_name }}" become: yes serial: 1 vars: galera_cluster_address_string: >- gcomm://{% for host in ( groups.get(galera_cluster_name | lower, []) + groups.get((galera_cluster_name | lower) | replace('a', '_a'), []) + groups.get('witness_group', []) ) | unique -%} {{ hostvars[host].get('ansible_ip', hostvars[host].get('ansible_host')) }} galera_segment: "{{ hostvars[inventory_hostname]['galera_segment'] | default(10) }}" tasks: # ----------------------------------------------------------- # BLOCK: DATA NODES ONLY (Segment != 3) # ----------------------------------------------------------- - name: MariaDB Configuration Tasks block: - name: Check current Cluster Address in config command: grep "wsrep_cluster_address" /etc/mysql/mariadb.conf.d/60-galera.cnf register: current_config changed_when: false ignore_errors: yes - name: Update Config and Restart block: - name: Stop MariaDB systemd: name: mariadb state: stopped - name: Deploy Updated Config template: src: ../templates/60-galera.cnf.j2 dest: /etc/mysql/mariadb.conf.d/60-galera.cnf owner: root group: root mode: '0644' - name: Start MariaDB systemd: name: mariadb state: started - name: Wait for Node to Sync command: mysql -e "SHOW STATUS LIKE 'wsrep_local_state_comment';" register: node_state until: node_state.stdout.find("Synced") != -1 retries: 30 delay: 10 # Only run restart block if the string is missing when: galera_cluster_address_string not in current_config.stdout # Only run this block on Data Nodes (Not the Witness on Site 3) when: galera_segment | int != 3 # ----------------------------------------------------------- # NEW TASKS: Final Next Step Reminder # ----------------------------------------------------------- - name: Determine OPNsense Site Name set_fact: opnsense_site_name: "{{ 'Site 1 (U vody)' if galera_segment|int == 1 else 'Site 2 (Tusarka)' }}" when: galera_segment | int != 3 - name: Next Steps Reminder debug: msg: "Remember to verify that you have the host {{ inventory_hostname }} added on {{ opnsense_site_name }}'s HAProxy as a Real Server and in the back-end pool to start serving traffic." when: galera_segment | int != 3 ```