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