diff --git a/nixos/modules/services/web-apps/netbox.nix b/nixos/modules/services/web-apps/netbox.nix index 584b159309f8..e3373f3c962d 100644 --- a/nixos/modules/services/web-apps/netbox.nix +++ b/nixos/modules/services/web-apps/netbox.nix @@ -10,6 +10,7 @@ let any attrValues mkChangedOptionModule + mkEnableOption mkOption mkRemovedOptionModule mkRenamedOptionModule @@ -132,8 +133,9 @@ in description = '' Whether to enable Netbox, a DCIM and IPAM source of truth. - This module requires setting up a reverse proxy. The NetBox project has - example configurations for [nginx] and the [Apache httpd] server. + This module requires setting up a reverse proxy and has native support + for nginx. Additionally, the NetBox project has example configurations + for [nginx] and the [Apache httpd] server. The important change to make is to serve `/static` from `''${config.services.netbox.settings.STATIC_ROOT}`. @@ -392,6 +394,23 @@ in ''; }; + nginx = { + enable = mkEnableOption "nginx and configure a virtual host"; + + hostname = mkOption { + type = types.str; + example = "netbox.example.com"; + description = '' + The hostname for which an nginx virtual host should be created. + + ::: {.tip} + Customize the virtual host through + `services.nginx.virtualHosts.''${config.services.netbox.nginx.hostname}`. + ::: + ''; + }; + }; + redis.createLocally = mkOption { type = types.bool; default = true; @@ -539,139 +558,164 @@ in }; }; - config = lib.mkIf cfg.enable { - services.netbox.plugins = lib.mkIf enableLDAP (ps: [ ps.django-auth-ldap ]); - - services.redis.servers.netbox.enable = cfg.redis.createLocally; - - services.postgresql = lib.mkIf cfg.postgresql.createLocally { - enable = true; - ensureDatabases = [ "netbox" ]; - ensureUsers = [ - { - name = "netbox"; - ensureDBOwnership = true; - } - ]; - }; - - environment.systemPackages = [ netboxManageScript ]; - - systemd.slices.system-netbox = { - description = "Netbox DCIM/IPAM"; - }; - - systemd.targets.netbox = { - description = "Target for all NetBox services"; - wantedBy = [ "multi-user.target" ]; - wants = [ "network-online.target" ]; - after = [ - "network-online.target" - "redis-netbox.service" - ]; - }; - - systemd.services = - let - defaultUnitConfig = { - documentation = [ "https://netboxlabs.com/docs/netbox/" ]; - environment.PYTHONPATH = finalPackage.pythonPath; - }; - defaultServiceConfig = { - WorkingDirectory = "${cfg.dataDir}"; - User = "netbox"; - Group = "netbox"; - StateDirectory = "netbox"; - StateDirectoryMode = "0750"; - Restart = "on-failure"; - RestartSec = 30; - Slice = "system-netbox.slice"; - EnvironmentFile = cfg.environmentFiles; - }; - in + config = lib.mkIf cfg.enable ( + lib.mkMerge [ { - netbox = defaultUnitConfig // { - description = "NetBox WSGI Service"; + services.netbox.plugins = lib.mkIf enableLDAP (ps: [ ps.django-auth-ldap ]); - wantedBy = [ "netbox.target" ]; + services.redis.servers.netbox.enable = cfg.redis.createLocally; - after = [ "network-online.target" ]; - wants = [ "network-online.target" ]; - - preStart = '' - # Generate random default secrets, if the user didn't supply any. - ${optionalString (cfg.secretKeyFile == null) '' - if [ ! -e "${secretKeyFile}" ]; then - ${finalPackage}/opt/netbox/netbox/generate_secret_key.py > "${secretKeyFile}" - fi - ''} - ${optionalString - (any (path: path == "${cfg.dataDir}/pepper.1") (attrValues cfg.apiTokenPepperFiles)) - '' - if [ ! -e "${cfg.dataDir}/pepper.1" ]; then - ${finalPackage}/opt/netbox/netbox/generate_secret_key.py > "${cfg.dataDir}/pepper.1" - fi - '' + services.postgresql = lib.mkIf cfg.postgresql.createLocally { + enable = true; + ensureDatabases = [ "netbox" ]; + ensureUsers = [ + { + name = "netbox"; + ensureDBOwnership = true; } + ]; + }; - # On the first run, or on upgrade / downgrade, run migrations and related. - # This mostly correspond to upstream NetBox's 'upgrade.sh' script. - versionFile="${cfg.dataDir}/version" + environment.systemPackages = [ netboxManageScript ]; - if [[ -h "$versionFile" && "$(readlink -- "$versionFile")" == "${cfg.package}" ]]; then - exit 0 - fi + systemd.slices.system-netbox = { + description = "Netbox DCIM/IPAM"; + }; - ${lib.getExe finalPackage} migrate - ${lib.getExe finalPackage} trace_paths --no-input - ${lib.getExe finalPackage} collectstatic --clear --no-input - ${lib.getExe finalPackage} remove_stale_contenttypes --no-input - ${lib.getExe finalPackage} reindex --lazy - ${lib.getExe finalPackage} clearsessions + systemd.targets.netbox = { + description = "Target for all NetBox services"; + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ]; + after = [ + "network-online.target" + "redis-netbox.service" + ]; + }; - ln -sfn "${cfg.package}" "$versionFile" - ''; + systemd.services = + let + defaultUnitConfig = { + documentation = [ "https://netboxlabs.com/docs/netbox/" ]; + environment.PYTHONPATH = finalPackage.pythonPath; + }; + defaultServiceConfig = { + WorkingDirectory = "${cfg.dataDir}"; + User = "netbox"; + Group = "netbox"; + StateDirectory = "netbox"; + StateDirectoryMode = "0750"; + Restart = "on-failure"; + RestartSec = 30; + Slice = "system-netbox.slice"; + EnvironmentFile = cfg.environmentFiles; + }; + in + { + netbox = defaultUnitConfig // { + description = "NetBox WSGI Service"; - serviceConfig = defaultServiceConfig // { - ExecStart = toString ( - [ - (lib.getExe finalPackage.gunicorn) - "netbox.wsgi" - ] - ++ lib.cli.toCommandLineGNU { } ( - { - inherit (cfg) bind; - pythonpath = "${finalPackage}/opt/netbox/netbox"; + wantedBy = [ "netbox.target" ]; + + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + + preStart = '' + # Generate random default secrets, if the user didn't supply any. + ${optionalString (cfg.secretKeyFile == null) '' + if [ ! -e "${secretKeyFile}" ]; then + ${finalPackage}/opt/netbox/netbox/generate_secret_key.py > "${secretKeyFile}" + fi + ''} + ${optionalString + (any (path: path == "${cfg.dataDir}/pepper.1") (attrValues cfg.apiTokenPepperFiles)) + '' + if [ ! -e "${cfg.dataDir}/pepper.1" ]; then + ${finalPackage}/opt/netbox/netbox/generate_secret_key.py > "${cfg.dataDir}/pepper.1" + fi + '' } - // cfg.gunicorn.extraArgs - ) - ); - PrivateTmp = true; - RuntimeDirectory = "netbox"; - TimeoutStartSec = lib.mkDefault "10min"; + + # On the first run, or on upgrade / downgrade, run migrations and related. + # This mostly correspond to upstream NetBox's 'upgrade.sh' script. + versionFile="${cfg.dataDir}/version" + + if [[ -h "$versionFile" && "$(readlink -- "$versionFile")" == "${cfg.package}" ]]; then + exit 0 + fi + + ${lib.getExe finalPackage} migrate + ${lib.getExe finalPackage} trace_paths --no-input + ${lib.getExe finalPackage} collectstatic --clear --no-input + ${lib.getExe finalPackage} remove_stale_contenttypes --no-input + ${lib.getExe finalPackage} reindex --lazy + ${lib.getExe finalPackage} clearsessions + + ln -sfn "${cfg.package}" "$versionFile" + ''; + + serviceConfig = defaultServiceConfig // { + ExecStart = toString ( + [ + (lib.getExe finalPackage.gunicorn) + "netbox.wsgi" + ] + ++ lib.cli.toCommandLineGNU { } ( + { + inherit (cfg) bind; + pythonpath = "${finalPackage}/opt/netbox/netbox"; + } + // cfg.gunicorn.extraArgs + ) + ); + PrivateTmp = true; + RuntimeDirectory = "netbox"; + RuntimeDirectoryMode = "0750"; + TimeoutStartSec = lib.mkDefault "10min"; + }; + }; + + netbox-rq = defaultUnitConfig // { + description = "NetBox Request Queue Worker"; + + wantedBy = [ "netbox.target" ]; + after = [ "netbox.service" ]; + + serviceConfig = defaultServiceConfig // { + ExecStart = toString [ + (lib.getExe finalPackage) + "rqworker" + "high" + "default" + "low" + ]; + PrivateTmp = true; + }; + }; + + netbox-housekeeping = defaultUnitConfig // { + description = "NetBox housekeeping job"; + + wantedBy = [ "multi-user.target" ]; + + after = [ + "network-online.target" + "netbox.service" + ]; + wants = [ "network-online.target" ]; + + serviceConfig = defaultServiceConfig // { + Type = "oneshot"; + ExecStart = toString [ + (lib.getExe finalPackage) + "housekeeping" + ]; + }; + }; }; - }; - netbox-rq = defaultUnitConfig // { - description = "NetBox Request Queue Worker"; - - wantedBy = [ "netbox.target" ]; - after = [ "netbox.service" ]; - - serviceConfig = defaultServiceConfig // { - ExecStart = toString [ - (lib.getExe finalPackage) - "rqworker" - "high" - "default" - "low" - ]; - PrivateTmp = true; - }; - }; - - netbox-housekeeping = defaultUnitConfig // { - description = "NetBox housekeeping job"; + systemd.timers.netbox-housekeeping = { + description = "Run NetBox housekeeping job"; + documentation = [ "https://netboxlabs.com/docs/netbox/" ]; wantedBy = [ "multi-user.target" ]; @@ -681,41 +725,36 @@ in ]; wants = [ "network-online.target" ]; - serviceConfig = defaultServiceConfig // { - Type = "oneshot"; - ExecStart = toString [ - (lib.getExe finalPackage) - "housekeeping" - ]; + timerConfig = { + OnCalendar = "daily"; + AccuracySec = "1h"; + Persistent = true; }; }; - }; - systemd.timers.netbox-housekeeping = { - description = "Run NetBox housekeeping job"; - documentation = [ "https://netboxlabs.com/docs/netbox/" ]; + users.users.netbox = { + home = "${cfg.dataDir}"; + isSystemUser = true; + group = "netbox"; + }; + users.groups.netbox = { }; + users.groups."${config.services.redis.servers.netbox.user}".members = [ "netbox" ]; + } - wantedBy = [ "multi-user.target" ]; + (lib.mkIf cfg.nginx.enable { + # Access to STATIC_ROOT and the UNIX domain socket + systemd.services.nginx.serviceConfig.SupplementaryGroups = [ "netbox" ]; - after = [ - "network-online.target" - "netbox.service" - ]; - wants = [ "network-online.target" ]; + services.nginx = { + enable = true; + recommendedProxySettings = true; - timerConfig = { - OnCalendar = "daily"; - AccuracySec = "1h"; - Persistent = true; - }; - }; - - users.users.netbox = { - home = "${cfg.dataDir}"; - isSystemUser = true; - group = "netbox"; - }; - users.groups.netbox = { }; - users.groups."${config.services.redis.servers.netbox.user}".members = [ "netbox" ]; - }; + virtualHosts.${cfg.nginx.hostname} = { + locations."/".proxyPass = "http://${toString config.services.netbox.bind}"; + locations."/static/".alias = cfg.settings.STATIC_ROOT; + }; + }; + }) + ] + ); } diff --git a/nixos/tests/web-apps/netbox-upgrade.nix b/nixos/tests/web-apps/netbox-upgrade.nix index f8ac7c261dd4..ffea21a648cd 100644 --- a/nixos/tests/web-apps/netbox-upgrade.nix +++ b/nixos/tests/web-apps/netbox-upgrade.nix @@ -34,17 +34,10 @@ in # Pick the NetBox package from this config's "pkgs" argument, # so that `nixpkgs.config.permittedInsecurePackages` works package = pkgs.${oldNetbox}; - }; - services.nginx = { - enable = true; - - recommendedProxySettings = true; - - virtualHosts.netbox = { - default = true; - locations."/".proxyPass = "http://${config.services.netbox.bind}"; - locations."/static/".alias = "/var/lib/netbox/static/"; + nginx = { + enable = true; + hostname = "localhost"; }; }; diff --git a/nixos/tests/web-apps/netbox/default.nix b/nixos/tests/web-apps/netbox/default.nix index 176b08d7c952..8ece8d8703fa 100644 --- a/nixos/tests/web-apps/netbox/default.nix +++ b/nixos/tests/web-apps/netbox/default.nix @@ -34,6 +34,11 @@ import ../../make-test-python.nix ( enable = true; package = netbox; + nginx = { + enable = true; + hostname = "localhost"; + }; + ldapConfigFile = pkgs.writeText "ldap_config.py" '' import ldap from django_auth_ldap.config import LDAPSearch, PosixGroupType @@ -61,18 +66,6 @@ import ../../make-test-python.nix ( ''; }; - services.nginx = { - enable = true; - - recommendedProxySettings = true; - - virtualHosts.netbox = { - default = true; - locations."/".proxyPass = "http://${toString config.services.netbox.bind}"; - locations."/static/".alias = "/var/lib/netbox/static/"; - }; - }; - # Adapted from the sssd-ldap NixOS test services.openldap = { enable = true;