homelab framework module init (everything is a mess)
Some checks failed
Test / tests (push) Has been cancelled
/ OpenTofu (push) Has been cancelled

This commit is contained in:
plasmagoat 2025-07-28 02:05:13 +02:00
parent 0347f4d325
commit bcbcc8b17b
94 changed files with 7289 additions and 436 deletions

View file

@ -0,0 +1,4 @@
{
# TODO
# https://github.com/L-Trump/nixos-configs/blob/ab3fb16e330b8a2904b9967e46af8c061b56266e/modules/nixos/server/backrest.nix#L7
}

View file

@ -0,0 +1,95 @@
# backups-option.nix
cfg: let
inherit (cfg.lib) mkOption types mkEnableOption attrNames;
in
mkOption {
type = types.attrsOf (
types.submodule (
{
name,
config,
...
} @ args: {
options = {
backend = mkOption {
type = types.enum (attrNames cfg.backends);
description = "The backup backend to use";
};
paths = mkOption {
type = types.listOf types.str;
default = [];
description = "Paths to backup";
};
enable = mkOption {
type = types.bool;
default = true;
description = "Whether to enable this backup job";
};
timerConfig = mkOption {
type = with types; nullOr attrs;
default = null;
example = {
OnCalendar = "00:05";
Persistent = true;
RandomizedDelaySec = "5h";
};
description = ''
When to run the backup. If null, inherits from backend's default timerConfig.
Set to null to disable automatic scheduling.
'';
};
backendOptions = mkOption {
type = let
backupConfig = config;
backupName = name;
in
types.submodule (
{config, ...} @ args'':
cfg.backends.${args.config.backend} (args'' // {inherit backupConfig backupName;})
);
default = {};
description = "Backend-specific options";
};
preBackupScript = mkOption {
type = types.lines;
default = "";
description = "Script to run before backing up";
};
postBackupScript = mkOption {
type = types.lines;
default = "";
description = ''
Script to run after backing up. Runs even if the backup fails.
'';
};
notifications = {
failure = {
enable = mkOption {
type = types.bool;
default = true;
description = "Enable failure notifications";
};
};
success = {
enable = mkOption {
type = types.bool;
default = false;
description = "Enable success notifications";
};
};
};
};
}
)
);
default = {};
description = "Backup job definitions";
}

View file

@ -0,0 +1,6 @@
{
imports = [
./root.nix
./restic.nix
];
}

View file

@ -0,0 +1,234 @@
# restic.nix - Restic backend implementation
{
config,
lib,
pkgs,
...
}:
with lib; let
cfg = config.system.backups;
resticCfg = cfg.restic;
# Get only restic backups that are enabled
resticBackups = filterAttrs (_: backup: backup.backend == "restic" && backup.enable) cfg.backups;
# Create restic service configurations
createResticServices =
mapAttrs (
name: backup: let
# Merge global defaults with backup-specific options
serviceConfig =
recursiveUpdate resticCfg.defaultBackendOptions backup.backendOptions
// {
inherit (backup) paths;
# Use backup-specific timer or fall back to global default
timerConfig =
if backup.timerConfig != null
then backup.timerConfig
else resticCfg.timerConfig;
};
in
serviceConfig
)
resticBackups;
in {
options.system.backups.restic = {
enable = mkEnableOption "restic backup backend";
timerConfig = mkOption {
type = types.attrs;
default = {
OnCalendar = "*-*-* 05:00:00";
Persistent = true;
};
description = "Default systemd timer configuration for restic backups";
};
defaultBackendOptions = mkOption {
type = types.attrs;
default = {};
example = {
repository = "/backup/restic";
passwordFile = "/etc/nixos/secrets/restic-password";
initialize = true;
pruneOpts = [
"--keep-daily 7"
"--keep-weekly 5"
"--keep-monthly 12"
"--keep-yearly 75"
];
};
description = "Default backend options applied to all restic backup jobs";
};
# Advanced options
runMaintenance = mkOption {
type = types.bool;
default = true;
description = "Whether to run repository maintenance after backups";
};
maintenanceTimer = mkOption {
type = types.attrs;
default = {
OnCalendar = "*-*-* 06:00:00";
Persistent = true;
};
description = "Timer configuration for maintenance tasks";
};
pruneOpts = mkOption {
type = types.listOf types.str;
default = [
"--keep-daily 7"
"--keep-weekly 4"
"--keep-monthly 6"
"--keep-yearly 3"
];
description = "Default pruning options for maintenance";
};
};
config = mkIf resticCfg.enable {
# Register restic backend
system.backups.backends.restic = {
backupConfig,
backupName,
...
}: {
# Define the proper options schema for restic backendOptions
options = {
repository = mkOption {
type = types.str;
description = "Restic repository path or URL";
};
passwordFile = mkOption {
type = types.str;
description = "Path to file containing the repository password";
};
initialize = mkOption {
type = types.bool;
default = true;
description = "Whether to initialize the repository if it doesn't exist";
};
exclude = mkOption {
type = types.listOf types.str;
default = [];
description = "Patterns to exclude from backup";
};
extraBackupArgs = mkOption {
type = types.listOf types.str;
default = [];
description = "Additional arguments passed to restic backup command";
};
user = mkOption {
type = types.str;
default = "root";
description = "User to run the backup as";
};
pruneOpts = mkOption {
type = types.listOf types.str;
default = resticCfg.pruneOpts;
description = "Pruning options for this backup";
};
};
# Default config merged with global defaults
config = {
extraBackupArgs =
[
"--tag ${backupName}"
"--verbose"
]
++ (resticCfg.defaultBackendOptions.extraBackupArgs or []);
};
};
# Create actual restic backup services
services.restic.backups = createResticServices;
# Add restic package
environment.systemPackages = [pkgs.restic];
# Systemd service customizations for restic backups
systemd.services =
(mapAttrs' (
name: backup:
nameValuePair "restic-backups-${name}" {
# Custom pre/post scripts
preStart = mkBefore backup.preBackupScript;
postStop = mkAfter backup.postBackupScript;
# Enhanced service configuration
serviceConfig = {
# Restart configuration
Restart = "on-failure";
RestartSec = "5m";
RestartMaxDelaySec = "30m";
RestartSteps = 3;
# Rate limiting
StartLimitBurst = 4;
StartLimitIntervalSec = "2h";
};
# Failure handling could be extended here for notifications
# onFailure = optional backup.notifications.failure.enable "restic-backup-${name}-failure-notify.service";
}
)
resticBackups)
// optionalAttrs resticCfg.runMaintenance {
# Repository maintenance service
restic-maintenance = {
description = "Restic repository maintenance";
after = map (name: "restic-backups-${name}.service") (attrNames resticBackups);
environment =
resticCfg.defaultBackendOptions
// {
RESTIC_CACHE_DIR = "/var/cache/restic-maintenance";
};
serviceConfig = {
Type = "oneshot";
ExecStart = [
"${pkgs.restic}/bin/restic forget --prune ${concatStringsSep " " resticCfg.pruneOpts}"
"${pkgs.restic}/bin/restic check --read-data-subset=500M"
];
User = "root";
CacheDirectory = "restic-maintenance";
CacheDirectoryMode = "0700";
};
};
};
# Maintenance timer
systemd.timers = mkIf resticCfg.runMaintenance {
restic-maintenance = {
description = "Timer for restic repository maintenance";
wantedBy = ["timers.target"];
timerConfig = resticCfg.maintenanceTimer;
};
};
# Helpful shell aliases
programs.zsh.shellAliases =
{
restic-snapshots = "restic snapshots --compact --group-by tags";
restic-repo-size = "restic stats --mode raw-data";
}
// (mapAttrs' (
name: _:
nameValuePair "backup-${name}" "systemctl start restic-backups-${name}"
)
resticBackups);
};
}

