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