···1+<chapter xmlns="http://docbook.org/ns/docbook"
2+ xmlns:xlink="http://www.w3.org/1999/xlink"
3+ xmlns:xi="http://www.w3.org/2001/XInclude"
4+ version="5.0"
5+ xml:id="module-services-piwik">
6+7+ <title>Piwik</title>
8+ <para>
9+ Piwik is a real-time web analytics application.
10+ This module configures php-fpm as backend for piwik, optionally configuring an nginx vhost as well.
11+ </para>
12+13+ <para>
14+ An automatic setup is not suported by piwik, so you need to configure piwik itself in the browser-based piwik setup.
15+ </para>
16+17+18+ <section>
19+ <title>Database Setup</title>
20+21+ <para>
22+ You also need to configure a MariaDB or MySQL database and -user for piwik yourself,
23+ and enter those credentials in your browser.
24+ You can use passwordless database authentication via the UNIX_SOCKET authentication plugin
25+ with the following SQL commands:
26+ <programlisting>
27+ INSTALL PLUGIN unix_socket SONAME 'auth_socket';
28+ ALTER USER root IDENTIFIED VIA unix_socket;
29+ CREATE DATABASE piwik;
30+ CREATE USER 'piwik'@'localhost' IDENTIFIED VIA unix_socket;
31+ GRANT ALL PRIVILEGES ON piwik.* TO 'piwik'@'localhost';
32+ </programlisting>
33+ Then fill in <literal>piwik</literal> as database user and database name, and leave the password field blank.
34+ This works with MariaDB and MySQL. This authentication works by allowing only the <literal>piwik</literal> unix
35+ user to authenticate as <literal>piwik</literal> database (without needing a password), but no other users.
36+ For more information on passwordless login, see
37+ <link xlink:href="https://mariadb.com/kb/en/mariadb/unix_socket-authentication-plugin/" />.
38+ </para>
39+40+ <para>
41+ Of course, you can use password based authentication as well, e.g. when the database is not on the same host.
42+ </para>
43+ </section>
44+45+46+ <section>
47+ <title>Backup</title>
48+ <para>
49+ You only need to take backups of your MySQL database and the
50+ <filename>/var/lib/piwik/config/config.ini.php</filename> file.
51+ Use a user in the <literal>piwik</literal> group or root to access the file.
52+ For more information, see <link xlink:href="https://piwik.org/faq/how-to-install/faq_138/" />.
53+ </para>
54+ </section>
55+56+57+ <section>
58+ <title>Issues</title>
59+ <itemizedlist>
60+ <listitem>
61+ <para>
62+ Piwik's file integrity check will warn you.
63+ This is due to the patches necessary for NixOS, you can safely ignore this.
64+ </para>
65+ </listitem>
66+67+ <listitem>
68+ <para>
69+ Piwik will warn you that the JavaScript tracker is not writable.
70+ This is because it's located in the read-only nix store.
71+ You can safely ignore this, unless you need a plugin that needs JavaScript tracker access.
72+ </para>
73+ </listitem>
74+75+ <listitem>
76+ <para>
77+ Sending mail from piwik, e.g. for the password reset function, might not work out of the box:
78+ There's a problem with using <command>sendmail</command> from <literal>php-fpm</literal> that is
79+ being investigated at <link xlink:href="https://github.com/NixOS/nixpkgs/issues/26611" />.
80+ If you have (or don't have) this problem as well, please report it. You can enable SMTP as method
81+ to send mail in piwik's <quote>General Settings</quote> > <quote>Mail Server Settings</quote> instead.
82+ </para>
83+ </listitem>
84+ </itemizedlist>
85+ </section>
86+87+88+ <section>
89+ <title>Using other Web Servers than nginx</title>
90+91+ <para>
92+ You can use other web servers by forwarding calls for <filename>index.php</filename> and
93+ <filename>piwik.php</filename> to the <literal>/run/phpfpm-piwik.sock</literal> fastcgi unix socket.
94+ You can use the nginx configuration in the module code as a reference to what else should be configured.
95+ </para>
96+ </section>
97+</chapter>
···1+{ config, lib, pkgs, services, ... }:
2+with lib;
3+let
4+ cfg = config.services.piwik;
5+6+ user = "piwik";
7+ dataDir = "/var/lib/${user}";
8+9+ pool = user;
10+ # it's not possible to use /run/phpfpm/${pool}.sock because /run/phpfpm/ is root:root 0770,
11+ # and therefore is not accessible by the web server.
12+ phpSocket = "/run/phpfpm-${pool}.sock";
13+ phpExecutionUnit = "phpfpm-${pool}";
14+ databaseService = "mysql.service";
15+16+in {
17+ options = {
18+ services.piwik = {
19+ # NixOS PR for database setup: https://github.com/NixOS/nixpkgs/pull/6963
20+ # piwik issue for automatic piwik setup: https://github.com/piwik/piwik/issues/10257
21+ # TODO: find a nice way to do this when more NixOS MySQL and / or piwik automatic setup stuff is implemented.
22+ enable = mkOption {
23+ type = types.bool;
24+ default = false;
25+ description = ''
26+ Enable piwik web analytics with php-fpm backend.
27+ '';
28+ };
29+30+ webServerUser = mkOption {
31+ type = types.str;
32+ example = "nginx";
33+ description = ''
34+ Name of the owner of the ${phpSocket} fastcgi socket for piwik.
35+ If you want to use another webserver than nginx, you need to set this to that server's user
36+ and pass fastcgi requests to `index.php` and `piwik.php` to this socket.
37+ '';
38+ };
39+40+ phpfpmProcessManagerConfig = mkOption {
41+ type = types.str;
42+ default = ''
43+ ; default phpfpm process manager settings
44+ pm = dynamic
45+ pm.max_children = 75
46+ pm.start_servers = 10
47+ pm.min_spare_servers = 5
48+ pm.max_spare_servers = 20
49+ pm.max_requests = 500
50+51+ ; log worker's stdout, but this has a performance hit
52+ catch_workers_output = yes
53+ '';
54+ description = ''
55+ Settings for phpfpm's process manager. You might need to change this depending on the load for piwik.
56+ '';
57+ };
58+59+ nginx = mkOption {
60+ # TODO: for maximum flexibility, it would be nice to use nginx's vhost_options module
61+ # but this only makes sense if we can somehow specify defaults suitable for piwik.
62+ # But users can always copy the piwik nginx config to their configuration.nix and customize it.
63+ type = types.nullOr (types.submodule {
64+ options = {
65+ virtualHost = mkOption {
66+ type = types.str;
67+ default = "piwik.${config.networking.hostName}";
68+ example = "piwik.$\{config.networking.hostName\}";
69+ description = ''
70+ Name of the nginx virtualhost to use and set up.
71+ '';
72+ };
73+ enableSSL = mkOption {
74+ type = types.bool;
75+ default = true;
76+ description = "Whether to enable https.";
77+ };
78+ forceSSL = mkOption {
79+ type = types.bool;
80+ default = true;
81+ description = "Whether to always redirect to https.";
82+ };
83+ enableACME = mkOption {
84+ type = types.bool;
85+ default = true;
86+ description = "Whether to ask Let's Encrypt to sign a certificate for this vhost.";
87+ };
88+ };
89+ });
90+ default = null;
91+ example = { virtualHost = "stats.$\{config.networking.hostName\}"; };
92+ description = ''
93+ The options to use to configure an nginx virtualHost.
94+ If null (the default), no nginx virtualHost will be configured.
95+ '';
96+ };
97+ };
98+ };
99+100+ config = mkIf cfg.enable {
101+102+ users.extraUsers.${user} = {
103+ isSystemUser = true;
104+ createHome = true;
105+ home = dataDir;
106+ group = user;
107+ };
108+ users.extraGroups.${user} = {};
109+110+ systemd.services.piwik_setup_update = {
111+ # everything needs to set up and up to date before piwik php files are executed
112+ requiredBy = [ "${phpExecutionUnit}.service" ];
113+ before = [ "${phpExecutionUnit}.service" ];
114+ # the update part of the script can only work if the database is already up and running
115+ requires = [ databaseService ];
116+ after = [ databaseService ];
117+ path = [ pkgs.piwik ];
118+ serviceConfig = {
119+ Type = "oneshot";
120+ User = user;
121+ # hide especially config.ini.php from other
122+ UMask = "0007";
123+ Environment = "PIWIK_USER_PATH=${dataDir}";
124+ # chown + chmod in preStart needs root
125+ PermissionsStartOnly = true;
126+ };
127+ # correct ownership and permissions in case they're not correct anymore,
128+ # e.g. after restoring from backup or moving from another system.
129+ # Note that ${dataDir}/config/config.ini.php might contain the MySQL password.
130+ preStart = ''
131+ chown -R ${user}:${user} ${dataDir}
132+ chmod -R ug+rwX,o-rwx ${dataDir}
133+ '';
134+ script = ''
135+ # Use User-Private Group scheme to protect piwik data, but allow administration / backup via piwik group
136+ # Copy config folder
137+ chmod g+s "${dataDir}"
138+ cp -r "${pkgs.piwik}/config" "${dataDir}/"
139+ chmod -R u+rwX,g+rwX,o-rwx "${dataDir}"
140+141+ # check whether user setup has already been done
142+ if test -f "${dataDir}/config/config.ini.php"; then
143+ # then execute possibly pending database upgrade
144+ piwik-console core:update --yes
145+ fi
146+ '';
147+ };
148+149+ systemd.services.${phpExecutionUnit} = {
150+ # stop phpfpm on package upgrade, do database upgrade via piwik_setup_update, and then restart
151+ restartTriggers = [ pkgs.piwik ];
152+ # stop config.ini.php from getting written with read permission for others
153+ serviceConfig.UMask = "0007";
154+ };
155+156+ services.phpfpm.poolConfigs = {
157+ ${pool} = ''
158+ listen = "${phpSocket}"
159+ listen.owner = ${cfg.webServerUser}
160+ listen.group = root
161+ listen.mode = 0600
162+ user = ${user}
163+ env[PIWIK_USER_PATH] = ${dataDir}
164+ ${cfg.phpfpmProcessManagerConfig}
165+ '';
166+ };
167+168+169+ services.nginx.virtualHosts = mkIf (cfg.nginx != null) {
170+ # References:
171+ # https://fralef.me/piwik-hardening-with-nginx-and-php-fpm.html
172+ # https://github.com/perusio/piwik-nginx
173+ ${cfg.nginx.virtualHost} = {
174+ root = "${pkgs.piwik}/share";
175+ enableSSL = cfg.nginx.enableSSL;
176+ enableACME = cfg.nginx.enableACME;
177+ forceSSL = cfg.nginx.forceSSL;
178+179+ locations."/" = {
180+ index = "index.php";
181+ };
182+ # allow index.php for webinterface
183+ locations."= /index.php".extraConfig = ''
184+ fastcgi_pass unix:${phpSocket};
185+ '';
186+ # allow piwik.php for tracking
187+ locations."= /piwik.php".extraConfig = ''
188+ fastcgi_pass unix:${phpSocket};
189+ '';
190+ # Any other attempt to access any php files is forbidden
191+ locations."~* ^.+\.php$".extraConfig = ''
192+ return 403;
193+ '';
194+ # Disallow access to unneeded directories
195+ # config and tmp are already removed
196+ locations."~ ^/(?:core|lang|misc)/".extraConfig = ''
197+ return 403;
198+ '';
199+ # Disallow access to several helper files
200+ locations."~* \.(?:bat|git|ini|sh|txt|tpl|xml|md)$".extraConfig = ''
201+ return 403;
202+ '';
203+ # No crawling of this site for bots that obey robots.txt - no useful information here.
204+ locations."= /robots.txt".extraConfig = ''
205+ return 200 "User-agent: *\nDisallow: /\n";
206+ '';
207+ # let browsers cache piwik.js
208+ locations."= /piwik.js".extraConfig = ''
209+ expires 1M;
210+ '';
211+ };
212+ };
213+ };
214+215+ meta = {
216+ doc = ./piwik-doc.xml;
217+ maintainers = with stdenv.lib.maintainers; [ florianjacob ];
218+ };
219+}
+6
pkgs/servers/web-apps/piwik/bootstrap.php
···000000
···1+<?php
2+// get PIWIK_USER_PATH from environment variable,
3+// so this bootstrap.php can be read-only but still configure PIWIK_USER_PATH at runtime
4+if ($path = getenv('PIWIK_USER_PATH')) {
5+ define('PIWIK_USER_PATH', $path);
6+}