diff --git a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml index 705d28aad5dc..0902b62251f4 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml @@ -398,6 +398,16 @@ systemd. + + + The services.bookstack.extraConfig option + has been replaced by + services.bookstack.config which implements + a + settings-style + configuration. + +
diff --git a/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md index 70691ccce877..447b6cabde13 100644 --- a/nixos/doc/manual/release-notes/rl-2205.section.md +++ b/nixos/doc/manual/release-notes/rl-2205.section.md @@ -125,6 +125,11 @@ In addition to numerous new and upgraded packages, this release has the followin - The `services.bookstack.cacheDir` option has been removed, since the cache directory is now handled by systemd. +- The `services.bookstack.extraConfig` option has been replaced by + `services.bookstack.config` which implements a + [settings-style](https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md) + configuration. + ## Other Notable Changes {#sec-release-22.05-notable-changes} - The option [services.redis.servers](#opt-services.redis.servers) was added diff --git a/nixos/modules/services/web-apps/bookstack.nix b/nixos/modules/services/web-apps/bookstack.nix index c0eec78059b5..54eaea63b6eb 100644 --- a/nixos/modules/services/web-apps/bookstack.nix +++ b/nixos/modules/services/web-apps/bookstack.nix @@ -28,6 +28,7 @@ let in { imports = [ + (mkRemovedOptionModule [ "services" "bookstack" "extraConfig" ] "Use services.bookstack.config instead.") (mkRemovedOptionModule [ "services" "bookstack" "cacheDir" ] "The cache directory is now handled automatically.") ]; @@ -49,8 +50,9 @@ in { appKeyFile = mkOption { description = '' - A file containing the AppKey. - Used for encryption where needed. Can be generated with head -c 32 /dev/urandom| base64 and must be prefixed with base64:. + A file containing the Laravel APP_KEY - a 32 character long, + base64 encoded key used for encryption where needed. Can be + generated with head -c 32 /dev/urandom | base64. ''; example = "/run/keys/bookstack-appkey"; type = types.path; @@ -216,16 +218,59 @@ in { ''; }; - extraConfig = mkOption { - type = types.nullOr types.lines; - default = null; - example = '' - ALLOWED_IFRAME_HOSTS="https://example.com" - WKHTMLTOPDF=/home/user/bins/wkhtmltopdf + config = mkOption { + type = with types; + attrsOf + (nullOr + (either + (oneOf [ + bool + int + port + path + str + ]) + (submodule { + options = { + _secret = mkOption { + type = nullOr str; + description = '' + The path to a file containing the value the + option should be set to in the final + configuration file. + ''; + }; + }; + }))); + default = {}; + example = literalExpression '' + { + ALLOWED_IFRAME_HOSTS = "https://example.com"; + WKHTMLTOPDF = "/home/user/bins/wkhtmltopdf"; + AUTH_METHOD = "oidc"; + OIDC_NAME = "MyLogin"; + OIDC_DISPLAY_NAME_CLAIMS = "name"; + OIDC_CLIENT_ID = "bookstack"; + OIDC_CLIENT_SECRET = {_secret = "/run/keys/oidc_secret"}; + OIDC_ISSUER = "https://keycloak.example.com/auth/realms/My%20Realm"; + OIDC_ISSUER_DISCOVER = true; + } ''; description = '' - Lines to be appended verbatim to the BookStack configuration. - Refer to for details on supported values. + BookStack configuration options to set in the + .env file. + + Refer to + for details on supported values. + + Settings containing secret data should be set to an attribute + set containing the attribute _secret - a + string pointing to a file containing the value the option + should be set to. See the example to get a better picture of + this: in the resulting .env file, the + OIDC_CLIENT_SECRET key will be set to the + contents of the /run/keys/oidc_secret + file. ''; }; @@ -242,6 +287,30 @@ in { } ]; + services.bookstack.config = { + APP_KEY._secret = cfg.appKeyFile; + APP_URL = cfg.appURL; + DB_HOST = db.host; + DB_PORT = db.port; + DB_DATABASE = db.name; + DB_USERNAME = db.user; + MAIL_DRIVER = mail.driver; + MAIL_FROM_NAME = mail.fromName; + MAIL_FROM = mail.from; + MAIL_HOST = mail.host; + MAIL_PORT = mail.port; + MAIL_USERNAME = mail.user; + MAIL_ENCRYPTION = mail.encryption; + DB_PASSWORD._secret = db.passwordFile; + MAIL_PASSWORD._secret = mail.passwordFile; + APP_SERVICES_CACHE = "/run/bookstack/cache/services.php"; + APP_PACKAGES_CACHE = "/run/bookstack/cache/packages.php"; + APP_CONFIG_CACHE = "/run/bookstack/cache/config.php"; + APP_ROUTES_CACHE = "/run/bookstack/cache/routes-v7.php"; + APP_EVENTS_CACHE = "/run/bookstack/cache/events.php"; + SESSION_SECURE_COOKIE = tlsEnabled; + }; + environment.systemPackages = [ artisan ]; services.mysql = mkIf db.createLocally { @@ -305,34 +374,41 @@ in { RuntimeDirectory = "bookstack/cache"; RuntimeDirectoryMode = 0700; }; - script = '' + path = [ pkgs.replace-secret ]; + script = + let + isSecret = v: isAttrs v && v ? _secret && isString v._secret; + bookstackEnvVars = lib.generators.toKeyValue { + mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" { + mkValueString = v: with builtins; + if isInt v then toString v + else if isString v then v + else if true == v then "true" + else if false == v then "false" + else if isSecret v then v._secret + else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}"; + }; + }; + secretPaths = lib.mapAttrsToList (_: v: v._secret) (lib.filterAttrs (_: isSecret) cfg.config); + mkSecretReplacement = file: '' + replace-secret ${escapeShellArgs [ file file "${cfg.dataDir}/.env" ]} + ''; + secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths; + filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [ {} null ])) cfg.config; + bookstackEnv = pkgs.writeText "bookstack.env" (bookstackEnvVars filteredConfig); + in '' + # error handling + set -euo pipefail + # set permissions umask 077 + # create .env file - echo " - APP_KEY=base64:$(head -n1 ${cfg.appKeyFile}) - APP_URL=${cfg.appURL} - DB_HOST=${db.host} - DB_PORT=${toString db.port} - DB_DATABASE=${db.name} - DB_USERNAME=${db.user} - MAIL_DRIVER=${mail.driver} - MAIL_FROM_NAME=\"${mail.fromName}\" - MAIL_FROM=${mail.from} - MAIL_HOST=${mail.host} - MAIL_PORT=${toString mail.port} - ${optionalString (mail.user != null) "MAIL_USERNAME=${mail.user};"} - ${optionalString (mail.encryption != null) "MAIL_ENCRYPTION=${mail.encryption};"} - ${optionalString (db.passwordFile != null) "DB_PASSWORD=$(head -n1 ${db.passwordFile})"} - ${optionalString (mail.passwordFile != null) "MAIL_PASSWORD=$(head -n1 ${mail.passwordFile})"} - APP_SERVICES_CACHE=/run/bookstack/cache/services.php - APP_PACKAGES_CACHE=/run/bookstack/cache/packages.php - APP_CONFIG_CACHE=/run/bookstack/cache/config.php - APP_ROUTES_CACHE=/run/bookstack/cache/routes-v7.php - APP_EVENTS_CACHE=/run/bookstack/cache/events.php - ${optionalString (cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME) "SESSION_SECURE_COOKIE=true"} - ${toString cfg.extraConfig} - " > "${cfg.dataDir}/.env" + install -T -m 0600 -o ${user} ${bookstackEnv} "${cfg.dataDir}/.env" + ${secretReplacements} + if ! grep 'APP_KEY=base64:' "${cfg.dataDir}/.env" >/dev/null; then + sed -i 's/APP_KEY=/APP_KEY=base64:/' "${cfg.dataDir}/.env" + fi # migrate db ${pkgs.php}/bin/php artisan migrate --force