From 03e889051e8fcd505d76b0bf04936bb5bf60e526 Mon Sep 17 00:00:00 2001 From: "Ben D." <=> Date: Mon, 27 Apr 2026 13:15:56 -0700 Subject: [PATCH] Added pfsense upgrade roles --- .gitignore | 5 + inventories/.DS_Store | Bin 0 -> 6148 bytes .../client_template/group_vars/pfsense.yml | 30 +++ inventories/client_template/hosts.yml | 10 +- inventories/clients/.DS_Store | Bin 0 -> 6148 bytes .../clients/brenex/group_vars/pfsense.yml | 30 +++ inventories/clients/brenex/hosts.yml | 10 +- playbooks/pfsense_manage.yml | 24 +++ roles/.DS_Store | Bin 0 -> 10244 bytes roles/hypervisor_backup_config/.DS_Store | Bin 0 -> 6148 bytes roles/linux_patch/.DS_Store | Bin 0 -> 6148 bytes roles/pfsense_upgrade/README.md | 139 +++++++++++++ roles/pfsense_upgrade/ansible.cfg | 12 ++ roles/pfsense_upgrade/defaults/main.yml | 29 +++ roles/pfsense_upgrade/hosts.yml | 29 +++ roles/pfsense_upgrade/tasks/carp.yml | 188 ++++++++++++++++++ roles/pfsense_upgrade/tasks/main.yml | 36 ++++ roles/pfsense_upgrade/tasks/preflight.yml | 41 ++++ roles/pfsense_upgrade/tasks/update_check.yml | 149 ++++++++++++++ roles/pfsense_upgrade/tasks/upgrade.yml | 100 ++++++++++ roles/pfsense_upgrade/tasks/verify.yml | 62 ++++++ .../pfsense_upgrade/tasks/version_detect.yml | 70 +++++++ roles/preflight/.DS_Store | Bin 0 -> 6148 bytes roles/proxmox_ceph/.DS_Store | Bin 0 -> 6148 bytes roles/proxmox_config_backup/.DS_Store | Bin 0 -> 6148 bytes roles/proxmox_drain/.DS_Store | Bin 0 -> 6148 bytes roles/proxmox_ha/.DS_Store | Bin 0 -> 6148 bytes roles/proxmox_preflight/.DS_Store | Bin 0 -> 6148 bytes roles/proxmox_restore/.DS_Store | Bin 0 -> 6148 bytes roles/proxmox_status/.DS_Store | Bin 0 -> 6148 bytes roles/proxmox_upgrade/.DS_Store | Bin 0 -> 6148 bytes roles/proxmox_upgrade_node/.DS_Store | Bin 0 -> 6148 bytes roles/report/.DS_Store | Bin 0 -> 6148 bytes roles/snapshot/.DS_Store | Bin 0 -> 6148 bytes roles/windows_patch/.DS_Store | Bin 0 -> 6148 bytes 35 files changed, 956 insertions(+), 8 deletions(-) create mode 100644 inventories/.DS_Store create mode 100644 inventories/client_template/group_vars/pfsense.yml create mode 100644 inventories/clients/.DS_Store create mode 100644 inventories/clients/brenex/group_vars/pfsense.yml create mode 100644 playbooks/pfsense_manage.yml create mode 100644 roles/.DS_Store create mode 100644 roles/hypervisor_backup_config/.DS_Store create mode 100644 roles/linux_patch/.DS_Store create mode 100644 roles/pfsense_upgrade/README.md create mode 100644 roles/pfsense_upgrade/ansible.cfg create mode 100644 roles/pfsense_upgrade/defaults/main.yml create mode 100644 roles/pfsense_upgrade/hosts.yml create mode 100644 roles/pfsense_upgrade/tasks/carp.yml create mode 100644 roles/pfsense_upgrade/tasks/main.yml create mode 100644 roles/pfsense_upgrade/tasks/preflight.yml create mode 100644 roles/pfsense_upgrade/tasks/update_check.yml create mode 100644 roles/pfsense_upgrade/tasks/upgrade.yml create mode 100644 roles/pfsense_upgrade/tasks/verify.yml create mode 100644 roles/pfsense_upgrade/tasks/version_detect.yml create mode 100644 roles/preflight/.DS_Store create mode 100644 roles/proxmox_ceph/.DS_Store create mode 100644 roles/proxmox_config_backup/.DS_Store create mode 100644 roles/proxmox_drain/.DS_Store create mode 100644 roles/proxmox_ha/.DS_Store create mode 100644 roles/proxmox_preflight/.DS_Store create mode 100644 roles/proxmox_restore/.DS_Store create mode 100644 roles/proxmox_status/.DS_Store create mode 100644 roles/proxmox_upgrade/.DS_Store create mode 100644 roles/proxmox_upgrade_node/.DS_Store create mode 100644 roles/report/.DS_Store create mode 100644 roles/snapshot/.DS_Store create mode 100644 roles/windows_patch/.DS_Store diff --git a/.gitignore b/.gitignore index b257325..37395a2 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,8 @@ __pycache__/ .ansible/ fact_cache/ *.swp +.DS_Store +.AppleDouble +.LSOverride +.Spotlight-V100 +.fseventsd diff --git a/inventories/.DS_Store b/inventories/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..a09d8583c92749b769ecd7dab1711b788c10f2bc GIT binary patch literal 6148 zcmeHKu};G<5IvVl6fw}POeh0C(1G1l=@*2B0Rc%6ASEPHVPlB+4nBi_U;_REA$92| z@XlwoNmB%30EF(O^K;I3vF|yub3|lDH|db5PeeT^V`l@+3gL0qmUO&@E6-9?8T+BG(}!4mcDa$ z`?mYM%B#85881~u8Bhk4fnQ;O4@5|BQ&3xFKp9X5<_z%p5I`BN$4by09cWw$0PMo- z1bwb0I41CDJywDkffxw|N~rNAhLLdC1Mg=&R)P{v#up#P&un~!V%+R_f8gB7SV3)- z0cD`cz?wYv`Tie&T>m$N^hp^|2L2TTrW;1#2r2oywUr#-wE^@5%EEq?;5-B!cNN2z dui|Z}6W9ap0IkPL5Eh932sj$FQ3igLfp^vyWrP3# literal 0 HcmV?d00001 diff --git a/inventories/client_template/group_vars/pfsense.yml b/inventories/client_template/group_vars/pfsense.yml new file mode 100644 index 0000000..74e9411 --- /dev/null +++ b/inventories/client_template/group_vars/pfsense.yml @@ -0,0 +1,30 @@ +--- +# inventory/group_vars/pfsense.yml +# Applied to all hosts in the [pfsense] group. + +# pfSense runs FreeBSD — Python may not be installed. +# Using 'raw' module throughout the role avoids this entirely, +# but set the interpreter discovery to auto for safety. +ansible_python_interpreter: auto_silent + +# SSH connection settings tuned for pfSense/FreeBSD +ansible_connection: ssh +ansible_ssh_common_args: >- + -o StrictHostKeyChecking=no + -o UserKnownHostsFile=/dev/null + -o ConnectTimeout=15 + -o ServerAliveInterval=10 + -o ServerAliveCountMax=3 + +# pfSense's shell is tcsh by default; force sh for compatibility +ansible_shell_type: sh +ansible_shell_executable: /bin/sh + +# Set to your SSH key or use ansible_password +# ansible_ssh_private_key_file: ~/.ssh/pfsense_rsa + +# Default upgrade settings (can be overridden per host in host_vars/) +perform_upgrade: false +allow_major_upgrade: false +auto_reboot: true +pkg_repo_update: true diff --git a/inventories/client_template/hosts.yml b/inventories/client_template/hosts.yml index 710f045..20e1b91 100644 --- a/inventories/client_template/hosts.yml +++ b/inventories/client_template/hosts.yml @@ -11,7 +11,7 @@ all: human_estimate_seconds: 2700 change_freeze: false ansible_ssh_extra_args: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" - + children: # --- NETWORK LAYER --- firewalls: @@ -20,11 +20,14 @@ all: hosts: client-fw-01: ansible_host: "{{ FW_HOST }}" - + pfsense: hosts: client-fw-01: ansible_host: "{{ FW_HOST }}" + ansible_port: 22222 + ha_role: "primary" + #ha_peer: "client-fw-02" # Uncomment if this node is part an HA pair # --- INFRASTRUCTURE --- hypervisors: @@ -37,7 +40,7 @@ all: hosts: client-xcp-01: ansible_host: "{{ XCP_HOST }}" - + # --- WORKSTATIONS/SERVERS --- linux_hosts: hosts: {} @@ -54,4 +57,3 @@ all: ansible_winrm_transport: ntlm ansible_winrm_server_cert_validation: validate ansible_port: 5986 - diff --git a/inventories/clients/.DS_Store b/inventories/clients/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..59667d3911c308884f768c0da4081035e029980e GIT binary patch literal 6148 zcmeHLyH3L}6upM36iOvV#tev=ij9R(l`kj)iKTC7rG`|LB0vmTSQ(kw-~;#vCYTtJ z*bsuB;9T1jyGaWY5<ztf4c3mP8g?=MXG)F`k6vosHs)XQvF0q)Sb`8z}=fGccfZyFT)o7oZw1+bE{-!U7`-8AptaZW~+B50p&6~yh z+qF2S@g=9*GUo{oLtv6=&<<6pOb1X>rHyC6Y|XQ;hxvd<9#v4FjbA>B(`C!seVLSI?v9g2`w$M~M4lkgR~+Bx7HupO9Gr*pjiuaDmU+ez-sIp7@lR}P3& zu~aPJmh{>hyE$HKJ(LOx2lFC@G6j`hj^zO_#r(g54D@+?0Q5CR3ekdaKLn%=u5u3i Gssrya<>Yh# literal 0 HcmV?d00001 diff --git a/inventories/clients/brenex/group_vars/pfsense.yml b/inventories/clients/brenex/group_vars/pfsense.yml new file mode 100644 index 0000000..74e9411 --- /dev/null +++ b/inventories/clients/brenex/group_vars/pfsense.yml @@ -0,0 +1,30 @@ +--- +# inventory/group_vars/pfsense.yml +# Applied to all hosts in the [pfsense] group. + +# pfSense runs FreeBSD — Python may not be installed. +# Using 'raw' module throughout the role avoids this entirely, +# but set the interpreter discovery to auto for safety. +ansible_python_interpreter: auto_silent + +# SSH connection settings tuned for pfSense/FreeBSD +ansible_connection: ssh +ansible_ssh_common_args: >- + -o StrictHostKeyChecking=no + -o UserKnownHostsFile=/dev/null + -o ConnectTimeout=15 + -o ServerAliveInterval=10 + -o ServerAliveCountMax=3 + +# pfSense's shell is tcsh by default; force sh for compatibility +ansible_shell_type: sh +ansible_shell_executable: /bin/sh + +# Set to your SSH key or use ansible_password +# ansible_ssh_private_key_file: ~/.ssh/pfsense_rsa + +# Default upgrade settings (can be overridden per host in host_vars/) +perform_upgrade: false +allow_major_upgrade: false +auto_reboot: true +pkg_repo_update: true diff --git a/inventories/clients/brenex/hosts.yml b/inventories/clients/brenex/hosts.yml index b8ff2dd..2fe25c4 100644 --- a/inventories/clients/brenex/hosts.yml +++ b/inventories/clients/brenex/hosts.yml @@ -22,7 +22,9 @@ all: vendor: "pfsense" ansible_host: "fw.brenex.com" ansible_port: 22222 - + ha_role: "primary" + #ha_peer: "fw-ha-secondary" # Uncomment if this node is part of an HA pair + xcpng_pools: vars: ansible_become: false @@ -32,7 +34,7 @@ all: shared_storage: false upgrade_order: - brenex-pool-01 - + hosts: brenex-pool-01: ansible_host: 192.168.123.11 @@ -41,7 +43,7 @@ all: vars: ansible_user: root os_family: "debian" - + hosts: caddy-server: ansible_host: 192.168.123.16 @@ -52,7 +54,7 @@ all: ansible_host: 192.168.123.146 graylog-server: ansible_host: 192.168.123.16 - + windows_hosts: diff --git a/playbooks/pfsense_manage.yml b/playbooks/pfsense_manage.yml new file mode 100644 index 0000000..d4d3aa6 --- /dev/null +++ b/playbooks/pfsense_manage.yml @@ -0,0 +1,24 @@ +--- +# pfSense Upgrade Playbook +# Upgrades pfSense systems within their current version branch. +# Detects available stable releases and reports or applies upgrades. +# +# Usage: +# ansible-playbook upgrade.yml -i inventory/hosts.yml +# ansible-playbook upgrade.yml -i inventory/hosts.yml --tags check # dry-run only +# ansible-playbook upgrade.yml -i inventory/hosts.yml -e "perform_upgrade=true" +# ansible-playbook upgrade.yml -i inventory/hosts.yml -e "perform_upgrade=true allow_major_upgrade=true" + +- name: pfSense Upgrade + hosts: pfsense + gather_facts: false + serial: 1 # Upgrade one host at a time to preserve redundancy + + vars: + perform_upgrade: false # Safety gate — must be explicitly set to true + allow_major_upgrade: false # Set true to allow crossing major version branches + reboot_timeout: 300 # Seconds to wait for host after reboot + upgrade_check_timeout: 120 # Seconds before pfSense-upgrade check times out + + roles: + - pfsense_upgrade diff --git a/roles/.DS_Store b/roles/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..f57b2d2d53f5c2c276d6a9ff0234789234c94241 GIT binary patch literal 10244 zcmeHMF>4e-82#1+In5<17zo;|g&~oU7(~;E+=WYHAwr6P2+1Xv+^KijBbRsz7H4Z= zA^rxzDi(r;O**kq3yXCAhWr2v-#4?)&fL!K?Hxe^&cN=sx&7YEdvA7TcP2#SE$&t3 ziF_i;;ox{akCUbFeLiC4RQStnL=SkP9ePTSs6_#;TyYLK2b=@W0q1~o;3PVLcQ%*Y zh>Y&+9B>Xe2b2RmKlnH}7Q)z(QA`~;i6sEyFn$&V*H{M#j335A7&|fwDC(4}2QP$r z;S$4>aNPGf9kCF`j*Jpco`jPZw(Nx~6i>7coG;SJE6C{1&H?8@+yP#@Pf(4XQ2{^X(LpJ+HL562K`OAu?rV!ruv!p$(g#wYX?eWLcNV3Qqnuy!uJd~ z?w}X91n(+(b=TxNy8^D3ES=}(SH;ts8F5U{H2JNeVaf}B0o-ihSz=E&z_!`#=-kh= zT%GHOL&Y_*Zr#-^`4-;e7T#(sK!r9=Ouy$|KuI5HUEA2_haZoAh%wPu4N?CLR)abq1#q$eA~bc5slJm|9yw_Xh)wBvZE||#$tL{3%@1ysAa0TaRq8p^K~t+E~`H3fr)x2E5@QD z?iauDt742-y8Gqvg0)`^gs!EukkT(*s7OtAl&R+c)elzJ z_-+Kl@;|95W`|E`>=wj7<$-L`sWVs+`hrR7<8jZ#~D&sD&w4Q`3wbd!@{n3An?z4}sd zO+`dbW;csqUy2j^br$OYGhFm`0bFDU=~p8;xOlz&c)uQx%YPNOeU7uXChtc& z!(Lj`j$U*}wce{^-`icw#apwMPJUUns~C^y*Y{lC#27FJjDb^S06m)}S}59R3>X8( zK*IokA3T&X30N!2rvrl=0e~}@MKG6tH=t(&FbP;I!UA!c3e;2=ju=kUVfP^}30Ny? zIytF~`-I9aoKT!phuup!xkS-MW55{bGceW5CHMcEpU?mOB)c*OjDeG4z|GPuUEz^J xZyh`w_gW9VgR*d3t=ObsBCQy)+=?%uBCva&0F!{VA}kR55fB<|Fb4jVfiD;XO(y^V literal 0 HcmV?d00001 diff --git a/roles/linux_patch/.DS_Store b/roles/linux_patch/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..6b0fb4c3f0674940b704419a7841b9d08b5ce61b GIT binary patch literal 6148 zcmeHKOKQVF4AmqlIAjy(rpvj4Z{P;!1m1Pkr0xf5<3O6dPtbGp4uwKjUG*Y8MW05( zpW214N(d5Y-dG=N{5)75B66!k9uqBzD1tIh<`_nV{j39-sD+&_+~Yt6J<-0nkF!Cv z8IF+wdAT{=(j9H6^ZstCa;FP^1xEBErmWI*TU1;2&EFQ!SDzo3<8k?);&u;l*4E_x zNN3nf8`{&O?5Ngz{?+$(*Yfh-tfiA*R_!XrBl`6{*EcZ+i~(cdR2e|eW{DPpHW~xQ zfHBZ8z~2WCWlR)n!SLz85RL%A8O%X25C3jJ&jw(kSPQ}eagqv@RHsJ_C+V Note: The GitHub master branch reflects the latest stable CE release. Adjust `pfsense_release_url` if you track a specific branch or use an internal mirror. + +--- + +## HA / CARP Considerations + +- `serial: 1` in the playbook ensures only one host upgrades at a time +- For CARP HA pairs, upgrade the **secondary first** so the primary continues to pass traffic +- Group your HA pairs in the inventory and order hosts accordingly +- Consider adding a task to demote the primary before upgrading if you need zero-downtime + +--- + +## File Structure + +``` +pfsense-upgrade/ +├── ansible.cfg +├── upgrade.yml # Main playbook entry point +├── inventory/ +│ ├── hosts.yml # Your pfSense hosts +│ └── group_vars/ +│ └── pfsense.yml # Shared SSH + default vars +└── roles/ + └── pfsense_upgrade/ + ├── defaults/ +│ └── main.yml # All default variable values + └── tasks/ + ├── main.yml # Task orchestration + ├── preflight.yml # SSH check, disk space, binary check + ├── version_detect.yml # Read and parse current version + ├── update_check.yml # pfSense-upgrade check + upstream compare + ├── upgrade.yml # Backup + execute upgrade + └── verify.yml # Post-reboot version + service check +``` diff --git a/roles/pfsense_upgrade/ansible.cfg b/roles/pfsense_upgrade/ansible.cfg new file mode 100644 index 0000000..47bf01a --- /dev/null +++ b/roles/pfsense_upgrade/ansible.cfg @@ -0,0 +1,12 @@ +[defaults] +inventory = inventory/hosts.yml +roles_path = roles +host_key_checking = False +timeout = 30 +forks = 1 # Never upgrade pfSense hosts in parallel +stdout_callback = yaml +callbacks_enabled = timer, profile_tasks + +[ssh_connection] +ssh_args = -o ControlMaster=no -o ControlPersist=no +pipelining = False # Disabled — pfSense/FreeBSD SSH may not support it diff --git a/roles/pfsense_upgrade/defaults/main.yml b/roles/pfsense_upgrade/defaults/main.yml new file mode 100644 index 0000000..8fa828e --- /dev/null +++ b/roles/pfsense_upgrade/defaults/main.yml @@ -0,0 +1,29 @@ +--- +# roles/pfsense_upgrade/defaults/main.yml +# Override any of these in group_vars, host_vars, or at the CLI with -e + +# --- Safety gates --- +perform_upgrade: false # Must explicitly set to true to apply upgrades +allow_major_upgrade: false # Set true to permit branch-crossing upgrades (e.g. 2.7 → 2.8) +skip_backup_check: false # Set true to skip the pre-upgrade config backup step + +# --- Upgrade behavior --- +auto_reboot: true # Reboot automatically after upgrade if required +reboot_timeout: 300 # Seconds to wait for host to come back after reboot +upgrade_check_timeout: 120 # Timeout for pfSense-upgrade version check +pkg_repo_update: true # Run pkg update before checking for upgrades + +# --- Notification --- +# Optional: set to a Slack/Teams webhook URL to post upgrade results +notify_webhook_url: "" + +# --- pfSense paths --- +pfsense_version_file: /etc/version +pfsense_version_patch_file: /etc/version.patch +pfsense_version_buildtime: /etc/version.buildtime +pfsense_upgrade_bin: /usr/local/sbin/pfSense-upgrade +pfsense_config_backup_path: /cf/conf/backup + +# --- Release tracking --- +# Netgate publishes release notes/versions at this URL (CE edition) +pfsense_release_url: "https://raw.githubusercontent.com/pfsense/pfsense/master/src/etc/version" diff --git a/roles/pfsense_upgrade/hosts.yml b/roles/pfsense_upgrade/hosts.yml new file mode 100644 index 0000000..3a0f04e --- /dev/null +++ b/roles/pfsense_upgrade/hosts.yml @@ -0,0 +1,29 @@ +--- +# inventory/hosts.yml +# Define your pfSense hosts here. +# Group them by site, role, or redundancy pair as needed. + +all: + children: + pfsense: + children: + + # --- Primary/standalone firewalls --- + pfsense_primary: + hosts: + fw-site-a: + ansible_host: 192.168.1.1 + ansible_user: admin + fw-site-b: + ansible_host: 10.10.0.1 + ansible_user: admin + + # --- HA pairs (upgrade sequentially — serial: 1 ensures this) --- + pfsense_ha_pair_1: + hosts: + fw-ha-primary: + ansible_host: 172.16.0.1 + ansible_user: admin + fw-ha-secondary: + ansible_host: 172.16.0.2 + ansible_user: admin diff --git a/roles/pfsense_upgrade/tasks/carp.yml b/roles/pfsense_upgrade/tasks/carp.yml new file mode 100644 index 0000000..56b6f56 --- /dev/null +++ b/roles/pfsense_upgrade/tasks/carp.yml @@ -0,0 +1,188 @@ +--- +# roles/pfsense_upgrade/tasks/carp.yml +# Handles CARP/HA awareness during upgrades. +# Only runs when ha_peer is defined. +# +# ha_role: backup → minimal pre-checks, upgrade proceeds normally +# ha_role: primary → full CARP state verification, forced demotion, peer version check + +# --------------------------------------------------------------------------- +# Backup node logic — runs on the backup before it upgrades +# --------------------------------------------------------------------------- +- name: "[CARP/backup] Verify primary peer is reachable before upgrading backup" + ansible.builtin.raw: echo "ping" + delegate_to: "{{ ha_peer }}" + register: _peer_reachable + failed_when: _peer_reachable.rc != 0 + when: ha_role == 'backup' + +- name: "[CARP/backup] Check current CARP state on this node" + ansible.builtin.raw: > + php -r "require_once('/etc/inc/interfaces.inc'); + \$carp = get_carp_status(); + echo \$carp;" + register: _backup_carp_state + changed_when: false + when: ha_role == 'backup' + +- name: "[CARP/backup] Display CARP state" + ansible.builtin.debug: + msg: "CARP state on {{ inventory_hostname }} (backup): {{ _backup_carp_state.stdout | trim }}" + when: ha_role == 'backup' + +# --------------------------------------------------------------------------- +# Primary node logic — runs on the primary before it upgrades +# --------------------------------------------------------------------------- + +# --- Step 1: Verify peer is reachable --- +- name: "[CARP/primary] Verify backup peer {{ ha_peer }} is reachable" + ansible.builtin.raw: echo "ping" + delegate_to: "{{ ha_peer }}" + register: _peer_reachable + failed_when: _peer_reachable.rc != 0 + when: + - ha_role == 'primary' + - ha_peer is defined + +# --- Step 2: Verify peer is on the upgraded version --- +- name: "[CARP/primary] Read version on backup peer {{ ha_peer }}" + ansible.builtin.raw: cat {{ pfsense_version_file }} + delegate_to: "{{ ha_peer }}" + register: _peer_version_raw + changed_when: false + when: + - ha_role == 'primary' + - ha_peer is defined + +- name: "[CARP/primary] Set peer version fact" + ansible.builtin.set_fact: + ha_peer_version: "{{ _peer_version_raw.stdout | trim }}" + when: + - ha_role == 'primary' + - ha_peer is defined + +- name: "[CARP/primary] Fail if backup peer is not on a newer version than primary" + ansible.builtin.fail: + msg: > + Backup peer {{ ha_peer }} is running {{ ha_peer_version }}, which is the same + as or older than this primary ({{ pfsense_current_version }}). + Upgrade the backup node first before proceeding with the primary. + when: + - ha_role == 'primary' + - ha_peer is defined + - ha_peer_version == pfsense_current_version + +# --- Step 3: Verify backup peer is in MASTER CARP state --- +- name: "[CARP/primary] Check CARP state on backup peer {{ ha_peer }}" + ansible.builtin.raw: > + ifconfig | grep -o 'carp: [A-Z]*' | awk '{print $2}' | sort -u + delegate_to: "{{ ha_peer }}" + register: _peer_carp_state_raw + changed_when: false + when: + - ha_role == 'primary' + - ha_peer is defined + +- name: "[CARP/primary] Fail if backup peer is not MASTER" + ansible.builtin.fail: + msg: > + Backup peer {{ ha_peer }} CARP state is '{{ _peer_carp_state_raw.stdout | trim }}'. + Expected MASTER. Resolve CARP state before upgrading the primary. + when: + - ha_role == 'primary' + - ha_peer is defined + - "'MASTER' not in _peer_carp_state_raw.stdout" + +# --- Step 4: Force demotion of primary --- +- name: "[CARP/primary] Force CARP demotion on this node (set advskew to 254)" + ansible.builtin.raw: > + php -r " + require_once('/etc/inc/interfaces.inc'); + interfaces_carp_set_maintenancemode(true); + " + register: _carp_demotion + changed_when: true + when: + - ha_role == 'primary' + - ha_peer is defined + +- name: "[CARP/primary] Wait for CARP failover to settle" + ansible.builtin.pause: + seconds: 15 + +# --- Step 5: Re-verify backup peer has taken MASTER --- +- name: "[CARP/primary] Re-check CARP state on backup peer after demotion" + ansible.builtin.raw: > + ifconfig | grep -o 'carp: [A-Z]*' | awk '{print $2}' | sort -u + delegate_to: "{{ ha_peer }}" + register: _peer_carp_recheck + changed_when: false + when: + - ha_role == 'primary' + - ha_peer is defined + +- name: "[CARP/primary] Fail if backup peer did not take MASTER after demotion" + ansible.builtin.fail: + msg: > + Backup peer {{ ha_peer }} did not take MASTER after primary demotion. + CARP state is '{{ _peer_carp_recheck.stdout | trim }}'. + Investigate CARP before proceeding — primary has been demoted but backup is not MASTER. + when: + - ha_role == 'primary' + - ha_peer is defined + - "'MASTER' not in _peer_carp_recheck.stdout" + +- name: "[CARP/primary] CARP demotion confirmed — backup is MASTER, safe to upgrade primary" + ansible.builtin.debug: + msg: > + {{ ha_peer }} is MASTER. {{ inventory_hostname }} is demoted. + Proceeding with primary upgrade. + when: + - ha_role == 'primary' + - ha_peer is defined + +# --------------------------------------------------------------------------- +# Post-upgrade: restore CARP on primary and verify state +# --------------------------------------------------------------------------- +- name: "[CARP/primary] Re-enable CARP maintenance mode off (restore advskew)" + ansible.builtin.raw: > + php -r " + require_once('/etc/inc/interfaces.inc'); + interfaces_carp_set_maintenancemode(false); + " + register: _carp_restore + changed_when: true + when: + - ha_role == 'primary' + - ha_peer is defined + +- name: "[CARP/primary] Wait for CARP state to stabilize after restore" + ansible.builtin.pause: + seconds: 20 + +- name: "[CARP/primary] Verify primary has reclaimed MASTER" + ansible.builtin.raw: > + ifconfig | grep -o 'carp: [A-Z]*' | awk '{print $2}' | sort -u + register: _primary_carp_final + changed_when: false + when: + - ha_role == 'primary' + - ha_peer is defined + +- name: "[CARP/primary] Warn if primary did not reclaim MASTER" + ansible.builtin.debug: + msg: > + WARNING: Primary CARP state is '{{ _primary_carp_final.stdout | trim }}' — expected MASTER. + This may resolve on its own. Check CARP status on both nodes manually. + when: + - ha_role == 'primary' + - ha_peer is defined + - "'MASTER' not in _primary_carp_final.stdout" + +- name: "[CARP/primary] CARP state confirmed restored" + ansible.builtin.debug: + msg: "{{ inventory_hostname }} has reclaimed MASTER. HA pair is fully operational." + when: + - ha_role == 'primary' + - ha_peer is defined + - "'MASTER' in _primary_carp_final.stdout" \ No newline at end of file diff --git a/roles/pfsense_upgrade/tasks/main.yml b/roles/pfsense_upgrade/tasks/main.yml new file mode 100644 index 0000000..52ac2ed --- /dev/null +++ b/roles/pfsense_upgrade/tasks/main.yml @@ -0,0 +1,36 @@ +--- +# roles/pfsense_upgrade/tasks/main.yml + +- name: Include pre-flight checks + ansible.builtin.import_tasks: preflight.yml + tags: [always, preflight, check] + +- name: Include version detection + ansible.builtin.import_tasks: version_detect.yml + tags: [always, check] + +- name: Include update check + ansible.builtin.import_tasks: update_check.yml + tags: [always, check] + +- name: Include CARP/HA pre-upgrade logic + ansible.builtin.import_tasks: carp.yml + tags: [always, check, carp] + when: ha_peer is defined + +- name: Include upgrade execution + ansible.builtin.import_tasks: upgrade.yml + tags: [upgrade] + when: perform_upgrade | bool + +- name: Include CARP/HA post-upgrade restore + ansible.builtin.import_tasks: carp.yml + tags: [upgrade, carp] + when: + - ha_peer is defined + - perform_upgrade | bool + +- name: Include post-upgrade verification + ansible.builtin.import_tasks: verify.yml + tags: [upgrade, verify] + when: perform_upgrade | bool \ No newline at end of file diff --git a/roles/pfsense_upgrade/tasks/preflight.yml b/roles/pfsense_upgrade/tasks/preflight.yml new file mode 100644 index 0000000..3500ac7 --- /dev/null +++ b/roles/pfsense_upgrade/tasks/preflight.yml @@ -0,0 +1,41 @@ +--- +# roles/pfsense_upgrade/tasks/preflight.yml +# Validate SSH connectivity, confirm host is pfSense, check disk space. + +- name: Verify SSH connectivity to pfSense host + ansible.builtin.raw: echo "ping" + register: _ssh_test + changed_when: false + failed_when: _ssh_test.rc != 0 + +- name: Confirm host is running pfSense (check version file) + ansible.builtin.raw: test -f {{ pfsense_version_file }} && echo "pfsense_ok" + register: _pfsense_check + changed_when: false + failed_when: "'pfsense_ok' not in _pfsense_check.stdout" + +- name: Check available disk space on root filesystem (must be ≥ 200 MB) + ansible.builtin.raw: > + df -m / | awk 'NR==2 {print $4}' + register: _disk_avail + changed_when: false + +- name: Fail if disk space is insufficient + ansible.builtin.fail: + msg: > + Host {{ inventory_hostname }} only has {{ _disk_avail.stdout | trim }} MB free on /. + At least 200 MB is required to safely upgrade pfSense. + when: (_disk_avail.stdout | trim | int) < 200 + +- name: Check that pfSense-upgrade binary exists + ansible.builtin.raw: test -x {{ pfsense_upgrade_bin }} && echo "bin_ok" + register: _bin_check + changed_when: false + failed_when: "'bin_ok' not in _bin_check.stdout" + +- name: Pre-flight summary + ansible.builtin.debug: + msg: > + Pre-flight OK — {{ inventory_hostname }}: + disk free={{ _disk_avail.stdout | trim }}MB, + pfSense-upgrade binary present. diff --git a/roles/pfsense_upgrade/tasks/update_check.yml b/roles/pfsense_upgrade/tasks/update_check.yml new file mode 100644 index 0000000..caa0e48 --- /dev/null +++ b/roles/pfsense_upgrade/tasks/update_check.yml @@ -0,0 +1,149 @@ +--- +# roles/pfsense_upgrade/tasks/update_check.yml +# Checks for available upgrades using pfSense-upgrade -c and pkg version. +# Also queries upstream for the latest stable release on this branch. + +# --------------------------------------------------------------------------- +# 1. Refresh the local pkg repository metadata +# --------------------------------------------------------------------------- +- name: Update pkg repository metadata + ansible.builtin.raw: pkg update -f 2>&1 + register: _pkg_update + changed_when: false + when: pkg_repo_update | bool + timeout: "{{ upgrade_check_timeout }}" + +# --------------------------------------------------------------------------- +# 2. Run pfSense-upgrade in check-only mode +# --------------------------------------------------------------------------- +- name: Run pfSense-upgrade --check (dry run) + ansible.builtin.raw: > + {{ pfsense_upgrade_bin }} -d -c 2>&1 + register: _upgrade_check + changed_when: false + timeout: "{{ upgrade_check_timeout }}" + # pfSense-upgrade exits 0 when up-to-date, non-zero when upgrade available. + # We capture both cases. + failed_when: false + +- name: Parse upgrade check output + ansible.builtin.set_fact: + upgrade_check_stdout: "{{ _upgrade_check.stdout | trim }}" + upgrade_check_rc: "{{ _upgrade_check.rc }}" + # True if the tool reports an update is available + upgrade_available: >- + {{ + _upgrade_check.rc != 0 or + 'Upgraded' in _upgrade_check.stdout or + 'update' in _upgrade_check.stdout | lower and + 'up to date' not in _upgrade_check.stdout | lower + }} + # Attempt to extract the new version string from the upgrade check output + # pfSense-upgrade typically prints: "pfSense-upgrade: New version available: 2.7.3-RELEASE" + upgrade_available_version: >- + {{ + _upgrade_check.stdout + | regex_search('(\d+\.\d+\.\d+[-a-zA-Z0-9]*)', '\1') + | first | default('unknown') + }} + +# --------------------------------------------------------------------------- +# 3. Check pkg for pending package updates (captures sub-component updates) +# --------------------------------------------------------------------------- +- name: Check for pending pkg upgrades (outdated packages) + ansible.builtin.raw: pkg version -l '<' 2>&1 | head -40 + register: _pkg_outdated + changed_when: false + failed_when: false + +- name: Count outdated packages + ansible.builtin.set_fact: + pkg_outdated_count: "{{ _pkg_outdated.stdout_lines | reject('match', '^\\s*$') | list | length }}" + pkg_outdated_list: "{{ _pkg_outdated.stdout | trim }}" + +# --------------------------------------------------------------------------- +# 4. Detect the latest stable release for this branch via GitHub +# --------------------------------------------------------------------------- +- name: Fetch latest stable release version from Netgate/pfSense repo + ansible.builtin.raw: > + fetch -q -o - "{{ pfsense_release_url }}" 2>/dev/null || echo "fetch_failed" + register: _upstream_version_raw + changed_when: false + failed_when: false + +- name: Parse upstream latest stable version + ansible.builtin.set_fact: + upstream_version: "{{ _upstream_version_raw.stdout | trim }}" + upstream_fetch_ok: "{{ 'fetch_failed' not in _upstream_version_raw.stdout }}" + +- name: Derive upstream branch (major.minor) + ansible.builtin.set_fact: + upstream_major_minor: >- + {{ + upstream_version + | regex_replace('^(\d+\.\d+).*$', '\1') + | default(pfsense_major_minor) + }} + when: upstream_fetch_ok | bool + +# --------------------------------------------------------------------------- +# 5. Compare branches — detect if a newer stable branch exists upstream +# --------------------------------------------------------------------------- +- name: Determine if a newer major release branch is available + ansible.builtin.set_fact: + new_major_release_available: >- + {{ + upstream_fetch_ok | bool and + (upstream_major_minor | string) != (pfsense_major_minor | string) and + (upstream_major_minor.split('.')[0] | int > pfsense_major_minor.split('.')[0] | int) or + (upstream_major_minor.split('.')[0] | int == pfsense_major_minor.split('.')[0] | int and + upstream_major_minor.split('.')[1] | int > pfsense_major_minor.split('.')[1] | int) + }} + when: upstream_fetch_ok | bool + +- name: Default new_major_release_available when fetch failed + ansible.builtin.set_fact: + new_major_release_available: false + when: not (upstream_fetch_ok | bool) + +# --------------------------------------------------------------------------- +# 6. Print the full update status report +# --------------------------------------------------------------------------- +- name: Display update status report + ansible.builtin.debug: + msg: + - "============================================================" + - " Update Status: {{ inventory_hostname }}" + - "============================================================" + - " Current version : {{ pfsense_current_version }}" + - " Current branch : {{ pfsense_major_minor }}" + - "------------------------------------------------------------" + - " In-branch update : {{ 'YES — ' ~ upgrade_available_version if upgrade_available | bool else 'No — already up to date' }}" + - " Outdated pkgs : {{ pkg_outdated_count }} package(s) behind" + - "------------------------------------------------------------" + - " Upstream latest : {{ upstream_version if upstream_fetch_ok | bool else 'Could not reach upstream' }}" + - " Upstream branch : {{ upstream_major_minor if upstream_fetch_ok | bool else 'N/A' }}" + - " New branch avail : {{ 'YES — ' ~ upstream_version if new_major_release_available | bool else 'No' }}" + - "------------------------------------------------------------" + - " perform_upgrade : {{ perform_upgrade | bool }}" + - " allow_major_upg : {{ allow_major_upgrade | bool }}" + - "============================================================" + +- name: Warn if a new major release branch is available but not allowed + ansible.builtin.debug: + msg: > + WARNING: pfSense {{ upstream_version }} is available on branch {{ upstream_major_minor }}, + which is newer than your running branch {{ pfsense_major_minor }}. + To upgrade across branches, re-run with: -e "perform_upgrade=true allow_major_upgrade=true" + when: + - new_major_release_available | bool + - not (allow_major_upgrade | bool) + +- name: Warn if perform_upgrade is false but upgrades are available + ansible.builtin.debug: + msg: > + DRY RUN — upgrades are available but perform_upgrade=false. + Re-run with -e "perform_upgrade=true" to apply. + when: + - (upgrade_available | bool) or (pkg_outdated_count | int > 0) + - not (perform_upgrade | bool) diff --git a/roles/pfsense_upgrade/tasks/upgrade.yml b/roles/pfsense_upgrade/tasks/upgrade.yml new file mode 100644 index 0000000..adb750d --- /dev/null +++ b/roles/pfsense_upgrade/tasks/upgrade.yml @@ -0,0 +1,100 @@ +--- +# roles/pfsense_upgrade/tasks/upgrade.yml +# Applies the upgrade after safety checks pass. +# Only runs when perform_upgrade=true. + +- name: Abort if no upgrade is available (nothing to do) + ansible.builtin.debug: + msg: > + No in-branch upgrade is available for {{ inventory_hostname }}. + Current version {{ pfsense_current_version }} is already the latest on branch {{ pfsense_major_minor }}. + Skipping upgrade. + when: + - not (upgrade_available | bool) + - not (new_major_release_available | bool and allow_major_upgrade | bool) + +- name: End play for this host if nothing to upgrade + ansible.builtin.meta: end_host + when: + - not (upgrade_available | bool) + - not (new_major_release_available | bool and allow_major_upgrade | bool) + +# --------------------------------------------------------------------------- +# Branch-crossing guard +# --------------------------------------------------------------------------- +- name: Abort if major upgrade is available but not explicitly allowed + ansible.builtin.fail: + msg: > + A new pfSense branch is available ({{ upstream_version }}) but allow_major_upgrade=false. + Review the release notes for {{ upstream_version }} before upgrading across branches. + Re-run with -e "allow_major_upgrade=true" when ready. + when: + - new_major_release_available | bool + - not (allow_major_upgrade | bool) + - not (upgrade_available | bool) + +# --------------------------------------------------------------------------- +# Pre-upgrade config backup +# --------------------------------------------------------------------------- +- name: Trigger config backup via PHP (writes to /cf/conf/backup/) + ansible.builtin.raw: > + php -r "require_once('/etc/inc/config.inc'); + require_once('/etc/inc/util.inc'); + backup_config();" + register: _backup_result + changed_when: false + when: not (skip_backup_check | bool) + +- name: Confirm backup file was created + ansible.builtin.raw: > + ls -t {{ pfsense_config_backup_path }}/config-*.xml 2>/dev/null | head -1 + register: _backup_file + changed_when: false + when: not (skip_backup_check | bool) + +- name: Display backup file path + ansible.builtin.debug: + msg: "Config backup written to: {{ _backup_file.stdout | trim }}" + when: + - not (skip_backup_check | bool) + - _backup_file.stdout | trim | length > 0 + +- name: Warn if no backup file found + ansible.builtin.debug: + msg: > + WARNING: Could not confirm config backup was written. + Check {{ pfsense_config_backup_path }} manually before proceeding. + when: + - not (skip_backup_check | bool) + - _backup_file.stdout | trim | length == 0 + +# --------------------------------------------------------------------------- +# Execute the upgrade +# --------------------------------------------------------------------------- +- name: "UPGRADE — Running pfSense-upgrade on {{ inventory_hostname }}" + ansible.builtin.raw: > + {{ pfsense_upgrade_bin }} -d -y 2>&1 + register: _upgrade_result + async: 600 # pfSense upgrades can take several minutes + poll: 10 + timeout: 620 + # The upgrade reboots the host — the connection will drop. That is expected. + failed_when: > + _upgrade_result.rc is defined and + _upgrade_result.rc != 0 and + 'reboot' not in _upgrade_result.stdout | lower and + 'Restarting' not in _upgrade_result.stdout + +- name: Display upgrade output + ansible.builtin.debug: + msg: "{{ _upgrade_result.stdout_lines | default(['(no output captured — likely rebooted mid-stream)']) }}" + +# --------------------------------------------------------------------------- +# Wait for host to come back after reboot +# --------------------------------------------------------------------------- +- name: Wait for pfSense to reboot and become reachable + ansible.builtin.wait_for_connection: + delay: 30 + timeout: "{{ reboot_timeout }}" + sleep: 10 + when: auto_reboot | bool diff --git a/roles/pfsense_upgrade/tasks/verify.yml b/roles/pfsense_upgrade/tasks/verify.yml new file mode 100644 index 0000000..596120b --- /dev/null +++ b/roles/pfsense_upgrade/tasks/verify.yml @@ -0,0 +1,62 @@ +--- +# roles/pfsense_upgrade/tasks/verify.yml +# Verifies the system is healthy after upgrade and reports the new version. + +- name: Wait an additional grace period before verifying + ansible.builtin.pause: + seconds: 15 + +- name: Read post-upgrade version + ansible.builtin.raw: cat {{ pfsense_version_file }} + register: _new_version_raw + changed_when: false + retries: 3 + delay: 10 + +- name: Set post-upgrade version fact + ansible.builtin.set_fact: + pfsense_new_version: "{{ _new_version_raw.stdout | trim }}" + +- name: Verify pfSense web GUI is responding (port 443) + ansible.builtin.raw: > + fetch -q -o /dev/null --no-verify-peer https://127.0.0.1/ 2>&1 || true + register: _webgui_check + changed_when: false + failed_when: false + +- name: Check that key pfSense services are running + ansible.builtin.raw: > + sockstat -l | grep -E ':(53|443|80)\b' | wc -l | tr -d ' ' + register: _services_check + changed_when: false + failed_when: false + +- name: Run pfSense-upgrade --check post-upgrade (confirm up-to-date) + ansible.builtin.raw: > + {{ pfsense_upgrade_bin }} -d -c 2>&1 + register: _post_upgrade_check + changed_when: false + failed_when: false + +- name: Upgrade result summary + ansible.builtin.debug: + msg: + - "============================================================" + - " Upgrade Result: {{ inventory_hostname }}" + - "============================================================" + - " Previous version : {{ pfsense_current_version }}" + - " New version : {{ pfsense_new_version }}" + - " Version changed : {{ pfsense_current_version != pfsense_new_version }}" + - " Listening ports : {{ _services_check.stdout | trim }} found (DNS/HTTP/HTTPS)" + - " Post-upg check : {{ 'Up to date' if _post_upgrade_check.rc == 0 else 'May still have pending updates' }}" + - "============================================================" + +- name: Fail if version did not change after upgrade attempt + ansible.builtin.fail: + msg: > + pfSense version on {{ inventory_hostname }} is still {{ pfsense_new_version }} + after upgrade attempt (was {{ pfsense_current_version }}). + The upgrade may not have applied correctly — check the host manually. + when: + - pfsense_current_version == pfsense_new_version + - upgrade_available | bool diff --git a/roles/pfsense_upgrade/tasks/version_detect.yml b/roles/pfsense_upgrade/tasks/version_detect.yml new file mode 100644 index 0000000..a44db08 --- /dev/null +++ b/roles/pfsense_upgrade/tasks/version_detect.yml @@ -0,0 +1,70 @@ +--- +# roles/pfsense_upgrade/tasks/version_detect.yml +# Reads version info from the running pfSense host and sets facts. + +- name: Read current pfSense version string + ansible.builtin.raw: cat {{ pfsense_version_file }} + register: _raw_version + changed_when: false + +- name: Read patch level (if present) + ansible.builtin.raw: > + test -f {{ pfsense_version_patch_file }} && cat {{ pfsense_version_patch_file }} || echo "0" + register: _raw_patch + changed_when: false + +- name: Read build timestamp (if present) + ansible.builtin.raw: > + test -f {{ pfsense_version_buildtime }} && cat {{ pfsense_version_buildtime }} || echo "unknown" + register: _raw_buildtime + changed_when: false + +- name: Read pfSense edition (CE vs Plus) + ansible.builtin.raw: > + pkg info pfSense 2>/dev/null | grep -i '^Name' | awk '{print $3}' || echo "pfSense" + register: _raw_edition + changed_when: false + +- name: Detect CPU architecture + ansible.builtin.raw: uname -m + register: _raw_arch + changed_when: false + +- name: Set version facts + ansible.builtin.set_fact: + pfsense_current_version: "{{ _raw_version.stdout | trim }}" + pfsense_current_patch: "{{ _raw_patch.stdout | trim }}" + pfsense_build_time: "{{ _raw_buildtime.stdout | trim }}" + pfsense_edition: "{{ _raw_edition.stdout | trim }}" + pfsense_arch: "{{ _raw_arch.stdout | trim }}" + # Parse major.minor from version string (e.g. "2.7.2-RELEASE" → "2.7") + pfsense_major_minor: >- + {{ + (_raw_version.stdout | trim) + | regex_replace('^(\d+\.\d+).*$', '\1') + }} + # Parse patch version integer (e.g. "2.7.2-RELEASE" → 2) + pfsense_patch_int: >- + {{ + (_raw_version.stdout | trim) + | regex_replace('^(\d+)\.(\d+)\.(\d+).*$', '\3') + | default('0') + }} + # Determine if this is a RELEASE, RC, BETA, or ALPHA + pfsense_release_type: >- + {{ + (_raw_version.stdout | trim) + | regex_replace('^.*-(RELEASE|RC\d*|BETA\d*|ALPHA\d*|DEVELOPMENT).*$', '\1') + | default('UNKNOWN') + }} + +- name: Display detected version info + ansible.builtin.debug: + msg: + - "Host : {{ inventory_hostname }}" + - "Edition : {{ pfsense_edition }}" + - "Version : {{ pfsense_current_version }}" + - "Branch : {{ pfsense_major_minor }}" + - "Release type : {{ pfsense_release_type }}" + - "Build time : {{ pfsense_build_time }}" + - "Architecture : {{ pfsense_arch }}" diff --git a/roles/preflight/.DS_Store b/roles/preflight/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..fc93f86d19cf1b730d8029420bd98d5850944594 GIT binary patch literal 6148 zcmeHK%}T>S5T30SBE9q?c-%)Y_yV!SCx{?;YHO^52`SXx^8~(DPrim1@g4kTXJ`@= z!Bdek1GC>|elnXcOD0Q1Znn*1qA3wYP{z?2h5=zeYfnaMVW$iC*iu2Sv?-qAtQT#D z|Hy#6+?*b1MGI=ZzlW+^>w;Inh#_hF)YxHQw{DuD89GtGmzJ4_QkqzpR>73`cb9UvhmDW55_N29A{h^lX-BB50#A zUt9062y@2*Lvt2%EEEA;64SD(25but@r{O1a{98V4_$H!UC}$fk1-|#=t=t_y#}R BPRalP literal 0 HcmV?d00001 diff --git a/roles/proxmox_ceph/.DS_Store b/roles/proxmox_ceph/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..f125855cad664388c586904262c6a3b147253806 GIT binary patch literal 6148 zcmeHKJ5Izf5FLjgf|gFi@fIuXxu$^RMD0;)oof# zveoey72tQ5P)XNxLrWUGzpJKR%Y-8v%Bhn>3jDYN*t6M^nV|NnfGVI0bPDkI!J;u{9$P{8=|Ja>0Kgb-XBfMG zH=tz)F!R_7!UHo>3Y1dgj~GVE;kQ07^VkYXIT^={`*>#KPbkK*!*5+WnM_c7RX`OO zE3hY*bKd{YzCQnti}XqrPzC;#0w&Ije1S*ud+X%kc(1i+muPI9*9vwe7`Uw%xx5vh bpgDuz@&K55Yz5(g>5qVuK|598R~7gKYyfFw literal 0 HcmV?d00001 diff --git a/roles/proxmox_config_backup/.DS_Store b/roles/proxmox_config_backup/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ba139b64f436b4132f78fcb193bbfb8f9b1b6311 GIT binary patch literal 6148 zcmeHKJ5Izf5FPIZ6tom8(NMbK0J%X}g%hNsVwdodNR*W}+vfyai;9vKiGph&-gvBh z6eH1q5So$f=gcS1d9mV{h|FqVBt%OhiqRNnN9Y2^ac&cvdCxJ>xO?oWq=t6oU6N0- z)$tb<;CB~OK{s?uYZ|=2>$=*?gy&5tr>e7TQ`Q@fRj-T3%jc)_a9;7PyzPCG_nf>Q zDWVGVYG}>tsgKdeFgBdW%Ud^(uC}Us{!l!?d%&EuqX${ZJ@d&Sm|K3j#G&Kow90ep~_U*=+GbPa|)dV%Z&!5QNyn2>>)+2;hFK=3%OyoiedZ{Vx0 zCLhv*8%3lFx?iV0d7YOgohBkP-xUebjEG`1#>o-7fN`AL$R^%%3^eW@J1S{QoAN%% zN7?H5iwf|&i>aVHTG4`f@9(y*)-vJfC6rUuS+*?eCCAG5>C@HQ>v=e@_*UNfA;~*V zUXK(}g?Vjh!Rx7u(Y_z+&*OUE&ZDiZ>W)7Y5AYr`CvE6aR&vLDdI{!Uw9ozg(Q*$m zI`(kJkpGOITTK;E1yq5bRsef8TRauiUKLOURDo6j{ytbV#?)gY=sq3j+z|j6!R-uV z_wNR@>;R@78$ozrMoNKFYWxwyNICr0=cOJSK`AHWm~kJ^Z2SquICl80ODB^GYOe~Y z0z(B39`gi358mripP)BS?plAqmM-k>y+=>JmM47mriCf$(PV1L}V7bG9{W5(GST2ionS53W^0lQ@$`P5CGuc~In@h@+)$BXBuvshnsQ+~^DeU}z} zPTr4{P>peIY03MkZ==_+EgZ+`dpC|QHq0MukL2CwoV1~P*~xw8qZ4EfTEpBAA6@Q& zM?d?xM{C}`e#>`_Y=zbk2+}#kE!0ZHd zcXtC?HUKk^tspEABcVVEHU5cVBpi0@{W6cOpoEignbD8WZ2Swwxa_c7=T0UQ)KVEx z28Imm$>*HU|MRck|HB~tQU;WPf5m`F@*-bgO8#seO^(l63%!K0uwN_Kg`nb&V)*h= cd<=C0yX6Wn^VkZ)0?~_rqd^O0;8z*=1eynGEdT%j literal 0 HcmV?d00001 diff --git a/roles/proxmox_preflight/.DS_Store b/roles/proxmox_preflight/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..6be5fdfd6dcf138239a7f5f8595fea7a8d968311 GIT binary patch literal 6148 zcmeHKOG?B*5Uq|;GB}HI=d!Pm3rHN|0VF#?aK?-ZCS+h{_Bnyq@d&QGj%#0aHTjSh z+$bVdQ2j!6RbJ<%Nu`O%EDvQ$v>>7c&KS)w1dQj|Cl>k09BAA<4ph;ecGY8AOtLlb z7Zu=lmrzL?dY}~z-rs#wZ)L&JbttE9@_b!2Yo5D}<}WwzZ_Y^r?lufc|TH2 zHP*GK74N5hjy{HS!*yJobnED9>!#-q%?EgonUi+(EIYYpK05;QV0Os;I_tPc89jSA zW6WXEpp=tw%(#zdHvWWS96RFHrIX17byNjZfw2O7 znt?62|6hH5{&z__r~<0M|5U)ld66&iY`(Wno|1d5h2O#1IIk7#N^s#?F><*TpTnIY YZutV3d29s{f$5KclR*bn;8zv+1YzA|Pyhe` literal 0 HcmV?d00001 diff --git a/roles/proxmox_restore/.DS_Store b/roles/proxmox_restore/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..df9ed2d8f535214daadf78ac77c13d763336912e GIT binary patch literal 6148 zcmeHKOHRWu5FMvMDxyLaEI_QhVxJp?Dx4rYRoLZEaO|{GoV&_kcNRL(j63JLc01F!!Q;?#WrpJ;>ah`o2WF%cD5b_9F^rVMZ+%|su@RJVGL9Md@yy1bP>f@T-@0@%si5|%fGRLl zU?P`u-v6(^KmQMl^hy;_1^$%+CeHF~fk*Ot>*(QlueE5mXl$I<2(~2{xUCquycM6L aIfLKw0GN7g1mS_{kARawJ5}IU75D-ny<}Pd literal 0 HcmV?d00001 diff --git a/roles/proxmox_status/.DS_Store b/roles/proxmox_status/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..bea71335343246970252de4e67369f226cec57da GIT binary patch literal 6148 zcmeHKyGq1B6ulE8thmK?+6aSA|aX)(FBb#IzSgNj&mDXxmdfZyGO3R=-EEvWbYZt7|+6OPV9IaQry%d%c_?B!(oc=h~r5zZ^VmAAf6@{W_& zBgIrso14(498&;4=Mat|^( z_Hf3K--19*6;K6Kfge`@dp6tTP*8hSKow90S_Sy~V9^*;kBy-Fbf9xb0ALKaGmPE8 z8_==?n0jmk;ei<`1xl&$M+_t7@LQjkdTa!xoQz|}eLS=ACluq@;kPcGOe(0oDxeAs z71)!@8Snp>U!VVnMS7(Qr~>~=0TX9=Hpe6Ry>;|(yw_T^Ycw{_YXsX84BS?XT;7V0 b(VW3=c>qj3HiGcL^hdzSpq(o4s|tJqD*R}x literal 0 HcmV?d00001 diff --git a/roles/proxmox_upgrade/.DS_Store b/roles/proxmox_upgrade/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..831df11255cf8896cc0963a78e153593bfc0e4b5 GIT binary patch literal 6148 zcmeHKy-LJD5S~3FoM>^SV6%^4-XI>avJui*d1w3q6LN4~zm=t}k7VUL`OVCd9}~e! zMVn|MzQH0~Y;Drrx<@;=E& z+3NU<3h=v|Q9*aKp(XX+-@2~0GU4fID5t8kY*p4Pj=fyPPuK6S7va3(TY1}uB=0zR zJyJv!=C!9Kuct0XAN^Q=9?v)JJlfi-?)XFT0PhiV(vBWwC3np68<>01KKJ>$&gDxeCe0zat$_H4G0Kf=tXBfMG zH=tz)F!k67!UHo>3Y1dgj~GVE;kQ07_1FkXIT^={`*>#KPbkK*!*5+WnN(1FRX`OO zDlnDHIq(0MU!VVnMS7(Qr~>~=0TX3;w!kC#y>;|(yw_T^TQoM#YXsX84BS?XT;7V$ b(44_qj3HiGcL^hdzSpq(o4s|tJq|2Ag? literal 0 HcmV?d00001 diff --git a/roles/report/.DS_Store b/roles/report/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..f51eb16d3639435f9411591ed63f4f64b7f15db0 GIT binary patch literal 6148 zcmeHKOHRW;4E2;E1;L_AcFYxWgHVMNgcWRP2_Hd1DwXc%0Ela`;!Zq|t$aw3SRqu| zlKmzgJL9~J;tUbF<*rPL7DSXl8D~c@6Tk|aJFB|RV?ndVfH7bUv<&d~ z!9y7{#UL0z9We0-0Gz`e1>^Yd2K4LzW{N=&9*C1vprkrIVmL{M-)mf^7z8DqTwEFV z(UqN^P+VLcejmfhWrAjn0b^juz=2#Yxc^`LeEuIM*_AP14D1yHZk`wU5|5<4b@XuD sYd!P^%EEC$uuZ`rwqnF`D?Wurf#350m?;K9cp&y85NR-D4E!krUtuUqIRF3v literal 0 HcmV?d00001 diff --git a/roles/snapshot/.DS_Store b/roles/snapshot/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..07a45517bb729771c26f78aead1e2718f4fe7fa2 GIT binary patch literal 6148 zcmeHKy-veG47S@M1%X6I#(RalL8!vSn2m{+0F_9TN^PZ%c>*4Y8HtJ4VdnGslphi# zCWIlkD%Yr-F90EuP|R5N(eC z$bi1woF3?rmehHF_f@&kg82p*(YKhgO4C(Qtz_Ag@&*%Ssl3f`C#=ucAz_T<<7kH%5 xTL%v(z1BhRpdu1iD>f+@_*RTqX~maNFYr4V0F!{VB0LcL5%4saF$VsWfiE6pO{D+; literal 0 HcmV?d00001 diff --git a/roles/windows_patch/.DS_Store b/roles/windows_patch/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..bfa53568a59c98ce840cdf5aab443ec9129c0391 GIT binary patch literal 6148 zcmeHKOHRW;4E2;E1r?+&SfVhk7q#=tQ$fS%0~%>>OF1IB3|7G0N@nvAQ*>#H=t(+Fj1@p;ej|w1xl*ZBZiZ7_`Sv@inXAm zlM5^3KDx5g6N(F~!|y{lxkS*cF<=bz890&4IrsmYpU?mOB)c*OjDe$Kz)jOEUEq52(1{g+=?%uLE!g1049pHAUqKJ5ePJxF$VsWfiDnqO^E;i literal 0 HcmV?d00001