Added pfsense upgrade roles
This commit is contained in:
188
roles/pfsense_upgrade/tasks/carp.yml
Normal file
188
roles/pfsense_upgrade/tasks/carp.yml
Normal file
@@ -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"
|
||||
36
roles/pfsense_upgrade/tasks/main.yml
Normal file
36
roles/pfsense_upgrade/tasks/main.yml
Normal file
@@ -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
|
||||
41
roles/pfsense_upgrade/tasks/preflight.yml
Normal file
41
roles/pfsense_upgrade/tasks/preflight.yml
Normal file
@@ -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.
|
||||
149
roles/pfsense_upgrade/tasks/update_check.yml
Normal file
149
roles/pfsense_upgrade/tasks/update_check.yml
Normal file
@@ -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)
|
||||
100
roles/pfsense_upgrade/tasks/upgrade.yml
Normal file
100
roles/pfsense_upgrade/tasks/upgrade.yml
Normal file
@@ -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
|
||||
62
roles/pfsense_upgrade/tasks/verify.yml
Normal file
62
roles/pfsense_upgrade/tasks/verify.yml
Normal file
@@ -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
|
||||
70
roles/pfsense_upgrade/tasks/version_detect.yml
Normal file
70
roles/pfsense_upgrade/tasks/version_detect.yml
Normal file
@@ -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 }}"
|
||||
Reference in New Issue
Block a user