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