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