1
0
Fork 0
Fediversity/panel/nix/configuration.nix

214 lines
5.7 KiB
Nix

{
config,
pkgs,
lib,
...
}:
let
inherit (lib)
concatStringsSep
mapAttrsToList
mkDefault
mkEnableOption
mkIf
mkOption
mkPackageOption
optionalString
types
;
inherit (pkgs) writeShellApplication;
# TODO: configure the name globally for everywhere it's used
name = "panel";
cfg = config.services.${name};
database-url = "sqlite:////var/lib/${name}/db.sqlite3";
python-environment = pkgs.python3.withPackages (
ps:
with ps;
[
uvicorn
cfg.package
dj-database-url
django-compressor
django-debug-toolbar
django-libsass
django_4
setuptools
]
++ cfg.package.propagatedBuildInputs
);
configFile = pkgs.concatText "configuration.py" [
((pkgs.formats.pythonVars { }).generate "settings.py" cfg.settings)
(builtins.toFile "extra-settings.py" cfg.extra-settings)
];
manage-service = writeShellApplication {
name = "manage";
text = ''exec ${cfg.package}/bin/manage.py "$@"'';
};
manage-admin = writeShellApplication {
# This allows running the `manage` command in the system environment, e.g. to initialise an admin user
# Executing
name = "manage";
text =
''
systemd-run --pty \
--same-dir \
--wait \
--collect \
--service-type=exec \
--unit "manage-${name}.service" \
--property "User=${name}" \
--property "Group=${name}" \
--property "Environment=DATABASE_URL=${database-url} USER_SETTINGS_FILE=${configFile}" \
''
+ optionalString (credentials != [ ]) (
(concatStringsSep " \\\n" (map (cred: "--property 'LoadCredential=${cred}'") credentials)) + " \\\n"
)
+ ''
${lib.getExe manage-service} "$@"
'';
};
credentials = mapAttrsToList (name: secretPath: "${name}:${secretPath}") cfg.secrets;
in
# TODO: for a more clever and generic way of running Django services:
# https://git.dgnum.eu/mdebray/djangonix/
# unlicensed at the time of writing, but surely worth taking some inspiration from...
{
options.services.${name} = {
enable = mkEnableOption "Service configuration for `${name}`";
# NOTE: this requires that the package is present in `pkgs`
package = mkPackageOption pkgs name { };
production = mkOption {
type = types.bool;
default = true;
};
restart = mkOption {
description = "systemd restart behavior";
type = types.enum [
"no"
"on-success"
"on-failure"
"on-abnormal"
"on-abort"
"always"
];
default = "always";
};
domain = mkOption { type = types.str; };
host = mkOption {
type = types.str;
default = "127.0.0.1";
};
port = mkOption {
type = types.port;
default = 8000;
};
settings = mkOption {
type = types.attrsOf types.anything;
default = {
STATIC_ROOT = mkDefault "/var/lib/${name}/static";
DEBUG = mkDefault false;
ALLOWED_HOSTS = mkDefault [
cfg.domain
cfg.host
"localhost"
"[::1]"
];
CSRF_TRUSTED_ORIGINS = mkDefault [ "https://${cfg.domain}" ];
COMPRESS_OFFLINE = true;
LIBSASS_OUTPUT_STYLE = "compressed";
};
description = ''
Django configuration as an attribute set.
Name-value pairs will be converted to Python variable assignments.
'';
};
extra-settings = mkOption {
type = types.lines;
default = "";
description = ''
Django configuration written in Python verbatim.
Contents will be appended to the definitions in `settings`.
'';
};
secrets = mkOption {
type = types.attrsOf types.path;
default = { };
};
};
config = mkIf cfg.enable {
environment.systemPackages = [ manage-admin ];
services = {
nginx.enable = true;
nginx.virtualHosts = {
${cfg.domain} =
{
locations = {
"/".proxyPass = "http://localhost:${toString cfg.port}";
"/static/".alias = "/var/lib/${name}/static/";
};
}
// lib.optionalAttrs cfg.production {
enableACME = true;
forceSSL = true;
};
};
};
users.users.${name} = {
isSystemUser = true;
group = name;
};
users.groups.${name} = { };
systemd.services.${name} = {
description = "${name} ASGI server";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
path = [
python-environment
manage-service
];
preStart = ''
# Auto-migrate on first run or if the package has changed
versionFile="/var/lib/${name}/package-version"
if [[ $(cat "$versionFile" 2>/dev/null) != ${cfg.package} ]]; then
manage migrate --no-input
manage collectstatic --no-input --clear
manage compress --force
echo ${cfg.package} > "$versionFile"
fi
'';
script = ''
export PYTHONPATH=$PYTHONPATH:${cfg.package}/lib/python3.12/site-packages
${python-environment}/bin/python -m uvicorn ${name}.asgi:application --host ${cfg.host} --port ${toString cfg.port}
'';
serviceConfig = {
Restart = "always";
User = name;
WorkingDirectory = "/var/lib/${name}";
StateDirectory = name;
RuntimeDirectory = name;
LogsDirectory = name;
} // lib.optionalAttrs (credentials != [ ]) { LoadCredential = credentials; };
environment = {
USER_SETTINGS_FILE = "${configFile}";
DATABASE_URL = database-url;
};
};
networking.firewall.allowedTCPPorts = [
80
443
];
};
}