update documentation

This commit is contained in:
ibizaman 2024-11-11 22:38:16 +01:00 committed by Pierre Penninckx
parent 64d337e1a6
commit 07f6de7409
15 changed files with 419 additions and 138 deletions

View file

@ -34,6 +34,10 @@ Not all blocks are yet documented. You can find all available blocks [in the rep
modules/blocks/ssl/docs/default.md modules/blocks/ssl/docs/default.md
``` ```
```{=include=} chapters html:into-file=//blocks-postgresql.html
modules/blocks/postgresql/docs/default.md
```
```{=include=} chapters html:into-file=//blocks-restic.html ```{=include=} chapters html:into-file=//blocks-restic.html
modules/blocks/restic/docs/default.md modules/blocks/restic/docs/default.md
``` ```

View file

@ -20,10 +20,13 @@ as possible, reducing the quite thick layer that it is now.
Provided contracts are: Provided contracts are:
- [SSL generator contract](contracts-ssl.html) to generate SSL certificates. - [SSL generator contract](contracts-ssl.html) to generate SSL certificates.
Two implementations are provided: self-signed and Let's Encrypt. Two providers are implemented: self-signed and Let's Encrypt.
- [Backup contract](contracts-backup.html) to backup directories. - [Backup contract](contracts-backup.html) to backup directories.
This contract allows to backup multiple times the same directories for extra protection. One provider is implemented: Restic.
- [Database Backup contract](contracts-databasebackup.html) to backup database dumps.
One provider is implemented: Restic.
- [Secret contract](contracts-secret.html) to provide secrets that are deployed outside of the Nix store. - [Secret contract](contracts-secret.html) to provide secrets that are deployed outside of the Nix store.
One provider is implemented: SOPS.
```{=include=} chapters html:into-file=//contracts-ssl.html ```{=include=} chapters html:into-file=//contracts-ssl.html
modules/contracts/ssl/docs/default.md modules/contracts/ssl/docs/default.md
@ -33,6 +36,10 @@ modules/contracts/ssl/docs/default.md
modules/contracts/backup/docs/default.md modules/contracts/backup/docs/default.md
``` ```
```{=include=} chapters html:into-file=//contracts-databasebackup.html
modules/contracts/databasebackup/docs/default.md
```
```{=include=} chapters html:into-file=//contracts-secret.html ```{=include=} chapters html:into-file=//contracts-secret.html
modules/contracts/secret/docs/default.md modules/contracts/secret/docs/default.md
``` ```

View file

@ -132,13 +132,15 @@ in stdenv.mkDerivation {
'@OPTIONS_JSON@' \ '@OPTIONS_JSON@' \
${individualModuleOptionsDocs [ ../modules/blocks/ssl.nix ]}/share/doc/nixos/options.json ${individualModuleOptionsDocs [ ../modules/blocks/ssl.nix ]}/share/doc/nixos/options.json
substituteInPlace ./modules/blocks/postgresql/docs/default.md \
--replace \
'@OPTIONS_JSON@' \
${individualModuleOptionsDocs [ ../modules/blocks/postgresql.nix ]}/share/doc/nixos/options.json
substituteInPlace ./modules/blocks/restic/docs/default.md \ substituteInPlace ./modules/blocks/restic/docs/default.md \
--replace \ --replace \
'@OPTIONS_JSON@' \ '@OPTIONS_JSON@' \
${individualModuleOptionsDocs [ ${individualModuleOptionsDocs [ ../modules/blocks/restic.nix ]}/share/doc/nixos/options.json
../modules/blocks/restic.nix
../modules/blocks/restic/dummyModule.nix
]}/share/doc/nixos/options.json
substituteInPlace ./modules/services/nextcloud-server/docs/default.md \ substituteInPlace ./modules/services/nextcloud-server/docs/default.md \
--replace \ --replace \
@ -163,6 +165,11 @@ in stdenv.mkDerivation {
'@OPTIONS_JSON@' \ '@OPTIONS_JSON@' \
${individualModuleOptionsDocs [ ../modules/contracts/backup/dummyModule.nix ]}/share/doc/nixos/options.json ${individualModuleOptionsDocs [ ../modules/contracts/backup/dummyModule.nix ]}/share/doc/nixos/options.json
substituteInPlace ./modules/contracts/databasebackup/docs/default.md \
--replace \
'@OPTIONS_JSON@' \
${individualModuleOptionsDocs [ ../modules/contracts/databasebackup/dummyModule.nix ]}/share/doc/nixos/options.json
substituteInPlace ./modules/contracts/secret/docs/default.md \ substituteInPlace ./modules/contracts/secret/docs/default.md \
--replace \ --replace \
'@OPTIONS_JSON@' \ '@OPTIONS_JSON@' \

View file

@ -50,8 +50,7 @@ in
default = false; default = false;
}; };
backup = lib.mkOption { databasebackup = lib.mkOption {
type = contracts.databasebackup.request;
description = '' description = ''
Backup configuration. This is an output option. Backup configuration. This is an output option.
@ -67,7 +66,9 @@ in
}; };
``` ```
''; '';
readOnly = true;
type = contracts.databasebackup.requestType;
default = { default = {
user = "postgres"; user = "postgres";

View file

@ -0,0 +1,39 @@
# PostgreSQL Block {#blocks-postgresql}
Defined in [`/modules/blocks/postgresql.nix`](@REPO@/modules/blocks/postgresql.nix).
This block sets up a [PostgreSQL][] database.
[postgresql]: https://www.postgresql.org/
## Tests {#blocks-postgresql-tests}
Specific integration tests are defined in [`/test/blocks/postgresql.nix`](@REPO@/test/blocks/postgresql.nix).
## Database Backup Requester Contracts {#blocks-postgresql-contract-databasebackup}
This block can be backed up using the [database backup](contracts-databasebackup.html) contract.
Contract integration tests are defined in [`/test/contracts/databasebackup.nix`](@REPO@/test/contracts/databasebackup.nix).
### Backing up All Databases {#blocks-postgresql-contract-databasebackup-all}
```nix
{
my.backup.provider."postgresql" = {
request = config.shb.postgresql.databasebackup;
settings = {
// Specific options for the backup provider.
};
};
}
```
## Options Reference {#blocks-postgresql-options}
```{=include=} options
id-prefix: blocks-postgresql-options-
list-id: selfhostblocks-block-postgresql-options
source: @OPTIONS_JSON@
```

View file

@ -10,9 +10,13 @@ let
inherit (lib) generators hasPrefix mkIf nameValuePair optionalAttrs removePrefix; inherit (lib) generators hasPrefix mkIf nameValuePair optionalAttrs removePrefix;
inherit (lib.types) attrsOf enum int ints listOf oneOf nonEmptyListOf nonEmptyStr nullOr path str submodule; inherit (lib.types) attrsOf enum int ints listOf oneOf nonEmptyListOf nonEmptyStr nullOr path str submodule;
commonOptions = submodule { commonOptions = {
options = { enable = mkEnableOption ''
enable = mkEnableOption "shb restic. A disabled instance will not backup data anymore but still provides the helper tool to introspect and rollback snapshots"; this backup intance.
A disabled instance will not backup data anymore
but still provides the helper tool to restore snapshots
'';
passphraseFile = mkOption { passphraseFile = mkOption {
description = "Encryption key for the backups."; description = "Encryption key for the backups.";
@ -40,8 +44,8 @@ let
''; '';
example = literalExpression '' example = literalExpression ''
{ {
AWS_ACCESS_KEY_ID = <path/to/secret>; AWS_ACCESS_KEY_ID.source = <path/to/secret>;
AWS_SECRET_ACCESS_KEY = <path/to/secret>; AWS_SECRET_ACCESS_KEY.source = <path/to/secret>;
} }
''; '';
}; };
@ -89,7 +93,6 @@ let
example = 8000; example = 8000;
}; };
}; };
};
repoSlugName = name: builtins.replaceStrings ["/" ":"] ["_" "_"] (removePrefix "/" name); repoSlugName = name: builtins.replaceStrings ["/" ":"] ["_" "_"] (removePrefix "/" name);
backupName = name: repository: "${name}_${repoSlugName repository.path}"; backupName = name: repository: "${name}_${repoSlugName repository.path}";
@ -103,43 +106,89 @@ in
type = attrsOf (submodule ({ name, options, ... }: { type = attrsOf (submodule ({ name, options, ... }: {
options = { options = {
request = mkOption { request = mkOption {
description = ''
Request part of the backup contract.
Accepts values from a requester.
'';
type = contracts.backup.request; type = contracts.backup.request;
}; };
settings = mkOption { settings = mkOption {
type = commonOptions; description = ''
Settings specific to the Restic provider.
'';
type = submodule {
options = commonOptions;
};
}; };
result = mkOption { result = mkOption {
type = contracts.databasebackup.result; description = ''
Result part of the backup contract.
Contains the output of the Restic provider.
'';
type = lib.types.anything; # contracts.databasebackup.result;
default = { default = {
restoreScript = fullName name options.settings.value.repository; restoreScript = fullName name options.settings.value.repository;
backupService = "${fullName name options.settings.value.repository}.service"; backupService = "${fullName name options.settings.value.repository}.service";
}; };
defaultText = literalExpression ''
{
restoreScript = "${fullName "<name>" { path = "path/to/repository"; }}";
backupService = "${fullName "<name>" { path = "path/to/repository"; }}.service";
}
'';
}; };
}; };
})); }));
}; };
databases = mkOption { databases = mkOption {
description = "Each item is backing up some database to one repository."; description = "Databases to backup following the database backup contract.";
default = {}; default = {};
type = attrsOf (submodule ({ name, options, ... }: { type = attrsOf (submodule ({ name, options, ... }: {
options = { options = {
request = mkOption { request = mkOption {
type = contracts.databasebackup.request; description = ''
Request part of the backup contract.
Accepts values from a requester.
'';
type = contracts.databasebackup.requestType;
}; };
settings = mkOption { settings = mkOption {
type = commonOptions; description = ''
Settings specific to the Restic provider.
'';
type = submodule {
options = commonOptions;
};
}; };
result = mkOption { result = mkOption {
type = contracts.databasebackup.result; description = ''
Result part of the backup contract.
Contains the output of the Restic provider.
'';
type = contracts.databasebackup.resultType;
default = { default = {
restoreScript = fullName name options.settings.value.repository; restoreScript = fullName name options.settings.value.repository;
backupService = "${fullName name (lib.debug.traceValSeqN 2 (lib.debug.traceValSeqN 2 (lib.debug.traceValSeqN 2 options).settings).value).repository}.service"; backupService = "${fullName name options.settings.value.repository}.service";
}; };
defaultText = literalExpression ''
{
restoreScript = "${fullName "<name>" { path = "path/to/repository"; }}";
backupService = "${fullName "<name>" { path = "path/to/repository"; }}.service";
}
'';
}; };
}; };
})); }));

View file

@ -6,15 +6,17 @@ This block sets up a backup job using [Restic][restic].
[restic]: https://restic.net/ [restic]: https://restic.net/
## Contract {#blocks-restic-features} ## Tests {#blocks-restic-tests}
This block implements the [backup](contracts-backup.html) contract. Specific integration tests are defined in [`/test/blocks/restic.nix`](@REPO@/test/blocks/restic.nix).
Integration tests are defined in [`/test/blocks/restic.nix`](@REPO@/test/blocks/restic.nix). ## Provider Contracts {#blocks-restic-contract-provider}
## Usage {#blocks-backup-usage} This block implements the [backup](contracts-backup.html) and [database backup](contracts-databasebackup.html) contracts.
### One folder backed up to mounted hard drives {#blocks-backup-usage-one} Contract integration tests are defined in [`/test/contracts/backup.nix`](@REPO@/test/contracts/backup.nix).
### One folder backed up to mounted hard drives {#blocks-restic-contract-provider-one}
The following snippet shows how to configure The following snippet shows how to configure
the backup of 1 folder to 1 repository. the backup of 1 folder to 1 repository.
@ -54,7 +56,7 @@ shb.restic.instances.myservice = {
}; };
``` ```
### One folder backed up to S3 {#blocks-restic-usage-remote} ### One folder backed up to S3 {#blocks-restic-contract-provider-remote}
Here we will only highlight the differences with the previous configuration. Here we will only highlight the differences with the previous configuration.
@ -79,7 +81,7 @@ This assumes you have access to such a remote S3 store, for example by using [Ba
} }
``` ```
### Secrets {#blocks-restic-secrets} ## Secrets {#blocks-restic-secrets}
To be secure, the secrets should deployed out of band, otherwise they will be world-readable in the nix store. To be secure, the secrets should deployed out of band, otherwise they will be world-readable in the nix store.
@ -119,7 +121,7 @@ sops.secrets."backup/b2/secret_access_key" = {
Pay attention that the owner must be the `myservice` user, the one owning the files to be backed up. Pay attention that the owner must be the `myservice` user, the one owning the files to be backed up.
A `secrets` contract is in progress that will allow one to not care about such details. A `secrets` contract is in progress that will allow one to not care about such details.
### Multiple directories to multiple destinations {#blocks-restic-usage-multiple} ## Multiple directories to multiple destinations {#blocks-restic-usage-multiple}
The following snippet shows how to configure backup of any number of folders to 3 repositories, 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. each happening at different times to avoid I/O contention.
@ -230,7 +232,7 @@ restic-myfolder2_s3_s3.us-west-000.backblazeb2.com_backups
Discovering those is easy thanks to tab-completion. Discovering those is easy thanks to tab-completion.
One can then restore a backup with: One can then restore a backup from a given repository with:
```bash ```bash
restic-myfolder1_srv_pool1_backups restore latest restic-myfolder1_srv_pool1_backups restore latest
@ -243,7 +245,7 @@ In case something bad happens with a backup, the [official documentation](https:
## Options Reference {#blocks-restic-options} ## Options Reference {#blocks-restic-options}
```{=include=} options ```{=include=} options
id-prefix: blocks-backup-options- id-prefix: blocks-restic-options-
list-id: selfhostblocks-block-backup-options list-id: selfhostblocks-block-restic-options
source: @OPTIONS_JSON@ source: @OPTIONS_JSON@
``` ```

View file

@ -1,8 +1,3 @@
{ lib, ... }: { lib, ... }:
{ {
config = {
shb.restic.databases."<name>".settings = {
repository = "";
};
};
} }

View file

@ -5,8 +5,6 @@ let
in in
{ {
request = submodule { request = submodule {
freeformType = anything;
options = { options = {
user = mkOption { user = mkOption {
description = "Unix user doing the backups."; description = "Unix user doing the backups.";

View file

@ -1,13 +1,13 @@
# Backup Contract {#backup-contract} # Backup Contract {#contract-backup}
This NixOS contract represents a backup job This NixOS contract represents a backup job
that will backup one or more files or directories that will backup one or more files or directories
at a regular schedule. on a regular schedule.
It is a contract between a service that has files to be backed up It is a contract between a service that has files to be backed up
and a service that backs up files. and a service that backs up files.
## Contract Reference {#backup-contract-options} ## Contract Reference {#contract-backup-options}
These are all the options that are expected to exist for this contract to be respected. These are all the options that are expected to exist for this contract to be respected.
@ -17,9 +17,11 @@ list-id: selfhostblocks-options
source: @OPTIONS_JSON@ source: @OPTIONS_JSON@
``` ```
## Usage {#backup-contract-usage} ## Usage {#contract-backup-usage}
A service that can be backed up will provide a `backup` option. A service that can be backed up will provide a `backup` option.
Such a service is a `requester` providing a `request` for a module `provider` of this contract.
What this option defines is, from the user perspective - that is _you_ - an implementation detail What this option defines is, from the user perspective - that is _you_ - an implementation detail
but it will at least define what directories to backup, but it will at least define what directories to backup,
the user to backup with the user to backup with
@ -30,9 +32,8 @@ Here is an example module defining such a `backup` option:
```nix ```nix
{ {
options = { options = {
myservice.backup = lib.mkOption { myservice.backup = mkOption {
type = contracts.backup.request; type = contracts.backup.request;
readOnly = true;
default = { default = {
user = "myservice"; user = "myservice";
sourceDirectories = [ sourceDirectories = [
@ -44,16 +45,18 @@ Here is an example module defining such a `backup` option:
}; };
``` ```
As you can see, NixOS modules are a bit abused to make contracts work.
Default values are set as well as the `readOnly` attribute to ensure those values stay as defined.
Now, on the other side we have a service that uses this `backup` option and actually backs up files. Now, on the other side we have a service that uses this `backup` option and actually backs up files.
This service is a `provider` of this contract and will provide a `result` option.
Let's assume such a module is available under the `backupservice` option Let's assume such a module is available under the `backupservice` option
and that one can create multiple backup instances under `backupservice.instances`. and that one can create multiple backup instances under `backupservice.instances`.
Then, to actually backup the `myservice` service, one would write: Then, to actually backup the `myservice` service, one would write:
```nix ```nix
backupservice.instances.myservice = myservice.backup // { backupservice.instances.myservice = {
request = myservice.backup;
settings = {
enable = true; enable = true;
repository = { repository = {
@ -61,6 +64,7 @@ backupservice.instances.myservice = myservice.backup // {
}; };
# ... Other options specific to backupservice like scheduling. # ... Other options specific to backupservice like scheduling.
};
}; };
``` ```
@ -68,28 +72,27 @@ It is advised to backup files to different location, to improve redundancy.
Thanks to using contracts, this can be made easily either with the same `backupservice`: Thanks to using contracts, this can be made easily either with the same `backupservice`:
```nix ```nix
backupservice.instances.myservice_2 = myservice.backup // { backupservice.instances.myservice_2 = {
request = myservice.backup;
settings = {
enable = true; enable = true;
repository = { repository = {
path = "<remote path>"; path = "<remote path>";
}; };
};
}; };
``` ```
Or with another module `backupservice_2`! Or with another module `backupservice_2`!
## Provided Implementations {#backup-contract-impl} ## Providers of the Backup Contract {#contract-backup-providers}
An implementation here is a service that understands the `backup` contract
and will backup the files accordingly.
One implementation is provided out of the box:
- [Restic block](blocks-restic.html). - [Restic block](blocks-restic.html).
- [Borgbackup block](blocks-borgbackup.html) [WIP].
A second one based on `borgbackup` is in progress. ## Requester Blocks and Services {#contract-backup-requesters}
## Services Providing `backup` Option {#backup-contract-services}
- <!-- [ -->Audiobookshelf<!-- ](services-audiobookshelf.html). --> (no manual yet) - <!-- [ -->Audiobookshelf<!-- ](services-audiobookshelf.html). --> (no manual yet)
- <!-- [ -->Deluge<!--](services-deluge.html). --> (no manual yet) - <!-- [ -->Deluge<!--](services-deluge.html). --> (no manual yet)

View file

@ -1,9 +1,11 @@
{ pkgs, lib, ... }: { pkgs, lib, ... }:
let let
contracts = pkgs.callPackage ../. {}; contracts = pkgs.callPackage ../. {};
inherit (lib) mkOption;
in in
{ {
options.shb.contracts.backup = lib.mkOption { options.shb.contracts.backup = mkOption {
description = "Contract for backups."; description = "Contract for backups.";
type = contracts.backup.request; type = contracts.backup.request;
}; };

View file

@ -1,45 +1,76 @@
{ lib, ... }: { lib, ... }:
let let
inherit (lib) mkOption; inherit (lib) mkIf mkOption literalExpression;
inherit (lib.types) anything submodule str; inherit (lib.types) anything submodule str;
in in
{ {
request = submodule { requestType = submodule {
freeformType = anything;
options = { options = {
user = mkOption { user = mkOption {
description = "Unix user doing the backups."; description = "Unix user doing the backups.";
type = str; type = str;
example = "postgres";
}; };
backupFile = mkOption { backupFile = mkOption {
description = "Filename of the backup."; description = "Filename of the backup.";
type = str; type = str;
default = "dump";
example = "postgresql.sql";
}; };
backupCmd = mkOption { backupCmd = mkOption {
description = "Command that produces the database dump on stdout."; description = "Command that produces the database dump on stdout.";
type = str; type = str;
example = literalExpression ''
''${pkgs.postgresql}/bin/pg_dumpall | ''${pkgs.gzip}/bin/gzip --rsyncable
'';
}; };
restoreCmd = mkOption { restoreCmd = mkOption {
description = "Command that reads the database dump on stdin and restores the database."; description = "Command that reads the database dump on stdin and restores the database.";
type = str; type = str;
example = literalExpression ''
''${pkgs.gzip}/bin/gunzip | ''${pkgs.postgresql}/bin/psql postgres
'';
}; };
}; };
}; };
result = submodule {
resultType = submodule {
options = { options = {
restoreScript = mkOption { restoreScript = mkOption {
description = "Name of script that can restore the database."; description = ''
Name of script that can restore the database.
One can then list snapshots with:
```bash
$ my_restore_script snapshots
```
And restore the database with:
```bash
$ my_restore_script restore latest
```
'';
type = str; type = str;
example = "my_restore_script";
}; };
backupService = mkOption { backupService = mkOption {
description = "Name of service backing up the database."; description = ''
Name of service backing up the database.
This script can be ran manually to backup the database:
```bash
$ systemctl start my_backup_service
```
'';
type = str; type = str;
example = "my_backup_service.service";
}; };
}; };
}; };

View file

@ -0,0 +1,100 @@
# Database Backup Contract {#contract-databasebackup}
This NixOS contract represents a backup job
that will backup everything in one database
on a regular schedule.
It is a contract between a service that has database dumps to be backed up
and a service that backs up databases dumps.
## Contract Reference {#contract-databasebackup-options}
These are all the options that are expected to exist for this contract to be respected.
```{=include=} options
id-prefix: contracts-databasebackup-options-
list-id: selfhostblocks-options
source: @OPTIONS_JSON@
```
## Usage {#contract-databasebackup-usage}
A database that can be backed up will provide a `databasebackup` option.
Such a service is a `requester` providing a `request` for a module `provider` of this contract.
What this option defines is, from the user perspective - that is _you_ - an implementation detail
but it will at least define how to create a database dump,
the user to backup with
and how to restore from a database dump.
Here is an example module defining such a `databasebackup` option:
```nix
{
options = {
myservice.databasebackup = mkOption {
type = contracts.databasebackup.request;
default = {
user = "myservice";
backupCmd = ''
${pkgs.postgresql}/bin/pg_dumpall | ${pkgs.gzip}/bin/gzip --rsyncable
'';
restoreCmd = ''
${pkgs.gzip}/bin/gunzip | ${pkgs.postgresql}/bin/psql postgres
'';
};
};
};
};
```
Now, on the other side we have a service that uses this `backup` option and actually backs up files.
This service is a `provider` of this contract and will provide a `result` option.
Let's assume such a module is available under the `databasebackupservice` option
and that one can create multiple backup instances under `databasebackupservice.instances`.
Then, to actually backup the `myservice` service, one would write:
```nix
databasebackupservice.instances.myservice = {
request = myservice.databasebackup;
settings = {
enable = true;
repository = {
path = "/srv/backup/myservice";
};
# ... Other options specific to backupservice like scheduling.
};
};
```
It is advised to backup files to different location, to improve redundancy.
Thanks to using contracts, this can be made easily either with the same `databasebackupservice`:
```nix
databasebackupservice.instances.myservice_2 = {
request = myservice.backup;
settings = {
enable = true;
repository = {
path = "<remote path>";
};
};
};
```
Or with another module `databasebackupservice_2`!
## Providers of the Database Backup Contract {#contract-databasebackup-providers}
- [Restic block](blocks-restic.html).
- [Borgbackup block](blocks-borgbackup.html) [WIP].
## Requester Blocks and Services {#contract-databasebackup-requesters}
- [PostgreSQL](blocks-postgresql.html#blocks-postgresql-contract-databasebackup).

View file

@ -0,0 +1,43 @@
{ pkgs, lib, ... }:
let
contracts = pkgs.callPackage ../. {};
inherit (lib) mkOption;
inherit (lib.types) anything submodule;
in
{
options.shb.contracts.databasebackup = mkOption {
description = ''
Contract for database backup between a requester module
and a provider module.
The requester communicates to the provider
how to backup the database
through the `request` options.
The provider reads from the `request` options
and backs up the database as requested.
It communicates to the requester what script is used
to backup and restore the database
through the `result` options.
'';
type = submodule {
options = {
request = mkOption {
description = ''
Options set by a requester module of the database contract.
'';
type = contracts.databasebackup.requestType;
};
result = mkOption {
description = ''
Options set by a provider module of the database contract.
'';
type = contracts.databasebackup.resultType;
};
};
};
};
}

View file

@ -21,7 +21,7 @@ in
imports = ( testLib.baseImports pkgs' ) ++ modules; imports = ( testLib.baseImports pkgs' ) ++ modules;
config = lib.mkMerge [ config = lib.mkMerge [
(setAttrByPath providerRoot { (setAttrByPath providerRoot {
request = (getAttrFromPath requesterRoot config).backup; request = (getAttrFromPath requesterRoot config).databasebackup;
settings = settings "/opt/repos/database"; settings = settings "/opt/repos/database";
}) })
(mkIf (username != "root") { (mkIf (username != "root") {