Files
nixos/utils/modules/services/web/typo3.nix

368 lines
12 KiB
Nix

{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.typo3;
instanceOpts = { name, ... }:
{
options = {
user = mkOption {
type = types.nullOr types.str;
default = null;
description = lib.mdDoc ''
User of the typo3 instance. Defaults to attribute name in instances.
'';
example = "example.org";
};
domain = mkOption {
type = types.nullOr types.str;
default = null;
description = lib.mdDoc ''
Domain of the typo3 instance. Defaults to attribute name in instances.
'';
example = "example.org";
};
domainAliases = mkOption {
type = types.listOf types.str;
default = [];
example = [ "www.example.org" "example.org" ];
description = lib.mdDoc ''
Additional domains served by this typo3 instance.
'';
};
phpPackage = mkOption {
type = types.package;
example = literalExpression "pkgs.php";
description = lib.mdDoc ''
Which PHP package to use in this typo3 instance.
'';
};
authorizedKeys = mkOption {
type = types.listOf types.str;
default = null;
description = lib.mdDoc ''
Authorized keys for the typo3 instance ssh user.
'';
};
};
};
in
{
options.services.typo3 = {
dataDir = mkOption {
type = types.path;
default = "/var/www";
description = lib.mdDoc ''
The data directory for MySQL.
::: {.note}
If left as the default value of `/var/www` this directory will automatically be created before the web
server starts, otherwise you are responsible for ensuring the directory exists with appropriate ownership and permissions.
:::
'';
};
instances = mkOption {
type = types.attrsOf (types.submodule instanceOpts);
default = {};
description = lib.mdDoc "Create vhosts for typo3";
example = literalExpression ''
{
"typo3.example.com" = {
domain = "example.com";
domainAliases = [ "www.example.com" ];
phpPackage = pkgs.php82;
authorizedKeys = [
"ssh-rsa AZA=="
];
};
};
'';
};
};
config = {
# systemd.services = mapAttrs' (instance: instanceOpts:
# let
# domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
# in
# nameValuePair "phpfpm-${domain}" {
# serviceConfig = {
# ProtectHome = lib.mkForce "tmpfs";
# BindPaths = "BindPaths=/var/www/${domain}:/var/www/${domain}";
# };
# }
# ) cfg.instances;
systemd.timers = mapAttrs' (instance: instanceOpts:
let
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
user = if instanceOpts.user != null
then instanceOps.user
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
in
nameValuePair ("typo3-cron-" + domain) {
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = "05:00";
Unit = "typo3-cron-" + domain + ".service";
};
}
) cfg.instances;
systemd.services = mapAttrs' (instance: instanceOpts:
let
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
user = if instanceOpts.user != null
then instanceOps.user
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
in
nameValuePair ("typo3-cron-" + domain) {
script = ''
set -eu
${instanceOpts.phpPackage}/bin/php /var/www/${domain}/.Build/bin/typo3 scheduler:run
${instanceOpts.phpPackage}/bin/php /var/www/${domain}/.Build/bin/typo3 ke_search:indexing
'';
serviceConfig = {
Type = "oneshot";
User = user;
};
}
) cfg.instances;
services.phpfpm.pools = mapAttrs' (instance: instanceOpts:
let
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
user = if instanceOpts.user != null
then instanceOps.user
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
in
nameValuePair domain {
user = user;
settings = {
"listen.owner" = config.services.nginx.user;
"pm" = "dynamic";
"pm.max_children" = 32;
"pm.max_requests" = 500;
"pm.start_servers" = 2;
"pm.min_spare_servers" = 2;
"pm.max_spare_servers" = 5;
"php_admin_value[error_log]" = "syslog";
"php_admin_value[max_execution_time]" = 240;
"php_admin_value[max_input_vars]" = 1500;
"php_admin_value[upload_max_filesize]" = "256M";
"php_admin_value[post_max_size]" = "256M";
"access.log" = "/var/log/$pool.access.log";
};
phpOptions = ''
opcache.enable=1
opcache.memory_consumption=128
opcache.validate_timestamps=0
opcache.revalidate_path=0
'';
phpPackage = instanceOpts.phpPackage;
phpEnv."PATH" = pkgs.lib.makeBinPath [ instanceOpts.phpPackage ];
}
) cfg.instances;
};
config.services.nginx.virtualHosts = mapAttrs' (instance: instanceOpts:
let
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
user = if instanceOpts.user != null
then instanceOps.user
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
in
nameValuePair domain {
forceSSL = true;
enableACME = true;
acmeRoot = null;
root = cfg.dataDir + "/" + domain + "/public";
serverAliases = instanceOpts.domainAliases;
locations."/favicon.ico".extraConfig = ''
log_not_found off;
access_log off;
'';
extraConfig = ''
if (!-e $request_filename) {
rewrite ^/(.+)\.(\d+)\.(php|js|css|png|jpg|gif|gzip)$ /$1.$3 last;
}
'';
# TYPO3 - Block access to composer files
locations."~* composer\\.(?:json|lock)".extraConfig = ''
deny all;
'';
# TYPO3 - Block access to flexform files
locations."~* flexform[^.]*\\.xml".extraConfig = ''
deny all;
'';
# TYPO3 - Block access to language files
locations."~* locallang[^.]*\\.(?:xml|xlf)$".extraConfig = ''
deny all;
'';
# TYPO3 - Block access to static typoscript files
locations."~* ext_conf_template\\.txt|ext_typoscript_constants\\.txt|ext_typoscript_setup\\.txt".extraConfig = ''
deny all;
'';
# TYPO3 - Block access to miscellaneous protected files
locations."~* /.*\\.(?:bak|co?nf|cfg|ya?ml|ts|typoscript|tsconfig|dist|fla|in[ci]|log|sh|sql|sqlite)$".extraConfig = ''
deny all;
'';
# locations."~* /.*\.(?:bak|cfg|co?nf|ya?ml|ts)$".extraConfig = ''
# deny all;
# '';
# TYPO3 - Block access to recycler and temporary directories
locations."~ _(?:recycler|temp)_/".extraConfig = ''
deny all;
'';
# TYPO3 - Block access to configuration files stored in fileadmin
locations."~ fileadmin/(?:templates)/.*\\.(?:txt|ts|typoscript)$".extraConfig = ''
deny all;
'';
# TYPO3 - Block access to libraries, source and temporary compiled data
locations."~ ^(?:vendor|typo3_src|typo3temp/var)".extraConfig = ''
deny all;
'';
# TYPO3 - Block access to protected extension directories
locations."~ (?:typo3conf/ext|typo3/sysext|typo3/ext)/[^/]+/(?:Configuration|Resources/Private|Tests?|Documentation|docs?)/".extraConfig = ''
deny all;
'';
# Cache.appcache, your document html and data
locations."~* \\.(?:manifest|appcache|html?|xml|json)$".extraConfig = ''
expires -1;
# access_log logs/static.log; # I don't usually include a static log
'';
# Cache Media: images, icons, video, audio, HTC
locations."~* \\.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|woff2)$".extraConfig = ''
expires 1y;
access_log off;
add_header Cache-Control "public";
'';
# Feed
locations."~* \\.(?:rss|atom)$".extraConfig = ''
expires 1h;
add_header Cache-Control "public";
'';
# Cache CSS, Javascript, Images, Icons, Video, Audio, HTC, Fonts
locations."~* \\.(?:css|js|jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|woff2)$".extraConfig = ''
expires 1y;
access_log off;
add_header Cache-Control "public";
'';
locations."/".extraConfig = ''
index index.php index.html;
try_files $uri $uri/ /index.php$is_args$args;
'';
# TYPO3 Backend URLs
locations."/typo3$".extraConfig = ''
rewrite ^ /typo3/;
'';
locations."/typo3/".extraConfig = ''
try_files $uri /typo3/index.php$is_args$args;
'';
locations."~ [^/]\\.php(/|$)".extraConfig = ''
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
if (!-f $document_root$fastcgi_script_name) {
return 404;
}
include ${pkgs.nginx}/conf/fastcgi_params;
include ${pkgs.nginx}/conf/fastcgi.conf;
fastcgi_buffer_size 32k;
fastcgi_buffers 8 16k;
fastcgi_connect_timeout 240s;
fastcgi_read_timeout 240s;
fastcgi_send_timeout 240s;
fastcgi_pass unix:${config.services.phpfpm.pools."${domain}".socket};
fastcgi_index index.php;
'';
}
) cfg.instances;
config.users.users = mapAttrs' (instance: instanceOpts:
let
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
user = if instanceOpts.user != null
then instanceOps.user
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
in
nameValuePair user {
isNormalUser = true;
createHome = true;
home = "/var/www/" + domain;
homeMode= "770";
group = config.services.nginx.group;
openssh.authorizedKeys.keys = instanceOpts.authorizedKeys;
}
) cfg.instances;
config.users.groups = mapAttrs' (instance: instanceOpts:
let
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
user = if instanceOpts.user != null
then instanceOps.user
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
in nameValuePair user {}) cfg.instances;
config.services.mysql.ensureUsers = mapAttrsToList (instance: instanceOpts:
let
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
user = if instanceOpts.user != null
then instanceOps.user
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
in
{
name = user;
ensurePermissions = {
"${user}.*" = "ALL PRIVILEGES";
};
}) cfg.instances;
config.services.mysql.ensureDatabases = mapAttrsToList (instance: instanceOpts:
let
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
user = if instanceOpts.user != null
then instanceOps.user
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
in
user
) cfg.instances;
config.services.mysqlBackup.databases = mapAttrsToList (instance: instanceOpts:
let
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
user = if instanceOpts.user != null
then instanceOps.user
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
in
user
) cfg.instances;
}