Fediversity/panel/nix/configuration.nix
Kiara Grouwstra dd5a6335b1
proxmox
pass in description

fix syntax

configure proxmox provider

typo

add doc comment in existing modules

add comment

allow insecure proxmox connection for use in dev

wip proxmox progress

use service configurations moved to machine-independent location

wire settings directly without option block terraform

adjust cwd

try tf on null input

update .envrc.sample with sample proxmox credentials
2025-05-11 19:22:49 +02:00

222 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)
];
REPO_DIR = import ../../infra/tf-env.nix {
inherit lib pkgs;
};
};
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}.isNormalUser = true;
users.groups.${name} = { };
systemd.services.${name} = {
description = "${name} ASGI server";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
path = [
pkgs.openssh
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
];
};
}