- name: Update system (APT + Flatpak) hosts: all gather_facts: false strategy: free serial: 2 become: true become_user: root become_method: sudo vars: ssh_precheck_timeout: 8 apt_async: 1800 apt_poll: 10 apt_retries: 3 apt_retry_delay: 5 flatpak_timeout: 300 flatpak_async: 600 flatpak_poll: 5 pre_tasks: - name: Ensure SSH is reachable (skip host if not) wait_for: host: "{{ ansible_host | default(inventory_hostname) }}" port: "{{ ansible_port | default(22) }}" timeout: "{{ ssh_precheck_timeout }}" delegate_to: localhost register: ssh_ok ignore_errors: true - meta: end_host when: ssh_ok is failed - name: Ping with retries (handle intermittent flaps) ping: register: ping_r retries: 3 delay: 3 until: ping_r is succeeded ignore_errors: true - meta: end_host when: ping_r is failed tasks: - name: Update APT cache (bounded + retried) environment: { DEBIAN_FRONTEND: noninteractive } apt: update_cache: yes cache_valid_time: 3600 async: "{{ apt_async }}" poll: "{{ apt_poll }}" register: apt_update retries: "{{ apt_retries }}" delay: "{{ apt_retry_delay }}" until: apt_update is succeeded - name: If APT cache update failed, try to fix dpkg and retry once block: - name: Fix partially configured packages command: dpkg --configure -a changed_when: false - name: Retry APT cache update after dpkg fix environment: { DEBIAN_FRONTEND: noninteractive } apt: update_cache: yes async: 600 poll: 5 when: apt_update is failed - name: Upgrade all APT packages (bounded + retried) environment: { DEBIAN_FRONTEND: noninteractive } apt: upgrade: dist async: "{{ apt_async }}" poll: "{{ apt_poll }}" register: apt_upgrade retries: "{{ apt_retries }}" delay: "{{ apt_retry_delay }}" until: apt_upgrade is succeeded - name: If APT upgrade failed, try to fix dpkg and retry once block: - name: Fix partially configured packages command: dpkg --configure -a changed_when: false - name: Retry APT upgrade after dpkg fix environment: { DEBIAN_FRONTEND: noninteractive } apt: upgrade: dist async: 1200 poll: 5 when: apt_upgrade is failed - name: Check if flatpak binary exists become: false stat: path: /usr/bin/flatpak register: flatpak_bin - name: Update system Flatpaks (bounded; treat timeout as non-fatal) command: bash -lc "timeout {{ flatpak_timeout }} flatpak update -y --noninteractive" register: flatpak_sys async: "{{ flatpak_async }}" poll: "{{ flatpak_poll }}" failed_when: flatpak_sys.rc is defined and flatpak_sys.rc not in [0, 124] when: flatpak_bin.stat.exists # ---- User-agnostic Flatpak updates (all non-system users) ---- - name: Get passwd database getent: database: passwd register: ge - name: Build list of regular users (uid >= 1000, real shells) set_fact: regular_users: >- {{ ge.ansible_facts.getent_passwd | dict2items | map(attribute='value') | selectattr('uid', 'defined') | selectattr('uid', '>=', 1000) | rejectattr('shell', 'in', ['/usr/sbin/nologin','/sbin/nologin','/bin/false']) | list }} when: ge is succeeded - name: Stat per-user runtime dir if flatpak is present stat: path: "/run/user/{{ item.uid }}" loop: "{{ regular_users | default([]) }}" loop_control: label: "{{ item.name }}" register: user_runtime_stats when: flatpak_bin.stat.exists - name: Merge runtime stats keyed by username set_fact: user_runtime_map: >- {{ user_runtime_stats.results | items2dict(key_name='item.name', value_name='stat') }} when: flatpak_bin.stat.exists - name: Update user Flatpaks (use XDG_RUNTIME_DIR when available) become_user: "{{ item.name }}" environment: >- {{ user_runtime_map[item.name].exists | default(false) | ternary({'XDG_RUNTIME_DIR': '/run/user/' ~ item.uid|string}, {}) }} command: bash -lc "timeout {{ flatpak_timeout }} flatpak --user update -y --noninteractive" register: flatpak_user_res async: "{{ flatpak_async }}" poll: "{{ flatpak_poll }}" failed_when: flatpak_user_res.rc is defined and flatpak_user_res.rc not in [0, 124] changed_when: "'Installing' in (flatpak_user_res.stdout | default('')) or 'Installing' in (flatpak_user_res.stderr | default('')) or 'Updating' in (flatpak_user_res.stdout | default('')) or 'Updating' in (flatpak_user_res.stderr | default(''))" loop: "{{ regular_users | default([]) }}" loop_control: label: "{{ item.name }}" when: flatpak_bin.stat.exists