djangonix/module.nix

226 lines
7 KiB
Nix

{
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;
};
}