diff --git a/nixos/doc/manual/release-notes/rl-2305.section.md b/nixos/doc/manual/release-notes/rl-2305.section.md
index 46f324c3c0f6..28e4df793daf 100644
--- a/nixos/doc/manual/release-notes/rl-2305.section.md
+++ b/nixos/doc/manual/release-notes/rl-2305.section.md
@@ -235,6 +235,10 @@ In addition to numerous new and upgraded packages, this release has the followin
- `services.openssh.ciphers` to `services.openssh.settings.Ciphers`
- `services.openssh.gatewayPorts` to `services.openssh.settings.GatewayPorts`
+- `netbox` was updated to 3.4. NixOS' `services.netbox.package` still defaults to 3.3 if `stateVersion` is earlier than 23.05. Please review upstream's [breaking changes](https://github.com/netbox-community/netbox/releases/tag/v3.4.0), and upgrade NetBox by changing `services.netbox.package`. Database migrations will be run automatically.
+
+- `services.netbox` now support RFC42-style options, through `services.netbox.settings`.
+
- `services.mastodon` gained a tootctl wrapped named `mastodon-tootctl` similar to `nextcloud-occ` which can be executed from any user and switches to the configured mastodon user with sudo and sources the environment variables.
- DocBook option documentation, which has been deprecated since 22.11, will now cause a warning when documentation is built. Out-of-tree modules should migrate to using CommonMark documentation as outlined in [](#sec-option-declarations) to silence this warning.
diff --git a/nixos/modules/services/web-apps/netbox.nix b/nixos/modules/services/web-apps/netbox.nix
index e028f16004ef..0ecb20e8c2c0 100644
--- a/nixos/modules/services/web-apps/netbox.nix
+++ b/nixos/modules/services/web-apps/netbox.nix
@@ -4,45 +4,17 @@ with lib;
let
cfg = config.services.netbox;
+ pythonFmt = pkgs.formats.pythonVars {};
staticDir = cfg.dataDir + "/static";
- configFile = pkgs.writeTextFile {
- name = "configuration.py";
- text = ''
- STATIC_ROOT = '${staticDir}'
- MEDIA_ROOT = '${cfg.dataDir}/media'
- REPORTS_ROOT = '${cfg.dataDir}/reports'
- SCRIPTS_ROOT = '${cfg.dataDir}/scripts'
- ALLOWED_HOSTS = ['*']
- DATABASE = {
- 'NAME': 'netbox',
- 'USER': 'netbox',
- 'HOST': '/run/postgresql',
- }
-
- # Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate
- # configuration exists for each. Full connection details are required in both sections, and it is strongly recommended
- # to use two separate database IDs.
- REDIS = {
- 'tasks': {
- 'URL': 'unix://${config.services.redis.servers.netbox.unixSocket}?db=0',
- 'SSL': False,
- },
- 'caching': {
- 'URL': 'unix://${config.services.redis.servers.netbox.unixSocket}?db=1',
- 'SSL': False,
- }
- }
-
- with open("${cfg.secretKeyFile}", "r") as file:
- SECRET_KEY = file.readline()
-
- ${optionalString cfg.enableLdap "REMOTE_AUTH_BACKEND = 'netbox.authentication.LDAPBackend'"}
-
- ${cfg.extraConfig}
- '';
+ settingsFile = pythonFmt.generate "netbox-settings.py" cfg.settings;
+ extraConfigFile = pkgs.writeTextFile {
+ name = "netbox-extraConfig.py";
+ text = cfg.extraConfig;
};
- pkg = (pkgs.netbox.overrideAttrs (old: {
+ configFile = pkgs.concatText "configuration.py" [ settingsFile extraConfigFile ];
+
+ pkg = (cfg.package.overrideAttrs (old: {
installPhase = old.installPhase + ''
ln -s ${configFile} $out/opt/netbox/netbox/netbox/configuration.py
'' + optionalString cfg.enableLdap ''
@@ -70,6 +42,30 @@ in {
'';
};
+ settings = lib.mkOption {
+ description = lib.mdDoc ''
+ Configuration options to set in `configuration.py`.
+ See the [documentation](https://docs.netbox.dev/en/stable/configuration/) for more possible options.
+ '';
+
+ default = { };
+
+ type = lib.types.submodule {
+ freeformType = pythonFmt.type;
+
+ options = {
+ ALLOWED_HOSTS = lib.mkOption {
+ type = with lib.types; listOf str;
+ default = ["*"];
+ description = lib.mdDoc ''
+ A list of valid fully-qualified domain names (FQDNs) and/or IP
+ addresses that can be used to reach the NetBox service.
+ '';
+ };
+ };
+ };
+ };
+
listenAddress = mkOption {
type = types.str;
default = "[::1]";
@@ -78,6 +74,17 @@ in {
'';
};
+ package = mkOption {
+ type = types.package;
+ default = if versionAtLeast config.system.stateVersion "23.05" then pkgs.netbox else pkgs.netbox_3_3;
+ defaultText = literalExpression ''
+ if versionAtLeast config.system.stateVersion "23.05" then pkgs.netbox else pkgs.netbox_3_3;
+ '';
+ description = lib.mdDoc ''
+ NetBox package to use.
+ '';
+ };
+
port = mkOption {
type = types.port;
default = 8001;
@@ -117,7 +124,7 @@ in {
default = "";
description = lib.mdDoc ''
Additional lines of configuration appended to the `configuration.py`.
- See the [documentation](https://netbox.readthedocs.io/en/stable/configuration/optional-settings/) for more possible options.
+ See the [documentation](https://docs.netbox.dev/en/stable/configuration/) for more possible options.
'';
};
@@ -138,11 +145,90 @@ in {
Path to the Configuration-File for LDAP-Authentication, will be loaded as `ldap_config.py`.
See the [documentation](https://netbox.readthedocs.io/en/stable/installation/6-ldap/#configuration) for possible options.
'';
+ example = ''
+ import ldap
+ from django_auth_ldap.config import LDAPSearch, PosixGroupType
+
+ AUTH_LDAP_SERVER_URI = "ldaps://ldap.example.com/"
+
+ AUTH_LDAP_USER_SEARCH = LDAPSearch(
+ "ou=accounts,ou=posix,dc=example,dc=com",
+ ldap.SCOPE_SUBTREE,
+ "(uid=%(user)s)",
+ )
+
+ AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
+ "ou=groups,ou=posix,dc=example,dc=com",
+ ldap.SCOPE_SUBTREE,
+ "(objectClass=posixGroup)",
+ )
+ AUTH_LDAP_GROUP_TYPE = PosixGroupType()
+
+ # Mirror LDAP group assignments.
+ AUTH_LDAP_MIRROR_GROUPS = True
+
+ # For more granular permissions, we can map LDAP groups to Django groups.
+ AUTH_LDAP_FIND_GROUP_PERMS = True
+ '';
};
};
config = mkIf cfg.enable {
- services.netbox.plugins = mkIf cfg.enableLdap (ps: [ ps.django-auth-ldap ]);
+ services.netbox = {
+ plugins = mkIf cfg.enableLdap (ps: [ ps.django-auth-ldap ]);
+ settings = {
+ STATIC_ROOT = staticDir;
+ MEDIA_ROOT = "${cfg.dataDir}/media";
+ REPORTS_ROOT = "${cfg.dataDir}/reports";
+ SCRIPTS_ROOT = "${cfg.dataDir}/scripts";
+
+ DATABASE = {
+ NAME = "netbox";
+ USER = "netbox";
+ HOST = "/run/postgresql";
+ };
+
+ # Redis database settings. Redis is used for caching and for queuing
+ # background tasks such as webhook events. A separate configuration
+ # exists for each. Full connection details are required in both
+ # sections, and it is strongly recommended to use two separate database
+ # IDs.
+ REDIS = {
+ tasks = {
+ URL = "unix://${config.services.redis.servers.netbox.unixSocket}?db=0";
+ SSL = false;
+ };
+ caching = {
+ URL = "unix://${config.services.redis.servers.netbox.unixSocket}?db=1";
+ SSL = false;
+ };
+ };
+
+ REMOTE_AUTH_BACKEND = lib.mkIf cfg.enableLdap "netbox.authentication.LDAPBackend";
+
+ LOGGING = lib.mkDefault {
+ version = 1;
+
+ formatters.precise.format = "[%(levelname)s@%(name)s] %(message)s";
+
+ handlers.console = {
+ class = "logging.StreamHandler";
+ formatter = "precise";
+ };
+
+ # log to console/systemd instead of file
+ root = {
+ level = "INFO";
+ handlers = [ "console" ];
+ };
+ };
+ };
+
+ extraConfig = ''
+ with open("${cfg.secretKeyFile}", "r") as file:
+ SECRET_KEY = file.readline()
+ '';
+ };
services.redis.servers.netbox.enable = true;
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index a155510450b1..0783f3bf68e2 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -460,7 +460,8 @@ in {
netdata = handleTest ./netdata.nix {};
networking.networkd = handleTest ./networking.nix { networkd = true; };
networking.scripted = handleTest ./networking.nix { networkd = false; };
- netbox = handleTest ./web-apps/netbox.nix {};
+ netbox = handleTest ./web-apps/netbox.nix { inherit (pkgs) netbox; };
+ netbox_3_3 = handleTest ./web-apps/netbox.nix { netbox = pkgs.netbox_3_3; };
# TODO: put in networking.nix after the test becomes more complete
networkingProxy = handleTest ./networking-proxy.nix {};
nextcloud = handleTest ./nextcloud {};
diff --git a/nixos/tests/web-apps/netbox.nix b/nixos/tests/web-apps/netbox.nix
index 35decdd49e87..30de74f1886c 100644
--- a/nixos/tests/web-apps/netbox.nix
+++ b/nixos/tests/web-apps/netbox.nix
@@ -1,21 +1,146 @@
-import ../make-test-python.nix ({ lib, pkgs, ... }: {
+let
+ ldapDomain = "example.org";
+ ldapSuffix = "dc=example,dc=org";
+
+ ldapRootUser = "admin";
+ ldapRootPassword = "foobar";
+
+ testUser = "alice";
+ testPassword = "verySecure";
+ testGroup = "netbox-users";
+in import ../make-test-python.nix ({ lib, pkgs, netbox, ... }: {
name = "netbox";
meta = with lib.maintainers; {
- maintainers = [ n0emis ];
+ maintainers = [ minijackson n0emis ];
};
- nodes.machine = { ... }: {
+ nodes.machine = { config, ... }: {
services.netbox = {
enable = true;
+ package = netbox;
secretKeyFile = pkgs.writeText "secret" ''
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
'';
+
+ enableLdap = true;
+ ldapConfigPath = pkgs.writeText "ldap_config.py" ''
+ import ldap
+ from django_auth_ldap.config import LDAPSearch, PosixGroupType
+
+ AUTH_LDAP_SERVER_URI = "ldap://localhost/"
+
+ AUTH_LDAP_USER_SEARCH = LDAPSearch(
+ "ou=accounts,ou=posix,${ldapSuffix}",
+ ldap.SCOPE_SUBTREE,
+ "(uid=%(user)s)",
+ )
+
+ AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
+ "ou=groups,ou=posix,${ldapSuffix}",
+ ldap.SCOPE_SUBTREE,
+ "(objectClass=posixGroup)",
+ )
+ AUTH_LDAP_GROUP_TYPE = PosixGroupType()
+
+ # Mirror LDAP group assignments.
+ AUTH_LDAP_MIRROR_GROUPS = True
+
+ # For more granular permissions, we can map LDAP groups to Django groups.
+ AUTH_LDAP_FIND_GROUP_PERMS = True
+ '';
};
+
+ services.nginx = {
+ enable = true;
+
+ recommendedProxySettings = true;
+
+ virtualHosts.netbox = {
+ default = true;
+ locations."/".proxyPass = "http://localhost:${toString config.services.netbox.port}";
+ locations."/static/".alias = "/var/lib/netbox/static/";
+ };
+ };
+
+ # Adapted from the sssd-ldap NixOS test
+ services.openldap = {
+ enable = true;
+ settings = {
+ children = {
+ "cn=schema".includes = [
+ "${pkgs.openldap}/etc/schema/core.ldif"
+ "${pkgs.openldap}/etc/schema/cosine.ldif"
+ "${pkgs.openldap}/etc/schema/inetorgperson.ldif"
+ "${pkgs.openldap}/etc/schema/nis.ldif"
+ ];
+ "olcDatabase={1}mdb" = {
+ attrs = {
+ objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
+ olcDatabase = "{1}mdb";
+ olcDbDirectory = "/var/lib/openldap/db";
+ olcSuffix = ldapSuffix;
+ olcRootDN = "cn=${ldapRootUser},${ldapSuffix}";
+ olcRootPW = ldapRootPassword;
+ };
+ };
+ };
+ };
+ declarativeContents = {
+ ${ldapSuffix} = ''
+ dn: ${ldapSuffix}
+ objectClass: top
+ objectClass: dcObject
+ objectClass: organization
+ o: ${ldapDomain}
+
+ dn: ou=posix,${ldapSuffix}
+ objectClass: top
+ objectClass: organizationalUnit
+
+ dn: ou=accounts,ou=posix,${ldapSuffix}
+ objectClass: top
+ objectClass: organizationalUnit
+
+ dn: uid=${testUser},ou=accounts,ou=posix,${ldapSuffix}
+ objectClass: person
+ objectClass: posixAccount
+ userPassword: ${testPassword}
+ homeDirectory: /home/${testUser}
+ uidNumber: 1234
+ gidNumber: 1234
+ cn: ""
+ sn: ""
+
+ dn: ou=groups,ou=posix,${ldapSuffix}
+ objectClass: top
+ objectClass: organizationalUnit
+
+ dn: cn=${testGroup},ou=groups,ou=posix,${ldapSuffix}
+ objectClass: posixGroup
+ gidNumber: 2345
+ memberUid: ${testUser}
+ '';
+ };
+ };
+
+ users.users.nginx.extraGroups = [ "netbox" ];
+
+ networking.firewall.allowedTCPPorts = [ 80 ];
};
- testScript = ''
- machine.start()
+ testScript = let
+ changePassword = pkgs.writeText "change-password.py" ''
+ from django.contrib.auth.models import User
+ u = User.objects.get(username='netbox')
+ u.set_password('netbox')
+ u.save()
+ '';
+ in ''
+ from typing import Any, Dict
+ import json
+
+ start_all()
machine.wait_for_unit("netbox.target")
machine.wait_until_succeeds("journalctl --since -1m --unit netbox --grep Listening")
@@ -26,5 +151,167 @@ import ../make-test-python.nix ({ lib, pkgs, ... }: {
with subtest("Staticfiles are generated"):
machine.succeed("test -e /var/lib/netbox/static/netbox.js")
+
+ with subtest("Superuser can be created"):
+ machine.succeed(
+ "netbox-manage createsuperuser --noinput --username netbox --email netbox@example.com"
+ )
+ # Django doesn't have a "clean" way of inputting the password from the command line
+ machine.succeed("cat '${changePassword}' | netbox-manage shell")
+
+ machine.wait_for_unit("network.target")
+
+ with subtest("Home screen loads from nginx"):
+ machine.succeed(
+ "curl -sSfL http://localhost | grep '
Home | NetBox'"
+ )
+
+ with subtest("Staticfiles can be fetched"):
+ machine.succeed("curl -sSfL http://localhost/static/netbox.js")
+ machine.succeed("curl -sSfL http://localhost/static/docs/")
+
+ with subtest("Can interact with API"):
+ json.loads(
+ machine.succeed("curl -sSfL -H 'Accept: application/json' 'http://localhost/api/'")
+ )
+
+ def login(username: str, password: str):
+ encoded_data = json.dumps({"username": username, "password": password})
+ uri = "/users/tokens/provision/"
+ result = json.loads(
+ machine.succeed(
+ "curl -sSfL "
+ "-X POST "
+ "-H 'Accept: application/json' "
+ "-H 'Content-Type: application/json' "
+ f"'http://localhost/api{uri}' "
+ f"--data '{encoded_data}'"
+ )
+ )
+ return result["key"]
+
+ with subtest("Can login"):
+ auth_token = login("netbox", "netbox")
+
+ def get(uri: str):
+ return json.loads(
+ machine.succeed(
+ "curl -sSfL "
+ "-H 'Accept: application/json' "
+ f"-H 'Authorization: Token {auth_token}' "
+ f"'http://localhost/api{uri}'"
+ )
+ )
+
+ def delete(uri: str):
+ return machine.succeed(
+ "curl -sSfL "
+ f"-X DELETE "
+ "-H 'Accept: application/json' "
+ f"-H 'Authorization: Token {auth_token}' "
+ f"'http://localhost/api{uri}'"
+ )
+
+
+ def data_request(uri: str, method: str, data: Dict[str, Any]):
+ encoded_data = json.dumps(data)
+ return json.loads(
+ machine.succeed(
+ "curl -sSfL "
+ f"-X {method} "
+ "-H 'Accept: application/json' "
+ "-H 'Content-Type: application/json' "
+ f"-H 'Authorization: Token {auth_token}' "
+ f"'http://localhost/api{uri}' "
+ f"--data '{encoded_data}'"
+ )
+ )
+
+ def post(uri: str, data: Dict[str, Any]):
+ return data_request(uri, "POST", data)
+
+ def patch(uri: str, data: Dict[str, Any]):
+ return data_request(uri, "PATCH", data)
+
+ with subtest("Can create objects"):
+ result = post("/dcim/sites/", {"name": "Test site", "slug": "test-site"})
+ site_id = result["id"]
+
+ # Example from:
+ # http://netbox.extra.cea.fr/static/docs/integrations/rest-api/#creating-a-new-object
+ post("/ipam/prefixes/", {"prefix": "192.0.2.0/24", "site": site_id})
+
+ result = post(
+ "/dcim/manufacturers/",
+ {"name": "Test manufacturer", "slug": "test-manufacturer"}
+ )
+ manufacturer_id = result["id"]
+
+ # Had an issue with device-types before NetBox 3.4.0
+ result = post(
+ "/dcim/device-types/",
+ {
+ "model": "Test device type",
+ "manufacturer": manufacturer_id,
+ "slug": "test-device-type",
+ },
+ )
+ device_type_id = result["id"]
+
+ with subtest("Can list objects"):
+ result = get("/dcim/sites/")
+
+ assert result["count"] == 1
+ assert result["results"][0]["id"] == site_id
+ assert result["results"][0]["name"] == "Test site"
+ assert result["results"][0]["description"] == ""
+
+ result = get("/dcim/device-types/")
+ assert result["count"] == 1
+ assert result["results"][0]["id"] == device_type_id
+ assert result["results"][0]["model"] == "Test device type"
+
+ with subtest("Can update objects"):
+ new_description = "Test site description"
+ patch(f"/dcim/sites/{site_id}/", {"description": new_description})
+ result = get(f"/dcim/sites/{site_id}/")
+ assert result["description"] == new_description
+
+ with subtest("Can delete objects"):
+ # Delete a device-type since no object depends on it
+ delete(f"/dcim/device-types/{device_type_id}/")
+
+ result = get("/dcim/device-types/")
+ assert result["count"] == 0
+
+ with subtest("Can use the GraphQL API"):
+ encoded_data = json.dumps({
+ "query": "query { prefix_list { prefix, site { id, description } } }",
+ })
+ result = json.loads(
+ machine.succeed(
+ "curl -sSfL "
+ "-H 'Accept: application/json' "
+ "-H 'Content-Type: application/json' "
+ f"-H 'Authorization: Token {auth_token}' "
+ "'http://localhost/graphql/' "
+ f"--data '{encoded_data}'"
+ )
+ )
+
+ assert len(result["data"]["prefix_list"]) == 1
+ assert result["data"]["prefix_list"][0]["prefix"] == "192.0.2.0/24"
+ assert result["data"]["prefix_list"][0]["site"]["id"] == str(site_id)
+ assert result["data"]["prefix_list"][0]["site"]["description"] == new_description
+
+ with subtest("Can login with LDAP"):
+ machine.wait_for_unit("openldap.service")
+ login("alice", "${testPassword}")
+
+ with subtest("Can associate LDAP groups"):
+ result = get("/users/users/?username=${testUser}")
+
+ assert result["count"] == 1
+ assert any(group["name"] == "${testGroup}" for group in result["results"][0]["groups"])
'';
})
diff --git a/pkgs/pkgs-lib/formats.nix b/pkgs/pkgs-lib/formats.nix
index 13aada3681f7..3a47d3dc849c 100644
--- a/pkgs/pkgs-lib/formats.nix
+++ b/pkgs/pkgs-lib/formats.nix
@@ -417,4 +417,39 @@ rec {
'';
};
+ # Outputs a succession of Python variable assignments
+ # Useful for many Django-based services
+ pythonVars = {}: {
+ type = with lib.types; let
+ valueType = nullOr(oneOf [
+ bool
+ float
+ int
+ path
+ str
+ (attrsOf valueType)
+ (listOf valueType)
+ ]) // {
+ description = "Python value";
+ };
+ in attrsOf valueType;
+ generate = name: value: pkgs.callPackage ({ runCommand, python3, black }: runCommand name {
+ nativeBuildInputs = [ python3 black ];
+ value = builtins.toJSON value;
+ pythonGen = ''
+ import json
+ import os
+
+ with open(os.environ["valuePath"], "r") as f:
+ for key, value in json.load(f).items():
+ print(f"{key} = {repr(value)}")
+ '';
+ passAsFile = [ "value" "pythonGen" ];
+ } ''
+ cat "$valuePath"
+ python3 "$pythonGenPath" > $out
+ black $out
+ '') {};
+ };
+
}
diff --git a/pkgs/servers/web-apps/netbox/config.patch b/pkgs/servers/web-apps/netbox/config.patch
index 1adc0b537a4e..a2e0b0b95a87 100644
--- a/pkgs/servers/web-apps/netbox/config.patch
+++ b/pkgs/servers/web-apps/netbox/config.patch
@@ -1,30 +1,30 @@
diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py
-index d5a7bfaec..68754a8c5 100644
+index 2de06dd10..00406af48 100644
--- a/netbox/netbox/settings.py
+++ b/netbox/netbox/settings.py
-@@ -222,6 +222,7 @@ TASKS_REDIS_PASSWORD = TASKS_REDIS.get('PASSWORD', '')
- TASKS_REDIS_DATABASE = TASKS_REDIS.get('DATABASE', 0)
+@@ -236,6 +236,7 @@ TASKS_REDIS_DATABASE = TASKS_REDIS.get('DATABASE', 0)
TASKS_REDIS_SSL = TASKS_REDIS.get('SSL', False)
TASKS_REDIS_SKIP_TLS_VERIFY = TASKS_REDIS.get('INSECURE_SKIP_TLS_VERIFY', False)
+ TASKS_REDIS_CA_CERT_PATH = TASKS_REDIS.get('CA_CERT_PATH', False)
+TASKS_REDIS_URL = TASKS_REDIS.get('URL')
# Caching
if 'caching' not in REDIS:
-@@ -236,11 +237,12 @@ CACHING_REDIS_SENTINELS = REDIS['caching'].get('SENTINELS', [])
- CACHING_REDIS_SENTINEL_SERVICE = REDIS['caching'].get('SENTINEL_SERVICE', 'default')
+@@ -253,11 +254,12 @@ CACHING_REDIS_SENTINEL_SERVICE = REDIS['caching'].get('SENTINEL_SERVICE', 'defau
CACHING_REDIS_PROTO = 'rediss' if REDIS['caching'].get('SSL', False) else 'redis'
CACHING_REDIS_SKIP_TLS_VERIFY = REDIS['caching'].get('INSECURE_SKIP_TLS_VERIFY', False)
+ CACHING_REDIS_CA_CERT_PATH = REDIS['caching'].get('CA_CERT_PATH', False)
+CACHING_REDIS_URL = REDIS['caching'].get('URL', f'{CACHING_REDIS_PROTO}://{CACHING_REDIS_HOST}:{CACHING_REDIS_PORT}/{CACHING_REDIS_DATABASE}')
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
-- 'LOCATION': f'{CACHING_REDIS_PROTO}://{CACHING_REDIS_HOST}:{CACHING_REDIS_PORT}/{CACHING_REDIS_DATABASE}',
+- 'LOCATION': f'{CACHING_REDIS_PROTO}://{CACHING_REDIS_USERNAME_HOST}:{CACHING_REDIS_PORT}/{CACHING_REDIS_DATABASE}',
+ 'LOCATION': CACHING_REDIS_URL,
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
'PASSWORD': CACHING_REDIS_PASSWORD,
-@@ -383,7 +385,7 @@ USE_X_FORWARDED_HOST = True
+@@ -410,7 +412,7 @@ USE_X_FORWARDED_HOST = True
X_FRAME_OPTIONS = 'SAMEORIGIN'
# Static files (CSS, JavaScript, Images)
@@ -33,7 +33,7 @@ index d5a7bfaec..68754a8c5 100644
STATIC_URL = f'/{BASE_PATH}static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'project-static', 'dist'),
-@@ -562,6 +564,14 @@ if TASKS_REDIS_USING_SENTINEL:
+@@ -640,6 +642,14 @@ if TASKS_REDIS_USING_SENTINEL:
'socket_connect_timeout': TASKS_REDIS_SENTINEL_TIMEOUT
},
}
diff --git a/pkgs/servers/web-apps/netbox/config_3_3.patch b/pkgs/servers/web-apps/netbox/config_3_3.patch
new file mode 100644
index 000000000000..1adc0b537a4e
--- /dev/null
+++ b/pkgs/servers/web-apps/netbox/config_3_3.patch
@@ -0,0 +1,50 @@
+diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py
+index d5a7bfaec..68754a8c5 100644
+--- a/netbox/netbox/settings.py
++++ b/netbox/netbox/settings.py
+@@ -222,6 +222,7 @@ TASKS_REDIS_PASSWORD = TASKS_REDIS.get('PASSWORD', '')
+ TASKS_REDIS_DATABASE = TASKS_REDIS.get('DATABASE', 0)
+ TASKS_REDIS_SSL = TASKS_REDIS.get('SSL', False)
+ TASKS_REDIS_SKIP_TLS_VERIFY = TASKS_REDIS.get('INSECURE_SKIP_TLS_VERIFY', False)
++TASKS_REDIS_URL = TASKS_REDIS.get('URL')
+
+ # Caching
+ if 'caching' not in REDIS:
+@@ -236,11 +237,12 @@ CACHING_REDIS_SENTINELS = REDIS['caching'].get('SENTINELS', [])
+ CACHING_REDIS_SENTINEL_SERVICE = REDIS['caching'].get('SENTINEL_SERVICE', 'default')
+ CACHING_REDIS_PROTO = 'rediss' if REDIS['caching'].get('SSL', False) else 'redis'
+ CACHING_REDIS_SKIP_TLS_VERIFY = REDIS['caching'].get('INSECURE_SKIP_TLS_VERIFY', False)
++CACHING_REDIS_URL = REDIS['caching'].get('URL', f'{CACHING_REDIS_PROTO}://{CACHING_REDIS_HOST}:{CACHING_REDIS_PORT}/{CACHING_REDIS_DATABASE}')
+
+ CACHES = {
+ 'default': {
+ 'BACKEND': 'django_redis.cache.RedisCache',
+- 'LOCATION': f'{CACHING_REDIS_PROTO}://{CACHING_REDIS_HOST}:{CACHING_REDIS_PORT}/{CACHING_REDIS_DATABASE}',
++ 'LOCATION': CACHING_REDIS_URL,
+ 'OPTIONS': {
+ 'CLIENT_CLASS': 'django_redis.client.DefaultClient',
+ 'PASSWORD': CACHING_REDIS_PASSWORD,
+@@ -383,7 +385,7 @@ USE_X_FORWARDED_HOST = True
+ X_FRAME_OPTIONS = 'SAMEORIGIN'
+
+ # Static files (CSS, JavaScript, Images)
+-STATIC_ROOT = BASE_DIR + '/static'
++STATIC_ROOT = getattr(configuration, 'STATIC_ROOT', os.path.join(BASE_DIR, 'static')).rstrip('/')
+ STATIC_URL = f'/{BASE_PATH}static/'
+ STATICFILES_DIRS = (
+ os.path.join(BASE_DIR, 'project-static', 'dist'),
+@@ -562,6 +564,14 @@ if TASKS_REDIS_USING_SENTINEL:
+ 'socket_connect_timeout': TASKS_REDIS_SENTINEL_TIMEOUT
+ },
+ }
++elif TASKS_REDIS_URL:
++ RQ_PARAMS = {
++ 'URL': TASKS_REDIS_URL,
++ 'PASSWORD': TASKS_REDIS_PASSWORD,
++ 'SSL': TASKS_REDIS_SSL,
++ 'SSL_CERT_REQS': None if TASKS_REDIS_SKIP_TLS_VERIFY else 'required',
++ 'DEFAULT_TIMEOUT': RQ_DEFAULT_TIMEOUT,
++ }
+ else:
+ RQ_PARAMS = {
+ 'HOST': TASKS_REDIS_HOST,
diff --git a/pkgs/servers/web-apps/netbox/default.nix b/pkgs/servers/web-apps/netbox/default.nix
index aeeb57fa3792..fb9e8a6aa57f 100644
--- a/pkgs/servers/web-apps/netbox/default.nix
+++ b/pkgs/servers/web-apps/netbox/default.nix
@@ -1,37 +1,14 @@
-{ lib
-, pkgs
-, fetchFromGitHub
-, fetchpatch
-, nixosTests
-, python3
-
-, plugins ? ps: [] }:
-
+{ lib, nixosTests, callPackage, fetchpatch }:
let
- py = python3 // {
- pkgs = python3.pkgs.overrideScope (self: super: {
- django = super.django_4;
- });
- };
-
- extraBuildInputs = plugins py.pkgs;
+ generic = import ./generic.nix;
in
-py.pkgs.buildPythonApplication rec {
- pname = "netbox";
- version = "3.3.9";
-
- format = "other";
-
- src = fetchFromGitHub {
- owner = "netbox-community";
- repo = pname;
- rev = "refs/tags/v${version}";
- sha256 = "sha256-KhnxD5pjlEIgISl4RMbhLCDwgUDfGFRi88ZcP1ndMhI=";
- };
-
- patches = [
+{
+ netbox_3_3 = callPackage generic {
+ version = "3.3.10";
+ hash = "sha256-MeOfTU5IxNDoUh7FyvwAQNRC/CE0R6p40WnlF+3RuxA=";
+ extraPatches = [
# Allow setting the STATIC_ROOT from within the configuration and setting a custom redis URL
- ./config.patch
+ ./config_3_3.patch
./graphql-3_2_0.patch
# fix compatibility ith django 4.1
(fetchpatch {
@@ -40,77 +17,22 @@ py.pkgs.buildPythonApplication rec {
})
];
- propagatedBuildInputs = with py.pkgs; [
- bleach
- django_4
- django-cors-headers
- django-debug-toolbar
- django-filter
- django-graphiql-debug-toolbar
- django-mptt
- django-pglocks
- django-prometheus
- django-redis
- django-rq
- django-tables2
- django-taggit
- django-timezone-field
- djangorestframework
- drf-yasg
- swagger-spec-validator # from drf-yasg[validation]
- graphene-django
- jinja2
- markdown
- markdown-include
- netaddr
- pillow
- psycopg2
- pyyaml
- sentry-sdk
- social-auth-core
- social-auth-app-django
- svgwrite
- tablib
- jsonschema
- ] ++ extraBuildInputs;
+ tests.netbox = nixosTests.netbox_3_3;
+ maintainers = with lib.maintainers; [ n0emis raitobezarius ];
+ eol = true;
+ };
- buildInputs = with py.pkgs; [
- mkdocs-material
- mkdocs-material-extensions
- mkdocstrings
- mkdocstrings-python
+ netbox = callPackage generic {
+ version = "3.4.7";
+ hash = "sha256-pWHGyzLc0tqfehWbCMF1l96L1pewb5FXBUkw9EqPtP8=";
+ extraPatches = [
+ # Allow setting the STATIC_ROOT from within the configuration and setting a custom redis URL
+ ./config.patch
];
-
- nativeBuildInputs = [
- py.pkgs.mkdocs
- ];
-
- postBuild = ''
- PYTHONPATH=$PYTHONPATH:netbox/
- python -m mkdocs build
- '';
-
- installPhase = ''
- mkdir -p $out/opt/netbox
- cp -r . $out/opt/netbox
- chmod +x $out/opt/netbox/netbox/manage.py
- makeWrapper $out/opt/netbox/netbox/manage.py $out/bin/netbox \
- --prefix PYTHONPATH : "$PYTHONPATH"
- '';
-
- passthru = {
- # PYTHONPATH of all dependencies used by the package
- pythonPath = python3.pkgs.makePythonPath propagatedBuildInputs;
-
- tests = {
- inherit (nixosTests) netbox;
- };
+ tests = {
+ inherit (nixosTests) netbox;
};
- meta = with lib; {
- homepage = "https://github.com/netbox-community/netbox";
- description = "IP address management (IPAM) and data center infrastructure management (DCIM) tool";
- license = licenses.asl20;
- maintainers = with maintainers; [ n0emis raitobezarius ];
- };
- }
+ maintainers = with lib.maintainers; [ minijackson n0emis raitobezarius ];
+ };
+}
diff --git a/pkgs/servers/web-apps/netbox/generic.nix b/pkgs/servers/web-apps/netbox/generic.nix
new file mode 100644
index 000000000000..ace3e4f011f0
--- /dev/null
+++ b/pkgs/servers/web-apps/netbox/generic.nix
@@ -0,0 +1,110 @@
+{ lib
+, fetchFromGitHub
+, python3
+, version
+, hash
+, plugins ? ps: []
+, extraPatches ? []
+, tests ? {}
+, maintainers ? []
+, eol ? false
+}:
+ let
+ py = python3 // {
+ pkgs = python3.pkgs.overrideScope (self: super: {
+ django = super.django_4;
+ });
+ };
+
+ extraBuildInputs = plugins py.pkgs;
+ in
+ py.pkgs.buildPythonApplication rec {
+ pname = "netbox";
+ inherit version;
+
+ format = "other";
+
+ src = fetchFromGitHub {
+ owner = "netbox-community";
+ repo = pname;
+ rev = "refs/tags/v${version}";
+ inherit hash;
+ };
+
+ patches = extraPatches;
+
+ propagatedBuildInputs = with py.pkgs; [
+ bleach
+ django_4
+ django-cors-headers
+ django-debug-toolbar
+ django-filter
+ django-graphiql-debug-toolbar
+ django-mptt
+ django-pglocks
+ django-prometheus
+ django-redis
+ django-rq
+ django-tables2
+ django-taggit
+ django-timezone-field
+ djangorestframework
+ drf-yasg
+ swagger-spec-validator # from drf-yasg[validation]
+ graphene-django
+ jinja2
+ markdown
+ markdown-include
+ netaddr
+ pillow
+ psycopg2
+ pyyaml
+ sentry-sdk
+ social-auth-core
+ social-auth-app-django
+ svgwrite
+ tablib
+ jsonschema
+ ] ++ extraBuildInputs;
+
+ buildInputs = with py.pkgs; [
+ mkdocs-material
+ mkdocs-material-extensions
+ mkdocstrings
+ mkdocstrings-python
+ ];
+
+ nativeBuildInputs = [
+ py.pkgs.mkdocs
+ ];
+
+ postBuild = ''
+ PYTHONPATH=$PYTHONPATH:netbox/
+ python -m mkdocs build
+ '';
+
+ installPhase = ''
+ mkdir -p $out/opt/netbox
+ cp -r . $out/opt/netbox
+ chmod +x $out/opt/netbox/netbox/manage.py
+ makeWrapper $out/opt/netbox/netbox/manage.py $out/bin/netbox \
+ --prefix PYTHONPATH : "$PYTHONPATH"
+ '';
+
+ passthru = {
+ # PYTHONPATH of all dependencies used by the package
+ pythonPath = python3.pkgs.makePythonPath propagatedBuildInputs;
+ inherit tests;
+ };
+
+ meta = {
+ homepage = "https://github.com/netbox-community/netbox";
+ description = "IP address management (IPAM) and data center infrastructure management (DCIM) tool";
+ license = lib.licenses.asl20;
+ knownVulnerabilities = (lib.optional eol "Netbox version ${version} is EOL; please upgrade by following the current release notes instructions.");
+ # Warning:
+ # Notice the missing `lib` in the inherit: it is using this function argument rather than a `with lib;` argument.
+ # If you replace this by `with lib;`, pay attention it does not inherit all maintainers in nixpkgs.
+ inherit maintainers;
+ };
+ }
diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix
index 2618128907d8..6ee29af177b0 100644
--- a/pkgs/top-level/all-packages.nix
+++ b/pkgs/top-level/all-packages.nix
@@ -10170,7 +10170,8 @@ with pkgs;
netbootxyz-efi = callPackage ../tools/misc/netbootxyz-efi { };
- netbox = callPackage ../servers/web-apps/netbox { };
+ inherit (callPackage ../servers/web-apps/netbox { })
+ netbox_3_3 netbox;
netcat = libressl.nc;