From e6aab76104c66abfdb6b40ad2bedaf1a1b5289a8 Mon Sep 17 00:00:00 2001 From: albert <albert@sysctl.io> Date: Thu, 16 Jan 2025 08:20:58 -0800 Subject: [PATCH] Remove btrbk, add btrfs-backup and use btrbk ssh key for it --- nixos/common/services/btrbk.nix | 63 ------------------- nixos/common/services/snapper.nix | 24 ++++++- nixos/common/software/cli/scripts.nix | 9 +-- .../software/cli/scripts/btrfs-backup.sh | 22 +++---- nixos/hosts/nuc-server/disks.nix | 27 +++----- secrets/secrets.yaml | 9 ++- 6 files changed, 45 insertions(+), 109 deletions(-) delete mode 100644 nixos/common/services/btrbk.nix diff --git a/nixos/common/services/btrbk.nix b/nixos/common/services/btrbk.nix deleted file mode 100644 index cfaa0c92..00000000 --- a/nixos/common/services/btrbk.nix +++ /dev/null @@ -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"; }; - }; - }; - }; - }; - }; -} diff --git a/nixos/common/services/snapper.nix b/nixos/common/services/snapper.nix index 56bd4f78..3d48f6bf 100644 --- a/nixos/common/services/snapper.nix +++ b/nixos/common/services/snapper.nix @@ -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; diff --git a/nixos/common/software/cli/scripts.nix b/nixos/common/software/cli/scripts.nix index aee0fa9e..8de0f93e 100644 --- a/nixos/common/software/cli/scripts.nix +++ b/nixos/common/software/cli/scripts.nix @@ -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; - }; } diff --git a/nixos/common/software/cli/scripts/btrfs-backup.sh b/nixos/common/software/cli/scripts/btrfs-backup.sh index d2872d30..50162a98 100644 --- a/nixos/common/software/cli/scripts/btrfs-backup.sh +++ b/nixos/common/software/cli/scripts/btrfs-backup.sh @@ -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 diff --git a/nixos/hosts/nuc-server/disks.nix b/nixos/hosts/nuc-server/disks.nix index 2c873913..659f7350 100644 --- a/nixos/hosts/nuc-server/disks.nix +++ b/nixos/hosts/nuc-server/disks.nix @@ -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 = { diff --git a/secrets/secrets.yaml b/secrets/secrets.yaml index d240832f..a3db0f8a 100644 --- a/secrets/secrets.yaml +++ b/secrets/secrets.yaml @@ -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: |-