grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / web-apps / gancio.nix
blobfa55db762c467fe06b7bccae96189c03c43a4d50
2   config,
3   lib,
4   pkgs,
5   ...
6 }:
7 let
8   cfg = config.services.gancio;
9   settingsFormat = pkgs.formats.json { };
10   inherit (lib)
11     mkEnableOption
12     mkPackageOption
13     mkOption
14     types
15     literalExpression
16     mkIf
17     optional
18     mapAttrsToList
19     concatStringsSep
20     concatMapStringsSep
21     getExe
22     mkMerge
23     mkDefault
24     ;
27   options.services.gancio = {
28     enable = mkEnableOption "Gancio, a shared agenda for local communities";
30     package = mkPackageOption pkgs "gancio" { };
32     plugins = mkOption {
33       type = with types; listOf package;
34       default = [ ];
35       example = literalExpression "[ pkgs.gancioPlugins.telegram-bridge ]";
36       description = ''
37         Paths of gancio plugins to activate (linked under $WorkingDirectory/plugins/).
38       '';
39     };
41     user = mkOption {
42       type = types.str;
43       description = "The user (and PostgreSQL database name) used to run the gancio server";
44       default = "gancio";
45     };
47     settings = mkOption rec {
48       type = types.submodule {
49         freeformType = settingsFormat.type;
50         options = {
51           hostname = mkOption {
52             type = types.str;
53             description = "The domain name under which the server is reachable.";
54           };
55           baseurl = mkOption {
56             type = types.str;
57             default = "http${
58               lib.optionalString config.services.nginx.virtualHosts."${cfg.settings.hostname}".enableACME "s"
59             }://${cfg.settings.hostname}";
60             defaultText = lib.literalExpression ''"https://''${cfg.settings.hostname}"'';
61             example = "https://demo.gancio.org/gancio";
62             description = "The full URL under which the server is reachable.";
63           };
64           server = {
65             socket = mkOption {
66               type = types.path;
67               readOnly = true;
68               default = "/run/gancio/socket";
69               description = ''
70                 The unix socket for the gancio server to listen on.
71               '';
72             };
73           };
74           db = {
75             dialect = mkOption {
76               type = types.enum [
77                 "sqlite"
78                 "postgres"
79               ];
80               default = "sqlite";
81               description = ''
82                 The database dialect to use
83               '';
84             };
85             storage = mkOption {
86               description = ''
87                 Location for the SQLite database.
88               '';
89               readOnly = true;
90               type = types.nullOr types.str;
91               default = if cfg.settings.db.dialect == "sqlite" then "/var/lib/gancio/db.sqlite" else null;
92               defaultText = ''
93                 if cfg.settings.db.dialect == "sqlite" then "/var/lib/gancio/db.sqlite" else null
94               '';
95             };
96             host = mkOption {
97               description = ''
98                 Connection string for the PostgreSQL database
99               '';
100               readOnly = true;
101               type = types.nullOr types.str;
102               default = if cfg.settings.db.dialect == "postgres" then "/run/postgresql" else null;
103               defaultText = ''
104                 if cfg.settings.db.dialect == "postgres" then "/run/postgresql" else null
105               '';
106             };
107             database = mkOption {
108               description = ''
109                 Name of the PostgreSQL database
110               '';
111               readOnly = true;
112               type = types.nullOr types.str;
113               default = if cfg.settings.db.dialect == "postgres" then cfg.user else null;
114               defaultText = ''
115                 if cfg.settings.db.dialect == "postgres" then cfg.user else null
116               '';
117             };
118           };
119           log_level = mkOption {
120             description = "Gancio log level.";
121             type = types.enum [
122               "debug"
123               "info"
124               "warning"
125               "error"
126             ];
127             default = "info";
128           };
129           # FIXME upstream proper journald logging
130           log_path = mkOption {
131             description = "Directory Gancio logs into";
132             readOnly = true;
133             type = types.str;
134             default = "/var/log/gancio";
135           };
136         };
137       };
138       description = ''
139         Configuration for Gancio, see <https://gancio.org/install/config> for supported values.
140       '';
141     };
143     userLocale = mkOption {
144       type = with types; attrsOf (attrsOf (attrsOf str));
145       default = { };
146       example = {
147         en.register.description = "My new registration page description";
148       };
149       description = ''
150         Override default locales within gancio.
151         See [https://framagit.org/les/gancio/tree/master/locales](default languages and locales).
152       '';
153     };
155     nginx = mkOption {
156       type = types.submodule (
157         lib.recursiveUpdate (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) {
158           # enable encryption by default,
159           # as sensitive login credentials should not be transmitted in clear text.
160           options.forceSSL.default = true;
161           options.enableACME.default = true;
162         }
163       );
164       default = { };
165       example = {
166         enableACME = false;
167         forceSSL = false;
168       };
169       description = "Extra configuration for the nginx virtual host of gancio.";
170     };
171   };
173   config = mkIf cfg.enable {
174     environment.systemPackages = [ cfg.package ];
176     users.users.gancio = lib.mkIf (cfg.user == "gancio") {
177       isSystemUser = true;
178       group = cfg.user;
179       home = "/var/lib/gancio";
180     };
181     users.groups.gancio = lib.mkIf (cfg.user == "gancio") { };
183     systemd.tmpfiles.settings."10-gancio" =
184       let
185         rules = {
186           mode = "0755";
187           user = cfg.user;
188           group = config.users.users.${cfg.user}.group;
189         };
190       in
191       {
192         "/var/lib/gancio/user_locale".d = rules;
193         "/var/lib/gancio/plugins".d = rules;
194       };
196     systemd.services.gancio =
197       let
198         configFile = settingsFormat.generate "gancio-config.json" cfg.settings;
199       in
200       {
201         description = "Gancio server";
202         documentation = [ "https://gancio.org/" ];
204         wantedBy = [ "multi-user.target" ];
205         after = [
206           "network.target"
207         ] ++ optional (cfg.settings.db.dialect == "postgres") "postgresql.service";
209         environment = {
210           NODE_ENV = "production";
211         };
213         preStart = ''
214           # We need this so the gancio executable run by the user finds the right settings.
215           ln -sf ${configFile} config.json
217           rm -f user_locale/*
218           ${concatStringsSep "\n" (
219             mapAttrsToList (
220               l: c: "ln -sf ${settingsFormat.generate "gancio-${l}-locale.json" c} user_locale/${l}.json"
221             ) cfg.userLocale
222           )}
224           rm -f plugins/*
225           ${concatMapStringsSep "\n" (p: "ln -sf ${p} plugins/") cfg.plugins}
226         '';
228         serviceConfig = {
229           ExecStart = "${getExe cfg.package} start ${configFile}";
230           # set umask so that nginx can write to the server socket
231           # FIXME: upstream socket permission configuration in Nuxt
232           UMask = "0002";
233           RuntimeDirectory = "gancio";
234           StateDirectory = "gancio";
235           WorkingDirectory = "/var/lib/gancio";
236           LogsDirectory = "gancio";
237           User = cfg.user;
238           # hardening
239           RestrictRealtime = true;
240           RestrictNamespaces = true;
241           LockPersonality = true;
242           ProtectKernelModules = true;
243           ProtectKernelTunables = true;
244           ProtectKernelLogs = true;
245           ProtectControlGroups = true;
246           ProtectClock = true;
247           RestrictSUIDSGID = true;
248           SystemCallArchitectures = "native";
249           CapabilityBoundingSet = "";
250           ProtectProc = "invisible";
251         };
252       };
254     services.postgresql = mkIf (cfg.settings.db.dialect == "postgres") {
255       enable = true;
256       ensureDatabases = [ cfg.user ];
257       ensureUsers = [
258         {
259           name = cfg.user;
260           ensureDBOwnership = true;
261         }
262       ];
263     };
265     services.nginx = {
266       enable = true;
267       virtualHosts."${cfg.settings.hostname}" = mkMerge [
268         cfg.nginx
269         {
270           locations = {
271             "/" = {
272               index = "index.html";
273               tryFiles = "$uri $uri @proxy";
274             };
275             "@proxy" = {
276               proxyWebsockets = true;
277               proxyPass = "http://unix:${cfg.settings.server.socket}";
278               recommendedProxySettings = true;
279             };
280           };
281         }
282       ];
283     };
284     # for nginx to access gancio socket
285     users.users."${config.services.nginx.user}".extraGroups = [ config.users.users.${cfg.user}.group ];
286   };