Compare commits

...

3 commits

Author SHA1 Message Date
15b4851e8e
refactor(borgbackup): implement shared staging with defense-in-depth
Major improvements to borgbackup configuration for better reliability and
maintainability:

**Shared staging directory:**
- Use single /btrfs-subvolumes directory (was /subvolumes-{onsite,offsite})
- Eliminates redundant path suffixes in archive structure
- Archive paths now semantic: /btrfs-subvolumes/srv-forgejo clearly indicates
  BTRFS subvolume content without redundant backup job metadata

**Defense-in-depth protection:**
- Layer 1: Systemd ordering - offsite waits for onsite completion
- Layer 2: Self-healing preHook - auto-cleanup orphaned snapshots from
  crashes/power loss
- Prevents cascading failures from race conditions or abnormal terminations

**Code quality improvements:**
- Extract subvolume lists to reduce duplication (DRY principle)
- Add /* sh */ syntax hints for proper editor highlighting
- Silent operation for consistency with existing hooks
- Improved readability with clearer comments and formatting
- All lines ≤ 100 characters

**Timing:**
- Offsite: *-*-* 00:15:00 (daily at 12:15 AM, waits for onsite)
- Onsite: hourly (unchanged)
2025-10-08 18:46:50 +08:00
37924375a2
refactor(borgbackup): backup from /persist paths instead of bind mounts
Update backup paths to use actual persistent storage locations (/persist/*) rather than bind-mounted paths, making it clear where data truly resides and simplifying restore operations.
2025-10-08 15:58:23 +08:00
26c08000a0
refactor(borgbackup): use visible directories with semantic subvolume names
Changes staging directories from hidden to visible and aligns backup paths with actual BTRFS subvolume naming conventions for better clarity when browsing archives.
2025-10-08 15:53:15 +08:00
4 changed files with 182 additions and 122 deletions

View file

@ -10,54 +10,63 @@
# Create staging directory before borg service starts
systemd.tmpfiles.rules = [
"d /.staging-offsite 0755 root root -"
"d /btrfs-subvolumes 0755 root root -"
];
# Wait for onsite backup to complete before starting offsite
systemd.services."borgbackup-job-offsite" = {
after = ["borgbackup-job-onsite.service"];
};
services.borgbackup.jobs."offsite" = {
# Allow writing to staging directory
readWritePaths = [ "/.staging-offsite" ];
readWritePaths = [ "/btrfs-subvolumes" ];
# Create staging snapshots before backup (independent from onsite)
preHook = ''
# Create read-only staging snapshots for home directory
preHook = /* sh */ ''
# Clean up orphaned snapshots from failed runs (crash/power loss)
[ -d "/btrfs-subvolumes/hm-sajenim" ] && \
${pkgs.btrfs-progs}/bin/btrfs subvolume delete \
"/btrfs-subvolumes/hm-sajenim" 2>/dev/null || true
# Create read-only BTRFS snapshot for backup
${pkgs.btrfs-progs}/bin/btrfs subvolume snapshot -r \
"/home/sajenim" "/.staging-offsite/home"
"/home/sajenim" "/btrfs-subvolumes/hm-sajenim"
'';
# Backup explicit home directories and persistent files
paths = [
# Home directories (valuable user data only)
"/.staging-offsite/home/Documents"
"/.staging-offsite/home/Pictures"
"/.staging-offsite/home/Videos"
"/.staging-offsite/home/Music"
"/.staging-offsite/home/Downloads"
"/.staging-offsite/home/Academics"
"/.staging-offsite/home/Notes"
"/.staging-offsite/home/Library"
"/btrfs-subvolumes/hm-sajenim/Documents"
"/btrfs-subvolumes/hm-sajenim/Pictures"
"/btrfs-subvolumes/hm-sajenim/Videos"
"/btrfs-subvolumes/hm-sajenim/Music"
"/btrfs-subvolumes/hm-sajenim/Downloads"
"/btrfs-subvolumes/hm-sajenim/Academics"
"/btrfs-subvolumes/hm-sajenim/Notes"
"/btrfs-subvolumes/hm-sajenim/Library"
# Dotfiles (critical user configuration)
"/.staging-offsite/home/.ssh"
"/.staging-offsite/home/.gnupg"
"/btrfs-subvolumes/hm-sajenim/.ssh"
"/btrfs-subvolumes/hm-sajenim/.gnupg"
# Files from persist.nix (restore to /persist)
"/etc/machine-id"
"/etc/ssh/ssh_host_rsa_key"
"/etc/ssh/ssh_host_rsa_key.pub"
"/etc/ssh/ssh_host_ed25519_key"
"/etc/ssh/ssh_host_ed25519_key.pub"
# Persistent files (actual storage location)
"/persist/etc/machine-id"
"/persist/etc/ssh/ssh_host_rsa_key"
"/persist/etc/ssh/ssh_host_rsa_key.pub"
"/persist/etc/ssh/ssh_host_ed25519_key"
"/persist/etc/ssh/ssh_host_ed25519_key.pub"
# Directories from persist.nix (restore to /persist)
"/var/lib/bluetooth"
"/var/lib/nixos"
"/var/lib/private"
"/etc/NetworkManager/system-connections"
# Persistent directories (actual storage location)
"/persist/var/lib/bluetooth"
"/persist/var/lib/nixos"
"/persist/var/lib/private"
"/persist/etc/NetworkManager/system-connections"
];
# Remove staging snapshots after backup completes
postHook = ''
postHook = /* sh */ ''
# Clean up snapshots after successful backup
${pkgs.btrfs-progs}/bin/btrfs subvolume delete \
"/.staging-offsite/home"
"/btrfs-subvolumes/hm-sajenim"
'';
# Remote repository configuration
@ -70,7 +79,7 @@
environment.BORG_RSH = "ssh -i /etc/ssh/ssh_host_ed25519_key";
compression = "zstd,9";
startAt = "daily";
startAt = "*-*-* 00:15:00"; # Daily at 12:15 AM
# Ensure backup runs on next boot if system was asleep
persistentTimer = true;

View file

@ -8,54 +8,58 @@ in {
# Create staging directory before borg service starts
systemd.tmpfiles.rules = [
"d /.staging-onsite 0755 root root -"
"d /btrfs-subvolumes 0755 root root -"
];
services.borgbackup.jobs."onsite" = {
# Allow writing to staging directory
readWritePaths = [ "/.staging-onsite" ];
readWritePaths = [ "/btrfs-subvolumes" ];
# Create staging snapshots before backup (independent from offsite)
preHook = ''
# Create read-only staging snapshots for home directory
preHook = /* sh */ ''
# Clean up orphaned snapshots from failed runs (crash/power loss)
[ -d "/btrfs-subvolumes/hm-sajenim" ] && \
${pkgs.btrfs-progs}/bin/btrfs subvolume delete \
"/btrfs-subvolumes/hm-sajenim" 2>/dev/null || true
# Create read-only BTRFS snapshot for backup
${pkgs.btrfs-progs}/bin/btrfs subvolume snapshot -r \
"/home/sajenim" "/.staging-onsite/home"
"/home/sajenim" "/btrfs-subvolumes/hm-sajenim"
'';
# Backup explicit home directories and persistent files
paths = [
# Home directories (valuable user data only)
"/.staging-onsite/home/Documents"
"/.staging-onsite/home/Pictures"
"/.staging-onsite/home/Videos"
"/.staging-onsite/home/Music"
"/.staging-onsite/home/Downloads"
"/.staging-onsite/home/Academics"
"/.staging-onsite/home/Notes"
"/.staging-onsite/home/Library"
"/btrfs-subvolumes/hm-sajenim/Documents"
"/btrfs-subvolumes/hm-sajenim/Pictures"
"/btrfs-subvolumes/hm-sajenim/Videos"
"/btrfs-subvolumes/hm-sajenim/Music"
"/btrfs-subvolumes/hm-sajenim/Downloads"
"/btrfs-subvolumes/hm-sajenim/Academics"
"/btrfs-subvolumes/hm-sajenim/Notes"
"/btrfs-subvolumes/hm-sajenim/Library"
# Dotfiles (critical user configuration)
"/.staging-onsite/home/.ssh"
"/.staging-onsite/home/.gnupg"
"/btrfs-subvolumes/hm-sajenim/.ssh"
"/btrfs-subvolumes/hm-sajenim/.gnupg"
# Files from persist.nix (restore to /persist)
"/etc/machine-id"
"/etc/ssh/ssh_host_rsa_key"
"/etc/ssh/ssh_host_rsa_key.pub"
"/etc/ssh/ssh_host_ed25519_key"
"/etc/ssh/ssh_host_ed25519_key.pub"
# Persistent files (actual storage location)
"/persist/etc/machine-id"
"/persist/etc/ssh/ssh_host_rsa_key"
"/persist/etc/ssh/ssh_host_rsa_key.pub"
"/persist/etc/ssh/ssh_host_ed25519_key"
"/persist/etc/ssh/ssh_host_ed25519_key.pub"
# Directories from persist.nix (restore to /persist)
"/var/lib/bluetooth"
"/var/lib/nixos"
"/var/lib/private"
"/etc/NetworkManager/system-connections"
# Persistent directories (actual storage location)
"/persist/var/lib/bluetooth"
"/persist/var/lib/nixos"
"/persist/var/lib/private"
"/persist/etc/NetworkManager/system-connections"
];
# Remove staging snapshots after backup completes
postHook = ''
postHook = /* sh */ ''
# Clean up snapshots after successful backup
${pkgs.btrfs-progs}/bin/btrfs subvolume delete \
"/.staging-onsite/home"
"/btrfs-subvolumes/hm-sajenim"
'';
# Onsite repository configuration (backup to viridian over SSH)

View file

@ -10,55 +10,81 @@
# Create staging directory before borg service starts
systemd.tmpfiles.rules = [
"d /.staging-offsite 0755 root root -"
"d /btrfs-subvolumes 0755 root root -"
];
# Wait for onsite backup to complete before starting offsite
systemd.services."borgbackup-job-offsite" = {
after = ["borgbackup-job-onsite.service"];
};
services.borgbackup.jobs."offsite" = {
# Allow writing to staging directory
readWritePaths = [ "/.staging-offsite" ];
readWritePaths = [ "/btrfs-subvolumes" ];
# Create staging snapshots before backup (independent from onsite)
preHook = ''
# Create read-only staging snapshots for each service
for subvol in containers forgejo lighttpd minecraft opengist; do
# Map config names to actual subvolume paths
preHook = let
subvolumes = [
"srv-containers"
"srv-forgejo"
"srv-lighttpd"
"srv-minecraft"
"srv-opengist"
];
in /* sh */ ''
# Clean up orphaned snapshots from failed runs (crash/power loss)
for subvol in ${toString subvolumes}; do
[ -d "/btrfs-subvolumes/$subvol" ] && \
${pkgs.btrfs-progs}/bin/btrfs subvolume delete \
"/btrfs-subvolumes/$subvol" 2>/dev/null || true
done
# Create read-only BTRFS snapshots for backup
for subvol in ${toString subvolumes}; do
case "$subvol" in
containers) src="/srv/multimedia/containers" ;;
*) src="/srv/$subvol" ;;
srv-containers) src="/srv/multimedia/containers" ;;
srv-*) src="/srv/''${subvol#srv-}" ;;
esac
${pkgs.btrfs-progs}/bin/btrfs subvolume snapshot -r \
"$src" "/.staging-offsite/$subvol"
"$src" "/btrfs-subvolumes/$subvol"
done
'';
# Backup staging snapshots and explicit persistent files
paths = [
"/.staging-offsite/containers"
"/.staging-offsite/forgejo"
"/.staging-offsite/lighttpd"
"/.staging-offsite/minecraft"
"/.staging-offsite/opengist"
"/btrfs-subvolumes/srv-containers"
"/btrfs-subvolumes/srv-forgejo"
"/btrfs-subvolumes/srv-lighttpd"
"/btrfs-subvolumes/srv-minecraft"
"/btrfs-subvolumes/srv-opengist"
# Files from persist.nix (restore to /persist)
"/etc/machine-id"
"/etc/ssh/ssh_host_rsa_key"
"/etc/ssh/ssh_host_rsa_key.pub"
"/etc/ssh/ssh_host_ed25519_key"
"/etc/ssh/ssh_host_ed25519_key.pub"
# Persistent files (actual storage location)
"/persist/etc/machine-id"
"/persist/etc/ssh/ssh_host_rsa_key"
"/persist/etc/ssh/ssh_host_rsa_key.pub"
"/persist/etc/ssh/ssh_host_ed25519_key"
"/persist/etc/ssh/ssh_host_ed25519_key.pub"
# Directories from persist.nix (restore to /persist)
"/var/lib/bluetooth"
"/var/lib/nixos"
"/var/lib/private"
"/etc/NetworkManager/system-connections"
# Persistent directories (actual storage location)
"/persist/var/lib/bluetooth"
"/persist/var/lib/nixos"
"/persist/var/lib/private"
"/persist/etc/NetworkManager/system-connections"
];
# Remove staging snapshots after backup completes
postHook = ''
for subvol in containers forgejo lighttpd minecraft opengist; do
postHook = let
subvolumes = [
"srv-containers"
"srv-forgejo"
"srv-lighttpd"
"srv-minecraft"
"srv-opengist"
];
in /* sh */ ''
# Clean up snapshots after successful backup
for subvol in ${toString subvolumes}; do
${pkgs.btrfs-progs}/bin/btrfs subvolume delete \
"/.staging-offsite/$subvol"
"/btrfs-subvolumes/$subvol"
done
'';
@ -72,7 +98,7 @@
environment.BORG_RSH = "ssh -i /etc/ssh/ssh_host_ed25519_key";
compression = "zstd,9";
startAt = "daily";
startAt = "*-*-* 00:15:00"; # Daily at 12:15 AM
# Ensure backup runs on next boot if system was asleep
persistentTimer = true;

