Remove btrbk, add btrfs-backup and use btrbk ssh key for it

This commit is contained in:
albert 2025-01-16 08:20:58 -08:00
parent a57c99e31e
commit e6aab76104
Signed by: albert
GPG key ID: 3895DD267CA11BA9
6 changed files with 45 additions and 109 deletions
nixos
common
hosts/nuc-server
secrets

View file

@ -1,63 +0,0 @@
{ hostname, pkgs, ... }: {
sops.secrets."btrbk/ssh_key" = {
sopsFile = ../../../secrets/secrets.yaml;
owner = "btrbk";
group = "btrbk";
};
security.sudo = {
enable = true;
extraRules = [
{
commands = [
{
command = "${pkgs.coreutils-full}/bin/test";
options = [ "NOPASSWD" ];
}
{
command = "${pkgs.coreutils-full}/bin/readlink";
options = [ "NOPASSWD" ];
}
{
command = "${pkgs.btrfs-progs}/bin/btrfs";
options = [ "NOPASSWD" ];
}
];
users = [ "btrbk" ];
}
];
};
# Ensure the btrbk snapshot folders are created since btrbk won't do it automatically:
systemd.tmpfiles.rules = [
"d /.snapshots/btrbk 0755 btrbk btrbk"
"d /nix/.snapshots/btrbk 0755 btrbk btrbk"
];
# More info: https://github.com/digint/btrbk/blob/master/btrbk.conf.example
# More info: https://digint.ch/btrbk/doc/btrbk.conf.5.html#_btrfs_specific_options
services.btrbk = {
instances."synology" = {
onCalendar = "daily";
settings = {
snapshot_create = "ondemand";
incremental_resolve = "directory";
snapshot_preserve_min = "7d";
snapshot_preserve = "7d 4w 6m";
target_preserve_min = "7d";
target_preserve = "7d 4w 6m";
ssh_identity = "/run/secrets/btrbk/ssh_key";
ssh_user = "root";
stream_compress = "gzip";
volume."/" = {
target = "ssh://synology/volume1/btrbk/hosts/${hostname}";
subvolume = {
"/" = { snapshot_dir = "/.snapshots/btrbk"; };
"/nix" = { snapshot_dir = "/nix/.snapshots/btrbk"; };
};
};
};
};
};
}

View file

