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