forked from Fediversity/Fediversity
Reviewed-on: Fediversity/Fediversity#269 Reviewed-by: Valentin Gagarin <valentin.gagarin@tweag.io> Co-authored-by: Kiara Grouwstra <kiara@procolix.eu> Co-committed-by: Kiara Grouwstra <kiara@procolix.eu>
221 lines
6.7 KiB
Nix
221 lines
6.7 KiB
Nix
{
|
|
config,
|
|
pkgs,
|
|
lib,
|
|
...
|
|
}:
|
|
let
|
|
inherit (lib)
|
|
concatStringsSep
|
|
mapAttrsToList
|
|
mkDefault
|
|
mkEnableOption
|
|
mkIf
|
|
mkOption
|
|
optionalString
|
|
types
|
|
;
|
|
inherit (pkgs) writeShellApplication;
|
|
|
|
# TODO: configure the name globally for everywhere it's used
|
|
name = "panel";
|
|
|
|
cfg = config.services.${name};
|
|
package = pkgs.callPackage ./package.nix { };
|
|
|
|
environment = import ../env.nix { inherit lib pkgs; } // {
|
|
DATABASE_URL = "sqlite:////var/lib/${name}/db.sqlite3";
|
|
USER_SETTINGS_FILE = pkgs.concatText "configuration.py" [
|
|
((pkgs.formats.pythonVars { }).generate "settings.py" cfg.settings)
|
|
(builtins.toFile "extra-settings.py" cfg.extra-settings)
|
|
];
|
|
};
|
|
|
|
python-environment = pkgs.python3.withPackages (
|
|
ps: with ps; [
|
|
package
|
|
uvicorn
|
|
]
|
|
);
|
|
|
|
manage-service = writeShellApplication {
|
|
name = "manage";
|
|
text = ''exec ${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 \
|
|
--wait \
|
|
--collect \
|
|
--service-type=exec \
|
|
--unit "manage-${name}.service" \
|
|
--property "User=${name}" \
|
|
--property "Group=${name}" \
|
|
--property "WorkingDirectory=/var/lib/${name}" \
|
|
--property "Environment=''
|
|
+ (toString (lib.mapAttrsToList (name: value: "${name}=${value}") environment))
|
|
+ "\" \\\n"
|
|
+ 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}`";
|
|
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 {
|
|
nixpkgs.overlays = [ (import ./overlay.nix) ];
|
|
|
|
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) != ${package} ]]; then
|
|
manage migrate --no-input
|
|
manage collectstatic --no-input --clear
|
|
manage compress --force
|
|
echo ${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; };
|
|
|
|
# TODO(@fricklerhandwerk):
|
|
# Unify handling of runtime settings.
|
|
# Right now we have four(!) places where we need to set environment variables, each in its own format:
|
|
# - Django's `settings.py` declaring the setting
|
|
# - the development environment
|
|
# - the `manage` command
|
|
# - here, the service configuration
|
|
# Ideally we'd set them in two places (development environment and service configuration) but in the same format.
|
|
#
|
|
# For that we need to take into account
|
|
# - the different types of settings
|
|
# - secrets, which must not end up in the store
|
|
# - other values, which can be world-readable
|
|
# - ergonomics
|
|
# - manipulation should be straightforward in both places; e.g. dumping secrets to a directory that is not git-tracked and adding values to an attrset otherwise
|
|
# - error detection and correction; it should be clear where and why one messed up so it can be fixed immediately
|
|
# We may also want to test the development environment in CI in order to make sure that we don't break it inadvertently, because misconfiguration due to multiplpe sources of truth wastes a lot of time.
|
|
inherit environment;
|
|
};
|
|
|
|
networking.firewall.allowedTCPPorts = [
|
|
80
|
|
443
|
|
];
|
|
};
|
|
}
|