diff --git a/modules/blocks/ldap.nix b/modules/blocks/ldap.nix index 782775c..48cee98 100644 --- a/modules/blocks/ldap.nix +++ b/modules/blocks/ldap.nix @@ -87,6 +87,30 @@ in readOnly = true; default = { path = "/var/lib/lldap"; }; }; + + backup = lib.mkOption { + type = contracts.backup; + description = '' + Backup configuration. This is an output option. + + Use it to initialize a block implementing the "backup" contract. + For example, with the restic block: + + ``` + shb.restic.instances."lldap" = { + enable = true; + + # Options specific to Restic. + } // config.shb.lldap.backup; + ``` + ''; + readOnly = true; + default = { + sourceDirectories = [ + "/var/lib/lldap" + ]; + }; + }; }; @@ -143,11 +167,5 @@ in verbose = cfg.debug; }; }; - - shb.backup.instances.lldap = { - sourceDirectories = [ - "/var/lib/lldap" - ]; - }; }; } diff --git a/modules/contracts/backup/docs/default.md b/modules/contracts/backup/docs/default.md index 4e8d790..e0ba5d0 100644 --- a/modules/contracts/backup/docs/default.md +++ b/modules/contracts/backup/docs/default.md @@ -34,7 +34,7 @@ Then, to actually backup the service, one would write: shb..instances."" = shb..backup // { enable = true; - // Options specific to backup_impl + # Options specific to backup_impl }; ``` @@ -44,7 +44,7 @@ Then, for extra caution, a second backup could be made using another module `shb shb..instances."" = shb..backup // { enable = true; - // Options specific to backup_impl_2 + # Options specific to backup_impl_2 }; ``` diff --git a/modules/services/arr.nix b/modules/services/arr.nix index 95a0f41..3cbb4cc 100644 --- a/modules/services/arr.nix +++ b/modules/services/arr.nix @@ -1,4 +1,4 @@ -{ config, pkgs, lib, ... }: +{ config, options, pkgs, lib, ... }: let cfg = config.shb.arr; @@ -329,13 +329,28 @@ let example = "https://authelia.example.com"; }; - backupCfg = lib.mkOption { - type = lib.types.anything; - description = "Backup configuration for ${name}."; - default = {}; - example = { - backend = "restic"; - repositories = []; + backup = lib.mkOption { + type = contracts.backup; + description = '' + Backup configuration. This is an output option. + + Use it to initialize a block implementing the "backup" contract. + For example, with the restic block: + + ``` + shb.restic.instances."${name}" = { + enable = true; + + # Options specific to Restic. + } // config.shb.${name}.backup; + ``` + ''; + readOnly = true; + default = { + sourceDirectories = [ + cfg.${name}.dataDir + ]; + excludePatterns = [".db-shm" ".db-wal" ".mono"]; }; }; } // (c.moreOptions or {}); @@ -370,13 +385,6 @@ in }; shb.nginx.vhosts = [ (vhosts {} cfg') ]; - - shb.backup.instances.radarr = cfg'.backupCfg // { - sourceDirectories = [ - cfg'.dataDir - ]; - excludePatterns = [".db-shm" ".db-wal" ".mono"]; - }; })) (lib.mkIf cfg.radarr.enable (backup "radarr")) @@ -407,13 +415,6 @@ in }; shb.nginx.vhosts = [ (vhosts {} cfg') ]; - - shb.backup.instances.sonarr = cfg'.backupCfg // { - sourceDirectories = [ - cfg'.dataDir - ]; - excludePatterns = [".db-shm" ".db-wal" ".mono"]; - }; })) (lib.mkIf cfg.sonarr.enable (backup "sonarr")) @@ -441,13 +442,6 @@ in }; shb.nginx.vhosts = [ (vhosts {} cfg') ]; - - shb.backup.instances.bazarr = cfg'.backupCfg // { - sourceDirectories = [ - cfg'.dataDir - ]; - excludePatterns = [".db-shm" ".db-wal" ".mono"]; - }; })) (lib.mkIf cfg.bazarr.enable (backup "bazarr")) @@ -470,13 +464,6 @@ in }; shb.nginx.vhosts = [ (vhosts {} cfg') ]; - - shb.backup.instances.readarr = cfg'.backupCfg // { - sourceDirectories = [ - cfg'.dataDir - ]; - excludePatterns = [".db-shm" ".db-wal" ".mono"]; - }; })) (lib.mkIf cfg.readarr.enable (backup "readarr")) @@ -504,13 +491,6 @@ in }; shb.nginx.vhosts = [ (vhosts {} cfg') ]; - - shb.backup.instances.lidarr = cfg'.backupCfg // { - sourceDirectories = [ - cfg'.dataDir - ]; - excludePatterns = [".db-shm" ".db-wal" ".mono"]; - }; })) (lib.mkIf cfg.lidarr.enable (backup "lidarr")) @@ -535,13 +515,6 @@ in shb.nginx.vhosts = [ (vhosts { extraBypassResources = [ "^/dl.*" ]; } cfg') ]; - - shb.backup.instances.jackett = cfg'.backupCfg // { - sourceDirectories = [ - cfg'.dataDir - ]; - excludePatterns = [".db-shm" ".db-wal" ".mono"]; - }; })) (lib.mkIf cfg.jackett.enable (backup "jackett")) ]; diff --git a/modules/services/audiobookshelf.nix b/modules/services/audiobookshelf.nix index c9a1140..d91a2df 100644 --- a/modules/services/audiobookshelf.nix +++ b/modules/services/audiobookshelf.nix @@ -82,6 +82,30 @@ in description = "File containing the SSO shared secret."; }; + backup = lib.mkOption { + type = contracts.backup; + description = '' + Backup configuration. This is an output option. + + Use it to initialize a block implementing the "backup" contract. + For example, with the restic block: + + ``` + shb.restic.instances."audiobookshelf" = { + enable = true; + + # Options specific to Restic. + } // config.shb.audiobookshelf.backup; + ``` + ''; + readOnly = true; + default = { + sourceDirectories = [ + "/var/lib/audiobookshelf" + ]; + }; + }; + logLevel = lib.mkOption { type = lib.types.nullOr (lib.types.enum ["critical" "error" "warning" "info" "debug"]); description = "Enable logging."; @@ -149,11 +173,6 @@ in # We backup the whole audiobookshelf directory and set permissions for the backup user accordingly. users.groups.audiobookshelf.members = [ "backup" ]; users.groups.media.members = [ "backup" ]; - shb.backup.instances.audiobookshelf = { - sourceDirectories = [ - /var/lib/${config.services.audiobookshelf.dataDir} - ]; - }; } { systemd.services.audiobookshelfd.serviceConfig = cfg.extraServiceConfig; }]); diff --git a/modules/services/deluge.nix b/modules/services/deluge.nix index cc24b2c..4efd3c1 100644 --- a/modules/services/deluge.nix +++ b/modules/services/deluge.nix @@ -39,6 +39,12 @@ in default = null; }; + dataDir = lib.mkOption { + type = lib.types.str; + description = "Path where all configuration and state is stored."; + default = "/var/lib/deluge"; + }; + daemonPort = lib.mkOption { type = lib.types.int; description = "Deluge daemon port"; @@ -227,6 +233,30 @@ in ''; }; + backup = lib.mkOption { + type = contracts.backup; + description = '' + Backup configuration. This is an output option. + + Use it to initialize a block implementing the "backup" contract. + For example, with the restic block: + + ``` + shb.restic.instances."vaultwarden" = { + enable = true; + + # Options specific to Restic. + } // config.shb.vaultwarden.backup; + ``` + ''; + readOnly = true; + default = { + sourceDirectories = [ + cfg.dataDir + ]; + }; + }; + logLevel = lib.mkOption { type = lib.types.nullOr (lib.types.enum ["critical" "error" "warning" "info" "debug"]); description = "Enable logging."; @@ -240,6 +270,8 @@ in enable = true; declarative = true; openFirewall = true; + inherit (cfg) dataDir; + config = { download_location = cfg.settings.downloadLocation; allow_remote = true; @@ -285,7 +317,7 @@ in new_release_check = false; }; - authFile = "${config.services.deluge.dataDir}/.config/deluge/authTemplate"; + authFile = "${cfg.dataDir}/.config/deluge/authTemplate"; web.enable = true; web.port = cfg.webPort; @@ -297,14 +329,14 @@ in } // (lib.optionalAttrs (config.shb.deluge.prometheusScraperPasswordFile != null) { prometheus_scraper.password.source = config.shb.deluge.prometheusScraperPasswordFile; }); - resultPath = "${config.services.deluge.dataDir}/.config/deluge/authTemplate"; + resultPath = "${cfg.dataDir}/.config/deluge/authTemplate"; generator = name: value: pkgs.writeText "delugeAuth" (authGenerator value); }); systemd.services.deluged.serviceConfig.ExecStart = lib.mkForce (lib.concatStringsSep " \\\n " ([ "${config.services.deluge.package}/bin/deluged" "--do-not-daemonize" - "--config ${config.services.deluge.dataDir}/.config/deluge" + "--config ${cfg.dataDir}/.config/deluge" ] ++ (lib.optional (!(isNull cfg.logLevel)) "-L ${cfg.logLevel}") )); @@ -316,7 +348,7 @@ in }; in [ - "L+ ${config.services.deluge.dataDir}/.config/deluge/plugins - - - - ${plugins}" + "L+ ${cfg.dataDir}/.config/deluge/plugins - - - - ${plugins}" ]; shb.nginx.vhosts = [ @@ -352,11 +384,6 @@ in # We backup the whole deluge directory and set permissions for the backup user accordingly. users.groups.deluge.members = [ "backup" ]; users.groups.media.members = [ "backup" ]; - shb.backup.instances.deluge = { - sourceDirectories = [ - config.services.deluge.dataDir - ]; - }; } { systemd.services.deluged.serviceConfig = cfg.extraServiceConfig; } (lib.mkIf (config.shb.deluge.prometheusScraperPasswordFile != null) { diff --git a/modules/services/grocy.nix b/modules/services/grocy.nix index de6968b..6c36d33 100644 --- a/modules/services/grocy.nix +++ b/modules/services/grocy.nix @@ -62,6 +62,30 @@ in ''; }; + backup = lib.mkOption { + type = contracts.backup; + description = '' + Backup configuration. This is an output option. + + Use it to initialize a block implementing the "backup" contract. + For example, with the restic block: + + ``` + shb.restic.instances."grocy" = { + enable = true; + + # Options specific to Restic. + } // config.shb.grocy.backup; + ``` + ''; + readOnly = true; + default = { + sourceDirectories = [ + cfg.dataDir + ]; + }; + }; + logLevel = lib.mkOption { type = lib.types.nullOr (lib.types.enum ["critical" "error" "warning" "info" "debug"]); description = "Enable logging."; @@ -95,11 +119,6 @@ in # We backup the whole grocy directory and set permissions for the backup user accordingly. users.groups.grocy.members = [ "backup" ]; users.groups.media.members = [ "backup" ]; - shb.backup.instances.grocy = { - sourceDirectories = [ - config.services.grocy.dataDir - ]; - }; } { systemd.services.grocyd.serviceConfig = cfg.extraServiceConfig; }]); diff --git a/modules/services/hledger.nix b/modules/services/hledger.nix index b4bb9da..335c8ee 100644 --- a/modules/services/hledger.nix +++ b/modules/services/hledger.nix @@ -23,6 +23,12 @@ in example = "mydomain.com"; }; + dataDir = lib.mkOption { + description = "Folder where Hledger will store all its data."; + type = lib.types.str; + default = "/var/lib/hledger"; + }; + ssl = lib.mkOption { description = "Path to SSL files"; type = lib.types.nullOr contracts.ssl.certs; @@ -47,6 +53,30 @@ in description = "OIDC endpoint for SSO"; example = "https://authelia.example.com"; }; + + backup = lib.mkOption { + type = contracts.backup; + description = '' + Backup configuration. This is an output option. + + Use it to initialize a block implementing the "backup" contract. + For example, with the restic block: + + ``` + shb.restic.instances."hledger" = { + enable = true; + + # Options specific to Restic. + } // config.shb.hledger.backup; + ``` + ''; + readOnly = true; + default = { + sourceDirectories = [ + cfg.dataDir + ]; + }; + }; }; config = lib.mkIf cfg.enable { @@ -55,7 +85,7 @@ in # Must be empty otherwise it repeats the fqdn, we get something like https://${fqdn}/${fqdn}/ baseUrl = ""; - stateDir = "/var/lib/hledger"; + stateDir = cfg.dataDir; journalFiles = ["hledger.journal"]; host = "127.0.0.1"; @@ -89,11 +119,5 @@ in }]; } ]; - - shb.backup.instances.hledger = { - sourceDirectories = [ - config.services.hledger-web.stateDir - ]; - }; }; } diff --git a/modules/services/home-assistant.nix b/modules/services/home-assistant.nix index 5b5424b..c9f852e 100644 --- a/modules/services/home-assistant.nix +++ b/modules/services/home-assistant.nix @@ -136,13 +136,28 @@ in }; }; - backupCfg = lib.mkOption { - type = lib.types.anything; - description = "Backup configuration for home-assistant"; - default = {}; - example = { - backend = "restic"; - repositories = []; + backup = lib.mkOption { + type = contracts.backup; + description = '' + Backup configuration. This is an output option. + + Use it to initialize a block implementing the "backup" contract. + For example, with the restic block: + + ``` + shb.restic.instances."home-assistant" = { + enable = true; + + # Options specific to Restic. + } // config.shb.home-assistant.backup; + ``` + ''; + readOnly = true; + default = { + # No need for backup hooks as we use an hourly automation job in home assistant directly with a cron job. + sourceDirectories = [ + "/var/lib/hass/backups" + ]; }; }; }; @@ -308,17 +323,6 @@ in "f ${config.services.home-assistant.configDir}/scripts.yaml 0755 hass hass" ]; - shb.backup.instances.home-assistant = lib.mkIf (cfg.backupCfg != {}) ( - cfg.backupCfg - // { - sourceDirectories = [ - "${config.services.home-assistant.configDir}/backups" - ]; - - # No need for backup hooks as we use an hourly automation job in home assistant directly with a cron job. - } - ); - # Adds the "backup" user to the "hass" group. users.groups.hass = { members = [ "backup" ]; diff --git a/modules/services/jellyfin.nix b/modules/services/jellyfin.nix index dcc1450..56f64ba 100644 --- a/modules/services/jellyfin.nix +++ b/modules/services/jellyfin.nix @@ -119,6 +119,30 @@ in }; }; }; + + backup = lib.mkOption { + type = contracts.backup; + description = '' + Backup configuration. This is an output option. + + Use it to initialize a block implementing the "backup" contract. + For example, with the restic block: + + ``` + shb.restic.instances."jellyfin" = { + enable = true; + + # Options specific to Restic. + } // config.shb.jellyfin.backup; + ``` + ''; + readOnly = true; + default = { + sourceDirectories = [ + "/var/lib/jellyfin" + ]; + }; + }; }; config = lib.mkIf cfg.enable { @@ -250,12 +274,6 @@ in ''; }; - shb.backup.instances.jellyfin = { - sourceDirectories = [ - "/var/lib/jellyfin" - ]; - }; - services.prometheus.scrapeConfigs = [{ job_name = "jellyfin"; static_configs = [ diff --git a/modules/services/nextcloud-server.nix b/modules/services/nextcloud-server.nix index a91a07e..c252164 100644 --- a/modules/services/nextcloud-server.nix +++ b/modules/services/nextcloud-server.nix @@ -1,7 +1,8 @@ -{ config, pkgs, lib, ... }: +{ config, options, pkgs, lib, ... }: let cfg = config.shb.nextcloud; + opt = options.shb.nextcloud; fqdn = "${cfg.subdomain}.${cfg.domain}"; fqdnWithPort = if isNull cfg.port then fqdn else "${fqdn}:${toString cfg.port}"; @@ -496,6 +497,32 @@ in ''; }; + + backup = lib.mkOption { + type = contracts.backup; + description = '' + Backup configuration. This is an output option. + + Use it to initialize a block implementing the "backup" contract. + For example, with the restic block: + + ``` + shb.restic.instances."nextcloud" = { + enable = true; + + # Options specific to Restic. + } // config.shb.nextcloud.backup; + ``` + ''; + readOnly = true; + default = { + sourceDirectories = [ + cfg.dataDir + ]; + excludePatterns = [".rnd"]; + }; + }; + debug = lib.mkOption { type = lib.types.bool; description = "Enable more verbose logging."; @@ -542,11 +569,11 @@ in }; }; - users.groups = { - nextcloud = { - members = [ "backup" ]; - }; - }; + # users.groups = { + # nextcloud = { + # members = [ "backup" ]; + # }; + # }; # LDAP is manually configured through # https://github.com/lldap/lldap/blob/main/example_configs/nextcloud.md, see also @@ -700,14 +727,6 @@ in systemd.services.nextcloud-setup.requires = cfg.mountPointServices; systemd.services.nextcloud-setup.after = cfg.mountPointServices; - - # Sets up backup for Nextcloud. - shb.backup.instances.nextcloud = { - sourceDirectories = [ - cfg.dataDir - ]; - excludePatterns = [".rnd"]; - }; }) (lib.mkIf cfg.apps.onlyoffice.enable { diff --git a/modules/services/vaultwarden.nix b/modules/services/vaultwarden.nix index 21ee588..ba2fa78 100644 --- a/modules/services/vaultwarden.nix +++ b/modules/services/vaultwarden.nix @@ -124,7 +124,9 @@ in ``` shb.restic.instances."vaultwarden" = { - poolName = "root"; + enable = true; + + # Options specific to Restic. } // config.shb.vaultwarden.backup; ``` ''; diff --git a/modules/services/vaultwarden/docs/default.md b/modules/services/vaultwarden/docs/default.md index af1a6d1..f2b130e 100644 --- a/modules/services/vaultwarden/docs/default.md +++ b/modules/services/vaultwarden/docs/default.md @@ -9,7 +9,7 @@ This NixOS module is a service that sets up a [Vaultwarden Server](https://githu - Access through subdomain using reverse proxy. - Access through HTTPS using reverse proxy. - Automatic setup of Redis database for caching. -- Backup of the data directory through the [backup block](./blocks-backup.html). +- Backup of the data directory through the [backup contract](./contracts-backup.html). - [Integration Tests](@REPO@/test/services/vaultwarden.nix) - Tests /admin can only be accessed when authenticated with SSO. - Access to advanced options not exposed here thanks to how NixOS modules work. @@ -92,13 +92,27 @@ shb.zfs.datasets."vaultwarden" = config.shb.vaultwarden.mount; shb.zfs.datasets."postgresql".path = "/var/lib/postgresql"; ``` +### Backup {#services-vaultwarden-backup} + +Backing up Vaultwarden using the [Restic block](blocks-restic.html) is done like so: + +```nix +shb.restic.instances."vaultwarden" = config.shb.vaultwarden.backup // { + enable = true; +}; +``` + +The name `"vaultwarden"` in the `instances` can be anything. +The `config.shb.vaultwarden.backup` option provides what directories to backup. +You can define any number of Restic instances to backup Vaultwarden multiple times. + ## Maintenance {#services-vaultwarden-maintenance} No command-line tool is provided to administer Vaultwarden. Instead, the admin section can be found at the `/admin` endpoint. -## Debug {#services-backup-debug} +## Debug {#services-vaultwarden-debug} In case of an issue, check the logs of the `vaultwarden.service` systemd service. diff --git a/test/blocks/authelia.nix b/test/blocks/authelia.nix index d6fdda9..d1c9df0 100644 --- a/test/blocks/authelia.nix +++ b/test/blocks/authelia.nix @@ -12,11 +12,6 @@ in imports = [ (pkgs'.path + "/nixos/modules/profiles/headless.nix") (pkgs'.path + "/nixos/modules/profiles/qemu-guest.nix") - { - options = { - shb.backup = lib.mkOption { type = lib.types.anything; }; - }; - } ../../modules/blocks/authelia.nix ../../modules/blocks/ldap.nix ../../modules/blocks/postgresql.nix diff --git a/test/blocks/ldap.nix b/test/blocks/ldap.nix index a6724ca..2b67cbd 100644 --- a/test/blocks/ldap.nix +++ b/test/blocks/ldap.nix @@ -13,7 +13,6 @@ in { options = { shb.ssl.enable = lib.mkEnableOption "ssl"; - shb.backup = lib.mkOption { type = lib.types.anything; }; }; } ../../modules/blocks/ldap.nix diff --git a/test/common.nix b/test/common.nix index 88a1322..d1ed745 100644 --- a/test/common.nix +++ b/test/common.nix @@ -104,12 +104,6 @@ in imports = ( baseImports pkgs ) ++ [ - # TODO: replace this option by the backup contract - { - options = { - shb.backup = lib.mkOption { type = lib.types.anything; }; - }; - } # TODO: replace postgresql.nix and authelia.nix by the sso contract ../modules/blocks/postgresql.nix ../modules/blocks/authelia.nix diff --git a/test/modules/arr.nix b/test/modules/arr.nix index 2553ab6..7273a89 100644 --- a/test/modules/arr.nix +++ b/test/modules/arr.nix @@ -13,7 +13,6 @@ let { options = { systemd = anyOpt {}; - shb.backup = anyOpt {}; shb.nginx = anyOpt {}; users = anyOpt {}; services.nginx = anyOpt {}; @@ -34,14 +33,13 @@ let in { inherit (cfg) services users; systemd = systemdRedacted; - shb = { inherit (cfg.shb) backup nginx; }; + shb = { inherit (cfg.shb) nginx; }; }; in { testArrNoOptions = { expected = { systemd = {}; - shb.backup = {}; shb.nginx = {}; users = {}; services.nginx = {}; @@ -67,16 +65,6 @@ in systemd.tmpfiles.rules = [ "d '/var/lib/radarr' 0750 radarr radarr - -" ]; - shb.backup.instances.radarr = { - excludePatterns = [ - ".db-shm" - ".db-wal" - ".mono" - ]; - sourceDirectories = [ - "/var/lib/radarr" - ]; - }; shb.nginx.vhosts = [ { autheliaRules = [ @@ -143,13 +131,6 @@ in systemd.tmpfiles.rules = [ "d '/var/lib/radarr' 0750 radarr radarr - -" ]; - shb.backup.instances = { - radarr = { - enable = true; - sourceDirectories = [ "/var/lib/radarr" ]; - excludePatterns = [ ".db-shm" ".db-wal" ".mono" ]; - }; - }; shb.nginx.vhosts = [ { autheliaRules = [ @@ -201,9 +182,6 @@ in settings = { ApiKey.source = pkgs.writeText "key" "/run/radarr/apikey"; }; - backupCfg = { - enable = true; - }; }; }; }; diff --git a/test/modules/nginx.nix b/test/modules/nginx.nix index fab349f..6cf1e7f 100644 --- a/test/modules/nginx.nix +++ b/test/modules/nginx.nix @@ -17,7 +17,6 @@ let security = anyOpt {}; services = anyOpt {}; shb.authelia = anyOpt {}; - shb.backup = anyOpt {}; systemd = anyOpt {}; users = anyOpt {}; }; @@ -29,13 +28,12 @@ let }).config; in lib.attrsets.filterAttrsRecursive (n: v: n != "extraConfig") { inherit (cfg) services; - shb = { inherit (cfg.shb) backup nginx; }; + shb = { inherit (cfg.shb) nginx; }; }; in { testNoOptions = { expected = { - shb.backup = {}; shb.nginx = { accessLog = false; vhosts = [];