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