{ config, lib, pkgs, ... }: with import <stockholm/lib>; let gunicorn = pkgs.pythonPackages.gunicorn; bepasty = pkgs.pythonPackages.bepasty-server; gevent = pkgs.pythonPackages.gevent; python = pkgs.pythonPackages.python; cfg = config.krebs.bepasty; out = { options.krebs.bepasty = api; config = lib.mkIf cfg.enable (lib.mkMerge [ (lib.mkIf cfg.serveNginx nginx-imp) imp ]); }; api = { enable = mkEnableOption "Bepasty Servers"; serveNginx = mkEnableOption "Serve Bepasty Servers with Nginx"; servers = mkOption { type = with types; attrsOf optionSet; example = '' { "paste.r" = { defaultPermissions = "read,delete,create"; }; "paste.krebsco.de" = { defaultPermissions = "read"; }; } ''; options = singleton { nginx = mkOption { # TODO use the correct type type = with types; attrsOf unspecified; description = '' Additional nginx configuration. ''; }; secretKey = mkOption { type = types.str; description = '' server secret for safe session cookies, must be set. ''; default = ""; }; # we create a wsgi socket in $workDir/gunicorn-${name}.wsgi workDir = mkOption { type = types.str; description = '' Path to the working directory (used for sockets and pidfile). Defaults to the users home directory. Must be accessible to nginx, permissions will be set to 755 ''; default = config.users.extraUsers.bepasty.home; }; dataDir = mkOption { type = types.str; description = '' Defaults to the new users home dir which defaults to /var/lib/bepasty-server/data ''; default = "${config.users.extraUsers.bepasty.home}/data"; }; extraConfig = mkOption { type = types.str; default = ""; # TODO configure permissions in separate example = '' PERMISSIONS = { 'myadminsecret': 'admin,list,create,read,delete', } MAX_ALLOWED_FILE_SIZE = 5 * 1000 * 1000 ''; }; defaultPermissions = mkOption { # TODO: listOf str type = types.str; description = '' default permissions for all unauthenticated users. ''; example = "read,create,delete"; default = "read"; }; }; default = {}; }; }; imp = { # Configures systemd services for each configured server # environment.systemPackages = [ bepasty gunicorn gevent ]; systemd.services = mapAttrs' (name: server: nameValuePair "bepasty-server-${name}" { description = "Bepasty Server ${name}"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; restartIfChanged = true; environment = let penv = python.buildEnv.override { extraLibs = [ bepasty gevent ]; }; in { BEPASTY_CONFIG = "${server.workDir}/bepasty-${name}.conf"; PYTHONPATH= "${penv}/${python.sitePackages}/"; }; serviceConfig = { Type = "simple"; PrivateTmp = true; ExecStartPre = assert server.secretKey != ""; pkgs.writeDash "bepasty-server.${name}-init" '' mkdir -p "${server.dataDir}" "${server.workDir}" chown bepasty:bepasty "${server.workDir}" "${server.dataDir}" cat > "${server.workDir}/bepasty-${name}.conf" <<EOF SITENAME="${name}" STORAGE_FILESYSTEM_DIRECTORY="${server.dataDir}" SECRET_KEY="${server.secretKey}" DEFAULT_PERMISSIONS="${server.defaultPermissions}" ${server.extraConfig} EOF ''; ExecStart = ''${gunicorn}/bin/gunicorn bepasty.wsgi --name ${name} \ -u bepasty \ -g bepasty \ --workers 3 --log-level=info \ --bind=unix:${server.workDir}/gunicorn-${name}.sock \ --pid ${server.workDir}/gunicorn-${name}.pid \ -k gevent ''; }; } ) cfg.servers; users.extraUsers.bepasty = { uid = genid "bepasty"; group = "bepasty"; home = "/var/lib/bepasty-server"; }; users.extraGroups.bepasty = { gid = genid "bepasty"; }; }; nginx-imp = { assertions = [{ assertion = config.services.nginx.enable; message = "services.nginx.enable must be true"; }]; services.nginx.virtualHosts = mapAttrs ( name: server: (mkMerge [ server.nginx { extraConfig = '' client_max_body_size 32M; ''; locations = { "/".extraConfig = "proxy_set_header Host $http_host;"; "/".proxyPass = "http://unix:${server.workDir}/gunicorn-${name}.sock"; "/static/".extraConfig = '' alias ${bepasty}/lib/${python.libPrefix}/site-packages/bepasty/static/; ''; }; }]) ) cfg.servers ; }; in out