@ -1,5 +1,22 @@
{ lib, pkgs, desktop, ... }: {
environment.systemPackages = [ pkgs.snapper ] ++ lib.optional (builtins.isString desktop) pkgs.btrfs-assistant;
{ lib, pkgs, desktop, ... }:
let
btrfs-backup = pkgs.writeScriptBin "btrfs-backup" "${builtins.readFile ../software/cli/scripts/btrfs-backup.sh}";
in {
environment.systemPackages = [
pkgs.snapper
btrfs-backup
] ++ lib.optional (builtins.isString desktop) pkgs.btrfs-assistant;
# SSH key for btrfs-backups
sops.secrets."btrfs-backups/ssh_key" = {
sopsFile = ../../../../secrets/secrets.yaml;
owner = "root";
};
sops.secrets."btrfs-backups/gotify_token" = {
owner = "root";
sopsFile = ../../../../secrets/secrets.yaml;
};
services.snapper = {
snapshotRootOnBoot = true;
@ -8,7 +25,8 @@
configs = {
root = {
TIMELINE_CREATE = true;
TIMELINE_CLEANUP = true;
TIMELINE_CLEANUP = true;
NUMBER_LIMIT = 10; # snapshotRootOnBoot cleanup
TIMELINE_LIMIT_YEARLY = 0;
TIMELINE_LIMIT_QUARTERLY = 2;
TIMELINE_LIMIT_MONTHLY = 3;

View file

@ -1,16 +1,9 @@
{ pkgs, ... }:
let
clean-hm = pkgs.writeScriptBin "clean-hm" "${builtins.readFile ./scripts/clean-hm.sh}";
btrfs-backup = pkgs.writeScriptBin "btrfs-backup" "${builtins.readFile ./scripts/btrfs-backup.sh}";
clean-hm = pkgs.writeScriptBin "clean-hm" "${builtins.readFile ./scripts/clean-hm.sh}";
in {
environment.systemPackages = [
clean-hm
btrfs-backup
];
sops.secrets."btrfs-backups/gotify_token" = {
owner = "albert";
sopsFile = ../../../../secrets/secrets.yaml;
};
}

View file

@ -13,7 +13,7 @@
# - snapper
# - btrfs-progs
# - pv
# - ssh (configured for remote access)
# - ssh configured for remote access)
# - curl (for Gotify notifications)
#
# Usage: ./script.sh SNAPPER_CONFIG
@ -26,7 +26,7 @@ set -eE
if ! command -v snapper >/dev/null 2>&1 || \
! command -v btrfs >/dev/null 2>&1 || \
! command -v pv >/dev/null 2>&1 || \
! command -v ssh >/dev/null 2>&1 || \
! command -v ssh >/dev/null 2>&1 || \
! command -v curl >/dev/null 2>&1; then
echo "ERROR: Missing required dependencies. Please ensure snapper, btrfs-progs, pv, ssh, and curl are installed."
exit 1
@ -224,7 +224,7 @@ verify_snapshot() {
# Function to verify remote connectivity
verify_remote() {
if ! ssh -q "$REMOTE_HOST" "exit"; then
if ! ssh -i /run/secrets/btrfs-backup/ssh_key -q "$REMOTE_HOST" "exit"; then
send_notification "Backup Failed" "Cannot connect to remote host from $HOSTNME" 8
log "ERROR: Cannot connect to remote host"
exit 1
@ -233,7 +233,7 @@ verify_remote() {
# Function to get remote snapshots
get_remote_snapshots() {
ssh "$REMOTE_HOST" "find '$BASE_DEST_PATH' -maxdepth 1 -type d -name '[0-9]*' | sort -n"
ssh -i /run/secrets/btrfs-backup/ssh_key "$REMOTE_HOST" "find '$BASE_DEST_PATH' -maxdepth 1 -type d -name '[0-9]*' | sort -n"
}
# Function to get local backup snapshots
@ -255,13 +255,13 @@ cleanup_snapshots() {
local snapshot="${local_snapshots[$i]}"
# Delete remote snapshot first
if ssh "$REMOTE_HOST" "[ -d '$BASE_DEST_PATH/$snapshot' ]"; then
if ssh -i /run/secrets/btrfs-backup/ssh_key "$REMOTE_HOST" "[ -d '$BASE_DEST_PATH/$snapshot' ]"; then
log "Deleting remote snapshot: $snapshot"
if ! ssh "$REMOTE_HOST" "btrfs subvolume delete '$BASE_DEST_PATH/$snapshot/snapshot'"; then
if ! ssh -i /run/secrets/btrfs-backup/ssh_key "$REMOTE_HOST" "btrfs subvolume delete '$BASE_DEST_PATH/$snapshot/snapshot'"; then
send_notification "Backup Warning" "Failed to delete remote snapshot $snapshot on $HOSTNME" 6
log "WARNING - Failed to delete remote snapshot $snapshot"
fi
if ! ssh "$REMOTE_HOST" "rm -rf '$BASE_DEST_PATH/$snapshot'"; then
if ! ssh -i /run/secrets/btrfs-backup/ssh_key "$REMOTE_HOST" "rm -rf '$BASE_DEST_PATH/$snapshot'"; then
send_notification "Backup Warning" "Failed to cleanup remote snapshot directory $snapshot on $HOSTNME" 6
log "WARNING - Failed to cleanup remote snapshot directory $snapshot"
fi
@ -292,7 +292,7 @@ if [ -n "$LAST_TRANSFERRED" ]; then
fi
# Create destination directory if it doesn't exist
ssh "$REMOTE_HOST" "mkdir -p '$DEST_PATH'"
ssh -i /run/secrets/btrfs-backup/ssh_key "$REMOTE_HOST" "mkdir -p '$DEST_PATH'"
# Perform the transfer
if [ -z "$LAST_TRANSFERRED" ]; then
@ -300,7 +300,7 @@ if [ -z "$LAST_TRANSFERRED" ]; then
log "Performing full send of snapshot $NEW_SNAPSHOT"
sudo btrfs send "$SNAPSHOT_PATH/$NEW_SNAPSHOT/snapshot" | \
pv --bytes | \
ssh "$REMOTE_HOST" "btrfs receive '$DEST_PATH'" && {
ssh -i /run/secrets/btrfs-backup/ssh_key "$REMOTE_HOST" "btrfs receive '$DEST_PATH'" && {
echo "$NEW_SNAPSHOT" | sudo tee "$STATE_FILE"
log "Full send completed successfully"
} || {
@ -315,7 +315,7 @@ else
sudo btrfs send -p "$SNAPSHOT_PATH/$LAST_TRANSFERRED/snapshot" \
"$SNAPSHOT_PATH/$NEW_SNAPSHOT/snapshot" | \
pv --bytes | \
ssh "$REMOTE_HOST" "btrfs receive '$DEST_PATH'" && {
ssh -i /run/secrets/btrfs-backup/ssh_key "$REMOTE_HOST" "btrfs receive '$DEST_PATH'" && {
echo "$NEW_SNAPSHOT" | sudo tee "$STATE_FILE"
log "Incremental send completed successfully"
} || {
@ -348,7 +348,7 @@ if [ -n "$LAST_TRANSFERRED" ]; then
fi
# Final verification
if ! ssh "$REMOTE_HOST" "btrfs subvolume show '$DEST_PATH/snapshot'" &>/dev/null; then
if ! ssh -i /run/secrets/btrfs-backup/ssh_key "$REMOTE_HOST" "btrfs subvolume show '$DEST_PATH/snapshot'" &>/dev/null; then
send_notification "Backup Failed" "Final verification failed for $SNAPPER_CONFIG on $HOSTNME" 8
log "WARNING - Final verification failed"
exit 1

View file

@ -1,27 +1,16 @@
{
imports = [
../../common/services/snapper.nix
../../common/services/btrbk.nix
];
# Ensure the btrbk snapshot folders are created since btrbk won't do it automatically:
systemd.tmpfiles.rules = [
"d /home/.snapshots/btrbk 0755 btrbk btrbk"
"d /Local-Storage/.snapshots/btrbk 0755 btrbk btrbk"
];
services.btrbk.instances."synology".settings.volume."/".subvolume = {
"/home" = { snapshot_dir = "/home/.snapshots/btrbk"; };
"/Local-Storage" = { snapshot_dir = "/Local-Storage/.snapshots/btrbk"; };
};
# services.cron = {
# systemCronJobs = [
# "0 0 * * * root btrfs-backup root"
# "0 0 * * * root btrfs-backup nix"
# "0 0 * * * root btrfs-backup home"
# "0 0 * * * root btrfs-backup Local-Storage"
# ];
# };
services.cron = {
systemCronJobs = [
"0 0 * * * root btrfs-backup root"
"0 0 * * * root btrfs-backup nix"
"0 0 * * * root btrfs-backup home"
"0 0 * * * root btrfs-backup Local-Storage"
];
};
# extra configs not present in the standard config above
services.snapper.configs.Local-Storage = {

View file

@ -4,17 +4,16 @@ services:
forgejo_token: ENC[AES256_GCM,data:vAH8v82+WI/P0HhtLDfrK66B3u2H49XA1AglfL1LthM6Dm+znBlx4QaFmNk3ag==,iv:/jqtUejqNC9f9kXdUqxl1+LaxKsjXSZdU+I0u+ssmdQ=,tag:+2oWh6sgc7R1PXYxIz3oVQ==,type:str]
btrfs-backups:
gotify_token: ENC[AES256_GCM,data:PP8UTJWrDKhonLxN8vEj,iv:hTGWyktK+Ce7hAd0bARztLAQDSvhWgLcKRyGqyfgVKU=,tag:2xboM6Uv8NWld89EUl2jEg==,type:str]
btrbk:
ssh_key: ENC[AES256_GCM,data:HxT85XlROSSKqPOEToSmrpzc6cutWRDkLxIO8o13AENtAlqEfBtcVTe/XKKbUjx+38FkqznVuHgVxXtnWLfON0yCx2PqoWVsluPfW44QFswQdtM0dYYCFy1hK7pg0xSplIeYdNZC9VFhL+SoBYXfTHKpalPo5LwYVrtQnO9yV/PrYw07oYTEKWGVxpi0KJJ2vo4UwxONzEJJSjJU/M0haQ1mvgTU5kFS5e4cMZOy3cGpGSUOPlUDlQgUlANcK92HFZlFnRyd2r+pjYVkSeixfQ9Zq30nNoaSu1J2VZhkt1KTfAXnuGIjlByvSVE9ZF78rlZfTBmZzJEyB3N3rtWEWUOrLZx8ZCh1YIpp6VK2WNJKtW2SpCpkG0fCoKzJiBvf+GuV4P0vdBHo6/xt12CHx498XUZSP644b1vnUim+RRmDQkVnTJnjY4YilIUME8MEFMzNuGamX/IX+U+UDGYwE+/7rc1BMZyJdzQka272uj66Q+fE7f/wzgtVbtgBBkIi3pDfv3smfFU8xGQoNmGJWIy1edvDJpoxsIaZ,iv:SvcLu1ffduCYj6tEfj6cSZM9CSB9TbGXWz4CixXYnsA=,tag:enn3+zPZbDl7IfivWv82Fg==,type:str]
ssh_pub_key: ENC[AES256_GCM,data:ARuXIOnW2x2NceekoRPG/Vy+zj/RdryE9m4MYt6e1NFGOgC2k69ZAmBui0+aIly2kJP4VyKJ/cf70APuv4uBmahAClFFdj5ZNrQnINiQzJF6iWOiaRI=,iv:+TqQ1WWaVRy/Ec8N3WUxSlkfU2me2ncII8smq6RsJr8=,tag:G4+/js93EFWudg9cRigxog==,type:str]
ssh_key: ENC[AES256_GCM,data:Nri7+3ZAPIEkYwjeyW0uQCycIxVx+ebOg2zHFHvt5MjootsIBXsQhBiGupP2/0ZQ2Val8VgX0wFnMG89GCCpOwiYe8vVaIeLmLOCAAchu16ftNqSN9zNv0aLPZ/lJhYdtnq79hXA6jbrwYONPaVU/5fqbl3+kWhEJtgjhB+eH0O9sLUAS8s66scov7yYHwYH7cWvJUiOomwCx0a6th2MzU2+PDvFCrr1hqQyS3CJdMrLTAHkg0F74pvmVFQB7Z1m2KWPJkaX1ji3/kyjrOKbq++GF6L7TwH03vMXtrHqExF8KTGfmelhlmlJ0UzaoOTZ9SG+0xIEwb7SjY4DNoUO2rZC3A+vn4g/RsYKhXAHEeRFgkAmcWZR65gbEliN7MMFcpwMkIFWEgM4zRmC4ZmGs3zxSxsQyHKqBtL4cRrlLmwXCwPjv4TbiV2BmlCNgahh+WtVTQnx/GQcaGZ9MT8lhRC7dSy0HS9Efc8uUz8e5aAER8F7z1MhemP92Ma3YsNyEUxcHU6Ruk7NUbgqMIxGbGiqXzW3DSMc6L0+,iv:5zjYDsZrg5hekMl58p0o1znrojke4u1LzUY3sJpInkE=,tag:WlNyfgWGSD2Xf7ualoefxA==,type:str]
ssh_pub_key: ENC[AES256_GCM,data:DO9KRWiB5yT3eY28P4QPVV4tho5CpaXEF2hHXubK8iAX/tlxzzztGAEGRo572BXP5T+FYB6jO12chLDNWoYDQ92yrdZGy5k5vPZSehTm6t3005Q0CF7JfeO0bX+biQ==,iv:VP2wx3ZD7RE1zDDRthv2329GXemmMKYl7te69KNGY0s=,tag:78QSISotcsMmi3leNcP5sQ==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age: []
lastmodified: "2025-01-16T00:34:44Z"
mac: ENC[AES256_GCM,data:oG1TahgiYBXqDV9oftlBfhSlUIDwkyPMQncoxq9+443ktXZ4Ze+FpYw3FH4NAR4n59CWfzrwUYWY+YYoRUIUzNrOloygy2z/FAdnyYXs3CDt++4Gilz74P9f5THloXgKF5LMSIyYh091zrEV25nSqdSHQZnXKQOQJAAsitMcAdU=,iv:k7SJ9bnC1eiOSkomIrB1Ou0VoUfRcYI3m0GUKTGATQw=,tag:wfjGh3q7X0EXbx4o37syLQ==,type:str]
lastmodified: "2025-01-16T16:08:57Z"
mac: ENC[AES256_GCM,data:by/kjCDwQFWkBJPDB/W44w+8VfIm68fGHWuQOpzrnrM4GFfqO4lSbd/Mzy6aZ9sWSkoNO3h4l3lx3RUvpc4nh1JWqfhoKjds9HIqMKcAwFdUtRCZeMc+g5nc5/BLL27duM2F0YaPFNLs2u2hBgMcRME+j80jQ4r7rMhOPIb+DuA=,iv:sYsORoOuvNW/0o4f6TjQe8vGuOQRW02T8ZAO5FApszg=,tag:0PN9MraA+Ozv1ozy7l+Fhw==,type:str]
pgp:
- created_at: "2025-01-10T20:21:25Z"
enc: |-