homelab framework module init (everything is a mess)
This commit is contained in:
parent
0347f4d325
commit
bcbcc8b17b
94 changed files with 7289 additions and 436 deletions
187
modules/nixos/backup-manager.nix
Normal file
187
modules/nixos/backup-manager.nix
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
# modules/backup-manager.nix
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.homelab.backups;
|
||||
globalCfg = config.homelab.global;
|
||||
|
||||
# Create systemd services for backup jobs
|
||||
createBackupService = job: let
|
||||
serviceName = "backup-${job.name}";
|
||||
allExcludes = globalCfg.backups.globalExcludes ++ job.excludePatterns;
|
||||
excludeArgs = map (pattern: "--exclude '${pattern}'") allExcludes;
|
||||
|
||||
backupScript =
|
||||
if job.backend == "restic"
|
||||
then ''
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
${optionalString (job.preHook != null) job.preHook}
|
||||
|
||||
# Restic backup
|
||||
${pkgs.restic}/bin/restic backup \
|
||||
${concatStringsSep " " (map (path: "'${path}'") job.paths)} \
|
||||
${concatStringsSep " " excludeArgs} \
|
||||
--tag "host:${globalCfg.hostname}" \
|
||||
--tag "job:${job.name}" \
|
||||
--tag "env:${globalCfg.environment}"
|
||||
|
||||
# Apply retention policy
|
||||
${pkgs.restic}/bin/restic forget \
|
||||
--keep-daily ${job.retention.daily} \
|
||||
--keep-weekly ${job.retention.weekly} \
|
||||
--keep-monthly ${job.retention.monthly} \
|
||||
--keep-yearly ${job.retention.yearly} \
|
||||
--prune
|
||||
|
||||
${optionalString (job.postHook != null) job.postHook}
|
||||
''
|
||||
else if job.backend == "borg"
|
||||
then ''
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
${optionalString (job.preHook != null) job.preHook}
|
||||
|
||||
# Borg backup
|
||||
${pkgs.borgbackup}/bin/borg create \
|
||||
--stats --progress \
|
||||
${concatStringsSep " " excludeArgs} \
|
||||
"::${globalCfg.hostname}-${job.name}-{now}" \
|
||||
${concatStringsSep " " (map (path: "'${path}'") job.paths)}
|
||||
|
||||
# Apply retention policy
|
||||
${pkgs.borgbackup}/bin/borg prune \
|
||||
--keep-daily ${job.retention.daily} \
|
||||
--keep-weekly ${job.retention.weekly} \
|
||||
--keep-monthly ${job.retention.monthly} \
|
||||
--keep-yearly ${job.retention.yearly}
|
||||
|
||||
${optionalString (job.postHook != null) job.postHook}
|
||||
''
|
||||
else throw "Unsupported backup backend: ${job.backend}";
|
||||
in {
|
||||
${serviceName} = {
|
||||
description = "Backup job: ${job.name}";
|
||||
after = ["network-online.target"];
|
||||
wants = ["network-online.target"];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "backup";
|
||||
Group = "backup";
|
||||
ExecStart = pkgs.writeScript "backup-${job.name}" backupScript;
|
||||
EnvironmentFile = "/etc/backup/environment";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Create systemd timers for backup jobs
|
||||
createBackupTimer = job: let
|
||||
serviceName = "backup-${job.name}";
|
||||
timerName = "${serviceName}.timer";
|
||||
in {
|
||||
${timerName} = {
|
||||
description = "Timer for backup job: ${job.name}";
|
||||
wantedBy = ["timers.target"];
|
||||
timerConfig = {
|
||||
OnCalendar =
|
||||
if job.schedule == "daily"
|
||||
then "daily"
|
||||
else if job.schedule == "weekly"
|
||||
then "weekly"
|
||||
else if job.schedule == "hourly"
|
||||
then "hourly"
|
||||
else job.schedule; # Assume it's a cron expression
|
||||
Persistent = true;
|
||||
RandomizedDelaySec = "15min";
|
||||
};
|
||||
};
|
||||
};
|
||||
in {
|
||||
options.homelab.backups = {
|
||||
enable = mkEnableOption "Backup management";
|
||||
|
||||
restic = {
|
||||
repository = mkOption {
|
||||
type = types.str;
|
||||
description = "Restic repository URL";
|
||||
};
|
||||
passwordFile = mkOption {
|
||||
type = types.str;
|
||||
default = "/etc/backup/restic-password";
|
||||
description = "Path to file containing restic password";
|
||||
};
|
||||
};
|
||||
|
||||
borg = {
|
||||
repository = mkOption {
|
||||
type = types.str;
|
||||
description = "Borg repository path";
|
||||
};
|
||||
sshKey = mkOption {
|
||||
type = types.str;
|
||||
default = "/etc/backup/borg-ssh-key";
|
||||
description = "Path to SSH key for borg repository";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf (cfg.enable && globalCfg.enable && (length globalCfg.backups.jobs) > 0) {
|
||||
# Create backup user
|
||||
users.users.backup = {
|
||||
isSystemUser = true;
|
||||
group = "backup";
|
||||
home = "/var/lib/backup";
|
||||
createHome = true;
|
||||
};
|
||||
|
||||
users.groups.backup = {};
|
||||
|
||||
# Install backup tools
|
||||
environment.systemPackages = with pkgs; [
|
||||
restic
|
||||
borgbackup
|
||||
rclone
|
||||
|
||||
(pkgs.writeScriptBin "backup-status" ''
|
||||
#!/bin/bash
|
||||
echo "=== Backup Status ==="
|
||||
echo
|
||||
${concatStringsSep "\n" (map (job: ''
|
||||
echo "Job: ${job.name}"
|
||||
systemctl is-active backup-${job.name}.timer || echo "Timer inactive"
|
||||
systemctl status backup-${job.name}.timer --no-pager -l | grep -E "(Active|Trigger)" || true
|
||||
echo
|
||||
'')
|
||||
globalCfg.backups.jobs)}
|
||||
'')
|
||||
];
|
||||
|
||||
# Create systemd services and timers
|
||||
systemd.services = lib.foldl' (acc: job: acc // (createBackupService job)) {} globalCfg.backups.jobs;
|
||||
systemd.timers = lib.foldl' (acc: job: acc // (createBackupTimer job)) {} globalCfg.backups.jobs;
|
||||
|
||||
# Environment file template
|
||||
environment.etc."backup/environment.example".text = ''
|
||||
# Restic configuration
|
||||
RESTIC_REPOSITORY=${cfg.restic.repository}
|
||||
RESTIC_PASSWORD_FILE=${cfg.restic.passwordFile}
|
||||
|
||||
# AWS S3 credentials (if using S3 backend)
|
||||
AWS_ACCESS_KEY_ID=your-access-key
|
||||
AWS_SECRET_ACCESS_KEY=your-secret-key
|
||||
|
||||
# Borg configuration
|
||||
BORG_REPO=${cfg.borg.repository}
|
||||
BORG_RSH="ssh -i ${cfg.borg.sshKey}"
|
||||
|
||||
# Notification settings
|
||||
NOTIFICATION_URL=your-webhook-url
|
||||
'';
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue