1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.uwsgi;
7
8 uwsgi = pkgs.uwsgi.override {
9 plugins = cfg.plugins;
10 };
11
12 buildCfg = name: c:
13 let
14 plugins =
15 if any (n: !any (m: m == n) cfg.plugins) (c.plugins or [])
16 then throw "`plugins` attribute in UWSGI configuration contains plugins not in config.services.uwsgi.plugins"
17 else c.plugins or cfg.plugins;
18
19 hasPython = v: filter (n: n == "python${v}") plugins != [];
20 hasPython2 = hasPython "2";
21 hasPython3 = hasPython "3";
22
23 python =
24 if hasPython2 && hasPython3 then
25 throw "`plugins` attribute in UWSGI configuration shouldn't contain both python2 and python3"
26 else if hasPython2 then uwsgi.python2
27 else if hasPython3 then uwsgi.python3
28 else null;
29
30 pythonEnv = python.withPackages (c.pythonPackages or (self: []));
31
32 uwsgiCfg = {
33 uwsgi =
34 if c.type == "normal"
35 then {
36 inherit plugins;
37 } // removeAttrs c [ "type" "pythonPackages" ]
38 // optionalAttrs (python != null) {
39 pythonpath = "${pythonEnv}/${python.sitePackages}";
40 env =
41 # Argh, uwsgi expects list of key-values there instead of a dictionary.
42 let env' = c.env or [];
43 getPath =
44 x: if hasPrefix "PATH=" x
45 then substring (stringLength "PATH=") (stringLength x) x
46 else null;
47 oldPaths = filter (x: x != null) (map getPath env');
48 in env' ++ [ "PATH=${optionalString (oldPaths != []) "${last oldPaths}:"}${pythonEnv}/bin" ];
49 }
50 else if c.type == "emperor"
51 then {
52 emperor = if builtins.typeOf c.vassals != "set" then c.vassals
53 else pkgs.buildEnv {
54 name = "vassals";
55 paths = mapAttrsToList buildCfg c.vassals;
56 };
57 } // removeAttrs c [ "type" "vassals" ]
58 else throw "`type` attribute in UWSGI configuration should be either 'normal' or 'emperor'";
59 };
60
61 in pkgs.writeTextDir "${name}.json" (builtins.toJSON uwsgiCfg);
62
63in {
64
65 options = {
66 services.uwsgi = {
67
68 enable = mkOption {
69 type = types.bool;
70 default = false;
71 description = "Enable uWSGI";
72 };
73
74 runDir = mkOption {
75 type = types.string;
76 default = "/run/uwsgi";
77 description = "Where uWSGI communication sockets can live";
78 };
79
80 instance = mkOption {
81 type = types.attrs;
82 default = {
83 type = "normal";
84 };
85 example = literalExample ''
86 {
87 type = "emperor";
88 vassals = {
89 moin = {
90 type = "normal";
91 pythonPackages = self: with self; [ moinmoin ];
92 socket = "${config.services.uwsgi.runDir}/uwsgi.sock";
93 };
94 };
95 }
96 '';
97 description = ''
98 uWSGI configuration. It awaits an attribute <literal>type</literal> inside which can be either
99 <literal>normal</literal> or <literal>emperor</literal>.
100
101 For <literal>normal</literal> mode you can specify <literal>pythonPackages</literal> as a function
102 from libraries set into a list of libraries. <literal>pythonpath</literal> will be set accordingly.
103
104 For <literal>emperor</literal> mode, you should use <literal>vassals</literal> attribute
105 which should be either a set of names and configurations or a path to a directory.
106
107 Other attributes will be used in configuration file as-is. Notice that you can redefine
108 <literal>plugins</literal> setting here.
109 '';
110 };
111
112 plugins = mkOption {
113 type = types.listOf types.str;
114 default = [];
115 description = "Plugins used with uWSGI";
116 };
117
118 user = mkOption {
119 type = types.str;
120 default = "uwsgi";
121 description = "User account under which uwsgi runs.";
122 };
123
124 group = mkOption {
125 type = types.str;
126 default = "uwsgi";
127 description = "Group account under which uwsgi runs.";
128 };
129 };
130 };
131
132 config = mkIf cfg.enable {
133 systemd.services.uwsgi = {
134 wantedBy = [ "multi-user.target" ];
135 preStart = ''
136 mkdir -p ${cfg.runDir}
137 chown ${cfg.user}:${cfg.group} ${cfg.runDir}
138 '';
139 serviceConfig = {
140 Type = "notify";
141 ExecStart = "${uwsgi}/bin/uwsgi --uid ${cfg.user} --gid ${cfg.group} --json ${buildCfg "server" cfg.instance}/server.json";
142 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
143 ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
144 NotifyAccess = "main";
145 KillSignal = "SIGQUIT";
146 };
147 };
148
149 users.users = optionalAttrs (cfg.user == "uwsgi") (singleton
150 { name = "uwsgi";
151 group = cfg.group;
152 uid = config.ids.uids.uwsgi;
153 });
154
155 users.groups = optionalAttrs (cfg.group == "uwsgi") (singleton
156 { name = "uwsgi";
157 gid = config.ids.gids.uwsgi;
158 });
159 };
160}