at 21.11-pre 12 kB view raw
1{ config, lib, pkgs, ... }: 2with lib; 3let 4 cfg = config.services.matomo; 5 fpm = config.services.phpfpm.pools.${pool}; 6 7 user = "matomo"; 8 dataDir = "/var/lib/${user}"; 9 deprecatedDataDir = "/var/lib/piwik"; 10 11 pool = user; 12 phpExecutionUnit = "phpfpm-${pool}"; 13 databaseService = "mysql.service"; 14 15 fqdn = 16 let 17 join = hostName: domain: hostName + optionalString (domain != null) ".${domain}"; 18 in join config.networking.hostName config.networking.domain; 19 20in { 21 imports = [ 22 (mkRenamedOptionModule [ "services" "piwik" "enable" ] [ "services" "matomo" "enable" ]) 23 (mkRenamedOptionModule [ "services" "piwik" "webServerUser" ] [ "services" "matomo" "webServerUser" ]) 24 (mkRemovedOptionModule [ "services" "piwik" "phpfpmProcessManagerConfig" ] "Use services.phpfpm.pools.<name>.settings") 25 (mkRemovedOptionModule [ "services" "matomo" "phpfpmProcessManagerConfig" ] "Use services.phpfpm.pools.<name>.settings") 26 (mkRenamedOptionModule [ "services" "piwik" "nginx" ] [ "services" "matomo" "nginx" ]) 27 ]; 28 29 options = { 30 services.matomo = { 31 # NixOS PR for database setup: https://github.com/NixOS/nixpkgs/pull/6963 32 # Matomo issue for automatic Matomo setup: https://github.com/matomo-org/matomo/issues/10257 33 # TODO: find a nice way to do this when more NixOS MySQL and / or Matomo automatic setup stuff is implemented. 34 enable = mkOption { 35 type = types.bool; 36 default = false; 37 description = '' 38 Enable Matomo web analytics with php-fpm backend. 39 Either the nginx option or the webServerUser option is mandatory. 40 ''; 41 }; 42 43 package = mkOption { 44 type = types.package; 45 description = '' 46 Matomo package for the service to use. 47 This can be used to point to newer releases from nixos-unstable, 48 as they don't get backported if they are not security-relevant. 49 ''; 50 default = pkgs.matomo; 51 defaultText = "pkgs.matomo"; 52 }; 53 54 webServerUser = mkOption { 55 type = types.nullOr types.str; 56 default = null; 57 example = "lighttpd"; 58 description = '' 59 Name of the web server user that forwards requests to <option>services.phpfpm.pools.&lt;name&gt;.socket</option> the fastcgi socket for Matomo if the nginx 60 option is not used. Either this option or the nginx option is mandatory. 61 If you want to use another webserver than nginx, you need to set this to that server's user 62 and pass fastcgi requests to `index.php`, `matomo.php` and `piwik.php` (legacy name) to this socket. 63 ''; 64 }; 65 66 periodicArchiveProcessing = mkOption { 67 type = types.bool; 68 default = true; 69 description = '' 70 Enable periodic archive processing, which generates aggregated reports from the visits. 71 72 This means that you can safely disable browser triggers for Matomo archiving, 73 and safely enable to delete old visitor logs. 74 Before deleting visitor logs, 75 make sure though that you run <literal>systemctl start matomo-archive-processing.service</literal> 76 at least once without errors if you have already collected data before. 77 ''; 78 }; 79 80 nginx = mkOption { 81 type = types.nullOr (types.submodule ( 82 recursiveUpdate 83 (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) 84 { 85 # enable encryption by default, 86 # as sensitive login and Matomo data should not be transmitted in clear text. 87 options.forceSSL.default = true; 88 options.enableACME.default = true; 89 } 90 ) 91 ); 92 default = null; 93 example = { 94 serverAliases = [ 95 "matomo.\${config.networking.domain}" 96 "stats.\${config.networking.domain}" 97 ]; 98 enableACME = false; 99 }; 100 description = '' 101 With this option, you can customize an nginx virtualHost which already has sensible defaults for Matomo. 102 Either this option or the webServerUser option is mandatory. 103 Set this to {} to just enable the virtualHost if you don't need any customization. 104 If enabled, then by default, the <option>serverName</option> is 105 <literal>''${user}.''${config.networking.hostName}.''${config.networking.domain}</literal>, 106 SSL is active, and certificates are acquired via ACME. 107 If this is set to null (the default), no nginx virtualHost will be configured. 108 ''; 109 }; 110 }; 111 }; 112 113 config = mkIf cfg.enable { 114 warnings = mkIf (cfg.nginx != null && cfg.webServerUser != null) [ 115 "If services.matomo.nginx is set, services.matomo.nginx.webServerUser is ignored and should be removed." 116 ]; 117 118 assertions = [ { 119 assertion = cfg.nginx != null || cfg.webServerUser != null; 120 message = "Either services.matomo.nginx or services.matomo.nginx.webServerUser is mandatory"; 121 }]; 122 123 users.users.${user} = { 124 isSystemUser = true; 125 createHome = true; 126 home = dataDir; 127 group = user; 128 }; 129 users.groups.${user} = {}; 130 131 systemd.services.matomo-setup-update = { 132 # everything needs to set up and up to date before Matomo php files are executed 133 requiredBy = [ "${phpExecutionUnit}.service" ]; 134 before = [ "${phpExecutionUnit}.service" ]; 135 # the update part of the script can only work if the database is already up and running 136 requires = [ databaseService ]; 137 after = [ databaseService ]; 138 path = [ cfg.package ]; 139 environment.PIWIK_USER_PATH = dataDir; 140 serviceConfig = { 141 Type = "oneshot"; 142 User = user; 143 # hide especially config.ini.php from other 144 UMask = "0007"; 145 # TODO: might get renamed to MATOMO_USER_PATH in future versions 146 # chown + chmod in preStart needs root 147 PermissionsStartOnly = true; 148 }; 149 150 # correct ownership and permissions in case they're not correct anymore, 151 # e.g. after restoring from backup or moving from another system. 152 # Note that ${dataDir}/config/config.ini.php might contain the MySQL password. 153 preStart = '' 154 # migrate data from piwik to Matomo folder 155 if [ -d ${deprecatedDataDir} ]; then 156 echo "Migrating from ${deprecatedDataDir} to ${dataDir}" 157 mv -T ${deprecatedDataDir} ${dataDir} 158 fi 159 chown -R ${user}:${user} ${dataDir} 160 chmod -R ug+rwX,o-rwx ${dataDir} 161 ''; 162 script = '' 163 # Use User-Private Group scheme to protect Matomo data, but allow administration / backup via 'matomo' group 164 # Copy config folder 165 chmod g+s "${dataDir}" 166 cp -r "${cfg.package}/share/config" "${dataDir}/" 167 chmod -R u+rwX,g+rwX,o-rwx "${dataDir}" 168 169 # check whether user setup has already been done 170 if test -f "${dataDir}/config/config.ini.php"; then 171 # then execute possibly pending database upgrade 172 matomo-console core:update --yes 173 fi 174 ''; 175 }; 176 177 # If this is run regularly via the timer, 178 # 'Browser trigger archiving' can be disabled in Matomo UI > Settings > General Settings. 179 systemd.services.matomo-archive-processing = { 180 description = "Archive Matomo reports"; 181 # the archiving can only work if the database is already up and running 182 requires = [ databaseService ]; 183 after = [ databaseService ]; 184 185 # TODO: might get renamed to MATOMO_USER_PATH in future versions 186 environment.PIWIK_USER_PATH = dataDir; 187 serviceConfig = { 188 Type = "oneshot"; 189 User = user; 190 UMask = "0007"; 191 CPUSchedulingPolicy = "idle"; 192 IOSchedulingClass = "idle"; 193 ExecStart = "${cfg.package}/bin/matomo-console core:archive --url=https://${user}.${fqdn}"; 194 }; 195 }; 196 197 systemd.timers.matomo-archive-processing = mkIf cfg.periodicArchiveProcessing { 198 description = "Automatically archive Matomo reports every hour"; 199 200 wantedBy = [ "timers.target" ]; 201 timerConfig = { 202 OnCalendar = "hourly"; 203 Persistent = "yes"; 204 AccuracySec = "10m"; 205 }; 206 }; 207 208 systemd.services.${phpExecutionUnit} = { 209 # stop phpfpm on package upgrade, do database upgrade via matomo-setup-update, and then restart 210 restartTriggers = [ cfg.package ]; 211 # stop config.ini.php from getting written with read permission for others 212 serviceConfig.UMask = "0007"; 213 }; 214 215 services.phpfpm.pools = let 216 # workaround for when both are null and need to generate a string, 217 # which is illegal, but as assertions apparently are being triggered *after* config generation, 218 # we have to avoid already throwing errors at this previous stage. 219 socketOwner = if (cfg.nginx != null) then config.services.nginx.user 220 else if (cfg.webServerUser != null) then cfg.webServerUser else ""; 221 in { 222 ${pool} = { 223 inherit user; 224 phpOptions = '' 225 error_log = 'stderr' 226 log_errors = on 227 ''; 228 settings = mapAttrs (name: mkDefault) { 229 "listen.owner" = socketOwner; 230 "listen.group" = "root"; 231 "listen.mode" = "0660"; 232 "pm" = "dynamic"; 233 "pm.max_children" = 75; 234 "pm.start_servers" = 10; 235 "pm.min_spare_servers" = 5; 236 "pm.max_spare_servers" = 20; 237 "pm.max_requests" = 500; 238 "catch_workers_output" = true; 239 }; 240 phpEnv.PIWIK_USER_PATH = dataDir; 241 }; 242 }; 243 244 245 services.nginx.virtualHosts = mkIf (cfg.nginx != null) { 246 # References: 247 # https://fralef.me/piwik-hardening-with-nginx-and-php-fpm.html 248 # https://github.com/perusio/piwik-nginx 249 "${user}.${fqdn}" = mkMerge [ cfg.nginx { 250 # don't allow to override the root easily, as it will almost certainly break Matomo. 251 # disadvantage: not shown as default in docs. 252 root = mkForce "${cfg.package}/share"; 253 254 # define locations here instead of as the submodule option's default 255 # so that they can easily be extended with additional locations if required 256 # without needing to redefine the Matomo ones. 257 # disadvantage: not shown as default in docs. 258 locations."/" = { 259 index = "index.php"; 260 }; 261 # allow index.php for webinterface 262 locations."= /index.php".extraConfig = '' 263 fastcgi_pass unix:${fpm.socket}; 264 ''; 265 # allow matomo.php for tracking 266 locations."= /matomo.php".extraConfig = '' 267 fastcgi_pass unix:${fpm.socket}; 268 ''; 269 # allow piwik.php for tracking (deprecated name) 270 locations."= /piwik.php".extraConfig = '' 271 fastcgi_pass unix:${fpm.socket}; 272 ''; 273 # Any other attempt to access any php files is forbidden 274 locations."~* ^.+\\.php$".extraConfig = '' 275 return 403; 276 ''; 277 # Disallow access to unneeded directories 278 # config and tmp are already removed 279 locations."~ ^/(?:core|lang|misc)/".extraConfig = '' 280 return 403; 281 ''; 282 # Disallow access to several helper files 283 locations."~* \\.(?:bat|git|ini|sh|txt|tpl|xml|md)$".extraConfig = '' 284 return 403; 285 ''; 286 # No crawling of this site for bots that obey robots.txt - no useful information here. 287 locations."= /robots.txt".extraConfig = '' 288 return 200 "User-agent: *\nDisallow: /\n"; 289 ''; 290 # let browsers cache matomo.js 291 locations."= /matomo.js".extraConfig = '' 292 expires 1M; 293 ''; 294 # let browsers cache piwik.js (deprecated name) 295 locations."= /piwik.js".extraConfig = '' 296 expires 1M; 297 ''; 298 }]; 299 }; 300 }; 301 302 meta = { 303 doc = ./matomo-doc.xml; 304 maintainers = with lib.maintainers; [ florianjacob ]; 305 }; 306}