nixpkgs/nixos/modules/services/web-apps/restya-board.nix
pennae 2e751c0772 treewide: automatically md-convert option descriptions
the conversion procedure is simple:

 - find all things that look like options, ie calls to either `mkOption`
   or `lib.mkOption` that take an attrset. remember the attrset as the
   option
 - for all options, find a `description` attribute who's value is not a
   call to `mdDoc` or `lib.mdDoc`
 - textually convert the entire value of the attribute to MD with a few
   simple regexes (the set from mdize-module.sh)
 - if the change produced a change in the manual output, discard
 - if the change kept the manual unchanged, add some text to the
   description to make sure we've actually found an option. if the
   manual changes this time, keep the converted description

this procedure converts 80% of nixos options to markdown. around 2000
options remain to be inspected, but most of those fail the "does not
change the manual output check": currently the MD conversion process
does not faithfully convert docbook tags like <code> and <package>, so
any option using such tags will not be converted at all.
2022-07-30 15:16:34 +02:00

381 lines
12 KiB
Nix

{ config, lib, pkgs, ... }:
with lib;
# TODO: are these php-packages needed?
#imagick
#php-geoip -> php.ini: extension = geoip.so
#expat
let
cfg = config.services.restya-board;
fpm = config.services.phpfpm.pools.${poolName};
runDir = "/run/restya-board";
poolName = "restya-board";
in
{
###### interface
options = {
services.restya-board = {
enable = mkEnableOption "restya-board";
dataDir = mkOption {
type = types.path;
default = "/var/lib/restya-board";
description = lib.mdDoc ''
Data of the application.
'';
};
user = mkOption {
type = types.str;
default = "restya-board";
description = lib.mdDoc ''
User account under which the web-application runs.
'';
};
group = mkOption {
type = types.str;
default = "nginx";
description = lib.mdDoc ''
Group account under which the web-application runs.
'';
};
virtualHost = {
serverName = mkOption {
type = types.str;
default = "restya.board";
description = lib.mdDoc ''
Name of the nginx virtualhost to use.
'';
};
listenHost = mkOption {
type = types.str;
default = "localhost";
description = lib.mdDoc ''
Listen address for the virtualhost to use.
'';
};
listenPort = mkOption {
type = types.int;
default = 3000;
description = lib.mdDoc ''
Listen port for the virtualhost to use.
'';
};
};
database = {
host = mkOption {
type = types.nullOr types.str;
default = null;
description = lib.mdDoc ''
Host of the database. Leave 'null' to use a local PostgreSQL database.
A local PostgreSQL database is initialized automatically.
'';
};
port = mkOption {
type = types.nullOr types.int;
default = 5432;
description = lib.mdDoc ''
The database's port.
'';
};
name = mkOption {
type = types.str;
default = "restya_board";
description = lib.mdDoc ''
Name of the database. The database must exist.
'';
};
user = mkOption {
type = types.str;
default = "restya_board";
description = lib.mdDoc ''
The database user. The user must exist and have access to
the specified database.
'';
};
passwordFile = mkOption {
type = types.nullOr types.path;
default = null;
description = lib.mdDoc ''
The database user's password. 'null' if no password is set.
'';
};
};
email = {
server = mkOption {
type = types.nullOr types.str;
default = null;
example = "localhost";
description = lib.mdDoc ''
Hostname to send outgoing mail. Null to use the system MTA.
'';
};
port = mkOption {
type = types.int;
default = 25;
description = lib.mdDoc ''
Port used to connect to SMTP server.
'';
};
login = mkOption {
type = types.str;
default = "";
description = lib.mdDoc ''
SMTP authentication login used when sending outgoing mail.
'';
};
password = mkOption {
type = types.str;
default = "";
description = lib.mdDoc ''
SMTP authentication password used when sending outgoing mail.
ATTENTION: The password is stored world-readable in the nix-store!
'';
};
};
timezone = mkOption {
type = types.lines;
default = "GMT";
description = lib.mdDoc ''
Timezone the web-app runs in.
'';
};
};
};
###### implementation
config = mkIf cfg.enable {
services.phpfpm.pools = {
${poolName} = {
inherit (cfg) user group;
phpOptions = ''
date.timezone = "CET"
${optionalString (cfg.email.server != null) ''
SMTP = ${cfg.email.server}
smtp_port = ${toString cfg.email.port}
auth_username = ${cfg.email.login}
auth_password = ${cfg.email.password}
''}
'';
settings = mapAttrs (name: mkDefault) {
"listen.owner" = "nginx";
"listen.group" = "nginx";
"listen.mode" = "0600";
"pm" = "dynamic";
"pm.max_children" = 75;
"pm.start_servers" = 10;
"pm.min_spare_servers" = 5;
"pm.max_spare_servers" = 20;
"pm.max_requests" = 500;
"catch_workers_output" = 1;
};
};
};
services.nginx.enable = true;
services.nginx.virtualHosts.${cfg.virtualHost.serverName} = {
listen = [ { addr = cfg.virtualHost.listenHost; port = cfg.virtualHost.listenPort; } ];
serverName = cfg.virtualHost.serverName;
root = runDir;
extraConfig = ''
index index.html index.php;
gzip on;
gzip_comp_level 6;
gzip_min_length 1100;
gzip_buffers 16 8k;
gzip_proxied any;
gzip_types text/plain application/xml text/css text/js text/xml application/x-javascript text/javascript application/json application/xml+rss;
client_max_body_size 300M;
rewrite ^/oauth/authorize$ /server/php/authorize.php last;
rewrite ^/oauth_callback/([a-zA-Z0-9_\.]*)/([a-zA-Z0-9_\.]*)$ /server/php/oauth_callback.php?plugin=$1&code=$2 last;
rewrite ^/download/([0-9]*)/([a-zA-Z0-9_\.]*)$ /server/php/download.php?id=$1&hash=$2 last;
rewrite ^/ical/([0-9]*)/([0-9]*)/([a-z0-9]*).ics$ /server/php/ical.php?board_id=$1&user_id=$2&hash=$3 last;
rewrite ^/api/(.*)$ /server/php/R/r.php?_url=$1&$args last;
rewrite ^/api_explorer/api-docs/$ /client/api_explorer/api-docs/index.php last;
'';
locations."/".root = "${runDir}/client";
locations."~ \\.php$" = {
tryFiles = "$uri =404";
extraConfig = ''
include ${config.services.nginx.package}/conf/fastcgi_params;
fastcgi_pass unix:${fpm.socket};
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PHP_VALUE "upload_max_filesize=9G \n post_max_size=9G \n max_execution_time=200 \n max_input_time=200 \n memory_limit=256M";
'';
};
locations."~* \\.(css|js|less|html|ttf|woff|jpg|jpeg|gif|png|bmp|ico)" = {
root = "${runDir}/client";
extraConfig = ''
if (-f $request_filename) {
break;
}
rewrite ^/img/([a-zA-Z_]*)/([a-zA-Z_]*)/([a-zA-Z0-9_\.]*)$ /server/php/image.php?size=$1&model=$2&filename=$3 last;
add_header Cache-Control public;
add_header Cache-Control must-revalidate;
expires 7d;
'';
};
};
systemd.services.restya-board-init = {
description = "Restya board initialization";
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
wantedBy = [ "multi-user.target" ];
requires = if cfg.database.host == null then [] else [ "postgresql.service" ];
after = [ "network.target" ] ++ (if cfg.database.host == null then [] else [ "postgresql.service" ]);
script = ''
rm -rf "${runDir}"
mkdir -m 750 -p "${runDir}"
cp -r "${pkgs.restya-board}/"* "${runDir}"
sed -i "s/@restya.com/@${cfg.virtualHost.serverName}/g" "${runDir}/sql/restyaboard_with_empty_data.sql"
rm -rf "${runDir}/media"
rm -rf "${runDir}/client/img"
chmod -R 0750 "${runDir}"
sed -i "s@^php@${config.services.phpfpm.phpPackage}/bin/php@" "${runDir}/server/php/shell/"*.sh
${if (cfg.database.host == null) then ''
sed -i "s/^.*'R_DB_HOST'.*$/define('R_DB_HOST', 'localhost');/g" "${runDir}/server/php/config.inc.php"
sed -i "s/^.*'R_DB_PASSWORD'.*$/define('R_DB_PASSWORD', 'restya');/g" "${runDir}/server/php/config.inc.php"
'' else ''
sed -i "s/^.*'R_DB_HOST'.*$/define('R_DB_HOST', '${cfg.database.host}');/g" "${runDir}/server/php/config.inc.php"
sed -i "s/^.*'R_DB_PASSWORD'.*$/define('R_DB_PASSWORD', ${if cfg.database.passwordFile == null then "''" else "'$(cat ${cfg.database.passwordFile})');/g"}" "${runDir}/server/php/config.inc.php"
''}
sed -i "s/^.*'R_DB_PORT'.*$/define('R_DB_PORT', '${toString cfg.database.port}');/g" "${runDir}/server/php/config.inc.php"
sed -i "s/^.*'R_DB_NAME'.*$/define('R_DB_NAME', '${cfg.database.name}');/g" "${runDir}/server/php/config.inc.php"
sed -i "s/^.*'R_DB_USER'.*$/define('R_DB_USER', '${cfg.database.user}');/g" "${runDir}/server/php/config.inc.php"
chmod 0400 "${runDir}/server/php/config.inc.php"
ln -sf "${cfg.dataDir}/media" "${runDir}/media"
ln -sf "${cfg.dataDir}/client/img" "${runDir}/client/img"
chmod g+w "${runDir}/tmp/cache"
chown -R "${cfg.user}":"${cfg.group}" "${runDir}"
mkdir -m 0750 -p "${cfg.dataDir}"
mkdir -m 0750 -p "${cfg.dataDir}/media"
mkdir -m 0750 -p "${cfg.dataDir}/client/img"
cp -r "${pkgs.restya-board}/media/"* "${cfg.dataDir}/media"
cp -r "${pkgs.restya-board}/client/img/"* "${cfg.dataDir}/client/img"
chown "${cfg.user}":"${cfg.group}" "${cfg.dataDir}"
chown -R "${cfg.user}":"${cfg.group}" "${cfg.dataDir}/media"
chown -R "${cfg.user}":"${cfg.group}" "${cfg.dataDir}/client/img"
${optionalString (cfg.database.host == null) ''
if ! [ -e "${cfg.dataDir}/.db-initialized" ]; then
${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} \
${config.services.postgresql.package}/bin/psql -U ${config.services.postgresql.superUser} \
-c "CREATE USER ${cfg.database.user} WITH ENCRYPTED PASSWORD 'restya'"
${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} \
${config.services.postgresql.package}/bin/psql -U ${config.services.postgresql.superUser} \
-c "CREATE DATABASE ${cfg.database.name} OWNER ${cfg.database.user} ENCODING 'UTF8' TEMPLATE template0"
${pkgs.sudo}/bin/sudo -u ${cfg.user} \
${config.services.postgresql.package}/bin/psql -U ${cfg.database.user} \
-d ${cfg.database.name} -f "${runDir}/sql/restyaboard_with_empty_data.sql"
touch "${cfg.dataDir}/.db-initialized"
fi
''}
'';
};
systemd.timers.restya-board = {
description = "restya-board scripts for e.g. email notification";
wantedBy = [ "timers.target" ];
after = [ "restya-board-init.service" ];
requires = [ "restya-board-init.service" ];
timerConfig = {
OnUnitInactiveSec = "60s";
Unit = "restya-board-timers.service";
};
};
systemd.services.restya-board-timers = {
description = "restya-board scripts for e.g. email notification";
serviceConfig.Type = "oneshot";
serviceConfig.User = cfg.user;
after = [ "restya-board-init.service" ];
requires = [ "restya-board-init.service" ];
script = ''
/bin/sh ${runDir}/server/php/shell/instant_email_notification.sh 2> /dev/null || true
/bin/sh ${runDir}/server/php/shell/periodic_email_notification.sh 2> /dev/null || true
/bin/sh ${runDir}/server/php/shell/imap.sh 2> /dev/null || true
/bin/sh ${runDir}/server/php/shell/webhook.sh 2> /dev/null || true
/bin/sh ${runDir}/server/php/shell/card_due_notification.sh 2> /dev/null || true
'';
};
users.users.restya-board = {
isSystemUser = true;
createHome = false;
home = runDir;
group = "restya-board";
};
users.groups.restya-board = {};
services.postgresql.enable = mkIf (cfg.database.host == null) true;
services.postgresql.identMap = optionalString (cfg.database.host == null)
''
restya-board-users restya-board restya_board
'';
services.postgresql.authentication = optionalString (cfg.database.host == null)
''
local restya_board all ident map=restya-board-users
'';
};
}