View file

@ -0,0 +1,66 @@
# root.nix - Main backup system module
{
config,
lib,
pkgs,
...
}:
with lib; let
cfg = config.system.backups;
# Filter backups by backend
getBackupsByBackend = backend:
filterAttrs (_: backup: backup.backend == backend && backup.enable) cfg.backups;
in {
options.system.backups = {
# Backend registration system - backends register themselves here
backends = mkOption {
type = with types; attrsOf (functionTo attrs);
internal = true;
default = {};
description = ''
Attribute set of backends where the value is a function that accepts
backend-specific arguments and returns an attribute set for the backend's options.
'';
};
# Import the backups option from separate file, passing cfg for backend inference
backups = import ./backups-option.nix cfg;
# Pass lib to the backups-option for access to mkOption, types, etc.
lib = mkOption {
type = types.attrs;
internal = true;
default = lib;
};
};
config = {
# Re-export backups at root level for convenience
# backups = cfg.backups;
# Common backup packages
environment.systemPackages = with pkgs; [
# Add common backup utilities here
];
# Common systemd service modifications for all backup services
systemd.services = let
allBackupServices = flatten (
mapAttrsToList (
backendName: backups:
mapAttrsToList (name: backup: "${backendName}-backups-${name}") backups
) (genAttrs (attrNames cfg.backends) (backend: getBackupsByBackend backend))
);
in
genAttrs allBackupServices (serviceName: {
serviceConfig = {
# Common hardening for all backup services
ProtectSystem = "strict";
ProtectHome = "read-only";
PrivateTmp = true;
NoNewPrivileges = true;
};
});
};
}