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