From e112bb307848f7f41ba8fcbf335a8e20d29b73f6 Mon Sep 17 00:00:00 2001
From: Marty Oehme <contact@martyoeh.me>
Date: Wed, 26 Feb 2025 14:47:49 +0100
Subject: [PATCH] feat(base): Add snapper setup stack

Creates separate snapper services for root and home directories. Does
_not_ yet set up any btrfs mounts or ensure that they are mounted at the
locations.
---
 .../templates/snapper-configurations/home.j2  | 55 ++++++++++++++
 .../templates/snapper-configurations/root.j2  | 55 ++++++++++++++
 books/void_base.yaml                          | 71 +++++++++++++++++++
 inventory_local.yaml                          |  1 +
 4 files changed, 182 insertions(+)
 create mode 100644 books/templates/snapper-configurations/home.j2
 create mode 100644 books/templates/snapper-configurations/root.j2

diff --git a/books/templates/snapper-configurations/home.j2 b/books/templates/snapper-configurations/home.j2
new file mode 100644
index 0000000..ef7bcf3
--- /dev/null
+++ b/books/templates/snapper-configurations/home.j2
@@ -0,0 +1,55 @@
+# subvolume to snapshot
+SUBVOLUME="/home"
+
+# filesystem type
+FSTYPE="btrfs"
+
+# btrfs qgroup for space aware cleanup algorithms
+QGROUP=""
+
+# fraction or absolute size of the filesystems space the snapshots may use
+SPACE_LIMIT="0.5"
+
+# fraction or absolute size of the filesystems space that should be free
+FREE_LIMIT="0.2"
+
+# users and groups allowed to work with config
+ALLOW_USERS="{{ user_name }}"
+ALLOW_GROUPS=""
+
+# sync users and groups from ALLOW_USERS and ALLOW_GROUPS to .snapshots
+# directory
+SYNC_ACL="no"
+
+# start comparing pre- and post-snapshot in background after creating
+# post-snapshot
+BACKGROUND_COMPARISON="yes"
+
+# run daily number cleanup
+NUMBER_CLEANUP="yes"
+
+# limit for number cleanup
+NUMBER_MIN_AGE="1800"
+NUMBER_LIMIT="4"
+NUMBER_LIMIT_IMPORTANT="2"
+
+# create hourly snapshots
+TIMELINE_CREATE="yes"
+
+# cleanup hourly snapshots after some time
+TIMELINE_CLEANUP="yes"
+
+# limits for timeline cleanup
+TIMELINE_MIN_AGE="1800"
+TIMELINE_LIMIT_HOURLY="10"
+TIMELINE_LIMIT_DAILY="2"
+TIMELINE_LIMIT_WEEKLY="1"
+TIMELINE_LIMIT_MONTHLY="1"
+TIMELINE_LIMIT_QUARTERLY="0"
+TIMELINE_LIMIT_YEARLY="0"
+
+# cleanup empty pre-post-pairs
+EMPTY_PRE_POST_CLEANUP="yes"
+
+# limits for empty pre-post-pair cleanup
+EMPTY_PRE_POST_MIN_AGE="1800"
diff --git a/books/templates/snapper-configurations/root.j2 b/books/templates/snapper-configurations/root.j2
new file mode 100644
index 0000000..6218117
--- /dev/null
+++ b/books/templates/snapper-configurations/root.j2
@@ -0,0 +1,55 @@
+# subvolume to snapshot
+SUBVOLUME="/"
+
+# filesystem type
+FSTYPE="btrfs"
+
+# btrfs qgroup for space aware cleanup algorithms
+QGROUP=""
+
+# fraction or absolute size of the filesystems space the snapshots may use
+SPACE_LIMIT="0.5"
+
+# fraction or absolute size of the filesystems space that should be free
+FREE_LIMIT="0.2"
+
+# users and groups allowed to work with config
+ALLOW_USERS="{{ user_name }}"
+ALLOW_GROUPS=""
+
+# sync users and groups from ALLOW_USERS and ALLOW_GROUPS to .snapshots
+# directory
+SYNC_ACL="no"
+
+# start comparing pre- and post-snapshot in background after creating
+# post-snapshot
+BACKGROUND_COMPARISON="yes"
+
+# run daily number cleanup
+NUMBER_CLEANUP="yes"
+
+# limit for number cleanup
+NUMBER_MIN_AGE="1800"
+NUMBER_LIMIT="6"
+NUMBER_LIMIT_IMPORTANT="4"
+
+# create hourly snapshots
+TIMELINE_CREATE="yes"
+
+# cleanup hourly snapshots after some time
+TIMELINE_CLEANUP="yes"
+
+# limits for timeline cleanup
+TIMELINE_MIN_AGE="1800"
+TIMELINE_LIMIT_HOURLY="6"
+TIMELINE_LIMIT_DAILY="5"
+TIMELINE_LIMIT_WEEKLY="2"
+TIMELINE_LIMIT_MONTHLY="1"
+TIMELINE_LIMIT_QUARTERLY="1"
+TIMELINE_LIMIT_YEARLY="0"
+
+# cleanup empty pre-post-pairs
+EMPTY_PRE_POST_CLEANUP="yes"
+
+# limits for empty pre-post-pair cleanup
+EMPTY_PRE_POST_MIN_AGE="1800"
diff --git a/books/void_base.yaml b/books/void_base.yaml
index 78d9003..a05b93e 100644
--- a/books/void_base.yaml
+++ b/books/void_base.yaml
@@ -131,3 +131,74 @@
         state: link
       with_items: [chronyd]
       listen: installed-chrony
+
+- name: Set up snapper backups
+  hosts: target_system
+  become: true
+  tags:
+    - btrfs
+    - snapshots
+  tasks:
+    - name: Install snapper
+      community.general.xbps:
+        name:
+          - snapper
+        state: present
+      notify: installed-snapper
+
+    # https://wiki.archlinux.org/title/Snapper#updatedb
+    - name: Disable updatedb indexing for snapshot directories
+      ansible.builtin.copy:
+        content: 'PRUNENAMES = ".snapshots"'
+        dest: "/etc/updatedb.conf"
+        owner: root
+        group: root
+        mode: 0644
+        force: true
+
+    - name: Ensure snapper configs directory exists
+      ansible.builtin.file:
+        dest: "/etc/snapper/configs"
+        state: directory
+        recurse: true
+
+    - name: Ensure root /.snapshots directory exists
+      ansible.builtin.file:
+        dest: "/.snapshots"
+        state: directory
+        mode: 0755
+
+    - name: Create root backup configuration
+      ansible.builtin.template:
+        src: snapper-configurations/root.j2
+        dest: "/etc/snapper/configs/root"
+        mode: 0640
+        force: true # ensure contents are always exact
+
+    - name: Ensure home /.snapshots directory exists
+      ansible.builtin.file:
+        dest: "/home/.snapshots"
+        state: directory
+        mode: 0755
+
+    - name: Create homedir backup configuration
+      ansible.builtin.template:
+        src: snapper-configurations/home.j2
+        dest: "/etc/snapper/configs/home"
+        mode: 0640
+        force: true
+
+  handlers:
+  #   # Do NOT activate the snapperd service -
+  #   # on systems without elogind I guess? Unsure
+  #   - name: Activate snapper service
+  #     ansible.builtin.file:
+  #       force: "yes"
+  #       src: "/etc/sv/snapperd"
+  #       dest: "/etc/runit/runsvdir/default/snapperd"
+  #       state: link
+  #     listen: installed-snapper
+    - name: Snapper handler stub
+      ansible.builtin.debug:
+        msg: ""
+      listen: installed-snapper
diff --git a/inventory_local.yaml b/inventory_local.yaml
index 400cdde..6c7d95d 100644
--- a/inventory_local.yaml
+++ b/inventory_local.yaml
@@ -116,6 +116,7 @@ terminal:
       - restic
       - ripgrep
       - sc-im
+      - snooze
       - starship
       - swaybg
       - swayidle