at 18.09-beta 12 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5# TODO: are these php-packages needed? 6#imagick 7#php-geoip -> php.ini: extension = geoip.so 8#expat 9 10let 11 cfg = config.services.restya-board; 12 13 runDir = "/run/restya-board"; 14 15 poolName = "restya-board"; 16 phpfpmSocketName = "/var/run/phpfpm/${poolName}.sock"; 17 18in 19 20{ 21 22 ###### interface 23 24 options = { 25 26 services.restya-board = { 27 28 enable = mkEnableOption "restya-board"; 29 30 dataDir = mkOption { 31 type = types.path; 32 default = "/var/lib/restya-board"; 33 example = "/var/lib/restya-board"; 34 description = '' 35 Data of the application. 36 ''; 37 }; 38 39 user = mkOption { 40 type = types.str; 41 default = "restya-board"; 42 example = "restya-board"; 43 description = '' 44 User account under which the web-application runs. 45 ''; 46 }; 47 48 group = mkOption { 49 type = types.str; 50 default = "nginx"; 51 example = "nginx"; 52 description = '' 53 Group account under which the web-application runs. 54 ''; 55 }; 56 57 virtualHost = { 58 serverName = mkOption { 59 type = types.str; 60 default = "restya.board"; 61 description = '' 62 Name of the nginx virtualhost to use. 63 ''; 64 }; 65 66 listenHost = mkOption { 67 type = types.str; 68 default = "localhost"; 69 description = '' 70 Listen address for the virtualhost to use. 71 ''; 72 }; 73 74 listenPort = mkOption { 75 type = types.int; 76 default = 3000; 77 description = '' 78 Listen port for the virtualhost to use. 79 ''; 80 }; 81 }; 82 83 database = { 84 host = mkOption { 85 type = types.nullOr types.str; 86 default = null; 87 description = '' 88 Host of the database. Leave 'null' to use a local PostgreSQL database. 89 A local PostgreSQL database is initialized automatically. 90 ''; 91 }; 92 93 port = mkOption { 94 type = types.nullOr types.int; 95 default = 5432; 96 description = '' 97 The database's port. 98 ''; 99 }; 100 101 name = mkOption { 102 type = types.str; 103 default = "restya_board"; 104 description = '' 105 Name of the database. The database must exist. 106 ''; 107 }; 108 109 user = mkOption { 110 type = types.str; 111 default = "restya_board"; 112 description = '' 113 The database user. The user must exist and have access to 114 the specified database. 115 ''; 116 }; 117 118 passwordFile = mkOption { 119 type = types.nullOr types.str; 120 default = null; 121 description = '' 122 The database user's password. 'null' if no password is set. 123 ''; 124 }; 125 }; 126 127 email = { 128 server = mkOption { 129 type = types.nullOr types.str; 130 default = null; 131 example = "localhost"; 132 description = '' 133 Hostname to send outgoing mail. Null to use the system MTA. 134 ''; 135 }; 136 137 port = mkOption { 138 type = types.int; 139 default = 25; 140 description = '' 141 Port used to connect to SMTP server. 142 ''; 143 }; 144 145 login = mkOption { 146 type = types.str; 147 default = ""; 148 description = '' 149 SMTP authentication login used when sending outgoing mail. 150 ''; 151 }; 152 153 password = mkOption { 154 type = types.str; 155 default = ""; 156 description = '' 157 SMTP authentication password used when sending outgoing mail. 158 159 ATTENTION: The password is stored world-readable in the nix-store! 160 ''; 161 }; 162 }; 163 164 timezone = mkOption { 165 type = types.lines; 166 default = "GMT"; 167 description = '' 168 Timezone the web-app runs in. 169 ''; 170 }; 171 172 }; 173 174 }; 175 176 177 ###### implementation 178 179 config = mkIf cfg.enable { 180 181 services.phpfpm.poolConfigs = { 182 "${poolName}" = '' 183 listen = "${phpfpmSocketName}"; 184 listen.owner = nginx 185 listen.group = nginx 186 listen.mode = 0600 187 user = ${cfg.user} 188 group = ${cfg.group} 189 pm = dynamic 190 pm.max_children = 75 191 pm.start_servers = 10 192 pm.min_spare_servers = 5 193 pm.max_spare_servers = 20 194 pm.max_requests = 500 195 catch_workers_output = 1 196 ''; 197 }; 198 199 services.phpfpm.phpOptions = '' 200 date.timezone = "CET" 201 202 ${optionalString (!isNull cfg.email.server) '' 203 SMTP = ${cfg.email.server} 204 smtp_port = ${toString cfg.email.port} 205 auth_username = ${cfg.email.login} 206 auth_password = ${cfg.email.password} 207 ''} 208 ''; 209 210 services.nginx.enable = true; 211 services.nginx.virtualHosts."${cfg.virtualHost.serverName}" = { 212 listen = [ { addr = cfg.virtualHost.listenHost; port = cfg.virtualHost.listenPort; } ]; 213 serverName = cfg.virtualHost.serverName; 214 root = runDir; 215 extraConfig = '' 216 index index.html index.php; 217 218 gzip on; 219 gzip_disable "msie6"; 220 221 gzip_comp_level 6; 222 gzip_min_length 1100; 223 gzip_buffers 16 8k; 224 gzip_proxied any; 225 gzip_types text/plain application/xml text/css text/js text/xml application/x-javascript text/javascript application/json application/xml+rss; 226 227 client_max_body_size 300M; 228 229 rewrite ^/oauth/authorize$ /server/php/authorize.php last; 230 rewrite ^/oauth_callback/([a-zA-Z0-9_\.]*)/([a-zA-Z0-9_\.]*)$ /server/php/oauth_callback.php?plugin=$1&code=$2 last; 231 rewrite ^/download/([0-9]*)/([a-zA-Z0-9_\.]*)$ /server/php/download.php?id=$1&hash=$2 last; 232 rewrite ^/ical/([0-9]*)/([0-9]*)/([a-z0-9]*).ics$ /server/php/ical.php?board_id=$1&user_id=$2&hash=$3 last; 233 rewrite ^/api/(.*)$ /server/php/R/r.php?_url=$1&$args last; 234 rewrite ^/api_explorer/api-docs/$ /client/api_explorer/api-docs/index.php last; 235 ''; 236 237 locations."/".root = "${runDir}/client"; 238 239 locations."~ \.php$" = { 240 tryFiles = "$uri =404"; 241 extraConfig = '' 242 include ${pkgs.nginx}/conf/fastcgi_params; 243 fastcgi_pass unix:${phpfpmSocketName}; 244 fastcgi_index index.php; 245 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 246 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"; 247 ''; 248 }; 249 250 locations."~* \.(css|js|less|html|ttf|woff|jpg|jpeg|gif|png|bmp|ico)" = { 251 root = "${runDir}/client"; 252 extraConfig = '' 253 if (-f $request_filename) { 254 break; 255 } 256 rewrite ^/img/([a-zA-Z_]*)/([a-zA-Z_]*)/([a-zA-Z0-9_\.]*)$ /server/php/image.php?size=$1&model=$2&filename=$3 last; 257 add_header Cache-Control public; 258 add_header Cache-Control must-revalidate; 259 expires 7d; 260 ''; 261 }; 262 }; 263 264 systemd.services.restya-board-init = { 265 description = "Restya board initialization"; 266 serviceConfig.Type = "oneshot"; 267 serviceConfig.RemainAfterExit = true; 268 269 wantedBy = [ "multi-user.target" ]; 270 requires = [ "postgresql.service" ]; 271 after = [ "network.target" "postgresql.service" ]; 272 273 script = '' 274 rm -rf "${runDir}" 275 mkdir -m 750 -p "${runDir}" 276 cp -r "${pkgs.restya-board}/"* "${runDir}" 277 sed -i "s/@restya.com/@${cfg.virtualHost.serverName}/g" "${runDir}/sql/restyaboard_with_empty_data.sql" 278 rm -rf "${runDir}/media" 279 rm -rf "${runDir}/client/img" 280 chmod -R 0750 "${runDir}" 281 282 sed -i "s@^php@${config.services.phpfpm.phpPackage}/bin/php@" "${runDir}/server/php/shell/"*.sh 283 284 ${if (isNull cfg.database.host) then '' 285 sed -i "s/^.*'R_DB_HOST'.*$/define('R_DB_HOST', 'localhost');/g" "${runDir}/server/php/config.inc.php" 286 sed -i "s/^.*'R_DB_PASSWORD'.*$/define('R_DB_PASSWORD', 'restya');/g" "${runDir}/server/php/config.inc.php" 287 '' else '' 288 sed -i "s/^.*'R_DB_HOST'.*$/define('R_DB_HOST', '${cfg.database.host}');/g" "${runDir}/server/php/config.inc.php" 289 sed -i "s/^.*'R_DB_PASSWORD'.*$/define('R_DB_PASSWORD', '$(<${cfg.database.dbPassFile})');/g" "${runDir}/server/php/config.inc.php" 290 ''} 291 sed -i "s/^.*'R_DB_PORT'.*$/define('R_DB_PORT', '${toString cfg.database.port}');/g" "${runDir}/server/php/config.inc.php" 292 sed -i "s/^.*'R_DB_NAME'.*$/define('R_DB_NAME', '${cfg.database.name}');/g" "${runDir}/server/php/config.inc.php" 293 sed -i "s/^.*'R_DB_USER'.*$/define('R_DB_USER', '${cfg.database.user}');/g" "${runDir}/server/php/config.inc.php" 294 295 chmod 0400 "${runDir}/server/php/config.inc.php" 296 297 ln -sf "${cfg.dataDir}/media" "${runDir}/media" 298 ln -sf "${cfg.dataDir}/client/img" "${runDir}/client/img" 299 300 chmod g+w "${runDir}/tmp/cache" 301 chown -R "${cfg.user}"."${cfg.group}" "${runDir}" 302 303 304 mkdir -m 0750 -p "${cfg.dataDir}" 305 mkdir -m 0750 -p "${cfg.dataDir}/media" 306 mkdir -m 0750 -p "${cfg.dataDir}/client/img" 307 cp -r "${pkgs.restya-board}/media/"* "${cfg.dataDir}/media" 308 cp -r "${pkgs.restya-board}/client/img/"* "${cfg.dataDir}/client/img" 309 chown "${cfg.user}"."${cfg.group}" "${cfg.dataDir}" 310 chown -R "${cfg.user}"."${cfg.group}" "${cfg.dataDir}/media" 311 chown -R "${cfg.user}"."${cfg.group}" "${cfg.dataDir}/client/img" 312 313 ${optionalString (isNull cfg.database.host) '' 314 if ! [ -e "${cfg.dataDir}/.db-initialized" ]; then 315 ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} \ 316 ${config.services.postgresql.package}/bin/psql -U ${config.services.postgresql.superUser} \ 317 -c "CREATE USER ${cfg.database.user} WITH ENCRYPTED PASSWORD 'restya'" 318 319 ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} \ 320 ${config.services.postgresql.package}/bin/psql -U ${config.services.postgresql.superUser} \ 321 -c "CREATE DATABASE ${cfg.database.name} OWNER ${cfg.database.user} ENCODING 'UTF8' TEMPLATE template0" 322 323 ${pkgs.sudo}/bin/sudo -u ${cfg.user} \ 324 ${config.services.postgresql.package}/bin/psql -U ${cfg.database.user} \ 325 -d ${cfg.database.name} -f "${runDir}/sql/restyaboard_with_empty_data.sql" 326 327 touch "${cfg.dataDir}/.db-initialized" 328 fi 329 ''} 330 ''; 331 }; 332 333 systemd.timers.restya-board = { 334 description = "restya-board scripts for e.g. email notification"; 335 wantedBy = [ "timers.target" ]; 336 after = [ "restya-board-init.service" ]; 337 requires = [ "restya-board-init.service" ]; 338 timerConfig = { 339 OnUnitInactiveSec = "60s"; 340 Unit = "restya-board-timers.service"; 341 }; 342 }; 343 344 systemd.services.restya-board-timers = { 345 description = "restya-board scripts for e.g. email notification"; 346 serviceConfig.Type = "oneshot"; 347 serviceConfig.User = cfg.user; 348 349 after = [ "restya-board-init.service" ]; 350 requires = [ "restya-board-init.service" ]; 351 352 script = '' 353 /bin/sh ${runDir}/server/php/shell/instant_email_notification.sh 2> /dev/null || true 354 /bin/sh ${runDir}/server/php/shell/periodic_email_notification.sh 2> /dev/null || true 355 /bin/sh ${runDir}/server/php/shell/imap.sh 2> /dev/null || true 356 /bin/sh ${runDir}/server/php/shell/webhook.sh 2> /dev/null || true 357 /bin/sh ${runDir}/server/php/shell/card_due_notification.sh 2> /dev/null || true 358 ''; 359 }; 360 361 users.users.restya-board = { 362 isSystemUser = true; 363 createHome = false; 364 home = runDir; 365 group = "restya-board"; 366 }; 367 users.groups.restya-board = {}; 368 369 services.postgresql.enable = mkIf (isNull cfg.database.host) true; 370 371 services.postgresql.identMap = optionalString (isNull cfg.database.host) 372 '' 373 restya-board-users restya-board restya_board 374 ''; 375 376 services.postgresql.authentication = optionalString (isNull cfg.database.host) 377 '' 378 local restya_board all ident map=restya-board-users 379 ''; 380 381 }; 382 383} 384