at 18.09-beta 9.6 kB view raw
1{ config, lib, pkgs, ... }: 2with lib; 3let 4 cfg = config.services.matomo; 5 6 user = "matomo"; 7 dataDir = "/var/lib/${user}"; 8 deprecatedDataDir = "/var/lib/piwik"; 9 10 pool = user; 11 # it's not possible to use /run/phpfpm/${pool}.sock because /run/phpfpm/ is root:root 0770, 12 # and therefore is not accessible by the web server. 13 phpSocket = "/run/phpfpm-${pool}.sock"; 14 phpExecutionUnit = "phpfpm-${pool}"; 15 databaseService = "mysql.service"; 16 17 fqdn = 18 let 19 join = hostName: domain: hostName + optionalString (domain != null) ".${domain}"; 20 in join config.networking.hostName config.networking.domain; 21 22in { 23 options = { 24 services.matomo = { 25 # NixOS PR for database setup: https://github.com/NixOS/nixpkgs/pull/6963 26 # matomo issue for automatic matomo setup: https://github.com/matomo-org/matomo/issues/10257 27 # TODO: find a nice way to do this when more NixOS MySQL and / or matomo automatic setup stuff is implemented. 28 enable = mkOption { 29 type = types.bool; 30 default = false; 31 description = '' 32 Enable matomo web analytics with php-fpm backend. 33 Either the nginx option or the webServerUser option is mandatory. 34 ''; 35 }; 36 37 webServerUser = mkOption { 38 type = types.nullOr types.str; 39 default = null; 40 example = "lighttpd"; 41 # TODO: piwik.php might get renamed to matomo.php in future releases 42 description = '' 43 Name of the web server user that forwards requests to the ${phpSocket} fastcgi socket for matomo if the nginx 44 option is not used. Either this option or the nginx option is mandatory. 45 If you want to use another webserver than nginx, you need to set this to that server's user 46 and pass fastcgi requests to `index.php` and `piwik.php` to this socket. 47 ''; 48 }; 49 50 phpfpmProcessManagerConfig = mkOption { 51 type = types.str; 52 default = '' 53 ; default phpfpm process manager settings 54 pm = dynamic 55 pm.max_children = 75 56 pm.start_servers = 10 57 pm.min_spare_servers = 5 58 pm.max_spare_servers = 20 59 pm.max_requests = 500 60 61 ; log worker's stdout, but this has a performance hit 62 catch_workers_output = yes 63 ''; 64 description = '' 65 Settings for phpfpm's process manager. You might need to change this depending on the load for matomo. 66 ''; 67 }; 68 69 nginx = mkOption { 70 type = types.nullOr (types.submodule ( 71 recursiveUpdate 72 (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) 73 { 74 # enable encryption by default, 75 # as sensitive login and matomo data should not be transmitted in clear text. 76 options.forceSSL.default = true; 77 options.enableACME.default = true; 78 } 79 ) 80 ); 81 default = null; 82 example = { 83 serverAliases = [ 84 "matomo.$\{config.networking.domain\}" 85 "stats.$\{config.networking.domain\}" 86 ]; 87 enableACME = false; 88 }; 89 description = '' 90 With this option, you can customize an nginx virtualHost which already has sensible defaults for matomo. 91 Either this option or the webServerUser option is mandatory. 92 Set this to {} to just enable the virtualHost if you don't need any customization. 93 If enabled, then by default, the <option>serverName</option> is 94 <literal>${user}.$\{config.networking.hostName\}.$\{config.networking.domain\}</literal>, 95 SSL is active, and certificates are acquired via ACME. 96 If this is set to null (the default), no nginx virtualHost will be configured. 97 ''; 98 }; 99 }; 100 }; 101 102 config = mkIf cfg.enable { 103 warnings = mkIf (cfg.nginx != null && cfg.webServerUser != null) [ 104 "If services.matomo.nginx is set, services.matomo.nginx.webServerUser is ignored and should be removed." 105 ]; 106 107 assertions = [ { 108 assertion = cfg.nginx != null || cfg.webServerUser != null; 109 message = "Either services.matomo.nginx or services.matomo.nginx.webServerUser is mandatory"; 110 }]; 111 112 users.users.${user} = { 113 isSystemUser = true; 114 createHome = true; 115 home = dataDir; 116 group = user; 117 }; 118 users.groups.${user} = {}; 119 120 systemd.services.matomo_setup_update = { 121 # everything needs to set up and up to date before matomo php files are executed 122 requiredBy = [ "${phpExecutionUnit}.service" ]; 123 before = [ "${phpExecutionUnit}.service" ]; 124 # the update part of the script can only work if the database is already up and running 125 requires = [ databaseService ]; 126 after = [ databaseService ]; 127 path = [ pkgs.matomo ]; 128 serviceConfig = { 129 Type = "oneshot"; 130 User = user; 131 # hide especially config.ini.php from other 132 UMask = "0007"; 133 # TODO: might get renamed to MATOMO_USER_PATH in future versions 134 Environment = "PIWIK_USER_PATH=${dataDir}"; 135 # chown + chmod in preStart needs root 136 PermissionsStartOnly = true; 137 }; 138 # correct ownership and permissions in case they're not correct anymore, 139 # e.g. after restoring from backup or moving from another system. 140 # Note that ${dataDir}/config/config.ini.php might contain the MySQL password. 141 preStart = '' 142 # migrate data from piwik to matomo folder 143 if [ -d ${deprecatedDataDir} ]; then 144 echo "Migrating from ${deprecatedDataDir} to ${dataDir}" 145 mv -T ${deprecatedDataDir} ${dataDir} 146 fi 147 chown -R ${user}:${user} ${dataDir} 148 chmod -R ug+rwX,o-rwx ${dataDir} 149 ''; 150 script = '' 151 # Use User-Private Group scheme to protect matomo data, but allow administration / backup via matomo group 152 # Copy config folder 153 chmod g+s "${dataDir}" 154 cp -r "${pkgs.matomo}/config" "${dataDir}/" 155 chmod -R u+rwX,g+rwX,o-rwx "${dataDir}" 156 157 # check whether user setup has already been done 158 if test -f "${dataDir}/config/config.ini.php"; then 159 # then execute possibly pending database upgrade 160 matomo-console core:update --yes 161 fi 162 ''; 163 }; 164 165 systemd.services.${phpExecutionUnit} = { 166 # stop phpfpm on package upgrade, do database upgrade via matomo_setup_update, and then restart 167 restartTriggers = [ pkgs.matomo ]; 168 # stop config.ini.php from getting written with read permission for others 169 serviceConfig.UMask = "0007"; 170 }; 171 172 services.phpfpm.poolConfigs = let 173 # workaround for when both are null and need to generate a string, 174 # which is illegal, but as assertions apparently are being triggered *after* config generation, 175 # we have to avoid already throwing errors at this previous stage. 176 socketOwner = if (cfg.nginx != null) then config.services.nginx.user 177 else if (cfg.webServerUser != null) then cfg.webServerUser else ""; 178 in { 179 ${pool} = '' 180 listen = "${phpSocket}" 181 listen.owner = ${socketOwner} 182 listen.group = root 183 listen.mode = 0600 184 user = ${user} 185 env[PIWIK_USER_PATH] = ${dataDir} 186 ${cfg.phpfpmProcessManagerConfig} 187 ''; 188 }; 189 190 191 services.nginx.virtualHosts = mkIf (cfg.nginx != null) { 192 # References: 193 # https://fralef.me/piwik-hardening-with-nginx-and-php-fpm.html 194 # https://github.com/perusio/piwik-nginx 195 "${user}.${fqdn}" = mkMerge [ cfg.nginx { 196 # don't allow to override the root easily, as it will almost certainly break matomo. 197 # disadvantage: not shown as default in docs. 198 root = mkForce "${pkgs.matomo}/share"; 199 200 # define locations here instead of as the submodule option's default 201 # so that they can easily be extended with additional locations if required 202 # without needing to redefine the matomo ones. 203 # disadvantage: not shown as default in docs. 204 locations."/" = { 205 index = "index.php"; 206 }; 207 # allow index.php for webinterface 208 locations."= /index.php".extraConfig = '' 209 fastcgi_pass unix:${phpSocket}; 210 ''; 211 # TODO: might get renamed to matomo.php in future versions 212 # allow piwik.php for tracking 213 locations."= /piwik.php".extraConfig = '' 214 fastcgi_pass unix:${phpSocket}; 215 ''; 216 # Any other attempt to access any php files is forbidden 217 locations."~* ^.+\.php$".extraConfig = '' 218 return 403; 219 ''; 220 # Disallow access to unneeded directories 221 # config and tmp are already removed 222 locations."~ ^/(?:core|lang|misc)/".extraConfig = '' 223 return 403; 224 ''; 225 # Disallow access to several helper files 226 locations."~* \.(?:bat|git|ini|sh|txt|tpl|xml|md)$".extraConfig = '' 227 return 403; 228 ''; 229 # No crawling of this site for bots that obey robots.txt - no useful information here. 230 locations."= /robots.txt".extraConfig = '' 231 return 200 "User-agent: *\nDisallow: /\n"; 232 ''; 233 # TODO: might get renamed to matomo.js in future versions 234 # let browsers cache piwik.js 235 locations."= /piwik.js".extraConfig = '' 236 expires 1M; 237 ''; 238 }]; 239 }; 240 }; 241 242 meta = { 243 doc = ./matomo-doc.xml; 244 maintainers = with lib.maintainers; [ florianjacob ]; 245 }; 246}