2e751c0772
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.
380 lines
12 KiB
Nix
380 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
|
|
'';
|
|
|
|
};
|
|
|
|
}
|
|
|