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