View file

@ -17,55 +17,76 @@ in {
# Create staging directory before borg service starts
systemd.tmpfiles.rules = [
"d /.staging-onsite 0755 root root -"
"d /btrfs-subvolumes 0755 root root -"
];
services.borgbackup.jobs."onsite" = {
# Allow writing to staging directory
readWritePaths = [ "/.staging-onsite" ];
readWritePaths = [ "/btrfs-subvolumes" ];
# Create staging snapshots before backup (independent from offsite)
preHook = ''
# Create read-only staging snapshots for each service
for subvol in containers forgejo lighttpd minecraft opengist; do
# Map config names to actual subvolume paths
preHook = let
subvolumes = [
"srv-containers"
"srv-forgejo"
"srv-lighttpd"
"srv-minecraft"
"srv-opengist"
];
in /* sh */ ''
# Clean up orphaned snapshots from failed runs (crash/power loss)
for subvol in ${toString subvolumes}; do
[ -d "/btrfs-subvolumes/$subvol" ] && \
${pkgs.btrfs-progs}/bin/btrfs subvolume delete \
"/btrfs-subvolumes/$subvol" 2>/dev/null || true
done
# Create read-only BTRFS snapshots for backup
for subvol in ${toString subvolumes}; do
case "$subvol" in
containers) src="/srv/multimedia/containers" ;;
*) src="/srv/$subvol" ;;
srv-containers) src="/srv/multimedia/containers" ;;
srv-*) src="/srv/''${subvol#srv-}" ;;
esac
${pkgs.btrfs-progs}/bin/btrfs subvolume snapshot -r \
"$src" "/.staging-onsite/$subvol"
"$src" "/btrfs-subvolumes/$subvol"
done
'';
# Backup staging snapshots and explicit persistent files
paths = [
"/.staging-onsite/containers"
"/.staging-onsite/forgejo"
"/.staging-onsite/lighttpd"
"/.staging-onsite/minecraft"
"/.staging-onsite/opengist"
"/btrfs-subvolumes/srv-containers"
"/btrfs-subvolumes/srv-forgejo"
"/btrfs-subvolumes/srv-lighttpd"
"/btrfs-subvolumes/srv-minecraft"
"/btrfs-subvolumes/srv-opengist"
# Files from persist.nix (restore to /persist)
"/etc/machine-id"
"/etc/ssh/ssh_host_rsa_key"
"/etc/ssh/ssh_host_rsa_key.pub"
"/etc/ssh/ssh_host_ed25519_key"
"/etc/ssh/ssh_host_ed25519_key.pub"
# Persistent files (actual storage location)
"/persist/etc/machine-id"
"/persist/etc/ssh/ssh_host_rsa_key"
"/persist/etc/ssh/ssh_host_rsa_key.pub"
"/persist/etc/ssh/ssh_host_ed25519_key"
"/persist/etc/ssh/ssh_host_ed25519_key.pub"
# Directories from persist.nix (restore to /persist)
"/var/lib/bluetooth"
"/var/lib/nixos"
"/var/lib/private"
"/etc/NetworkManager/system-connections"
# Persistent directories (actual storage location)
"/persist/var/lib/bluetooth"
"/persist/var/lib/nixos"
"/persist/var/lib/private"
"/persist/etc/NetworkManager/system-connections"
];
# Remove staging snapshots after backup completes
postHook = ''
for subvol in containers forgejo lighttpd minecraft opengist; do
postHook = let
subvolumes = [
"srv-containers"
"srv-forgejo"
"srv-lighttpd"
"srv-minecraft"
"srv-opengist"
];
in /* sh */ ''
# Clean up snapshots after successful backup
for subvol in ${toString subvolumes}; do
${pkgs.btrfs-progs}/bin/btrfs subvolume delete \
"/.staging-onsite/$subvol"
"/btrfs-subvolumes/$subvol"
done
'';