Auto-update legacy-of-lunatic-kingdom weekly
Auto-reboot stays off given data on /mnt, so node-exporter surfaces
last-run, pending-security, and reboot-required to keep the runs
observable.

Assisted-by: Claude Opus 4.8 via Claude Code
change yktrnpvlmqmxqtrwrokuyyltvkrukyxy
commit e2027c89d6ef32b1e48266e0ae0a5e87767e0210
author Alpha Chen <alpha@kejadlen.dev>
date
parent ltppwxqz
diff --git a/legacy-of-lunatic-kingdom/main.yml b/legacy-of-lunatic-kingdom/main.yml
index c7abf92..f0e61c3 100644
--- a/legacy-of-lunatic-kingdom/main.yml
+++ b/legacy-of-lunatic-kingdom/main.yml
@@ -10,19 +10,38 @@
           - net-tools # ifconfig
           - neovim
           - prometheus-node-exporter
+          - prometheus-node-exporter-collectors # apt_info + reboot-required metrics
           - python3-lxml # ansible bcrypt
         state: present
         update_cache: true
 
-    - name: Include /mnt in node-exporter
+    - name: Configure node-exporter collectors
+      vars:
+        # The systemd + apt collectors expose unattended-upgrades health to the
+        # node-exporter target (last run, pending security updates, reboot
+        # needed). See playbooks/unattended-upgrades.yml.
+        node_exporter_args:
+          - --collector.textfile.directory=/var/lib/prometheus/node-exporter
+          - --collector.systemd
+          - --collector.systemd.unit-include=apt-daily.*\.(timer|service)
+          - >-
+            --collector.filesystem.mount-points-exclude=^/(dev|proc|run|sys|media|var/lib/docker/.+|var/lib/containers/storage/.+)($|/)
       ansible.builtin.lineinfile:
         path: /etc/default/prometheus-node-exporter
         regexp: '^ARGS='
-        line: 'ARGS="--collector.filesystem.mount-points-exclude=^/(dev|proc|run|sys|media|var/lib/docker/.+|var/lib/containers/storage/.+)($|/)"'
+        line: 'ARGS="{{ node_exporter_args | join(" ") }}"'
         create: true
         mode: '0644'
       notify: Restart node exporter
 
+    # Periodically regenerates apt.prom (pending upgrades, reboot-required) in
+    # the textfile directory above.
+    - name: Enable node-exporter apt collector
+      ansible.builtin.systemd:
+        name: prometheus-node-exporter-apt.timer
+        enabled: true
+        state: started
+
   handlers:
     - name: Restart node exporter
       ansible.builtin.systemd:
@@ -60,9 +79,10 @@
         state: restarted
 
 # TODO disallow network access from non-tailscale interfaces
-- import_playbook: ../playbooks/tailscale.yml # noqa: name[play]
-- import_playbook: ../playbooks/docker.yml    # noqa: name[play]
-- import_playbook: ../playbooks/syncthing.yml # noqa: name[play]
+- import_playbook: ../playbooks/tailscale.yml           # noqa: name[play]
+- import_playbook: ../playbooks/docker.yml              # noqa: name[play]
+- import_playbook: ../playbooks/syncthing.yml           # noqa: name[play]
+- import_playbook: ../playbooks/unattended-upgrades.yml # noqa: name[play]
 
 # TODO
 # - restic
diff --git a/playbooks/unattended-upgrades.yml b/playbooks/unattended-upgrades.yml
new file mode 100644
index 0000000..24798fb
--- /dev/null
+++ b/playbooks/unattended-upgrades.yml
@@ -0,0 +1,63 @@
+# https://wiki.debian.org/UnattendedUpgrades
+- name: Configure unattended upgrades
+  tags: unattended-upgrades
+  hosts: all
+  become: true
+  tasks:
+
+    - name: Assert distribution is Debian
+      ansible.builtin.assert:
+        that:
+          - ansible_facts["distribution"] == "Debian"
+        fail_msg: "This playbook only supports Debian distributions"
+
+    # sudo apt-get install unattended-upgrades
+    - name: Install unattended-upgrades
+      ansible.builtin.apt:
+        name: unattended-upgrades
+        state: present
+        update_cache: true
+
+    # Refresh package lists and pre-download upgrades daily (via apt-daily.timer)
+    # so the weekly apply below is fast. Origins stay at the package default
+    # (security only), the safe choice for an unattended box holding data on /mnt.
+    - name: Enable periodic unattended upgrades
+      ansible.builtin.copy:
+        content: |
+          APT::Periodic::Update-Package-Lists "1";
+          APT::Periodic::Download-Upgradeable-Packages "1";
+          APT::Periodic::Unattended-Upgrade "1";
+          APT::Periodic::AutocleanInterval "7";
+        dest: /etc/apt/apt.conf.d/20auto-upgrades
+        mode: "0644"
+
+    - name: Create apt-daily-upgrade.timer override directory
+      ansible.builtin.file:
+        path: /etc/systemd/system/apt-daily-upgrade.timer.d
+        state: directory
+        mode: "0755"
+
+    # Apply upgrades weekly instead of daily. The empty OnCalendar clears the
+    # packaged daily schedule before setting the weekly one (OnCalendar is
+    # additive otherwise). Persistent catches up a run missed while powered off.
+    - name: Apply unattended upgrades weekly
+      ansible.builtin.copy:
+        content: |
+          [Timer]
+          OnCalendar=
+          OnCalendar=Sun *-*-* 03:00
+          RandomizedDelaySec=30m
+          Persistent=true
+        dest: /etc/systemd/system/apt-daily-upgrade.timer.d/override.conf
+        mode: "0644"
+      notify: Reload apt-daily-upgrade timer
+
+  handlers:
+
+    - name: Reload apt-daily-upgrade timer
+      ansible.builtin.systemd:
+        name: apt-daily-upgrade.timer
+        state: restarted
+        daemon_reload: true
+
+# vim: ft=yaml.ansible