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