# nixos-config - My current NixOS configuration # # Copyright (C) 2025 Benedikt Peetz # SPDX-License-Identifier: GPL-3.0-or-later # # This file is part of my nixos-config. # # You should have received a copy of the License along with this program. # If not, see . { lib, config, pkgs, ... }: let cfg = config.soispha.services.backup; in { options.soispha.services.backup = { storagebox = { enable = lib.mkEnableOption "remote backups"; user = lib.mkOption { type = lib.types.str; description = "The storagebox-user to use"; example = "u384702-sub2"; }; sshKey = lib.mkOption { type = lib.types.path; description = "The age-encrypted ssh-key, passed to agenix"; default = ./secrets/storagebox/ssh_key.age; }; repositoryPassword = lib.mkOption { type = lib.types.path; description = "The age-encrypted restic password, passed to agenix"; default = ./secrets/storagebox/repository_password.age; }; }; local = { enable = lib.mkEnableOption "local backups"; repositoryPassword = lib.mkOption { type = lib.types.path; description = "The age-encrypted restic password, passed to agenix"; default = ./secrets/local/repository_password.age; }; backupDiskUuid = lib.mkOption { example = lib.literalExpression "d1d20ae7-3d8a-44da-86da-677dbbb10c89"; description = "The UUID of the backup disk"; }; }; }; config = { age.secrets = { resticStorageboxSshKey = lib.mkIf cfg.storagebox.enable { file = cfg.storagebox.sshKey; mode = "0700"; owner = "root"; group = "root"; }; resticStorageboxRepositoryPassword = lib.mkIf cfg.storagebox.enable { file = cfg.storagebox.repositoryPassword; mode = "0700"; owner = "root"; group = "root"; }; resticLocalRepositoryPassword = lib.mkIf cfg.local.enable { file = cfg.local.repositoryPassword; mode = "0700"; owner = "root"; group = "root"; }; }; soispha.programs.ssh = lib.mkIf cfg.storagebox.enable { enable = true; rootKnownHosts = { "[u459143-sub1.your-storagebox.de]:23" = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIICf9svRenC/PLKIL9nk6K/pxQgoiFC41wTNvoIncOxs"; }; }; services.restic.backups = let snapshotDir = "/srv/last_snapshot"; homeDir = "${snapshotDir}/home"; paths = [ snapshotDir ]; exclude = [ "${homeDir}/soispha/.cache" ]; extraBackupArgs = [ "--verbose=2" ]; backupPrepareCommand = extra: # bash extra + '' [ -d "${snapshotDir}" ] && ${lib.getExe' pkgs.btrfs-progs "btrfs"} subvolume delete "${snapshotDir}" # -r := Make the snapshot read-only ${lib.getExe' pkgs.btrfs-progs "btrfs"} subvolume snapshot -r /srv "${snapshotDir}"; ''; in { local = let backupMountPoint = "/run/media/${cfg.local.backupDiskUuid}"; in lib.mkIf cfg.local.enable { inhibitsSleep = true; initialize = true; inherit paths exclude extraBackupArgs; # TODO: We could maybe use systemd's built-in system for this mounting. <2025-05-01> backupPrepareCommand = backupPrepareCommand # bash '' set -xeu ${lib.getExe' pkgs.util-linux "mount"} --mkdir "/dev/disk/by-uuid/${cfg.local.backupDiskUuid}" "${backupMountPoint}" ''; backupCleanupCommand = # bash '' ${lib.getExe' pkgs.util-linux "umount"} "${backupMountPoint}" ''; passwordFile = config.age.secrets.resticLocalRepositoryPassword.path; repository = "${backupMountPoint}/restic-backup-data/"; # Start on demand. timerConfig = null; }; storagebox = lib.mkIf cfg.storagebox.enable { inhibitsSleep = true; initialize = true; inherit paths exclude extraBackupArgs; backupPrepareCommand = backupPrepareCommand ""; passwordFile = config.age.secrets.resticStorageboxRepositoryPassword.path; extraOptions = [ "rclone.program='ssh -p 23 ${cfg.storagebox.user}@${cfg.storagebox.user}.your-storagebox.de -i ${config.age.secrets.resticStorageboxSshKey.path} command_forced_on_remote'" ]; # This setting is normally passed to rclone, but we force # the command on the remote. # As such, the value does not matter and must only be parseable by restic. repository = "rclone: "; timerConfig = { Requires = "network-online.target"; OnActiveSec = "30m"; OnUnitInactiveSec = "2h"; Persistent = true; }; }; }; }; }