at 25.11-pre 13 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8let 9 cfg = config.services.netbox; 10 pythonFmt = pkgs.formats.pythonVars { }; 11 staticDir = cfg.dataDir + "/static"; 12 13 settingsFile = pythonFmt.generate "netbox-settings.py" cfg.settings; 14 extraConfigFile = pkgs.writeTextFile { 15 name = "netbox-extraConfig.py"; 16 text = cfg.extraConfig; 17 }; 18 configFile = pkgs.concatText "configuration.py" [ 19 settingsFile 20 extraConfigFile 21 ]; 22 23 pkg = 24 (cfg.package.overrideAttrs (old: { 25 installPhase = 26 old.installPhase 27 + '' 28 ln -s ${configFile} $out/opt/netbox/netbox/netbox/configuration.py 29 '' 30 + lib.optionalString cfg.enableLdap '' 31 ln -s ${cfg.ldapConfigPath} $out/opt/netbox/netbox/netbox/ldap_config.py 32 ''; 33 })).override 34 { 35 inherit (cfg) plugins; 36 }; 37 netboxManageScript = 38 with pkgs; 39 (writeScriptBin "netbox-manage" '' 40 #!${stdenv.shell} 41 export PYTHONPATH=${pkg.pythonPath} 42 sudo -u netbox ${pkg}/bin/netbox "$@" 43 ''); 44 45in 46{ 47 options.services.netbox = { 48 enable = lib.mkOption { 49 type = lib.types.bool; 50 default = false; 51 description = '' 52 Enable Netbox. 53 54 This module requires a reverse proxy that serves `/static` separately. 55 See this [example](https://github.com/netbox-community/netbox/blob/develop/contrib/nginx.conf/) on how to configure this. 56 ''; 57 }; 58 59 settings = lib.mkOption { 60 description = '' 61 Configuration options to set in `configuration.py`. 62 See the [documentation](https://docs.netbox.dev/en/stable/configuration/) for more possible options. 63 ''; 64 65 default = { }; 66 67 type = lib.types.submodule { 68 freeformType = pythonFmt.type; 69 70 options = { 71 ALLOWED_HOSTS = lib.mkOption { 72 type = with lib.types; listOf str; 73 default = [ "*" ]; 74 description = '' 75 A list of valid fully-qualified domain names (FQDNs) and/or IP 76 addresses that can be used to reach the NetBox service. 77 ''; 78 }; 79 }; 80 }; 81 }; 82 83 listenAddress = lib.mkOption { 84 type = lib.types.str; 85 default = "[::1]"; 86 description = '' 87 Address the server will listen on. 88 Ignored if `unixSocket` is set. 89 ''; 90 }; 91 92 unixSocket = lib.mkOption { 93 type = lib.types.nullOr lib.types.str; 94 default = null; 95 description = '' 96 Enable Unix Socket for the server to listen on. 97 `listenAddress` and `port` will be ignored. 98 ''; 99 example = "/run/netbox/netbox.sock"; 100 }; 101 102 package = lib.mkOption { 103 type = lib.types.package; 104 default = 105 if lib.versionAtLeast config.system.stateVersion "25.05" then 106 pkgs.netbox_4_2 107 else if lib.versionAtLeast config.system.stateVersion "24.11" then 108 pkgs.netbox_4_1 109 else if lib.versionAtLeast config.system.stateVersion "24.05" then 110 pkgs.netbox_3_7 111 else 112 pkgs.netbox_3_6; 113 defaultText = lib.literalExpression '' 114 if lib.versionAtLeast config.system.stateVersion "24.11" 115 then pkgs.netbox_4_1 116 else if lib.versionAtLeast config.system.stateVersion "24.05" 117 then pkgs.netbox_3_7 118 else pkgs.netbox_3_6; 119 ''; 120 description = '' 121 NetBox package to use. 122 ''; 123 }; 124 125 port = lib.mkOption { 126 type = lib.types.port; 127 default = 8001; 128 description = '' 129 Port the server will listen on. 130 Ignored if `unixSocket` is set. 131 ''; 132 }; 133 134 plugins = lib.mkOption { 135 type = with lib.types; functionTo (listOf package); 136 default = _: [ ]; 137 defaultText = lib.literalExpression '' 138 python3Packages: with python3Packages; []; 139 ''; 140 description = '' 141 List of plugin packages to install. 142 ''; 143 }; 144 145 dataDir = lib.mkOption { 146 type = lib.types.str; 147 default = "/var/lib/netbox"; 148 description = '' 149 Storage path of netbox. 150 ''; 151 }; 152 153 secretKeyFile = lib.mkOption { 154 type = lib.types.path; 155 description = '' 156 Path to a file containing the secret key. 157 ''; 158 }; 159 160 extraConfig = lib.mkOption { 161 type = lib.types.lines; 162 default = ""; 163 description = '' 164 Additional lines of configuration appended to the `configuration.py`. 165 See the [documentation](https://docs.netbox.dev/en/stable/configuration/) for more possible options. 166 ''; 167 }; 168 169 enableLdap = lib.mkOption { 170 type = lib.types.bool; 171 default = false; 172 description = '' 173 Enable LDAP-Authentication for Netbox. 174 175 This requires a configuration file being pass through `ldapConfigPath`. 176 ''; 177 }; 178 179 ldapConfigPath = lib.mkOption { 180 type = lib.types.path; 181 default = ""; 182 description = '' 183 Path to the Configuration-File for LDAP-Authentication, will be loaded as `ldap_config.py`. 184 See the [documentation](https://netbox.readthedocs.io/en/stable/installation/6-ldap/#configuration) for possible options. 185 ''; 186 example = '' 187 import ldap 188 from django_auth_ldap.config import LDAPSearch, PosixGroupType 189 190 AUTH_LDAP_SERVER_URI = "ldaps://ldap.example.com/" 191 192 AUTH_LDAP_USER_SEARCH = LDAPSearch( 193 "ou=accounts,ou=posix,dc=example,dc=com", 194 ldap.SCOPE_SUBTREE, 195 "(uid=%(user)s)", 196 ) 197 198 AUTH_LDAP_GROUP_SEARCH = LDAPSearch( 199 "ou=groups,ou=posix,dc=example,dc=com", 200 ldap.SCOPE_SUBTREE, 201 "(objectClass=posixGroup)", 202 ) 203 AUTH_LDAP_GROUP_TYPE = PosixGroupType() 204 205 # Mirror LDAP group assignments. 206 AUTH_LDAP_MIRROR_GROUPS = True 207 208 # For more granular permissions, we can map LDAP groups to Django groups. 209 AUTH_LDAP_FIND_GROUP_PERMS = True 210 ''; 211 }; 212 keycloakClientSecret = lib.mkOption { 213 type = with lib.types; nullOr path; 214 default = null; 215 description = '' 216 File that contains the keycloak client secret. 217 ''; 218 }; 219 }; 220 221 config = lib.mkIf cfg.enable { 222 services.netbox = { 223 plugins = lib.mkIf cfg.enableLdap (ps: [ ps.django-auth-ldap ]); 224 settings = { 225 STATIC_ROOT = staticDir; 226 MEDIA_ROOT = "${cfg.dataDir}/media"; 227 REPORTS_ROOT = "${cfg.dataDir}/reports"; 228 SCRIPTS_ROOT = "${cfg.dataDir}/scripts"; 229 230 GIT_PATH = "${pkgs.gitMinimal}/bin/git"; 231 232 DATABASE = { 233 NAME = "netbox"; 234 USER = "netbox"; 235 HOST = "/run/postgresql"; 236 }; 237 238 # Redis database settings. Redis is used for caching and for queuing 239 # background tasks such as webhook events. A separate configuration 240 # exists for each. Full connection details are required in both 241 # sections, and it is strongly recommended to use two separate database 242 # IDs. 243 REDIS = { 244 tasks = { 245 URL = "unix://${config.services.redis.servers.netbox.unixSocket}?db=0"; 246 SSL = false; 247 }; 248 caching = { 249 URL = "unix://${config.services.redis.servers.netbox.unixSocket}?db=1"; 250 SSL = false; 251 }; 252 }; 253 254 REMOTE_AUTH_BACKEND = lib.mkIf cfg.enableLdap "netbox.authentication.LDAPBackend"; 255 256 LOGGING = lib.mkDefault { 257 version = 1; 258 259 formatters.precise.format = "[%(levelname)s@%(name)s] %(message)s"; 260 261 handlers.console = { 262 class = "logging.StreamHandler"; 263 formatter = "precise"; 264 }; 265 266 # log to console/systemd instead of file 267 root = { 268 level = "INFO"; 269 handlers = [ "console" ]; 270 }; 271 }; 272 }; 273 274 extraConfig = 275 '' 276 with open("${cfg.secretKeyFile}", "r") as file: 277 SECRET_KEY = file.readline() 278 '' 279 + (lib.optionalString (cfg.keycloakClientSecret != null) '' 280 with open("${cfg.keycloakClientSecret}", "r") as file: 281 SOCIAL_AUTH_KEYCLOAK_SECRET = file.readline() 282 ''); 283 }; 284 285 services.redis.servers.netbox.enable = true; 286 287 services.postgresql = { 288 enable = true; 289 ensureDatabases = [ "netbox" ]; 290 ensureUsers = [ 291 { 292 name = "netbox"; 293 ensureDBOwnership = true; 294 } 295 ]; 296 }; 297 298 environment.systemPackages = [ netboxManageScript ]; 299 300 systemd.targets.netbox = { 301 description = "Target for all NetBox services"; 302 wantedBy = [ "multi-user.target" ]; 303 wants = [ "network-online.target" ]; 304 after = [ 305 "network-online.target" 306 "redis-netbox.service" 307 ]; 308 }; 309 310 systemd.services = 311 let 312 defaultServiceConfig = { 313 WorkingDirectory = "${cfg.dataDir}"; 314 User = "netbox"; 315 Group = "netbox"; 316 StateDirectory = "netbox"; 317 StateDirectoryMode = "0750"; 318 Restart = "on-failure"; 319 RestartSec = 30; 320 }; 321 in 322 { 323 netbox = { 324 description = "NetBox WSGI Service"; 325 documentation = [ "https://docs.netbox.dev/" ]; 326 327 wantedBy = [ "netbox.target" ]; 328 329 after = [ "network-online.target" ]; 330 wants = [ "network-online.target" ]; 331 332 environment.PYTHONPATH = pkg.pythonPath; 333 334 preStart = '' 335 # On the first run, or on upgrade / downgrade, run migrations and related. 336 # This mostly correspond to upstream NetBox's 'upgrade.sh' script. 337 versionFile="${cfg.dataDir}/version" 338 339 if [[ -h "$versionFile" && "$(readlink -- "$versionFile")" == "${cfg.package}" ]]; then 340 exit 0 341 fi 342 343 ${pkg}/bin/netbox migrate 344 ${pkg}/bin/netbox trace_paths --no-input 345 ${pkg}/bin/netbox collectstatic --clear --no-input 346 ${pkg}/bin/netbox remove_stale_contenttypes --no-input 347 ${pkg}/bin/netbox reindex --lazy 348 ${pkg}/bin/netbox clearsessions 349 ${lib.optionalString 350 # The clearcache command was removed in 3.7.0: 351 # https://github.com/netbox-community/netbox/issues/14458 352 (lib.versionOlder cfg.package.version "3.7.0") 353 "${pkg}/bin/netbox clearcache" 354 } 355 356 ln -sfn "${cfg.package}" "$versionFile" 357 ''; 358 359 serviceConfig = defaultServiceConfig // { 360 ExecStart = '' 361 ${pkg.gunicorn}/bin/gunicorn netbox.wsgi \ 362 --bind ${ 363 if (cfg.unixSocket != null) then 364 "unix:${cfg.unixSocket}" 365 else 366 "${cfg.listenAddress}:${toString cfg.port}" 367 } \ 368 --pythonpath ${pkg}/opt/netbox/netbox 369 ''; 370 PrivateTmp = true; 371 TimeoutStartSec = lib.mkDefault "5min"; 372 }; 373 }; 374 375 netbox-rq = { 376 description = "NetBox Request Queue Worker"; 377 documentation = [ "https://docs.netbox.dev/" ]; 378 379 wantedBy = [ "netbox.target" ]; 380 after = [ "netbox.service" ]; 381 382 environment.PYTHONPATH = pkg.pythonPath; 383 384 serviceConfig = defaultServiceConfig // { 385 ExecStart = '' 386 ${pkg}/bin/netbox rqworker high default low 387 ''; 388 PrivateTmp = true; 389 }; 390 }; 391 392 netbox-housekeeping = { 393 description = "NetBox housekeeping job"; 394 documentation = [ "https://docs.netbox.dev/" ]; 395 396 wantedBy = [ "multi-user.target" ]; 397 398 after = [ 399 "network-online.target" 400 "netbox.service" 401 ]; 402 wants = [ "network-online.target" ]; 403 404 environment.PYTHONPATH = pkg.pythonPath; 405 406 serviceConfig = defaultServiceConfig // { 407 Type = "oneshot"; 408 ExecStart = '' 409 ${pkg}/bin/netbox housekeeping 410 ''; 411 }; 412 }; 413 }; 414 415 systemd.timers.netbox-housekeeping = { 416 description = "Run NetBox housekeeping job"; 417 documentation = [ "https://docs.netbox.dev/" ]; 418 419 wantedBy = [ "multi-user.target" ]; 420 421 after = [ 422 "network-online.target" 423 "netbox.service" 424 ]; 425 wants = [ "network-online.target" ]; 426 427 timerConfig = { 428 OnCalendar = "daily"; 429 AccuracySec = "1h"; 430 Persistent = true; 431 }; 432 }; 433 434 users.users.netbox = { 435 home = "${cfg.dataDir}"; 436 isSystemUser = true; 437 group = "netbox"; 438 }; 439 users.groups.netbox = { }; 440 users.groups."${config.services.redis.servers.netbox.user}".members = [ "netbox" ]; 441 }; 442}