1 { config, lib, options, pkgs, ... }:
4 cfg = config.services.matomo;
5 fpm = config.services.phpfpm.pools.${pool};
8 dataDir = "/var/lib/${user}";
9 deprecatedDataDir = "/var/lib/piwik";
12 phpExecutionUnit = "phpfpm-${pool}";
13 databaseService = "mysql.service";
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" ])
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.
34 Enable Matomo web analytics with php-fpm backend.
35 Either the nginx option or the webServerUser option is mandatory.
39 package = mkPackageOption pkgs "matomo" { };
41 webServerUser = mkOption {
42 type = types.nullOr types.str;
46 Name of the web server user that forwards requests to {option}`services.phpfpm.pools.<name>.socket` the fastcgi socket for Matomo if the nginx
47 option is not used. Either this option or the nginx option is mandatory.
48 If you want to use another webserver than nginx, you need to set this to that server's user
49 and pass fastcgi requests to `index.php`, `matomo.php` and `piwik.php` (legacy name) to this socket.
53 periodicArchiveProcessing = mkOption {
57 Enable periodic archive processing, which generates aggregated reports from the visits.
59 This means that you can safely disable browser triggers for Matomo archiving,
60 and safely enable to delete old visitor logs.
61 Before deleting visitor logs,
62 make sure though that you run `systemctl start matomo-archive-processing.service`
63 at least once without errors if you have already collected data before.
69 default = "${user}.${config.networking.fqdnOrHostName}";
70 defaultText = literalExpression ''
71 "${user}.''${config.${options.networking.fqdnOrHostName}}"
73 example = "matomo.yourdomain.org";
75 URL of the host, without https prefix. You may want to change it if you
76 run Matomo on a different URL than matomo.yourdomain.
81 type = types.nullOr (types.submodule (
83 (import ../web-servers/nginx/vhost-options.nix { inherit config lib; })
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;
93 example = literalExpression ''
96 "matomo.''${config.networking.domain}"
97 "stats.''${config.networking.domain}"
103 With this option, you can customize an nginx virtualHost which already has sensible defaults for Matomo.
104 Either this option or the webServerUser option is mandatory.
105 Set this to {} to just enable the virtualHost if you don't need any customization.
106 If enabled, then by default, the {option}`serverName` is
107 `''${user}.''${config.networking.hostName}.''${config.networking.domain}`,
108 SSL is active, and certificates are acquired via ACME.
109 If this is set to null (the default), no nginx virtualHost will be configured.
115 config = mkIf cfg.enable {
116 warnings = mkIf (cfg.nginx != null && cfg.webServerUser != null) [
117 "If services.matomo.nginx is set, services.matomo.nginx.webServerUser is ignored and should be removed."
121 assertion = cfg.nginx != null || cfg.webServerUser != null;
122 message = "Either services.matomo.nginx or services.matomo.nginx.webServerUser is mandatory";
125 users.users.${user} = {
131 users.groups.${user} = {};
133 systemd.services.matomo-setup-update = {
134 # everything needs to set up and up to date before Matomo php files are executed
135 requiredBy = [ "${phpExecutionUnit}.service" ];
136 before = [ "${phpExecutionUnit}.service" ];
137 # the update part of the script can only work if the database is already up and running
138 requires = [ databaseService ];
139 after = [ databaseService ];
140 path = [ cfg.package ];
141 environment.PIWIK_USER_PATH = dataDir;
145 # hide especially config.ini.php from other
147 # TODO: might get renamed to MATOMO_USER_PATH in future versions
148 # chown + chmod in preStart needs root
149 PermissionsStartOnly = true;
152 # correct ownership and permissions in case they're not correct anymore,
153 # e.g. after restoring from backup or moving from another system.
154 # Note that ${dataDir}/config/config.ini.php might contain the MySQL password.
156 # migrate data from piwik to Matomo folder
157 if [ -d ${deprecatedDataDir} ]; then
158 echo "Migrating from ${deprecatedDataDir} to ${dataDir}"
159 mv -T ${deprecatedDataDir} ${dataDir}
161 chown -R ${user}:${user} ${dataDir}
162 chmod -R ug+rwX,o-rwx ${dataDir}
164 if [ -e ${dataDir}/current-package ]; then
165 CURRENT_PACKAGE=$(readlink ${dataDir}/current-package)
166 NEW_PACKAGE=${cfg.package}
167 if [ "$CURRENT_PACKAGE" != "$NEW_PACKAGE" ]; then
168 # keeping tmp around between upgrades seems to bork stuff, so delete it
169 rm -rf ${dataDir}/tmp
171 elif [ -e ${dataDir}/tmp ]; then
173 rm -rf ${dataDir}/tmp
175 ln -sfT ${cfg.package} ${dataDir}/current-package
178 # Use User-Private Group scheme to protect Matomo data, but allow administration / backup via 'matomo' group
180 chmod g+s "${dataDir}"
181 cp -r "${cfg.package}/share/config" "${dataDir}/"
182 mkdir -p "${dataDir}/misc"
183 chmod -R u+rwX,g+rwX,o-rwx "${dataDir}"
185 # check whether user setup has already been done
186 if test -f "${dataDir}/config/config.ini.php"; then
187 # then execute possibly pending database upgrade
188 matomo-console core:update --yes
193 # If this is run regularly via the timer,
194 # 'Browser trigger archiving' can be disabled in Matomo UI > Settings > General Settings.
195 systemd.services.matomo-archive-processing = {
196 description = "Archive Matomo reports";
197 # the archiving can only work if the database is already up and running
198 requires = [ databaseService ];
199 after = [ databaseService ];
201 # TODO: might get renamed to MATOMO_USER_PATH in future versions
202 environment.PIWIK_USER_PATH = dataDir;
207 CPUSchedulingPolicy = "idle";
208 IOSchedulingClass = "idle";
209 ExecStart = "${cfg.package}/bin/matomo-console core:archive --url=https://${cfg.hostname}";
213 systemd.timers.matomo-archive-processing = mkIf cfg.periodicArchiveProcessing {
214 description = "Automatically archive Matomo reports every hour";
216 wantedBy = [ "timers.target" ];
218 OnCalendar = "hourly";
224 systemd.services.${phpExecutionUnit} = {
225 # stop phpfpm on package upgrade, do database upgrade via matomo-setup-update, and then restart
226 restartTriggers = [ cfg.package ];
227 # stop config.ini.php from getting written with read permission for others
228 serviceConfig.UMask = "0007";
231 services.phpfpm.pools = let
232 # workaround for when both are null and need to generate a string,
233 # which is illegal, but as assertions apparently are being triggered *after* config generation,
234 # we have to avoid already throwing errors at this previous stage.
235 socketOwner = if (cfg.nginx != null) then config.services.nginx.user
236 else if (cfg.webServerUser != null) then cfg.webServerUser else "";
244 settings = mapAttrs (name: mkDefault) {
245 "listen.owner" = socketOwner;
246 "listen.group" = "root";
247 "listen.mode" = "0660";
249 "pm.max_children" = 75;
250 "pm.start_servers" = 10;
251 "pm.min_spare_servers" = 5;
252 "pm.max_spare_servers" = 20;
253 "pm.max_requests" = 500;
254 "catch_workers_output" = true;
256 phpEnv.PIWIK_USER_PATH = dataDir;
261 services.nginx.virtualHosts = mkIf (cfg.nginx != null) {
263 # https://fralef.me/piwik-hardening-with-nginx-and-php-fpm.html
264 # https://github.com/perusio/piwik-nginx
265 "${cfg.hostname}" = mkMerge [ cfg.nginx {
266 # don't allow to override the root easily, as it will almost certainly break Matomo.
267 # disadvantage: not shown as default in docs.
268 root = mkForce "${cfg.package}/share";
270 # define locations here instead of as the submodule option's default
271 # so that they can easily be extended with additional locations if required
272 # without needing to redefine the Matomo ones.
273 # disadvantage: not shown as default in docs.
277 # allow index.php for webinterface
278 locations."= /index.php".extraConfig = ''
279 fastcgi_pass unix:${fpm.socket};
281 # allow matomo.php for tracking
282 locations."= /matomo.php".extraConfig = ''
283 fastcgi_pass unix:${fpm.socket};
285 # allow piwik.php for tracking (deprecated name)
286 locations."= /piwik.php".extraConfig = ''
287 fastcgi_pass unix:${fpm.socket};
289 # Any other attempt to access any php files is forbidden
290 locations."~* ^.+\\.php$".extraConfig = ''
293 # Disallow access to unneeded directories
294 # config and tmp are already removed
295 locations."~ ^/(?:core|lang|misc)/".extraConfig = ''
298 # Disallow access to several helper files
299 locations."~* \\.(?:bat|git|ini|sh|txt|tpl|xml|md)$".extraConfig = ''
302 # No crawling of this site for bots that obey robots.txt - no useful information here.
303 locations."= /robots.txt".extraConfig = ''
304 return 200 "User-agent: *\nDisallow: /\n";
306 # let browsers cache matomo.js
307 locations."= /matomo.js".extraConfig = ''
310 # let browsers cache piwik.js (deprecated name)
311 locations."= /piwik.js".extraConfig = ''
320 maintainers = with lib.maintainers; [ florianjacob ];