Compare commits

...

4 commits

17 changed files with 354 additions and 2 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
*.retry *.retry
tmp tmp
ldap-password.txt ldap-password.txt
__pycache__

4
hosts
View file

@ -92,7 +92,7 @@ dns-fleming-backup.adm.auro.re
ntp-1.int.infra.auro.re ntp-1.int.infra.auro.re
prometheus-fleming.adm.auro.re prometheus-fleming.adm.auro.re
#prometheus-fleming-fo.adm.auro.re #prometheus-fleming-fo.adm.auro.re
radius-fleming.adm.auro.re #radius-fleming.adm.auro.re
dns-1.int.infra.auro.re dns-1.int.infra.auro.re
isp-1.rtr.infra.auro.re isp-1.rtr.infra.auro.re
isp-2.rtr.infra.auro.re isp-2.rtr.infra.auro.re
@ -266,7 +266,7 @@ ps-4-3.borne.auro.re
caradoc.adm.auro.re caradoc.adm.auro.re
[edc_pve] [edc_pve]
chapalux.adm.auro.re #chapalux.adm.auro.re
[edc_vm] [edc_vm]
routeur-edc.adm.auro.re routeur-edc.adm.auro.re

5
playbooks/zfsbackup.yml Executable file
View file

@ -0,0 +1,5 @@
#!/usr/bin/env ansible-playbook
---
- hosts: caradoc.adm.auro.re
roles:
- zfs-backup

5
playbooks/zfsprune.yml Executable file
View file

@ -0,0 +1,5 @@
#!/usr/bin/env ansible-playbook
---
- hosts: perceval.adm.auro.re
roles:
- zfs-prune

View file

@ -0,0 +1,4 @@
---
zfs_backup:
scriptpath: /var/zfs-backup-nas
...

View file

