1 { config, lib, pkgs, ... }:
4 cfg = config.services.netbox;
5 pythonFmt = pkgs.formats.pythonVars {};
6 staticDir = cfg.dataDir + "/static";
8 settingsFile = pythonFmt.generate "netbox-settings.py" cfg.settings;
9 extraConfigFile = pkgs.writeTextFile {
10 name = "netbox-extraConfig.py";
11 text = cfg.extraConfig;
13 configFile = pkgs.concatText "configuration.py" [ settingsFile extraConfigFile ];
15 pkg = (cfg.package.overrideAttrs (old: {
16 installPhase = old.installPhase + ''
17 ln -s ${configFile} $out/opt/netbox/netbox/netbox/configuration.py
18 '' + lib.optionalString cfg.enableLdap ''
19 ln -s ${cfg.ldapConfigPath} $out/opt/netbox/netbox/netbox/ldap_config.py
22 inherit (cfg) plugins;
24 netboxManageScript = with pkgs; (writeScriptBin "netbox-manage" ''
26 export PYTHONPATH=${pkg.pythonPath}
27 sudo -u netbox ${pkg}/bin/netbox "$@"
31 options.services.netbox = {
32 enable = lib.mkOption {
33 type = lib.types.bool;
38 This module requires a reverse proxy that serves `/static` separately.
39 See this [example](https://github.com/netbox-community/netbox/blob/develop/contrib/nginx.conf/) on how to configure this.
43 settings = lib.mkOption {
45 Configuration options to set in `configuration.py`.
46 See the [documentation](https://docs.netbox.dev/en/stable/configuration/) for more possible options.
51 type = lib.types.submodule {
52 freeformType = pythonFmt.type;
55 ALLOWED_HOSTS = lib.mkOption {
56 type = with lib.types; listOf str;
59 A list of valid fully-qualified domain names (FQDNs) and/or IP
60 addresses that can be used to reach the NetBox service.
67 listenAddress = lib.mkOption {
71 Address the server will listen on.
75 package = lib.mkOption {
76 type = lib.types.package;
78 if lib.versionAtLeast config.system.stateVersion "24.05"
80 else if lib.versionAtLeast config.system.stateVersion "23.11"
82 else if lib.versionAtLeast config.system.stateVersion "23.05"
85 defaultText = lib.literalExpression ''
86 if lib.versionAtLeast config.system.stateVersion "24.05"
88 else if lib.versionAtLeast config.system.stateVersion "23.11"
90 else if lib.versionAtLeast config.system.stateVersion "23.05"
95 NetBox package to use.
100 type = lib.types.port;
103 Port the server will listen on.
107 plugins = lib.mkOption {
108 type = with lib.types; functionTo (listOf package);
110 defaultText = lib.literalExpression ''
111 python3Packages: with python3Packages; [];
114 List of plugin packages to install.
118 dataDir = lib.mkOption {
119 type = lib.types.str;
120 default = "/var/lib/netbox";
122 Storage path of netbox.
126 secretKeyFile = lib.mkOption {
127 type = lib.types.path;
129 Path to a file containing the secret key.
133 extraConfig = lib.mkOption {
134 type = lib.types.lines;
137 Additional lines of configuration appended to the `configuration.py`.
138 See the [documentation](https://docs.netbox.dev/en/stable/configuration/) for more possible options.
142 enableLdap = lib.mkOption {
143 type = lib.types.bool;
146 Enable LDAP-Authentication for Netbox.
148 This requires a configuration file being pass through `ldapConfigPath`.
152 ldapConfigPath = lib.mkOption {
153 type = lib.types.path;
156 Path to the Configuration-File for LDAP-Authentication, will be loaded as `ldap_config.py`.
157 See the [documentation](https://netbox.readthedocs.io/en/stable/installation/6-ldap/#configuration) for possible options.
161 from django_auth_ldap.config import LDAPSearch, PosixGroupType
163 AUTH_LDAP_SERVER_URI = "ldaps://ldap.example.com/"
165 AUTH_LDAP_USER_SEARCH = LDAPSearch(
166 "ou=accounts,ou=posix,dc=example,dc=com",
171 AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
172 "ou=groups,ou=posix,dc=example,dc=com",
174 "(objectClass=posixGroup)",
176 AUTH_LDAP_GROUP_TYPE = PosixGroupType()
178 # Mirror LDAP group assignments.
179 AUTH_LDAP_MIRROR_GROUPS = True
181 # For more granular permissions, we can map LDAP groups to Django groups.
182 AUTH_LDAP_FIND_GROUP_PERMS = True
185 keycloakClientSecret = lib.mkOption {
186 type = with lib.types; nullOr path;
189 File that contains the keycloak client secret.
194 config = lib.mkIf cfg.enable {
196 plugins = lib.mkIf cfg.enableLdap (ps: [ ps.django-auth-ldap ]);
198 STATIC_ROOT = staticDir;
199 MEDIA_ROOT = "${cfg.dataDir}/media";
200 REPORTS_ROOT = "${cfg.dataDir}/reports";
201 SCRIPTS_ROOT = "${cfg.dataDir}/scripts";
203 GIT_PATH = "${pkgs.gitMinimal}/bin/git";
208 HOST = "/run/postgresql";
211 # Redis database settings. Redis is used for caching and for queuing
212 # background tasks such as webhook events. A separate configuration
213 # exists for each. Full connection details are required in both
214 # sections, and it is strongly recommended to use two separate database
218 URL = "unix://${config.services.redis.servers.netbox.unixSocket}?db=0";
222 URL = "unix://${config.services.redis.servers.netbox.unixSocket}?db=1";
227 REMOTE_AUTH_BACKEND = lib.mkIf cfg.enableLdap "netbox.authentication.LDAPBackend";
229 LOGGING = lib.mkDefault {
232 formatters.precise.format = "[%(levelname)s@%(name)s] %(message)s";
235 class = "logging.StreamHandler";
236 formatter = "precise";
239 # log to console/systemd instead of file
242 handlers = [ "console" ];
248 with open("${cfg.secretKeyFile}", "r") as file:
249 SECRET_KEY = file.readline()
250 '' + (lib.optionalString (cfg.keycloakClientSecret != null) ''
251 with open("${cfg.keycloakClientSecret}", "r") as file:
252 SOCIAL_AUTH_KEYCLOAK_SECRET = file.readline()
256 services.redis.servers.netbox.enable = true;
258 services.postgresql = {
260 ensureDatabases = [ "netbox" ];
264 ensureDBOwnership = true;
269 environment.systemPackages = [ netboxManageScript ];
271 systemd.targets.netbox = {
272 description = "Target for all NetBox services";
273 wantedBy = [ "multi-user.target" ];
274 wants = [ "network-online.target" ];
275 after = [ "network-online.target" "redis-netbox.service" ];
278 systemd.services = let
279 defaultServiceConfig = {
280 WorkingDirectory = "${cfg.dataDir}";
283 StateDirectory = "netbox";
284 StateDirectoryMode = "0750";
285 Restart = "on-failure";
290 description = "NetBox WSGI Service";
291 documentation = [ "https://docs.netbox.dev/" ];
293 wantedBy = [ "netbox.target" ];
295 after = [ "network-online.target" ];
296 wants = [ "network-online.target" ];
298 environment.PYTHONPATH = pkg.pythonPath;
301 # On the first run, or on upgrade / downgrade, run migrations and related.
302 # This mostly correspond to upstream NetBox's 'upgrade.sh' script.
303 versionFile="${cfg.dataDir}/version"
305 if [[ -e "$versionFile" && "$(cat "$versionFile")" == "${cfg.package.version}" ]]; then
309 ${pkg}/bin/netbox migrate
310 ${pkg}/bin/netbox trace_paths --no-input
311 ${pkg}/bin/netbox collectstatic --no-input
312 ${pkg}/bin/netbox remove_stale_contenttypes --no-input
313 ${pkg}/bin/netbox reindex --lazy
314 ${pkg}/bin/netbox clearsessions
316 # The clearcache command was removed in 3.7.0:
317 # https://github.com/netbox-community/netbox/issues/14458
318 (lib.versionOlder cfg.package.version "3.7.0")
319 "${pkg}/bin/netbox clearcache"}
321 echo "${cfg.package.version}" > "$versionFile"
324 serviceConfig = defaultServiceConfig // {
326 ${pkg.gunicorn}/bin/gunicorn netbox.wsgi \
327 --bind ${cfg.listenAddress}:${toString cfg.port} \
328 --pythonpath ${pkg}/opt/netbox/netbox
335 description = "NetBox Request Queue Worker";
336 documentation = [ "https://docs.netbox.dev/" ];
338 wantedBy = [ "netbox.target" ];
339 after = [ "netbox.service" ];
341 environment.PYTHONPATH = pkg.pythonPath;
343 serviceConfig = defaultServiceConfig // {
345 ${pkg}/bin/netbox rqworker high default low
351 netbox-housekeeping = {
352 description = "NetBox housekeeping job";
353 documentation = [ "https://docs.netbox.dev/" ];
355 wantedBy = [ "multi-user.target" ];
357 after = [ "network-online.target" "netbox.service" ];
358 wants = [ "network-online.target" ];
360 environment.PYTHONPATH = pkg.pythonPath;
362 serviceConfig = defaultServiceConfig // {
365 ${pkg}/bin/netbox housekeeping
371 systemd.timers.netbox-housekeeping = {
372 description = "Run NetBox housekeeping job";
373 documentation = [ "https://docs.netbox.dev/" ];
375 wantedBy = [ "multi-user.target" ];
377 after = [ "network-online.target" "netbox.service" ];
378 wants = [ "network-online.target" ];
381 OnCalendar = "daily";
387 users.users.netbox = {
388 home = "${cfg.dataDir}";
392 users.groups.netbox = {};
393 users.groups."${config.services.redis.servers.netbox.user}".members = [ "netbox" ];