simplify backup of services by using user
This commit is contained in:
parent
2e8b3fb166
commit
dd4a13ad43
13 changed files with 178 additions and 364 deletions
|
|
@ -106,6 +106,7 @@ in
|
||||||
'';
|
'';
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
default = {
|
default = {
|
||||||
|
user = "lldap";
|
||||||
sourceDirectories = [
|
sourceDirectories = [
|
||||||
"/var/lib/lldap"
|
"/var/lib/lldap"
|
||||||
];
|
];
|
||||||
|
|
@ -139,10 +140,7 @@ in
|
||||||
group = "lldap";
|
group = "lldap";
|
||||||
isSystemUser = true;
|
isSystemUser = true;
|
||||||
};
|
};
|
||||||
|
users.groups.lldap = {};
|
||||||
users.groups.lldap = {
|
|
||||||
members = [ "backup" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
services.lldap = {
|
services.lldap = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,9 @@ let
|
||||||
|
|
||||||
user = lib.mkOption {
|
user = lib.mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
Unix user doing the backups.
|
Unix user doing the backups. Must be the user owning the files to be backed up.
|
||||||
|
|
||||||
For Restic, the same user must be used for all instances.
|
|
||||||
'';
|
'';
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
default = cfg.user;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
sourceDirectories = lib.mkOption {
|
sourceDirectories = lib.mkOption {
|
||||||
|
|
@ -115,12 +112,6 @@ let
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.shb.restic = {
|
options.shb.restic = {
|
||||||
user = lib.mkOption {
|
|
||||||
description = "Unix user doing the backups.";
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "backup";
|
|
||||||
};
|
|
||||||
|
|
||||||
instances = lib.mkOption {
|
instances = lib.mkOption {
|
||||||
description = "Each instance is a backup setting";
|
description = "Each instance is a backup setting";
|
||||||
default = {};
|
default = {};
|
||||||
|
|
@ -159,34 +150,6 @@ in
|
||||||
let
|
let
|
||||||
enabledInstances = lib.attrsets.filterAttrs (k: i: i.enable) cfg.instances;
|
enabledInstances = lib.attrsets.filterAttrs (k: i: i.enable) cfg.instances;
|
||||||
in lib.mkMerge [
|
in lib.mkMerge [
|
||||||
{
|
|
||||||
assertions = [
|
|
||||||
{
|
|
||||||
assertion = lib.all (x: x.user == cfg.user) (lib.mapAttrsToList (n: v: v)cfg.instances);
|
|
||||||
message = "All Restic instances must have the same user as 'shb.restic.user'.";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
assertion = lib.all (x: x.group == cfg.group) (lib.mapAttrsToList (n: v: v) cfg.instances);
|
|
||||||
message = "All Restic instances must have the same group as 'shb.restic.group'.";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
users.users = {
|
|
||||||
${cfg.user} = {
|
|
||||||
name = cfg.user;
|
|
||||||
group = cfg.group;
|
|
||||||
home = lib.mkForce "/var/lib/${cfg.user}";
|
|
||||||
createHome = true;
|
|
||||||
isSystemUser = true;
|
|
||||||
extraGroups = [ "keys" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
users.groups = {
|
|
||||||
${cfg.group} = {
|
|
||||||
name = cfg.group;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
environment.systemPackages = lib.optionals (enabledInstances != {}) [ pkgs.restic ];
|
environment.systemPackages = lib.optionals (enabledInstances != {}) [ pkgs.restic ];
|
||||||
|
|
||||||
|
|
@ -204,7 +167,8 @@ in
|
||||||
let
|
let
|
||||||
mkRepositorySettings = name: instance: repository: {
|
mkRepositorySettings = name: instance: repository: {
|
||||||
"${name}_${repoSlugName repository.path}" = {
|
"${name}_${repoSlugName repository.path}" = {
|
||||||
inherit (cfg) user;
|
inherit (instance) user;
|
||||||
|
|
||||||
repository = repository.path;
|
repository = repository.path;
|
||||||
|
|
||||||
paths = instance.sourceDirectories;
|
paths = instance.sourceDirectories;
|
||||||
|
|
@ -244,12 +208,13 @@ in
|
||||||
Nice = cfg.performance.niceness;
|
Nice = cfg.performance.niceness;
|
||||||
IOSchedulingClass = cfg.performance.ioSchedulingClass;
|
IOSchedulingClass = cfg.performance.ioSchedulingClass;
|
||||||
IOSchedulingPriority = cfg.performance.ioPriority;
|
IOSchedulingPriority = cfg.performance.ioPriority;
|
||||||
|
BindReadOnlyPaths = instance.sourceDirectories;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
(lib.attrsets.optionalAttrs (repository.secrets != {})
|
(lib.attrsets.optionalAttrs (repository.secrets != {})
|
||||||
{
|
{
|
||||||
serviceConfig.EnvironmentFile = [
|
serviceConfig.EnvironmentFile = [
|
||||||
"/run/secrets/restic/${serviceName}"
|
"/run/secrets_restic/${serviceName}"
|
||||||
];
|
];
|
||||||
after = [ "${serviceName}-pre.service" ];
|
after = [ "${serviceName}-pre.service" ];
|
||||||
requires = [ "${serviceName}-pre.service" ];
|
requires = [ "${serviceName}-pre.service" ];
|
||||||
|
|
@ -260,8 +225,9 @@ in
|
||||||
(let
|
(let
|
||||||
script = shblib.genConfigOutOfBandSystemd {
|
script = shblib.genConfigOutOfBandSystemd {
|
||||||
config = repository.secrets;
|
config = repository.secrets;
|
||||||
configLocation = "/run/secrets/restic/${serviceName}";
|
configLocation = "/run/secrets_restic/${serviceName}";
|
||||||
generator = name: v: pkgs.writeText "template" (lib.generators.toINIWithGlobalSection {} { globalSection = v; });
|
generator = name: v: pkgs.writeText "template" (lib.generators.toINIWithGlobalSection {} { globalSection = v; });
|
||||||
|
user = instance.user;
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -277,20 +277,6 @@ let
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
backup = name: {
|
|
||||||
systemd.tmpfiles.rules = [
|
|
||||||
"d '${config.shb.arr.${name}.dataDir}' 0750 ${config.services.${name}.user} ${config.services.${name}.group} - -"
|
|
||||||
];
|
|
||||||
users.groups.${name} = {
|
|
||||||
members = [ "backup" ];
|
|
||||||
};
|
|
||||||
systemd.services.${name}.serviceConfig = {
|
|
||||||
# Setup permissions needed for backups, as the backup user is member of the jellyfin group.
|
|
||||||
UMask = lib.mkForce "0027";
|
|
||||||
StateDirectoryMode = lib.mkForce "0750";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
appOption = name: c: lib.nameValuePair name (lib.mkOption {
|
appOption = name: c: lib.nameValuePair name (lib.mkOption {
|
||||||
description = "Configuration for ${name}";
|
description = "Configuration for ${name}";
|
||||||
default = {};
|
default = {};
|
||||||
|
|
@ -347,6 +333,7 @@ let
|
||||||
'';
|
'';
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
default = {
|
default = {
|
||||||
|
user = name;
|
||||||
sourceDirectories = [
|
sourceDirectories = [
|
||||||
cfg.${name}.dataDir
|
cfg.${name}.dataDir
|
||||||
];
|
];
|
||||||
|
|
@ -386,7 +373,6 @@ in
|
||||||
|
|
||||||
shb.nginx.vhosts = [ (vhosts {} cfg') ];
|
shb.nginx.vhosts = [ (vhosts {} cfg') ];
|
||||||
}))
|
}))
|
||||||
(lib.mkIf cfg.radarr.enable (backup "radarr"))
|
|
||||||
|
|
||||||
(lib.mkIf cfg.sonarr.enable (
|
(lib.mkIf cfg.sonarr.enable (
|
||||||
let
|
let
|
||||||
|
|
@ -416,7 +402,6 @@ in
|
||||||
|
|
||||||
shb.nginx.vhosts = [ (vhosts {} cfg') ];
|
shb.nginx.vhosts = [ (vhosts {} cfg') ];
|
||||||
}))
|
}))
|
||||||
(lib.mkIf cfg.sonarr.enable (backup "sonarr"))
|
|
||||||
|
|
||||||
(lib.mkIf cfg.bazarr.enable (
|
(lib.mkIf cfg.bazarr.enable (
|
||||||
let
|
let
|
||||||
|
|
@ -443,7 +428,6 @@ in
|
||||||
|
|
||||||
shb.nginx.vhosts = [ (vhosts {} cfg') ];
|
shb.nginx.vhosts = [ (vhosts {} cfg') ];
|
||||||
}))
|
}))
|
||||||
(lib.mkIf cfg.bazarr.enable (backup "bazarr"))
|
|
||||||
|
|
||||||
(lib.mkIf cfg.readarr.enable (
|
(lib.mkIf cfg.readarr.enable (
|
||||||
let
|
let
|
||||||
|
|
@ -465,7 +449,6 @@ in
|
||||||
|
|
||||||
shb.nginx.vhosts = [ (vhosts {} cfg') ];
|
shb.nginx.vhosts = [ (vhosts {} cfg') ];
|
||||||
}))
|
}))
|
||||||
(lib.mkIf cfg.readarr.enable (backup "readarr"))
|
|
||||||
|
|
||||||
(lib.mkIf cfg.lidarr.enable (
|
(lib.mkIf cfg.lidarr.enable (
|
||||||
let
|
let
|
||||||
|
|
@ -492,7 +475,6 @@ in
|
||||||
|
|
||||||
shb.nginx.vhosts = [ (vhosts {} cfg') ];
|
shb.nginx.vhosts = [ (vhosts {} cfg') ];
|
||||||
}))
|
}))
|
||||||
(lib.mkIf cfg.lidarr.enable (backup "lidarr"))
|
|
||||||
|
|
||||||
(lib.mkIf cfg.jackett.enable (
|
(lib.mkIf cfg.jackett.enable (
|
||||||
let
|
let
|
||||||
|
|
@ -503,6 +485,7 @@ in
|
||||||
enable = true;
|
enable = true;
|
||||||
dataDir = "/var/lib/jackett";
|
dataDir = "/var/lib/jackett";
|
||||||
};
|
};
|
||||||
|
# TODO: avoid implicitly relying on the media group
|
||||||
users.users.jackett = {
|
users.users.jackett = {
|
||||||
extraGroups = [ "media" ];
|
extraGroups = [ "media" ];
|
||||||
};
|
};
|
||||||
|
|
@ -516,6 +499,5 @@ in
|
||||||
extraBypassResources = [ "^/dl.*" ];
|
extraBypassResources = [ "^/dl.*" ];
|
||||||
} cfg') ];
|
} cfg') ];
|
||||||
}))
|
}))
|
||||||
(lib.mkIf cfg.jackett.enable (backup "jackett"))
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,7 @@ in
|
||||||
'';
|
'';
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
default = {
|
default = {
|
||||||
|
user = "audiobookshelf";
|
||||||
sourceDirectories = [
|
sourceDirectories = [
|
||||||
"/var/lib/audiobookshelf"
|
"/var/lib/audiobookshelf"
|
||||||
];
|
];
|
||||||
|
|
@ -162,17 +163,6 @@ in
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
# We want audiobookshelf to create files in the media group and to make those files group readable.
|
|
||||||
users.users.audiobookshelf = {
|
|
||||||
extraGroups = [ "media" ];
|
|
||||||
};
|
|
||||||
systemd.services.audiobookshelfd.serviceConfig.Group = lib.mkForce "media";
|
|
||||||
systemd.services.audiobookshelfd.serviceConfig.UMask = lib.mkForce "0027";
|
|
||||||
|
|
||||||
# We backup the whole audiobookshelf directory and set permissions for the backup user accordingly.
|
|
||||||
users.groups.audiobookshelf.members = [ "backup" ];
|
|
||||||
users.groups.media.members = [ "backup" ];
|
|
||||||
} {
|
} {
|
||||||
systemd.services.audiobookshelfd.serviceConfig = cfg.extraServiceConfig;
|
systemd.services.audiobookshelfd.serviceConfig = cfg.extraServiceConfig;
|
||||||
}]);
|
}]);
|
||||||
|
|
|
||||||
|
|
@ -251,6 +251,7 @@ in
|
||||||
'';
|
'';
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
default = {
|
default = {
|
||||||
|
user = "deluge";
|
||||||
sourceDirectories = [
|
sourceDirectories = [
|
||||||
cfg.dataDir
|
cfg.dataDir
|
||||||
];
|
];
|
||||||
|
|
@ -373,17 +374,6 @@ in
|
||||||
inherit (cfg) authEndpoint;
|
inherit (cfg) authEndpoint;
|
||||||
}))
|
}))
|
||||||
];
|
];
|
||||||
|
|
||||||
# We want deluge to create files in the media group and to make those files group readable.
|
|
||||||
users.users.deluge = {
|
|
||||||
extraGroups = [ "media" ];
|
|
||||||
};
|
|
||||||
systemd.services.deluged.serviceConfig.Group = lib.mkForce "media";
|
|
||||||
systemd.services.deluged.serviceConfig.UMask = lib.mkForce "0027";
|
|
||||||
|
|
||||||
# We backup the whole deluge directory and set permissions for the backup user accordingly.
|
|
||||||
users.groups.deluge.members = [ "backup" ];
|
|
||||||
users.groups.media.members = [ "backup" ];
|
|
||||||
} {
|
} {
|
||||||
systemd.services.deluged.serviceConfig = cfg.extraServiceConfig;
|
systemd.services.deluged.serviceConfig = cfg.extraServiceConfig;
|
||||||
} (lib.mkIf (config.shb.deluge.prometheusScraperPasswordFile != null) {
|
} (lib.mkIf (config.shb.deluge.prometheusScraperPasswordFile != null) {
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ in
|
||||||
'';
|
'';
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
default = {
|
default = {
|
||||||
|
user = "grocy";
|
||||||
sourceDirectories = [
|
sourceDirectories = [
|
||||||
cfg.dataDir
|
cfg.dataDir
|
||||||
];
|
];
|
||||||
|
|
@ -115,10 +116,6 @@ in
|
||||||
sslCertificate = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.cert;
|
sslCertificate = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.cert;
|
||||||
sslCertificateKey = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.key;
|
sslCertificateKey = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.key;
|
||||||
};
|
};
|
||||||
|
|
||||||
# We backup the whole grocy directory and set permissions for the backup user accordingly.
|
|
||||||
users.groups.grocy.members = [ "backup" ];
|
|
||||||
users.groups.media.members = [ "backup" ];
|
|
||||||
} {
|
} {
|
||||||
systemd.services.grocyd.serviceConfig = cfg.extraServiceConfig;
|
systemd.services.grocyd.serviceConfig = cfg.extraServiceConfig;
|
||||||
}]);
|
}]);
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ in
|
||||||
'';
|
'';
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
default = {
|
default = {
|
||||||
|
user = "hledger";
|
||||||
sourceDirectories = [
|
sourceDirectories = [
|
||||||
cfg.dataDir
|
cfg.dataDir
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,7 @@ in
|
||||||
'';
|
'';
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
default = {
|
default = {
|
||||||
|
user = "hass";
|
||||||
# No need for backup hooks as we use an hourly automation job in home assistant directly with a cron job.
|
# No need for backup hooks as we use an hourly automation job in home assistant directly with a cron job.
|
||||||
sourceDirectories = [
|
sourceDirectories = [
|
||||||
"/var/lib/hass/backups"
|
"/var/lib/hass/backups"
|
||||||
|
|
@ -322,22 +323,5 @@ in
|
||||||
"f ${config.services.home-assistant.configDir}/scenes.yaml 0755 hass hass"
|
"f ${config.services.home-assistant.configDir}/scenes.yaml 0755 hass hass"
|
||||||
"f ${config.services.home-assistant.configDir}/scripts.yaml 0755 hass hass"
|
"f ${config.services.home-assistant.configDir}/scripts.yaml 0755 hass hass"
|
||||||
];
|
];
|
||||||
|
|
||||||
# Adds the "backup" user to the "hass" group.
|
|
||||||
users.groups.hass = {
|
|
||||||
members = [ "backup" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
# This allows the "backup" user, member of the "backup" group, to access what's inside the home
|
|
||||||
# folder, which is needed for accessing the "backups" folder. It allows to read (r), enter the
|
|
||||||
# directory (x) but not modify what's inside.
|
|
||||||
users.users.hass.homeMode = "0750";
|
|
||||||
|
|
||||||
systemd.services.home-assistant.serviceConfig = {
|
|
||||||
# This allows all members of the "hass" group to read files, list directories and enter
|
|
||||||
# directories created by the home-assistant service. This is needed for the "backup" user,
|
|
||||||
# member of the "hass" group, to backup what is inside the "backup/" folder.
|
|
||||||
UMask = lib.mkForce "0027";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,7 @@ in
|
||||||
'';
|
'';
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
default = {
|
default = {
|
||||||
|
user = "jellyfin";
|
||||||
sourceDirectories = [
|
sourceDirectories = [
|
||||||
"/var/lib/jellyfin"
|
"/var/lib/jellyfin"
|
||||||
];
|
];
|
||||||
|
|
@ -153,16 +154,6 @@ in
|
||||||
allowedUDPPorts = [ 1900 7359 ];
|
allowedUDPPorts = [ 1900 7359 ];
|
||||||
};
|
};
|
||||||
|
|
||||||
users.groups = {
|
|
||||||
media = {
|
|
||||||
name = "media";
|
|
||||||
members = [ "jellyfin" ];
|
|
||||||
};
|
|
||||||
jellyfin = {
|
|
||||||
members = [ "backup" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
services.nginx.enable = true;
|
services.nginx.enable = true;
|
||||||
|
|
||||||
# Take advice from https://jellyfin.org/docs/general/networking/nginx/ and https://nixos.wiki/wiki/Plex
|
# Take advice from https://jellyfin.org/docs/general/networking/nginx/ and https://nixos.wiki/wiki/Plex
|
||||||
|
|
@ -432,13 +423,5 @@ in
|
||||||
redirect_uris = [ "https://${cfg.subdomain}.${cfg.domain}/sso/OID/r/${cfg.sso.provider}" ];
|
redirect_uris = [ "https://${cfg.subdomain}.${cfg.domain}/sso/OID/r/${cfg.sso.provider}" ];
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
# For backup
|
|
||||||
|
|
||||||
systemd.services.jellyfin.serviceConfig = {
|
|
||||||
# Setup permissions needed for backups, as the backup user is member of the jellyfin group.
|
|
||||||
UMask = lib.mkForce "0027";
|
|
||||||
StateDirectoryMode = lib.mkForce "0750";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -515,6 +515,7 @@ in
|
||||||
'';
|
'';
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
default = {
|
default = {
|
||||||
|
user = "nextcloud";
|
||||||
sourceDirectories = [
|
sourceDirectories = [
|
||||||
cfg.dataDir
|
cfg.dataDir
|
||||||
];
|
];
|
||||||
|
|
@ -568,12 +569,6 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# users.groups = {
|
|
||||||
# nextcloud = {
|
|
||||||
# members = [ "backup" ];
|
|
||||||
# };
|
|
||||||
# };
|
|
||||||
|
|
||||||
# LDAP is manually configured through
|
# LDAP is manually configured through
|
||||||
# https://github.com/lldap/lldap/blob/main/example_configs/nextcloud.md, see also
|
# https://github.com/lldap/lldap/blob/main/example_configs/nextcloud.md, see also
|
||||||
# https://docs.nextcloud.com/server/latest/admin_manual/configuration_user/user_auth_ldap.html
|
# https://docs.nextcloud.com/server/latest/admin_manual/configuration_user/user_auth_ldap.html
|
||||||
|
|
@ -708,10 +703,6 @@ in
|
||||||
|
|
||||||
services.postgresql.settings = lib.mkIf (! (isNull cfg.postgresSettings)) cfg.postgresSettings;
|
services.postgresql.settings = lib.mkIf (! (isNull cfg.postgresSettings)) cfg.postgresSettings;
|
||||||
|
|
||||||
systemd.services.phpfpm-nextcloud.serviceConfig = {
|
|
||||||
# Setup permissions needed for backups, as the backup user is member of the jellyfin group.
|
|
||||||
UMask = lib.mkForce "0027";
|
|
||||||
};
|
|
||||||
systemd.services.phpfpm-nextcloud.preStart = ''
|
systemd.services.phpfpm-nextcloud.preStart = ''
|
||||||
mkdir -p /var/log/xdebug; chown -R nextcloud: /var/log/xdebug
|
mkdir -p /var/log/xdebug; chown -R nextcloud: /var/log/xdebug
|
||||||
'';
|
'';
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,7 @@ in
|
||||||
'';
|
'';
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
default = {
|
default = {
|
||||||
|
user = "vaultwarden";
|
||||||
sourceDirectories = [
|
sourceDirectories = [
|
||||||
dataFolder
|
dataFolder
|
||||||
];
|
];
|
||||||
|
|
@ -224,17 +225,6 @@ in
|
||||||
passwordFile = builtins.toString cfg.databasePasswordFile;
|
passwordFile = builtins.toString cfg.databasePasswordFile;
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
systemd.services.vaultwarden.serviceConfig.UMask = lib.mkForce "0027";
|
|
||||||
# systemd.services.vaultwarden.serviceConfig.Group = lib.mkForce "media";
|
|
||||||
users.users.vaultwarden = {
|
|
||||||
extraGroups = [ "media" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
users.groups.vaultwarden = {
|
|
||||||
members = [ "backup" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
# TODO: make this work.
|
# TODO: make this work.
|
||||||
# It does not work because it leads to infinite recursion.
|
# It does not work because it leads to infinite recursion.
|
||||||
# ${cfg.mount}.path = dataFolder;
|
# ${cfg.mount}.path = dataFolder;
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,72 @@ let
|
||||||
../../modules/blocks/restic.nix
|
../../modules/blocks/restic.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
commonTestScript = ''
|
commonTest = user: pkgs.testers.runNixOSTest {
|
||||||
|
name = "restic_backupAndRestore_${user}";
|
||||||
|
|
||||||
|
nodes.machine = {
|
||||||
|
imports = ( testLib.baseImports pkgs' ) ++ [
|
||||||
|
../../modules/blocks/restic.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
shb.restic.instances."testinstance" = {
|
||||||
|
enable = true;
|
||||||
|
|
||||||
|
passphraseFile = pkgs.writeText "passphrase" "PassPhrase";
|
||||||
|
|
||||||
|
sourceDirectories = [
|
||||||
|
"/opt/files/A"
|
||||||
|
"/opt/files/B"
|
||||||
|
];
|
||||||
|
|
||||||
|
user = user;
|
||||||
|
|
||||||
|
repositories = [
|
||||||
|
{
|
||||||
|
path = "/opt/repos/A";
|
||||||
|
timerConfig = {
|
||||||
|
OnCalendar = "00:00:00";
|
||||||
|
RandomizedDelaySec = "5h";
|
||||||
|
};
|
||||||
|
# Those are not needed by the repository but are still included
|
||||||
|
# so we can test them in the hooks section.
|
||||||
|
secrets = {
|
||||||
|
A.source = "/run/secrets/A";
|
||||||
|
B.source = "/run/secrets/B";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
path = "/opt/repos/B";
|
||||||
|
timerConfig = {
|
||||||
|
OnCalendar = "00:00:00";
|
||||||
|
RandomizedDelaySec = "5h";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
hooks.before_backup = [''
|
||||||
|
echo $RUNTIME_DIRECTORY
|
||||||
|
if [ "$RUNTIME_DIRECTORY" = /run/restic-backups-testinstance_opt_repos_A ]; then
|
||||||
|
if ! [ -f /run/secrets_restic/restic-backups-testinstance_opt_repos_A ]; then
|
||||||
|
exit 10
|
||||||
|
fi
|
||||||
|
if [ -z "$A" ] || ! [ "$A" = "secretA" ]; then
|
||||||
|
echo "A:$A"
|
||||||
|
exit 11
|
||||||
|
fi
|
||||||
|
if [ -z "$B" ] || ! [ "$B" = "secretB" ]; then
|
||||||
|
echo "B:$B"
|
||||||
|
exit 12
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
''];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
extraPythonPackages = p: [ p.dictdiffer ];
|
||||||
|
skipTypeCheck = true;
|
||||||
|
|
||||||
|
testScript = ''
|
||||||
from dictdiffer import diff
|
from dictdiffer import diff
|
||||||
|
|
||||||
def list_files(dir):
|
def list_files(dir):
|
||||||
|
|
@ -32,6 +97,19 @@ let
|
||||||
if len(result) > 0:
|
if len(result) > 0:
|
||||||
raise Exception("Unexpected files:", result)
|
raise Exception("Unexpected files:", result)
|
||||||
|
|
||||||
|
with subtest("Create secrets"):
|
||||||
|
print(machine.succeed("""
|
||||||
|
mkdir -p /run/secrets/
|
||||||
|
|
||||||
|
echo secretA > /run/secrets/A
|
||||||
|
echo secretB > /run/secrets/B
|
||||||
|
|
||||||
|
chown root:keys -R /run/secrets
|
||||||
|
find /run/secrets -type d -exec chmod u=rwx,g=rx,o=x '{}' ';'
|
||||||
|
find /run/secrets -type f -exec chmod u=r,g=r,o= '{}' ';'
|
||||||
|
ls -l /run/secrets
|
||||||
|
"""))
|
||||||
|
|
||||||
with subtest("Create initial content"):
|
with subtest("Create initial content"):
|
||||||
machine.succeed("""
|
machine.succeed("""
|
||||||
mkdir -p /opt/files/A
|
mkdir -p /opt/files/A
|
||||||
|
|
@ -42,7 +120,8 @@ let
|
||||||
echo repoB_fileA_1 > /opt/files/B/fileA
|
echo repoB_fileA_1 > /opt/files/B/fileA
|
||||||
echo repoB_fileB_1 > /opt/files/B/fileB
|
echo repoB_fileB_1 > /opt/files/B/fileB
|
||||||
|
|
||||||
# chown :backup -R /opt/files
|
chown ${user}: -R /opt/files
|
||||||
|
chmod go-rwx -R /opt/files
|
||||||
""")
|
""")
|
||||||
|
|
||||||
assert_files("/opt/files", {
|
assert_files("/opt/files", {
|
||||||
|
|
@ -104,145 +183,10 @@ let
|
||||||
'/opt/files/A/fileB': 'repoA_fileB_2',
|
'/opt/files/A/fileB': 'repoA_fileB_2',
|
||||||
})
|
})
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
backupAndRestoreRoot = pkgs.testers.runNixOSTest {
|
backupAndRestoreRoot = commonTest "root";
|
||||||
name = "restic_backupAndRestore";
|
backupAndRestoreUser = commonTest "nobody";
|
||||||
|
|
||||||
nodes.machine = {
|
|
||||||
imports = ( testLib.baseImports pkgs' ) ++ [
|
|
||||||
../../modules/blocks/restic.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
shb.restic = {
|
|
||||||
user = "root";
|
|
||||||
group = "root";
|
|
||||||
};
|
|
||||||
shb.restic.instances."testinstance" = {
|
|
||||||
enable = true;
|
|
||||||
|
|
||||||
passphraseFile = pkgs.writeText "passphrase" "PassPhrase";
|
|
||||||
|
|
||||||
sourceDirectories = [
|
|
||||||
"/opt/files/A"
|
|
||||||
"/opt/files/B"
|
|
||||||
];
|
|
||||||
|
|
||||||
repositories = [
|
|
||||||
{
|
|
||||||
path = "/opt/repos/A";
|
|
||||||
timerConfig = {
|
|
||||||
OnCalendar = "00:00:00";
|
|
||||||
RandomizedDelaySec = "5h";
|
|
||||||
};
|
|
||||||
# Those are not needed by the repository but are still included
|
|
||||||
# so we can test them in the hooks section.
|
|
||||||
secrets = {
|
|
||||||
A.source = pkgs.writeText "A" "secretA";
|
|
||||||
B.source = pkgs.writeText "B" "secretB";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
{
|
|
||||||
path = "/opt/repos/B";
|
|
||||||
timerConfig = {
|
|
||||||
OnCalendar = "00:00:00";
|
|
||||||
RandomizedDelaySec = "5h";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
hooks.before_backup = [''
|
|
||||||
echo $RUNTIME_DIRECTORY
|
|
||||||
if [ "$RUNTIME_DIRECTORY" = /run/restic-backups-testinstance_opt_repos_A ]; then
|
|
||||||
if ! [ -f /run/secrets/restic/restic-backups-testinstance_opt_repos_A ]; then
|
|
||||||
exit 10
|
|
||||||
fi
|
|
||||||
if [ -z "$A" ] || ! [ "$A" = "secretA" ]; then
|
|
||||||
echo "A:$A"
|
|
||||||
exit 11
|
|
||||||
fi
|
|
||||||
if [ -z "$B" ] || ! [ "$B" = "secretB" ]; then
|
|
||||||
echo "A:$A"
|
|
||||||
exit 12
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
''];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
extraPythonPackages = p: [ p.dictdiffer ];
|
|
||||||
skipTypeCheck = true;
|
|
||||||
|
|
||||||
testScript = commonTestScript;
|
|
||||||
};
|
|
||||||
|
|
||||||
backupAndRestoreUser = pkgs.testers.runNixOSTest {
|
|
||||||
name = "restic_backupAndRestore";
|
|
||||||
|
|
||||||
nodes.machine = {
|
|
||||||
imports = ( testLib.baseImports pkgs' ) ++ [
|
|
||||||
../../modules/blocks/restic.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
shb.restic = {
|
|
||||||
user = "backup";
|
|
||||||
group = "backup";
|
|
||||||
};
|
|
||||||
shb.restic.instances."testinstance" = {
|
|
||||||
enable = true;
|
|
||||||
|
|
||||||
passphraseFile = pkgs.writeText "passphrase" "PassPhrase";
|
|
||||||
|
|
||||||
sourceDirectories = [
|
|
||||||
"/opt/files/A"
|
|
||||||
"/opt/files/B"
|
|
||||||
];
|
|
||||||
|
|
||||||
repositories = [
|
|
||||||
{
|
|
||||||
path = "/opt/repos/A";
|
|
||||||
timerConfig = {
|
|
||||||
OnCalendar = "00:00:00";
|
|
||||||
RandomizedDelaySec = "5h";
|
|
||||||
};
|
|
||||||
# Those are not needed by the repository but are still included
|
|
||||||
# so we can test them in the hooks section.
|
|
||||||
secrets = {
|
|
||||||
A.source = pkgs.writeText "A" "secretA";
|
|
||||||
B.source = pkgs.writeText "B" "secretB";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
{
|
|
||||||
path = "/opt/repos/B";
|
|
||||||
timerConfig = {
|
|
||||||
OnCalendar = "00:00:00";
|
|
||||||
RandomizedDelaySec = "5h";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
hooks.before_backup = [''
|
|
||||||
echo $RUNTIME_DIRECTORY
|
|
||||||
if [ "$RUNTIME_DIRECTORY" = /run/restic-backups-testinstance_opt_repos_A ]; then
|
|
||||||
if ! [ -f /run/secrets/restic/restic-backups-testinstance_opt_repos_A ]; then
|
|
||||||
exit 10
|
|
||||||
fi
|
|
||||||
if [ -z "$A" ] || ! [ "$A" = "secretA" ]; then
|
|
||||||
echo "A:$A"
|
|
||||||
exit 11
|
|
||||||
fi
|
|
||||||
if [ -z "$B" ] || ! [ "$B" = "secretB" ]; then
|
|
||||||
echo "A:$A"
|
|
||||||
exit 12
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
''];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
extraPythonPackages = p: [ p.dictdiffer ];
|
|
||||||
skipTypeCheck = true;
|
|
||||||
|
|
||||||
testScript = commonTestScript;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,6 @@ in
|
||||||
ssl = null;
|
ssl = null;
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
users.groups.radarr.members = [ "backup" ];
|
|
||||||
services.nginx.enable = true;
|
services.nginx.enable = true;
|
||||||
services.bazarr = {};
|
services.bazarr = {};
|
||||||
services.jackett = {};
|
services.jackett = {};
|
||||||
|
|
@ -156,7 +155,6 @@ in
|
||||||
ssl = null;
|
ssl = null;
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
users.groups.radarr.members = [ "backup" ];
|
|
||||||
services.nginx.enable = true;
|
services.nginx.enable = true;
|
||||||
services.bazarr = {};
|
services.bazarr = {};
|
||||||
services.jackett = {};
|
services.jackett = {};
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue