forked from Fediversity/Fediversity
200 lines
5.3 KiB
Nix
200 lines
5.3 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; [
|
||
|
cfg.package
|
||
|
uvicorn
|
||
|
]
|
||
|
);
|
||
|
|
||
|
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 = ''
|
||
|
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;
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
}
|