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