diff --git a/roles/pfsense_upgrade/tasks/upgrade.yml b/roles/pfsense_upgrade/tasks/upgrade.yml index 64d8086..1b71be8 100644 --- a/roles/pfsense_upgrade/tasks/upgrade.yml +++ b/roles/pfsense_upgrade/tasks/upgrade.yml @@ -1,100 +1,207 @@ --- # roles/pfsense_upgrade/tasks/upgrade.yml -# Applies the upgrade after safety checks pass. -# Only runs when perform_upgrade=true. +# Execute the actual upgrade process with dynamic repository switching -- 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 +- name: Fail if upgrade target not detected 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) + msg: "No upgrade target repository detected. Cannot proceed." + when: + - perform_upgrade | bool + - not upgrade_available | bool -# --------------------------------------------------------------------------- -# Pre-upgrade config backup -# --------------------------------------------------------------------------- -- name: Trigger config backup via PHP (writes to /cf/conf/backup/) - ansible.builtin.raw: > - sudo 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 +- name: Display upgrade details ansible.builtin.debug: - msg: "Config backup written to: {{ _backup_file.stdout | trim }}" - when: - - not (skip_backup_check | bool) - - _backup_file.stdout | trim | length > 0 + msg: + - "============================================================" + - "Starting upgrade process on {{ inventory_hostname }}" + - "============================================================" + - " Current version : {{ pfsense_current_version }}" + - " Current repo : {{ current_repo }}" + - " Target repo : {{ upgrade_target_repo }}" + - " Target description : {{ upgrade_target_description }}" + - "============================================================" -- name: Warn if no backup file found +# --------------------------------------------------------------------------- +# 1. Backup current configuration before any changes +# --------------------------------------------------------------------------- +- name: Create backup of current config.xml + ansible.builtin.raw: + cmd: cp /conf/config.xml /conf/config.xml.pre_upgrade_{{ ansible_date_time.epoch }} + become: yes + register: _config_backup + when: perform_upgrade | bool + +- name: Verify config backup was created + ansible.builtin.raw: + cmd: test -f /conf/config.xml.pre_upgrade_* + become: yes + register: _backup_verified + failed_when: false + when: perform_upgrade | bool + +# --------------------------------------------------------------------------- +# 2. Switch repository in configuration +# --------------------------------------------------------------------------- +- name: Switch repository in config.xml + ansible.builtin.raw: + cmd: php -r 'require_once("/etc/inc/config.inc"); require_once("/etc/inc/pkg-utils.inc"); config_set_path("system/pkg_repo_conf_path", "{{ upgrade_target_repo }}"); write_config("Switched to {{ upgrade_target_repo }} for upgrade");' + become: yes + register: _repo_switch + changed_when: true + when: perform_upgrade | bool + +- name: Apply repository configuration via pfSense-repo-setup + ansible.builtin.raw: + cmd: /usr/local/sbin/pfSense-repo-setup -U + become: yes + register: _repo_apply + when: perform_upgrade | bool + +- name: Verify repository switch took effect + ansible.builtin.raw: + cmd: php -r 'require_once("/etc/inc/pkg-utils.inc"); foreach(pkg_list_repos() as $r) { if (isset($r["default"])) { echo $r["name"]; } }' + become: yes + register: _verify_repo_switch + when: perform_upgrade | bool + +- name: Display new default repository 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 + msg: "Repository switched to: {{ _verify_repo_switch.stdout | trim }}" + when: + - perform_upgrade | bool + - _verify_repo_switch.stdout | trim == upgrade_target_repo # --------------------------------------------------------------------------- -# Execute the upgrade +# 3. Execute the upgrade (with retry for lock error RC=99) # --------------------------------------------------------------------------- -- name: "UPGRADE — Running pfSense-upgrade on {{ inventory_hostname }}" - ansible.builtin.raw: > - sudo {{ 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: Execute pfSense upgrade command + ansible.builtin.raw: + cmd: /usr/local/sbin/pfSense-upgrade -y -l /conf/upgrade_log.txt -p /tmp/pfSense-upgrade.sock + become: yes + register: _upgrade_exec + until: _upgrade_exec.rc != 99 + retries: 3 + delay: 10 + ignore_errors: yes + when: perform_upgrade | bool + timeout: "{{ upgrade_check_timeout | default(120) }}" -- name: Display upgrade output +- name: Display upgrade command exit code ansible.builtin.debug: - msg: "{{ _upgrade_result.stdout_lines | default(['(no output captured — likely rebooted mid-stream)']) }}" + msg: "Upgrade command exit code: {{ _upgrade_exec.rc }}" + when: perform_upgrade | bool + +- name: Check upgrade success from log file + ansible.builtin.raw: + cmd: grep -q "__RC=0" /conf/upgrade_log.txt && echo "SUCCESS" || echo "FAILED" + become: yes + register: _upgrade_verify + when: perform_upgrade | bool + +- name: Parse upgrade verification result + ansible.builtin.set_fact: + upgrade_successful: "{{ _upgrade_verify.stdout | trim == 'SUCCESS' }}" + upgrade_exit_code: "{{ _upgrade_exec.rc }}" + when: perform_upgrade | bool + +- name: Display upgrade verification result + ansible.builtin.debug: + msg: + - "Upgrade verification: {{ _upgrade_verify.stdout | trim }}" + - "Exit code from log: {{ upgrade_exit_code }}" + when: perform_upgrade | bool # --------------------------------------------------------------------------- -# Wait for host to come back after reboot +# 4. Extract upgrade log summary for debugging # --------------------------------------------------------------------------- -- name: Wait for pfSense to reboot and become reachable +- name: Get last 20 lines of upgrade log + ansible.builtin.raw: + cmd: tail -20 /conf/upgrade_log.txt + become: yes + register: _upgrade_log_tail + when: perform_upgrade | bool + +- name: Display upgrade log tail (for debugging) + ansible.builtin.debug: + msg: "Upgrade log tail:\n{{ _upgrade_log_tail.stdout }}" + when: + - perform_upgrade | bool + - not upgrade_successful + +# --------------------------------------------------------------------------- +# 5. Handle reboot if needed +# --------------------------------------------------------------------------- +- name: Check if reboot is required from upgrade log + ansible.builtin.raw: + cmd: grep -q "__REBOOT_AFTER" /conf/upgrade_log.txt && echo "REBOOT_NEEDED" || echo "NO_REBOOT" + become: yes + register: _reboot_check + when: + - perform_upgrade | bool + - upgrade_successful + +- name: Display reboot status + ansible.builtin.debug: + msg: "Reboot required: {{ _reboot_check.stdout | trim }}" + when: + - perform_upgrade | bool + - upgrade_successful + +- name: Initiate system reboot + ansible.builtin.raw: + cmd: /sbin/reboot + become: yes + when: + - perform_upgrade | bool + - upgrade_successful + - _reboot_check.stdout | trim == "REBOOT_NEEDED" + +- name: Wait for system to come back online after reboot ansible.builtin.wait_for_connection: - delay: 30 - timeout: "{{ reboot_timeout }}" - sleep: 10 - when: auto_reboot | bool \ No newline at end of file + timeout: "{{ reboot_timeout | default(300) }}" + delay: 15 + when: + - perform_upgrade | bool + - upgrade_successful + - _reboot_check.stdout | trim == "REBOOT_NEEDED" + +- name: Additional delay for services to stabilize + ansible.builtin.pause: + seconds: 30 + when: + - perform_upgrade | bool + - upgrade_successful + - _reboot_check.stdout | trim == "REBOOT_NEEDED" + +# --------------------------------------------------------------------------- +# 6. Final status and failure handling +# --------------------------------------------------------------------------- +- name: Display upgrade completion message + ansible.builtin.debug: + msg: + - "============================================================" + - "✅ Upgrade completed successfully on {{ inventory_hostname }}" + - " New version should be available after reboot" + - "============================================================" + when: + - perform_upgrade | bool + - upgrade_successful + +- name: Display upgrade failure message + ansible.builtin.debug: + msg: + - "============================================================" + - "❌ Upgrade FAILED on {{ inventory_hostname }}" + - " Exit code: {{ upgrade_exit_code }}" + - " Check /conf/upgrade_log.txt on the target system" + - "============================================================" + when: + - perform_upgrade | bool + - not upgrade_successful + +- name: Fail playbook if upgrade unsuccessful + ansible.builtin.fail: + msg: "Upgrade failed on {{ inventory_hostname }}. Manual intervention required. SSH to the system and check /conf/upgrade_log.txt" + when: + - perform_upgrade | bool + - not upgrade_successful \ No newline at end of file