1
0
Fork 0

fix: NixOS deployment code

- simplify the configuration module

  the `package` attribute makes little sense to be user-configurable,
  since it will always need to be the derivation defined in this very
  repository. for debugging one may as well change the original code itself.

- unbreak deployment

  setting `CREDENTIALS_DIRECTORY` disabled the systemd mechanism set up
  in the configuration module.

- remove unneeded configuration for deployment

- unbreak integration tests

  before that missed waiting for the service to create some
  state before running the application-level tests.
This commit is contained in:
Valentin Gagarin 2025-03-18 18:18:49 +01:00
parent 8f0bcc35f0
commit 3364d6c972
5 changed files with 19 additions and 41 deletions

View file

@ -4,15 +4,10 @@
}: }:
let let
name = "panel"; name = "panel";
panel = (import ../../../panel/default.nix { }).package;
in in
{ {
imports = [ imports = [
../../../panel/nix/configuration.nix (import ../../../panel { }).module
];
environment.systemPackages = [
panel
]; ];
security.acme = { security.acme = {
@ -22,18 +17,11 @@ in
services.${name} = { services.${name} = {
enable = true; enable = true;
package = panel;
production = true; production = true;
domain = "demo.fediversity.eu"; domain = "demo.fediversity.eu";
host = "0.0.0.0";
secrets = { secrets = {
SECRET_KEY = config.age.secrets.panel-secret-key.path; SECRET_KEY = config.age.secrets.panel-secret-key.path;
}; };
port = 8000; port = 8000;
settings = {
DATABASE_URL = "sqlite:///var/lib/${name}/db.sqlite3";
CREDENTIALS_DIRECTORY = "/var/lib/${name}/.credentials";
STATIC_ROOT = "/var/lib/${name}/static";
};
}; };
} }

View file

@ -8,17 +8,13 @@
}, },
}: }:
let let
package = pkgs.callPackage ./nix/package.nix { };
pkgs' = pkgs.extend (_final: _prev: { panel = package; });
manage = pkgs.writeScriptBin "manage" '' manage = pkgs.writeScriptBin "manage" ''
exec ${pkgs.lib.getExe pkgs.python3} ${toString ./src/manage.py} $@ exec ${pkgs.lib.getExe pkgs.python3} ${toString ./src/manage.py} $@
''; '';
in in
{ {
shell = pkgs.mkShellNoCC { shell = pkgs.mkShellNoCC {
inputsFrom = [ package ]; inputsFrom = [ (pkgs.callPackage ./nix/package.nix { }) ];
packages = [ packages = [
pkgs.npins pkgs.npins
manage manage
@ -36,8 +32,8 @@ in
''; '';
}; };
tests = pkgs'.callPackage ./nix/tests.nix { }; module = import ./nix/configuration.nix;
inherit package; tests = pkgs.callPackage ./nix/tests.nix { };
# re-export inputs so they can be overridden granularly # re-export inputs so they can be overridden granularly
# (they can't be accessed from the outside any other way) # (they can't be accessed from the outside any other way)

View file

@ -12,7 +12,6 @@ let
mkEnableOption mkEnableOption
mkIf mkIf
mkOption mkOption
mkPackageOption
optionalString optionalString
types types
; ;
@ -22,23 +21,15 @@ let
name = "panel"; name = "panel";
cfg = config.services.${name}; cfg = config.services.${name};
package = pkgs.callPackage ./package.nix { };
database-url = "sqlite:////var/lib/${name}/db.sqlite3"; database-url = "sqlite:////var/lib/${name}/db.sqlite3";
python-environment = pkgs.python3.withPackages ( python-environment = pkgs.python3.withPackages (
ps: ps: with ps; [
with ps; package
[
uvicorn uvicorn
cfg.package
dj-database-url
django-compressor
django-debug-toolbar
django-libsass
django_4
setuptools
] ]
++ cfg.package.propagatedBuildInputs
); );
configFile = pkgs.concatText "configuration.py" [ configFile = pkgs.concatText "configuration.py" [
@ -48,7 +39,7 @@ let
manage-service = writeShellApplication { manage-service = writeShellApplication {
name = "manage"; name = "manage";
text = ''exec ${cfg.package}/bin/manage.py "$@"''; text = ''exec ${package}/bin/manage.py "$@"'';
}; };
manage-admin = writeShellApplication { manage-admin = writeShellApplication {
@ -83,8 +74,6 @@ in
{ {
options.services.${name} = { options.services.${name} = {
enable = mkEnableOption "Service configuration for `${name}`"; enable = mkEnableOption "Service configuration for `${name}`";
# NOTE: this requires that the package is present in `pkgs`
package = mkPackageOption pkgs name { };
production = mkOption { production = mkOption {
type = types.bool; type = types.bool;
default = true; default = true;
@ -145,6 +134,8 @@ in
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable {
nixpkgs.overlays = [ (import ./overlay.nix) ];
environment.systemPackages = [ manage-admin ]; environment.systemPackages = [ manage-admin ];
services = { services = {
@ -181,16 +172,15 @@ in
preStart = '' preStart = ''
# Auto-migrate on first run or if the package has changed # Auto-migrate on first run or if the package has changed
versionFile="/var/lib/${name}/package-version" versionFile="/var/lib/${name}/package-version"
if [[ $(cat "$versionFile" 2>/dev/null) != ${cfg.package} ]]; then if [[ $(cat "$versionFile" 2>/dev/null) != ${package} ]]; then
manage migrate --no-input manage migrate --no-input
manage collectstatic --no-input --clear manage collectstatic --no-input --clear
manage compress --force manage compress --force
echo ${cfg.package} > "$versionFile" echo ${package} > "$versionFile"
fi fi
''; '';
script = '' script = ''
export PYTHONPATH=$PYTHONPATH:${cfg.package}/lib/python3.12/site-packages uvicorn ${name}.asgi:application --host ${cfg.host} --port ${toString cfg.port}
${python-environment}/bin/python -m uvicorn ${name}.asgi:application --host ${cfg.host} --port ${toString cfg.port}
''; '';
serviceConfig = { serviceConfig = {
Restart = "always"; Restart = "always";

View file

@ -3,6 +3,8 @@ let
# TODO: specify project/service name globally # TODO: specify project/service name globally
name = "panel"; name = "panel";
defaults = { defaults = {
# XXX: we have to duplicate this here despite it being defined in the service module, otherwise the test framework will error out
nixpkgs.overlays = lib.mkForce [ (import ./overlay.nix) ];
services.${name} = { services.${name} = {
enable = true; enable = true;
production = false; production = false;
@ -26,6 +28,7 @@ lib.mapAttrs (name: test: pkgs.testers.runNixOSTest (test // { inherit name; }))
# run all application-level tests managed by Django # run all application-level tests managed by Django
# https://docs.djangoproject.com/en/5.0/topics/testing/overview/ # https://docs.djangoproject.com/en/5.0/topics/testing/overview/
testScript = '' testScript = ''
server.wait_for_unit("${name}.service")
server.succeed("manage test ${name}") server.succeed("manage test ${name}")
''; '';
}; };
@ -34,7 +37,6 @@ lib.mapAttrs (name: test: pkgs.testers.runNixOSTest (test // { inherit name; }))
nodes.server = _: { imports = [ ./configuration.nix ]; }; nodes.server = _: { imports = [ ./configuration.nix ]; };
# check that the admin interface is served # check that the admin interface is served
testScript = '' testScript = ''
server.wait_for_unit("multi-user.target")
server.wait_for_unit("${name}.service") server.wait_for_unit("${name}.service")
server.wait_for_open_port(8000) server.wait_for_open_port(8000)
server.succeed("curl --fail -L -H 'Host: example.org' http://localhost/admin") server.succeed("curl --fail -L -H 'Host: example.org' http://localhost/admin")
@ -45,11 +47,11 @@ lib.mapAttrs (name: test: pkgs.testers.runNixOSTest (test // { inherit name; }))
inherit defaults; inherit defaults;
nodes.server = _: { imports = [ ./configuration.nix ]; }; nodes.server = _: { imports = [ ./configuration.nix ]; };
extraPythonPackages = ps: with ps; [ beautifulsoup4 ]; extraPythonPackages = ps: with ps; [ beautifulsoup4 ];
# type checking on `beautifulsoup4` will error out
skipTypeCheck = true; skipTypeCheck = true;
# check that stylesheets are pre-processed and served # check that stylesheets are pre-processed and served
testScript = '' testScript = ''
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
server.wait_for_unit("multi-user.target")
server.wait_for_unit("${name}.service") server.wait_for_unit("${name}.service")
server.wait_for_open_port(8000) server.wait_for_open_port(8000)
stdout = server.succeed("curl --fail -H 'Host: example.org' http://localhost") stdout = server.succeed("curl --fail -H 'Host: example.org' http://localhost")

View file

@ -26,6 +26,8 @@ BASE_DIR = Path(__file__).resolve().parent.parent
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
def get_secret(name: str, encoding: str = "utf-8") -> str: def get_secret(name: str, encoding: str = "utf-8") -> str:
# In the NixOS deployment, this variable is set by `systemd` via `LoadCredential`
# https://systemd.io/CREDENTIALS/
credentials_dir = env.get("CREDENTIALS_DIRECTORY") credentials_dir = env.get("CREDENTIALS_DIRECTORY")
if credentials_dir is None: if credentials_dir is None: