From 1cf6c81ac560c81393d87d361a9ce73193589682 Mon Sep 17 00:00:00 2001 From: Adyrem Date: Sat, 16 May 2026 04:22:05 +0200 Subject: [PATCH] Add sanoid Ansible role for ZFS snapshots and replication Replaces manually deployed config files with a fully idempotent role: installs sanoid, templates sanoid.conf and syncoid service/timer from variables, enables both systemd timers. Removes static proxmox/sanoid.* files. README rebuild section updated accordingly. Co-Authored-By: Claude Sonnet 4.6 --- README.md | 14 +----- ansible/playbooks/host.yml | 1 + ansible/roles/sanoid/defaults/main.yml | 37 +++++++++++++++ ansible/roles/sanoid/handlers/main.yml | 12 +++++ ansible/roles/sanoid/tasks/main.yml | 47 +++++++++++++++++++ ansible/roles/sanoid/templates/sanoid.conf.j2 | 19 ++++++++ .../roles/sanoid/templates/syncoid.service.j2 | 9 ++++ .../roles/sanoid/templates/syncoid.timer.j2 | 4 +- proxmox/sanoid.conf | 28 ----------- proxmox/syncoid.service | 8 ---- 10 files changed, 128 insertions(+), 51 deletions(-) create mode 100644 ansible/roles/sanoid/defaults/main.yml create mode 100644 ansible/roles/sanoid/handlers/main.yml create mode 100644 ansible/roles/sanoid/tasks/main.yml create mode 100644 ansible/roles/sanoid/templates/sanoid.conf.j2 create mode 100644 ansible/roles/sanoid/templates/syncoid.service.j2 rename proxmox/syncoid.timer => ansible/roles/sanoid/templates/syncoid.timer.j2 (60%) delete mode 100644 proxmox/sanoid.conf delete mode 100644 proxmox/syncoid.service diff --git a/README.md b/README.md index 278b20d..9b45eb7 100644 --- a/README.md +++ b/README.md @@ -173,19 +173,7 @@ ansible-playbook playbooks/claude-code-vm.yml ### 6. Set up sanoid/syncoid for ZFS snapshots -Sanoid and syncoid are not yet in an Ansible role — deploy manually: - -```bash -apt install sanoid - -# Copy config from repo -cp proxmox/sanoid.conf /etc/sanoid/sanoid.conf -cp proxmox/syncoid.service /etc/systemd/system/syncoid.service -cp proxmox/syncoid.timer /etc/systemd/system/syncoid.timer - -systemctl enable --now sanoid.timer -systemctl enable --now syncoid.timer -``` +Handled automatically by `playbooks/host.yml` (the `sanoid` role). No manual steps needed. ### 7. Restore Grafana password diff --git a/ansible/playbooks/host.yml b/ansible/playbooks/host.yml index 598fcf1..a89fefe 100644 --- a/ansible/playbooks/host.yml +++ b/ansible/playbooks/host.yml @@ -11,3 +11,4 @@ hosts: proxmox_hosts roles: - proxmox_host + - sanoid diff --git a/ansible/roles/sanoid/defaults/main.yml b/ansible/roles/sanoid/defaults/main.yml new file mode 100644 index 0000000..6a78283 --- /dev/null +++ b/ansible/roles/sanoid/defaults/main.yml @@ -0,0 +1,37 @@ +--- +sanoid_datasets: + - dataset: rpool/data + template: vms + recursive: true + - dataset: rpool/ROOT + template: system + recursive: true + +sanoid_templates: + vms: + daily: 7 + weekly: 4 + monthly: 0 + hourly: 0 + frequently: 0 + autosnap: true + autoprune: true + system: + daily: 3 + weekly: 2 + monthly: 0 + hourly: 0 + frequently: 0 + autosnap: true + autoprune: true + +syncoid_jobs: + - src: rpool/data + dst: hdd/backups/rpool-data + recursive: true + - src: rpool/ROOT/pve-1 + dst: hdd/backups/pve-root + recursive: false + +syncoid_schedule: "*-*-* 02:00:00" +syncoid_randomized_delay: 10min diff --git a/ansible/roles/sanoid/handlers/main.yml b/ansible/roles/sanoid/handlers/main.yml new file mode 100644 index 0000000..1afbac4 --- /dev/null +++ b/ansible/roles/sanoid/handlers/main.yml @@ -0,0 +1,12 @@ +--- +- name: Restart sanoid timer + ansible.builtin.systemd: + name: sanoid.timer + state: restarted + daemon_reload: true + +- name: Restart syncoid timer + ansible.builtin.systemd: + name: syncoid.timer + state: restarted + daemon_reload: true diff --git a/ansible/roles/sanoid/tasks/main.yml b/ansible/roles/sanoid/tasks/main.yml new file mode 100644 index 0000000..c45561c --- /dev/null +++ b/ansible/roles/sanoid/tasks/main.yml @@ -0,0 +1,47 @@ +--- +- name: Install sanoid + ansible.builtin.apt: + name: sanoid + state: present + update_cache: true + +- name: Deploy sanoid.conf + ansible.builtin.template: + src: sanoid.conf.j2 + dest: /etc/sanoid/sanoid.conf + owner: root + group: root + mode: '0644' + notify: Restart sanoid timer + +- name: Deploy syncoid systemd service + ansible.builtin.template: + src: syncoid.service.j2 + dest: /etc/systemd/system/syncoid.service + owner: root + group: root + mode: '0644' + notify: Restart syncoid timer + +- name: Deploy syncoid systemd timer + ansible.builtin.template: + src: syncoid.timer.j2 + dest: /etc/systemd/system/syncoid.timer + owner: root + group: root + mode: '0644' + notify: Restart syncoid timer + +- name: Enable and start sanoid timer + ansible.builtin.systemd: + name: sanoid.timer + enabled: true + state: started + daemon_reload: true + +- name: Enable and start syncoid timer + ansible.builtin.systemd: + name: syncoid.timer + enabled: true + state: started + daemon_reload: true diff --git a/ansible/roles/sanoid/templates/sanoid.conf.j2 b/ansible/roles/sanoid/templates/sanoid.conf.j2 new file mode 100644 index 0000000..e1ff9aa --- /dev/null +++ b/ansible/roles/sanoid/templates/sanoid.conf.j2 @@ -0,0 +1,19 @@ +{% for entry in sanoid_datasets %} +[{{ entry.dataset }}] + use_template = {{ entry.template }} +{% if entry.recursive %} + recursive = yes +{% endif %} + +{% endfor %} +{% for name, t in sanoid_templates.items() %} +[template_{{ name }}] + daily = {{ t.daily }} + weekly = {{ t.weekly }} + monthly = {{ t.monthly }} + hourly = {{ t.hourly }} + frequently = {{ t.frequently }} + autosnap = {{ 'yes' if t.autosnap else 'no' }} + autoprune = {{ 'yes' if t.autoprune else 'no' }} + +{% endfor %} diff --git a/ansible/roles/sanoid/templates/syncoid.service.j2 b/ansible/roles/sanoid/templates/syncoid.service.j2 new file mode 100644 index 0000000..b55343a --- /dev/null +++ b/ansible/roles/sanoid/templates/syncoid.service.j2 @@ -0,0 +1,9 @@ +[Unit] +Description=Syncoid ZFS replication to HDD +After=zfs.target + +[Service] +Type=oneshot +{% for job in syncoid_jobs %} +ExecStart=/usr/sbin/syncoid --no-privilege-elevation{% if job.recursive %} --recursive{% endif %} {{ job.src }} {{ job.dst }} +{% endfor %} diff --git a/proxmox/syncoid.timer b/ansible/roles/sanoid/templates/syncoid.timer.j2 similarity index 60% rename from proxmox/syncoid.timer rename to ansible/roles/sanoid/templates/syncoid.timer.j2 index 98237c7..8d3c2a0 100644 --- a/proxmox/syncoid.timer +++ b/ansible/roles/sanoid/templates/syncoid.timer.j2 @@ -3,8 +3,8 @@ Description=Daily ZFS replication to HDD After=sanoid.timer [Timer] -OnCalendar=*-*-* 02:00:00 -RandomizedDelaySec=10min +OnCalendar={{ syncoid_schedule }} +RandomizedDelaySec={{ syncoid_randomized_delay }} Persistent=true [Install] diff --git a/proxmox/sanoid.conf b/proxmox/sanoid.conf deleted file mode 100644 index fbd6cc5..0000000 --- a/proxmox/sanoid.conf +++ /dev/null @@ -1,28 +0,0 @@ -# VM disks on SSD pool — snapshot daily/weekly -[rpool/data] - use_template = vms - recursive = yes - -# Proxmox OS root — lighter schedule, mainly for recovery -[rpool/ROOT] - use_template = system - recursive = yes - -############################# -[template_vms] - daily = 7 - weekly = 4 - monthly = 0 - hourly = 0 - frequently = 0 - autosnap = yes - autoprune = yes - -[template_system] - daily = 3 - weekly = 2 - monthly = 0 - hourly = 0 - frequently = 0 - autosnap = yes - autoprune = yes diff --git a/proxmox/syncoid.service b/proxmox/syncoid.service deleted file mode 100644 index ea89224..0000000 --- a/proxmox/syncoid.service +++ /dev/null @@ -1,8 +0,0 @@ -[Unit] -Description=Syncoid ZFS replication to HDD -After=zfs.target - -[Service] -Type=oneshot -ExecStart=/usr/sbin/syncoid --recursive --no-privilege-elevation rpool/data hdd/backups/rpool-data -ExecStart=/usr/sbin/syncoid --no-privilege-elevation rpool/ROOT/pve-1 hdd/backups/pve-root