vuls: init at 0.27.0 (#348530)
[NixPkgs.git] / nixos / modules / services / web-apps / freshrss.nix
blob9622913feca078c921141b8e734af3df920d6dae
1 { config, lib, pkgs, ... }:
3 with lib;
4 let
5   cfg = config.services.freshrss;
7   extension-env = pkgs.buildEnv {
8     name = "freshrss-extensions";
9     paths = cfg.extensions;
10   };
11   env-vars = {
12     DATA_PATH = cfg.dataDir;
13     THIRDPARTY_EXTENSIONS_PATH = "${extension-env}/share/freshrss/";
14   };
17   meta.maintainers = with maintainers; [ etu stunkymonkey mattchrist ];
19   options.services.freshrss = {
20     enable = mkEnableOption "FreshRSS RSS aggregator and reader with php-fpm backend";
22     package = mkPackageOption pkgs "freshrss" { };
24     extensions = mkOption {
25       type = types.listOf types.package;
26       default = [ ];
27       defaultText = literalExpression "[]";
28       example = literalExpression ''
29         with freshrss-extensions; [
30           youtube
31         ] ++ [
32           (freshrss-extensions.buildFreshRssExtension {
33             FreshRssExtUniqueId = "ReadingTime";
34             pname = "reading-time";
35             version = "1.5";
36             src = pkgs.fetchFromGitLab {
37               domain = "framagit.org";
38               owner = "Lapineige";
39               repo = "FreshRSS_Extension-ReadingTime";
40               rev = "fb6e9e944ef6c5299fa56ffddbe04c41e5a34ebf";
41              hash = "sha256-C5cRfaphx4Qz2xg2z+v5qRji8WVSIpvzMbethTdSqsk=";
42            };
43           })
44         ]
45       '';
46       description = "Additional extensions to be used.";
47     };
49     defaultUser = mkOption {
50       type = types.str;
51       default = "admin";
52       description = "Default username for FreshRSS.";
53       example = "eva";
54     };
56     passwordFile = mkOption {
57       type = types.nullOr types.path;
58       default = null;
59       description = "Password for the defaultUser for FreshRSS.";
60       example = "/run/secrets/freshrss";
61     };
63     baseUrl = mkOption {
64       type = types.str;
65       description = "Default URL for FreshRSS.";
66       example = "https://freshrss.example.com";
67     };
69     language = mkOption {
70       type = types.str;
71       default = "en";
72       description = "Default language for FreshRSS.";
73       example = "de";
74     };
76     database = {
77       type = mkOption {
78         type = types.enum [ "sqlite" "pgsql" "mysql" ];
79         default = "sqlite";
80         description = "Database type.";
81         example = "pgsql";
82       };
84       host = mkOption {
85         type = types.nullOr types.str;
86         default = "localhost";
87         description = "Database host for FreshRSS.";
88       };
90       port = mkOption {
91         type = types.nullOr types.port;
92         default = null;
93         description = "Database port for FreshRSS.";
94         example = 3306;
95       };
97       user = mkOption {
98         type = types.nullOr types.str;
99         default = "freshrss";
100         description = "Database user for FreshRSS.";
101       };
103       passFile = mkOption {
104         type = types.nullOr types.path;
105         default = null;
106         description = "Database password file for FreshRSS.";
107         example = "/run/secrets/freshrss";
108       };
110       name = mkOption {
111         type = types.nullOr types.str;
112         default = "freshrss";
113         description = "Database name for FreshRSS.";
114       };
116       tableprefix = mkOption {
117         type = types.nullOr types.str;
118         default = null;
119         description = "Database table prefix for FreshRSS.";
120         example = "freshrss";
121       };
122     };
124     dataDir = mkOption {
125       type = types.str;
126       default = "/var/lib/freshrss";
127       description = "Default data folder for FreshRSS.";
128       example = "/mnt/freshrss";
129     };
131     virtualHost = mkOption {
132       type = types.nullOr types.str;
133       default = "freshrss";
134       description = ''
135         Name of the nginx virtualhost to use and setup. If null, do not setup any virtualhost.
136         You may need to configure the virtualhost further through services.nginx.virtualHosts.<virtualhost>,
137         for example to enable SSL.
138       '';
139     };
141     pool = mkOption {
142       type = types.nullOr types.str;
143       default = "freshrss";
144       description = ''
145         Name of the php-fpm pool to use and setup. If not specified, a pool will be created
146         with default values.
147       '';
148     };
150     user = mkOption {
151       type = types.str;
152       default = "freshrss";
153       description = "User under which FreshRSS runs.";
154     };
156     authType = mkOption {
157       type = types.enum [ "form" "http_auth" "none" ];
158       default = "form";
159       description = "Authentication type for FreshRSS.";
160     };
161   };
163   config =
164     let
165       defaultServiceConfig = {
166         ReadWritePaths = "${cfg.dataDir}";
167         CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
168         DeviceAllow = "";
169         LockPersonality = true;
170         NoNewPrivileges = true;
171         PrivateDevices = true;
172         PrivateTmp = true;
173         PrivateUsers = true;
174         ProcSubset = "pid";
175         ProtectClock = true;
176         ProtectControlGroups = true;
177         ProtectHome = true;
178         ProtectHostname = true;
179         ProtectKernelLogs = true;
180         ProtectKernelModules = true;
181         ProtectKernelTunables = true;
182         ProtectProc = "invisible";
183         ProtectSystem = "strict";
184         RemoveIPC = true;
185         RestrictNamespaces = true;
186         RestrictRealtime = true;
187         RestrictSUIDSGID = true;
188         SystemCallArchitectures = "native";
189         SystemCallFilter = [ "@system-service" "~@resources" "~@privileged" ];
190         UMask = "0007";
191         Type = "oneshot";
192         User = cfg.user;
193         Group = config.users.users.${cfg.user}.group;
194         StateDirectory = "freshrss";
195         WorkingDirectory = cfg.package;
196       };
197     in
198     mkIf cfg.enable {
199       assertions = mkIf (cfg.authType == "form") [
200         {
201           assertion = cfg.passwordFile != null;
202           message = ''
203             `passwordFile` must be supplied when using "form" authentication!
204           '';
205         }
206       ];
207       # Set up a Nginx virtual host.
208       services.nginx = mkIf (cfg.virtualHost != null) {
209         enable = true;
210         virtualHosts.${cfg.virtualHost} = {
211           root = "${cfg.package}/p";
213           # php files handling
214           # this regex is mandatory because of the API
215           locations."~ ^.+?\.php(/.*)?$".extraConfig = ''
216             fastcgi_pass unix:${config.services.phpfpm.pools.${cfg.pool}.socket};
217             fastcgi_split_path_info ^(.+\.php)(/.*)$;
218             # By default, the variable PATH_INFO is not set under PHP-FPM
219             # But FreshRSS API greader.php need it. If you have a “Bad Request” error, double check this var!
220             # NOTE: the separate $path_info variable is required. For more details, see:
221             # https://trac.nginx.org/nginx/ticket/321
222             set $path_info $fastcgi_path_info;
223             fastcgi_param PATH_INFO $path_info;
224             include ${pkgs.nginx}/conf/fastcgi_params;
225             include ${pkgs.nginx}/conf/fastcgi.conf;
226           '';
228           locations."/" = {
229             tryFiles = "$uri $uri/ index.php";
230             index = "index.php index.html index.htm";
231           };
232         };
233       };
235       # Set up phpfpm pool
236       services.phpfpm.pools = mkIf (cfg.pool != null) {
237         ${cfg.pool} = {
238           user = "freshrss";
239           settings = {
240             "listen.owner" = "nginx";
241             "listen.group" = "nginx";
242             "listen.mode" = "0600";
243             "pm" = "dynamic";
244             "pm.max_children" = 32;
245             "pm.max_requests" = 500;
246             "pm.start_servers" = 2;
247             "pm.min_spare_servers" = 2;
248             "pm.max_spare_servers" = 5;
249             "catch_workers_output" = true;
250           };
251           phpEnv = env-vars;
252         };
253       };
255       users.users."${cfg.user}" = {
256         description = "FreshRSS service user";
257         isSystemUser = true;
258         group = "${cfg.user}";
259         home = cfg.dataDir;
260       };
261       users.groups."${cfg.user}" = { };
263       systemd.tmpfiles.settings."10-freshrss".${cfg.dataDir}.d = {
264         inherit (cfg) user;
265         group = config.users.users.${cfg.user}.group;
266       };
268       systemd.services.freshrss-config =
269         let
270           settingsFlags = concatStringsSep " \\\n    "
271             (mapAttrsToList (k: v: "${k} ${toString v}") {
272               "--default-user" = ''"${cfg.defaultUser}"'';
273               "--auth-type" = ''"${cfg.authType}"'';
274               "--base-url" = ''"${cfg.baseUrl}"'';
275               "--language" = ''"${cfg.language}"'';
276               "--db-type" = ''"${cfg.database.type}"'';
277               # The following attributes are optional depending on the type of
278               # database.  Those that evaluate to null on the left hand side
279               # will be omitted.
280               ${if cfg.database.name != null then "--db-base" else null} = ''"${cfg.database.name}"'';
281               ${if cfg.database.passFile != null then "--db-password" else null} = ''"$(cat ${cfg.database.passFile})"'';
282               ${if cfg.database.user != null then "--db-user" else null} = ''"${cfg.database.user}"'';
283               ${if cfg.database.tableprefix != null then "--db-prefix" else null} = ''"${cfg.database.tableprefix}"'';
284               # hostname:port e.g. "localhost:5432"
285               ${if cfg.database.host != null && cfg.database.port != null then "--db-host" else null} = ''"${cfg.database.host}:${toString cfg.database.port}"'';
286               # socket path e.g. "/run/postgresql"
287               ${if cfg.database.host != null && cfg.database.port == null then "--db-host" else null} = ''"${cfg.database.host}"'';
288             });
289         in
290         {
291           description = "Set up the state directory for FreshRSS before use";
292           wantedBy = [ "multi-user.target" ];
293           serviceConfig = defaultServiceConfig // {
294             RemainAfterExit = true;
295           };
296           restartIfChanged = true;
297           environment = env-vars;
299           script =
300             let
301               userScriptArgs = ''--user ${cfg.defaultUser} ${optionalString (cfg.authType == "form") ''--password "$(cat ${cfg.passwordFile})"''}'';
302               updateUserScript = optionalString (cfg.authType == "form" || cfg.authType == "none") ''
303                 ./cli/update-user.php ${userScriptArgs}
304               '';
305               createUserScript = optionalString (cfg.authType == "form" || cfg.authType == "none") ''
306                 ./cli/create-user.php ${userScriptArgs}
307               '';
308             in
309             ''
310               # do installation or reconfigure
311               if test -f ${cfg.dataDir}/config.php; then
312                 # reconfigure with settings
313                 ./cli/reconfigure.php ${settingsFlags}
314                 ${updateUserScript}
315               else
316                 # check correct folders in data folder
317                 ./cli/prepare.php
318                 # install with settings
319                 ./cli/do-install.php ${settingsFlags}
320                 ${createUserScript}
321               fi
322             '';
323         };
325       systemd.services.freshrss-updater = {
326         description = "FreshRSS feed updater";
327         after = [ "freshrss-config.service" ];
328         startAt = "*:0/5";
329         environment = env-vars;
330         serviceConfig = defaultServiceConfig // {
331           ExecStart = "${cfg.package}/app/actualize_script.php";
332         };
333       };
334     };