1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.uwsgi;
7
8 python2Pkgs = pkgs.python2Packages.override {
9 python = pkgs.uwsgi.python2;
10 self = python2Pkgs;
11 };
12
13 python3Pkgs = pkgs.python3Packages.override {
14 python = pkgs.uwsgi.python3;
15 self = python3Pkgs;
16 };
17
18 buildCfg = c: if builtins.typeOf c != "set" then builtins.readFile c else builtins.toJSON {
19 uwsgi =
20 if c.type == "normal"
21 then {
22 pythonpath =
23 (if c ? python2Packages
24 then builtins.map (x: "${x}/${pkgs.uwsgi.python2.sitePackages}") (c.python2Packages python2Pkgs)
25 else [])
26 ++ (if c ? python3Packages
27 then builtins.map (x: "${x}/${pkgs.uwsgi.python3.sitePackages}") (c.python3Packages python3Pkgs)
28 else []);
29 plugins = cfg.plugins;
30 } // removeAttrs c [ "type" "python2Packages" "python3Packages" ]
31 else if c.type == "emperor"
32 then {
33 emperor = if builtins.typeOf c.vassals != "set" then c.vassals
34 else pkgs.buildEnv {
35 name = "vassals";
36 paths = mapAttrsToList (n: c: pkgs.writeTextDir "${n}.json" (buildCfg c)) c.vassals;
37 };
38 } // removeAttrs c [ "type" "vassals" ]
39 else abort "type should be either 'normal' or 'emperor'";
40 };
41
42 uwsgi = pkgs.uwsgi.override {
43 plugins = cfg.plugins;
44 };
45
46in {
47
48 options = {
49 services.uwsgi = {
50
51 enable = mkOption {
52 type = types.bool;
53 default = false;
54 description = "Enable uWSGI";
55 };
56
57 runDir = mkOption {
58 type = types.string;
59 default = "/run/uwsgi";
60 description = "Where uWSGI communication sockets can live";
61 };
62
63 instance = mkOption {
64 type = types.attrs;
65 default = {
66 type = "normal";
67 };
68 example = literalExample ''
69 {
70 type = "emperor";
71 vassals = {
72 moin = {
73 type = "normal";
74 python2Packages = self: with self; [ moinmoin ];
75 socket = "${config.services.uwsgi.runDir}/uwsgi.sock";
76 };
77 };
78 }
79 '';
80 description = ''
81 uWSGI configuration. This awaits either a path to file or a set which will be made into one.
82 If given a set, it awaits an attribute <literal>type</literal> which can be either <literal>normal</literal>
83 or <literal>emperor</literal>.
84
85 For <literal>normal</literal> mode you can specify <literal>python2Packages</literal> and
86 <literal>python3Packages</literal> as functions from libraries set into lists of libraries.
87 For <literal>emperor</literal> mode, you should use <literal>vassals</literal> attribute
88 which should be either a set of names and configurations or a path to a directory.
89 '';
90 };
91
92 plugins = mkOption {
93 type = types.listOf types.str;
94 default = [];
95 description = "Plugins used with uWSGI";
96 };
97
98 user = mkOption {
99 type = types.str;
100 default = "uwsgi";
101 description = "User account under which uwsgi runs.";
102 };
103
104 group = mkOption {
105 type = types.str;
106 default = "uwsgi";
107 description = "Group account under which uwsgi runs.";
108 };
109 };
110 };
111
112 config = mkIf cfg.enable {
113 systemd.services.uwsgi = {
114 wantedBy = [ "multi-user.target" ];
115 preStart = ''
116 mkdir -p ${cfg.runDir}
117 chown ${cfg.user}:${cfg.group} ${cfg.runDir}
118 '';
119 serviceConfig = {
120 Type = "notify";
121 ExecStart = "${uwsgi}/bin/uwsgi --uid ${cfg.user} --gid ${cfg.group} --json ${pkgs.writeText "uwsgi.json" (buildCfg cfg.instance)}";
122 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
123 ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
124 NotifyAccess = "main";
125 KillSignal = "SIGQUIT";
126 };
127 };
128
129 users.extraUsers = optionalAttrs (cfg.user == "uwsgi") (singleton
130 { name = "uwsgi";
131 group = cfg.group;
132 uid = config.ids.uids.uwsgi;
133 });
134
135 users.extraGroups = optionalAttrs (cfg.group == "uwsgi") (singleton
136 { name = "uwsgi";
137 gid = config.ids.gids.uwsgi;
138 });
139 };
140}