1{ config, pkgs, lib, ... }:
2
3with lib;
4let
5 cfg = config.services.tandoor-recipes;
6 pkg = cfg.package;
7
8 # SECRET_KEY through an env file
9 env = {
10 GUNICORN_CMD_ARGS = "--bind=${cfg.address}:${toString cfg.port}";
11 DEBUG = "0";
12 MEDIA_ROOT = "/var/lib/tandoor-recipes";
13 } // optionalAttrs (config.time.timeZone != null) {
14 TIMEZONE = config.time.timeZone;
15 } // (
16 lib.mapAttrs (_: toString) cfg.extraConfig
17 );
18
19 manage =
20 let
21 setupEnv = lib.concatStringsSep "\n" (mapAttrsToList (name: val: "export ${name}=\"${val}\"") env);
22 in
23 pkgs.writeShellScript "manage" ''
24 ${setupEnv}
25 exec ${pkg}/bin/tandoor-recipes "$@"
26 '';
27in
28{
29 meta.maintainers = with maintainers; [ ambroisie ];
30
31 options.services.tandoor-recipes = {
32 enable = mkOption {
33 type = lib.types.bool;
34 default = false;
35 description = lib.mdDoc ''
36 Enable Tandoor Recipes.
37
38 When started, the Tandoor Recipes database is automatically created if
39 it doesn't exist and updated if the package has changed. Both tasks are
40 achieved by running a Django migration.
41
42 A script to manage the instance (by wrapping Django's manage.py) is linked to
43 `/var/lib/tandoor-recipes/tandoor-recipes-manage`.
44 '';
45 };
46
47 address = mkOption {
48 type = types.str;
49 default = "localhost";
50 description = lib.mdDoc "Web interface address.";
51 };
52
53 port = mkOption {
54 type = types.port;
55 default = 8080;
56 description = lib.mdDoc "Web interface port.";
57 };
58
59 extraConfig = mkOption {
60 type = types.attrs;
61 default = { };
62 description = lib.mdDoc ''
63 Extra tandoor recipes config options.
64
65 See [the example dot-env file](https://raw.githubusercontent.com/vabene1111/recipes/master/.env.template)
66 for available options.
67 '';
68 example = {
69 ENABLE_SIGNUP = "1";
70 };
71 };
72
73 package = mkOption {
74 type = types.package;
75 default = pkgs.tandoor-recipes;
76 defaultText = literalExpression "pkgs.tandoor-recipes";
77 description = lib.mdDoc "The Tandoor Recipes package to use.";
78 };
79 };
80
81 config = mkIf cfg.enable {
82 systemd.services.tandoor-recipes = {
83 description = "Tandoor Recipes server";
84
85 serviceConfig = {
86 ExecStart = ''
87 ${pkg.python.pkgs.gunicorn}/bin/gunicorn recipes.wsgi
88 '';
89 Restart = "on-failure";
90
91 User = "tandoor_recipes";
92 DynamicUser = true;
93 StateDirectory = "tandoor-recipes";
94 WorkingDirectory = "/var/lib/tandoor-recipes";
95 RuntimeDirectory = "tandoor-recipes";
96
97 BindReadOnlyPaths = [
98 "${config.environment.etc."ssl/certs/ca-certificates.crt".source}:/etc/ssl/certs/ca-certificates.crt"
99 builtins.storeDir
100 "-/etc/resolv.conf"
101 "-/etc/nsswitch.conf"
102 "-/etc/hosts"
103 "-/etc/localtime"
104 "-/run/postgresql"
105 ];
106 CapabilityBoundingSet = "";
107 LockPersonality = true;
108 MemoryDenyWriteExecute = true;
109 PrivateDevices = true;
110 PrivateUsers = true;
111 ProtectClock = true;
112 ProtectControlGroups = true;
113 ProtectHome = true;
114 ProtectHostname = true;
115 ProtectKernelLogs = true;
116 ProtectKernelModules = true;
117 ProtectKernelTunables = true;
118 RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
119 RestrictNamespaces = true;
120 RestrictRealtime = true;
121 SystemCallArchitectures = "native";
122 # gunicorn needs setuid
123 SystemCallFilter = [ "@system-service" "~@privileged" "@resources" "@setuid" "@keyring" ];
124 UMask = "0066";
125 } // lib.optionalAttrs (cfg.port < 1024) {
126 AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
127 CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
128 };
129
130 wantedBy = [ "multi-user.target" ];
131
132 preStart = ''
133 ln -sf ${manage} tandoor-recipes-manage
134
135 # Let django migrate the DB as needed
136 ${pkg}/bin/tandoor-recipes migrate
137 '';
138
139 environment = env // {
140 PYTHONPATH = "${pkg.python.pkgs.makePythonPath pkg.propagatedBuildInputs}:${pkg}/lib/tandoor-recipes";
141 };
142 };
143 };
144}