1{
2 config,
3 pkgs,
4 lib,
5 ...
6}:
7let
8 cfg = config.services.tandoor-recipes;
9 pkg = cfg.package;
10
11 # SECRET_KEY through an env file
12 env = {
13 GUNICORN_CMD_ARGS = "--bind=${cfg.address}:${toString cfg.port}";
14 DEBUG = "0";
15 DEBUG_TOOLBAR = "0";
16 MEDIA_ROOT = "/var/lib/tandoor-recipes";
17 }
18 // lib.optionalAttrs (config.time.timeZone != null) {
19 TZ = config.time.timeZone;
20 }
21 // (lib.mapAttrs (_: toString) cfg.extraConfig);
22
23 manage = pkgs.writeShellScript "manage" ''
24 set -o allexport # Export the following env vars
25 ${lib.toShellVars env}
26 # UID is a read-only shell variable
27 eval "$(${config.systemd.package}/bin/systemctl show -pUID,GID,MainPID tandoor-recipes.service | tr '[:upper:]' '[:lower:]')"
28 exec ${pkgs.util-linux}/bin/nsenter \
29 -t $mainpid -m -S $uid -G $gid --wdns=${env.MEDIA_ROOT} \
30 ${pkg}/bin/tandoor-recipes "$@"
31 '';
32in
33{
34 meta.maintainers = with lib.maintainers; [ jvanbruegge ];
35
36 options.services.tandoor-recipes = {
37 enable = lib.mkOption {
38 type = lib.types.bool;
39 default = false;
40 description = ''
41 Enable Tandoor Recipes.
42
43 When started, the Tandoor Recipes database is automatically created if
44 it doesn't exist and updated if the package has changed. Both tasks are
45 achieved by running a Django migration.
46
47 A script to manage the instance (by wrapping Django's manage.py) is linked to
48 `/var/lib/tandoor-recipes/tandoor-recipes-manage`.
49 '';
50 };
51
52 address = lib.mkOption {
53 type = lib.types.str;
54 default = "localhost";
55 description = "Web interface address.";
56 };
57
58 port = lib.mkOption {
59 type = lib.types.port;
60 default = 8080;
61 description = "Web interface port.";
62 };
63
64 extraConfig = lib.mkOption {
65 type = lib.types.attrs;
66 default = { };
67 description = ''
68 Extra tandoor recipes config options.
69
70 See [the example dot-env file](https://raw.githubusercontent.com/vabene1111/recipes/master/.env.template)
71 for available options.
72 '';
73 example = {
74 ENABLE_SIGNUP = "1";
75 };
76 };
77
78 user = lib.mkOption {
79 type = lib.types.str;
80 default = "tandoor_recipes";
81 description = "User account under which Tandoor runs.";
82 };
83
84 group = lib.mkOption {
85 type = lib.types.str;
86 default = "tandoor_recipes";
87 description = "Group under which Tandoor runs.";
88 };
89
90 package = lib.mkPackageOption pkgs "tandoor-recipes" { };
91
92 database = {
93 createLocally = lib.mkOption {
94 type = lib.types.bool;
95 default = false;
96 description = ''
97 Configure local PostgreSQL database server for Tandoor Recipes.
98 '';
99 };
100 };
101 };
102
103 config = lib.mkIf cfg.enable {
104 users.users = lib.mkIf (cfg.user == "tandoor_recipes") {
105 tandoor_recipes = {
106 inherit (cfg) group;
107 isSystemUser = true;
108 };
109 };
110
111 users.groups = lib.mkIf (cfg.group == "tandoor_recipes") {
112 tandoor_recipes = { };
113 };
114
115 systemd.services.tandoor-recipes = {
116 description = "Tandoor Recipes server";
117
118 requires = lib.optional cfg.database.createLocally "postgresql.target";
119 after = lib.optional cfg.database.createLocally "postgresql.target";
120
121 serviceConfig = {
122 ExecStart = ''
123 ${pkg.python.pkgs.gunicorn}/bin/gunicorn recipes.wsgi
124 '';
125 Restart = "on-failure";
126
127 User = cfg.user;
128 Group = cfg.group;
129 StateDirectory = "tandoor-recipes";
130 WorkingDirectory = env.MEDIA_ROOT;
131 RuntimeDirectory = "tandoor-recipes";
132
133 BindReadOnlyPaths = [
134 "${config.security.pki.caBundle}:/etc/ssl/certs/ca-certificates.crt"
135 builtins.storeDir
136 "-/etc/resolv.conf"
137 "-/etc/nsswitch.conf"
138 "-/etc/hosts"
139 "-/etc/localtime"
140 "-/run/postgresql"
141 ];
142 CapabilityBoundingSet = "";
143 LockPersonality = true;
144 MemoryDenyWriteExecute = true;
145 PrivateDevices = true;
146 PrivateUsers = true;
147 ProtectClock = true;
148 ProtectControlGroups = true;
149 ProtectHome = true;
150 ProtectHostname = true;
151 ProtectKernelLogs = true;
152 ProtectKernelModules = true;
153 ProtectKernelTunables = true;
154 RestrictAddressFamilies = [
155 "AF_UNIX"
156 "AF_INET"
157 "AF_INET6"
158 ];
159 RestrictNamespaces = true;
160 RestrictRealtime = true;
161 SystemCallArchitectures = "native";
162 # gunicorn needs setuid
163 SystemCallFilter = [
164 "@system-service"
165 "~@privileged"
166 "@resources"
167 "@setuid"
168 "@keyring"
169 ];
170 UMask = "0066";
171 };
172
173 wantedBy = [ "multi-user.target" ];
174
175 preStart = ''
176 ln -sf ${manage} tandoor-recipes-manage
177
178 # Let django migrate the DB as needed
179 ${pkg}/bin/tandoor-recipes migrate
180 '';
181
182 environment = env // {
183 PYTHONPATH = "${pkg.python.pkgs.makePythonPath pkg.propagatedBuildInputs}:${pkg}/lib/tandoor-recipes";
184 };
185 };
186
187 services.tandoor-recipes.extraConfig = lib.mkIf cfg.database.createLocally {
188 DB_ENGINE = "django.db.backends.postgresql";
189 POSTGRES_HOST = "/run/postgresql";
190 POSTGRES_USER = "tandoor_recipes";
191 POSTGRES_DB = "tandoor_recipes";
192 };
193
194 services.postgresql = lib.mkIf cfg.database.createLocally {
195 enable = true;
196 ensureDatabases = [ "tandoor_recipes" ];
197 ensureUsers = [
198 {
199 name = "tandoor_recipes";
200 ensureDBOwnership = true;
201 }
202 ];
203 };
204 };
205}