update secret contract and add sops module (#356)
I find these `mkProvider` and `mkRequester` functions really nice. Also fixes doc generation related to https://github.com/NixOS/nixpkgs/pull/353513
This commit is contained in:
parent
fe137ded23
commit
5056d203d9
50 changed files with 2710 additions and 743 deletions
|
|
@ -40,6 +40,7 @@
|
|||
- `shb.forgejo.databasePasswordFile` -> `shb.forgejo.databasePassword.result.path`.
|
||||
- Backup:
|
||||
- `shb.restic.instances` options has been split between `shb.restic.instances.request` and `shb.restic.instances.settings`, matching better with contracts.
|
||||
- Use of secret contract everywhere.
|
||||
|
||||
|
||||
## User Facing Backwards Compatible Changes
|
||||
|
|
|
|||
14
README.md
14
README.md
|
|
@ -69,7 +69,7 @@ SHB's first goal is to provide unified [building blocks](#available-blocks)
|
|||
and by extension configuration interface, for self-hosting.
|
||||
|
||||
Compare the configuration for Nextcloud and Forgejo in Self Host Blocks.
|
||||
The following snippets focus on similitudes and assume the relevant blocks are configured off-screen.
|
||||
The following snippets focus on similitudes and assume the relevant blocks - like secrets - are configured off-screen.
|
||||
It also does not show specific options for each service.
|
||||
These are still complete snippets that configure HTTPS,
|
||||
subdomain serving the service, LDAP and SSO integration.
|
||||
|
|
@ -87,14 +87,14 @@ shb.nextcloud = {
|
|||
host = "127.0.0.1";
|
||||
port = config.shb.ldap.ldapPort;
|
||||
dcdomain = config.shb.ldap.dcdomain;
|
||||
adminPasswordFile = config.sops.secrets."nextcloud/ldap_admin_password".path;
|
||||
adminPassword.result = config.shb.sops.secrets."nextcloud/ldap/admin_password".result;
|
||||
};
|
||||
apps.sso = {
|
||||
enable = true;
|
||||
endpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
||||
|
||||
secretFile = config.sops.secrets."nextcloud/sso/secret".path;
|
||||
secretFileForAuthelia = config.sops.secrets."authelia/nextcloud_sso_secret".path;
|
||||
secret.result = config.shb.sops.secrets."nextcloud/sso/secret".result;
|
||||
secretForAuthelia.result = config.shb.sops.secrets."nextcloud/sso/secretForAuthelia".result;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
|
@ -112,15 +112,15 @@ shb.forgejo = {
|
|||
host = "127.0.0.1";
|
||||
port = config.shb.ldap.ldapPort;
|
||||
dcdomain = config.shb.ldap.dcdomain;
|
||||
adminPasswordFile = config.sops.secrets."forgejo/ldap_admin_password".path;
|
||||
adminPassword.result = config.shb.sops.secrets."nextcloud/ldap/admin_password".result;
|
||||
};
|
||||
|
||||
sso = {
|
||||
enable = true;
|
||||
endpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
||||
|
||||
secretFile = config.sops.secrets."forgejo/ssoSecret".path;
|
||||
secretFileForAuthelia = config.sops.secrets."forgejo/authelia/ssoSecret".path;
|
||||
secret.result = config.shb.sops.secrets."forgejo/sso/secret".result;
|
||||
secretForAuthelia.result = config.shb.sops.secrets."forgejo/sso/secretForAuthelia".result;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ chmod 600 sshkey
|
|||
|
||||
This is only needed because git mangles with the permissions. You will not even see this change in
|
||||
`git status`.
|
||||
### Deploy with Colmena {#demo-homeassitant-deploy-colmena}
|
||||
### Deploy with Colmena {#demo-homeassistant-deploy-colmena}
|
||||
|
||||
If you deploy with Colmena, you must first build the VM and start it:
|
||||
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ ssh -F ssh_config example
|
|||
|
||||
:::: {.note}
|
||||
This section corresponds to the `basic` section of the [Nextcloud
|
||||
manual](services-nextcloud.html#services-nextcloud-server-usage-basic).
|
||||
manual](services-nextcloud.html#services-nextcloudserver-usage-basic).
|
||||
::::
|
||||
|
||||
Assuming you already deployed the `basic` demo, now you must add the following entry to the
|
||||
|
|
@ -143,7 +143,7 @@ This is the admin user of Nextcloud and that's the end of the `basic` demo.
|
|||
|
||||
:::: {.note}
|
||||
This section corresponds to the `ldap` section of the [Nextcloud
|
||||
manual](services-nextcloud.html#services-nextcloud-server-usage-ldap).
|
||||
manual](services-nextcloud.html#services-nextcloudserver-usage-ldap).
|
||||
::::
|
||||
|
||||
Assuming you already deployed the `ldap` demo, now you must add the following entry to the
|
||||
|
|
@ -182,7 +182,7 @@ This is the end of the `ldap` demo.
|
|||
|
||||
:::: {.note}
|
||||
This section corresponds to the `sso` section of the [Nextcloud
|
||||
manual](services-nextcloud.html#services-nextcloud-server-usage-oidc).
|
||||
manual](services-nextcloud.html#services-nextcloudserver-usage-oidc).
|
||||
::::
|
||||
|
||||
At this point, it is assumed you already deployed the `sso` demo. There is no host to add to
|
||||
|
|
|
|||
|
|
@ -30,6 +30,10 @@ services you already have deployed.
|
|||
Not all blocks are yet documented. You can find all available blocks [in the repository](@REPO@/modules/blocks).
|
||||
:::
|
||||
|
||||
```{=include=} chapters html:into-file=//blocks-sops.html
|
||||
modules/blocks/sops/docs/default.md
|
||||
```
|
||||
|
||||
```{=include=} chapters html:into-file=//blocks-ssl.html
|
||||
modules/blocks/ssl/docs/default.md
|
||||
```
|
||||
|
|
|
|||
|
|
@ -148,11 +148,14 @@ Provided contracts are:
|
|||
- [SSL generator contract](contracts-ssl.html) to generate SSL certificates.
|
||||
Two providers are implemented: self-signed and Let's Encrypt.
|
||||
- [Backup contract](contracts-backup.html) to backup directories.
|
||||
One provider is implemented: Restic.
|
||||
One provider is implemented: [Restic][].
|
||||
- [Database Backup contract](contracts-databasebackup.html) to backup database dumps.
|
||||
One provider is implemented: Restic.
|
||||
One provider is implemented: [Restic][].
|
||||
- [Secret contract](contracts-secret.html) to provide secrets that are deployed outside of the Nix store.
|
||||
One provider is implemented: SOPS.
|
||||
One provider is implemented: [SOPS][].
|
||||
|
||||
[restic]: blocks-restic.html
|
||||
[sops]: blocks-sops.html
|
||||
|
||||
```{=include=} chapters html:into-file=//contracts-ssl.html
|
||||
modules/contracts/ssl/docs/default.md
|
||||
|
|
|
|||
|
|
@ -142,6 +142,11 @@ in stdenv.mkDerivation {
|
|||
'@OPTIONS_JSON@' \
|
||||
${individualModuleOptionsDocs [ ../modules/blocks/restic.nix ]}/share/doc/nixos/options.json
|
||||
|
||||
substituteInPlace ./modules/blocks/sops/docs/default.md \
|
||||
--replace \
|
||||
'@OPTIONS_JSON@' \
|
||||
${individualModuleOptionsDocs [ ../modules/blocks/sops.nix ]}/share/doc/nixos/options.json
|
||||
|
||||
substituteInPlace ./modules/services/nextcloud-server/docs/default.md \
|
||||
--replace \
|
||||
'@OPTIONS_JSON@' \
|
||||
|
|
@ -190,6 +195,7 @@ in stdenv.mkDerivation {
|
|||
|
||||
nixos-render-docs manual html \
|
||||
--manpage-urls ${manpage-urls} \
|
||||
--redirects ./redirects.json \
|
||||
--media-dir media \
|
||||
--revision ${lib.trivial.revisionWithDefault release} \
|
||||
--stylesheet static/style.css \
|
||||
|
|
|
|||
|
|
@ -7,6 +7,6 @@ your local machine with minimal manual steps.
|
|||
demo/homeassistant/README.md
|
||||
```
|
||||
|
||||
```{=include=} chapters html:into-file=//demo-nextcloud-server.html
|
||||
```{=include=} chapters html:into-file=//demo-nextcloud.html
|
||||
demo/nextcloud/README.md
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# All Options {#ch-options}
|
||||
# All Options {#all-options}
|
||||
|
||||
```{=include=} options
|
||||
id-prefix: opt-
|
||||
|
|
|
|||
|
|
@ -39,14 +39,14 @@ shb.nextcloud = {
|
|||
host = "127.0.0.1";
|
||||
port = config.shb.ldap.ldapPort;
|
||||
dcdomain = config.shb.ldap.dcdomain;
|
||||
adminPasswordFile = config.sops.secrets."nextcloud/ldap_admin_password".path;
|
||||
adminPassword.result = config.shb.sops.secrets."nextcloud/ldap/admin_password".result;
|
||||
};
|
||||
apps.sso = {
|
||||
enable = true;
|
||||
endpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
||||
|
||||
secretFile = config.sops.secrets."nextcloud/sso/secret".path;
|
||||
secretFileForAuthelia = config.sops.secrets."authelia/nextcloud_sso_secret".path;
|
||||
secret.result = config.shb.sops.secrets."nextcloud/sso/secret".result;
|
||||
secretForAuthelia.result = config.shb.sops.secrets."nextcloud/sso/secretForAuthelia".result;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
|
@ -64,15 +64,15 @@ shb.forgejo = {
|
|||
host = "127.0.0.1";
|
||||
port = config.shb.ldap.ldapPort;
|
||||
dcdomain = config.shb.ldap.dcdomain;
|
||||
adminPasswordFile = config.sops.secrets."forgejo/ldap_admin_password".path;
|
||||
adminPassword.result = config.shb.sops.secrets."nextcloud/ldap/admin_password".result;
|
||||
};
|
||||
|
||||
sso = {
|
||||
enable = true;
|
||||
endpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
||||
|
||||
secretFile = config.sops.secrets."forgejo/ssoSecret".path;
|
||||
secretFileForAuthelia = config.sops.secrets."forgejo/authelia/ssoSecret".path;
|
||||
secret.result = config.shb.sops.secrets."forgejo/sso/secret".result;
|
||||
secretForAuthelia.result = config.shb.sops.secrets."forgejo/sso/secretForAuthelia".result;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
|
|
|||
1625
docs/redirects.json
Normal file
1625
docs/redirects.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -286,8 +286,8 @@ One way to setup secrets management using `sops-nix`:
|
|||
Setting the default this way makes all sops instances use that same file.
|
||||
7. Reference the secrets in nix:
|
||||
```nix
|
||||
shb.nextcloud.adminPass.result.path = config.sops.secrets."nextcloud/adminpass".path;
|
||||
|
||||
sops.secrets."nextcloud/adminpass" = config.shb.nextcloud.adminPass.request;
|
||||
shb.sops.secrets."nextcloud/adminpass".request = config.shb.nextcloud.adminPass.request;
|
||||
shb.nextcloud.adminPass.result = config.shb.sops.secrets."nextcloud/adminpass".result;
|
||||
```
|
||||
The above snippet uses the [secrets contract](./contracts-secret.html) to ease configuration.
|
||||
The above snippet uses the [secrets contract](./contracts-secret.html)
|
||||
and [sops block](./blocks-sops.html) to ease the configuration.
|
||||
|
|
|
|||
|
|
@ -68,45 +68,69 @@ in
|
|||
description = "Secrets needed by Authelia";
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
jwtSecret = contracts.secret.mkOption {
|
||||
jwtSecret = lib.mkOption {
|
||||
description = "JWT secret.";
|
||||
mode = "0400";
|
||||
owner = cfg.autheliaUser;
|
||||
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
|
||||
type = lib.types.submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
mode = "0400";
|
||||
owner = cfg.autheliaUser;
|
||||
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
ldapAdminPassword = contracts.secret.mkOption {
|
||||
ldapAdminPassword = lib.mkOption {
|
||||
description = "LDAP admin user password.";
|
||||
mode = "0400";
|
||||
owner = cfg.autheliaUser;
|
||||
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
|
||||
type = lib.types.submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
mode = "0400";
|
||||
owner = cfg.autheliaUser;
|
||||
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
sessionSecret = contracts.secret.mkOption {
|
||||
sessionSecret = lib.mkOption {
|
||||
description = "Session secret.";
|
||||
mode = "0400";
|
||||
owner = cfg.autheliaUser;
|
||||
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
|
||||
type = lib.types.submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
mode = "0400";
|
||||
owner = cfg.autheliaUser;
|
||||
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
storageEncryptionKey = contracts.secret.mkOption {
|
||||
storageEncryptionKey = lib.mkOption {
|
||||
description = "Storage encryption key.";
|
||||
mode = "0400";
|
||||
owner = cfg.autheliaUser;
|
||||
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
|
||||
type = lib.types.submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
mode = "0400";
|
||||
owner = cfg.autheliaUser;
|
||||
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
identityProvidersOIDCHMACSecret = contracts.secret.mkOption {
|
||||
identityProvidersOIDCHMACSecret = lib.mkOption {
|
||||
description = "Identity provider OIDC HMAC secret.";
|
||||
mode = "0400";
|
||||
owner = cfg.autheliaUser;
|
||||
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
|
||||
type = lib.types.submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
mode = "0400";
|
||||
owner = cfg.autheliaUser;
|
||||
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
identityProvidersOIDCIssuerPrivateKey = contracts.secret.mkOption {
|
||||
identityProvidersOIDCIssuerPrivateKey = lib.mkOption {
|
||||
description = ''
|
||||
Identity provider OIDC issuer private key.
|
||||
|
||||
Generate one with `nix run nixpkgs#openssl -- genrsa -out keypair.pem 2048`
|
||||
'';
|
||||
mode = "0400";
|
||||
owner = cfg.autheliaUser;
|
||||
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
|
||||
type = lib.types.submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
mode = "0400";
|
||||
owner = cfg.autheliaUser;
|
||||
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -220,11 +244,15 @@ in
|
|||
type = lib.types.str;
|
||||
description = "Username to connect to the SMTP host.";
|
||||
};
|
||||
password = contracts.secret.mkOption {
|
||||
password = lib.mkOption {
|
||||
description = "File containing the password to connect to the SMTP host.";
|
||||
mode = "0400";
|
||||
owner = cfg.autheliaUser;
|
||||
restartUnits = [ "authelia-${fqdn}" ];
|
||||
type = lib.types.submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
mode = "0400";
|
||||
owner = cfg.autheliaUser;
|
||||
restartUnits = [ "authelia-${fqdn}" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
{ config, options, lib, pkgs, ... }:
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.shb.hardcodedsecret;
|
||||
opt = options.shb.hardcodedsecret;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
|
||||
inherit (lib) mapAttrs' mkOption nameValuePair;
|
||||
inherit (lib.types) attrsOf listOf path nullOr str submodule;
|
||||
inherit (lib.types) attrsOf nullOr str submodule;
|
||||
inherit (pkgs) writeText;
|
||||
in
|
||||
{
|
||||
|
|
@ -16,74 +17,49 @@ in
|
|||
example = lib.literalExpression ''
|
||||
{
|
||||
mySecret = {
|
||||
user = "me";
|
||||
mode = "0400";
|
||||
restartUnits = [ "myservice.service" ];
|
||||
content = "My Secrets";
|
||||
request = {
|
||||
user = "me";
|
||||
mode = "0400";
|
||||
restartUnits = [ "myservice.service" ];
|
||||
};
|
||||
settings.content = "My Secret";
|
||||
};
|
||||
}
|
||||
'';
|
||||
type = attrsOf (submodule ({ name, ... }: {
|
||||
options = {
|
||||
mode = mkOption {
|
||||
options = contracts.secret.mkProvider {
|
||||
settings = mkOption {
|
||||
description = ''
|
||||
Mode of the secret file.
|
||||
Settings specific to the hardcoded secret module.
|
||||
|
||||
Give either `content` or `source`.
|
||||
'';
|
||||
type = str;
|
||||
default = "0400";
|
||||
|
||||
type = submodule {
|
||||
options = {
|
||||
content = mkOption {
|
||||
type = nullOr str;
|
||||
description = ''
|
||||
Content of the secret as a string.
|
||||
|
||||
This will be stored in the nix store and should only be used for testing or maybe in dev.
|
||||
'';
|
||||
default = null;
|
||||
};
|
||||
|
||||
source = mkOption {
|
||||
type = nullOr str;
|
||||
description = ''
|
||||
Source of the content of the secret as a path in the nix store.
|
||||
'';
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
owner = mkOption {
|
||||
description = ''
|
||||
Linux user owning the secret file.
|
||||
'';
|
||||
type = str;
|
||||
default = "root";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
description = ''
|
||||
Linux group owning the secret file.
|
||||
'';
|
||||
type = str;
|
||||
default = "root";
|
||||
};
|
||||
|
||||
restartUnits = mkOption {
|
||||
description = ''
|
||||
Systemd units to restart after the secret is updated.
|
||||
'';
|
||||
type = listOf str;
|
||||
default = [];
|
||||
};
|
||||
|
||||
path = mkOption {
|
||||
type = path;
|
||||
description = ''
|
||||
Path to the file containing the secret generated out of band.
|
||||
|
||||
This path will exist after deploying to a target host,
|
||||
it is not available through the nix store.
|
||||
'';
|
||||
default = "/run/hardcodedsecrets/hardcodedsecret_${name}";
|
||||
};
|
||||
|
||||
content = mkOption {
|
||||
type = nullOr str;
|
||||
description = ''
|
||||
Content of the secret.
|
||||
|
||||
This will be stored in the nix store and should only be used for testing or maybe in dev.
|
||||
'';
|
||||
default = null;
|
||||
};
|
||||
|
||||
source = mkOption {
|
||||
type = nullOr str;
|
||||
description = ''
|
||||
Source of the content of the secret.
|
||||
'';
|
||||
default = null;
|
||||
resultCfg = {
|
||||
path = "/run/hardcodedsecrets/hardcodedsecret_${name}";
|
||||
};
|
||||
};
|
||||
}));
|
||||
|
|
@ -92,16 +68,16 @@ in
|
|||
config = {
|
||||
system.activationScripts = mapAttrs' (n: cfg':
|
||||
let
|
||||
source = if cfg'.source != null
|
||||
then cfg'.source
|
||||
else writeText "hardcodedsecret_${n}_content" cfg'.content;
|
||||
source = if cfg'.settings.source != null
|
||||
then cfg'.settings.source
|
||||
else writeText "hardcodedsecret_${n}_content" cfg'.settings.content;
|
||||
in
|
||||
nameValuePair "hardcodedsecret_${n}" ''
|
||||
mkdir -p "$(dirname "${cfg'.path}")"
|
||||
touch "${cfg'.path}"
|
||||
chmod ${cfg'.mode} "${cfg'.path}"
|
||||
chown ${cfg'.owner}:${cfg'.group} "${cfg'.path}"
|
||||
cp ${source} "${cfg'.path}"
|
||||
mkdir -p "$(dirname "${cfg'.result.path}")"
|
||||
touch "${cfg'.result.path}"
|
||||
chmod ${cfg'.request.mode} "${cfg'.result.path}"
|
||||
chown ${cfg'.request.owner}:${cfg'.request.group} "${cfg'.result.path}"
|
||||
cp ${source} "${cfg'.result.path}"
|
||||
''
|
||||
) cfg;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -47,20 +47,28 @@ in
|
|||
default = 17170;
|
||||
};
|
||||
|
||||
ldapUserPassword = contracts.secret.mkOption {
|
||||
ldapUserPassword = lib.mkOption {
|
||||
description = "LDAP admin user secret.";
|
||||
mode = "0440";
|
||||
owner = "lldap";
|
||||
group = "lldap";
|
||||
restartUnits = [ "lldap.service" ];
|
||||
type = lib.types.submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
mode = "0440";
|
||||
owner = "lldap";
|
||||
group = "lldap";
|
||||
restartUnits = [ "lldap.service" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
jwtSecret = contracts.secret.mkOption {
|
||||
jwtSecret = lib.mkOption {
|
||||
description = "JWT secret.";
|
||||
mode = "0440";
|
||||
owner = "lldap";
|
||||
group = "lldap";
|
||||
restartUnits = [ "lldap.service" ];
|
||||
type = lib.types.submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
mode = "0440";
|
||||
owner = "lldap";
|
||||
group = "lldap";
|
||||
restartUnits = [ "lldap.service" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
restrictAccessIPRange = lib.mkOption {
|
||||
|
|
|
|||
|
|
@ -82,14 +82,28 @@ in
|
|||
default = [];
|
||||
};
|
||||
|
||||
adminPasswordFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
description = "File containing the initial admin password.";
|
||||
adminPassword = lib.mkOption {
|
||||
description = "Initial admin password.";
|
||||
type = lib.types.submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
mode = "0400";
|
||||
owner = "grafana";
|
||||
group = "grafana";
|
||||
restartUnits = [ "grafana.service" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
secretKeyFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
description = "File containing the secret key used for signing.";
|
||||
secretKey = lib.mkOption {
|
||||
description = "Secret key used for signing.";
|
||||
type = lib.types.submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
mode = "0400";
|
||||
owner = "grafana";
|
||||
group = "grafana";
|
||||
restartUnits = [ "grafana.service" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
smtp = lib.mkOption {
|
||||
|
|
@ -159,9 +173,9 @@ in
|
|||
};
|
||||
|
||||
security = {
|
||||
secret_key = "$__file{${cfg.secretKeyFile}}";
|
||||
secret_key = "$__file{${cfg.secretKey.result.path}}";
|
||||
disable_initial_admin_creation = false; # Enable when LDAP support is configured.
|
||||
admin_password = "$__file{${cfg.adminPasswordFile}}"; # Remove when LDAP support is configured.
|
||||
admin_password = "$__file{${cfg.adminPassword.result.path}}"; # Remove when LDAP support is configured.
|
||||
};
|
||||
|
||||
server = {
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ shb.monitoring = {
|
|||
subdomain = "grafana";
|
||||
inherit domain;
|
||||
contactPoints = [ "me@example.com" ];
|
||||
adminPasswordFile = config.sops.secrets."monitoring/admin_password".path;
|
||||
secretKeyFile = config.sops.secrets."monitoring/secret_key".path;
|
||||
adminPassword.result = config.sops.secrets."monitoring/admin_password".reuslt;
|
||||
secretKey.result = config.sops.secrets."monitoring/secret_key".result;
|
||||
};
|
||||
|
||||
sops.secrets."monitoring/admin_password" = {
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ let
|
|||
shblib = pkgs.callPackage ../../lib {};
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
|
||||
inherit (lib) concatStringsSep filterAttrs flatten literalExpression optionals optionalString listToAttrs mapAttrsToList mkEnableOption mkOption mkMerge;
|
||||
inherit (lib) concatStringsSep filterAttrs flatten literalExpression optionals listToAttrs mapAttrsToList mkEnableOption mkOption mkMerge;
|
||||
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 oneOf nonEmptyStr nullOr str submodule;
|
||||
|
||||
commonOptions = { name, options, prefix, ... }: {
|
||||
commonOptions = { name, prefix, config, ... }: {
|
||||
enable = mkEnableOption ''
|
||||
this backup intance.
|
||||
|
||||
|
|
@ -18,13 +18,17 @@ let
|
|||
but still provides the helper tool to restore snapshots
|
||||
'';
|
||||
|
||||
passphrase = contracts.secret.mkOption {
|
||||
passphrase = lib.mkOption {
|
||||
description = "Encryption key for the backup repository.";
|
||||
mode = "0400";
|
||||
owner = options.request.value.user;
|
||||
ownerText = "[shb.restic.${prefix}.<name>.request.user](#blocks-restic-options-shb.restic.${prefix}._name_.request.user)";
|
||||
restartUnits = [ (fullName name options.settings.value.repository) ];
|
||||
restartUnitsText = "[ [shb.restic.${prefix}.<name>.settings.repository](#blocks-restic-options-shb.restic.${prefix}._name_.settings.repository) ]";
|
||||
type = lib.types.submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
mode = "0400";
|
||||
owner = config.request.user;
|
||||
ownerText = "[shb.restic.${prefix}.<name>.request.user](#blocks-restic-options-shb.restic.${prefix}._name_.request.user)";
|
||||
restartUnits = [ (fullName name config.settings.repository) ];
|
||||
restartUnitsText = "[ [shb.restic.${prefix}.<name>.settings.repository](#blocks-restic-options-shb.restic.${prefix}._name_.settings.repository) ]";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
repository = mkOption {
|
||||
|
|
@ -99,7 +103,6 @@ let
|
|||
};
|
||||
|
||||
repoSlugName = name: builtins.replaceStrings ["/" ":"] ["_" "_"] (removePrefix "/" name);
|
||||
backupName = name: repository: "${name}_${repoSlugName repository.path}";
|
||||
fullName = name: repository: "restic-backups-${name}_${repoSlugName repository.path}";
|
||||
in
|
||||
{
|
||||
|
|
@ -107,7 +110,7 @@ in
|
|||
instances = mkOption {
|
||||
description = "Files to backup following the [backup contract](./contracts-backup.html).";
|
||||
default = {};
|
||||
type = attrsOf (submodule ({ name, options, ... }: {
|
||||
type = attrsOf (submodule ({ name, config, ... }: {
|
||||
options = {
|
||||
request = mkOption {
|
||||
description = ''
|
||||
|
|
@ -125,7 +128,7 @@ in
|
|||
'';
|
||||
|
||||
type = submodule {
|
||||
options = commonOptions { inherit name options; prefix = "instances"; };
|
||||
options = commonOptions { inherit name config; prefix = "instances"; };
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -136,16 +139,16 @@ in
|
|||
Contains the output of the Restic provider.
|
||||
'';
|
||||
default = {
|
||||
restoreScript = fullName name options.settings.value.repository;
|
||||
backupService = "${fullName name options.settings.value.repository}.service";
|
||||
restoreScript = fullName name config.settings.repository;
|
||||
backupService = "${fullName name config.settings.repository}.service";
|
||||
};
|
||||
defaultText = {
|
||||
restoreScriptText = "${fullName "<name>" { path = "path/to/repository"; }}";
|
||||
backupServiceText = "${fullName "<name>" { path = "path/to/repository"; }}.service";
|
||||
};
|
||||
type = contracts.backup.result {
|
||||
restoreScript = fullName name options.settings.value.repository;
|
||||
backupService = "${fullName name options.settings.value.repository}.service";
|
||||
restoreScript = fullName name config.settings.repository;
|
||||
backupService = "${fullName name config.settings.repository}.service";
|
||||
restoreScriptText = "${fullName "<name>" { path = "path/to/repository"; }}";
|
||||
backupServiceText = "${fullName "<name>" { path = "path/to/repository"; }}.service";
|
||||
};
|
||||
|
|
@ -157,7 +160,7 @@ in
|
|||
databases = mkOption {
|
||||
description = "Databases to backup following the [database backup contract](./contracts-databasebackup.html).";
|
||||
default = {};
|
||||
type = attrsOf (submodule ({ name, options, ... }: {
|
||||
type = attrsOf (submodule ({ name, config, ... }: {
|
||||
options = {
|
||||
request = mkOption {
|
||||
description = ''
|
||||
|
|
@ -175,7 +178,7 @@ in
|
|||
'';
|
||||
|
||||
type = submodule {
|
||||
options = commonOptions { inherit name options; prefix = "databases"; };
|
||||
options = commonOptions { inherit name config; prefix = "databases"; };
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -186,16 +189,16 @@ in
|
|||
Contains the output of the Restic provider.
|
||||
'';
|
||||
default = {
|
||||
restoreScript = fullName name options.settings.value.repository;
|
||||
backupService = "${fullName name options.settings.value.repository}.service";
|
||||
restoreScript = fullName name config.settings.repository;
|
||||
backupService = "${fullName name config.settings.repository}.service";
|
||||
};
|
||||
defaultText = {
|
||||
restoreScriptText = "${fullName "<name>" { path = "path/to/repository"; }}";
|
||||
backupServiceText = "${fullName "<name>" { path = "path/to/repository"; }}.service";
|
||||
};
|
||||
type = contracts.databasebackup.result {
|
||||
restoreScript = fullName name options.settings.value.repository;
|
||||
backupService = "${fullName name options.settings.value.repository}.service";
|
||||
restoreScript = fullName name config.settings.repository;
|
||||
backupService = "${fullName name config.settings.repository}.service";
|
||||
restoreScriptText = "${fullName "<name>" { path = "path/to/repository"; }}";
|
||||
backupServiceText = "${fullName "<name>" { path = "path/to/repository"; }}.service";
|
||||
};
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ Specific integration tests are defined in [`/test/blocks/restic.nix`](@REPO@/tes
|
|||
|
||||
## Provider Contracts {#blocks-restic-contract-provider}
|
||||
|
||||
This block provides:
|
||||
This block provides the following contracts:
|
||||
|
||||
- [backup contract](contracts-backup.html) under the [`shb.restic.instances`][instances] option.
|
||||
It is tested with [contract tests][backup contract tests].
|
||||
|
|
@ -31,10 +31,11 @@ the only requirement to run it is to be able to `sudo` in the expected user.
|
|||
|
||||
## Usage {#blocks-restic-usage}
|
||||
|
||||
The following examples assume usage of SOPS to provide secrets
|
||||
The following examples assume usage of the [sops block][] to provide secrets
|
||||
although any blocks providing the [secrets contract][] works too.
|
||||
The [secrets setup section](usage.html#usage-secrets) explains
|
||||
how to setup SOPS.
|
||||
|
||||
[sops block]: ./blocks-sops.html
|
||||
[secrets contract]: ./contracts-secrets.html
|
||||
|
||||
### One folder backed up manually {#blocks-restic-usage-provider-manual}
|
||||
|
||||
|
|
@ -55,7 +56,7 @@ shb.restic.instances."myservice" = {
|
|||
settings = {
|
||||
enable = true;
|
||||
|
||||
passphraseFile = "<path/to/passphrase>";
|
||||
passphrase.result = shb.sops.secret."passphrase".result;
|
||||
|
||||
repository = {
|
||||
path = "/srv/backups/myservice";
|
||||
|
|
@ -74,6 +75,9 @@ shb.restic.instances."myservice" = {
|
|||
};
|
||||
};
|
||||
};
|
||||
|
||||
shb.sops.secret."passphrase".request =
|
||||
shb.restic.instances."myservice".settings.passphrase.request;
|
||||
```
|
||||
|
||||
### One folder backed up with contract {#blocks-restic-usage-provider-contract}
|
||||
|
|
@ -89,7 +93,7 @@ shb.restic.instances."myservice" = {
|
|||
settings = {
|
||||
enable = true;
|
||||
|
||||
passphraseFile = "<path/to/passphrase>";
|
||||
passphrase.result = shb.sops.secret."passphrase".result;
|
||||
|
||||
repository = {
|
||||
path = "/srv/backups/myservice";
|
||||
|
|
@ -108,6 +112,9 @@ shb.restic.instances."myservice" = {
|
|||
};
|
||||
};
|
||||
};
|
||||
|
||||
shb.sops.secret."passphrase".request =
|
||||
shb.restic.instances."myservice".settings.passphrase.request;
|
||||
```
|
||||
|
||||
### One folder backed up to S3 {#blocks-restic-usage-provider-remote}
|
||||
|
|
|
|||
36
modules/blocks/sops.nix
Normal file
36
modules/blocks/sops.nix
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{ lib, pkgs, ... }:
|
||||
let
|
||||
inherit (lib) mkOption;
|
||||
inherit (lib.types) attrsOf anything submodule;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
in
|
||||
{
|
||||
options.shb.sops = {
|
||||
secret = mkOption {
|
||||
description = "Secret following the [secret contract](./contracts-secret.html).";
|
||||
default = {};
|
||||
type = attrsOf (submodule ({ name, options, ... }: {
|
||||
options = contracts.secret.mkProvider {
|
||||
settings = mkOption {
|
||||
description = ''
|
||||
Settings specific to the Sops provider.
|
||||
|
||||
This is a passthrough option to set [sops-nix options](https://github.com/Mic92/sops-nix/blob/master/modules/sops/default.nix).
|
||||
|
||||
Note though that the `mode`, `owner`, `group`, and `restartUnits`
|
||||
are managed by the [shb.sops.secret.<name>.request](#blocks-sops-options-shb.sops.secret._name_.request) option.
|
||||
'';
|
||||
|
||||
type = anything;
|
||||
};
|
||||
|
||||
resultCfg = {
|
||||
path = "/run/secrets/${name}";
|
||||
pathText = "/run/secrets/<name>";
|
||||
};
|
||||
};
|
||||
}));
|
||||
};
|
||||
};
|
||||
}
|
||||
47
modules/blocks/sops/docs/default.md
Normal file
47
modules/blocks/sops/docs/default.md
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# SOPS Block {#blocks-sops}
|
||||
|
||||
Defined in [`/modules/blocks/sops.nix`](@REPO@/modules/blocks/sops.nix).
|
||||
|
||||
This block sets up a [sops-nix][] secret.
|
||||
|
||||
It is only a small layer on top of `sops-nix` options
|
||||
to adapt it to the [secret contract](./contract-secret.html).
|
||||
|
||||
[sops-nix]: https://github.com/Mic92/sops-nix
|
||||
|
||||
## Provider Contracts {#blocks-sops-contract-provider}
|
||||
|
||||
This block provides the following contracts:
|
||||
|
||||
- [secret contract][] under the [`shb.sops.secrets`][secret] option.
|
||||
It is not yet tested with [contract tests][secret contract tests] but it is used extensively on several machines.
|
||||
|
||||
[secret]: #blocks-sops-options-shb.sops.secret
|
||||
[secret contract]: contracts-secret.html
|
||||
[secret contract tests]: @REPO@/test/contracts/secret.nix
|
||||
|
||||
As requested by the contract, when asking for a secret with the `shb.sops` module,
|
||||
the path where the secret will be located can be found under the [`shb.sops.secrets.<name>.result`][result] option.
|
||||
|
||||
[result]: #blocks-sops-options-shb.sops.secret._name_.result
|
||||
|
||||
## Usage {#blocks-sops-usage}
|
||||
|
||||
First, a file with encrypted secrets must be created by following the [secrets setup section](usage.html#usage-secrets).
|
||||
|
||||
### With Requester Module {#blocks-sops-usage-requester}
|
||||
|
||||
This example shows how to use this sops block
|
||||
to fulfill the request of a module using the [secret contract][] under the option `services.mymodule.mysecret`.
|
||||
|
||||
```nix
|
||||
|
||||
```
|
||||
|
||||
## Options Reference {#blocks-sops-options}
|
||||
|
||||
```{=include=} options
|
||||
id-prefix: blocks-sops-options-
|
||||
list-id: selfhostblocks-block-sops-options
|
||||
source: @OPTIONS_JSON@
|
||||
```
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# SSL Generator Block {#ssl-block}
|
||||
# SSL Generator Block {#block-ssl}
|
||||
|
||||
This NixOS module is a block that implements the [SSL certificate generator](contracts-ssl.html) contract.
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ It is implemented by:
|
|||
[11]: blocks-ssl.html#blocks-ssl-options-shb.certs.certs.selfsigned
|
||||
[12]: blocks-ssl.html#blocks-ssl-options-shb.certs.certs.letsencrypt
|
||||
|
||||
## Self-Signed Certificates {#ssl-block-impl-self-signed}
|
||||
## Self-Signed Certificates {#block-ssl-impl-self-signed}
|
||||
|
||||
Defined in [`/modules/blocks/ssl.nix`](@REPO@/modules/blocks/ssl.nix).
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ shb.certs.certs.selfsigned = {
|
|||
The group has been chosen to be `nginx` to be consistent with the examples further down in this
|
||||
document.
|
||||
|
||||
## Let's Encrypt {#ssl-block-impl-lets-encrypt}
|
||||
## Let's Encrypt {#block-ssl-impl-lets-encrypt}
|
||||
|
||||
Defined in [`/modules/blocks/ssl.nix`](@REPO@/modules/blocks/ssl.nix).
|
||||
|
||||
|
|
@ -81,7 +81,7 @@ LINODE_TOKEN=XYZ...
|
|||
|
||||
For other providers, see the [official instruction](https://go-acme.github.io/lego/dns/).
|
||||
|
||||
## Usage {#ssl-block-usage}
|
||||
## Usage {#block-ssl-usage}
|
||||
|
||||
To use either a self-signed certificates or a Let's Encrypt generated one, we can reference the path
|
||||
where the certificate and the private key are located:
|
||||
|
|
@ -110,16 +110,16 @@ config.shb.certs.systemdService
|
|||
See also the [SSL certificate generator usage](contracts-ssl.html#ssl-contract-usage) for a more detailed usage
|
||||
example.
|
||||
|
||||
## Debug {#ssl-block-debug}
|
||||
## Debug {#block-ssl-debug}
|
||||
|
||||
Each CA and Cert is generated by a systemd service whose name can be seen in the `systemdService`
|
||||
option. You can then see the latest errors messages using `journalctl`.
|
||||
|
||||
## Tests {#ssl-block-tests}
|
||||
## Tests {#block-ssl-tests}
|
||||
|
||||
The self-signed implementation is tested in [`/tests/vm/ssl.nix`](@REPO@/tests/vm/ssl.nix).
|
||||
|
||||
## Options Reference {#ssl-block-options}
|
||||
## Options Reference {#block-ssl-options}
|
||||
|
||||
```{=include=} options
|
||||
id-prefix: blocks-ssl-options-
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{ lib, ... }:
|
||||
let
|
||||
inherit (lib) mkOption;
|
||||
inherit (lib.types) anything listOf nonEmptyListOf submodule str;
|
||||
inherit (lib.types) listOf nonEmptyListOf submodule str;
|
||||
in
|
||||
{
|
||||
request = submodule {
|
||||
|
|
@ -9,8 +9,6 @@ in
|
|||
user = mkOption {
|
||||
description = ''
|
||||
Unix user doing the backups.
|
||||
|
||||
Most of the time, this should be the user owning the files.
|
||||
'';
|
||||
type = str;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -101,6 +101,6 @@ Or with another module `backupservice_2`!
|
|||
- <!-- [ -->Home Assistant<!--](services-home-assistant.html). --> (no manual yet)
|
||||
- <!-- [ -->Jellyfin<!--](services-jellyfin.html). --> (no manual yet)
|
||||
- <!-- [ -->LLDAP<!--](blocks-ldap.html). --> (no manual yet)
|
||||
- [Nextcloud](services-nextcloud.html#services-nextcloud-server-usage-backup).
|
||||
- [Nextcloud](services-nextcloud.html#services-nextcloudserver-usage-backup).
|
||||
- [Vaultwarden](services-vaultwarden.html#services-vaultwarden-backup).
|
||||
- <!-- [ -->*arr<!--](services-arr.html). --> (no manual yet)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ in
|
|||
"/opt/files/B"
|
||||
],
|
||||
settings, # { repository, config } -> attrset
|
||||
extraConfig ? null, # { username } -> attrset
|
||||
extraConfig ? null, # { username, config } -> attrset
|
||||
}: pkgs.testers.runNixOSTest {
|
||||
inherit name;
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ in
|
|||
group = "root";
|
||||
};
|
||||
})
|
||||
(optionalAttrs (extraConfig != null) (extraConfig { inherit username; }))
|
||||
(optionalAttrs (extraConfig != null) (extraConfig { inherit username config; }))
|
||||
];
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -9,11 +9,10 @@ in
|
|||
{ name,
|
||||
requesterRoot,
|
||||
providerRoot,
|
||||
extraConfig ? null, # { username, database } -> attrset
|
||||
extraConfig ? null, # { config, database } -> attrset
|
||||
modules ? [],
|
||||
username ? "me",
|
||||
database ? "me",
|
||||
settings, # repository -> attrset
|
||||
settings, # { repository, config } -> attrset
|
||||
}: pkgs.testers.runNixOSTest {
|
||||
inherit name;
|
||||
|
||||
|
|
@ -22,16 +21,19 @@ in
|
|||
config = lib.mkMerge [
|
||||
(setAttrByPath providerRoot {
|
||||
request = (getAttrFromPath requesterRoot config).databasebackup;
|
||||
settings = settings "/opt/repos/database";
|
||||
settings = settings {
|
||||
inherit config;
|
||||
repository = "/opt/repos/database";
|
||||
};
|
||||
})
|
||||
(mkIf (username != "root") {
|
||||
users.users.${username} = {
|
||||
(mkIf (database != "root") {
|
||||
users.users.${database} = {
|
||||
isSystemUser = true;
|
||||
extraGroups = [ "sudoers" ];
|
||||
group = "root";
|
||||
};
|
||||
})
|
||||
(optionalAttrs (extraConfig != null) (extraConfig { inherit username database; }))
|
||||
(optionalAttrs (extraConfig != null) (extraConfig { inherit config database; }))
|
||||
];
|
||||
};
|
||||
|
||||
|
|
@ -45,7 +47,7 @@ in
|
|||
machine.wait_for_open_port(5432)
|
||||
|
||||
def peer_cmd(cmd, db="me"):
|
||||
return "sudo -u me psql -U me {db} --csv --command \"{cmd}\"".format(cmd=cmd, db=db)
|
||||
return "sudo -u ${database} psql -U ${database} {db} --csv --command \"{cmd}\"".format(cmd=cmd, db=db)
|
||||
|
||||
def query(query):
|
||||
res = machine.succeed(peer_cmd(query))
|
||||
|
|
@ -68,10 +70,11 @@ in
|
|||
|
||||
with subtest("backup"):
|
||||
print(machine.succeed("systemctl cat ${provider.backupService}"))
|
||||
print(machine.succeed("ls -l /run/hardcodedsecrets/hardcodedsecret_passphrase"))
|
||||
machine.succeed("systemctl start ${provider.backupService}")
|
||||
|
||||
with subtest("drop database"):
|
||||
machine.succeed(peer_cmd("DROP DATABASE me", db="postgres"))
|
||||
machine.succeed(peer_cmd("DROP DATABASE ${database}", db="postgres"))
|
||||
machine.fail(peer_cmd("SELECT * FROM test"))
|
||||
|
||||
with subtest("restore"):
|
||||
|
|
|
|||
|
|
@ -1,9 +1,48 @@
|
|||
{ pkgs, lib }:
|
||||
let
|
||||
inherit (lib) mkOption optionalAttrs;
|
||||
inherit (lib.types) anything;
|
||||
|
||||
mkContractFunctions =
|
||||
{ mkRequest,
|
||||
mkResult,
|
||||
}: {
|
||||
mkRequester = requestCfg: {
|
||||
request = mkRequest requestCfg;
|
||||
|
||||
result = mkResult {};
|
||||
};
|
||||
|
||||
mkProvider =
|
||||
{ resultCfg,
|
||||
settings ? {},
|
||||
}: {
|
||||
request = mkRequest {};
|
||||
|
||||
result = mkResult resultCfg;
|
||||
} // optionalAttrs (settings != {}) { inherit settings; };
|
||||
|
||||
contract = {
|
||||
request = mkRequest {};
|
||||
|
||||
result = mkResult {};
|
||||
|
||||
settings = mkOption {
|
||||
description = ''
|
||||
Optional attribute set with options specific to the provider.
|
||||
'';
|
||||
type = anything;
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
inherit mkContractFunctions;
|
||||
|
||||
databasebackup = import ./databasebackup.nix { inherit lib; };
|
||||
backup = import ./backup.nix { inherit lib; };
|
||||
mount = import ./mount.nix { inherit lib; };
|
||||
secret = import ./secret.nix { inherit lib; };
|
||||
secret = import ./secret.nix { inherit pkgs lib; };
|
||||
ssl = import ./ssl.nix { inherit lib; };
|
||||
test = {
|
||||
secret = import ./secret/test.nix { inherit pkgs lib; };
|
||||
|
|
|
|||
|
|
@ -1,114 +1,115 @@
|
|||
{ lib, ... }:
|
||||
{
|
||||
mkOption =
|
||||
{ description,
|
||||
mode ? "0400",
|
||||
{ pkgs, lib, ... }:
|
||||
let
|
||||
inherit (lib) concatStringsSep literalMD mkOption optionalAttrs optionalString;
|
||||
inherit (lib.types) anything listOf submodule str;
|
||||
|
||||
contractsLib = import ./default.nix { inherit pkgs lib; };
|
||||
|
||||
mkRequest =
|
||||
{ mode ? "0400",
|
||||
owner ? "root",
|
||||
ownerText ? null,
|
||||
group ? "root",
|
||||
restartUnits ? [],
|
||||
restartUnitsText ? null,
|
||||
}: lib.mkOption {
|
||||
inherit description;
|
||||
}: mkOption {
|
||||
description = ''
|
||||
Request part of the secret contract.
|
||||
|
||||
type = lib.types.submodule {
|
||||
Options set by the requester module
|
||||
enforcing some properties the secret should have.
|
||||
'';
|
||||
|
||||
default = {
|
||||
inherit mode owner group restartUnits;
|
||||
};
|
||||
|
||||
defaultText = optionalString (ownerText != null || restartUnitsText != null) (literalMD ''
|
||||
{
|
||||
mode = ${mode};
|
||||
owner = ${if ownerText != null then ownerText else owner};
|
||||
group = ${group};
|
||||
restartUnits = ${if restartUnitsText != null then restartUnitsText else "[ " + concatStringsSep " " restartUnits + " ]"};
|
||||
}
|
||||
'');
|
||||
|
||||
type = submodule {
|
||||
options = {
|
||||
request = lib.mkOption {
|
||||
default = {
|
||||
inherit mode owner group restartUnits;
|
||||
};
|
||||
|
||||
defaultText = lib.optionalString (ownerText != null || restartUnitsText != null) (lib.literalMD ''
|
||||
{
|
||||
mode = ${mode};
|
||||
owner = ${if ownerText != null then ownerText else owner};
|
||||
group = ${group};
|
||||
restartUnits = ${if restartUnitsText != null then restartUnitsText else "[ " + lib.concatStringsSep " " restartUnits + " ]"};
|
||||
}
|
||||
'');
|
||||
|
||||
readOnly = true;
|
||||
|
||||
mode = mkOption {
|
||||
description = ''
|
||||
Options set by the requester module
|
||||
enforcing some properties the secret should have.
|
||||
|
||||
Use the `contracts.secret.mkOption` function to
|
||||
create a secret option for a requester module.
|
||||
See the [requester usage section](contracts-secret.html#secret-contract-usage-requester) for an example.
|
||||
|
||||
Some providers will need more options to be defined and this is allowed.
|
||||
These extra options will be set by the user.
|
||||
For example, the `sops` implementation requires to be given
|
||||
the sops key in which the secret is encrypted.
|
||||
|
||||
`request` options are set read-only
|
||||
because they must be set through option defaults,
|
||||
they shouldn't be changed in the `config` section.
|
||||
This would otherwise lead to infinite recursion
|
||||
during evaluation.
|
||||
This is handled automatically when using the `contracts.secret.mkOption` function.
|
||||
Mode of the secret file.
|
||||
'';
|
||||
type = lib.types.submodule {
|
||||
freeformType = lib.types.anything;
|
||||
|
||||
options = {
|
||||
mode = lib.mkOption {
|
||||
description = ''
|
||||
Mode of the secret file.
|
||||
'';
|
||||
type = lib.types.str;
|
||||
default = mode;
|
||||
};
|
||||
|
||||
owner = lib.mkOption {
|
||||
description = ''
|
||||
Linux user owning the secret file.
|
||||
'';
|
||||
type = lib.types.str;
|
||||
default = owner;
|
||||
defaultText = if ownerText != null then lib.literalMD ownerText else null;
|
||||
};
|
||||
|
||||
group = lib.mkOption {
|
||||
description = ''
|
||||
Linux group owning the secret file.
|
||||
'';
|
||||
type = lib.types.str;
|
||||
default = group;
|
||||
};
|
||||
|
||||
restartUnits = lib.mkOption {
|
||||
description = ''
|
||||
Systemd units to restart after the secret is updated.
|
||||
'';
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = restartUnits;
|
||||
defaultText = if restartUnitsText != null then lib.literalMD restartUnitsText else null;
|
||||
};
|
||||
};
|
||||
};
|
||||
type = str;
|
||||
default = mode;
|
||||
};
|
||||
|
||||
result = lib.mkOption {
|
||||
owner = mkOption ({
|
||||
description = ''
|
||||
Options set by the provider module that indicates where the secret can be found.
|
||||
Linux user owning the secret file.
|
||||
'';
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
path = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
description = ''
|
||||
Path to the file containing the secret generated out of band.
|
||||
type = str;
|
||||
default = owner;
|
||||
} // optionalAttrs (ownerText != null) {
|
||||
defaultText = literalMD ownerText;
|
||||
});
|
||||
|
||||
This path will exist after deploying to a target host,
|
||||
it is not available through the nix store.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
group = mkOption {
|
||||
description = ''
|
||||
Linux group owning the secret file.
|
||||
'';
|
||||
type = str;
|
||||
default = group;
|
||||
};
|
||||
|
||||
restartUnits = mkOption ({
|
||||
description = ''
|
||||
Systemd units to restart after the secret is updated.
|
||||
'';
|
||||
type = listOf str;
|
||||
default = restartUnits;
|
||||
} // optionalAttrs (restartUnitsText != null) {
|
||||
defaultText = literalMD restartUnitsText;
|
||||
});
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
mkResult =
|
||||
{
|
||||
path ? "/run/secrets/secret",
|
||||
pathText ? null,
|
||||
}:
|
||||
mkOption ({
|
||||
description = ''
|
||||
Result part of the secret contract.
|
||||
|
||||
Options set by the provider module that indicates where the secret can be found.
|
||||
'';
|
||||
default = {
|
||||
inherit path;
|
||||
};
|
||||
type = submodule {
|
||||
options = {
|
||||
path = mkOption {
|
||||
type = lib.types.path;
|
||||
description = ''
|
||||
Path to the file containing the secret generated out of band.
|
||||
|
||||
This path will exist after deploying to a target host,
|
||||
it is not available through the nix store.
|
||||
'';
|
||||
default = path;
|
||||
} // optionalAttrs (pathText != null) {
|
||||
defaultText = pathText;
|
||||
};
|
||||
};
|
||||
};
|
||||
} // optionalAttrs (pathText != null) {
|
||||
defaultText = {
|
||||
path = pathText;
|
||||
};
|
||||
});
|
||||
in
|
||||
contractsLib.mkContractFunctions {
|
||||
inherit mkRequest mkResult;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Secret Contract {#secret-contract}
|
||||
# Secret Contract {#contract-secret}
|
||||
|
||||
This NixOS contract represents a secret file
|
||||
that must be created out of band - from outside the nix store -
|
||||
|
|
@ -7,7 +7,7 @@ and that must be placed in an expected location with expected permission.
|
|||
More formally, this contract is made between a requester module - the one needing a secret -
|
||||
and a provider module - the one creating the secret and making it available.
|
||||
|
||||
## Problem Statement {#secret-contract-problem}
|
||||
## Motivation {#contract-secret-motivation}
|
||||
|
||||
Let's provide the [ldap SHB module][ldap-module] option `ldapUserPasswordFile`
|
||||
with a secret managed by [sops-nix][].
|
||||
|
|
@ -19,14 +19,14 @@ Without the secret contract, configuring the option would look like so:
|
|||
|
||||
```nix
|
||||
sops.secrets."ldap/user_password" = {
|
||||
sopsFile = ./secrets.yaml;
|
||||
mode = "0440";
|
||||
owner = "lldap";
|
||||
group = "lldap";
|
||||
restartUnits = [ "lldap.service" ];
|
||||
sopsFile = ./secrets.yaml;
|
||||
};
|
||||
|
||||
shb.ldap.ldapUserPasswordFile = config.sops.secrets."ldap/user_password".path;
|
||||
shb.ldap.userPassword.result = config.sops.secrets."ldap/user_password".result;
|
||||
```
|
||||
|
||||
The problem this contract intends to fix is how to ensure
|
||||
|
|
@ -38,19 +38,21 @@ or more likely, they will need to figure it out by looking
|
|||
at the module source code.
|
||||
Not a great user experience.
|
||||
|
||||
Now, with this contract, the configuration becomes:
|
||||
Now, with this contract, a layer on top of `sops` is added which is found under `shb.sops`.
|
||||
The configuration then becomes:
|
||||
|
||||
```nix
|
||||
sops.secrets."ldap/user_password" = config.shb.ldap.secret.ldapUserPassword.request // {
|
||||
sopsFile = ./secrets.yaml;
|
||||
shb.sops.secrets."ldap/user_password" = {
|
||||
request = config.shb.ldap.userPassword.request;
|
||||
settings.sopsFile = ./secrets.yaml;
|
||||
};
|
||||
|
||||
shb.ldap.ldapUserPassword.result.path = config.sops.secrets."ldap/user_password".path;
|
||||
shb.ldap.userPassword.result = config.shb.sops.secrets."ldap/user_password".result;
|
||||
```
|
||||
|
||||
The issue is now gone at the expense of some plumbing.
|
||||
The module maintainer is now in charge of describing
|
||||
how the module expects the secret to be provided.
|
||||
The issue is now gone as the responsibility falls
|
||||
on the module maintainer
|
||||
for describing how the secret should be provided.
|
||||
|
||||
If taking advantage of the `sops.defaultSopsFile` option like so:
|
||||
|
||||
|
|
@ -61,12 +63,12 @@ sops.defaultSopsFile = ./secrets.yaml;
|
|||
Then the snippet above is even more simplified:
|
||||
|
||||
```nix
|
||||
sops.secrets."ldap/user_password" = config.shb.ldap.secret.ldapUserPassword.request;
|
||||
shb.sops.secrets."ldap/user_password".request = config.shb.ldap.userPassword.request;
|
||||
|
||||
shb.ldap.ldapUserPassword.result.path = config.sops.secrets."ldap/user_password".path;
|
||||
shb.ldap.userPassword.result = config.shb.sops.secrets."ldap/user_password".result;
|
||||
```
|
||||
|
||||
## Contract Reference {#secret-contract-options}
|
||||
## Contract Reference {#contract-secret-options}
|
||||
|
||||
These are all the options that are expected to exist for this contract to be respected.
|
||||
|
||||
|
|
@ -76,7 +78,7 @@ list-id: selfhostblocks-options
|
|||
source: @OPTIONS_JSON@
|
||||
```
|
||||
|
||||
## Usage {#secret-contract-usage}
|
||||
## Usage {#contract-secret-usage}
|
||||
|
||||
A contract involves 3 parties:
|
||||
|
||||
|
|
@ -86,24 +88,28 @@ A contract involves 3 parties:
|
|||
|
||||
The usage of this contract is similarly separated into 3 sections.
|
||||
|
||||
### Requester Module {#secret-contract-usage-requester}
|
||||
### Requester Module {#contract-secret-usage-requester}
|
||||
|
||||
Here is an example module requesting two secrets through the `secret` contract.
|
||||
|
||||
```nix
|
||||
{ config, ... }:
|
||||
let
|
||||
inherit (lib) mkOption;
|
||||
inherit (lib.types) submodule;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
myservice = lib.mkOption {
|
||||
type = lib.types.submodule {
|
||||
myservice = mkOption {
|
||||
type = submodule {
|
||||
options = {
|
||||
adminPassword = contracts.secret.mkOption {
|
||||
adminPassword = contracts.secret.mkRequester {
|
||||
owner = "myservice";
|
||||
group = "myservice";
|
||||
mode = "0440";
|
||||
restartUnits = [ "myservice.service" ];
|
||||
};
|
||||
databasePassword = contracts.secret.mkOption {
|
||||
databasePassword = contracts.secret.mkRequester {
|
||||
owner = "myservice";
|
||||
# group defaults to "root"
|
||||
# mode defaults to "0400"
|
||||
|
|
@ -122,7 +128,7 @@ Here is an example module requesting two secrets through the `secret` contract.
|
|||
};
|
||||
```
|
||||
|
||||
### Provider Module {#secret-contract-usage-provider}
|
||||
### Provider Module {#contract-secret-usage-provider}
|
||||
|
||||
Now, on the other side, we have a module that uses those options and provides a secret.
|
||||
Let's assume such a module is available under the `secretservice` option
|
||||
|
|
@ -130,63 +136,67 @@ and that one can create multiple instances.
|
|||
|
||||
```nix
|
||||
{ config, ... }:
|
||||
let
|
||||
inherit (lib) mkOption;
|
||||
inherit (lib.types) attrsOf submodule;
|
||||
|
||||
contracts = pkgs.callPackage ./contracts {};
|
||||
in
|
||||
{
|
||||
options = {
|
||||
secretservice = lib.mkOption {
|
||||
type = lib.types.attrsOf (lib.types.submodule {
|
||||
options = {
|
||||
mode = lib.mkOption {
|
||||
description = "Mode of the secret file.";
|
||||
type = lib.types.str;
|
||||
};
|
||||
options.secretservice.secret = mkOption {
|
||||
description = "Secret following the secret contract.";
|
||||
default = {};
|
||||
type = attrsOf (submodule ({ name, options, ... }: {
|
||||
options = contracts.secret.mkProvider {
|
||||
settings = mkOption {
|
||||
description = ''
|
||||
Settings specific to the secrets provider.
|
||||
'';
|
||||
|
||||
owner = lib.mkOption {
|
||||
description = "Linux user owning the secret file.";
|
||||
type = lib.types.str;
|
||||
};
|
||||
|
||||
group = lib.mkOption {
|
||||
description = "Linux group owning the secret file.";
|
||||
type = lib.types.str;
|
||||
};
|
||||
|
||||
restartUnits = lib.mkOption {
|
||||
description = "Systemd units to restart after the secret is updated.";
|
||||
type = lib.types.listOf lib.types.str;
|
||||
};
|
||||
|
||||
path = lib.mkOption {
|
||||
description = "Path where the secret file will be located.";
|
||||
type = lib.types.str;
|
||||
};
|
||||
|
||||
// The contract allows more options to be defined to accomodate specific implementations.
|
||||
secretFile = lib.mkOption {
|
||||
description = "File containing the encrypted secret.";
|
||||
type = lib.types.path;
|
||||
type = submodule {
|
||||
options = {
|
||||
secretFile = lib.mkOption {
|
||||
description = "File containing the encrypted secret.";
|
||||
type = lib.types.path;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
resultCfg = {
|
||||
path = "/run/secrets/${name}";
|
||||
pathText = "/run/secrets/<name>";
|
||||
};
|
||||
};
|
||||
}));
|
||||
};
|
||||
|
||||
config = {
|
||||
// ...
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### End User {#secret-contract-usage-enduser}
|
||||
### End User {#contract-secret-usage-enduser}
|
||||
|
||||
The end user's responsibility is now to do some plumbing.
|
||||
|
||||
They will setup the provider module - here `secretservice` - with the options set by the requester module,
|
||||
while also setting other necessary options to satisfy the provider service.
|
||||
And then they will give back the result to the requester module `myservice`.
|
||||
|
||||
```nix
|
||||
secretservice.adminPassword = myservice.secret.adminPassword.request // {
|
||||
secretFile = ./secret.yaml;
|
||||
secretservice.secret."adminPassword" = {
|
||||
request = myservice.adminPasswor".request;
|
||||
settings.secretFile = ./secret.yaml;
|
||||
};
|
||||
myservice.adminPassword.result = secretservice.secret."adminPassword".result;
|
||||
|
||||
secretservice.databasePassword = myservice.secret.databasePassword.request // {
|
||||
secretFile = ./secret.yaml;
|
||||
secretservice.secret."databasePassword" = {
|
||||
request = myservice.databasePassword.request;
|
||||
settings.secretFile = ./secret.yaml;
|
||||
};
|
||||
myservice.databasePassword.result = secretservice.service."databasePassword".result;
|
||||
```
|
||||
|
||||
Assuming the `secretservice` module accepts default options,
|
||||
|
|
@ -195,14 +205,13 @@ the above snippet could be reduced to:
|
|||
```nix
|
||||
secretservice.default.secretFile = ./secret.yaml;
|
||||
|
||||
secretservice.adminPassword = myservice.secret.adminPassword.request;
|
||||
secretservice.databasePassword = myservice.secret.databasePassword.request;
|
||||
secretservice.secret."adminPassword".request = myservice.adminPasswor".request;
|
||||
myservice.adminPassword.result = secretservice.secret."adminPassword".result;
|
||||
|
||||
secretservice.secret."databasePassword".request = myservice.databasePassword.request;
|
||||
myservice.databasePassword.result = secretservice.service."databasePassword".result;
|
||||
```
|
||||
|
||||
Then they will setup the requester module - here `myservice` - with the result of the provider module.
|
||||
|
||||
```nix
|
||||
myservice.secret.adminPassword.result.path = secretservice.adminPassword.result.path;
|
||||
|
||||
myservice.secret.databasePassword.result.path = secretservice.adminPassword.result.path;
|
||||
```
|
||||
The plumbing of request from the requester to the provider
|
||||
and then the result from the provider back to the requester
|
||||
is quite explicit in this snippet.
|
||||
|
|
|
|||
|
|
@ -1,21 +1,27 @@
|
|||
{ pkgs, lib, ... }:
|
||||
let
|
||||
contracts = pkgs.callPackage ../. {};
|
||||
|
||||
inherit (lib) mkOption;
|
||||
inherit (lib.types) submodule;
|
||||
in
|
||||
{
|
||||
options.shb.contracts.secret = contracts.secret.mkOption {
|
||||
options.shb.contracts.secret = mkOption {
|
||||
description = ''
|
||||
Contract for secrets between a requester module
|
||||
and a provider module.
|
||||
|
||||
The requester communicates to the provider
|
||||
some properties the secret should have
|
||||
through the `request` options.
|
||||
through the `request.*` options.
|
||||
|
||||
The provider reads from the `request` options
|
||||
The provider reads from the `request.*` options
|
||||
and creates the secret as requested.
|
||||
It then communicates to the requester where the secret can be found
|
||||
through the `result` options.
|
||||
through the `result.*` options.
|
||||
'';
|
||||
type = submodule {
|
||||
options = contracts.secret.contract;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ let
|
|||
in
|
||||
{ name,
|
||||
configRoot,
|
||||
createContent, # config to create a secret with value "secretA".
|
||||
settingsCfg, # str -> attrset
|
||||
modules ? [],
|
||||
owner ? "root",
|
||||
group ? "root",
|
||||
|
|
@ -23,8 +23,11 @@ in
|
|||
config = lib.mkMerge [
|
||||
(setAttrByPath configRoot {
|
||||
A = {
|
||||
inherit owner group mode restartUnits;
|
||||
} // createContent;
|
||||
request = {
|
||||
inherit owner group mode restartUnits;
|
||||
};
|
||||
settings = settingsCfg "secretA";
|
||||
};
|
||||
})
|
||||
(mkIf (owner != "root") {
|
||||
users.users.${owner}.isNormalUser = true;
|
||||
|
|
@ -37,26 +40,26 @@ in
|
|||
|
||||
testScript = { nodes, ... }:
|
||||
let
|
||||
cfg = (getAttrFromPath configRoot nodes.machine)."A";
|
||||
result = (getAttrFromPath configRoot nodes.machine)."A".result;
|
||||
in
|
||||
''
|
||||
owner = machine.succeed("stat -c '%U' ${cfg.path}").strip()
|
||||
owner = machine.succeed("stat -c '%U' ${result.path}").strip()
|
||||
print(f"Got owner {owner}")
|
||||
if owner != "${owner}":
|
||||
raise Exception(f"Owner should be '${owner}' but got '{owner}'")
|
||||
|
||||
group = machine.succeed("stat -c '%G' ${cfg.path}").strip()
|
||||
group = machine.succeed("stat -c '%G' ${result.path}").strip()
|
||||
print(f"Got group {group}")
|
||||
if group != "${group}":
|
||||
raise Exception(f"Group should be '${group}' but got '{group}'")
|
||||
|
||||
mode = str(int(machine.succeed("stat -c '%a' ${cfg.path}").strip()))
|
||||
mode = str(int(machine.succeed("stat -c '%a' ${result.path}").strip()))
|
||||
print(f"Got mode {mode}")
|
||||
wantedMode = str(int("${mode}"))
|
||||
if mode != wantedMode:
|
||||
raise Exception(f"Mode should be '{wantedMode}' but got '{mode}'")
|
||||
|
||||
content = machine.succeed("cat ${cfg.path}").strip()
|
||||
content = machine.succeed("cat ${result.path}").strip()
|
||||
print(f"Got content {content}")
|
||||
if content != "secretA":
|
||||
raise Exception(f"Content should be 'secretA' but got '{content}'")
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
# SSL Generator Contract {#ssl-contract}
|
||||
# SSL Generator Contract {#contract-ssl}
|
||||
|
||||
This NixOS contract represents an SSL certificate generator. This contract is used to decouple
|
||||
generating an SSL certificate from using it. In practice, you can swap generators without updating
|
||||
modules depending on it.
|
||||
|
||||
## Contract Reference {#ssl-contract-options}
|
||||
## Contract Reference {#contract-ssl-options}
|
||||
|
||||
These are all the options that are expected to exist for this contract to be respected.
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ list-id: selfhostblocks-options
|
|||
source: @OPTIONS_JSON@
|
||||
```
|
||||
|
||||
## Usage {#ssl-contract-usage}
|
||||
## Usage {#contract-ssl-usage}
|
||||
|
||||
Let's assume a module implementing this contract is available under the `ssl` variable:
|
||||
|
||||
|
|
@ -56,11 +56,11 @@ systemd.services.nginx = {
|
|||
};
|
||||
```
|
||||
|
||||
## Provided Implementations {#ssl-contract-impl-shb}
|
||||
## Provided Implementations {#contract-ssl-impl-shb}
|
||||
|
||||
Multiple implementation are provided out of the box at [SSL block](blocks-ssl.html).
|
||||
|
||||
## Custom Implementation {#ssl-contract-impl-custom}
|
||||
## Custom Implementation {#contract-ssl-impl-custom}
|
||||
|
||||
To implement this contract, you must create a module that respects this contract. The following
|
||||
snippet shows an example.
|
||||
|
|
|
|||
|
|
@ -4,13 +4,16 @@ let
|
|||
cfg = config.shb.forgejo;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
|
||||
inherit (lib) getExe lists literalExpression mkBefore mkEnableOption mkForce mkIf mkMerge mkOption mkOverride optionals;
|
||||
inherit (lib.types) bool enum listOf nullOr package port submodule str;
|
||||
in
|
||||
{
|
||||
options.shb.forgejo = {
|
||||
enable = lib.mkEnableOption "selfhostblocks.forgejo";
|
||||
enable = mkEnableOption "selfhostblocks.forgejo";
|
||||
|
||||
subdomain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
subdomain = mkOption {
|
||||
type = str;
|
||||
description = ''
|
||||
Subdomain under which Forgejo will be served.
|
||||
|
||||
|
|
@ -21,7 +24,7 @@ in
|
|||
example = "forgejo";
|
||||
};
|
||||
|
||||
domain = lib.mkOption {
|
||||
domain = mkOption {
|
||||
description = ''
|
||||
Domain under which Forgejo is served.
|
||||
|
||||
|
|
@ -29,75 +32,79 @@ in
|
|||
<subdomain>.<domain>[:<port>]
|
||||
```
|
||||
'';
|
||||
type = lib.types.str;
|
||||
type = str;
|
||||
example = "domain.com";
|
||||
};
|
||||
|
||||
ssl = lib.mkOption {
|
||||
ssl = mkOption {
|
||||
description = "Path to SSL files";
|
||||
type = lib.types.nullOr contracts.ssl.certs;
|
||||
type = nullOr contracts.ssl.certs;
|
||||
default = null;
|
||||
};
|
||||
|
||||
ldap = lib.mkOption {
|
||||
ldap = mkOption {
|
||||
description = ''
|
||||
LDAP Integration.
|
||||
'';
|
||||
default = {};
|
||||
type = lib.types.nullOr (lib.types.submodule {
|
||||
type = nullOr (submodule {
|
||||
options = {
|
||||
enable = lib.mkEnableOption "LDAP integration.";
|
||||
enable = mkEnableOption "LDAP integration.";
|
||||
|
||||
provider = lib.mkOption {
|
||||
type = lib.types.enum [ "LLDAP" ];
|
||||
provider = mkOption {
|
||||
type = enum [ "LLDAP" ];
|
||||
description = "LDAP provider name, used for display.";
|
||||
default = "LLDAP";
|
||||
};
|
||||
|
||||
host = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
host = mkOption {
|
||||
type = str;
|
||||
description = ''
|
||||
Host serving the LDAP server.
|
||||
'';
|
||||
default = "127.0.0.1";
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
port = mkOption {
|
||||
type = port;
|
||||
description = ''
|
||||
Port of the service serving the LDAP server.
|
||||
'';
|
||||
default = 389;
|
||||
};
|
||||
|
||||
dcdomain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
dcdomain = mkOption {
|
||||
type = str;
|
||||
description = "dc domain for ldap.";
|
||||
example = "dc=mydomain,dc=com";
|
||||
};
|
||||
|
||||
adminName = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
adminName = mkOption {
|
||||
type = str;
|
||||
description = "Admin user of the LDAP server.";
|
||||
default = "admin";
|
||||
};
|
||||
|
||||
adminPassword = contracts.secret.mkOption {
|
||||
adminPassword = mkOption {
|
||||
description = "LDAP admin password.";
|
||||
mode = "0440";
|
||||
owner = "forgejo";
|
||||
group = "forgejo";
|
||||
restartUnits = [ "forgejo.service" ];
|
||||
type = submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
mode = "0440";
|
||||
owner = "forgejo";
|
||||
group = "forgejo";
|
||||
restartUnits = [ "forgejo.service" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
userGroup = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
userGroup = mkOption {
|
||||
type = str;
|
||||
description = "Group users must belong to be able to login.";
|
||||
default = "forgejo_user";
|
||||
};
|
||||
|
||||
adminGroup = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
adminGroup = mkOption {
|
||||
type = str;
|
||||
description = "Group users must belong to be admins.";
|
||||
default = "forgejo_admin";
|
||||
};
|
||||
|
|
@ -105,81 +112,97 @@ in
|
|||
});
|
||||
};
|
||||
|
||||
sso = lib.mkOption {
|
||||
sso = mkOption {
|
||||
description = ''
|
||||
Setup SSO integration.
|
||||
'';
|
||||
default = {};
|
||||
type = lib.types.submodule {
|
||||
type = submodule {
|
||||
options = {
|
||||
enable = lib.mkEnableOption "SSO integration.";
|
||||
enable = mkEnableOption "SSO integration.";
|
||||
|
||||
provider = lib.mkOption {
|
||||
type = lib.types.enum [ "Authelia" ];
|
||||
provider = mkOption {
|
||||
type = enum [ "Authelia" ];
|
||||
description = "OIDC provider name, used for display.";
|
||||
default = "Authelia";
|
||||
};
|
||||
|
||||
endpoint = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
endpoint = mkOption {
|
||||
type = str;
|
||||
description = "OIDC endpoint for SSO.";
|
||||
example = "https://authelia.example.com";
|
||||
};
|
||||
|
||||
clientID = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
clientID = mkOption {
|
||||
type = str;
|
||||
description = "Client ID for the OIDC endpoint.";
|
||||
default = "forgejo";
|
||||
};
|
||||
|
||||
authorization_policy = lib.mkOption {
|
||||
type = lib.types.enum [ "one_factor" "two_factor" ];
|
||||
authorization_policy = mkOption {
|
||||
type = enum [ "one_factor" "two_factor" ];
|
||||
description = "Require one factor (password) or two factor (device) authentication.";
|
||||
default = "one_factor";
|
||||
};
|
||||
|
||||
sharedSecret = contracts.secret.mkOption {
|
||||
sharedSecret = mkOption {
|
||||
description = "OIDC shared secret for Forgejo.";
|
||||
mode = "0440";
|
||||
owner = "forgejo";
|
||||
group = "forgejo";
|
||||
restartUnits = [ "forgejo.service" ];
|
||||
type = submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
mode = "0440";
|
||||
owner = "forgejo";
|
||||
group = "forgejo";
|
||||
restartUnits = [ "forgejo.service" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
sharedSecretForAuthelia = contracts.secret.mkOption {
|
||||
sharedSecretForAuthelia = mkOption {
|
||||
description = "OIDC shared secret for Authelia.";
|
||||
mode = "0400";
|
||||
owner = "authelia";
|
||||
type = submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
mode = "0400";
|
||||
owner = "authelia";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
adminPassword = contracts.secret.mkOption {
|
||||
adminPassword = mkOption {
|
||||
description = "File containing the Forgejo admin user password.";
|
||||
mode = "0440";
|
||||
owner = "forgejo";
|
||||
group = "forgejo";
|
||||
restartUnits = [ "forgejo.service" ];
|
||||
type = submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
mode = "0440";
|
||||
owner = "forgejo";
|
||||
group = "forgejo";
|
||||
restartUnits = [ "forgejo.service" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
databasePassword = contracts.secret.mkOption {
|
||||
databasePassword = mkOption {
|
||||
description = "File containing the Forgejo database password.";
|
||||
mode = "0440";
|
||||
owner = "forgejo";
|
||||
group = "forgejo";
|
||||
restartUnits = [ "forgejo.service" ];
|
||||
type = submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
mode = "0440";
|
||||
owner = "forgejo";
|
||||
group = "forgejo";
|
||||
restartUnits = [ "forgejo.service" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
repositoryRoot = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
repositoryRoot = mkOption {
|
||||
type = nullOr str;
|
||||
description = "Path where to store the repositories. If null, uses the default under the Forgejo StateDir.";
|
||||
default = null;
|
||||
example = "/srv/forgejo";
|
||||
};
|
||||
|
||||
localActionRunner = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
localActionRunner = mkOption {
|
||||
type = bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Enable local action runner that runs for all labels.
|
||||
|
|
@ -187,8 +210,8 @@ in
|
|||
};
|
||||
|
||||
|
||||
hostPackages = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.package;
|
||||
hostPackages = mkOption {
|
||||
type = listOf package;
|
||||
default = with pkgs; [
|
||||
bash
|
||||
coreutils
|
||||
|
|
@ -199,7 +222,7 @@ in
|
|||
nodejs
|
||||
wget
|
||||
];
|
||||
defaultText = lib.literalExpression ''
|
||||
defaultText = literalExpression ''
|
||||
with pkgs; [
|
||||
bash
|
||||
coreutils
|
||||
|
|
@ -217,7 +240,7 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
backup = lib.mkOption {
|
||||
backup = mkOption {
|
||||
type = contracts.backup.request;
|
||||
description = ''
|
||||
Backup configuration. This is an output option.
|
||||
|
|
@ -239,13 +262,13 @@ in
|
|||
user = options.services.forgejo.user.value;
|
||||
sourceDirectories = [
|
||||
options.services.forgejo.dump.backupDir.value
|
||||
] ++ lib.optionals (cfg.repositoryRoot != null) [
|
||||
] ++ optionals (cfg.repositoryRoot != null) [
|
||||
cfg.repositoryRoot
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
mount = lib.mkOption {
|
||||
mount = mkOption {
|
||||
type = contracts.mount;
|
||||
description = ''
|
||||
Mount configuration. This is an output option.
|
||||
|
|
@ -263,51 +286,51 @@ in
|
|||
default = { path = config.services.forgejo.stateDir; };
|
||||
};
|
||||
|
||||
smtp = lib.mkOption {
|
||||
smtp = mkOption {
|
||||
description = ''
|
||||
Send notifications by smtp.
|
||||
'';
|
||||
default = null;
|
||||
type = lib.types.nullOr (lib.types.submodule {
|
||||
type = nullOr (submodule {
|
||||
options = {
|
||||
from_address = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
from_address = mkOption {
|
||||
type = str;
|
||||
description = "SMTP address from which the emails originate.";
|
||||
example = "authelia@mydomain.com";
|
||||
};
|
||||
host = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
host = mkOption {
|
||||
type = str;
|
||||
description = "SMTP host to send the emails to.";
|
||||
};
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
port = mkOption {
|
||||
type = port;
|
||||
description = "SMTP port to send the emails to.";
|
||||
default = 25;
|
||||
};
|
||||
username = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
username = mkOption {
|
||||
type = str;
|
||||
description = "Username to connect to the SMTP host.";
|
||||
};
|
||||
passwordFile = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
passwordFile = mkOption {
|
||||
type = str;
|
||||
description = "File containing the password to connect to the SMTP host.";
|
||||
};
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
debug = lib.mkOption {
|
||||
debug = mkOption {
|
||||
description = "Enable debug logging.";
|
||||
type = lib.types.bool;
|
||||
type = bool;
|
||||
default = false;
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkMerge [
|
||||
(lib.mkIf cfg.enable {
|
||||
config = mkMerge [
|
||||
(mkIf cfg.enable {
|
||||
services.forgejo = {
|
||||
enable = true;
|
||||
repositoryRoot = lib.mkIf (cfg.repositoryRoot != null) cfg.repositoryRoot;
|
||||
repositoryRoot = mkIf (cfg.repositoryRoot != null) cfg.repositoryRoot;
|
||||
settings = {
|
||||
server = {
|
||||
DOMAIN = cfg.domain;
|
||||
|
|
@ -328,10 +351,10 @@ in
|
|||
};
|
||||
|
||||
# 1 lower than default, to solve conflict between shb.postgresql and nixpkgs' forgejo module.
|
||||
services.postgresql.enable = lib.mkOverride 999 true;
|
||||
services.postgresql.enable = mkOverride 999 true;
|
||||
|
||||
# https://github.com/NixOS/nixpkgs/issues/258371#issuecomment-2271967113
|
||||
systemd.services.forgejo.serviceConfig.Type = lib.mkForce "exec";
|
||||
systemd.services.forgejo.serviceConfig.Type = mkForce "exec";
|
||||
|
||||
shb.nginx.vhosts = [{
|
||||
inherit (cfg) domain subdomain ssl;
|
||||
|
|
@ -339,7 +362,7 @@ in
|
|||
}];
|
||||
})
|
||||
|
||||
(lib.mkIf cfg.enable {
|
||||
(mkIf cfg.enable {
|
||||
services.forgejo.database = {
|
||||
type = "postgres";
|
||||
|
||||
|
|
@ -347,7 +370,7 @@ in
|
|||
};
|
||||
})
|
||||
|
||||
(lib.mkIf cfg.enable {
|
||||
(mkIf cfg.enable {
|
||||
services.forgejo.dump = {
|
||||
enable = true;
|
||||
type = "tar.gz";
|
||||
|
|
@ -358,12 +381,12 @@ in
|
|||
# For Forgejo setup: https://github.com/lldap/lldap/blob/main/example_configs/gitea.md
|
||||
# For cli info: https://docs.gitea.com/usage/command-line
|
||||
# Security protocols in: https://codeberg.org/forgejo/forgejo/src/branch/forgejo/services/auth/source/ldap/security_protocol.go#L27-L31
|
||||
(lib.mkIf (cfg.enable && cfg.ldap.enable != false) {
|
||||
(mkIf (cfg.enable && cfg.ldap.enable != false) {
|
||||
# The delimiter in the `cut` command is a TAB!
|
||||
systemd.services.forgejo.preStart = let
|
||||
provider = "SHB-${cfg.ldap.provider}";
|
||||
in ''
|
||||
auth="${lib.getExe config.services.forgejo.package} admin auth"
|
||||
auth="${getExe config.services.forgejo.package} admin auth"
|
||||
|
||||
echo "Trying to find existing ldap configuration for ${provider}"...
|
||||
set +e -o pipefail
|
||||
|
|
@ -417,7 +440,7 @@ in
|
|||
# For Authelia to Forgejo integration: https://www.authelia.com/integration/openid-connect/gitea/
|
||||
# For Forgejo config: https://forgejo.org/docs/latest/admin/config-cheat-sheet
|
||||
# For cli info: https://docs.gitea.com/usage/command-line
|
||||
(lib.mkIf (cfg.enable && cfg.sso.enable != false) {
|
||||
(mkIf (cfg.enable && cfg.sso.enable != false) {
|
||||
services.forgejo.settings = {
|
||||
oauth2 = {
|
||||
ENABLED = true;
|
||||
|
|
@ -430,7 +453,7 @@ in
|
|||
};
|
||||
|
||||
service = {
|
||||
# DISABLE_REGISTRATION = lib.mkForce false;
|
||||
# DISABLE_REGISTRATION = mkForce false;
|
||||
# ALLOW_ONLY_EXTERNAL_REGISTRATION = false;
|
||||
SHOW_REGISTRATION_BUTTON = false;
|
||||
};
|
||||
|
|
@ -440,7 +463,7 @@ in
|
|||
systemd.services.forgejo.preStart = let
|
||||
provider = "SHB-${cfg.sso.provider}";
|
||||
in ''
|
||||
auth="${lib.getExe config.services.forgejo.package} admin auth"
|
||||
auth="${getExe config.services.forgejo.package} admin auth"
|
||||
|
||||
echo "Trying to find existing sso configuration for ${provider}"...
|
||||
set +e -o pipefail
|
||||
|
|
@ -468,7 +491,7 @@ in
|
|||
fi
|
||||
'';
|
||||
|
||||
shb.authelia.oidcClients = lib.lists.optionals (!(isNull cfg.sso)) [
|
||||
shb.authelia.oidcClients = lists.optionals (!(isNull cfg.sso)) [
|
||||
(let
|
||||
provider = "SHB-${cfg.sso.provider}";
|
||||
in {
|
||||
|
|
@ -482,15 +505,15 @@ in
|
|||
];
|
||||
})
|
||||
|
||||
(lib.mkIf cfg.enable {
|
||||
(mkIf cfg.enable {
|
||||
systemd.services.forgejo.preStart = ''
|
||||
admin="${lib.getExe config.services.forgejo.package} admin user"
|
||||
admin="${getExe config.services.forgejo.package} admin user"
|
||||
$admin create --admin --email "root@localhost" --username meadmin --password "$(tr -d '\n' < ${cfg.adminPassword.result.path})" || true
|
||||
$admin change-password --username meadmin --password "$(tr -d '\n' < ${cfg.adminPassword.result.path})" || true
|
||||
'';
|
||||
})
|
||||
|
||||
(lib.mkIf (cfg.enable && cfg.smtp != null) {
|
||||
(mkIf (cfg.enable && cfg.smtp != null) {
|
||||
services.forgejo.settings.mailer = {
|
||||
ENABLED = true;
|
||||
SMTP_ADDR = "${cfg.smtp.host}:${toString cfg.smtp.port}";
|
||||
|
|
@ -502,13 +525,13 @@ in
|
|||
})
|
||||
|
||||
# https://wiki.nixos.org/wiki/Forgejo#Runner
|
||||
(lib.mkIf cfg.enable {
|
||||
(mkIf cfg.enable {
|
||||
services.forgejo.settings.actions = {
|
||||
ENABLED = true;
|
||||
DEFAULT_ACTIONS_URL = "github";
|
||||
};
|
||||
|
||||
services.gitea-actions-runner = lib.mkIf cfg.localActionRunner {
|
||||
services.gitea-actions-runner = mkIf cfg.localActionRunner {
|
||||
package = pkgs.forgejo-actions-runner;
|
||||
instances.local = {
|
||||
enable = true;
|
||||
|
|
@ -530,9 +553,9 @@ in
|
|||
|
||||
# This combined with the next statement takes care of
|
||||
# automatically registering a forgejo runner.
|
||||
systemd.services.forgejo.postStart = lib.mkIf cfg.localActionRunner (lib.mkBefore ''
|
||||
systemd.services.forgejo.postStart = mkIf cfg.localActionRunner (mkBefore ''
|
||||
${pkgs.bash}/bin/bash -c '(while ! ${pkgs.netcat-openbsd}/bin/nc -z -U ${config.services.forgejo.settings.server.HTTP_ADDR}; do echo "Waiting for unix ${config.services.forgejo.settings.server.HTTP_ADDR} to open..."; sleep 2; done); sleep 2'
|
||||
actions="${lib.getExe config.services.forgejo.package} actions"
|
||||
actions="${getExe config.services.forgejo.package} actions"
|
||||
echo -n TOKEN= > /run/forgejo/forgejo-runner-token
|
||||
$actions generate-runner-token >> /run/forgejo/forgejo-runner-token
|
||||
'');
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ LDAP and SSO integration as well as one local runner.
|
|||
- Declarative [local runner](#services-forgejo-options-shb.forgejo.localActionRunner) Configuration.
|
||||
- Access through [subdomain](#services-forgejo-options-shb.forgejo.subdomain) using reverse proxy. [Manual](#services-forgejo-usage-basic).
|
||||
- Access through [HTTPS](#services-forgejo-options-shb.forgejo.ssl) using reverse proxy. [Manual](#services-forgejo-usage-basic).
|
||||
- [Backup](#services-forgejo-options-shb.forgejo.sso) through the [backup block](./blocks-backup.html) with the . [Manual](#services-forgejo-usage-backup).
|
||||
- [Backup](#services-forgejo-options-shb.forgejo.sso) through the [backup block](./blocks-backup.html). [Manual](#services-forgejo-usage-backup).
|
||||
|
||||
## Usage {#services-forgejo-usage}
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ Then you can use that secret:
|
|||
shb.forgejo.adminPasswordFile = config.sops.secrets."forgejo/adminPasswordFile".path;
|
||||
```
|
||||
|
||||
### Forgejo through HTTP(S) {#services-forgejo-usage-basic}
|
||||
### Forgejo through HTTPS {#services-forgejo-usage-basic}
|
||||
|
||||
This will set up a Forgejo service that runs on the NixOS target machine,
|
||||
reachable at `http://forgejo.example.com`.
|
||||
|
|
@ -60,25 +60,22 @@ shb.forgejo = {
|
|||
If the `shb.ssl` block is used (see [manual](blocks-ssl.html#usage) on how to set it up),
|
||||
the instance will be reachable at `https://fogejo.example.com`.
|
||||
|
||||
Here is an example with self-signed certificates:
|
||||
Here is an example with Let's Encrypt certificates, validated using the HTTP method:
|
||||
|
||||
```nix
|
||||
shb.certs = {
|
||||
cas.selfsigned.myca = {
|
||||
name = "My CA";
|
||||
};
|
||||
certs.selfsigned = {
|
||||
foregejo = {
|
||||
ca = config.shb.certs.cas.selfsigned.myca;
|
||||
domain = "forgejo.example.com";
|
||||
};
|
||||
};
|
||||
shb.certs.certs.letsencrypt."example.com" = {
|
||||
domain = "example.com";
|
||||
group = "nginx";
|
||||
reloadServices = [ "nginx.service" ];
|
||||
adminEmail = "myemail@mydomain.com";
|
||||
};
|
||||
```
|
||||
|
||||
Then you can tell Forgejo to use those certificates.
|
||||
|
||||
```nix
|
||||
shb.certs.certs.letsencrypt."example.com".extraDomains = [ "forgejo.example.com" ];
|
||||
|
||||
shb.forgejo = {
|
||||
ssl = config.shb.certs.certs.selfsigned.forgejo;
|
||||
};
|
||||
|
|
@ -87,7 +84,7 @@ shb.forgejo = {
|
|||
### With LDAP Support {#services-forgejo-usage-ldap}
|
||||
|
||||
:::: {.note}
|
||||
We will build upon the [Forgejo through HTTP(S)](#services-forgejo-usage-basic) section,
|
||||
We will build upon the [HTTPS](#services-forgejo-usage-basic) section,
|
||||
so please follow that first.
|
||||
::::
|
||||
|
||||
|
|
@ -99,12 +96,18 @@ shb.ldap = {
|
|||
enable = true;
|
||||
domain = "example.com";
|
||||
subdomain = "ldap";
|
||||
ssl = config.shb.certs.certs.letsencrypt."example.com";
|
||||
ldapPort = 3890;
|
||||
webUIListenPort = 17170;
|
||||
dcdomain = "dc=example,dc=com";
|
||||
ldapUserPasswordFile = <path/to/ldapUserPasswordSecret>;
|
||||
jwtSecretFile = <path/to/ldapJwtSecret>;
|
||||
ldapUserPassword.result = config.shb.sops.secrets."ldap/userPassword".result;
|
||||
jwtSecret.result = config.shb.sops.secrets."ldap/jwtSecret".result;
|
||||
};
|
||||
|
||||
shb.certs.certs.letsencrypt."example.com".extraDomains = [ "ldap.example.com" ];
|
||||
|
||||
shb.sops.secrets."ldap/userPassword".request = config.shb.ldap.userPassword.request;
|
||||
shb.sops.secrets."ldap/jwtSecret".request = config.shb.ldap.jwtSecret.request;
|
||||
```
|
||||
|
||||
We also need to configure the `forgejo` service
|
||||
|
|
@ -116,7 +119,12 @@ shb.forgejo.ldap
|
|||
host = "127.0.0.1";
|
||||
port = config.shb.ldap.ldapPort;
|
||||
dcdomain = config.shb.ldap.dcdomain;
|
||||
adminPasswordFile = <path/to/ldapUserPasswordSecret>;
|
||||
adminPassword.result = config.shb.sops.secrets."forgejo/ldap/adminPassword".result
|
||||
};
|
||||
|
||||
shb.sops.secrets."forgejo/ldap/adminPassword" = {
|
||||
request = config.shb.forgejo.ldap.adminPassword.request;
|
||||
settings.key = "ldap/userPassword";
|
||||
};
|
||||
```
|
||||
|
||||
|
|
@ -135,29 +143,10 @@ When that's done, go back to the Forgejo server at
|
|||
### With SSO Support {#services-forgejo-usage-sso}
|
||||
|
||||
:::: {.note}
|
||||
We will build upon the [With LDAP Support](#services-forgejo-usage-ldap) section,
|
||||
We will build upon the [LDAP](#services-forgejo-usage-ldap) section,
|
||||
so please follow that first.
|
||||
::::
|
||||
|
||||
Here though, we must setup SSL certificates
|
||||
because the SSO provider only works with the https protocol.
|
||||
Let's add self-signed certificates for Authelia and LLDAP:
|
||||
|
||||
```nix
|
||||
shb.certs = {
|
||||
certs.selfsigned = {
|
||||
auth = {
|
||||
ca = config.shb.certs.cas.selfsigned.myca;
|
||||
domain = "auth.example.com";
|
||||
};
|
||||
ldap = {
|
||||
ca = config.shb.certs.cas.selfsigned.myca;
|
||||
domain = "ldap.example.com";
|
||||
};
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
We then need to setup the SSO provider,
|
||||
here Authelia thanks to the corresponding SHB block:
|
||||
|
||||
|
|
@ -166,21 +155,31 @@ shb.authelia = {
|
|||
enable = true;
|
||||
domain = "example.com";
|
||||
subdomain = "auth";
|
||||
ssl = config.shb.certs.certs.selfsigned.auth;
|
||||
ssl = config.shb.certs.certs.letsencrypt."example.com";
|
||||
|
||||
ldapHostname = "127.0.0.1";
|
||||
ldapPort = config.shb.ldap.ldapPort;
|
||||
dcdomain = config.shb.ldap.dcdomain;
|
||||
|
||||
secrets = {
|
||||
jwtSecretFile = <path/to/autheliaJwtSecret>;
|
||||
ldapAdminPasswordFile = <path/to/ldapUserPasswordSecret>;
|
||||
sessionSecretFile = <path/to/autheliaSessionSecret>;
|
||||
storageEncryptionKeyFile = <path/to/autheliaStorageEncryptionKeySecret>;
|
||||
identityProvidersOIDCHMACSecretFile = <path/to/providersOIDCHMACSecret>;
|
||||
identityProvidersOIDCIssuerPrivateKeyFile = <path/to/providersOIDCIssuerSecret>;
|
||||
jwtSecret.result = config.shb.sops.secrets."authelia/jwt_secret".result;
|
||||
ldapAdminPassword.result = config.shb.sops.secrets."authelia/ldap_admin_password".result;
|
||||
sessionSecret.result = config.shb.sops.secrets."authelia/session_secret".result;
|
||||
storageEncryptionKey.result = config.shb.sops.secrets."authelia/storage_encryption_key".result;
|
||||
identityProvidersOIDCHMACSecret.result = config.shb.sops.secrets."authelia/hmac_secret".result;
|
||||
identityProvidersOIDCIssuerPrivateKey.result = config.shb.sops.secrets."authelia/private_key".result;
|
||||
};
|
||||
};
|
||||
|
||||
shb.certs.certs.letsencrypt."example.com".extraDomains = [ "auth.example.com" ];
|
||||
|
||||
shb.sops.secrets."authelia/jwt_secret".request = config.shb.authelia.secrets.jwtSecret.request;
|
||||
shb.sops.secrets."authelia/ldap_admin_password".request = config.shb.authelia.secrets.ldapAdminPassword.request;
|
||||
shb.sops.secrets."authelia/session_secret".request = config.shb.authelia.secrets.sessionSecret.request;
|
||||
shb.sops.secrets."authelia/storage_encryption_key".request = config.shb.authelia.secrets.storageEncryptionKey.request;
|
||||
shb.sops.secrets."authelia/hmac_secret".request = config.shb.authelia.secrets.identityProvidersOIDCHMACSecret.request;
|
||||
shb.sops.secrets."authelia/private_key".request = config.shb.authelia.secrets.identityProvidersOIDCIssuerPrivateKey.request;
|
||||
shb.sops.secrets."authelia/smtp_password".request = config.shb.authelia.smtp.password.request;
|
||||
```
|
||||
|
||||
The `shb.authelia.secrets.ldapAdminPasswordFile` must be the same
|
||||
|
|
|
|||
|
|
@ -67,12 +67,16 @@ in
|
|||
default = "jellyfin_admin";
|
||||
};
|
||||
|
||||
adminPassword = contracts.secret.mkOption {
|
||||
adminPassword = lib.mkOption {
|
||||
description = "LDAP admin password.";
|
||||
mode = "0440";
|
||||
owner = "jellyfin";
|
||||
group = "jellyfin";
|
||||
restartUnits = [ "jellyfin.service" ];
|
||||
type = lib.types.submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
mode = "0440";
|
||||
owner = "jellyfin";
|
||||
group = "jellyfin";
|
||||
restartUnits = [ "jellyfin.service" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -121,18 +125,26 @@ in
|
|||
default = "one_factor";
|
||||
};
|
||||
|
||||
sharedSecret = contracts.secret.mkOption {
|
||||
sharedSecret = lib.mkOption {
|
||||
description = "OIDC shared secret for Jellyfin.";
|
||||
mode = "0440";
|
||||
owner = "jellyfin";
|
||||
group = "jellyfin";
|
||||
restartUnits = [ "jellyfin.service" ];
|
||||
type = lib.types.submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
mode = "0440";
|
||||
owner = "jellyfin";
|
||||
group = "jellyfin";
|
||||
restartUnits = [ "jellyfin.service" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
sharedSecretForAuthelia = contracts.secret.mkOption {
|
||||
sharedSecretForAuthelia = lib.mkOption {
|
||||
description = "OIDC shared secret for Authelia.";
|
||||
mode = "0400";
|
||||
owner = config.shb.authelia.autheliaUser;
|
||||
type = lib.types.submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
mode = "0400";
|
||||
owner = config.shb.authelia.autheliaUser;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -95,13 +95,18 @@ in
|
|||
default = "root";
|
||||
};
|
||||
|
||||
adminPass = contracts.secret.mkOption {
|
||||
adminPass = lib.mkOption {
|
||||
description = "Nextcloud admin password.";
|
||||
mode = "0400";
|
||||
owner = "nextcloud";
|
||||
restartUnits = [ "phpfpm-nextcloud.service" ];
|
||||
type = lib.types.submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
mode = "0400";
|
||||
owner = "nextcloud";
|
||||
restartUnits = [ "phpfpm-nextcloud.service" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
maxUploadSize = lib.mkOption {
|
||||
default = "4G";
|
||||
type = lib.types.str;
|
||||
|
|
@ -374,13 +379,18 @@ in
|
|||
default = "admin";
|
||||
};
|
||||
|
||||
adminPassword = contracts.secret.mkOption {
|
||||
adminPassword = lib.mkOption {
|
||||
description = "LDAP server admin password.";
|
||||
mode = "0400";
|
||||
owner = "nextcloud";
|
||||
restartUnits = [ "phpfpm-nextcloud.service" ];
|
||||
type = lib.types.submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
mode = "0400";
|
||||
owner = "nextcloud";
|
||||
restartUnits = [ "phpfpm-nextcloud.service" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
userGroup = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Group users must belong to to be able to login to Nextcloud.";
|
||||
|
|
@ -441,19 +451,29 @@ in
|
|||
default = "one_factor";
|
||||
};
|
||||
|
||||
secret = contracts.secret.mkOption {
|
||||
secret = lib.mkOption {
|
||||
description = "OIDC shared secret.";
|
||||
mode = "0400";
|
||||
owner = "nextcloud";
|
||||
restartUnits = [ "phpfpm-nextcloud.service" ];
|
||||
type = lib.types.submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
mode = "0400";
|
||||
owner = "nextcloud";
|
||||
restartUnits = [ "phpfpm-nextcloud.service" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
secretForAuthelia = contracts.secret.mkOption {
|
||||
|
||||
secretForAuthelia = lib.mkOption {
|
||||
description = "OIDC shared secret. Content must be the same as `secretFile` option.";
|
||||
mode = "0400";
|
||||
owner = "authelia";
|
||||
type = lib.types.submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
mode = "0400";
|
||||
owner = "authelia";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
fallbackDefaultAuth = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = ''
|
||||
|
|
|
|||
|
|
@ -1,27 +1,27 @@
|
|||
# Nextcloud Server Service {#services-nextcloud-server}
|
||||
# Nextcloud Server Service {#services-nextcloudserver}
|
||||
|
||||
Defined in [`/modules/services/nextcloud-server.nix`](@REPO@/modules/services/nextcloud-server.nix).
|
||||
|
||||
This NixOS module is a service that sets up a [Nextcloud Server](https://nextcloud.com/).
|
||||
It is based on the nixpkgs Nextcloud server and provides opinionated defaults.
|
||||
|
||||
## Features {#services-nextcloud-server-features}
|
||||
## Features {#services-nextcloudserver-features}
|
||||
|
||||
- Declarative [Apps](#services-nextcloud-server-options-shb.nextcloud.apps) Configuration - no need
|
||||
- Declarative [Apps](#services-nextcloudserver-options-shb.nextcloud.apps) Configuration - no need
|
||||
to configure those with the UI.
|
||||
- [LDAP](#services-nextcloud-server-usage-ldap) app:
|
||||
- [LDAP](#services-nextcloudserver-usage-ldap) app:
|
||||
enables app and sets up integration with an existing LDAP server, in this case LLDAP.
|
||||
- [OIDC](#services-nextcloud-server-usage-oidc) app:
|
||||
enables app and sets up integration with an existing OIDC server, in this case Authelia.
|
||||
- [Preview Generator](#services-nextcloud-server-usage-previewgenerator) app:
|
||||
- [SSO](#services-nextcloudserver-usage-oidc) app:
|
||||
enables app and sets up integration with an existing SSO server, in this case Authelia.
|
||||
- [Preview Generator](#services-nextcloudserver-usage-previewgenerator) app:
|
||||
enables app and sets up required cron job.
|
||||
- [External Storage](#services-nextcloud-server-usage-externalstorage) app:
|
||||
- [External Storage](#services-nextcloudserver-usage-externalstorage) app:
|
||||
enables app and optionally configures one local mount.
|
||||
This enables having data living on separate hard drives.
|
||||
- [Only Office](#services-nextcloud-server-usage-onlyoffice) app:
|
||||
- [Only Office](#services-nextcloudserver-usage-onlyoffice) app:
|
||||
enables app and sets up Only Office service.
|
||||
- Any other app through the
|
||||
[shb.nextcloud.extraApps](#services-nextcloud-server-options-shb.nextcloud.extraApps) option.
|
||||
[shb.nextcloud.extraApps](#services-nextcloudserver-options-shb.nextcloud.extraApps) option.
|
||||
- Access through subdomain using reverse proxy.
|
||||
- Forces Nginx as the reverse proxy. (This is hardcoded in the upstream nixpkgs module).
|
||||
- Sets good defaults for trusted proxies settings, chunk size, opcache php options.
|
||||
|
|
@ -40,15 +40,15 @@ It is based on the nixpkgs Nextcloud server and provides opinionated defaults.
|
|||
- By default automatically disables maintenance mode on start.
|
||||
- By default automatically launches repair mode with expensive migrations on start.
|
||||
- Access to advanced options not exposed here thanks to how NixOS modules work.
|
||||
- Has a [demo](#services-nextcloud-server-demo).
|
||||
- Has a [demo](#services-nextcloudserver-demo).
|
||||
|
||||
[dataDir]: ./services-nextcloud.html#services-nextcloud-server-options-shb.nextcloud.dataDir
|
||||
[dataDir]: ./services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.dataDir
|
||||
|
||||
## Usage {#services-nextcloud-server-usage}
|
||||
## Usage {#services-nextcloudserver-usage}
|
||||
|
||||
### Nextcloud through HTTP {#services-nextcloud-server-usage-basic}
|
||||
### Nextcloud through HTTP {#services-nextcloudserver-usage-basic}
|
||||
|
||||
[HTTP]: #services-nextcloud-server-usage-basic
|
||||
[HTTP]: #services-nextcloudserver-usage-basic
|
||||
|
||||
:::: {.note}
|
||||
This section corresponds to the `basic` section of the [Nextcloud
|
||||
|
|
@ -66,10 +66,10 @@ shb.nextcloud = {
|
|||
domain = "example.com";
|
||||
subdomain = "n";
|
||||
defaultPhoneRegion = "US";
|
||||
adminPass.result.path = config.sops.secrets."nextcloud/adminpass".path;
|
||||
adminPass.result = config.shb.sops.secrets."nextcloud/adminpass".result;
|
||||
};
|
||||
|
||||
sops.secrets."nextcloud/adminpass" = config.shb.nextcloud.adminPass.request;
|
||||
shb.sops.secrets."nextcloud/adminpass".request = config.shb.nextcloud.adminPass.request;
|
||||
```
|
||||
|
||||
This assumes secrets are setup with SOPS as mentioned in [the secrets setup section](usage.html#usage-secrets) of the manual.
|
||||
|
|
@ -82,9 +82,9 @@ We will set that up in the next section.
|
|||
You can now login as the admin user using the username `admin`
|
||||
and the password defined in `sops.secrets."nextcloud/adminpass"`.
|
||||
|
||||
### Nextcloud through HTTPS {#services-nextcloud-server-usage-https}
|
||||
### Nextcloud through HTTPS {#services-nextcloudserver-usage-https}
|
||||
|
||||
[HTTPS]: #services-nextcloud-server-usage-https
|
||||
[HTTPS]: #services-nextcloudserver-usage-https
|
||||
|
||||
To setup HTTPS, we will get our certificates from Let's Encrypt using the HTTP method.
|
||||
This is the easiest way to get started and does not require you to programmatically
|
||||
|
|
@ -94,7 +94,7 @@ Under the hood, we use the Self Host Block [SSL contract](./contracts-ssl.html).
|
|||
It allows the end user to choose how to generate the certificates.
|
||||
If you want other options to generate the certificate, follow the SSL contract link.
|
||||
|
||||
Building upon the [Basic Configuration](#services-nextcloud-server-usage-basic) above, we add:
|
||||
Building upon the [Basic Configuration](#services-nextcloudserver-usage-basic) above, we add:
|
||||
|
||||
```nix
|
||||
shb.certs.certs.letsencrypt."example.com" = {
|
||||
|
|
@ -111,17 +111,17 @@ shb.nextcloud = {
|
|||
};
|
||||
```
|
||||
|
||||
### Choose Nextcloud Version {#services-nextcloud-server-usage-version}
|
||||
### Choose Nextcloud Version {#services-nextcloudserver-usage-version}
|
||||
|
||||
Self Host Blocks is conservative in the version of Nextcloud it's using.
|
||||
To choose the version and upgrade at the time of your liking,
|
||||
just use the [version](#services-nextcloud-server-options-shb.nextcloud.version) option:
|
||||
just use the [version](#services-nextcloudserver-options-shb.nextcloud.version) option:
|
||||
|
||||
```nix
|
||||
shb.nextcloud.version = 29;
|
||||
```
|
||||
|
||||
### Mount Point {#services-nextcloud-server-usage-mount-point}
|
||||
### Mount Point {#services-nextcloudserver-usage-mount-point}
|
||||
|
||||
If the `dataDir` exists in a mount point,
|
||||
it is highly recommended to make the various Nextcloud services wait on the mount point before starting.
|
||||
|
|
@ -134,9 +134,9 @@ fileSystems."/var".device = "...";
|
|||
shb.nextcloud.mountPointServices = [ "var.mount" ];
|
||||
```
|
||||
|
||||
### With LDAP Support {#services-nextcloud-server-usage-ldap}
|
||||
### With LDAP Support {#services-nextcloudserver-usage-ldap}
|
||||
|
||||
[LDAP]: #services-nextcloud-server-usage-ldap
|
||||
[LDAP]: #services-nextcloudserver-usage-ldap
|
||||
|
||||
:::: {.note}
|
||||
This section corresponds to the `ldap` section of the [Nextcloud
|
||||
|
|
@ -154,15 +154,18 @@ shb.ldap = {
|
|||
enable = true;
|
||||
domain = "example.com";
|
||||
subdomain = "ldap";
|
||||
ssl = config.shb.certs.certs.letsencrypt."example.com";
|
||||
ldapPort = 3890;
|
||||
webUIListenPort = 17170;
|
||||
dcdomain = "dc=example,dc=com";
|
||||
ldapUserPassword.result.path = config.sops.secrets."ldap/userPassword".path;
|
||||
jwtSecret.result.path = config.sops.secrets."ldap/jwtSecret".path;
|
||||
ldapUserPassword.result = config.shb.sops.secrets."ldap/userPassword".result;
|
||||
jwtSecret.result = config.shb.sops.secrets."ldap/jwtSecret".result;
|
||||
};
|
||||
|
||||
sops.secrets."ldap/userPassword" = config.shb.ldap.userPassword.request;
|
||||
sops.secrets."ldap/jwtSecret" = config.shb.ldap.jwtSecret.request;
|
||||
shb.certs.certs.letsencrypt."example.com".extraDomains = [ "ldap.example.com" ];
|
||||
|
||||
shb.sops.secrets."ldap/userPassword".request = config.shb.ldap.userPassword.request;
|
||||
shb.sops.secrets."ldap/jwtSecret".request = config.shb.ldap.jwtSecret.request;
|
||||
```
|
||||
|
||||
On the `nextcloud` module side, we need to configure it to talk to the LDAP server we
|
||||
|
|
@ -175,12 +178,13 @@ shb.nextcloud.apps.ldap = {
|
|||
port = config.shb.ldap.ldapPort;
|
||||
dcdomain = config.shb.ldap.dcdomain;
|
||||
adminName = "admin";
|
||||
adminPassword.result.path = config.sops.secrets."nextcloud/ldapUserPassword".path
|
||||
adminPassword.result = config.shb.sops.secrets."nextcloud/ldap/adminPassword".result
|
||||
userGroup = "nextcloud_user";
|
||||
};
|
||||
|
||||
sops.secrets."nextcloud/ldapUserPassword" = config.shb.nextcloud.adminPasswordFile.request // {
|
||||
key = "ldap/userPassword";
|
||||
shb.sops.secrets."nextcloud/ldap/adminPassword" = {
|
||||
request = config.shb.nextcloud.apps.ldap.adminPassword.request;
|
||||
settings.key = "ldap/userPassword";
|
||||
};
|
||||
```
|
||||
|
||||
|
|
@ -202,7 +206,7 @@ so you need to create a normal user like above,
|
|||
login with it once so it is known to Nextcloud, then logout,
|
||||
login with the admin Nextcloud user and promote that new user to admin level.
|
||||
|
||||
### With OIDC Support {#services-nextcloud-server-usage-oidc}
|
||||
### With SSO Support {#services-nextcloudserver-usage-oidc}
|
||||
|
||||
:::: {.note}
|
||||
This section corresponds to the `sso` section of the [Nextcloud
|
||||
|
|
@ -230,26 +234,28 @@ shb.authelia = {
|
|||
port = 587;
|
||||
username = "postmaster@mg.example.com";
|
||||
from_address = "authelia@example.com";
|
||||
password.result.path = config.sops.secrets."authelia/smtp_password".path;
|
||||
password.result = config.shb.sops.secrets."authelia/smtp_password".result;
|
||||
};
|
||||
|
||||
secrets = {
|
||||
jwtSecret.result.path = config.sops.secrets."authelia/jwt_secret".path;
|
||||
ldapAdminPassword.result.path = config.sops.secrets."authelia/ldap_admin_password".path;
|
||||
sessionSecret.result.path = config.sops.secrets."authelia/session_secret".path;
|
||||
storageEncryptionKey.result.path = config.sops.secrets."authelia/storage_encryption_key".path;
|
||||
identityProvidersOIDCHMACSecret.result.path = config.sops.secrets."authelia/hmac_secret".path;
|
||||
identityProvidersOIDCIssuerPrivateKey.result.path = config.sops.secrets."authelia/private_key".path;
|
||||
jwtSecret.result = config.shb.sops.secrets."authelia/jwt_secret".result;
|
||||
ldapAdminPassword.result = config.shb.sops.secrets."authelia/ldap_admin_password".result;
|
||||
sessionSecret.result = config.shb.sops.secrets."authelia/session_secret".result;
|
||||
storageEncryptionKey.result = config.shb.sops.secrets."authelia/storage_encryption_key".result;
|
||||
identityProvidersOIDCHMACSecret.result = config.shb.sops.secrets."authelia/hmac_secret".result;
|
||||
identityProvidersOIDCIssuerPrivateKey.result = config.shb.sops.secrets."authelia/private_key".result;
|
||||
};
|
||||
};
|
||||
|
||||
sops.secrets."authelia/jwt_secret" = config.shb.authelia.secrets.jwtSecret.request;
|
||||
sops.secrets."authelia/ldap_admin_password" = config.shb.authelia.secrets.ldapAdminPassword.request;
|
||||
sops.secrets."authelia/session_secret" = config.shb.authelia.secrets.sessionSecret.request;
|
||||
sops.secrets."authelia/storage_encryption_key" = config.shb.authelia.secrets.storageEncryptionKey.request;
|
||||
sops.secrets."authelia/hmac_secret" = config.shb.authelia.secrets.identityProvidersOIDCHMACSecret.request;
|
||||
sops.secrets."authelia/private_key" = config.shb.authelia.secrets.identityProvidersOIDCIssuerPrivateKey.request;
|
||||
sops.secrets."authelia/smtp_password" = config.shb.authelia.smtp.password.request;
|
||||
shb.certs.certs.letsencrypt."example.com".extraDomains = [ "auth.example.com" ];
|
||||
|
||||
shb.sops.secrets."authelia/jwt_secret".request = config.shb.authelia.secrets.jwtSecret.request;
|
||||
shb.sops.secrets."authelia/ldap_admin_password".request = config.shb.authelia.secrets.ldapAdminPassword.request;
|
||||
shb.sops.secrets."authelia/session_secret".request = config.shb.authelia.secrets.sessionSecret.request;
|
||||
shb.sops.secrets."authelia/storage_encryption_key".request = config.shb.authelia.secrets.storageEncryptionKey.request;
|
||||
shb.sops.secrets."authelia/hmac_secret".request = config.shb.authelia.secrets.identityProvidersOIDCHMACSecret.request;
|
||||
shb.sops.secrets."authelia/private_key".request = config.shb.authelia.secrets.identityProvidersOIDCIssuerPrivateKey.request;
|
||||
shb.sops.secrets."authelia/smtp_password".request = config.shb.authelia.smtp.password.request;
|
||||
```
|
||||
|
||||
The secrets can be randomly generated with `nix run nixpkgs#openssl -- rand -hex 64`.
|
||||
|
|
@ -263,13 +269,14 @@ shb.nextcloud.apps.sso = {
|
|||
clientID = "nextcloud";
|
||||
fallbackDefaultAuth = false;
|
||||
|
||||
secret.result.path = config.sops.secrets."nextcloud/sso/secret".path;
|
||||
secretForAuthelia.result.path = config.sops.secrets."nextcloud/sso/secretForAuthelia".path;
|
||||
secret.result = config.shb.sops.secrets."nextcloud/sso/secret".result;
|
||||
secretForAuthelia.result = config.shb.sops.secrets."nextcloud/sso/secretForAuthelia".result;
|
||||
};
|
||||
|
||||
sops.secret."nextcloud/sso/secret" = config.shb.nextcloud.apps.sso.secret.request;
|
||||
sops.secret."nextcloud/sso/secretForAuthelia" = config.shb.nextcloud.apps.sso.secretForAuthelia.request // {
|
||||
key = "nextcloud/sso/secret";
|
||||
shb.sops.secret."nextcloud/sso/secret".request = config.shb.nextcloud.apps.sso.secret.request;
|
||||
shb.sops.secret."nextcloud/sso/secretForAuthelia" = {
|
||||
request = config.shb.nextcloud.apps.sso.secretForAuthelia.request;
|
||||
settings.key = "nextcloud/sso/secret";
|
||||
};
|
||||
```
|
||||
|
||||
|
|
@ -282,7 +289,7 @@ secrets have the same content.
|
|||
Setting the `fallbackDefaultAuth` to `false` means the only way to login is through Authelia.
|
||||
If this does not work for any reason, you can let users login through Nextcloud directly by setting this option to `true`.
|
||||
|
||||
### Tweak PHPFpm Config {#services-nextcloud-server-usage-phpfpm}
|
||||
### Tweak PHPFpm Config {#services-nextcloudserver-usage-phpfpm}
|
||||
|
||||
For instances with more users, or if you feel the pages are loading slowly,
|
||||
you can tweak the `php-fpm` pool settings.
|
||||
|
|
@ -304,7 +311,7 @@ I don't have a good heuristic for what are good values here but what I found
|
|||
is that you don't want too high of a `max_children` value
|
||||
to avoid I/O strain on the hard drives, especially if you use spinning drives.
|
||||
|
||||
### Tweak PostgreSQL Settings {#services-nextcloud-server-usage-postgres}
|
||||
### Tweak PostgreSQL Settings {#services-nextcloudserver-usage-postgres}
|
||||
|
||||
These settings will impact all databases since the NixOS Postgres module
|
||||
configures only one Postgres instance.
|
||||
|
|
@ -346,7 +353,7 @@ shb.nextcloud.postgresSettings = {
|
|||
};
|
||||
```
|
||||
|
||||
### Backup {#services-nextcloud-server-usage-backup}
|
||||
### Backup {#services-nextcloudserver-usage-backup}
|
||||
|
||||
Backing up Nextcloud data files using the [Restic block](blocks-restic.html) is done like so:
|
||||
|
||||
|
|
@ -378,7 +385,7 @@ Note that this will backup the whole PostgreSQL instance,
|
|||
not just the Nextcloud database.
|
||||
This limitation will be lifted in the future.
|
||||
|
||||
### Enable Preview Generator App {#services-nextcloud-server-usage-previewgenerator}
|
||||
### Enable Preview Generator App {#services-nextcloudserver-usage-previewgenerator}
|
||||
|
||||
The following snippet installs and enables the [Preview
|
||||
Generator](https://apps.nextcloud.com/apps/previewgenerator) application as well as creates the
|
||||
|
|
@ -403,7 +410,7 @@ You can opt-out with:
|
|||
shb.nextcloud.apps.previewgenerator.recommendedSettings = false;
|
||||
```
|
||||
|
||||
### Enable External Storage App {#services-nextcloud-server-usage-externalstorage}
|
||||
### Enable External Storage App {#services-nextcloudserver-usage-externalstorage}
|
||||
|
||||
The following snippet installs and enables the [External
|
||||
Storage](https://docs.nextcloud.com/server/28/go.php?to=admin-external-storage) application.
|
||||
|
|
@ -440,7 +447,7 @@ which is well suited for randomly accessing small files like thumbnails.
|
|||
On the other side, a spinning hard drive can store more data
|
||||
which is well suited for storing user data.
|
||||
|
||||
### Enable OnlyOffice App {#services-nextcloud-server-usage-onlyoffice}
|
||||
### Enable OnlyOffice App {#services-nextcloudserver-usage-onlyoffice}
|
||||
|
||||
The following snippet installs and enables the [Only
|
||||
Office](https://apps.nextcloud.com/apps/onlyoffice) application as well as sets up an Only Office
|
||||
|
|
@ -462,7 +469,7 @@ nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (pkgs.lib.getName pkg)
|
|||
];
|
||||
```
|
||||
|
||||
### Enable Monitoring {#services-nextcloud-server-server-usage-monitoring}
|
||||
### Enable Monitoring {#services-nextcloudserver-server-usage-monitoring}
|
||||
|
||||
Enable the [monitoring block](./blocks-monitoring.html).
|
||||
A [Grafana dashboard][] for overall server performance will be created
|
||||
|
|
@ -470,7 +477,7 @@ and the Nextcloud metrics will automatically appear there.
|
|||
|
||||
[Grafana dashboard]: ./blocks-monitoring.html#blocks-monitoring-performance-dashboard
|
||||
|
||||
### Enable Tracing {#services-nextcloud-server-server-usage-tracing}
|
||||
### Enable Tracing {#services-nextcloudserver-server-usage-tracing}
|
||||
|
||||
You can enable tracing with:
|
||||
|
||||
|
|
@ -485,7 +492,7 @@ but that's not the case yet.
|
|||
|
||||
[my blog post]: http://blog.tiserbox.com/posts/2023-08-12-what%27s-up-with-nextcloud-webdav-slowness.html
|
||||
|
||||
### Appdata Location {#services-nextcloud-server-server-usage-appdata}
|
||||
### Appdata Location {#services-nextcloudserver-server-usage-appdata}
|
||||
|
||||
The appdata folder is a special folder located under the `shb.nextcloud.dataDir` directory.
|
||||
It is named `appdata_<instanceid>` with the Nextcloud's instance ID as a suffix.
|
||||
|
|
@ -496,7 +503,7 @@ For performance reasons, it is recommended to store this folder on a fast drive
|
|||
that is optimized for randomized read and write access.
|
||||
The best would be either an SSD or an NVMe drive.
|
||||
|
||||
The best way to solve this is to use the [External Storage app](#services-nextcloud-server-usage-externalstorage).
|
||||
The best way to solve this is to use the [External Storage app](#services-nextcloudserver-usage-externalstorage).
|
||||
|
||||
If you have an existing installation and put Nextcloud's `shb.nextcloud.dataDir` folder on a HDD with spinning disks,
|
||||
then the appdata folder is also located on spinning drives.
|
||||
|
|
@ -514,16 +521,16 @@ mount --bind /srv/sdd/appdata_nextcloud /var/lib/nextcloud/data/appdata_ocxvky2f
|
|||
Note that you can re-generate a new appdata folder
|
||||
by issuing the command `nextcloud-occ config:system:delete instanceid`.
|
||||
|
||||
## Demo {#services-nextcloud-server-demo}
|
||||
## Demo {#services-nextcloudserver-demo}
|
||||
|
||||
Head over to the [Nextcloud demo](demo-nextcloud-server.html) for a demo that installs Nextcloud with or
|
||||
without LDAP integration on a VM with minimal manual steps.
|
||||
|
||||
## Maintenance {#services-nextcloud-server-maintenance}
|
||||
## Maintenance {#services-nextcloudserver-maintenance}
|
||||
|
||||
On the command line, the `occ` tool is called `nextcloud-occ`.
|
||||
|
||||
## Debug {#services-nextcloud-server-debug}
|
||||
## Debug {#services-nextcloudserver-debug}
|
||||
|
||||
In case of an issue, check the logs for any systemd service mentioned in this section.
|
||||
|
||||
|
|
@ -540,10 +547,10 @@ Access the database with `sudo -u nextcloud psql`.
|
|||
|
||||
Access Redis with `sudo -u nextcloud redis-cli -s /run/redis-nextcloud/redis.sock`.
|
||||
|
||||
## Options Reference {#services-nextcloud-server-options}
|
||||
## Options Reference {#services-nextcloudserver-options}
|
||||
|
||||
```{=include=} options
|
||||
id-prefix: services-nextcloud-server-options-
|
||||
id-prefix: services-nextcloudserver-options-
|
||||
list-id: selfhostblocks-service-nextcloud-options
|
||||
source: @OPTIONS_JSON@
|
||||
```
|
||||
|
|
|
|||
|
|
@ -45,9 +45,16 @@ in
|
|||
example = "https://authelia.example.com";
|
||||
};
|
||||
|
||||
databasePasswordFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
description = "File containing the password to connect to the postgresql database.";
|
||||
databasePassword = lib.mkOption {
|
||||
description = "File containing the Vaultwarden database password.";
|
||||
type = lib.types.submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
mode = "0440";
|
||||
owner = "vaultwarden";
|
||||
group = "postgres";
|
||||
restartUnits = [ "vaultwarden.service" "postgresql.service" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
smtp = lib.mkOption {
|
||||
|
|
@ -88,9 +95,15 @@ in
|
|||
description = "Auth mechanism.";
|
||||
default = "Login";
|
||||
};
|
||||
passwordFile = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
password = lib.mkOption {
|
||||
description = "File containing the password to connect to the SMTP host.";
|
||||
type = lib.types.submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
mode = "0400";
|
||||
owner = "vaultwarden";
|
||||
restartUnits = [ "vaultwarden.service" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
});
|
||||
|
|
@ -187,10 +200,10 @@ in
|
|||
systemd.services.vaultwarden.preStart =
|
||||
shblib.replaceSecrets {
|
||||
userConfig = {
|
||||
DATABASE_URL.source = cfg.databasePasswordFile;
|
||||
DATABASE_URL.source = cfg.databasePassword.result.path;
|
||||
DATABASE_URL.transform = v: "postgresql://vaultwarden:${v}@127.0.0.1:5432/vaultwarden";
|
||||
} // lib.optionalAttrs (cfg.smtp != null) {
|
||||
SMTP_PASSWORD.source = cfg.smtp.passwordFile;
|
||||
SMTP_PASSWORD.source = cfg.smtp.password.result.path;
|
||||
};
|
||||
resultPath = "${dataFolder}/vaultwarden.env";
|
||||
generator = name: v: pkgs.writeText "template" (lib.generators.toINIWithGlobalSection {} { globalSection = v; });
|
||||
|
|
@ -224,7 +237,7 @@ in
|
|||
{
|
||||
username = "vaultwarden";
|
||||
database = "vaultwarden";
|
||||
passwordFile = builtins.toString cfg.databasePasswordFile;
|
||||
passwordFile = cfg.databasePassword.result.path;
|
||||
}
|
||||
];
|
||||
# TODO: make this work.
|
||||
|
|
|
|||
|
|
@ -33,8 +33,17 @@ in
|
|||
dcdomain = "dc=example,dc=com";
|
||||
subdomain = "ldap";
|
||||
domain = "machine.com";
|
||||
ldapUserPassword.result.path = pkgs.writeText "user_password" ldapAdminPassword;
|
||||
jwtSecret.result.path = pkgs.writeText "jwt_secret" "securejwtsecret";
|
||||
ldapUserPassword.result = config.shb.hardcodedsecret.ldapUserPassword.result;
|
||||
jwtSecret.result = config.shb.hardcodedsecret.jwtSecret.result;
|
||||
};
|
||||
|
||||
shb.hardcodedsecret.ldapUserPassword = {
|
||||
request = config.shb.ldap.ldapUserPassword.request;
|
||||
settings.content = ldapAdminPassword;
|
||||
};
|
||||
shb.hardcodedsecret.jwtSecret = {
|
||||
request = config.shb.ldap.jwtSecret.request;
|
||||
settings.content = "jwtsecret";
|
||||
};
|
||||
|
||||
shb.authelia = {
|
||||
|
|
@ -45,12 +54,12 @@ in
|
|||
ldapPort = config.shb.ldap.ldapPort;
|
||||
dcdomain = config.shb.ldap.dcdomain;
|
||||
secrets = {
|
||||
jwtSecret.result.path = config.shb.hardcodedsecret.autheliaJwtSecret.path;
|
||||
ldapAdminPassword.result.path = config.shb.hardcodedsecret.ldapAdminPassword.path;
|
||||
sessionSecret.result.path = config.shb.hardcodedsecret.sessionSecret.path;
|
||||
storageEncryptionKey.result.path = config.shb.hardcodedsecret.storageEncryptionKey.path;
|
||||
identityProvidersOIDCHMACSecret.result.path = config.shb.hardcodedsecret.identityProvidersOIDCHMACSecret.path;
|
||||
identityProvidersOIDCIssuerPrivateKey.result.path = config.shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey.path;
|
||||
jwtSecret.result = config.shb.hardcodedsecret.autheliaJwtSecret.result;
|
||||
ldapAdminPassword.result = config.shb.hardcodedsecret.ldapAdminPassword.result;
|
||||
sessionSecret.result = config.shb.hardcodedsecret.sessionSecret.result;
|
||||
storageEncryptionKey.result = config.shb.hardcodedsecret.storageEncryptionKey.result;
|
||||
identityProvidersOIDCHMACSecret.result = config.shb.hardcodedsecret.identityProvidersOIDCHMACSecret.result;
|
||||
identityProvidersOIDCIssuerPrivateKey.result = config.shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey.result;
|
||||
};
|
||||
|
||||
oidcClients = [
|
||||
|
|
@ -73,23 +82,29 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
shb.hardcodedsecret.autheliaJwtSecret = config.shb.authelia.secrets.jwtSecret.request // {
|
||||
content = "jwtSecret";
|
||||
shb.hardcodedsecret.autheliaJwtSecret = {
|
||||
request = config.shb.authelia.secrets.jwtSecret.request;
|
||||
settings.content = "jwtSecret";
|
||||
};
|
||||
shb.hardcodedsecret.ldapAdminPassword = config.shb.authelia.secrets.ldapAdminPassword.request // {
|
||||
content = ldapAdminPassword;
|
||||
shb.hardcodedsecret.ldapAdminPassword = {
|
||||
request = config.shb.authelia.secrets.ldapAdminPassword.request;
|
||||
settings.content = ldapAdminPassword;
|
||||
};
|
||||
shb.hardcodedsecret.sessionSecret = config.shb.authelia.secrets.sessionSecret.request // {
|
||||
content = "sessionSecret";
|
||||
shb.hardcodedsecret.sessionSecret = {
|
||||
request = config.shb.authelia.secrets.sessionSecret.request;
|
||||
settings.content = "sessionSecret";
|
||||
};
|
||||
shb.hardcodedsecret.storageEncryptionKey = config.shb.authelia.secrets.storageEncryptionKey.request // {
|
||||
content = "storageEncryptionKey";
|
||||
shb.hardcodedsecret.storageEncryptionKey = {
|
||||
request = config.shb.authelia.secrets.storageEncryptionKey.request;
|
||||
settings.content = "storageEncryptionKey";
|
||||
};
|
||||
shb.hardcodedsecret.identityProvidersOIDCHMACSecret = config.shb.authelia.secrets.identityProvidersOIDCHMACSecret.request // {
|
||||
content = "identityProvidersOIDCHMACSecret";
|
||||
shb.hardcodedsecret.identityProvidersOIDCHMACSecret = {
|
||||
request = config.shb.authelia.secrets.identityProvidersOIDCHMACSecret.request;
|
||||
settings.content = "identityProvidersOIDCHMACSecret";
|
||||
};
|
||||
shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey = config.shb.authelia.secrets.identityProvidersOIDCIssuerPrivateKey.request // {
|
||||
source = (pkgs.runCommand "gen-private-key" {} ''
|
||||
shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey = {
|
||||
request = config.shb.authelia.secrets.identityProvidersOIDCIssuerPrivateKey.request;
|
||||
settings.source = (pkgs.runCommand "gen-private-key" {} ''
|
||||
mkdir $out
|
||||
${pkgs.openssl}/bin/openssl genrsa -out $out/private.pem 4096
|
||||
'') + "/private.pem";
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
{ pkgs, lib, ... }:
|
||||
let
|
||||
pkgs' = pkgs;
|
||||
|
||||
password = "securepassword";
|
||||
in
|
||||
{
|
||||
auth = pkgs.testers.runNixOSTest {
|
||||
|
|
@ -15,6 +17,7 @@ in
|
|||
shb.ssl.enable = lib.mkEnableOption "ssl";
|
||||
};
|
||||
}
|
||||
../../modules/blocks/hardcodedsecret.nix
|
||||
../../modules/blocks/ldap.nix
|
||||
];
|
||||
|
||||
|
|
@ -23,10 +26,19 @@ in
|
|||
dcdomain = "dc=example,dc=com";
|
||||
subdomain = "ldap";
|
||||
domain = "example.com";
|
||||
ldapUserPassword.result.path = pkgs.writeText "user_password" "securepw";
|
||||
jwtSecret.result.path = pkgs.writeText "jwt_secret" "securejwtsecret";
|
||||
ldapUserPassword.result = config.shb.hardcodedsecret.ldapUserPassword.result;
|
||||
jwtSecret.result = config.shb.hardcodedsecret.jwtSecret.result;
|
||||
debug = true;
|
||||
};
|
||||
shb.hardcodedsecret.ldapUserPassword = {
|
||||
request = config.shb.ldap.ldapUserPassword.request;
|
||||
settings.content = password;
|
||||
};
|
||||
shb.hardcodedsecret.jwtSecret = {
|
||||
request = config.shb.ldap.jwtSecret.request;
|
||||
settings.content = "jwtSecret";
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 80 ]; # nginx port
|
||||
};
|
||||
|
||||
|
|
@ -63,7 +75,7 @@ in
|
|||
+ """ -H "Content-type: application/json" """
|
||||
+ """ -H "Host: ldap.example.com" """
|
||||
+ " http://server/auth/simple/login "
|
||||
+ """ -d '{"username": "admin", "password": "securepw"}' """
|
||||
+ """ -d '{"username": "admin", "password": "${password}"}' """
|
||||
))['token']
|
||||
|
||||
data = json.loads(client.succeed(
|
||||
|
|
|
|||
|
|
@ -19,23 +19,32 @@ let
|
|||
];
|
||||
|
||||
shb.hardcodedsecret.A = {
|
||||
owner = "root";
|
||||
group = "keys";
|
||||
mode = "0440";
|
||||
content = "secretA";
|
||||
request = {
|
||||
owner = "root";
|
||||
group = "keys";
|
||||
mode = "0440";
|
||||
};
|
||||
settings.content = "secretA";
|
||||
};
|
||||
shb.hardcodedsecret.B = {
|
||||
owner = "root";
|
||||
group = "keys";
|
||||
mode = "0440";
|
||||
content = "secretB";
|
||||
request = {
|
||||
owner = "root";
|
||||
group = "keys";
|
||||
mode = "0440";
|
||||
};
|
||||
settings.content = "secretB";
|
||||
};
|
||||
|
||||
shb.hardcodedsecret.passphrase = {
|
||||
request = config.shb.restic.instances."testinstance".settings.passphrase.request;
|
||||
settings.content = "secretB";
|
||||
};
|
||||
|
||||
shb.restic.instances."testinstance" = {
|
||||
settings = {
|
||||
enable = true;
|
||||
|
||||
passphrase.result.path = pkgs.writeText "passphrase" "PassPhrase";
|
||||
passphrase.result = config.shb.hardcodedsecret.passphrase.result;
|
||||
|
||||
repository = {
|
||||
path = "/opt/repos/A";
|
||||
|
|
@ -46,8 +55,8 @@ let
|
|||
# Those are not needed by the repository but are still included
|
||||
# so we can test them in the hooks section.
|
||||
secrets = {
|
||||
A.source = config.shb.hardcodedsecret.A.path;
|
||||
B.source = config.shb.hardcodedsecret.B.path;
|
||||
A.source = config.shb.hardcodedsecret.A.result.path;
|
||||
B.source = config.shb.hardcodedsecret.B.result.path;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -146,11 +146,13 @@ in
|
|||
"127.0.0.1" = [ "ldap.${domain}" ];
|
||||
};
|
||||
|
||||
shb.hardcodedsecret.ldapUserPassword = config.shb.ldap.ldapUserPassword.request // {
|
||||
content = "ldapUserPassword";
|
||||
shb.hardcodedsecret.ldapUserPassword = {
|
||||
request = config.shb.ldap.ldapUserPassword.request;
|
||||
settings.content = "ldapUserPassword";
|
||||
};
|
||||
shb.hardcodedsecret.jwtSecret = config.shb.ldap.jwtSecret.request // {
|
||||
content = "jwtSecrets";
|
||||
shb.hardcodedsecret.jwtSecret = {
|
||||
request = config.shb.ldap.jwtSecret.request;
|
||||
settings.content = "jwtSecrets";
|
||||
};
|
||||
|
||||
shb.ldap = {
|
||||
|
|
@ -160,8 +162,8 @@ in
|
|||
ldapPort = 3890;
|
||||
webUIListenPort = 17170;
|
||||
dcdomain = "dc=example,dc=com";
|
||||
ldapUserPassword.result.path = config.shb.hardcodedsecret.ldapUserPassword.path;
|
||||
jwtSecret.result.path = config.shb.hardcodedsecret.jwtSecret.path;
|
||||
ldapUserPassword.result = config.shb.hardcodedsecret.ldapUserPassword.result;
|
||||
jwtSecret.result = config.shb.hardcodedsecret.jwtSecret.result;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -185,32 +187,38 @@ in
|
|||
dcdomain = config.shb.ldap.dcdomain;
|
||||
|
||||
secrets = {
|
||||
jwtSecret.result.path = config.shb.hardcodedsecret.autheliaJwtSecret.path;
|
||||
ldapAdminPassword.result.path = config.shb.hardcodedsecret.ldapAdminPassword.path;
|
||||
sessionSecret.result.path = config.shb.hardcodedsecret.sessionSecret.path;
|
||||
storageEncryptionKey.result.path = config.shb.hardcodedsecret.storageEncryptionKey.path;
|
||||
identityProvidersOIDCHMACSecret.result.path = config.shb.hardcodedsecret.identityProvidersOIDCHMACSecret.path;
|
||||
identityProvidersOIDCIssuerPrivateKey.result.path = config.shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey.path;
|
||||
jwtSecret.result = config.shb.hardcodedsecret.autheliaJwtSecret.result;
|
||||
ldapAdminPassword.result = config.shb.hardcodedsecret.ldapAdminPassword.result;
|
||||
sessionSecret.result = config.shb.hardcodedsecret.sessionSecret.result;
|
||||
storageEncryptionKey.result = config.shb.hardcodedsecret.storageEncryptionKey.result;
|
||||
identityProvidersOIDCHMACSecret.result = config.shb.hardcodedsecret.identityProvidersOIDCHMACSecret.result;
|
||||
identityProvidersOIDCIssuerPrivateKey.result = config.shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey.result;
|
||||
};
|
||||
};
|
||||
|
||||
shb.hardcodedsecret.autheliaJwtSecret = config.shb.authelia.secrets.jwtSecret.request // {
|
||||
content = "jwtSecret";
|
||||
shb.hardcodedsecret.autheliaJwtSecret = {
|
||||
request = config.shb.authelia.secrets.jwtSecret.request;
|
||||
settings.content = "jwtSecret";
|
||||
};
|
||||
shb.hardcodedsecret.ldapAdminPassword = config.shb.authelia.secrets.ldapAdminPassword.request // {
|
||||
content = "ldapUserPassword";
|
||||
shb.hardcodedsecret.ldapAdminPassword = {
|
||||
request = config.shb.authelia.secrets.ldapAdminPassword.request;
|
||||
settings.content = "ldapUserPassword";
|
||||
};
|
||||
shb.hardcodedsecret.sessionSecret = config.shb.authelia.secrets.sessionSecret.request // {
|
||||
content = "sessionSecret";
|
||||
shb.hardcodedsecret.sessionSecret = {
|
||||
request = config.shb.authelia.secrets.sessionSecret.request;
|
||||
settings.content = "sessionSecret";
|
||||
};
|
||||
shb.hardcodedsecret.storageEncryptionKey = config.shb.authelia.secrets.storageEncryptionKey.request // {
|
||||
content = "storageEncryptionKey";
|
||||
shb.hardcodedsecret.storageEncryptionKey = {
|
||||
request = config.shb.authelia.secrets.storageEncryptionKey.request;
|
||||
settings.content = "storageEncryptionKey";
|
||||
};
|
||||
shb.hardcodedsecret.identityProvidersOIDCHMACSecret = config.shb.authelia.secrets.identityProvidersOIDCHMACSecret.request // {
|
||||
content = "identityProvidersOIDCHMACSecret";
|
||||
shb.hardcodedsecret.identityProvidersOIDCHMACSecret = {
|
||||
request = config.shb.authelia.secrets.identityProvidersOIDCHMACSecret.request;
|
||||
settings.content = "identityProvidersOIDCHMACSecret";
|
||||
};
|
||||
shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey = config.shb.authelia.secrets.identityProvidersOIDCIssuerPrivateKey.request // {
|
||||
source = (pkgs.runCommand "gen-private-key" {} ''
|
||||
shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey = {
|
||||
request = config.shb.authelia.secrets.identityProvidersOIDCIssuerPrivateKey.request;
|
||||
settings.source = (pkgs.runCommand "gen-private-key" {} ''
|
||||
mkdir $out
|
||||
${pkgs.openssl}/bin/openssl genrsa -out $out/private.pem 4096
|
||||
'') + "/private.pem";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{ pkgs, ... }:
|
||||
{ pkgs, lib, ... }:
|
||||
let
|
||||
contracts = pkgs.callPackage ../../modules/contracts {};
|
||||
in
|
||||
|
|
@ -13,7 +13,7 @@ in
|
|||
];
|
||||
settings = { repository, config, ... }: {
|
||||
enable = true;
|
||||
passphrase.result.path = config.shb.hardcodedsecret.passphrase.path;
|
||||
passphrase.result = config.shb.hardcodedsecret.passphrase.result;
|
||||
repository = {
|
||||
path = repository;
|
||||
timerConfig = {
|
||||
|
|
@ -21,16 +21,16 @@ in
|
|||
};
|
||||
};
|
||||
};
|
||||
extraConfig = { username, ... }: {
|
||||
extraConfig = { username, config, ... }: {
|
||||
shb.hardcodedsecret.passphrase = {
|
||||
owner = username;
|
||||
content = "passphrase";
|
||||
request = config.shb.restic.instances."mytest".settings.passphrase.request;
|
||||
settings.content = "passphrase";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
restic_me = contracts.test.backup {
|
||||
name = "restic_me";
|
||||
restic_nonroot = contracts.test.backup {
|
||||
name = "restic_nonroot";
|
||||
username = "me";
|
||||
providerRoot = [ "shb" "restic" "instances" "mytest" ];
|
||||
modules = [
|
||||
|
|
@ -39,7 +39,7 @@ in
|
|||
];
|
||||
settings = { repository, config, ... }: {
|
||||
enable = true;
|
||||
passphrase.result.path = config.shb.hardcodedsecret.passphrase.path;
|
||||
passphrase.result = config.shb.hardcodedsecret.passphrase.result;
|
||||
repository = {
|
||||
path = repository;
|
||||
timerConfig = {
|
||||
|
|
@ -47,10 +47,10 @@ in
|
|||
};
|
||||
};
|
||||
};
|
||||
extraConfig = { username, ... }: {
|
||||
extraConfig = { username, config, ... }: {
|
||||
shb.hardcodedsecret.passphrase = {
|
||||
owner = username;
|
||||
content = "passphrase";
|
||||
request = config.shb.restic.instances."mytest".settings.passphrase.request;
|
||||
settings.content = "passphrase";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,10 +10,11 @@ in
|
|||
modules = [
|
||||
../../modules/blocks/postgresql.nix
|
||||
../../modules/blocks/restic.nix
|
||||
../../modules/blocks/hardcodedsecret.nix
|
||||
];
|
||||
settings = repository: {
|
||||
settings = { repository, config, ... }: {
|
||||
enable = true;
|
||||
passphrase.result.path = pkgs.writeText "passphrase" "PassPhrase";
|
||||
passphrase.result = config.shb.hardcodedsecret.passphrase.result;
|
||||
repository = {
|
||||
path = repository;
|
||||
timerConfig = {
|
||||
|
|
@ -21,12 +22,17 @@ in
|
|||
};
|
||||
};
|
||||
};
|
||||
extraConfig = { username, database, ... }: {
|
||||
extraConfig = { config, database, ... }: {
|
||||
shb.postgresql.ensures = [
|
||||
{
|
||||
inherit username database;
|
||||
inherit database;
|
||||
username = database;
|
||||
}
|
||||
];
|
||||
shb.hardcodedsecret.passphrase = {
|
||||
request = config.shb.restic.databases.postgresql.settings.passphrase.request;
|
||||
settings.content = "passphrase";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ in
|
|||
name = "hardcoded";
|
||||
modules = [ ../../modules/blocks/hardcodedsecret.nix ];
|
||||
configRoot = [ "shb" "hardcodedsecret" ];
|
||||
createContent = {
|
||||
content = "secretA";
|
||||
settingsCfg = secret: {
|
||||
content = secret;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -16,8 +16,8 @@ in
|
|||
name = "hardcoded";
|
||||
modules = [ ../../modules/blocks/hardcodedsecret.nix ];
|
||||
configRoot = [ "shb" "hardcodedsecret" ];
|
||||
createContent = {
|
||||
content = "secretA";
|
||||
settingsCfg = secret: {
|
||||
content = secret;
|
||||
};
|
||||
owner = "user";
|
||||
group = "group";
|
||||
|
|
|
|||
|
|
@ -34,8 +34,8 @@ let
|
|||
enable = true;
|
||||
inherit domain subdomain;
|
||||
|
||||
adminPassword.result.path = config.shb.hardcodedsecret.forgejoAdminPassword.path;
|
||||
databasePassword.result.path = config.shb.hardcodedsecret.forgejoDatabasePassword.path;
|
||||
adminPassword.result = config.shb.hardcodedsecret.forgejoAdminPassword.result;
|
||||
databasePassword.result = config.shb.hardcodedsecret.forgejoDatabasePassword.result;
|
||||
};
|
||||
|
||||
# Needed for gitea-runner-local to be able to ping forgejo.
|
||||
|
|
@ -43,12 +43,14 @@ let
|
|||
"127.0.0.1" = [ "${subdomain}.${domain}" ];
|
||||
};
|
||||
|
||||
shb.hardcodedsecret.forgejoAdminPassword = config.shb.forgejo.adminPassword.request // {
|
||||
content = adminPassword;
|
||||
shb.hardcodedsecret.forgejoAdminPassword = {
|
||||
request = config.shb.forgejo.adminPassword.request;
|
||||
settings.content = adminPassword;
|
||||
};
|
||||
|
||||
shb.hardcodedsecret.forgejoDatabasePassword = config.shb.forgejo.databasePassword.request // {
|
||||
content = "databasePassword";
|
||||
shb.hardcodedsecret.forgejoDatabasePassword = {
|
||||
request = config.shb.forgejo.databasePassword.request;
|
||||
settings.content = "databasePassword";
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -65,12 +67,13 @@ let
|
|||
host = "127.0.0.1";
|
||||
port = config.shb.ldap.ldapPort;
|
||||
dcdomain = config.shb.ldap.dcdomain;
|
||||
adminPassword.result.path = config.shb.hardcodedsecret.forgejoLdapUserPassword.path;
|
||||
adminPassword.result = config.shb.hardcodedsecret.forgejoLdapUserPassword.result;
|
||||
};
|
||||
};
|
||||
|
||||
shb.hardcodedsecret.forgejoLdapUserPassword = config.shb.forgejo.ldap.adminPassword.request // {
|
||||
content = "ldapUserPassword";
|
||||
shb.hardcodedsecret.forgejoLdapUserPassword = {
|
||||
request = config.shb.forgejo.ldap.adminPassword.request;
|
||||
settings.content = "ldapUserPassword";
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -79,17 +82,19 @@ let
|
|||
sso = {
|
||||
enable = true;
|
||||
endpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
||||
sharedSecret.result.path = config.shb.hardcodedsecret.forgejoSSOPassword.path;
|
||||
sharedSecretForAuthelia.result.path = config.shb.hardcodedsecret.forgejoSSOPasswordAuthelia.path;
|
||||
sharedSecret.result = config.shb.hardcodedsecret.forgejoSSOPassword.result;
|
||||
sharedSecretForAuthelia.result = config.shb.hardcodedsecret.forgejoSSOPasswordAuthelia.result;
|
||||
};
|
||||
};
|
||||
|
||||
shb.hardcodedsecret.forgejoSSOPassword = config.shb.forgejo.sso.sharedSecret.request // {
|
||||
content = "ssoPassword";
|
||||
shb.hardcodedsecret.forgejoSSOPassword = {
|
||||
request = config.shb.forgejo.sso.sharedSecret.request;
|
||||
settings.content = "ssoPassword";
|
||||
};
|
||||
|
||||
shb.hardcodedsecret.forgejoSSOPasswordAuthelia = config.shb.forgejo.sso.sharedSecretForAuthelia.request // {
|
||||
content = "ssoPassword";
|
||||
shb.hardcodedsecret.forgejoSSOPasswordAuthelia = {
|
||||
request = config.shb.forgejo.sso.sharedSecretForAuthelia.request;
|
||||
settings.content = "ssoPassword";
|
||||
};
|
||||
};
|
||||
in
|
||||
|
|
|
|||
|
|
@ -43,12 +43,13 @@ let
|
|||
host = "127.0.0.1";
|
||||
port = config.shb.ldap.ldapPort;
|
||||
dcdomain = config.shb.ldap.dcdomain;
|
||||
adminPassword.result.path = config.shb.hardcodedsecret.jellyfinLdapUserPassword.path;
|
||||
adminPassword.result = config.shb.hardcodedsecret.jellyfinLdapUserPassword.result;
|
||||
};
|
||||
};
|
||||
|
||||
shb.hardcodedsecret.jellyfinLdapUserPassword = config.shb.jellyfin.ldap.adminPassword.request // {
|
||||
content = "ldapUserPassword";
|
||||
shb.hardcodedsecret.jellyfinLdapUserPassword = {
|
||||
request = config.shb.jellyfin.ldap.adminPassword.request;
|
||||
settings.content = "ldapUserPassword";
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -57,17 +58,19 @@ let
|
|||
sso = {
|
||||
enable = true;
|
||||
endpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
||||
sharedSecret.result.path = config.shb.hardcodedsecret.jellyfinSSOPassword.path;
|
||||
sharedSecretForAuthelia.result.path = config.shb.hardcodedsecret.jellyfinSSOPasswordAuthelia.path;
|
||||
sharedSecret.result = config.shb.hardcodedsecret.jellyfinSSOPassword.result;
|
||||
sharedSecretForAuthelia.result = config.shb.hardcodedsecret.jellyfinSSOPasswordAuthelia.result;
|
||||
};
|
||||
};
|
||||
|
||||
shb.hardcodedsecret.jellyfinSSOPassword = config.shb.jellyfin.sso.sharedSecret.request // {
|
||||
content = "ssoPassword";
|
||||
shb.hardcodedsecret.jellyfinSSOPassword = {
|
||||
request = config.shb.jellyfin.sso.sharedSecret.request;
|
||||
settings.content = "ssoPassword";
|
||||
};
|
||||
|
||||
shb.hardcodedsecret.jellyfinSSOPasswordAuthelia = config.shb.jellyfin.sso.sharedSecretForAuthelia.request // {
|
||||
content = "ssoPassword";
|
||||
shb.hardcodedsecret.jellyfinSSOPasswordAuthelia = {
|
||||
request = config.shb.jellyfin.sso.sharedSecretForAuthelia.request;
|
||||
settings.content = "ssoPassword";
|
||||
};
|
||||
};
|
||||
in
|
||||
|
|
|
|||
|
|
@ -30,8 +30,17 @@ let
|
|||
inherit subdomain domain;
|
||||
|
||||
grafanaPort = 3000;
|
||||
adminPasswordFile = pkgs.writeText "admin_password" password;
|
||||
secretKeyFile = pkgs.writeText "secret_key" "secret_key";
|
||||
adminPassword.result = config.shb.hardcodedsecret."admin_password".result;
|
||||
secretKey.result = config.shb.hardcodedsecret."secret_key".result;
|
||||
};
|
||||
|
||||
shb.hardcodedsecret."admin_password" = {
|
||||
request = config.shb.monitoring.adminPassword.request;
|
||||
settings.content = password;
|
||||
};
|
||||
shb.hardcodedsecret."secret_key" = {
|
||||
request = config.shb.monitoring.secretKey.request;
|
||||
settings.content = "secret_key_pw";
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -132,12 +132,13 @@ let
|
|||
externalFqdn = "${fqdn}:8080";
|
||||
|
||||
adminUser = adminUser;
|
||||
adminPass.result.path = config.shb.hardcodedsecret.adminPass.path;
|
||||
adminPass.result = config.shb.hardcodedsecret.adminPass.result;
|
||||
debug = true;
|
||||
};
|
||||
|
||||
shb.hardcodedsecret.adminPass = config.shb.nextcloud.adminPass.request // {
|
||||
content = adminPass;
|
||||
shb.hardcodedsecret.adminPass = {
|
||||
request = config.shb.nextcloud.adminPass.request;
|
||||
settings.content = adminPass;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -157,7 +158,7 @@ let
|
|||
port = config.shb.ldap.ldapPort;
|
||||
dcdomain = config.shb.ldap.dcdomain;
|
||||
adminName = "admin";
|
||||
adminPassword.result.path = config.shb.ldap.ldapUserPassword.result.path;
|
||||
adminPassword.result = config.shb.ldap.ldapUserPassword.result;
|
||||
userGroup = "nextcloud_user";
|
||||
};
|
||||
};
|
||||
|
|
@ -172,18 +173,20 @@ let
|
|||
clientID = "nextcloud";
|
||||
# adminUserGroup = "nextcloud_admin";
|
||||
|
||||
secret.result.path = config.shb.hardcodedsecret.oidcSecret.path;
|
||||
secretForAuthelia.result.path = config.shb.hardcodedsecret.oidcAutheliaSecret.path;
|
||||
secret.result = config.shb.hardcodedsecret.oidcSecret.result;
|
||||
secretForAuthelia.result = config.shb.hardcodedsecret.oidcAutheliaSecret.result;
|
||||
|
||||
fallbackDefaultAuth = false;
|
||||
};
|
||||
};
|
||||
|
||||
shb.hardcodedsecret.oidcSecret = config.shb.nextcloud.apps.sso.secret.request // {
|
||||
content = oidcSecret;
|
||||
shb.hardcodedsecret.oidcSecret = {
|
||||
request = config.shb.nextcloud.apps.sso.secret.request;
|
||||
settings.content = oidcSecret;
|
||||
};
|
||||
shb.hardcodedsecret.oidcAutheliaSecret = config.shb.nextcloud.apps.sso.secretForAuthelia.request // {
|
||||
content = oidcSecret;
|
||||
shb.hardcodedsecret.oidcAutheliaSecret = {
|
||||
request = config.shb.nextcloud.apps.sso.secretForAuthelia.request;
|
||||
settings.content = oidcSecret;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ let
|
|||
};
|
||||
|
||||
base = testLib.base pkgs' [
|
||||
../../modules/blocks/hardcodedsecret.nix
|
||||
../../modules/services/vaultwarden.nix
|
||||
];
|
||||
|
||||
|
|
@ -61,7 +62,11 @@ let
|
|||
inherit subdomain domain;
|
||||
|
||||
port = 8222;
|
||||
databasePasswordFile = pkgs.writeText "pwfile" "DBPASSWORDFILE";
|
||||
databasePassword.result = config.shb.hardcodedsecret.passphrase.result;
|
||||
};
|
||||
shb.hardcodedsecret.passphrase = {
|
||||
request = config.shb.vaultwarden.databasePassword.request;
|
||||
settings.content = "PassPhrase";
|
||||
};
|
||||
|
||||
# networking.hosts = {
|
||||
|
|
@ -97,7 +102,7 @@ let
|
|||
request = config.shb.vaultwarden.backup;
|
||||
settings = {
|
||||
enable = true;
|
||||
passphrase.result.path = config.shb.hardcodedsecret.passphrase.path;
|
||||
passphrase.result = config.shb.hardcodedsecret.backupPassphrase.result;
|
||||
repository = {
|
||||
path = "/opt/repos/A";
|
||||
timerConfig = {
|
||||
|
|
@ -107,8 +112,9 @@ let
|
|||
};
|
||||
};
|
||||
};
|
||||
shb.hardcodedsecret.passphrase = config.shb.restic.instances."testinstance".settings.passphrase.request // {
|
||||
content = "PassPhrase";
|
||||
shb.hardcodedsecret.backupPassphrase = {
|
||||
request = config.shb.restic.instances."testinstance".settings.passphrase.request;
|
||||
settings.content = "PassPhrase";
|
||||
};
|
||||
};
|
||||
in
|
||||
|
|
|
|||
Loading…
Reference in a new issue