{ pkgs, lib, config, ... }: let mkManagePy = pkgs.callPackage ./utils/mkManagePy.nix { }; mkManageCommand = app: cfg: pkgs.writeShellScriptBin "manage-${app}" '' eval "$(${config.systemd.package}/bin/systemctl show -pMainPID django-${app}.service)" ${pkgs.util-linux}/bin/nsenter -e -a -t $MainPID -G follow -S follow ${lib.getExe cfg.managePy} "$@" ''; mkStaticAssets = { app, managePy, mainModule, }: pkgs.runCommand "django-${app}-static" { } '' mkdir -p "$out/static" STATIC_ROOT="\"$out/static\"" \ DJANGO_SETTINGS_MODULE="${mainModule}_settings.mock" \ ${lib.getExe managePy} collectstatic --noinput ''; mkSettingsModule = { runtimeSettings, settings, secrets, mainModule, extraConfig, p, }: p.callPackage ./utils/mkSettingsModule.nix { inherit runtimeSettings settings secrets mainModule extraConfig ; }; djangoAppModule = lib.types.submodule ( { config, name, ... }: { options = { enable = lib.mkEnableOption (lib.mdDoc "Enable django application") // { default = true; }; src = lib.mkOption { type = lib.types.path; }; sourceRoot = lib.mkOption { type = lib.types.str; default = ""; example = "myproject/"; description = "Directory in the source directory to find the django app (must point to basedir of manage.py)"; }; mainModule = lib.mkOption { type = lib.types.str; description = "Name of the django application"; default = name; defaultText = lib.literalMD "Defaults to attribute name in djangoAppModule."; }; settings = lib.mkOption { type = lib.types.submodule { freeformType = with lib.types; attrsOf anything; options = { }; }; description = '' Settings to pass to django. ''; example = { DATABASES = { "default" = { "ENGINE" = "django.db.backends.sqlite3"; "NAME" = "/var/lib/django-myproject/db.sqlite3"; }; }; }; }; runtimeSettings = lib.mkOption { type = with lib.types; attrsOf str; default = { }; description = '' Settings to pass to only at runtime. Useful for settings that depends on the python package. ''; }; secrets = lib.mkOption { type = lib.types.attrsOf lib.types.path; default = { }; example = { SECRET_KEY = "/etc/django-secret.json"; DATABASES = "/etc/django-db-config.json"; }; description = '' Secrets to pass to django through a file. The content of the file will be loaded as json ''; }; extraConfig = lib.mkOption { type = lib.types.lines; default = ""; description = "Extra python code to append at the end of the production settings module."; }; port = lib.mkOption { type = lib.types.port; default = 51666; description = "Port for the gunicorn process"; }; processes = lib.mkOption { type = lib.types.int; default = 2; }; threads = lib.mkOption { type = lib.types.int; default = 2; }; staticAssets = lib.mkOption { type = lib.types.path; default = mkStaticAssets { inherit (config) mainModule managePy; app = name; }; description = "Satic assets to be served directly by nginx. The default value should be good enough in most cases."; }; manageFilePath = lib.mkOption { type = lib.types.str; default = "${config.sourceRoot}manage.py"; description = "Path relative to src pointing to manage.py file"; }; pythonPackage = lib.mkOption { internal = true; type = lib.types.path; description = "Final python environment containing everything including the settings module"; default = pkgs.python3.withPackages ( p: [ p.django p.gunicorn (mkSettingsModule { inherit (config) secrets settings mainModule extraConfig ; runtimeSettings = builtins.attrNames config.runtimeSettings; inherit p; }) ] ++ config.extraPackages p ); }; extraPackages = lib.mkOption { type = lib.types.functionTo (lib.types.listOf lib.types.package); default = p: [ ]; defaultText = lib.literalExpression "p: []"; description = '' Extra Python packages available to django app. The value must be a function which receives the attrset defined in {var}`python3Packages` as the sole argument. ''; }; managePy = lib.mkOption { internal = true; type = lib.types.package; default = mkManagePy { inherit (config) pythonPackage manageFilePath src; app = name; }; description = "Manage py script"; }; }; config = { runtimeSettings.STATIC_ROOT = ""; # config.staticAssets; }; } ); in { options = { services.django = lib.mkOption { type = lib.types.attrsOf djangoAppModule; description = "Attribute set of djanfo app modules"; }; }; config = { systemd.services = lib.mapAttrs' ( app: cfg: lib.nameValuePair "django-${app}" ( lib.mkIf cfg.enable { description = "${app} django service"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; wants = [ "network.target" ]; serviceConfig = rec { Type = "notify"; #NotifyAllow = "exec"; DynamicUser = true; LoadCredential = lib.mapAttrsToList (k: v: "${k}:${v}") cfg.secrets; StateDirectory = "django-${app}"; }; environment = { DJANGO_SETTINGS_MODULE = "${cfg.mainModule}_settings.prod"; } // (lib.mapAttrs (_: v: builtins.toJSON v) cfg.runtimeSettings); script = '' ${lib.getExe cfg.managePy} migrate exec ${cfg.pythonPackage}/bin/gunicorn ${cfg.mainModule}.wsgi \ --pythonpath ${cfg.src}/${cfg.sourceRoot} \ -b 127.0.0.1:${builtins.toString cfg.port} \ --workers=${builtins.toString cfg.processes} \ --threads=${builtins.toString cfg.threads} ''; } ) ) config.services.django; environment.systemPackages = lib.mapAttrsToList (k: v: mkManageCommand k v) config.services.django; }; }