grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / misc / etebase-server.nix
blobba45a1703ac275b7185ed936bdae72abe5dd62cc
1 { config, pkgs, lib, ... }:
2 let
3   cfg = config.services.etebase-server;
5   iniFmt = pkgs.formats.ini {};
7   configIni = iniFmt.generate "etebase-server.ini" cfg.settings;
9   defaultUser = "etebase-server";
12   imports = [
13     (lib.mkRemovedOptionModule
14       [ "services" "etebase-server" "customIni" ]
15       "Set the option `services.etebase-server.settings' instead.")
16     (lib.mkRemovedOptionModule
17       [ "services" "etebase-server" "database" ]
18       "Set the option `services.etebase-server.settings.database' instead.")
19     (lib.mkRenamedOptionModule
20       [ "services" "etebase-server" "secretFile" ]
21       [ "services" "etebase-server" "settings" "secret_file" ])
22     (lib.mkRenamedOptionModule
23       [ "services" "etebase-server" "host" ]
24       [ "services" "etebase-server" "settings" "allowed_hosts" "allowed_host1" ])
25   ];
27   options = {
28     services.etebase-server = {
29       enable = lib.mkOption {
30         type = lib.types.bool;
31         default = false;
32         example = true;
33         description = ''
34           Whether to enable the Etebase server.
36           Once enabled you need to create an admin user by invoking the
37           shell command `etebase-server createsuperuser` with
38           the user specified by the `user` option or a superuser.
39           Then you can login and create accounts on your-etebase-server.com/admin
40         '';
41       };
43       package = lib.mkOption {
44         type = lib.types.package;
45         default = pkgs.etebase-server;
46         defaultText = lib.literalExpression "pkgs.python3.pkgs.etebase-server";
47         description = "etebase-server package to use.";
48       };
50       dataDir = lib.mkOption {
51         type = lib.types.str;
52         default = "/var/lib/etebase-server";
53         description = "Directory to store the Etebase server data.";
54       };
56       port = lib.mkOption {
57         type = with lib.types; nullOr port;
58         default = 8001;
59         description = "Port to listen on.";
60       };
62       openFirewall = lib.mkOption {
63         type = lib.types.bool;
64         default = false;
65         description = ''
66           Whether to open ports in the firewall for the server.
67         '';
68       };
70       unixSocket = lib.mkOption {
71         type = with lib.types; nullOr str;
72         default = null;
73         description = "The path to the socket to bind to.";
74         example = "/run/etebase-server/etebase-server.sock";
75       };
77       settings = lib.mkOption {
78         type = lib.types.submodule {
79           freeformType = iniFmt.type;
81           options = {
82             global = {
83               debug = lib.mkOption {
84                 type = lib.types.bool;
85                 default = false;
86                 description = ''
87                   Whether to set django's DEBUG flag.
88                 '';
89               };
90               secret_file = lib.mkOption {
91                 type = with lib.types; nullOr str;
92                 default = null;
93                 description = ''
94                   The path to a file containing the secret
95                   used as django's SECRET_KEY.
96                 '';
97               };
98               static_root = lib.mkOption {
99                 type = lib.types.str;
100                 default = "${cfg.dataDir}/static";
101                 defaultText = lib.literalExpression ''"''${config.services.etebase-server.dataDir}/static"'';
102                 description = "The directory for static files.";
103               };
104               media_root = lib.mkOption {
105                 type = lib.types.str;
106                 default = "${cfg.dataDir}/media";
107                 defaultText = lib.literalExpression ''"''${config.services.etebase-server.dataDir}/media"'';
108                 description = "The media directory.";
109               };
110             };
111             allowed_hosts = {
112               allowed_host1 = lib.mkOption {
113                 type = lib.types.str;
114                 default = "0.0.0.0";
115                 example = "localhost";
116                 description = ''
117                   The main host that is allowed access.
118                 '';
119               };
120             };
121             database = {
122               engine = lib.mkOption {
123                 type = lib.types.enum [ "django.db.backends.sqlite3" "django.db.backends.postgresql" ];
124                 default = "django.db.backends.sqlite3";
125                 description = "The database engine to use.";
126               };
127               name = lib.mkOption {
128                 type = lib.types.str;
129                 default = "${cfg.dataDir}/db.sqlite3";
130                 defaultText = lib.literalExpression ''"''${config.services.etebase-server.dataDir}/db.sqlite3"'';
131                 description = "The database name.";
132               };
133             };
134           };
135         };
136         default = {};
137         description = ''
138           Configuration for `etebase-server`. Refer to
139           <https://github.com/etesync/server/blob/master/etebase-server.ini.example>
140           and <https://github.com/etesync/server/wiki>
141           for details on supported values.
142         '';
143         example = {
144           global = {
145             debug = true;
146             media_root = "/path/to/media";
147           };
148           allowed_hosts = {
149             allowed_host2 = "localhost";
150           };
151         };
152       };
154       user = lib.mkOption {
155         type = lib.types.str;
156         default = defaultUser;
157         description = "User under which Etebase server runs.";
158       };
159     };
160   };
162   config = lib.mkIf cfg.enable {
164     environment.systemPackages = with pkgs; [
165       (runCommand "etebase-server" {
166         nativeBuildInputs = [ makeWrapper ];
167       } ''
168         makeWrapper ${cfg.package}/bin/etebase-server \
169           $out/bin/etebase-server \
170           --chdir ${lib.escapeShellArg cfg.dataDir} \
171           --prefix ETEBASE_EASY_CONFIG_PATH : "${configIni}"
172       '')
173     ];
175     systemd.tmpfiles.rules = [
176       "d '${cfg.dataDir}' - ${cfg.user} ${config.users.users.${cfg.user}.group} - -"
177     ] ++ lib.optionals (cfg.unixSocket != null) [
178       "d '${builtins.dirOf cfg.unixSocket}' - ${cfg.user} ${config.users.users.${cfg.user}.group} - -"
179     ];
181     systemd.services.etebase-server = {
182       description = "An Etebase (EteSync 2.0) server";
183       after = [ "network.target" "systemd-tmpfiles-setup.service" ];
184       path = [ cfg.package ];
185       wantedBy = [ "multi-user.target" ];
186       serviceConfig = {
187         User = cfg.user;
188         Restart = "always";
189         WorkingDirectory = cfg.dataDir;
190       };
191       environment = {
192         ETEBASE_EASY_CONFIG_PATH = configIni;
193         PYTHONPATH = cfg.package.pythonPath;
194       };
195       preStart = ''
196         # Auto-migrate on first run or if the package has changed
197         versionFile="${cfg.dataDir}/src-version"
198         if [[ $(cat "$versionFile" 2>/dev/null) != ${cfg.package} ]]; then
199           etebase-server migrate --no-input
200           etebase-server collectstatic --no-input --clear
201           echo ${cfg.package} > "$versionFile"
202         fi
203       '';
204       script =
205         let
206           python = cfg.package.python;
207           networking = if cfg.unixSocket != null
208           then "--uds ${cfg.unixSocket}"
209           else "--host 0.0.0.0 --port ${toString cfg.port}";
210         in ''
211           ${python.pkgs.uvicorn}/bin/uvicorn ${networking} \
212             --app-dir ${cfg.package}/${cfg.package.python.sitePackages} \
213             etebase_server.asgi:application
214         '';
215     };
217     users = lib.optionalAttrs (cfg.user == defaultUser) {
218       users.${defaultUser} = {
219         isSystemUser = true;
220         group = defaultUser;
221         home = cfg.dataDir;
222       };
224       groups.${defaultUser} = {};
225     };
227     networking.firewall = lib.mkIf cfg.openFirewall {
228       allowedTCPPorts = [ cfg.port ];
229     };
230   };