legacy-of-lunatic-kingdom: switch from docker to podman
The purge playbook targets only this host since lotus-land-story still runs on Docker.

Assisted-by: Claude Opus 5 via Claude Code
change ntyqlmkznpkttsyklzvlmyrvqtusvqqx
commit fe509d04697639714110311841aba0cc82040ab5
author Alpha Chen <alpha@kejadlen.dev>
date
parent rstrppqo
diff --git a/legacy-of-lunatic-kingdom/main.yml b/legacy-of-lunatic-kingdom/main.yml
index f0e61c3..5b8636a 100644
--- a/legacy-of-lunatic-kingdom/main.yml
+++ b/legacy-of-lunatic-kingdom/main.yml
@@ -1,3 +1,7 @@
+# Imported first because a leftover Docker source breaks every apt task that
+# follows it. Drop this once it has run against the host.
+- import_playbook: ../playbooks/docker-purge.yml         # noqa: name[play]
+
 - name: Basic requirements
   hosts: legacy-of-lunatic-kingdom
   become: true
@@ -25,7 +29,7 @@
           - --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/.+)($|/)
+            --collector.filesystem.mount-points-exclude=^/(dev|proc|run|sys|media|var/lib/containers/storage/.+)($|/)
       ansible.builtin.lineinfile:
         path: /etc/default/prometheus-node-exporter
         regexp: '^ARGS='
@@ -80,7 +84,7 @@
 
 # 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/podman.yml              # noqa: name[play]
 - import_playbook: ../playbooks/syncthing.yml           # noqa: name[play]
 - import_playbook: ../playbooks/unattended-upgrades.yml # noqa: name[play]
 
diff --git a/playbooks/docker-purge.yml b/playbooks/docker-purge.yml
new file mode 100644
index 0000000..85986d5
--- /dev/null
+++ b/playbooks/docker-purge.yml
@@ -0,0 +1,55 @@
+# Removes Docker from a host that has finished moving to podman. The reverse of
+# playbooks/docker.yml.
+- name: Purge Docker
+  tags: docker-purge
+  hosts: legacy-of-lunatic-kingdom
+  become: true
+  tasks:
+
+    # Both formats have been used here over time, so match on the URI rather
+    # than a filename. read_whole_file switches contains from re.match to
+    # re.search; without it the pattern would have to match from column zero.
+    - name: Find Docker apt sources
+      ansible.builtin.find:
+        paths: /etc/apt/sources.list.d
+        contains: download\.docker\.com
+        read_whole_file: true
+      register: docker_sources
+
+    - name: Remove Docker apt sources
+      ansible.builtin.file:
+        path: "{{ item.path }}"
+        state: absent
+      loop: "{{ docker_sources.files }}"
+      loop_control:
+        label: "{{ item.path }}"
+
+    # Every apt run fails while a source references a keyring that is gone, so
+    # this only happens once the sources above are removed.
+    - name: Remove Docker signing key
+      ansible.builtin.file:
+        path: /etc/apt/keyrings/docker.asc
+        state: absent
+
+    - name: Purge Docker packages
+      ansible.builtin.apt:
+        name:
+          - containerd.io
+          - docker-buildx-plugin
+          - docker-ce
+          - docker-ce-cli
+          - docker-compose-plugin
+        state: absent
+        purge: true
+        autoremove: true
+
+    # apt purge leaves image and container storage behind.
+    - name: Remove Docker state
+      ansible.builtin.file:
+        path: "{{ item }}"
+        state: absent
+      loop:
+        - /var/lib/containerd
+        - /var/lib/docker
+
+# vim: ft=yaml.ansible
diff --git a/playbooks/podman.yml b/playbooks/podman.yml
new file mode 100644
index 0000000..bef65c9
--- /dev/null
+++ b/playbooks/podman.yml
@@ -0,0 +1,40 @@
+# https://podman.io/docs/installation#debian
+- name: Install Podman
+  tags: podman
+  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"
+
+    # Quadlet arrived in podman 4.4 and bookworm ships 4.3, so there is no
+    # supported path to unit-generated containers before trixie.
+    - name: Assert Podman is new enough for Quadlet
+      ansible.builtin.assert:
+        that:
+          - ansible_facts["distribution_major_version"] | int >= 13
+        fail_msg: >-
+          Quadlet needs podman >= 4.4, which Debian first ships in trixie;
+          this host is on {{ ansible_facts["distribution_release"] }}.
+
+    # podman-docker is deliberately absent: it aliases docker(1) to podman,
+    # which makes the two runtimes indistinguishable on hosts running both.
+    - name: Install Podman
+      ansible.builtin.apt:
+        name: podman
+        state: present
+        update_cache: true
+
+    # Quadlet reads unit files from here and generates systemd services at
+    # daemon-reload. Rootful only; rootless units live under ~/.config.
+    - name: Create Quadlet unit directory
+      ansible.builtin.file:
+        path: /etc/containers/systemd
+        state: directory
+        mode: "0755"
+
+# vim: ft=yaml.ansible