3
0
Fork 0
forked from mirrors/nixpkgs

Merge pull request #154564 from talyz/bookstack-improve

nixos/bookstack: Improve configuration handling and fix issues
This commit is contained in:
Kim Lindberger 2022-01-18 15:35:43 +01:00 committed by GitHub
commit 50863c506e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 167 additions and 60 deletions

View file

@ -391,6 +391,23 @@
<literal>reloadIfChanged</literal> of the units.
</para>
</listitem>
<listitem>
<para>
The <literal>services.bookstack.cacheDir</literal> option has
been removed, since the cache directory is now handled by
systemd.
</para>
</listitem>
<listitem>
<para>
The <literal>services.bookstack.extraConfig</literal> option
has been replaced by
<literal>services.bookstack.config</literal> which implements
a
<link xlink:href="https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md">settings-style</link>
configuration.
</para>
</listitem>
</itemizedlist>
</section>
<section xml:id="sec-release-22.05-notable-changes">

View file

@ -122,6 +122,14 @@ In addition to numerous new and upgraded packages, this release has the followin
- The interface that allows activation scripts to restart units has been reworked. Restarting and reloading is now done by a single file `/run/nixos/activation-restart-list` that honors `restartIfChanged` and `reloadIfChanged` of the units.
- 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

View file

@ -24,8 +24,14 @@ let
$sudo ${pkgs.php}/bin/php artisan $*
'';
tlsEnabled = cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME;
in {
imports = [
(mkRemovedOptionModule [ "services" "bookstack" "extraConfig" ] "Use services.bookstack.config instead.")
(mkRemovedOptionModule [ "services" "bookstack" "cacheDir" ] "The cache directory is now handled automatically.")
];
options.services.bookstack = {
enable = mkEnableOption "BookStack";
@ -44,28 +50,38 @@ in {
appKeyFile = mkOption {
description = ''
A file containing the AppKey.
Used for encryption where needed. Can be generated with <code>head -c 32 /dev/urandom| base64</code> and must be prefixed with <literal>base64:</literal>.
A file containing the Laravel APP_KEY - a 32 character long,
base64 encoded key used for encryption where needed. Can be
generated with <code>head -c 32 /dev/urandom | base64</code>.
'';
example = "/run/keys/bookstack-appkey";
type = types.path;
};
hostname = lib.mkOption {
type = lib.types.str;
default = if config.networking.domain != null then
config.networking.fqdn
else
config.networking.hostName;
defaultText = lib.literalExpression "config.networking.fqdn";
example = "bookstack.example.com";
description = ''
The hostname to serve BookStack on.
'';
};
appURL = mkOption {
description = ''
The root URL that you want to host BookStack on. All URLs in BookStack will be generated using this value.
If you change this in the future you may need to run a command to update stored URLs in the database. Command example: <code>php artisan bookstack:update-url https://old.example.com https://new.example.com</code>
'';
default = "http${lib.optionalString tlsEnabled "s"}://${cfg.hostname}";
defaultText = ''http''${lib.optionalString tlsEnabled "s"}://''${cfg.hostname}'';
example = "https://example.com";
type = types.str;
};
cacheDir = mkOption {
description = "BookStack cache directory";
default = "/var/cache/bookstack";
type = types.path;
};
dataDir = mkOption {
description = "BookStack data directory";
default = "/var/lib/bookstack";
@ -202,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 <link xlink:href="https://www.bookstackapp.com/docs/"/> for details on supported values.
BookStack configuration options to set in the
<filename>.env</filename> file.
Refer to <link xlink:href="https://www.bookstackapp.com/docs/"/>
for details on supported values.
Settings containing secret data should be set to an attribute
set containing the attribute <literal>_secret</literal> - 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 <filename>.env</filename> file, the
<literal>OIDC_CLIENT_SECRET</literal> key will be set to the
contents of the <filename>/run/keys/oidc_secret</filename>
file.
'';
};
@ -228,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 {
@ -258,24 +341,19 @@ in {
services.nginx = {
enable = mkDefault true;
virtualHosts.bookstack = mkMerge [ cfg.nginx {
recommendedTlsSettings = true;
recommendedOptimisation = true;
recommendedGzipSettings = true;
virtualHosts.${cfg.hostname} = mkMerge [ cfg.nginx {
root = mkForce "${bookstack}/public";
extraConfig = optionalString (cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME) "fastcgi_param HTTPS on;";
locations = {
"/" = {
index = "index.php";
extraConfig = ''try_files $uri $uri/ /index.php?$query_string;'';
};
"~ \.php$" = {
extraConfig = ''
try_files $uri $uri/ /index.php?$query_string;
include ${pkgs.nginx}/conf/fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param REDIRECT_STATUS 200;
fastcgi_pass unix:${config.services.phpfpm.pools."bookstack".socket};
${optionalString (cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME) "fastcgi_param HTTPS on;"}
'';
tryFiles = "$uri $uri/ /index.php?$query_string";
};
"~ \.php$".extraConfig = ''
fastcgi_pass unix:${config.services.phpfpm.pools."bookstack".socket};
'';
"~ \.(js|css|gif|png|ico|jpg|jpeg)$" = {
extraConfig = "expires 365d;";
};
@ -290,50 +368,54 @@ in {
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
User = user;
WorkingDirectory = "${bookstack}";
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=${cfg.cacheDir}/services.php
APP_PACKAGES_CACHE=${cfg.cacheDir}/packages.php
APP_CONFIG_CACHE=${cfg.cacheDir}/config.php
APP_ROUTES_CACHE=${cfg.cacheDir}/routes-v7.php
APP_EVENTS_CACHE=${cfg.cacheDir}/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
# clear & create caches (needed in case of update)
${pkgs.php}/bin/php artisan cache:clear
${pkgs.php}/bin/php artisan config:clear
${pkgs.php}/bin/php artisan view:clear
'';
};
systemd.tmpfiles.rules = [
"d ${cfg.cacheDir} 0700 ${user} ${group} - -"
"d ${cfg.dataDir} 0710 ${user} ${group} - -"
"d ${cfg.dataDir}/public 0750 ${user} ${group} - -"
"d ${cfg.dataDir}/public/uploads 0750 ${user} ${group} - -"