From 5c25d746671f387dc3cd675442920b67919cdaf3 Mon Sep 17 00:00:00 2001 From: ibizaman Date: Wed, 20 Nov 2024 18:26:34 +0100 Subject: [PATCH 1/2] update secret contract and add sops module --- CHANGELOG.md | 1 + README.md | 14 +- docs/blocks.md | 4 + docs/contracts.md | 9 +- docs/default.nix | 5 + docs/preface.md | 12 +- docs/usage.md | 8 +- modules/blocks/authelia.nix | 84 ++++--- modules/blocks/hardcodedsecret.nix | 110 ++++----- modules/blocks/ldap.nix | 28 ++- modules/blocks/monitoring.nix | 30 ++- modules/blocks/monitoring/docs/default.md | 4 +- modules/blocks/restic.nix | 47 ++-- modules/blocks/restic/docs/default.md | 19 +- modules/blocks/sops.nix | 36 +++ modules/blocks/sops/docs/default.md | 47 ++++ modules/contracts/backup.nix | 4 +- modules/contracts/backup/test.nix | 4 +- modules/contracts/databasebackup/test.nix | 21 +- modules/contracts/default.nix | 41 +++- modules/contracts/secret.nix | 185 +++++++------- modules/contracts/secret/docs/default.md | 135 ++++++----- modules/contracts/secret/dummyModule.nix | 14 +- modules/contracts/secret/test.nix | 19 +- modules/services/forgejo.nix | 227 ++++++++++-------- modules/services/forgejo/docs/default.md | 87 ++++--- modules/services/jellyfin.nix | 38 ++- modules/services/nextcloud-server.nix | 50 ++-- .../services/nextcloud-server/docs/default.md | 69 +++--- modules/services/vaultwarden.nix | 29 ++- test/blocks/authelia.nix | 55 +++-- test/blocks/ldap.nix | 18 +- test/blocks/restic.nix | 31 ++- test/common.nix | 56 +++-- test/contracts/backup.nix | 22 +- test/contracts/databasebackup.nix | 14 +- test/contracts/secret.nix | 8 +- test/services/forgejo.nix | 35 +-- test/services/jellyfin.nix | 21 +- test/services/monitoring.nix | 13 +- test/services/nextcloud.nix | 23 +- test/services/vaultwarden.nix | 14 +- 42 files changed, 1016 insertions(+), 675 deletions(-) create mode 100644 modules/blocks/sops.nix create mode 100644 modules/blocks/sops/docs/default.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ac0224c..8b42c05c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index dbc72b1e..856beb58 100644 --- a/README.md +++ b/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; }; }; ``` diff --git a/docs/blocks.md b/docs/blocks.md index e9781289..b89bb30d 100644 --- a/docs/blocks.md +++ b/docs/blocks.md @@ -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 ``` diff --git a/docs/contracts.md b/docs/contracts.md index 8ddb07ac..16d24d82 100644 --- a/docs/contracts.md +++ b/docs/contracts.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 diff --git a/docs/default.nix b/docs/default.nix index 410d5e4f..c30758a3 100644 --- a/docs/default.nix +++ b/docs/default.nix @@ -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@' \ diff --git a/docs/preface.md b/docs/preface.md index 219fec5c..19460fb0 100644 --- a/docs/preface.md +++ b/docs/preface.md @@ -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; }; }; ``` diff --git a/docs/usage.md b/docs/usage.md index 03e513ca..97511887 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -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. diff --git a/modules/blocks/authelia.nix b/modules/blocks/authelia.nix index 1c27dd3b..06eaa51b 100644 --- a/modules/blocks/authelia.nix +++ b/modules/blocks/authelia.nix @@ -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}" ]; + }; + }; }; }; })) diff --git a/modules/blocks/hardcodedsecret.nix b/modules/blocks/hardcodedsecret.nix index 4a174d04..2c0ee843 100644 --- a/modules/blocks/hardcodedsecret.nix +++ b/modules/blocks/hardcodedsecret.nix @@ -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 { - description = '' - Mode of the secret file. - ''; - type = str; - default = "0400"; - }; - - owner = mkOption { + options = contracts.secret.mkProvider { + settings = 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"; - }; + Settings specific to the hardcoded secret module. - restartUnits = mkOption { - description = '' - Systemd units to restart after the secret is updated. + Give either `content` or `source`. ''; - type = listOf str; - default = []; - }; - path = mkOption { - type = path; - description = '' - Path to the file containing the secret generated out of band. + type = submodule { + options = { + content = mkOption { + type = nullOr str; + description = '' + Content of the secret as a string. - This path will exist after deploying to a target host, - it is not available through the nix store. - ''; - default = "/run/hardcodedsecrets/hardcodedsecret_${name}"; - }; + This will be stored in the nix store and should only be used for testing or maybe in dev. + ''; + default = null; + }; - 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 as a path in the nix store. + ''; + 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; }; diff --git a/modules/blocks/ldap.nix b/modules/blocks/ldap.nix index 6bee35b2..ef2ed613 100644 --- a/modules/blocks/ldap.nix +++ b/modules/blocks/ldap.nix @@ -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 { diff --git a/modules/blocks/monitoring.nix b/modules/blocks/monitoring.nix index f59c4a6a..ed34e7d9 100644 --- a/modules/blocks/monitoring.nix +++ b/modules/blocks/monitoring.nix @@ -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 = { diff --git a/modules/blocks/monitoring/docs/default.md b/modules/blocks/monitoring/docs/default.md index 17240e72..a0774efb 100644 --- a/modules/blocks/monitoring/docs/default.md +++ b/modules/blocks/monitoring/docs/default.md @@ -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" = { diff --git a/modules/blocks/restic.nix b/modules/blocks/restic.nix index 2a9b3705..0f51fb50 100644 --- a/modules/blocks/restic.nix +++ b/modules/blocks/restic.nix @@ -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}..request.user](#blocks-restic-options-shb.restic.${prefix}._name_.request.user)"; - restartUnits = [ (fullName name options.settings.value.repository) ]; - restartUnitsText = "[ [shb.restic.${prefix}..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}..request.user](#blocks-restic-options-shb.restic.${prefix}._name_.request.user)"; + restartUnits = [ (fullName name config.settings.repository) ]; + restartUnitsText = "[ [shb.restic.${prefix}..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 "" { path = "path/to/repository"; }}"; backupServiceText = "${fullName "" { 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 "" { path = "path/to/repository"; }}"; backupServiceText = "${fullName "" { 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 "" { path = "path/to/repository"; }}"; backupServiceText = "${fullName "" { 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 "" { path = "path/to/repository"; }}"; backupServiceText = "${fullName "" { path = "path/to/repository"; }}.service"; }; diff --git a/modules/blocks/restic/docs/default.md b/modules/blocks/restic/docs/default.md index 24fce18b..5209a811 100644 --- a/modules/blocks/restic/docs/default.md +++ b/modules/blocks/restic/docs/default.md @@ -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 = ""; + 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 = ""; + 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} diff --git a/modules/blocks/sops.nix b/modules/blocks/sops.nix new file mode 100644 index 00000000..4f9f26ab --- /dev/null +++ b/modules/blocks/sops.nix @@ -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..request](#blocks-sops-options-shb.sops.secret._name_.request) option. + ''; + + type = anything; + }; + + resultCfg = { + path = "/run/secrets/${name}"; + pathText = "/run/secrets/"; + }; + }; + })); + }; + }; +} diff --git a/modules/blocks/sops/docs/default.md b/modules/blocks/sops/docs/default.md new file mode 100644 index 00000000..f833c48a --- /dev/null +++ b/modules/blocks/sops/docs/default.md @@ -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..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@ +``` diff --git a/modules/contracts/backup.nix b/modules/contracts/backup.nix index 9b0b2581..1422dbb8 100644 --- a/modules/contracts/backup.nix +++ b/modules/contracts/backup.nix @@ -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; }; diff --git a/modules/contracts/backup/test.nix b/modules/contracts/backup/test.nix index b6b18328..52d8bf0a 100644 --- a/modules/contracts/backup/test.nix +++ b/modules/contracts/backup/test.nix @@ -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; })) ]; }; diff --git a/modules/contracts/databasebackup/test.nix b/modules/contracts/databasebackup/test.nix index e9d374a0..bd09fa53 100644 --- a/modules/contracts/databasebackup/test.nix +++ b/modules/contracts/databasebackup/test.nix @@ -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"): diff --git a/modules/contracts/default.nix b/modules/contracts/default.nix index d5a28732..911279a9 100644 --- a/modules/contracts/default.nix +++ b/modules/contracts/default.nix @@ -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; }; diff --git a/modules/contracts/secret.nix b/modules/contracts/secret.nix index b679babe..49ab678b 100644 --- a/modules/contracts/secret.nix +++ b/modules/contracts/secret.nix @@ -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 = { - request = lib.mkOption { - default = { - inherit mode owner group restartUnits; - }; + Options set by the requester module + enforcing some properties the secret should have. + ''; - 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 + " ]"}; - } - ''); + default = { + inherit mode owner group restartUnits; + }; - readOnly = true; + 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 = { + 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; - }; + type = str; + default = mode; + }; - group = lib.mkOption { - description = '' - Linux group owning the secret file. - ''; - type = lib.types.str; - default = group; - }; + owner = mkOption ({ + description = '' + Linux user owning the secret file. + ''; + type = str; + default = owner; + } // optionalAttrs (ownerText != null) { + defaultText = literalMD ownerText; + }); - 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; - }; - }; - }; + group = mkOption { + description = '' + Linux group owning the secret file. + ''; + type = str; + default = group; }; - result = lib.mkOption { + restartUnits = mkOption ({ description = '' - Options set by the provider module that indicates where the secret can be found. + Systemd units to restart after the secret is updated. ''; - 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 = 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. - This path will exist after deploying to a target host, - it is not available through the nix store. - ''; - }; - }; - }; + 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; } diff --git a/modules/contracts/secret/docs/default.md b/modules/contracts/secret/docs/default.md index 3fd1e8c1..ca2a18f0 100644 --- a/modules/contracts/secret/docs/default.md +++ b/modules/contracts/secret/docs/default.md @@ -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 {#secret-contract-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,9 +63,9 @@ 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} @@ -92,18 +94,22 @@ 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" @@ -130,44 +136,43 @@ and that one can create multiple instances. ```nix { config, ... }: -{ - 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; - }; - - 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; - }; +let + inherit (lib) mkOption; + inherit (lib.types) attrsOf submodule; - path = lib.mkOption { - description = "Path where the secret file will be located."; - type = lib.types.str; + contracts = pkgs.callPackage ./contracts {}; +in +{ + 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. + ''; + + type = submodule { + options = { + secretFile = lib.mkOption { + description = "File containing the encrypted secret."; + type = lib.types.path; + }; + }; }; + }; - // 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; - }; + resultCfg = { + path = "/run/secrets/${name}"; + pathText = "/run/secrets/"; }; - }); - }; + }; + })); + }; + + config = { + // ... }; } ``` @@ -178,15 +183,20 @@ 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; -``` - -Then they will setup the requester module - here `myservice` - with the result of the provider module. +secretservice.secret."adminPassword".request = myservice.adminPasswor".request; +myservice.adminPassword.result = secretservice.secret."adminPassword".result; -```nix -myservice.secret.adminPassword.result.path = secretservice.adminPassword.result.path; - -myservice.secret.databasePassword.result.path = secretservice.adminPassword.result.path; +secretservice.secret."databasePassword".request = myservice.databasePassword.request; +myservice.databasePassword.result = secretservice.service."databasePassword".result; ``` + +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. diff --git a/modules/contracts/secret/dummyModule.nix b/modules/contracts/secret/dummyModule.nix index 7a998bed..ab41e0be 100644 --- a/modules/contracts/secret/dummyModule.nix +++ b/modules/contracts/secret/dummyModule.nix @@ -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; + }; }; } diff --git a/modules/contracts/secret/test.nix b/modules/contracts/secret/test.nix index 046b8d7c..517b56a1 100644 --- a/modules/contracts/secret/test.nix +++ b/modules/contracts/secret/test.nix @@ -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}'") diff --git a/modules/services/forgejo.nix b/modules/services/forgejo.nix index 50fafb6d..894373e7 100644 --- a/modules/services/forgejo.nix +++ b/modules/services/forgejo.nix @@ -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 .[:] ``` ''; - 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 ''); diff --git a/modules/services/forgejo/docs/default.md b/modules/services/forgejo/docs/default.md index 8ad3ffc7..5b0b84e4 100644 --- a/modules/services/forgejo/docs/default.md +++ b/modules/services/forgejo/docs/default.md @@ -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 = ; - jwtSecretFile = ; + 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 = ; + 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 = ; - ldapAdminPasswordFile = ; - sessionSecretFile = ; - storageEncryptionKeyFile = ; - identityProvidersOIDCHMACSecretFile = ; - identityProvidersOIDCIssuerPrivateKeyFile = ; + 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 diff --git a/modules/services/jellyfin.nix b/modules/services/jellyfin.nix index 506340c8..40cd6978 100644 --- a/modules/services/jellyfin.nix +++ b/modules/services/jellyfin.nix @@ -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; + }; + }; }; }; }; diff --git a/modules/services/nextcloud-server.nix b/modules/services/nextcloud-server.nix index f675326a..426fddc1 100644 --- a/modules/services/nextcloud-server.nix +++ b/modules/services/nextcloud-server.nix @@ -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 = '' diff --git a/modules/services/nextcloud-server/docs/default.md b/modules/services/nextcloud-server/docs/default.md index 7eac7bc1..fb67daeb 100644 --- a/modules/services/nextcloud-server/docs/default.md +++ b/modules/services/nextcloud-server/docs/default.md @@ -11,8 +11,8 @@ It is based on the nixpkgs Nextcloud server and provides opinionated defaults. to configure those with the UI. - [LDAP](#services-nextcloud-server-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. + - [SSO](#services-nextcloud-server-usage-oidc) app: + enables app and sets up integration with an existing SSO server, in this case Authelia. - [Preview Generator](#services-nextcloud-server-usage-previewgenerator) app: enables app and sets up required cron job. - [External Storage](#services-nextcloud-server-usage-externalstorage) app: @@ -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. @@ -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-nextcloud-server-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"; }; ``` diff --git a/modules/services/vaultwarden.nix b/modules/services/vaultwarden.nix index 710a9e6c..680fbeb7 100644 --- a/modules/services/vaultwarden.nix +++ b/modules/services/vaultwarden.nix @@ -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. diff --git a/test/blocks/authelia.nix b/test/blocks/authelia.nix index 5c523c57..fdbe939c 100644 --- a/test/blocks/authelia.nix +++ b/test/blocks/authelia.nix @@ -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"; diff --git a/test/blocks/ldap.nix b/test/blocks/ldap.nix index b1589399..eeb23cc0 100644 --- a/test/blocks/ldap.nix +++ b/test/blocks/ldap.nix @@ -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( diff --git a/test/blocks/restic.nix b/test/blocks/restic.nix index 5bafc333..a5406bd9 100644 --- a/test/blocks/restic.nix +++ b/test/blocks/restic.nix @@ -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; }; }; }; diff --git a/test/common.nix b/test/common.nix index 6af88ee8..c89d2a32 100644 --- a/test/common.nix +++ b/test/common.nix @@ -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"; diff --git a/test/contracts/backup.nix b/test/contracts/backup.nix index c9870f5a..c53e7f31 100644 --- a/test/contracts/backup.nix +++ b/test/contracts/backup.nix @@ -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"; }; }; }; diff --git a/test/contracts/databasebackup.nix b/test/contracts/databasebackup.nix index b1c18eac..aa6d3a41 100644 --- a/test/contracts/databasebackup.nix +++ b/test/contracts/databasebackup.nix @@ -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"; + }; }; }; } diff --git a/test/contracts/secret.nix b/test/contracts/secret.nix index d029100f..c594027c 100644 --- a/test/contracts/secret.nix +++ b/test/contracts/secret.nix @@ -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"; diff --git a/test/services/forgejo.nix b/test/services/forgejo.nix index dbae04b0..df26b3a2 100644 --- a/test/services/forgejo.nix +++ b/test/services/forgejo.nix @@ -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 diff --git a/test/services/jellyfin.nix b/test/services/jellyfin.nix index 8af4a962..b97873bc 100644 --- a/test/services/jellyfin.nix +++ b/test/services/jellyfin.nix @@ -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 diff --git a/test/services/monitoring.nix b/test/services/monitoring.nix index a349f576..732ee89c 100644 --- a/test/services/monitoring.nix +++ b/test/services/monitoring.nix @@ -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"; }; }; diff --git a/test/services/nextcloud.nix b/test/services/nextcloud.nix index f3eaf29f..e0fcb364 100644 --- a/test/services/nextcloud.nix +++ b/test/services/nextcloud.nix @@ -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; }; }; diff --git a/test/services/vaultwarden.nix b/test/services/vaultwarden.nix index 33026cd4..1b5ea2c8 100644 --- a/test/services/vaultwarden.nix +++ b/test/services/vaultwarden.nix @@ -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 From e05f0779b8f88871c7682a5b890995794944d8dd Mon Sep 17 00:00:00 2001 From: ibizaman Date: Thu, 21 Nov 2024 21:22:36 +0100 Subject: [PATCH 2/2] fix documentation generation after update https://github.com/NixOS/nixpkgs/pull/353513/files --- demo/homeassistant/README.md | 2 +- demo/nextcloud/README.md | 6 +- docs/default.nix | 1 + docs/demos.md | 2 +- docs/options.md | 2 +- docs/redirects.json | 1625 +++++++++++++++++ modules/blocks/ssl/docs/default.md | 14 +- modules/contracts/backup/docs/default.md | 2 +- modules/contracts/secret/docs/default.md | 14 +- modules/contracts/ssl/docs/default.md | 10 +- .../services/nextcloud-server/docs/default.md | 76 +- 11 files changed, 1690 insertions(+), 64 deletions(-) create mode 100644 docs/redirects.json diff --git a/demo/homeassistant/README.md b/demo/homeassistant/README.md index 7b5b835c..0d438467 100644 --- a/demo/homeassistant/README.md +++ b/demo/homeassistant/README.md @@ -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: diff --git a/demo/nextcloud/README.md b/demo/nextcloud/README.md index fd232598..2a89ab54 100644 --- a/demo/nextcloud/README.md +++ b/demo/nextcloud/README.md @@ -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 diff --git a/docs/default.nix b/docs/default.nix index c30758a3..1e3594ec 100644 --- a/docs/default.nix +++ b/docs/default.nix @@ -195,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 \ diff --git a/docs/demos.md b/docs/demos.md index f83a818e..730ee538 100644 --- a/docs/demos.md +++ b/docs/demos.md @@ -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 ``` diff --git a/docs/options.md b/docs/options.md index 7641d710..c5f312d6 100644 --- a/docs/options.md +++ b/docs/options.md @@ -1,4 +1,4 @@ -# All Options {#ch-options} +# All Options {#all-options} ```{=include=} options id-prefix: opt- diff --git a/docs/redirects.json b/docs/redirects.json new file mode 100644 index 00000000..a6d94c47 --- /dev/null +++ b/docs/redirects.json @@ -0,0 +1,1625 @@ +{ + "all-options": [ + "options.html#all-options" + ], + "blocks": [ + "blocks.html#blocks" + ], + "blocks-monitoring": [ + "blocks-monitoring.html#blocks-monitoring" + ], + "blocks-monitoring-budget-alerts": [ + "blocks-monitoring.html#blocks-monitoring-budget-alerts" + ], + "blocks-monitoring-configuration": [ + "blocks-monitoring.html#blocks-monitoring-configuration" + ], + "blocks-monitoring-error-dashboard": [ + "blocks-monitoring.html#blocks-monitoring-error-dashboard" + ], + "blocks-monitoring-performance-dashboard": [ + "blocks-monitoring.html#blocks-monitoring-performance-dashboard" + ], + "blocks-monitoring-provisioning": [ + "blocks-monitoring.html#blocks-monitoring-provisioning" + ], + "blocks-postgresql": [ + "blocks-postgresql.html#blocks-postgresql" + ], + "blocks-postgresql-contract-databasebackup": [ + "blocks-postgresql.html#blocks-postgresql-contract-databasebackup" + ], + "blocks-postgresql-contract-databasebackup-all": [ + "blocks-postgresql.html#blocks-postgresql-contract-databasebackup-all" + ], + "blocks-postgresql-options": [ + "blocks-postgresql.html#blocks-postgresql-options" + ], + "blocks-postgresql-options-shb.postgresql.databasebackup": [ + "blocks-postgresql.html#blocks-postgresql-options-shb.postgresql.databasebackup" + ], + "blocks-postgresql-options-shb.postgresql.databasebackup.backupCmd": [ + "blocks-postgresql.html#blocks-postgresql-options-shb.postgresql.databasebackup.backupCmd" + ], + "blocks-postgresql-options-shb.postgresql.databasebackup.backupName": [ + "blocks-postgresql.html#blocks-postgresql-options-shb.postgresql.databasebackup.backupName" + ], + "blocks-postgresql-options-shb.postgresql.databasebackup.restoreCmd": [ + "blocks-postgresql.html#blocks-postgresql-options-shb.postgresql.databasebackup.restoreCmd" + ], + "blocks-postgresql-options-shb.postgresql.databasebackup.user": [ + "blocks-postgresql.html#blocks-postgresql-options-shb.postgresql.databasebackup.user" + ], + "blocks-postgresql-options-shb.postgresql.debug": [ + "blocks-postgresql.html#blocks-postgresql-options-shb.postgresql.debug" + ], + "blocks-postgresql-options-shb.postgresql.enableTCPIP": [ + "blocks-postgresql.html#blocks-postgresql-options-shb.postgresql.enableTCPIP" + ], + "blocks-postgresql-options-shb.postgresql.ensures": [ + "blocks-postgresql.html#blocks-postgresql-options-shb.postgresql.ensures" + ], + "blocks-postgresql-options-shb.postgresql.ensures._.database": [ + "blocks-postgresql.html#blocks-postgresql-options-shb.postgresql.ensures._.database" + ], + "blocks-postgresql-options-shb.postgresql.ensures._.passwordFile": [ + "blocks-postgresql.html#blocks-postgresql-options-shb.postgresql.ensures._.passwordFile" + ], + "blocks-postgresql-options-shb.postgresql.ensures._.username": [ + "blocks-postgresql.html#blocks-postgresql-options-shb.postgresql.ensures._.username" + ], + "blocks-postgresql-tests": [ + "blocks-postgresql.html#blocks-postgresql-tests" + ], + "blocks-restic": [ + "blocks-restic.html#blocks-restic" + ], + "blocks-restic-contract-provider": [ + "blocks-restic.html#blocks-restic-contract-provider" + ], + "blocks-restic-demo": [ + "blocks-restic.html#blocks-restic-demo" + ], + "blocks-restic-maintenance": [ + "blocks-restic.html#blocks-restic-maintenance" + ], + "blocks-restic-maintenance-troubleshooting": [ + "blocks-restic.html#blocks-restic-maintenance-troubleshooting" + ], + "blocks-restic-monitoring": [ + "blocks-restic.html#blocks-restic-monitoring" + ], + "blocks-restic-options": [ + "blocks-restic.html#blocks-restic-options" + ], + "blocks-restic-options-shb.restic.databases": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases" + ], + "blocks-restic-options-shb.restic.databases._name_.request": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.request" + ], + "blocks-restic-options-shb.restic.databases._name_.request.backupCmd": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.request.backupCmd" + ], + "blocks-restic-options-shb.restic.databases._name_.request.backupName": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.request.backupName" + ], + "blocks-restic-options-shb.restic.databases._name_.request.restoreCmd": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.request.restoreCmd" + ], + "blocks-restic-options-shb.restic.databases._name_.request.user": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.request.user" + ], + "blocks-restic-options-shb.restic.databases._name_.result": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.result" + ], + "blocks-restic-options-shb.restic.databases._name_.result.backupService": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.result.backupService" + ], + "blocks-restic-options-shb.restic.databases._name_.result.restoreScript": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.result.restoreScript" + ], + "blocks-restic-options-shb.restic.databases._name_.settings": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.settings" + ], + "blocks-restic-options-shb.restic.databases._name_.settings.enable": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.settings.enable" + ], + "blocks-restic-options-shb.restic.databases._name_.settings.limitDownloadKiBs": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.settings.limitDownloadKiBs" + ], + "blocks-restic-options-shb.restic.databases._name_.settings.limitUploadKiBs": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.settings.limitUploadKiBs" + ], + "blocks-restic-options-shb.restic.databases._name_.settings.passphrase": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.settings.passphrase" + ], + "blocks-restic-options-shb.restic.databases._name_.settings.passphrase.request": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.settings.passphrase.request" + ], + "blocks-restic-options-shb.restic.databases._name_.settings.passphrase.request.group": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.settings.passphrase.request.group" + ], + "blocks-restic-options-shb.restic.databases._name_.settings.passphrase.request.mode": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.settings.passphrase.request.mode" + ], + "blocks-restic-options-shb.restic.databases._name_.settings.passphrase.request.owner": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.settings.passphrase.request.owner" + ], + "blocks-restic-options-shb.restic.databases._name_.settings.passphrase.request.restartUnits": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.settings.passphrase.request.restartUnits" + ], + "blocks-restic-options-shb.restic.databases._name_.settings.passphrase.result": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.settings.passphrase.result" + ], + "blocks-restic-options-shb.restic.databases._name_.settings.passphrase.result.path": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.settings.passphrase.result.path" + ], + "blocks-restic-options-shb.restic.databases._name_.settings.repository": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.settings.repository" + ], + "blocks-restic-options-shb.restic.databases._name_.settings.repository.path": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.settings.repository.path" + ], + "blocks-restic-options-shb.restic.databases._name_.settings.repository.secrets": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.settings.repository.secrets" + ], + "blocks-restic-options-shb.restic.databases._name_.settings.repository.secrets._name_.source": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.settings.repository.secrets._name_.source" + ], + "blocks-restic-options-shb.restic.databases._name_.settings.repository.secrets._name_.transform": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.settings.repository.secrets._name_.transform" + ], + "blocks-restic-options-shb.restic.databases._name_.settings.repository.timerConfig": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.settings.repository.timerConfig" + ], + "blocks-restic-options-shb.restic.databases._name_.settings.retention": [ + "blocks-restic.html#blocks-restic-options-shb.restic.databases._name_.settings.retention" + ], + "blocks-restic-options-shb.restic.instances": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances" + ], + "blocks-restic-options-shb.restic.instances._name_.request": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.request" + ], + "blocks-restic-options-shb.restic.instances._name_.request.excludePatterns": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.request.excludePatterns" + ], + "blocks-restic-options-shb.restic.instances._name_.request.hooks": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.request.hooks" + ], + "blocks-restic-options-shb.restic.instances._name_.request.hooks.after_backup": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.request.hooks.after_backup" + ], + "blocks-restic-options-shb.restic.instances._name_.request.hooks.before_backup": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.request.hooks.before_backup" + ], + "blocks-restic-options-shb.restic.instances._name_.request.sourceDirectories": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.request.sourceDirectories" + ], + "blocks-restic-options-shb.restic.instances._name_.request.user": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.request.user" + ], + "blocks-restic-options-shb.restic.instances._name_.result": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.result" + ], + "blocks-restic-options-shb.restic.instances._name_.result.backupService": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.result.backupService" + ], + "blocks-restic-options-shb.restic.instances._name_.result.restoreScript": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.result.restoreScript" + ], + "blocks-restic-options-shb.restic.instances._name_.settings": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.settings" + ], + "blocks-restic-options-shb.restic.instances._name_.settings.enable": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.settings.enable" + ], + "blocks-restic-options-shb.restic.instances._name_.settings.limitDownloadKiBs": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.settings.limitDownloadKiBs" + ], + "blocks-restic-options-shb.restic.instances._name_.settings.limitUploadKiBs": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.settings.limitUploadKiBs" + ], + "blocks-restic-options-shb.restic.instances._name_.settings.passphrase": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.settings.passphrase" + ], + "blocks-restic-options-shb.restic.instances._name_.settings.passphrase.request": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.settings.passphrase.request" + ], + "blocks-restic-options-shb.restic.instances._name_.settings.passphrase.request.group": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.settings.passphrase.request.group" + ], + "blocks-restic-options-shb.restic.instances._name_.settings.passphrase.request.mode": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.settings.passphrase.request.mode" + ], + "blocks-restic-options-shb.restic.instances._name_.settings.passphrase.request.owner": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.settings.passphrase.request.owner" + ], + "blocks-restic-options-shb.restic.instances._name_.settings.passphrase.request.restartUnits": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.settings.passphrase.request.restartUnits" + ], + "blocks-restic-options-shb.restic.instances._name_.settings.passphrase.result": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.settings.passphrase.result" + ], + "blocks-restic-options-shb.restic.instances._name_.settings.passphrase.result.path": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.settings.passphrase.result.path" + ], + "blocks-restic-options-shb.restic.instances._name_.settings.repository": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.settings.repository" + ], + "blocks-restic-options-shb.restic.instances._name_.settings.repository.path": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.settings.repository.path" + ], + "blocks-restic-options-shb.restic.instances._name_.settings.repository.secrets": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.settings.repository.secrets" + ], + "blocks-restic-options-shb.restic.instances._name_.settings.repository.secrets._name_.source": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.settings.repository.secrets._name_.source" + ], + "blocks-restic-options-shb.restic.instances._name_.settings.repository.secrets._name_.transform": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.settings.repository.secrets._name_.transform" + ], + "blocks-restic-options-shb.restic.instances._name_.settings.repository.timerConfig": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.settings.repository.timerConfig" + ], + "blocks-restic-options-shb.restic.instances._name_.settings.retention": [ + "blocks-restic.html#blocks-restic-options-shb.restic.instances._name_.settings.retention" + ], + "blocks-restic-options-shb.restic.performance": [ + "blocks-restic.html#blocks-restic-options-shb.restic.performance" + ], + "blocks-restic-options-shb.restic.performance.ioPriority": [ + "blocks-restic.html#blocks-restic-options-shb.restic.performance.ioPriority" + ], + "blocks-restic-options-shb.restic.performance.ioSchedulingClass": [ + "blocks-restic.html#blocks-restic-options-shb.restic.performance.ioSchedulingClass" + ], + "blocks-restic-options-shb.restic.performance.niceness": [ + "blocks-restic.html#blocks-restic-options-shb.restic.performance.niceness" + ], + "blocks-restic-tests": [ + "blocks-restic.html#blocks-restic-tests" + ], + "blocks-restic-usage": [ + "blocks-restic.html#blocks-restic-usage" + ], + "blocks-restic-usage-multiple": [ + "blocks-restic.html#blocks-restic-usage-multiple" + ], + "blocks-restic-usage-provider-contract": [ + "blocks-restic.html#blocks-restic-usage-provider-contract" + ], + "blocks-restic-usage-provider-manual": [ + "blocks-restic.html#blocks-restic-usage-provider-manual" + ], + "blocks-restic-usage-provider-remote": [ + "blocks-restic.html#blocks-restic-usage-provider-remote" + ], + "blocks-sops": [ + "blocks-sops.html#blocks-sops" + ], + "blocks-sops-contract-provider": [ + "blocks-sops.html#blocks-sops-contract-provider" + ], + "blocks-sops-options": [ + "blocks-sops.html#blocks-sops-options" + ], + "blocks-sops-options-shb.sops.secret": [ + "blocks-sops.html#blocks-sops-options-shb.sops.secret" + ], + "blocks-sops-options-shb.sops.secret._name_.request": [ + "blocks-sops.html#blocks-sops-options-shb.sops.secret._name_.request" + ], + "blocks-sops-options-shb.sops.secret._name_.request.group": [ + "blocks-sops.html#blocks-sops-options-shb.sops.secret._name_.request.group" + ], + "blocks-sops-options-shb.sops.secret._name_.request.mode": [ + "blocks-sops.html#blocks-sops-options-shb.sops.secret._name_.request.mode" + ], + "blocks-sops-options-shb.sops.secret._name_.request.owner": [ + "blocks-sops.html#blocks-sops-options-shb.sops.secret._name_.request.owner" + ], + "blocks-sops-options-shb.sops.secret._name_.request.restartUnits": [ + "blocks-sops.html#blocks-sops-options-shb.sops.secret._name_.request.restartUnits" + ], + "blocks-sops-options-shb.sops.secret._name_.result": [ + "blocks-sops.html#blocks-sops-options-shb.sops.secret._name_.result" + ], + "blocks-sops-options-shb.sops.secret._name_.result.path": [ + "blocks-sops.html#blocks-sops-options-shb.sops.secret._name_.result.path" + ], + "blocks-sops-options-shb.sops.secret._name_.settings": [ + "blocks-sops.html#blocks-sops-options-shb.sops.secret._name_.settings" + ], + "blocks-sops-usage": [ + "blocks-sops.html#blocks-sops-usage" + ], + "blocks-sops-usage-requester": [ + "blocks-sops.html#blocks-sops-usage-requester" + ], + "block-ssl": [ + "blocks-ssl.html#block-ssl" + ], + "block-ssl-debug": [ + "blocks-ssl.html#block-ssl-debug" + ], + "block-ssl-impl-lets-encrypt": [ + "blocks-ssl.html#block-ssl-impl-lets-encrypt" + ], + "block-ssl-impl-self-signed": [ + "blocks-ssl.html#block-ssl-impl-self-signed" + ], + "block-ssl-options": [ + "blocks-ssl.html#block-ssl-options" + ], + "block-ssl-tests": [ + "blocks-ssl.html#block-ssl-tests" + ], + "block-ssl-usage": [ + "blocks-ssl.html#block-ssl-usage" + ], + "blocks-ssl-options-shb.certs.cas.selfsigned": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.cas.selfsigned" + ], + "blocks-ssl-options-shb.certs.cas.selfsigned._name_.name": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.cas.selfsigned._name_.name" + ], + "blocks-ssl-options-shb.certs.cas.selfsigned._name_.paths": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.cas.selfsigned._name_.paths" + ], + "blocks-ssl-options-shb.certs.cas.selfsigned._name_.paths.cert": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.cas.selfsigned._name_.paths.cert" + ], + "blocks-ssl-options-shb.certs.cas.selfsigned._name_.paths.key": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.cas.selfsigned._name_.paths.key" + ], + "blocks-ssl-options-shb.certs.cas.selfsigned._name_.systemdService": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.cas.selfsigned._name_.systemdService" + ], + "blocks-ssl-options-shb.certs.certs.letsencrypt": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.letsencrypt" + ], + "blocks-ssl-options-shb.certs.certs.letsencrypt._name_.additionalEnvironment": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.letsencrypt._name_.additionalEnvironment" + ], + "blocks-ssl-options-shb.certs.certs.letsencrypt._name_.adminEmail": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.letsencrypt._name_.adminEmail" + ], + "blocks-ssl-options-shb.certs.certs.letsencrypt._name_.afterAndWants": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.letsencrypt._name_.afterAndWants" + ], + "blocks-ssl-options-shb.certs.certs.letsencrypt._name_.credentialsFile": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.letsencrypt._name_.credentialsFile" + ], + "blocks-ssl-options-shb.certs.certs.letsencrypt._name_.debug": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.letsencrypt._name_.debug" + ], + "blocks-ssl-options-shb.certs.certs.letsencrypt._name_.dnsProvider": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.letsencrypt._name_.dnsProvider" + ], + "blocks-ssl-options-shb.certs.certs.letsencrypt._name_.dnsResolver": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.letsencrypt._name_.dnsResolver" + ], + "blocks-ssl-options-shb.certs.certs.letsencrypt._name_.domain": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.letsencrypt._name_.domain" + ], + "blocks-ssl-options-shb.certs.certs.letsencrypt._name_.extraDomains": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.letsencrypt._name_.extraDomains" + ], + "blocks-ssl-options-shb.certs.certs.letsencrypt._name_.group": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.letsencrypt._name_.group" + ], + "blocks-ssl-options-shb.certs.certs.letsencrypt._name_.makeAvailableToUser": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.letsencrypt._name_.makeAvailableToUser" + ], + "blocks-ssl-options-shb.certs.certs.letsencrypt._name_.paths": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.letsencrypt._name_.paths" + ], + "blocks-ssl-options-shb.certs.certs.letsencrypt._name_.paths.cert": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.letsencrypt._name_.paths.cert" + ], + "blocks-ssl-options-shb.certs.certs.letsencrypt._name_.paths.key": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.letsencrypt._name_.paths.key" + ], + "blocks-ssl-options-shb.certs.certs.letsencrypt._name_.reloadServices": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.letsencrypt._name_.reloadServices" + ], + "blocks-ssl-options-shb.certs.certs.letsencrypt._name_.stagingServer": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.letsencrypt._name_.stagingServer" + ], + "blocks-ssl-options-shb.certs.certs.letsencrypt._name_.systemdService": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.letsencrypt._name_.systemdService" + ], + "blocks-ssl-options-shb.certs.certs.selfsigned": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.selfsigned" + ], + "blocks-ssl-options-shb.certs.certs.selfsigned._name_.ca": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.selfsigned._name_.ca" + ], + "blocks-ssl-options-shb.certs.certs.selfsigned._name_.ca.paths": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.selfsigned._name_.ca.paths" + ], + "blocks-ssl-options-shb.certs.certs.selfsigned._name_.ca.paths.cert": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.selfsigned._name_.ca.paths.cert" + ], + "blocks-ssl-options-shb.certs.certs.selfsigned._name_.ca.paths.key": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.selfsigned._name_.ca.paths.key" + ], + "blocks-ssl-options-shb.certs.certs.selfsigned._name_.ca.systemdService": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.selfsigned._name_.ca.systemdService" + ], + "blocks-ssl-options-shb.certs.certs.selfsigned._name_.domain": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.selfsigned._name_.domain" + ], + "blocks-ssl-options-shb.certs.certs.selfsigned._name_.extraDomains": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.selfsigned._name_.extraDomains" + ], + "blocks-ssl-options-shb.certs.certs.selfsigned._name_.group": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.selfsigned._name_.group" + ], + "blocks-ssl-options-shb.certs.certs.selfsigned._name_.paths": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.selfsigned._name_.paths" + ], + "blocks-ssl-options-shb.certs.certs.selfsigned._name_.paths.cert": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.selfsigned._name_.paths.cert" + ], + "blocks-ssl-options-shb.certs.certs.selfsigned._name_.paths.key": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.selfsigned._name_.paths.key" + ], + "blocks-ssl-options-shb.certs.certs.selfsigned._name_.reloadServices": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.selfsigned._name_.reloadServices" + ], + "blocks-ssl-options-shb.certs.certs.selfsigned._name_.systemdService": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.certs.selfsigned._name_.systemdService" + ], + "blocks-ssl-options-shb.certs.systemdService": [ + "blocks-ssl.html#blocks-ssl-options-shb.certs.systemdService" + ], + "contract-backup": [ + "contracts-backup.html#contract-backup" + ], + "contract-backup-options": [ + "contracts-backup.html#contract-backup-options" + ], + "contract-backup-providers": [ + "contracts-backup.html#contract-backup-providers" + ], + "contract-backup-requesters": [ + "contracts-backup.html#contract-backup-requesters" + ], + "contract-backup-usage": [ + "contracts-backup.html#contract-backup-usage" + ], + "contract-databasebackup": [ + "contracts-databasebackup.html#contract-databasebackup" + ], + "contract-databasebackup-options": [ + "contracts-databasebackup.html#contract-databasebackup-options" + ], + "contract-databasebackup-providers": [ + "contracts-databasebackup.html#contract-databasebackup-providers" + ], + "contract-databasebackup-requesters": [ + "contracts-databasebackup.html#contract-databasebackup-requesters" + ], + "contract-databasebackup-usage": [ + "contracts-databasebackup.html#contract-databasebackup-usage" + ], + "contract-secret": [ + "contracts-secret.html#contract-secret" + ], + "contract-secret-motivation": [ + "contracts-secret.html#contract-secret-motivation" + ], + "contract-secret-options": [ + "contracts-secret.html#contract-secret-options" + ], + "contract-secret-usage": [ + "contracts-secret.html#contract-secret-usage" + ], + "contract-secret-usage-enduser": [ + "contracts-secret.html#contract-secret-usage-enduser" + ], + "contract-secret-usage-provider": [ + "contracts-secret.html#contract-secret-usage-provider" + ], + "contract-secret-usage-requester": [ + "contracts-secret.html#contract-secret-usage-requester" + ], + "contract-ssl": [ + "contracts-ssl.html#contract-ssl" + ], + "contract-ssl-impl-custom": [ + "contracts-ssl.html#contract-ssl-impl-custom" + ], + "contract-ssl-impl-shb": [ + "contracts-ssl.html#contract-ssl-impl-shb" + ], + "contract-ssl-options": [ + "contracts-ssl.html#contract-ssl-options" + ], + "contract-ssl-usage": [ + "contracts-ssl.html#contract-ssl-usage" + ], + "contracts": [ + "contracts.html#contracts" + ], + "contracts-nixpkgs": [ + "contracts.html#contracts-nixpkgs" + ], + "contracts-provided": [ + "contracts.html#contracts-provided" + ], + "contracts-schema": [ + "contracts.html#contracts-schema" + ], + "contracts-test": [ + "contracts.html#contracts-test" + ], + "contracts-videos": [ + "contracts.html#contracts-videos" + ], + "contracts-why": [ + "contracts.html#contracts-why" + ], + "contracts-backup-options-shb.contracts.backup": [ + "contracts-backup.html#contracts-backup-options-shb.contracts.backup" + ], + "contracts-backup-options-shb.contracts.backup.request": [ + "contracts-backup.html#contracts-backup-options-shb.contracts.backup.request" + ], + "contracts-backup-options-shb.contracts.backup.request.excludePatterns": [ + "contracts-backup.html#contracts-backup-options-shb.contracts.backup.request.excludePatterns" + ], + "contracts-backup-options-shb.contracts.backup.request.hooks": [ + "contracts-backup.html#contracts-backup-options-shb.contracts.backup.request.hooks" + ], + "contracts-backup-options-shb.contracts.backup.request.hooks.after_backup": [ + "contracts-backup.html#contracts-backup-options-shb.contracts.backup.request.hooks.after_backup" + ], + "contracts-backup-options-shb.contracts.backup.request.hooks.before_backup": [ + "contracts-backup.html#contracts-backup-options-shb.contracts.backup.request.hooks.before_backup" + ], + "contracts-backup-options-shb.contracts.backup.request.sourceDirectories": [ + "contracts-backup.html#contracts-backup-options-shb.contracts.backup.request.sourceDirectories" + ], + "contracts-backup-options-shb.contracts.backup.request.user": [ + "contracts-backup.html#contracts-backup-options-shb.contracts.backup.request.user" + ], + "contracts-backup-options-shb.contracts.backup.result": [ + "contracts-backup.html#contracts-backup-options-shb.contracts.backup.result" + ], + "contracts-backup-options-shb.contracts.backup.result.backupService": [ + "contracts-backup.html#contracts-backup-options-shb.contracts.backup.result.backupService" + ], + "contracts-backup-options-shb.contracts.backup.result.restoreScript": [ + "contracts-backup.html#contracts-backup-options-shb.contracts.backup.result.restoreScript" + ], + "contracts-databasebackup-options-shb.contracts.databasebackup": [ + "contracts-databasebackup.html#contracts-databasebackup-options-shb.contracts.databasebackup" + ], + "contracts-databasebackup-options-shb.contracts.databasebackup.request": [ + "contracts-databasebackup.html#contracts-databasebackup-options-shb.contracts.databasebackup.request" + ], + "contracts-databasebackup-options-shb.contracts.databasebackup.request.backupCmd": [ + "contracts-databasebackup.html#contracts-databasebackup-options-shb.contracts.databasebackup.request.backupCmd" + ], + "contracts-databasebackup-options-shb.contracts.databasebackup.request.backupName": [ + "contracts-databasebackup.html#contracts-databasebackup-options-shb.contracts.databasebackup.request.backupName" + ], + "contracts-databasebackup-options-shb.contracts.databasebackup.request.restoreCmd": [ + "contracts-databasebackup.html#contracts-databasebackup-options-shb.contracts.databasebackup.request.restoreCmd" + ], + "contracts-databasebackup-options-shb.contracts.databasebackup.request.user": [ + "contracts-databasebackup.html#contracts-databasebackup-options-shb.contracts.databasebackup.request.user" + ], + "contracts-databasebackup-options-shb.contracts.databasebackup.result": [ + "contracts-databasebackup.html#contracts-databasebackup-options-shb.contracts.databasebackup.result" + ], + "contracts-databasebackup-options-shb.contracts.databasebackup.result.backupService": [ + "contracts-databasebackup.html#contracts-databasebackup-options-shb.contracts.databasebackup.result.backupService" + ], + "contracts-databasebackup-options-shb.contracts.databasebackup.result.restoreScript": [ + "contracts-databasebackup.html#contracts-databasebackup-options-shb.contracts.databasebackup.result.restoreScript" + ], + "contracts-secret-options-shb.contracts.secret": [ + "contracts-secret.html#contracts-secret-options-shb.contracts.secret" + ], + "contracts-secret-options-shb.contracts.secret.request": [ + "contracts-secret.html#contracts-secret-options-shb.contracts.secret.request" + ], + "contracts-secret-options-shb.contracts.secret.request.group": [ + "contracts-secret.html#contracts-secret-options-shb.contracts.secret.request.group" + ], + "contracts-secret-options-shb.contracts.secret.request.mode": [ + "contracts-secret.html#contracts-secret-options-shb.contracts.secret.request.mode" + ], + "contracts-secret-options-shb.contracts.secret.request.owner": [ + "contracts-secret.html#contracts-secret-options-shb.contracts.secret.request.owner" + ], + "contracts-secret-options-shb.contracts.secret.request.restartUnits": [ + "contracts-secret.html#contracts-secret-options-shb.contracts.secret.request.restartUnits" + ], + "contracts-secret-options-shb.contracts.secret.result": [ + "contracts-secret.html#contracts-secret-options-shb.contracts.secret.result" + ], + "contracts-secret-options-shb.contracts.secret.result.path": [ + "contracts-secret.html#contracts-secret-options-shb.contracts.secret.result.path" + ], + "contracts-secret-options-shb.contracts.secret.settings": [ + "contracts-secret.html#contracts-secret-options-shb.contracts.secret.settings" + ], + "contracts-ssl-options-shb.contracts.ssl": [ + "contracts-ssl.html#contracts-ssl-options-shb.contracts.ssl" + ], + "contracts-ssl-options-shb.contracts.ssl.paths": [ + "contracts-ssl.html#contracts-ssl-options-shb.contracts.ssl.paths" + ], + "contracts-ssl-options-shb.contracts.ssl.paths.cert": [ + "contracts-ssl.html#contracts-ssl-options-shb.contracts.ssl.paths.cert" + ], + "contracts-ssl-options-shb.contracts.ssl.paths.key": [ + "contracts-ssl.html#contracts-ssl-options-shb.contracts.ssl.paths.key" + ], + "contracts-ssl-options-shb.contracts.ssl.systemdService": [ + "contracts-ssl.html#contracts-ssl-options-shb.contracts.ssl.systemdService" + ], + "contributing": [ + "contributing.html#contributing" + ], + "contributing-chat": [ + "contributing.html#contributing-chat" + ], + "contributing-deploy-colmena": [ + "contributing.html#contributing-deploy-colmena" + ], + "contributing-diff": [ + "contributing.html#contributing-diff" + ], + "contributing-diff-deployed": [ + "contributing.html#contributing-diff-deployed" + ], + "contributing-diff-full": [ + "contributing.html#contributing-diff-full" + ], + "contributing-diff-todeploy": [ + "contributing.html#contributing-diff-todeploy" + ], + "contributing-diff-version": [ + "contributing.html#contributing-diff-version" + ], + "contributing-gensecret": [ + "contributing.html#contributing-gensecret" + ], + "contributing-links": [ + "contributing.html#contributing-links" + ], + "contributing-localversion": [ + "contributing.html#contributing-localversion" + ], + "contributing-runtests": [ + "contributing.html#contributing-runtests" + ], + "contributing-upload": [ + "contributing.html#contributing-upload" + ], + "contributing-upstream": [ + "contributing.html#contributing-upstream" + ], + "demo-homeassistant": [ + "demo-homeassistant.html#demo-homeassistant" + ], + "demo-homeassistant-deploy": [ + "demo-homeassistant.html#demo-homeassistant-deploy" + ], + "demo-homeassistant-deploy-basic": [ + "demo-homeassistant.html#demo-homeassistant-deploy-basic" + ], + "demo-homeassistant-deploy-ldap": [ + "demo-homeassistant.html#demo-homeassistant-deploy-ldap" + ], + "demo-homeassistant-deploy-nixosrebuild": [ + "demo-homeassistant.html#demo-homeassistant-deploy-nixosrebuild" + ], + "demo-homeassistant-files": [ + "demo-homeassistant.html#demo-homeassistant-files" + ], + "demo-homeassistant-in-more-details": [ + "demo-homeassistant.html#demo-homeassistant-in-more-details" + ], + "demo-homeassistant-secrets": [ + "demo-homeassistant.html#demo-homeassistant-secrets" + ], + "demo-homeassistant-tips-deploy": [ + "demo-homeassistant.html#demo-homeassistant-tips-deploy" + ], + "demo-homeassistant-tips-public-key-necessity": [ + "demo-homeassistant.html#demo-homeassistant-tips-public-key-necessity" + ], + "demo-homeassistant-tips-ssh": [ + "demo-homeassistant.html#demo-homeassistant-tips-ssh" + ], + "demo-homeassistant-tips-update-demo": [ + "demo-homeassistant.html#demo-homeassistant-tips-update-demo" + ], + "demo-homeassistant-virtual-machine": [ + "demo-homeassistant.html#demo-homeassistant-virtual-machine" + ], + "demo-homeassistant-deploy-colmena": [ + "demo-homeassistant.html#demo-homeassistant-deploy-colmena" + ], + "demo-nextcloud": [ + "demo-nextcloud.html#demo-nextcloud" + ], + "demo-nextcloud-deploy": [ + "demo-nextcloud.html#demo-nextcloud-deploy" + ], + "demo-nextcloud-deploy-basic": [ + "demo-nextcloud.html#demo-nextcloud-deploy-basic" + ], + "demo-nextcloud-deploy-colmena": [ + "demo-nextcloud.html#demo-nextcloud-deploy-colmena" + ], + "demo-nextcloud-deploy-ldap": [ + "demo-nextcloud.html#demo-nextcloud-deploy-ldap" + ], + "demo-nextcloud-deploy-nixosrebuild": [ + "demo-nextcloud.html#demo-nextcloud-deploy-nixosrebuild" + ], + "demo-nextcloud-deploy-sso": [ + "demo-nextcloud.html#demo-nextcloud-deploy-sso" + ], + "demo-nextcloud-tips": [ + "demo-nextcloud.html#demo-nextcloud-tips" + ], + "demo-nextcloud-tips-deploy": [ + "demo-nextcloud.html#demo-nextcloud-tips-deploy" + ], + "demo-nextcloud-tips-files": [ + "demo-nextcloud.html#demo-nextcloud-tips-files" + ], + "demo-nextcloud-tips-public-key-necessity": [ + "demo-nextcloud.html#demo-nextcloud-tips-public-key-necessity" + ], + "demo-nextcloud-tips-secrets": [ + "demo-nextcloud.html#demo-nextcloud-tips-secrets" + ], + "demo-nextcloud-tips-ssh": [ + "demo-nextcloud.html#demo-nextcloud-tips-ssh" + ], + "demo-nextcloud-tips-update-demo": [ + "demo-nextcloud.html#demo-nextcloud-tips-update-demo" + ], + "demo-nextcloud-tips-virtual-machine": [ + "demo-nextcloud.html#demo-nextcloud-tips-virtual-machine" + ], + "demos": [ + "demos.html#demos" + ], + "preface": [ + "index.html#preface" + ], + "self-host-blocks-manual": [ + "index.html#self-host-blocks-manual" + ], + "services": [ + "services.html#services" + ], + "services-forgejo": [ + "services-forgejo.html#services-forgejo" + ], + "services-forgejo-debug": [ + "services-forgejo.html#services-forgejo-debug" + ], + "services-forgejo-features": [ + "services-forgejo.html#services-forgejo-features" + ], + "services-forgejo-options": [ + "services-forgejo.html#services-forgejo-options" + ], + "services-forgejo-options-shb.forgejo.adminPassword": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.adminPassword" + ], + "services-forgejo-options-shb.forgejo.adminPassword.request": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.adminPassword.request" + ], + "services-forgejo-options-shb.forgejo.adminPassword.request.group": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.adminPassword.request.group" + ], + "services-forgejo-options-shb.forgejo.adminPassword.request.mode": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.adminPassword.request.mode" + ], + "services-forgejo-options-shb.forgejo.adminPassword.request.owner": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.adminPassword.request.owner" + ], + "services-forgejo-options-shb.forgejo.adminPassword.request.restartUnits": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.adminPassword.request.restartUnits" + ], + "services-forgejo-options-shb.forgejo.adminPassword.result": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.adminPassword.result" + ], + "services-forgejo-options-shb.forgejo.adminPassword.result.path": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.adminPassword.result.path" + ], + "services-forgejo-options-shb.forgejo.backup": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.backup" + ], + "services-forgejo-options-shb.forgejo.backup.excludePatterns": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.backup.excludePatterns" + ], + "services-forgejo-options-shb.forgejo.backup.hooks": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.backup.hooks" + ], + "services-forgejo-options-shb.forgejo.backup.hooks.after_backup": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.backup.hooks.after_backup" + ], + "services-forgejo-options-shb.forgejo.backup.hooks.before_backup": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.backup.hooks.before_backup" + ], + "services-forgejo-options-shb.forgejo.backup.sourceDirectories": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.backup.sourceDirectories" + ], + "services-forgejo-options-shb.forgejo.backup.user": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.backup.user" + ], + "services-forgejo-options-shb.forgejo.databasePassword": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.databasePassword" + ], + "services-forgejo-options-shb.forgejo.databasePassword.request": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.databasePassword.request" + ], + "services-forgejo-options-shb.forgejo.databasePassword.request.group": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.databasePassword.request.group" + ], + "services-forgejo-options-shb.forgejo.databasePassword.request.mode": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.databasePassword.request.mode" + ], + "services-forgejo-options-shb.forgejo.databasePassword.request.owner": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.databasePassword.request.owner" + ], + "services-forgejo-options-shb.forgejo.databasePassword.request.restartUnits": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.databasePassword.request.restartUnits" + ], + "services-forgejo-options-shb.forgejo.databasePassword.result": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.databasePassword.result" + ], + "services-forgejo-options-shb.forgejo.databasePassword.result.path": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.databasePassword.result.path" + ], + "services-forgejo-options-shb.forgejo.debug": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.debug" + ], + "services-forgejo-options-shb.forgejo.domain": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.domain" + ], + "services-forgejo-options-shb.forgejo.enable": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.enable" + ], + "services-forgejo-options-shb.forgejo.hostPackages": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.hostPackages" + ], + "services-forgejo-options-shb.forgejo.ldap": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.ldap" + ], + "services-forgejo-options-shb.forgejo.ldap.adminGroup": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.ldap.adminGroup" + ], + "services-forgejo-options-shb.forgejo.ldap.adminName": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.ldap.adminName" + ], + "services-forgejo-options-shb.forgejo.ldap.adminPassword": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.ldap.adminPassword" + ], + "services-forgejo-options-shb.forgejo.ldap.adminPassword.request": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.ldap.adminPassword.request" + ], + "services-forgejo-options-shb.forgejo.ldap.adminPassword.request.group": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.ldap.adminPassword.request.group" + ], + "services-forgejo-options-shb.forgejo.ldap.adminPassword.request.mode": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.ldap.adminPassword.request.mode" + ], + "services-forgejo-options-shb.forgejo.ldap.adminPassword.request.owner": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.ldap.adminPassword.request.owner" + ], + "services-forgejo-options-shb.forgejo.ldap.adminPassword.request.restartUnits": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.ldap.adminPassword.request.restartUnits" + ], + "services-forgejo-options-shb.forgejo.ldap.adminPassword.result": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.ldap.adminPassword.result" + ], + "services-forgejo-options-shb.forgejo.ldap.adminPassword.result.path": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.ldap.adminPassword.result.path" + ], + "services-forgejo-options-shb.forgejo.ldap.dcdomain": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.ldap.dcdomain" + ], + "services-forgejo-options-shb.forgejo.ldap.enable": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.ldap.enable" + ], + "services-forgejo-options-shb.forgejo.ldap.host": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.ldap.host" + ], + "services-forgejo-options-shb.forgejo.ldap.port": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.ldap.port" + ], + "services-forgejo-options-shb.forgejo.ldap.provider": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.ldap.provider" + ], + "services-forgejo-options-shb.forgejo.ldap.userGroup": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.ldap.userGroup" + ], + "services-forgejo-options-shb.forgejo.localActionRunner": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.localActionRunner" + ], + "services-forgejo-options-shb.forgejo.mount": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.mount" + ], + "services-forgejo-options-shb.forgejo.mount.path": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.mount.path" + ], + "services-forgejo-options-shb.forgejo.repositoryRoot": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.repositoryRoot" + ], + "services-forgejo-options-shb.forgejo.smtp": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.smtp" + ], + "services-forgejo-options-shb.forgejo.smtp.from_address": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.smtp.from_address" + ], + "services-forgejo-options-shb.forgejo.smtp.host": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.smtp.host" + ], + "services-forgejo-options-shb.forgejo.smtp.passwordFile": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.smtp.passwordFile" + ], + "services-forgejo-options-shb.forgejo.smtp.port": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.smtp.port" + ], + "services-forgejo-options-shb.forgejo.smtp.username": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.smtp.username" + ], + "services-forgejo-options-shb.forgejo.ssl": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.ssl" + ], + "services-forgejo-options-shb.forgejo.ssl.paths": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.ssl.paths" + ], + "services-forgejo-options-shb.forgejo.ssl.paths.cert": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.ssl.paths.cert" + ], + "services-forgejo-options-shb.forgejo.ssl.paths.key": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.ssl.paths.key" + ], + "services-forgejo-options-shb.forgejo.ssl.systemdService": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.ssl.systemdService" + ], + "services-forgejo-options-shb.forgejo.sso": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.sso" + ], + "services-forgejo-options-shb.forgejo.sso.authorization_policy": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.sso.authorization_policy" + ], + "services-forgejo-options-shb.forgejo.sso.clientID": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.sso.clientID" + ], + "services-forgejo-options-shb.forgejo.sso.enable": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.sso.enable" + ], + "services-forgejo-options-shb.forgejo.sso.endpoint": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.sso.endpoint" + ], + "services-forgejo-options-shb.forgejo.sso.provider": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.sso.provider" + ], + "services-forgejo-options-shb.forgejo.sso.sharedSecret": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecret" + ], + "services-forgejo-options-shb.forgejo.sso.sharedSecretForAuthelia": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecretForAuthelia" + ], + "services-forgejo-options-shb.forgejo.sso.sharedSecretForAuthelia.request": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecretForAuthelia.request" + ], + "services-forgejo-options-shb.forgejo.sso.sharedSecretForAuthelia.request.group": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecretForAuthelia.request.group" + ], + "services-forgejo-options-shb.forgejo.sso.sharedSecretForAuthelia.request.mode": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecretForAuthelia.request.mode" + ], + "services-forgejo-options-shb.forgejo.sso.sharedSecretForAuthelia.request.owner": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecretForAuthelia.request.owner" + ], + "services-forgejo-options-shb.forgejo.sso.sharedSecretForAuthelia.request.restartUnits": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecretForAuthelia.request.restartUnits" + ], + "services-forgejo-options-shb.forgejo.sso.sharedSecretForAuthelia.result": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecretForAuthelia.result" + ], + "services-forgejo-options-shb.forgejo.sso.sharedSecretForAuthelia.result.path": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecretForAuthelia.result.path" + ], + "services-forgejo-options-shb.forgejo.sso.sharedSecret.request": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecret.request" + ], + "services-forgejo-options-shb.forgejo.sso.sharedSecret.request.group": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecret.request.group" + ], + "services-forgejo-options-shb.forgejo.sso.sharedSecret.request.mode": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecret.request.mode" + ], + "services-forgejo-options-shb.forgejo.sso.sharedSecret.request.owner": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecret.request.owner" + ], + "services-forgejo-options-shb.forgejo.sso.sharedSecret.request.restartUnits": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecret.request.restartUnits" + ], + "services-forgejo-options-shb.forgejo.sso.sharedSecret.result": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecret.result" + ], + "services-forgejo-options-shb.forgejo.sso.sharedSecret.result.path": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecret.result.path" + ], + "services-forgejo-options-shb.forgejo.subdomain": [ + "services-forgejo.html#services-forgejo-options-shb.forgejo.subdomain" + ], + "services-forgejo-secrets": [ + "services-forgejo.html#services-forgejo-secrets" + ], + "services-forgejo-usage": [ + "services-forgejo.html#services-forgejo-usage" + ], + "services-forgejo-usage-backup": [ + "services-forgejo.html#services-forgejo-usage-backup" + ], + "services-forgejo-usage-basic": [ + "services-forgejo.html#services-forgejo-usage-basic" + ], + "services-forgejo-usage-extra-settings": [ + "services-forgejo.html#services-forgejo-usage-extra-settings" + ], + "services-forgejo-usage-ldap": [ + "services-forgejo.html#services-forgejo-usage-ldap" + ], + "services-forgejo-usage-sso": [ + "services-forgejo.html#services-forgejo-usage-sso" + ], + "services-nextcloudserver": [ + "services-nextcloud.html#services-nextcloudserver" + ], + "services-nextcloudserver-debug": [ + "services-nextcloud.html#services-nextcloudserver-debug" + ], + "services-nextcloudserver-demo": [ + "services-nextcloud.html#services-nextcloudserver-demo" + ], + "services-nextcloudserver-features": [ + "services-nextcloud.html#services-nextcloudserver-features" + ], + "services-nextcloudserver-maintenance": [ + "services-nextcloud.html#services-nextcloudserver-maintenance" + ], + "services-nextcloudserver-options": [ + "services-nextcloud.html#services-nextcloudserver-options" + ], + "services-nextcloudserver-options-shb.nextcloud.adminPass": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.adminPass" + ], + "services-nextcloudserver-options-shb.nextcloud.adminPass.request": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.adminPass.request" + ], + "services-nextcloudserver-options-shb.nextcloud.adminPass.request.group": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.adminPass.request.group" + ], + "services-nextcloudserver-options-shb.nextcloud.adminPass.request.mode": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.adminPass.request.mode" + ], + "services-nextcloudserver-options-shb.nextcloud.adminPass.request.owner": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.adminPass.request.owner" + ], + "services-nextcloudserver-options-shb.nextcloud.adminPass.request.restartUnits": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.adminPass.request.restartUnits" + ], + "services-nextcloudserver-options-shb.nextcloud.adminPass.result": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.adminPass.result" + ], + "services-nextcloudserver-options-shb.nextcloud.adminPass.result.path": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.adminPass.result.path" + ], + "services-nextcloudserver-options-shb.nextcloud.adminUser": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.adminUser" + ], + "services-nextcloudserver-options-shb.nextcloud.alwaysApplyExpensiveMigrations": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.alwaysApplyExpensiveMigrations" + ], + "services-nextcloudserver-options-shb.nextcloud.apps": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.externalStorage": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.externalStorage" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.externalStorage.enable": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.externalStorage.enable" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.externalStorage.userLocalMount": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.externalStorage.userLocalMount" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.externalStorage.userLocalMount.directory": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.externalStorage.userLocalMount.directory" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.externalStorage.userLocalMount.mountName": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.externalStorage.userLocalMount.mountName" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.ldap": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.ldap" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.ldap.adminName": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.ldap.adminName" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.ldap.adminPassword": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.ldap.adminPassword" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.ldap.adminPassword.request": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.ldap.adminPassword.request" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.ldap.adminPassword.request.group": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.ldap.adminPassword.request.group" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.ldap.adminPassword.request.mode": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.ldap.adminPassword.request.mode" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.ldap.adminPassword.request.owner": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.ldap.adminPassword.request.owner" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.ldap.adminPassword.request.restartUnits": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.ldap.adminPassword.request.restartUnits" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.ldap.adminPassword.result": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.ldap.adminPassword.result" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.ldap.adminPassword.result.path": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.ldap.adminPassword.result.path" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.ldap.configID": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.ldap.configID" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.ldap.dcdomain": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.ldap.dcdomain" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.ldap.enable": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.ldap.enable" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.ldap.host": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.ldap.host" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.ldap.port": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.ldap.port" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.ldap.userGroup": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.ldap.userGroup" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.onlyoffice": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.onlyoffice" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.onlyoffice.enable": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.onlyoffice.enable" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.onlyoffice.jwtSecretFile": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.onlyoffice.jwtSecretFile" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.onlyoffice.localNetworkIPRange": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.onlyoffice.localNetworkIPRange" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.onlyoffice.ssl": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.onlyoffice.ssl" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.onlyoffice.ssl.paths": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.onlyoffice.ssl.paths" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.onlyoffice.ssl.paths.cert": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.onlyoffice.ssl.paths.cert" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.onlyoffice.ssl.paths.key": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.onlyoffice.ssl.paths.key" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.onlyoffice.ssl.systemdService": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.onlyoffice.ssl.systemdService" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.onlyoffice.subdomain": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.onlyoffice.subdomain" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.previewgenerator": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.previewgenerator" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.previewgenerator.debug": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.previewgenerator.debug" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.previewgenerator.enable": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.previewgenerator.enable" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.previewgenerator.recommendedSettings": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.previewgenerator.recommendedSettings" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.sso": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.sso.authorization_policy": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.authorization_policy" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.sso.clientID": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.clientID" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.sso.enable": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.enable" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.sso.endpoint": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.endpoint" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.sso.fallbackDefaultAuth": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.fallbackDefaultAuth" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.sso.port": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.port" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.sso.provider": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.provider" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.sso.secret": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secret" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.sso.secretForAuthelia": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secretForAuthelia" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.sso.secretForAuthelia.request": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secretForAuthelia.request" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.sso.secretForAuthelia.request.group": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secretForAuthelia.request.group" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.sso.secretForAuthelia.request.mode": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secretForAuthelia.request.mode" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.sso.secretForAuthelia.request.owner": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secretForAuthelia.request.owner" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.sso.secretForAuthelia.request.restartUnits": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secretForAuthelia.request.restartUnits" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.sso.secretForAuthelia.result": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secretForAuthelia.result" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.sso.secretForAuthelia.result.path": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secretForAuthelia.result.path" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request.group": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request.group" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request.mode": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request.mode" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request.owner": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request.owner" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request.restartUnits": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request.restartUnits" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.result": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.result" + ], + "services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.result.path": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.result.path" + ], + "services-nextcloudserver-options-shb.nextcloud.autoDisableMaintenanceModeOnStart": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.autoDisableMaintenanceModeOnStart" + ], + "services-nextcloudserver-options-shb.nextcloud.backup": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.backup" + ], + "services-nextcloudserver-options-shb.nextcloud.backup.excludePatterns": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.backup.excludePatterns" + ], + "services-nextcloudserver-options-shb.nextcloud.backup.hooks": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.backup.hooks" + ], + "services-nextcloudserver-options-shb.nextcloud.backup.hooks.after_backup": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.backup.hooks.after_backup" + ], + "services-nextcloudserver-options-shb.nextcloud.backup.hooks.before_backup": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.backup.hooks.before_backup" + ], + "services-nextcloudserver-options-shb.nextcloud.backup.sourceDirectories": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.backup.sourceDirectories" + ], + "services-nextcloudserver-options-shb.nextcloud.backup.user": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.backup.user" + ], + "services-nextcloudserver-options-shb.nextcloud.dataDir": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.dataDir" + ], + "services-nextcloudserver-options-shb.nextcloud.debug": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.debug" + ], + "services-nextcloudserver-options-shb.nextcloud.defaultPhoneRegion": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.defaultPhoneRegion" + ], + "services-nextcloudserver-options-shb.nextcloud.domain": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.domain" + ], + "services-nextcloudserver-options-shb.nextcloud.enable": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.enable" + ], + "services-nextcloudserver-options-shb.nextcloud.externalFqdn": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.externalFqdn" + ], + "services-nextcloudserver-options-shb.nextcloud.extraApps": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.extraApps" + ], + "services-nextcloudserver-options-shb.nextcloud.maxUploadSize": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.maxUploadSize" + ], + "services-nextcloudserver-options-shb.nextcloud.mountPointServices": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.mountPointServices" + ], + "services-nextcloudserver-options-shb.nextcloud.phpFpmPoolSettings": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.phpFpmPoolSettings" + ], + "services-nextcloudserver-options-shb.nextcloud.port": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.port" + ], + "services-nextcloudserver-options-shb.nextcloud.postgresSettings": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.postgresSettings" + ], + "services-nextcloudserver-options-shb.nextcloud.ssl": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.ssl" + ], + "services-nextcloudserver-options-shb.nextcloud.ssl.paths": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.ssl.paths" + ], + "services-nextcloudserver-options-shb.nextcloud.ssl.paths.cert": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.ssl.paths.cert" + ], + "services-nextcloudserver-options-shb.nextcloud.ssl.paths.key": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.ssl.paths.key" + ], + "services-nextcloudserver-options-shb.nextcloud.ssl.systemdService": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.ssl.systemdService" + ], + "services-nextcloudserver-options-shb.nextcloud.subdomain": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.subdomain" + ], + "services-nextcloudserver-options-shb.nextcloud.tracing": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.tracing" + ], + "services-nextcloudserver-options-shb.nextcloud.version": [ + "services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.version" + ], + "services-nextcloudserver-server-usage-appdata": [ + "services-nextcloud.html#services-nextcloudserver-server-usage-appdata" + ], + "services-nextcloudserver-server-usage-monitoring": [ + "services-nextcloud.html#services-nextcloudserver-server-usage-monitoring" + ], + "services-nextcloudserver-server-usage-tracing": [ + "services-nextcloud.html#services-nextcloudserver-server-usage-tracing" + ], + "services-nextcloudserver-usage": [ + "services-nextcloud.html#services-nextcloudserver-usage" + ], + "services-nextcloudserver-usage-backup": [ + "services-nextcloud.html#services-nextcloudserver-usage-backup" + ], + "services-nextcloudserver-usage-basic": [ + "services-nextcloud.html#services-nextcloudserver-usage-basic" + ], + "services-nextcloudserver-usage-externalstorage": [ + "services-nextcloud.html#services-nextcloudserver-usage-externalstorage" + ], + "services-nextcloudserver-usage-https": [ + "services-nextcloud.html#services-nextcloudserver-usage-https" + ], + "services-nextcloudserver-usage-ldap": [ + "services-nextcloud.html#services-nextcloudserver-usage-ldap" + ], + "services-nextcloudserver-usage-mount-point": [ + "services-nextcloud.html#services-nextcloudserver-usage-mount-point" + ], + "services-nextcloudserver-usage-oidc": [ + "services-nextcloud.html#services-nextcloudserver-usage-oidc" + ], + "services-nextcloudserver-usage-onlyoffice": [ + "services-nextcloud.html#services-nextcloudserver-usage-onlyoffice" + ], + "services-nextcloudserver-usage-phpfpm": [ + "services-nextcloud.html#services-nextcloudserver-usage-phpfpm" + ], + "services-nextcloudserver-usage-postgres": [ + "services-nextcloud.html#services-nextcloudserver-usage-postgres" + ], + "services-nextcloudserver-usage-previewgenerator": [ + "services-nextcloud.html#services-nextcloudserver-usage-previewgenerator" + ], + "services-nextcloudserver-usage-version": [ + "services-nextcloud.html#services-nextcloudserver-usage-version" + ], + "services-vaultwarden": [ + "services-vaultwarden.html#services-vaultwarden" + ], + "services-vaultwarden-backup": [ + "services-vaultwarden.html#services-vaultwarden-backup" + ], + "services-vaultwarden-debug": [ + "services-vaultwarden.html#services-vaultwarden-debug" + ], + "services-vaultwarden-features": [ + "services-vaultwarden.html#services-vaultwarden-features" + ], + "services-vaultwarden-maintenance": [ + "services-vaultwarden.html#services-vaultwarden-maintenance" + ], + "services-vaultwarden-options": [ + "services-vaultwarden.html#services-vaultwarden-options" + ], + "services-vaultwarden-options-shb.vaultwarden.authEndpoint": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.authEndpoint" + ], + "services-vaultwarden-options-shb.vaultwarden.backup": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.backup" + ], + "services-vaultwarden-options-shb.vaultwarden.backup.excludePatterns": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.backup.excludePatterns" + ], + "services-vaultwarden-options-shb.vaultwarden.backup.hooks": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.backup.hooks" + ], + "services-vaultwarden-options-shb.vaultwarden.backup.hooks.after_backup": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.backup.hooks.after_backup" + ], + "services-vaultwarden-options-shb.vaultwarden.backup.hooks.before_backup": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.backup.hooks.before_backup" + ], + "services-vaultwarden-options-shb.vaultwarden.backup.sourceDirectories": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.backup.sourceDirectories" + ], + "services-vaultwarden-options-shb.vaultwarden.backup.user": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.backup.user" + ], + "services-vaultwarden-options-shb.vaultwarden.databasePassword": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.databasePassword" + ], + "services-vaultwarden-options-shb.vaultwarden.databasePassword.request": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.databasePassword.request" + ], + "services-vaultwarden-options-shb.vaultwarden.databasePassword.request.group": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.databasePassword.request.group" + ], + "services-vaultwarden-options-shb.vaultwarden.databasePassword.request.mode": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.databasePassword.request.mode" + ], + "services-vaultwarden-options-shb.vaultwarden.databasePassword.request.owner": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.databasePassword.request.owner" + ], + "services-vaultwarden-options-shb.vaultwarden.databasePassword.request.restartUnits": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.databasePassword.request.restartUnits" + ], + "services-vaultwarden-options-shb.vaultwarden.databasePassword.result": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.databasePassword.result" + ], + "services-vaultwarden-options-shb.vaultwarden.databasePassword.result.path": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.databasePassword.result.path" + ], + "services-vaultwarden-options-shb.vaultwarden.debug": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.debug" + ], + "services-vaultwarden-options-shb.vaultwarden.domain": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.domain" + ], + "services-vaultwarden-options-shb.vaultwarden.enable": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.enable" + ], + "services-vaultwarden-options-shb.vaultwarden.mount": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.mount" + ], + "services-vaultwarden-options-shb.vaultwarden.mount.path": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.mount.path" + ], + "services-vaultwarden-options-shb.vaultwarden.port": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.port" + ], + "services-vaultwarden-options-shb.vaultwarden.smtp": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.smtp" + ], + "services-vaultwarden-options-shb.vaultwarden.smtp.auth_mechanism": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.smtp.auth_mechanism" + ], + "services-vaultwarden-options-shb.vaultwarden.smtp.from_address": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.smtp.from_address" + ], + "services-vaultwarden-options-shb.vaultwarden.smtp.from_name": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.smtp.from_name" + ], + "services-vaultwarden-options-shb.vaultwarden.smtp.host": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.smtp.host" + ], + "services-vaultwarden-options-shb.vaultwarden.smtp.password": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.smtp.password" + ], + "services-vaultwarden-options-shb.vaultwarden.smtp.password.request": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.smtp.password.request" + ], + "services-vaultwarden-options-shb.vaultwarden.smtp.password.request.group": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.smtp.password.request.group" + ], + "services-vaultwarden-options-shb.vaultwarden.smtp.password.request.mode": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.smtp.password.request.mode" + ], + "services-vaultwarden-options-shb.vaultwarden.smtp.password.request.owner": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.smtp.password.request.owner" + ], + "services-vaultwarden-options-shb.vaultwarden.smtp.password.request.restartUnits": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.smtp.password.request.restartUnits" + ], + "services-vaultwarden-options-shb.vaultwarden.smtp.password.result": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.smtp.password.result" + ], + "services-vaultwarden-options-shb.vaultwarden.smtp.password.result.path": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.smtp.password.result.path" + ], + "services-vaultwarden-options-shb.vaultwarden.smtp.port": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.smtp.port" + ], + "services-vaultwarden-options-shb.vaultwarden.smtp.security": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.smtp.security" + ], + "services-vaultwarden-options-shb.vaultwarden.smtp.username": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.smtp.username" + ], + "services-vaultwarden-options-shb.vaultwarden.ssl": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.ssl" + ], + "services-vaultwarden-options-shb.vaultwarden.ssl.paths": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.ssl.paths" + ], + "services-vaultwarden-options-shb.vaultwarden.ssl.paths.cert": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.ssl.paths.cert" + ], + "services-vaultwarden-options-shb.vaultwarden.ssl.paths.key": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.ssl.paths.key" + ], + "services-vaultwarden-options-shb.vaultwarden.ssl.systemdService": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.ssl.systemdService" + ], + "services-vaultwarden-options-shb.vaultwarden.subdomain": [ + "services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.subdomain" + ], + "services-vaultwarden-secrets": [ + "services-vaultwarden.html#services-vaultwarden-secrets" + ], + "services-vaultwarden-sso": [ + "services-vaultwarden.html#services-vaultwarden-sso" + ], + "services-vaultwarden-usage": [ + "services-vaultwarden.html#services-vaultwarden-usage" + ], + "services-vaultwarden-zfs": [ + "services-vaultwarden.html#services-vaultwarden-zfs" + ], + "usage": [ + "usage.html#usage" + ], + "usage-example-colmena": [ + "usage.html#usage-example-colmena" + ], + "usage-example-nixosrebuild": [ + "usage.html#usage-example-nixosrebuild" + ], + "usage-flake": [ + "usage.html#usage-flake" + ], + "usage-secrets": [ + "usage.html#usage-secrets" + ] +} diff --git a/modules/blocks/ssl/docs/default.md b/modules/blocks/ssl/docs/default.md index 68090a83..9e237fe7 100644 --- a/modules/blocks/ssl/docs/default.md +++ b/modules/blocks/ssl/docs/default.md @@ -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- diff --git a/modules/contracts/backup/docs/default.md b/modules/contracts/backup/docs/default.md index 403797ea..43bcec1d 100644 --- a/modules/contracts/backup/docs/default.md +++ b/modules/contracts/backup/docs/default.md @@ -101,6 +101,6 @@ Or with another module `backupservice_2`! - Home Assistant (no manual yet) - Jellyfin (no manual yet) - LLDAP (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 (no manual yet) diff --git a/modules/contracts/secret/docs/default.md b/modules/contracts/secret/docs/default.md index ca2a18f0..69b6048f 100644 --- a/modules/contracts/secret/docs/default.md +++ b/modules/contracts/secret/docs/default.md @@ -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. -## Motivation {#secret-contract-motivation} +## Motivation {#contract-secret-motivation} Let's provide the [ldap SHB module][ldap-module] option `ldapUserPasswordFile` with a secret managed by [sops-nix][]. @@ -68,7 +68,7 @@ shb.sops.secrets."ldap/user_password".request = config.shb.ldap.userPassword.req 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. @@ -78,7 +78,7 @@ list-id: selfhostblocks-options source: @OPTIONS_JSON@ ``` -## Usage {#secret-contract-usage} +## Usage {#contract-secret-usage} A contract involves 3 parties: @@ -88,7 +88,7 @@ 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. @@ -128,7 +128,7 @@ in }; ``` -### 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 @@ -177,7 +177,7 @@ in } ``` -### End User {#secret-contract-usage-enduser} +### End User {#contract-secret-usage-enduser} The end user's responsibility is now to do some plumbing. diff --git a/modules/contracts/ssl/docs/default.md b/modules/contracts/ssl/docs/default.md index db30337a..a9af8059 100644 --- a/modules/contracts/ssl/docs/default.md +++ b/modules/contracts/ssl/docs/default.md @@ -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. diff --git a/modules/services/nextcloud-server/docs/default.md b/modules/services/nextcloud-server/docs/default.md index fb67daeb..1e22c3e0 100644 --- a/modules/services/nextcloud-server/docs/default.md +++ b/modules/services/nextcloud-server/docs/default.md @@ -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. - - [SSO](#services-nextcloud-server-usage-oidc) 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-nextcloud-server-usage-previewgenerator) app: + - [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 @@ -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 @@ -206,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 SSO Support {#services-nextcloud-server-usage-oidc} +### With SSO Support {#services-nextcloudserver-usage-oidc} :::: {.note} This section corresponds to the `sso` section of the [Nextcloud @@ -289,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. @@ -311,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. @@ -353,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: @@ -385,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 @@ -410,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. @@ -447,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 @@ -469,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 @@ -477,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: @@ -492,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_` with the Nextcloud's instance ID as a suffix. @@ -503,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. @@ -521,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. @@ -547,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@ ```