Merge branch 'master' into staging-next
This commit is contained in:
@@ -288,7 +288,7 @@ foreach my $u (values %usersOut) {
|
||||
push @shadowNew, join(":", $u->{name}, $hashedPassword, "1::::::") . "\n";
|
||||
}
|
||||
|
||||
updateFile("/etc/shadow", \@shadowNew, 0600);
|
||||
updateFile("/etc/shadow", \@shadowNew, 0640);
|
||||
{
|
||||
my $uid = getpwnam "root";
|
||||
my $gid = getgrnam "shadow";
|
||||
|
||||
@@ -882,6 +882,7 @@
|
||||
./services/web-apps/atlassian/confluence.nix
|
||||
./services/web-apps/atlassian/crowd.nix
|
||||
./services/web-apps/atlassian/jira.nix
|
||||
./services/web-apps/bookstack.nix
|
||||
./services/web-apps/convos.nix
|
||||
./services/web-apps/cryptpad.nix
|
||||
./services/web-apps/documize.nix
|
||||
|
||||
@@ -90,7 +90,7 @@ in {
|
||||
rxvt-unicode # For backward compatibility (old default terminal)
|
||||
];
|
||||
defaultText = literalExample ''
|
||||
with pkgs; [ swaylock swayidle xwayland rxvt-unicode dmenu ];
|
||||
with pkgs; [ swaylock swayidle rxvt-unicode alacritty dmenu ];
|
||||
'';
|
||||
example = literalExample ''
|
||||
with pkgs; [
|
||||
|
||||
@@ -30,12 +30,49 @@ in
|
||||
Whether to run the exporter as the local 'postgres' super user.
|
||||
'';
|
||||
};
|
||||
|
||||
# TODO perhaps LoadCredential would be more appropriate
|
||||
environmentFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/root/prometheus-postgres-exporter.env";
|
||||
description = ''
|
||||
Environment file as defined in <citerefentry>
|
||||
<refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum>
|
||||
</citerefentry>.
|
||||
|
||||
Secrets may be passed to the service without adding them to the
|
||||
world-readable Nix store, by specifying placeholder variables as
|
||||
the option value in Nix and setting these variables accordingly in the
|
||||
environment file.
|
||||
|
||||
Environment variables from this file will be interpolated into the
|
||||
config file using envsubst with this syntax:
|
||||
<literal>$ENVIRONMENT ''${VARIABLE}</literal>
|
||||
|
||||
The main use is to set the DATA_SOURCE_NAME that contains the
|
||||
postgres password
|
||||
|
||||
note that contents from this file will override dataSourceName
|
||||
if you have set it from nix.
|
||||
|
||||
<programlisting>
|
||||
# Content of the environment file
|
||||
DATA_SOURCE_NAME=postgresql://username:password@localhost:5432/postgres?sslmode=disable
|
||||
</programlisting>
|
||||
|
||||
Note that this file needs to be available on the host on which
|
||||
this exporter is running.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
serviceOpts = {
|
||||
environment.DATA_SOURCE_NAME = cfg.dataSourceName;
|
||||
serviceConfig = {
|
||||
DynamicUser = false;
|
||||
User = mkIf cfg.runAsLocalSuperUser (mkForce "postgres");
|
||||
EnvironmentFile = mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
|
||||
ExecStart = ''
|
||||
${pkgs.prometheus-postgres-exporter}/bin/postgres_exporter \
|
||||
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
|
||||
|
||||
365
nixos/modules/services/web-apps/bookstack.nix
Normal file
365
nixos/modules/services/web-apps/bookstack.nix
Normal file
@@ -0,0 +1,365 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.bookstack;
|
||||
bookstack = pkgs.bookstack.override {
|
||||
dataDir = cfg.dataDir;
|
||||
};
|
||||
db = cfg.database;
|
||||
mail = cfg.mail;
|
||||
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
|
||||
# shell script for local administration
|
||||
artisan = pkgs.writeScriptBin "bookstack" ''
|
||||
#! ${pkgs.runtimeShell}
|
||||
cd ${bookstack}
|
||||
sudo=exec
|
||||
if [[ "$USER" != ${user} ]]; then
|
||||
sudo='exec /run/wrappers/bin/sudo -u ${user}'
|
||||
fi
|
||||
$sudo ${pkgs.php}/bin/php artisan $*
|
||||
'';
|
||||
|
||||
|
||||
in {
|
||||
options.services.bookstack = {
|
||||
|
||||
enable = mkEnableOption "BookStack";
|
||||
|
||||
user = mkOption {
|
||||
default = "bookstack";
|
||||
description = "User bookstack runs as.";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
default = "bookstack";
|
||||
description = "Group bookstack runs as.";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
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>.
|
||||
'';
|
||||
example = "/run/keys/bookstack-appkey";
|
||||
type = types.path;
|
||||
};
|
||||
|
||||
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>
|
||||
'';
|
||||
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";
|
||||
type = types.path;
|
||||
};
|
||||
|
||||
database = {
|
||||
host = mkOption {
|
||||
type = types.str;
|
||||
default = "localhost";
|
||||
description = "Database host address.";
|
||||
};
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 3306;
|
||||
description = "Database host port.";
|
||||
};
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
default = "bookstack";
|
||||
description = "Database name.";
|
||||
};
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = user;
|
||||
defaultText = "\${user}";
|
||||
description = "Database username.";
|
||||
};
|
||||
passwordFile = mkOption {
|
||||
type = with types; nullOr path;
|
||||
default = null;
|
||||
example = "/run/keys/bookstack-dbpassword";
|
||||
description = ''
|
||||
A file containing the password corresponding to
|
||||
<option>database.user</option>.
|
||||
'';
|
||||
};
|
||||
createLocally = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Create the database and database user locally.";
|
||||
};
|
||||
};
|
||||
|
||||
mail = {
|
||||
driver = mkOption {
|
||||
type = types.enum [ "smtp" "sendmail" ];
|
||||
default = "smtp";
|
||||
description = "Mail driver to use.";
|
||||
};
|
||||
host = mkOption {
|
||||
type = types.str;
|
||||
default = "localhost";
|
||||
description = "Mail host address.";
|
||||
};
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 1025;
|
||||
description = "Mail host port.";
|
||||
};
|
||||
fromName = mkOption {
|
||||
type = types.str;
|
||||
default = "BookStack";
|
||||
description = "Mail \"from\" name.";
|
||||
};
|
||||
from = mkOption {
|
||||
type = types.str;
|
||||
default = "mail@bookstackapp.com";
|
||||
description = "Mail \"from\" email.";
|
||||
};
|
||||
user = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
example = "bookstack";
|
||||
description = "Mail username.";
|
||||
};
|
||||
passwordFile = mkOption {
|
||||
type = with types; nullOr path;
|
||||
default = null;
|
||||
example = "/run/keys/bookstack-mailpassword";
|
||||
description = ''
|
||||
A file containing the password corresponding to
|
||||
<option>mail.user</option>.
|
||||
'';
|
||||
};
|
||||
encryption = mkOption {
|
||||
type = with types; nullOr (enum [ "tls" ]);
|
||||
default = null;
|
||||
description = "SMTP encryption mechanism to use.";
|
||||
};
|
||||
};
|
||||
|
||||
maxUploadSize = mkOption {
|
||||
type = types.str;
|
||||
default = "18M";
|
||||
example = "1G";
|
||||
description = "The maximum size for uploads (e.g. images).";
|
||||
};
|
||||
|
||||
poolConfig = mkOption {
|
||||
type = with types; attrsOf (oneOf [ str int bool ]);
|
||||
default = {
|
||||
"pm" = "dynamic";
|
||||
"pm.max_children" = 32;
|
||||
"pm.start_servers" = 2;
|
||||
"pm.min_spare_servers" = 2;
|
||||
"pm.max_spare_servers" = 4;
|
||||
"pm.max_requests" = 500;
|
||||
};
|
||||
description = ''
|
||||
Options for the bookstack PHP pool. See the documentation on <literal>php-fpm.conf</literal>
|
||||
for details on configuration directives.
|
||||
'';
|
||||
};
|
||||
|
||||
nginx = mkOption {
|
||||
type = types.submodule (
|
||||
recursiveUpdate
|
||||
(import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) {}
|
||||
);
|
||||
default = {};
|
||||
example = {
|
||||
serverAliases = [
|
||||
"bookstack.\${config.networking.domain}"
|
||||
];
|
||||
# To enable encryption and let let's encrypt take care of certificate
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
};
|
||||
description = ''
|
||||
With this option, you can customize the nginx virtualHost settings.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.nullOr types.lines;
|
||||
default = null;
|
||||
example = ''
|
||||
ALLOWED_IFRAME_HOSTS="https://example.com"
|
||||
WKHTMLTOPDF=/home/user/bins/wkhtmltopdf
|
||||
'';
|
||||
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.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
assertions = [
|
||||
{ assertion = db.createLocally -> db.user == user;
|
||||
message = "services.bookstack.database.user must be set to ${user} if services.mediawiki.database.createLocally is set true.";
|
||||
}
|
||||
{ assertion = db.createLocally -> db.passwordFile == null;
|
||||
message = "services.bookstack.database.passwordFile cannot be specified if services.bookstack.database.createLocally is set to true.";
|
||||
}
|
||||
];
|
||||
|
||||
environment.systemPackages = [ artisan ];
|
||||
|
||||
services.mysql = mkIf db.createLocally {
|
||||
enable = true;
|
||||
package = mkDefault pkgs.mariadb;
|
||||
ensureDatabases = [ db.name ];
|
||||
ensureUsers = [
|
||||
{ name = db.user;
|
||||
ensurePermissions = { "${db.name}.*" = "ALL PRIVILEGES"; };
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
services.phpfpm.pools.bookstack = {
|
||||
inherit user;
|
||||
inherit group;
|
||||
phpOptions = ''
|
||||
log_errors = on
|
||||
post_max_size = ${cfg.maxUploadSize}
|
||||
upload_max_filesize = ${cfg.maxUploadSize}
|
||||
'';
|
||||
settings = {
|
||||
"listen.mode" = "0660";
|
||||
"listen.owner" = user;
|
||||
"listen.group" = group;
|
||||
} // cfg.poolConfig;
|
||||
};
|
||||
|
||||
services.nginx = {
|
||||
enable = mkDefault true;
|
||||
virtualHosts.bookstack = 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;"}
|
||||
'';
|
||||
};
|
||||
"~ \.(js|css|gif|png|ico|jpg|jpeg)$" = {
|
||||
extraConfig = "expires 365d;";
|
||||
};
|
||||
};
|
||||
}];
|
||||
};
|
||||
|
||||
systemd.services.bookstack-setup = {
|
||||
description = "Preperation tasks for BookStack";
|
||||
before = [ "phpfpm-bookstack.service" ];
|
||||
after = optional db.createLocally "mysql.service";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = user;
|
||||
WorkingDirectory = "${bookstack}";
|
||||
};
|
||||
script = ''
|
||||
# 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"
|
||||
# set permissions
|
||||
chmod 700 "${cfg.dataDir}/.env"
|
||||
|
||||
# migrate db
|
||||
${pkgs.php}/bin/php artisan migrate --force
|
||||
|
||||
# create caches
|
||||
${pkgs.php}/bin/php artisan config:cache
|
||||
${pkgs.php}/bin/php artisan route:cache
|
||||
${pkgs.php}/bin/php artisan view:cache
|
||||
'';
|
||||
};
|
||||
|
||||
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} - -"
|
||||
"d ${cfg.dataDir}/storage 0700 ${user} ${group} - -"
|
||||
"d ${cfg.dataDir}/storage/app 0700 ${user} ${group} - -"
|
||||
"d ${cfg.dataDir}/storage/fonts 0700 ${user} ${group} - -"
|
||||
"d ${cfg.dataDir}/storage/framework 0700 ${user} ${group} - -"
|
||||
"d ${cfg.dataDir}/storage/framework/cache 0700 ${user} ${group} - -"
|
||||
"d ${cfg.dataDir}/storage/framework/sessions 0700 ${user} ${group} - -"
|
||||
"d ${cfg.dataDir}/storage/framework/views 0700 ${user} ${group} - -"
|
||||
"d ${cfg.dataDir}/storage/logs 0700 ${user} ${group} - -"
|
||||
"d ${cfg.dataDir}/storage/uploads 0700 ${user} ${group} - -"
|
||||
];
|
||||
|
||||
users = {
|
||||
users = mkIf (user == "bookstack") {
|
||||
bookstack = {
|
||||
inherit group;
|
||||
isSystemUser = true;
|
||||
};
|
||||
"${config.services.nginx.user}".extraGroups = [ group ];
|
||||
};
|
||||
groups = mkIf (group == "bookstack") {
|
||||
bookstack = {};
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
meta.maintainers = with maintainers; [ ymarkus ];
|
||||
}
|
||||
@@ -182,7 +182,18 @@ in rec {
|
||||
# upstream unit.
|
||||
for i in ${toString (mapAttrsToList (n: v: v.unit) units)}; do
|
||||
fn=$(basename $i/*)
|
||||
if [ -e $out/$fn ]; then
|
||||
|
||||
case $fn in
|
||||
# if file name is a template specialization, use the template's name
|
||||
*@?*.service)
|
||||
# remove @foo.service and replace it with @.service
|
||||
ofn="''${fn%@*.service}@.service"
|
||||
;;
|
||||
*)
|
||||
ofn="$fn"
|
||||
esac
|
||||
|
||||
if [ -e $out/$ofn ]; then
|
||||
if [ "$(readlink -f $i/$fn)" = /dev/null ]; then
|
||||
ln -sfn /dev/null $out/$fn
|
||||
else
|
||||
|
||||
41
nixos/tests/systemd-template-override.nix
Normal file
41
nixos/tests/systemd-template-override.nix
Normal file
@@ -0,0 +1,41 @@
|
||||
import ./make-test-python.nix {
|
||||
name = "systemd-template-override";
|
||||
|
||||
machine = { pkgs, lib, ... }: let
|
||||
touchTmp = pkgs.writeTextFile {
|
||||
name = "touch-tmp@.service";
|
||||
text = ''
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=${pkgs.coreutils}/bin/touch /tmp/%I
|
||||
'';
|
||||
destination = "/etc/systemd/system/touch-tmp@.service";
|
||||
};
|
||||
in {
|
||||
systemd.packages = [ touchTmp ];
|
||||
|
||||
systemd.services."touch-tmp@forbidden" = {
|
||||
serviceConfig.ExecStart = [ "" ''
|
||||
${pkgs.coreutils}/bin/true
|
||||
''];
|
||||
};
|
||||
|
||||
systemd.services."touch-tmp@intercept" = {
|
||||
serviceConfig.ExecStart = [ "" ''
|
||||
${pkgs.coreutils}/bin/touch /tmp/renamed
|
||||
''];
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.wait_for_unit("default.target")
|
||||
|
||||
machine.succeed("systemctl start touch-tmp@normal")
|
||||
machine.succeed("systemctl start touch-tmp@forbbidden")
|
||||
machine.succeed("systemctl start touch-tmp@intercept")
|
||||
|
||||
machine.succeed("[ -e /tmp/normal ]")
|
||||
machine.succeed("[ ! -e /tmp/forbidden ]")
|
||||
machine.succeed("[ -e /tmp/renamed ]")
|
||||
'';
|
||||
}
|
||||
Reference in New Issue
Block a user