168 lines
7.2 KiB
YAML
168 lines
7.2 KiB
YAML
---
|
|
# ============================================================================
|
|
# life-church/playbooks/thinkcenter-firmware.yml
|
|
# Firmware (LVFS/fwupd) upgrade + controlled reboot for thinkcenter ONLY.
|
|
# HIGH-RISK: applies device/UEFI firmware and REBOOTS the machine.
|
|
# become password from Infisical /lc-hosts at runtime.
|
|
#
|
|
# Behaviour:
|
|
# - Installs fwupd if missing; refreshes LVFS metadata.
|
|
# - Lists available firmware updates (get-updates).
|
|
# - Applies updates with `fwupdmgr update` ONLY if updates exist.
|
|
# Many UEFI/system-firmware updates are STAGED and flashed during the next
|
|
# reboot (fwupd schedules them), so a reboot is required to complete them.
|
|
# - Reboots the host (guarded by `do_reboot`, default true) and waits for it
|
|
# to come back, then reports post-reboot firmware state.
|
|
#
|
|
# Controls (extra-vars / survey):
|
|
# do_reboot=true|false -> set false to apply/stage firmware but NOT reboot.
|
|
# force_reboot=true -> reboot even if no firmware update was applied
|
|
# (e.g. to also pick up a pending kernel update).
|
|
#
|
|
# UNDO: firmware is generally NOT downgradable safely. fwupd keeps history:
|
|
# fwupdmgr get-history # what was applied
|
|
# Downgrade (only if a vendor build allows): fwupdmgr downgrade <device>
|
|
# This is a ONE-WAY door for most UEFI updates — treat as irreversible.
|
|
# ============================================================================
|
|
|
|
# --- Play 1 (localhost): fetch thinkcenter sudo pass from Infisical ----------
|
|
- name: "thinkcenter firmware — resolve become password from Infisical"
|
|
hosts: localhost
|
|
gather_facts: false
|
|
tasks:
|
|
- name: "[resolve] Infisical creds from env"
|
|
ansible.builtin.set_fact:
|
|
infisical_client_id: "{{ lookup('env','INFISICAL_CLIENT_ID') }}"
|
|
infisical_client_secret: "{{ lookup('env','INFISICAL_CLIENT_SECRET') }}"
|
|
infisical_project_id: "{{ lookup('env','INFISICAL_PROJECT_ID') }}"
|
|
infisical_url: "{{ lookup('env','INFISICAL_API_URL') | default('http://192.168.122.41:8080', true) }}"
|
|
infisical_env_slug: "{{ lookup('env','INFISICAL_ENV_SLUG') | default('prod', true) }}"
|
|
|
|
- name: "[resolve] Assert Infisical creds present"
|
|
ansible.builtin.assert:
|
|
that:
|
|
- infisical_client_id | length > 0
|
|
- infisical_client_secret | length > 0
|
|
- infisical_project_id | length > 0
|
|
fail_msg: "Infisical creds missing from Semaphore environment 'infisical-dev'."
|
|
|
|
- name: "[infisical] Log in"
|
|
infisical.vault.login:
|
|
url: "{{ infisical_url }}"
|
|
auth_method: universal_auth
|
|
universal_auth_client_id: "{{ infisical_client_id }}"
|
|
universal_auth_client_secret: "{{ infisical_client_secret }}"
|
|
register: infisical_login
|
|
no_log: true
|
|
|
|
- name: "[infisical] Read /lc-hosts"
|
|
infisical.vault.read_secrets:
|
|
login_data: "{{ infisical_login.login_data }}"
|
|
project_id: "{{ infisical_project_id }}"
|
|
env_slug: "{{ infisical_env_slug }}"
|
|
path: "/lc-hosts"
|
|
as_dict: true
|
|
register: lc_secrets
|
|
no_log: true
|
|
|
|
- name: "[set] Stash thinkcenter sudo pass"
|
|
ansible.builtin.set_fact:
|
|
thinkcenter_become_pass: "{{ lc_secrets.secrets['THINKCENTER_SUDO_PASS'] }}"
|
|
no_log: true
|
|
|
|
# --- Play 2: firmware upgrade + reboot on thinkcenter ONLY -------------------
|
|
- name: "Firmware upgrade + reboot thinkcenter"
|
|
hosts: thinkcenter
|
|
gather_facts: true
|
|
become: true
|
|
vars:
|
|
ansible_become_pass: "{{ hostvars['localhost'].thinkcenter_become_pass }}"
|
|
do_reboot: true # override to false to stage firmware without rebooting
|
|
force_reboot: false # reboot even if no firmware was applied
|
|
tasks:
|
|
- name: "[safety] Confirm we are on thinkcenter"
|
|
ansible.builtin.assert:
|
|
that: inventory_hostname == 'thinkcenter'
|
|
fail_msg: "Refusing to run — this play is scoped to thinkcenter only."
|
|
|
|
- name: "[fwupd] Ensure fwupd is installed"
|
|
ansible.builtin.apt:
|
|
name: fwupd
|
|
state: present
|
|
update_cache: true
|
|
cache_valid_time: 3600
|
|
|
|
- name: "[fwupd] Refresh LVFS metadata (force)"
|
|
ansible.builtin.command: fwupdmgr refresh --force
|
|
register: fw_refresh
|
|
changed_when: false
|
|
failed_when: fw_refresh.rc not in [0, 2] # rc 2 = metadata already current
|
|
|
|
- name: "[fwupd] List available firmware updates"
|
|
ansible.builtin.command: fwupdmgr get-updates --json
|
|
register: fw_updates_json
|
|
changed_when: false
|
|
failed_when: false
|
|
|
|
- name: "[fwupd] Parse whether any updates are available"
|
|
ansible.builtin.set_fact:
|
|
fw_update_devices: >-
|
|
{{ (fw_updates_json.stdout | default('{}') | from_json).Devices | default([])
|
|
| selectattr('Releases','defined') | map(attribute='Name') | list }}
|
|
|
|
- name: "[fwupd] Set has-updates flag (devices carrying a Releases payload)"
|
|
ansible.builtin.set_fact:
|
|
fw_has_updates: "{{ fw_update_devices | length > 0 }}"
|
|
|
|
- name: "[fwupd] Show updatable devices (if any)"
|
|
ansible.builtin.debug:
|
|
msg: "{{ fw_update_devices if fw_has_updates else 'No firmware updates available.' }}"
|
|
|
|
- name: "[fwupd] Apply firmware updates (assume-yes, no interactive reboot)"
|
|
ansible.builtin.command: fwupdmgr update --assume-yes --no-reboot-check
|
|
register: fw_apply
|
|
when: fw_has_updates
|
|
changed_when: fw_apply.rc == 0
|
|
failed_when: fw_apply.rc not in [0, 2] # rc 2 = nothing to do
|
|
|
|
- name: "[reboot] Check for pending OS reboot flag too"
|
|
ansible.builtin.stat:
|
|
path: /var/run/reboot-required
|
|
register: os_reboot_flag
|
|
|
|
- name: "[reboot] Decide whether to reboot"
|
|
ansible.builtin.set_fact:
|
|
reboot_now: "{{ do_reboot | bool and (fw_has_updates | bool or force_reboot | bool or os_reboot_flag.stat.exists) }}"
|
|
|
|
- name: "[reboot] Reboot to flash staged firmware / complete updates"
|
|
ansible.builtin.reboot:
|
|
msg: "Rebooting to apply firmware/system updates (Ansible)"
|
|
reboot_timeout: 600 # wait up to 10 min for it to come back
|
|
pre_reboot_delay: 5
|
|
post_reboot_delay: 30
|
|
test_command: fwupdmgr --version
|
|
when: reboot_now | bool
|
|
|
|
- name: "[verify] fwupd history after run"
|
|
ansible.builtin.command: fwupdmgr get-history
|
|
register: fw_history
|
|
changed_when: false
|
|
failed_when: false
|
|
|
|
- name: "[verify] Uptime after (confirms reboot happened)"
|
|
ansible.builtin.command: uptime -p
|
|
register: post_uptime
|
|
changed_when: false
|
|
|
|
- name: "[report] Firmware run summary"
|
|
ansible.builtin.debug:
|
|
msg:
|
|
- "===== thinkcenter firmware ====="
|
|
- "Updates available this run : {{ fw_has_updates }}"
|
|
- "Firmware applied : {{ fw_apply.changed | default(false) }}"
|
|
- "OS reboot flag was set : {{ os_reboot_flag.stat.exists }}"
|
|
- "Rebooted this run : {{ reboot_now }}"
|
|
- "Uptime now : {{ post_uptime.stdout }}"
|
|
- "fwupd history (last applied): see get-history output on host"
|
|
- "NOTE: some UEFI updates flash during POST on the reboot — verify BIOS version in-firmware if critical."
|