@ -0,0 +1,123 @@
#!/usr/bin/env bash
if [ $# -ne 1 ]
then
echo "USAGE: $0 <tank>"
exit 1
fi
TANK="$1"
IS_TANK_EXIST=$(zfs list | grep -c "tank/${TANK} ")
if [ "${IS_TANK_EXIST}" -ne 1 ]
then
echo "${TANK} n'existe pas. Arrêt."
exit 1
fi
TODAY=$(date "+%Y-%m-%d")
TODAY_EPOCH=$(date -d "${TODAY}" +%s)
# # 1. On fait les snapshots sur le ZFS du NAS.
/sbin/zfs snapshot "tank/${TANK}@${TODAY}"
# 2. On envoie les snapshots sur le ZFS de backup.
while true
do
# Au préalable, on regarde si un envoi a été interrompu.
TOKEN=$(ssh perceval.adm.auro.re zfs list -o receive_resume_token "tank/${TANK}_backup" | tail -n 1)
if [ "${#TOKEN}" -gt 15 ]
then
echo "Un envoi a été interrompu. Reprise."
zfs send -t "${TOKEN}" | pv -trb | ssh perceval.adm.auro.re zfs recv -s -u "tank/${TANK}_backup"
if [ $? -ne 0 ]
then
echo "La reprise s'est mal déroulée. Arrêt."
exit 1
fi
fi
# On récupère les dernières snapshots envoyées sur backup.
LAST_SNAPSHOT=$(ssh perceval.adm.auro.re zfs list -t snapshot \
| grep "tank/${TANK}_backup" \
| cut -d' ' -f1 | cut -d'@' -f2 \
| sort | tail -n1)
LAST_SNAPSHOT_EPOCH=$(date -d "${LAST_SNAPSHOT}" "+%s")
# Si la dernière backup envoyée est celle d'aujourd'hui: On sort.
if [ "${LAST_SNAPSHOT_EPOCH}" -ge "${TODAY_EPOCH}" ]
then
echo "La backup distance ${TANK} ${LAST_SNAPSHOT} est suffisament récente. Fin."
break
fi
# Sinon, on envoie une backup supplémentaire à partir de la dernière snapshot.
NEW_SNAPSHOT=$(date -d "${LAST_SNAPSHOT} +1day" "+%Y-%m-%d")
echo "Envoi de la backup ${TANK} ${NEW_SNAPSHOT}."
zfs send -i "tank/${TANK}@${LAST_SNAPSHOT}" "tank/${TANK}@${NEW_SNAPSHOT}" \
| pv -trb | ssh perceval.adm.auro.re zfs recv -s -u "tank/${TANK}_backup"
if [ $? -ne 0 ]
then
echo "L'envoi s'est mal déroulé. Arrêt."
exit 1
fi
done
# 3. On ne garde que les 15 dernières snapshots ZFS
LIMIT=$(date -d "${TODAY} -15days" "+%Y-%m-%d")
LIMIT_EPOCH=$(date -d "${LIMIT}" "+%s")
while true
do
# On vérifie qu'il existe au moins 15 backups
COUNT=$(zfs list -t snapshot \
| grep -c "tank/${TANK}")
if [ "${COUNT}" -le 15 ]
then
echo "Il y a moins de 16 backups. Fin."
break
fi
# On récupère la plus vieille snapshot
OLDEST_SNAPSHOT=$(zfs list -t snapshot \
| grep "tank/${TANK}" \
| cut -d' ' -f1 | cut -d'@' -f2 \
| sort | head -n1)
OLDEST_SNAPSHOT_EPOCH=$(date -d "${OLDEST_SNAPSHOT}" "+%s")
# Sanity-check: Si la plus vieille backup est celle d'il y a moins de 15 jours: On sort.
if [ "${OLDEST_SNAPSHOT_EPOCH}" -ge "${LIMIT_EPOCH}" ]
then
echo "La backup locale ${TANK} ${OLDEST_SNAPSHOT} est suffisament récente. Fin."
break
fi
# Sinon, on supprime la plus vieille snapshot.
echo "Suppression de la backup ${TANK} ${OLDEST_SNAPSHOT}."
zfs destroy "tank/${TANK}@${OLDEST_SNAPSHOT}"
if [ $? -ne 0 ]
then
echo "La suppression s'est mal déroulée. Arrêt."
exit 1
fi
done

View file

@ -0,0 +1,11 @@
---
- name: Run systemd daemon-reload
systemd:
daemon_reload: true
- name: Restart and enable ZFS-backup timer
systemd:
name: zfs-backup.timer
state: restarted
enabled: true
...

View file

@ -0,0 +1,23 @@
---
- name: Copy files for zfs-backup
template:
src: "{{ item }}.j2"
dest: /etc/systemd/system/{{ item }}
owner: root
group: root
mode: u=rw,g=r,o=
loop:
- zfs-backup.service
- zfs-backup.timer
notify:
- Run systemd daemon-reload
- Restart and enable ZFS-backup timer
- name: Copie du script
copy:
src: files/zfs-snapshot-nas
dest: "{{ zfs_backup.scriptpath }}"
owner: root
group: root
mode: u=rx,g=r,o=
...

View file

@ -0,0 +1,22 @@
{{ ansible_managed | comment }}
[Unit]
Description=Service pour ZFS-backup
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=root
TimeoutStartSec = 7200
Nice=19
CPUSchedulingPolicy=batch
Restart=no
LogRateLimitIntervalSec=0
ExecStartPre=/usr/bin/bash {{ zfs_backup.scriptpath }} nextcloud
ExecStart=/usr/bin/bash {{ zfs_backup.scriptpath }} gitea

View file

@ -0,0 +1,10 @@
{{ ansible_managed | comment }}
[Unit]
Description=Timer for ZFS-backup
[Timer]
OnCalendar=daily
[Install]
WantedBy=timers.target

View file

@ -0,0 +1,4 @@
---
zfs_prune:
scriptpath: /var/zfs-prune-snapshot
...

View file

@ -0,0 +1,66 @@
#!/usr/bin/env bash
if [ $# -ne 1 ]
then
echo "USAGE: $0 <tank>"
exit 1
fi
TANK="$1"
IS_TANK_EXIST=$(zfs list | grep -c "tank/${TANK} ")
if [ "${IS_TANK_EXIST}" -ne 1 ]
then
echo "${TANK} n'existe pas. Arrêt."
exit 1
fi
TODAY=$(date "+%Y-%m-%d")
# On ne garde que les 365 dernières snapshots ZFS
LIMIT=$(date -d "${TODAY} -1year" "+%Y-%m-%d")
LIMIT_EPOCH=$(date -d "${LIMIT}" "+%s")
while true
do
# On vérifie qu'il existe au moins 365 backups
COUNT=$(zfs list -t snapshot \
| grep -c "tank/${TANK}")
if [ "${COUNT}" -le 365 ]
then
echo "Il y a moins de 366 backups. Fin."
break
fi
# On récupère la plus vieille snapshot
OLDEST_SNAPSHOT=$(zfs list -t snapshot \
| grep "tank/${TANK}" \
| cut -d' ' -f1 | cut -d'@' -f2 \
| sort | head -n1)
OLDEST_SNAPSHOT_EPOCH=$(date -d "${OLDEST_SNAPSHOT}" "+%s")
# Sanity-check: Si la plus vieille backup est celle d'il y a moins d'un an: On sort.
if [ "${OLDEST_SNAPSHOT_EPOCH}" -ge "${LIMIT_EPOCH}" ]
then
echo "La backup locale ${TANK} ${OLDEST_SNAPSHOT} est suffisament récente. Fin."
break
fi
# Sinon, on supprime la plus vieille snapshot.
echo "Suppression de la backup ${TANK} ${OLDEST_SNAPSHOT}."
sleep 2
zfs destroy "tank/${TANK}@${OLDEST_SNAPSHOT}"
if [ $? -ne 0 ]
then
echo "La suppression s'est mal déroulée. Arrêt."
exit 1
fi
done

View file

@ -0,0 +1,11 @@
---
- name: Run systemd daemon-reload
systemd:
daemon_reload: true
- name: Restart and enable ZFS-prune timer
systemd:
name: zfs-prune.timer
state: restarted
enabled: true
...

View file

@ -0,0 +1,23 @@
---
- name: Copy files for zfs-prune
template:
src: "{{ item }}.j2"
dest: /etc/systemd/system/{{ item }}
owner: root
group: root
mode: u=rw,g=r,o=
loop:
- zfs-prune.service
- zfs-prune.timer
notify:
- Run systemd daemon-reload
- Restart and enable ZFS-prune timer
- name: Copie du script
copy:
src: files/zfs-remove-old-snapshot
dest: "{{ zfs_prune.scriptpath }}"
owner: root
group: root
mode: u=rx,g=r,o=
...

View file

@ -0,0 +1,23 @@
{{ ansible_managed | comment }}
[Unit]
Description=Service pour ZFS-backup
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=root
TimeoutStartSec = 7200
Nice=19
CPUSchedulingPolicy=batch
Restart=no
LogRateLimitIntervalSec=0
ExecStartPre=/usr/bin/bash {{ zfs_prune.scriptpath }} nextcloud_backup
ExecStart=/usr/bin/bash {{ zfs_prune.scriptpath }} gitea_backup

View file

@ -0,0 +1,11 @@
{{ ansible_managed | comment }}
[Unit]
Description=Timer for ZFS-prune
[Timer]
OnCalendar=daily
[Install]
WantedBy=timers.target

10
shell.nix Normal file
View file

@ -0,0 +1,10 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
buildInputs = with pkgs.buildPackages; [
ansible_2_16
python313Packages.jinja2
python313Packages.requests
python313Packages.pysocks
];
LANG="C.UTF-8";
}