add backup contract
This commit is contained in:
parent
597853655d
commit
6aed5ee6a5
16 changed files with 685 additions and 233 deletions
|
@ -34,8 +34,8 @@ Not all blocks are yet documented. You can find all available blocks [in the rep
|
|||
modules/blocks/ssl/docs/default.md
|
||||
```
|
||||
|
||||
```{=include=} chapters html:into-file=//blocks-backup.html
|
||||
modules/blocks/backup/docs/default.md
|
||||
```{=include=} chapters html:into-file=//blocks-restic.html
|
||||
modules/blocks/restic/docs/default.md
|
||||
```
|
||||
|
||||
```{=include=} chapters html:into-file=//blocks-monitoring.html
|
||||
|
|
|
@ -19,12 +19,19 @@ as possible, reducing the quite thick layer that it is now.
|
|||
|
||||
Provided contracts are:
|
||||
|
||||
- [SSL generator contract](contracts-ssl.html) to generate SSL certificates. Two implementations are provided: self-signed and Let's Encrypt.
|
||||
- [SSL generator contract](contracts-ssl.html) to generate SSL certificates.
|
||||
Two implementations are provided: self-signed and Let's Encrypt.
|
||||
- [Backup contract](contracts-backup.html) to backup directories.
|
||||
This contract allows to backup multiple times the same directories for extra protection.
|
||||
|
||||
```{=include=} chapters html:into-file=//contracts-ssl.html
|
||||
modules/contracts/ssl/docs/default.md
|
||||
```
|
||||
|
||||
```{=include=} chapters html:into-file=//contracts-backup.html
|
||||
modules/contracts/backup/docs/default.md
|
||||
```
|
||||
|
||||
## Why do we need this new concept? {#contracts-why}
|
||||
|
||||
Currently in nixpkgs, every module needing access to a shared resource must implement the logic
|
||||
|
|
|
@ -67,7 +67,9 @@ let
|
|||
};
|
||||
|
||||
optionsDocs = buildOptionsDocs {
|
||||
modules = allModules ++ [ scrubbedModule ];
|
||||
modules = allModules ++ [
|
||||
scrubbedModule
|
||||
];
|
||||
variablelistId = "selfhostblocks-options";
|
||||
includeModuleSystemOptions = false;
|
||||
};
|
||||
|
@ -134,10 +136,10 @@ in stdenv.mkDerivation {
|
|||
'@OPTIONS_JSON@' \
|
||||
${individualModuleOptionsDocs ../modules/blocks/ssl.nix}/share/doc/nixos/options.json
|
||||
|
||||
substituteInPlace ./modules/blocks/backup/docs/default.md \
|
||||
substituteInPlace ./modules/blocks/restic/docs/default.md \
|
||||
--replace \
|
||||
'@OPTIONS_JSON@' \
|
||||
${individualModuleOptionsDocs ../modules/blocks/backup.nix}/share/doc/nixos/options.json
|
||||
${individualModuleOptionsDocs ../modules/blocks/restic.nix}/share/doc/nixos/options.json
|
||||
|
||||
substituteInPlace ./modules/services/nextcloud-server/docs/default.md \
|
||||
--replace \
|
||||
|
@ -149,6 +151,11 @@ in stdenv.mkDerivation {
|
|||
'@OPTIONS_JSON@' \
|
||||
${individualModuleOptionsDocs ../modules/services/vaultwarden.nix}/share/doc/nixos/options.json
|
||||
|
||||
substituteInPlace ./modules/contracts/backup/docs/default.md \
|
||||
--replace \
|
||||
'@OPTIONS_JSON@' \
|
||||
${individualModuleOptionsDocs ../modules/contracts/backup/dummyModule.nix}/share/doc/nixos/options.json
|
||||
|
||||
substituteInPlace ./modules/contracts/ssl/docs/default.md \
|
||||
--replace \
|
||||
'@OPTIONS_JSON@' \
|
||||
|
|
54
flake.lock
54
flake.lock
|
@ -49,38 +49,6 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1716655032,
|
||||
"narHash": "sha256-kQ25DAiCGigsNR/Quxm3v+JGXAEXZ8I7RAF4U94bGzE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "59a450646ec8ee0397f5fa54a08573e8240eb91f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "release-23.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1716651315,
|
||||
"narHash": "sha256-iMgzIeedMqf30TXZ439zW3Yvng1Xm9QTGO+ZwG1IWSw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c5187508b11177ef4278edf19616f44f21cc8c69",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nmdsrc": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
|
@ -102,27 +70,7 @@
|
|||
"flake-utils": "flake-utils",
|
||||
"nix-flake-tests": "nix-flake-tests",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nmdsrc": "nmdsrc",
|
||||
"sops-nix": "sops-nix"
|
||||
}
|
||||
},
|
||||
"sops-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1716692524,
|
||||
"narHash": "sha256-sALodaA7Zkp/JD6ehgwc0UCBrSBfB4cX66uFGTsqeFU=",
|
||||
"owner": "Mic92",
|
||||
"repo": "sops-nix",
|
||||
"rev": "962797a8d7f15ed7033031731d0bb77244839960",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "Mic92",
|
||||
"repo": "sops-nix",
|
||||
"type": "github"
|
||||
"nmdsrc": "nmdsrc"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
|
|
|
@ -36,12 +36,12 @@
|
|||
|
||||
allModules = [
|
||||
modules/blocks/authelia.nix
|
||||
modules/blocks/backup.nix
|
||||
modules/blocks/davfs.nix
|
||||
modules/blocks/ldap.nix
|
||||
modules/blocks/monitoring.nix
|
||||
modules/blocks/nginx.nix
|
||||
modules/blocks/postgresql.nix
|
||||
modules/blocks/restic.nix
|
||||
modules/blocks/ssl.nix
|
||||
modules/blocks/tinyproxy.nix
|
||||
modules/blocks/vpn.nix
|
||||
|
@ -60,6 +60,7 @@
|
|||
|
||||
# Only used for documentation.
|
||||
contractDummyModules = [
|
||||
modules/contracts/backup/dummyModule.nix
|
||||
modules/contracts/ssl/dummyModule.nix
|
||||
];
|
||||
in
|
||||
|
@ -133,6 +134,7 @@
|
|||
// (vm_test "ldap" ./test/blocks/ldap.nix)
|
||||
// (vm_test "lib" ./test/blocks/lib.nix)
|
||||
// (vm_test "postgresql" ./test/blocks/postgresql.nix)
|
||||
// (vm_test "restic" ./test/blocks/restic.nix)
|
||||
// (vm_test "ssl" ./test/blocks/ssl.nix)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,30 +1,32 @@
|
|||
{ config, pkgs, lib, utils, ... }:
|
||||
|
||||
let
|
||||
cfg = config.shb.backup;
|
||||
cfg = config.shb.borgbackup;
|
||||
|
||||
instanceOptions = {
|
||||
enable = lib.mkEnableOption "shb backup instance";
|
||||
|
||||
backend = lib.mkOption {
|
||||
description = "What program to use to make the backups.";
|
||||
type = lib.types.enum [ "borgmatic" "restic" ];
|
||||
example = "borgmatic";
|
||||
};
|
||||
enable = lib.mkEnableOption "shb borgbackup";
|
||||
|
||||
keySopsFile = lib.mkOption {
|
||||
description = "Sops file that holds this instance's Borgmatic repository key and passphrase.";
|
||||
description = "Sops file that holds this instance's repository key and passphrase.";
|
||||
type = lib.types.path;
|
||||
example = "secrets/backup.yaml";
|
||||
};
|
||||
|
||||
encryptionKeyFile = lib.mkOption {
|
||||
description = "Encryption key for the backup.";
|
||||
type = lib.types.path;
|
||||
};
|
||||
|
||||
encryption_passcommand = "cat /run/secrets/borgmatic/passphrases/${if isNull instance.secretName then name else instance.secretName}";
|
||||
borg_keys_directory = "/run/secrets/borgmatic/keys";
|
||||
|
||||
sourceDirectories = lib.mkOption {
|
||||
description = "Borgmatic source directories.";
|
||||
description = "Source directories.";
|
||||
type = lib.types.nonEmptyListOf lib.types.str;
|
||||
};
|
||||
|
||||
excludePatterns = lib.mkOption {
|
||||
description = "Borgmatic exclude patterns.";
|
||||
description = "Exclude patterns.";
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
};
|
||||
|
@ -74,7 +76,7 @@ let
|
|||
};
|
||||
|
||||
consistency = lib.mkOption {
|
||||
description = "Consistency frequency options. Only applicable for borgmatic";
|
||||
description = "Consistency frequency options.";
|
||||
type = lib.types.attrsOf lib.types.nonEmptyStr;
|
||||
default = {};
|
||||
example = {
|
||||
|
@ -84,7 +86,7 @@ let
|
|||
};
|
||||
|
||||
hooks = lib.mkOption {
|
||||
description = "Borgmatic hooks.";
|
||||
description = "Hooks to run before or after the backup.";
|
||||
default = {};
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
|
@ -115,14 +117,7 @@ let
|
|||
|
||||
in
|
||||
{
|
||||
options.shb.backup = {
|
||||
onlyOnAC = lib.mkOption {
|
||||
description = "Run backups only if AC power is plugged in.";
|
||||
default = true;
|
||||
example = false;
|
||||
type = lib.types.bool;
|
||||
};
|
||||
|
||||
options.shb.borgbackup = {
|
||||
user = lib.mkOption {
|
||||
description = "Unix user doing the backups.";
|
||||
type = lib.types.str;
|
||||
|
@ -163,12 +158,12 @@ in
|
|||
};
|
||||
ioSchedulingClass = lib.mkOption {
|
||||
type = lib.types.enum [ "idle" "best-effort" "realtime" ];
|
||||
description = "ionice scheduling class, defaults to best-effort IO. Only used for `restic backup`, `restic forget` and `restic check` commands.";
|
||||
description = "ionice scheduling class, defaults to best-effort IO.";
|
||||
default = "best-effort";
|
||||
};
|
||||
ioPriority = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.ints.between 0 7);
|
||||
description = "ionice priority, defaults to 7 for lowest priority IO. Only used for `restic backup`, `restic forget` and `restic check` commands.";
|
||||
description = "ionice priority, defaults to 7 for lowest priority IO.";
|
||||
default = 7;
|
||||
};
|
||||
};
|
||||
|
@ -179,8 +174,6 @@ in
|
|||
config = lib.mkIf (cfg.instances != {}) (
|
||||
let
|
||||
enabledInstances = lib.attrsets.filterAttrs (k: i: i.enable) cfg.instances;
|
||||
borgmaticInstances = lib.attrsets.filterAttrs (k: i: i.backend == "borgmatic") enabledInstances;
|
||||
resticInstances = lib.attrsets.filterAttrs (k: i: i.backend == "restic") enabledInstances;
|
||||
in lib.mkMerge [
|
||||
# Secrets configuration
|
||||
{
|
||||
|
@ -234,13 +227,13 @@ in
|
|||
}
|
||||
# Borgmatic configuration
|
||||
{
|
||||
systemd.timers.borgmatic = lib.mkIf (borgmaticInstances != {}) {
|
||||
systemd.timers.borgmatic = lib.mkIf (enabledInstances != {}) {
|
||||
timerConfig = {
|
||||
OnCalendar = "hourly";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.borgmatic = lib.mkIf (borgmaticInstances != {}) {
|
||||
systemd.services.borgmatic = lib.mkIf (enabledInstances != {}) {
|
||||
serviceConfig = {
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
|
@ -252,10 +245,10 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
systemd.packages = lib.mkIf (borgmaticInstances != {}) [ pkgs.borgmatic ];
|
||||
systemd.packages = lib.mkIf (enabledInstances != {}) [ pkgs.borgmatic ];
|
||||
environment.systemPackages = (
|
||||
lib.optionals cfg.borgServer [ pkgs.borgbackup ]
|
||||
++ lib.optionals (borgmaticInstances != {}) [ pkgs.borgbackup pkgs.borgmatic ]
|
||||
++ lib.optionals (enabledInstances != {}) [ pkgs.borgbackup pkgs.borgmatic ]
|
||||
);
|
||||
|
||||
environment.etc =
|
||||
|
@ -272,7 +265,7 @@ in
|
|||
});
|
||||
|
||||
storage = {
|
||||
encryption_passcommand = "cat /run/secrets/borgmatic/passphrases/${if isNull instance.secretName then name else instance.secretName}";
|
||||
encryption_passcommand = "cat ${instance.encryptionKeyFile}";
|
||||
borg_keys_directory = "/run/secrets/borgmatic/keys";
|
||||
};
|
||||
|
||||
|
@ -296,57 +289,7 @@ in
|
|||
};
|
||||
};
|
||||
in
|
||||
lib.mkMerge (lib.attrsets.mapAttrsToList mkSettings borgmaticInstances);
|
||||
}
|
||||
# Restic configuration
|
||||
{
|
||||
environment.systemPackages = lib.optionals (resticInstances != {}) [ pkgs.restic ];
|
||||
|
||||
services.restic.backups =
|
||||
let
|
||||
mkRepositorySettings = name: instance: repository: {
|
||||
"${name}_${repoSlugName repository.path}" = {
|
||||
inherit (cfg) user;
|
||||
repository = repository.path;
|
||||
|
||||
paths = instance.sourceDirectories;
|
||||
|
||||
passwordFile = "/run/secrets/${instance.backend}/passphrases/${name}";
|
||||
|
||||
initialize = true;
|
||||
|
||||
inherit (repository) timerConfig;
|
||||
|
||||
pruneOpts = lib.mapAttrsToList (name: value:
|
||||
"--${builtins.replaceStrings ["_"] ["-"] name} ${builtins.toString value}"
|
||||
) instance.retention;
|
||||
|
||||
backupPrepareCommand = lib.strings.concatStringsSep "\n" instance.hooks.before_backup;
|
||||
|
||||
backupCleanupCommand = lib.strings.concatStringsSep "\n" instance.hooks.after_backup;
|
||||
} // lib.attrsets.optionalAttrs (instance.environmentFile) {
|
||||
environmentFile = "/run/secrets/${instance.backend}/environmentfiles/${name}";
|
||||
} // lib.attrsets.optionalAttrs (builtins.length instance.excludePatterns > 0) {
|
||||
exclude = instance.excludePatterns;
|
||||
};
|
||||
};
|
||||
|
||||
mkSettings = name: instance: builtins.map (mkRepositorySettings name instance) instance.repositories;
|
||||
in
|
||||
lib.mkMerge (lib.flatten (lib.attrsets.mapAttrsToList mkSettings resticInstances));
|
||||
|
||||
systemd.services =
|
||||
let
|
||||
mkRepositorySettings = name: instance: repository: {
|
||||
"restic-backups-${name}_${repoSlugName repository.path}".serviceConfig = {
|
||||
Nice = cfg.performance.niceness;
|
||||
IOSchedulingClass = cfg.performance.ioSchedulingClass;
|
||||
IOSchedulingPriority = cfg.performance.ioPriority;
|
||||
};
|
||||
};
|
||||
mkSettings = name: instance: builtins.map (mkRepositorySettings name instance) instance.repositories;
|
||||
in
|
||||
lib.mkMerge (lib.flatten (lib.attrsets.mapAttrsToList mkSettings resticInstances));
|
||||
lib.mkMerge (lib.attrsets.mapAttrsToList mkSettings enabledInstances);
|
||||
}
|
||||
]);
|
||||
}
|
238
modules/blocks/restic.nix
Normal file
238
modules/blocks/restic.nix
Normal file
|
@ -0,0 +1,238 @@
|
|||
{ config, pkgs, lib, utils, ... }:
|
||||
|
||||
let
|
||||
cfg = config.shb.restic;
|
||||
|
||||
shblib = pkgs.callPackage ../../lib {};
|
||||
|
||||
instanceOptions = {
|
||||
enable = lib.mkEnableOption "shb restic";
|
||||
|
||||
passphraseFile = lib.mkOption {
|
||||
description = "Encryption key for the backups.";
|
||||
type = lib.types.path;
|
||||
};
|
||||
|
||||
sourceDirectories = lib.mkOption {
|
||||
description = "Source directories.";
|
||||
type = lib.types.nonEmptyListOf lib.types.str;
|
||||
};
|
||||
|
||||
excludePatterns = lib.mkOption {
|
||||
description = "Exclude patterns.";
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
};
|
||||
|
||||
repositories = lib.mkOption {
|
||||
description = "Repositories to back this instance to.";
|
||||
type = lib.types.nonEmptyListOf (lib.types.submodule {
|
||||
options = {
|
||||
path = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Repository location";
|
||||
};
|
||||
|
||||
extraSecrets = lib.mkOption {
|
||||
type = lib.types.attrsOf shblib.secretFileType;
|
||||
default = {};
|
||||
description = ''
|
||||
Extra secrets needed to access the repository where the backups will be stored.
|
||||
|
||||
See [s3 config](https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html#amazon-s3) for an example.
|
||||
'';
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
AWS_ACCESS_KEY_ID = <path/to/secret>;
|
||||
AWS_SECRET_ACCESS_KEY = <path/to/secret>;
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
timerConfig = lib.mkOption {
|
||||
type = lib.types.attrsOf utils.systemdUtils.unitOptions.unitOption;
|
||||
default = {
|
||||
OnCalendar = "daily";
|
||||
Persistent = true;
|
||||
};
|
||||
description = ''When to run the backup. See {manpage}`systemd.timer(5)` for details.'';
|
||||
example = {
|
||||
OnCalendar = "00:05";
|
||||
RandomizedDelaySec = "5h";
|
||||
Persistent = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
retention = lib.mkOption {
|
||||
description = "For how long to keep backup files.";
|
||||
type = lib.types.attrsOf (lib.types.oneOf [ lib.types.int lib.types.nonEmptyStr ]);
|
||||
default = {
|
||||
keep_within = "1d";
|
||||
keep_hourly = 24;
|
||||
keep_daily = 7;
|
||||
keep_weekly = 4;
|
||||
keep_monthly = 6;
|
||||
};
|
||||
};
|
||||
|
||||
hooks = lib.mkOption {
|
||||
description = "Hooks to run before or after the backup.";
|
||||
default = {};
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
before_backup = lib.mkOption {
|
||||
description = "Hooks to run before backup";
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
};
|
||||
|
||||
after_backup = lib.mkOption {
|
||||
description = "Hooks to run after backup";
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
repoSlugName = name: builtins.replaceStrings ["/" ":"] ["_" "_"] (lib.strings.removePrefix "/" name);
|
||||
in
|
||||
{
|
||||
options.shb.restic = {
|
||||
user = lib.mkOption {
|
||||
description = "Unix user doing the backups.";
|
||||
type = lib.types.str;
|
||||
default = "backup";
|
||||
};
|
||||
|
||||
group = lib.mkOption {
|
||||
description = "Unix group doing the backups.";
|
||||
type = lib.types.str;
|
||||
default = "backup";
|
||||
};
|
||||
|
||||
instances = lib.mkOption {
|
||||
description = "Each instance is a backup setting";
|
||||
default = {};
|
||||
type = lib.types.attrsOf (lib.types.submodule {
|
||||
options = instanceOptions;
|
||||
});
|
||||
};
|
||||
|
||||
# Taken from https://github.com/HubbeKing/restic-kubernetes/blob/73bfbdb0ba76939a4c52173fa2dbd52070710008/README.md?plain=1#L23
|
||||
performance = lib.mkOption {
|
||||
description = "Reduce performance impact of backup jobs.";
|
||||
default = {};
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
niceness = lib.mkOption {
|
||||
type = lib.types.ints.between (-20) 19;
|
||||
description = "nice priority adjustment, defaults to 15 for ~20% CPU time of normal-priority process";
|
||||
default = 15;
|
||||
};
|
||||
ioSchedulingClass = lib.mkOption {
|
||||
type = lib.types.enum [ "idle" "best-effort" "realtime" ];
|
||||
description = "ionice scheduling class, defaults to best-effort IO. Only used for `restic backup`, `restic forget` and `restic check` commands.";
|
||||
default = "best-effort";
|
||||
};
|
||||
ioPriority = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.ints.between 0 7);
|
||||
description = "ionice priority, defaults to 7 for lowest priority IO. Only used for `restic backup`, `restic forget` and `restic check` commands.";
|
||||
default = 7;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (cfg.instances != {}) (
|
||||
let
|
||||
enabledInstances = lib.attrsets.filterAttrs (k: i: i.enable) cfg.instances;
|
||||
in lib.mkMerge [
|
||||
{
|
||||
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 ];
|
||||
|
||||
services.restic.backups =
|
||||
let
|
||||
mkRepositorySettings = name: instance: repository: {
|
||||
"${name}_${repoSlugName repository.path}" = {
|
||||
inherit (cfg) user;
|
||||
repository = repository.path;
|
||||
|
||||
paths = instance.sourceDirectories;
|
||||
|
||||
passwordFile = toString instance.passphraseFile;
|
||||
|
||||
initialize = true;
|
||||
|
||||
inherit (repository) timerConfig;
|
||||
|
||||
pruneOpts = lib.mapAttrsToList (name: value:
|
||||
"--${builtins.replaceStrings ["_"] ["-"] name} ${builtins.toString value}"
|
||||
) instance.retention;
|
||||
|
||||
backupPrepareCommand = lib.strings.concatStringsSep "\n" instance.hooks.before_backup;
|
||||
|
||||
backupCleanupCommand = lib.strings.concatStringsSep "\n" instance.hooks.after_backup;
|
||||
} // lib.attrsets.optionalAttrs (repository.extraSecrets != {}) {
|
||||
environmentFile = shblib.replaceSecrets {
|
||||
userConfig = repository.extraSecrets;
|
||||
resultPath = "/var/lib/backup/${name}";
|
||||
generator = name: v: pkgs.writeText "template" (lib.generators.toINIWithGlobalSection {} v);
|
||||
};
|
||||
} // lib.attrsets.optionalAttrs (builtins.length instance.excludePatterns > 0) {
|
||||
exclude = instance.excludePatterns;
|
||||
};
|
||||
};
|
||||
|
||||
mkSettings = name: instance: builtins.map (mkRepositorySettings name instance) instance.repositories;
|
||||
in
|
||||
lib.mkMerge (lib.flatten (lib.attrsets.mapAttrsToList mkSettings enabledInstances));
|
||||
|
||||
systemd.services =
|
||||
let
|
||||
mkRepositorySettings = name: instance: repository: {
|
||||
"restic-backups-${name}_${repoSlugName repository.path}".serviceConfig = {
|
||||
Nice = cfg.performance.niceness;
|
||||
IOSchedulingClass = cfg.performance.ioSchedulingClass;
|
||||
IOSchedulingPriority = cfg.performance.ioPriority;
|
||||
};
|
||||
};
|
||||
mkSettings = name: instance: builtins.map (mkRepositorySettings name instance) instance.repositories;
|
||||
in
|
||||
lib.mkMerge (lib.flatten (lib.attrsets.mapAttrsToList mkSettings enabledInstances));
|
||||
}
|
||||
{
|
||||
environment.systemPackages = let
|
||||
mkResticBinary = name: instance: repository: pkgs.writeShellScriptBin "restic-${name}_${repoSlugName repository.path}" ''
|
||||
export RESTIC_PASSWORD_FILE=${instance.passphraseFile}
|
||||
export RESTIC_REPOSITORY=${repository.path}
|
||||
${pkgs.restic}/bin/restic $@
|
||||
'';
|
||||
mkSettings = name: instance: builtins.map (mkResticBinary name instance) instance.repositories;
|
||||
in
|
||||
lib.flatten (lib.attrsets.mapAttrsToList mkSettings enabledInstances);
|
||||
}
|
||||
]);
|
||||
}
|
|
@ -1,33 +1,32 @@
|
|||
# Backup Block {#blocks-backup}
|
||||
# Restic Block {#blocks-restic}
|
||||
|
||||
Defined in [`/modules/blocks/backup.nix`](@REPO@/modules/blocks/backup.nix).
|
||||
Defined in [`/modules/blocks/restic.nix`](@REPO@/modules/blocks/restic.nix).
|
||||
|
||||
This block sets up backup jobs for Self Host Blocks.
|
||||
This block sets up a backup job using [Restic][restic].
|
||||
|
||||
## Features {#blocks-backup-features}
|
||||
Two implementations for this block are provided:
|
||||
- [Restic](https://restic.net/)
|
||||
- [Borgmatic](https://torsion.org/borgmatic/)
|
||||
[restic]: https://restic.net/
|
||||
|
||||
No integration tests are provided yet.
|
||||
## Contract {#blocks-restic-features}
|
||||
|
||||
This block implements the [backup](contracts-backup.html) contract.
|
||||
|
||||
Integration tests are defined in [`/test/blocks/restic.nix`](@REPO@/test/blocks/restic.nix).
|
||||
|
||||
## Usage {#blocks-backup-usage}
|
||||
|
||||
### One folder backed up to mounted hard drives {#blocks-backup-usage-one}
|
||||
|
||||
The following snippet shows how to configure backup of 1 folder using the Restic implementation to 1
|
||||
repository.
|
||||
The following snippet shows how to configure
|
||||
the backup of 1 folder to 1 repository.
|
||||
|
||||
Assumptions:
|
||||
- 1 hard drive pool is used for backup and is mounted on `/srv/pool1`.
|
||||
|
||||
```nix
|
||||
shb.backup.instances.myfolder = {
|
||||
shb.restic.instances.myfolder = {
|
||||
enable = true;
|
||||
|
||||
backend = "restic";
|
||||
|
||||
keySopsFile = ./secrets.yaml;
|
||||
passphraseFile = "<path/to/passphrase>";
|
||||
|
||||
repositories = [{
|
||||
path = "/srv/pool1/backups/myfolder";
|
||||
|
@ -56,36 +55,14 @@ shb.backup.instances.myfolder = {
|
|||
};
|
||||
```
|
||||
|
||||
The referenced Sops file must follow this structure:
|
||||
To be secure, the `passphraseFile` must contain a secret that is deployed out of band, otherwise it will be world-readable in the nix store.
|
||||
To achieve that, I recommend [sops](usage.html#usage-secrets) although other methods work great too.
|
||||
|
||||
```yaml
|
||||
restic:
|
||||
passphrases:
|
||||
myfolder: <secret>
|
||||
```
|
||||
|
||||
To generate a secret, use: `nix run nixpkgs#openssl -- rand -hex 64`.
|
||||
|
||||
With the borgmatic implementation, the structure should be:
|
||||
|
||||
```yaml
|
||||
borgmatic:
|
||||
keys:
|
||||
myfolder: |
|
||||
BORG_KEY <key>
|
||||
passphrases:
|
||||
myfolder: <secret>
|
||||
```
|
||||
|
||||
You can have both borgmatic and restic implementations working at the same time.
|
||||
|
||||
### One folder backed up to S3 {#blocks-backup-usage-remote}
|
||||
|
||||
> This is only supported by the Restic implementation.
|
||||
### One folder backed up to S3 {#blocks-restic-usage-remote}
|
||||
|
||||
Here we will only highlight the differences with the previous configuration.
|
||||
|
||||
This assumes you have access to such a remote S3 store, for example by using Backblaze.
|
||||
This assumes you have access to such a remote S3 store, for example by using [Backblaze](https://www.backblaze.com/).
|
||||
|
||||
```diff
|
||||
shb.backup.instances.myfolder = {
|
||||
|
@ -97,31 +74,19 @@ This assumes you have access to such a remote S3 store, for example by using Bac
|
|||
OnCalendar = "00:00:00";
|
||||
RandomizedDelaySec = "3h";
|
||||
};
|
||||
|
||||
+ extraSecrets = {
|
||||
+ AWS_ACCESS_KEY_ID="<path/to/access_key_id>";
|
||||
+ AWS_SECRET_ACCESS_KEY="<path/to/secret_access_key>";
|
||||
+ };
|
||||
}];
|
||||
|
||||
|
||||
+ environmentFile = true; # Needed for s3
|
||||
}
|
||||
```
|
||||
|
||||
The Sops file has a new required field:
|
||||
### Multiple directories to multiple destinations {#blocks-restic-usage-multiple}
|
||||
|
||||
```yaml
|
||||
|
||||
restic:
|
||||
passphrases:
|
||||
myfolder: <secret>
|
||||
+ environmentfiles:
|
||||
+ myfolder: |-
|
||||
+ AWS_ACCESS_KEY_ID=<aws_key_id>
|
||||
+ AWS_SECRET_ACCESS_KEY=<aws_secret_key>
|
||||
```
|
||||
|
||||
### Multiple folder to multiple destinations {#blocks-backup-usage-multiple}
|
||||
|
||||
The following snippet shows how to configure backup of any number of folders using the Restic
|
||||
implementation to 3 repositories, each happening at different times to avoid contending for I/O
|
||||
time.
|
||||
The following snippet shows how to configure backup of any number of folders to 3 repositories,
|
||||
each happening at different times to avoid I/O contention.
|
||||
|
||||
We will also make sure to be able to re-use as much as the configuration as possible.
|
||||
|
||||
|
@ -129,7 +94,7 @@ A few assumptions:
|
|||
- 2 hard drive pools used for backup are mounted respectively on `/srv/pool1` and `/srv/pool2`.
|
||||
- You have a backblaze account.
|
||||
|
||||
First, let's define a variable to hold all our repositories you want to back up to:
|
||||
First, let's define a variable to hold all the repositories we want to back up to:
|
||||
|
||||
```nix
|
||||
repos = [
|
||||
|
@ -209,19 +174,38 @@ below) is the former splits the backups into sub-folders on the repositories.
|
|||
shb.backup.instances.all = backupcfg repos ["/var/lib/myfolder1" "/var/lib/myfolder2"];
|
||||
```
|
||||
|
||||
## Demo {#blocks-backup-demo}
|
||||
## Demo {#blocks-restic-demo}
|
||||
|
||||
[WIP]
|
||||
|
||||
## Monitoring {#blocks-backup-monitoring}
|
||||
## Monitoring {#blocks-restic-monitoring}
|
||||
|
||||
[WIP]
|
||||
|
||||
## Maintenance {#blocks-backup-maintenance}
|
||||
## Maintenance {#blocks-restic-maintenance}
|
||||
|
||||
[WIP]
|
||||
One command-line helper is provided per backup instance and repository pair to automatically supply the needed secrets.
|
||||
|
||||
## Options Reference {#blocks-backup-options}
|
||||
In the [multiple directories example](#blocks-restic-usage-multiple) above, the following 6 helpers are provided in the `$PATH`:
|
||||
|
||||
```bash
|
||||
restic-myfolder1_srv_pool1_backups
|
||||
restic-myfolder1_srv_pool2_backups
|
||||
restic-myfolder1_s3_s3.us-west-000.backblazeb2.com_backups
|
||||
restic-myfolder2_srv_pool1_backups
|
||||
restic-myfolder2_srv_pool2_backups
|
||||
restic-myfolder2_s3_s3.us-west-000.backblazeb2.com_backups
|
||||
```
|
||||
|
||||
Discovering those is easy thanks to tab-completion.
|
||||
|
||||
One can then restore a backup with:
|
||||
|
||||
```bash
|
||||
restic-myfolder1_srv_pool1_backups restore latest -t /
|
||||
```
|
||||
|
||||
## Options Reference {#blocks-restic-options}
|
||||
|
||||
```{=include=} options
|
||||
id-prefix: blocks-backup-options-
|
61
modules/contracts/backup.nix
Normal file
61
modules/contracts/backup.nix
Normal file
|
@ -0,0 +1,61 @@
|
|||
{ lib, ... }:
|
||||
lib.types.submodule {
|
||||
freeformType = lib.types.anything;
|
||||
|
||||
options = {
|
||||
user = lib.mkOption {
|
||||
description = "Unix user doing the backups.";
|
||||
type = lib.types.str;
|
||||
default = "backup";
|
||||
};
|
||||
|
||||
group = lib.mkOption {
|
||||
description = "Unix group doing the backups.";
|
||||
type = lib.types.str;
|
||||
default = "backup";
|
||||
};
|
||||
|
||||
sourceDirectories = lib.mkOption {
|
||||
description = "Directories to backup.";
|
||||
type = lib.types.nonEmptyListOf lib.types.str;
|
||||
};
|
||||
|
||||
excludePatterns = lib.mkOption {
|
||||
description = "Patterns to exclude.";
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
};
|
||||
|
||||
retention = lib.mkOption {
|
||||
description = "Backup files retention.";
|
||||
type = lib.types.attrsOf (lib.types.oneOf [ lib.types.int lib.types.nonEmptyStr ]);
|
||||
default = {
|
||||
keep_within = "1d";
|
||||
keep_hourly = 24;
|
||||
keep_daily = 7;
|
||||
keep_weekly = 4;
|
||||
keep_monthly = 6;
|
||||
};
|
||||
};
|
||||
|
||||
hooks = lib.mkOption {
|
||||
description = "Hooks to run around the backup.";
|
||||
default = {};
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
before_backup = lib.mkOption {
|
||||
description = "Hooks to run before backup";
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
};
|
||||
|
||||
after_backup = lib.mkOption {
|
||||
description = "Hooks to run after backup";
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
56
modules/contracts/backup/docs/default.md
Normal file
56
modules/contracts/backup/docs/default.md
Normal file
|
@ -0,0 +1,56 @@
|
|||
# Backup Contract {#backup-contract}
|
||||
|
||||
This NixOS contract represents a backup job
|
||||
that will backup one or more files or directories
|
||||
at a regular schedule.
|
||||
|
||||
## Contract Reference {#backup-contract-options}
|
||||
|
||||
These are all the options that are expected to exist for this contract to be respected.
|
||||
|
||||
```{=include=} options
|
||||
id-prefix: contracts-backup-options-
|
||||
list-id: selfhostblocks-options
|
||||
source: @OPTIONS_JSON@
|
||||
```
|
||||
|
||||
## Usage {#backup-contract-usage}
|
||||
|
||||
A service that can be backed up will provide a `backup` option, like for the [Vaultwarden service][vaultwarden-service-backup].
|
||||
What this option defines is an implementation detail of that service
|
||||
but it will at least define what directories to backup
|
||||
and possibly hooks to run before or after the backup job runs.
|
||||
|
||||
[vaultwarden-service-backup]: services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.backup
|
||||
|
||||
```nix
|
||||
shb.<service>.backup
|
||||
```
|
||||
|
||||
Let's assume a module implementing this contract is available under the `shb.<backup_impl>` variable.
|
||||
Then, to actually backup the service, one would write:
|
||||
|
||||
```nix
|
||||
shb.<backup_impl>.instances."<service>" = shb.<service>.backup // {
|
||||
enable = true;
|
||||
|
||||
// Options specific to backup_impl
|
||||
};
|
||||
```
|
||||
|
||||
Then, for extra caution, a second backup could be made using another module `shb.<backup_impl_2>`:
|
||||
|
||||
```nix
|
||||
shb.<backup_impl_2>.instances."<service>" = shb.<service>.backup // {
|
||||
enable = true;
|
||||
|
||||
// Options specific to backup_impl_2
|
||||
};
|
||||
```
|
||||
|
||||
## Provided Implementations {#backup-contract-impl}
|
||||
|
||||
One implementation is provided out of the box:
|
||||
- [Restic block](blocks-restic.html).
|
||||
|
||||
A second one based on `borgbackup` is in progress.
|
10
modules/contracts/backup/dummyModule.nix
Normal file
10
modules/contracts/backup/dummyModule.nix
Normal file
|
@ -0,0 +1,10 @@
|
|||
{ pkgs, lib, ... }:
|
||||
let
|
||||
contracts = pkgs.callPackage ../. {};
|
||||
in
|
||||
{
|
||||
options.shb.contracts.backup = lib.mkOption {
|
||||
description = "Contract for backups.";
|
||||
type = contracts.backup;
|
||||
};
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
{ lib }:
|
||||
{
|
||||
backup = import ./backup.nix { inherit lib; };
|
||||
mount = import ./mount.nix { inherit lib; };
|
||||
ssl = import ./ssl.nix { inherit lib; };
|
||||
}
|
||||
|
|
|
@ -114,10 +114,26 @@ in
|
|||
default = { path = dataFolder; };
|
||||
};
|
||||
|
||||
backupConfig = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.anything;
|
||||
description = "Backup configuration of Vaultwarden.";
|
||||
default = null;
|
||||
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" = {
|
||||
poolName = "root";
|
||||
} // config.shb.vaultwarden.backup;
|
||||
```
|
||||
'';
|
||||
readOnly = true;
|
||||
default = {
|
||||
sourceDirectories = [
|
||||
dataFolder
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
debug = lib.mkOption {
|
||||
|
@ -217,14 +233,6 @@ in
|
|||
members = [ "backup" ];
|
||||
};
|
||||
|
||||
shb.backup.instances.vaultwarden = lib.mkIf (cfg.backupConfig != null) (
|
||||
cfg.backupConfig //
|
||||
{
|
||||
sourceDirectories = [
|
||||
config.services.vaultwarden.config.DATA_FOLDER
|
||||
];
|
||||
});
|
||||
|
||||
# TODO: make this work.
|
||||
# It does not work because it leads to infinite recursion.
|
||||
# ${cfg.mount}.path = dataFolder;
|
||||
|
|
|
@ -91,3 +91,25 @@ Integration with the ZFS block allows to automatically create the relevant datas
|
|||
shb.zfs.datasets."vaultwarden" = config.shb.vaultwarden.mount;
|
||||
shb.zfs.datasets."postgresql".path = "/var/lib/postgresql";
|
||||
```
|
||||
|
||||
## 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}
|
||||
|
||||
In case of an issue, check the logs of the `vaultwarden.service` systemd service.
|
||||
|
||||
Enable verbose logging by setting the `shb.vaultwarden.debug` boolean to `true`.
|
||||
|
||||
Access the database with `sudo -u vaultwarden psql`.
|
||||
|
||||
## Options Reference {#services-vaultwarden-options}
|
||||
|
||||
```{=include=} options
|
||||
id-prefix: services-vaultwarden-options-
|
||||
list-id: selfhostblocks-vaultwarden-options
|
||||
source: @OPTIONS_JSON@
|
||||
```
|
||||
|
|
156
test/blocks/restic.nix
Normal file
156
test/blocks/restic.nix
Normal file
|
@ -0,0 +1,156 @@
|
|||
{ pkgs, lib, ... }:
|
||||
let
|
||||
pkgs' = pkgs;
|
||||
|
||||
testLib = pkgs.callPackage ../common.nix {};
|
||||
|
||||
base = testLib.base [
|
||||
../../modules/blocks/restic.nix
|
||||
];
|
||||
in
|
||||
{
|
||||
backupAndRestore = pkgs.testers.runNixOSTest {
|
||||
name = "restic_backupAndRestore";
|
||||
|
||||
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";
|
||||
};
|
||||
}
|
||||
{
|
||||
path = "/opt/repos/B";
|
||||
timerConfig = {
|
||||
OnCalendar = "00:00:00";
|
||||
RandomizedDelaySec = "5h";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
extraPythonPackages = p: [ p.dictdiffer ];
|
||||
skipTypeCheck = true;
|
||||
|
||||
testScript = { nodes, ... }: let
|
||||
instanceCfg = nodes.machine.shb.restic.instances."testinstance";
|
||||
in ''
|
||||
from dictdiffer import diff
|
||||
|
||||
def list_files(dir):
|
||||
files_and_content = {}
|
||||
|
||||
files = machine.succeed(f"""
|
||||
find {dir} -type f
|
||||
""").split("\n")[:-1]
|
||||
|
||||
for f in files:
|
||||
content = machine.succeed(f"""
|
||||
cat {f}
|
||||
""").strip()
|
||||
files_and_content[f] = content
|
||||
|
||||
return files_and_content
|
||||
|
||||
def assert_files(dir, files):
|
||||
result = list(diff(list_files(dir), files))
|
||||
if len(result) > 0:
|
||||
raise Exception("Unexpected files:", result)
|
||||
|
||||
with subtest("Create initial content"):
|
||||
machine.succeed("""
|
||||
mkdir -p /opt/files/A
|
||||
mkdir -p /opt/files/B
|
||||
mkdir -p /opt/repos/A
|
||||
mkdir -p /opt/repos/B
|
||||
|
||||
echo repoA_fileA_1 > /opt/files/A/fileA
|
||||
echo repoA_fileB_1 > /opt/files/A/fileB
|
||||
echo repoB_fileA_1 > /opt/files/B/fileA
|
||||
echo repoB_fileB_1 > /opt/files/B/fileB
|
||||
|
||||
# chown :backup -R /opt/files
|
||||
""")
|
||||
|
||||
assert_files("/opt/files", {
|
||||
'/opt/files/B/fileA': 'repoB_fileA_1',
|
||||
'/opt/files/B/fileB': 'repoB_fileB_1',
|
||||
'/opt/files/A/fileA': 'repoA_fileA_1',
|
||||
'/opt/files/A/fileB': 'repoA_fileB_1',
|
||||
})
|
||||
|
||||
with subtest("First backup in repo A"):
|
||||
machine.succeed("systemctl start restic-backups-testinstance_opt_repos_A")
|
||||
|
||||
with subtest("New content"):
|
||||
machine.succeed("""
|
||||
echo repoA_fileA_2 > /opt/files/A/fileA
|
||||
echo repoA_fileB_2 > /opt/files/A/fileB
|
||||
echo repoB_fileA_2 > /opt/files/B/fileA
|
||||
echo repoB_fileB_2 > /opt/files/B/fileB
|
||||
""")
|
||||
|
||||
assert_files("/opt/files", {
|
||||
'/opt/files/B/fileA': 'repoB_fileA_2',
|
||||
'/opt/files/B/fileB': 'repoB_fileB_2',
|
||||
'/opt/files/A/fileA': 'repoA_fileA_2',
|
||||
'/opt/files/A/fileB': 'repoA_fileB_2',
|
||||
})
|
||||
|
||||
with subtest("Second backup in repo B"):
|
||||
machine.succeed("systemctl start restic-backups-testinstance_opt_repos_B")
|
||||
|
||||
with subtest("Delete content"):
|
||||
machine.succeed("""
|
||||
rm -r /opt/files/A /opt/files/B
|
||||
""")
|
||||
|
||||
assert_files("/opt/files", {})
|
||||
|
||||
with subtest("Restore initial content from repo A"):
|
||||
machine.succeed("""
|
||||
restic-testinstance_opt_repos_A restore latest -t /
|
||||
""")
|
||||
|
||||
assert_files("/opt/files", {
|
||||
'/opt/files/B/fileA': 'repoB_fileA_1',
|
||||
'/opt/files/B/fileB': 'repoB_fileB_1',
|
||||
'/opt/files/A/fileA': 'repoA_fileA_1',
|
||||
'/opt/files/A/fileB': 'repoA_fileB_1',
|
||||
})
|
||||
|
||||
with subtest("Restore initial content from repo B"):
|
||||
machine.succeed("""
|
||||
restic-testinstance_opt_repos_B restore latest -t /
|
||||
""")
|
||||
|
||||
assert_files("/opt/files", {
|
||||
'/opt/files/B/fileA': 'repoB_fileA_2',
|
||||
'/opt/files/B/fileB': 'repoB_fileB_2',
|
||||
'/opt/files/A/fileA': 'repoA_fileA_2',
|
||||
'/opt/files/A/fileB': 'repoA_fileB_2',
|
||||
})
|
||||
'';
|
||||
};
|
||||
}
|
|
@ -1,6 +1,12 @@
|
|||
{
|
||||
lib,
|
||||
}:
|
||||
let
|
||||
baseImports = pkgs: [
|
||||
(pkgs.path + "/nixos/modules/profiles/headless.nix")
|
||||
(pkgs.path + "/nixos/modules/profiles/qemu-guest.nix")
|
||||
];
|
||||
in
|
||||
{
|
||||
accessScript = {
|
||||
subdomain
|
||||
|
@ -92,10 +98,12 @@
|
|||
${indent 4 script}
|
||||
'');
|
||||
|
||||
inherit baseImports;
|
||||
|
||||
base = pkgs: additionalModules: {
|
||||
imports = [
|
||||
(pkgs.path + "/nixos/modules/profiles/headless.nix")
|
||||
(pkgs.path + "/nixos/modules/profiles/qemu-guest.nix")
|
||||
imports =
|
||||
( baseImports pkgs )
|
||||
++ [
|
||||
# TODO: replace this option by the backup contract
|
||||
{
|
||||
options = {
|
||||
|
@ -106,7 +114,8 @@
|
|||
../modules/blocks/postgresql.nix
|
||||
../modules/blocks/authelia.nix
|
||||
../modules/blocks/nginx.nix
|
||||
] ++ additionalModules;
|
||||
]
|
||||
++ additionalModules;
|
||||
|
||||
# Nginx port.
|
||||
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
||||
|
|
Loading…
Reference in a new issue