at 24.11-pre 12 kB view raw
1{ config, lib, pkgs, ... }: 2 3let 4 cfg = config.services.netbox; 5 pythonFmt = pkgs.formats.pythonVars {}; 6 staticDir = cfg.dataDir + "/static"; 7 8 settingsFile = pythonFmt.generate "netbox-settings.py" cfg.settings; 9 extraConfigFile = pkgs.writeTextFile { 10 name = "netbox-extraConfig.py"; 11 text = cfg.extraConfig; 12 }; 13 configFile = pkgs.concatText "configuration.py" [ settingsFile extraConfigFile ]; 14 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 20 ''; 21 })).override { 22 inherit (cfg) plugins; 23 }; 24 netboxManageScript = with pkgs; (writeScriptBin "netbox-manage" '' 25 #!${stdenv.shell} 26 export PYTHONPATH=${pkg.pythonPath} 27 sudo -u netbox ${pkg}/bin/netbox "$@" 28 ''); 29 30in { 31 options.services.netbox = { 32 enable = lib.mkOption { 33 type = lib.types.bool; 34 default = false; 35 description = '' 36 Enable Netbox. 37 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. 40 ''; 41 }; 42 43 settings = lib.mkOption { 44 description = '' 45 Configuration options to set in `configuration.py`. 46 See the [documentation](https://docs.netbox.dev/en/stable/configuration/) for more possible options. 47 ''; 48 49 default = { }; 50 51 type = lib.types.submodule { 52 freeformType = pythonFmt.type; 53 54 options = { 55 ALLOWED_HOSTS = lib.mkOption { 56 type = with lib.types; listOf str; 57 default = ["*"]; 58 description = '' 59 A list of valid fully-qualified domain names (FQDNs) and/or IP 60 addresses that can be used to reach the NetBox service. 61 ''; 62 }; 63 }; 64 }; 65 }; 66 67 listenAddress = lib.mkOption { 68 type = lib.types.str; 69 default = "[::1]"; 70 description = '' 71 Address the server will listen on. 72 ''; 73 }; 74 75 package = lib.mkOption { 76 type = lib.types.package; 77 default = 78 if lib.versionAtLeast config.system.stateVersion "24.05" 79 then pkgs.netbox_3_7 80 else if lib.versionAtLeast config.system.stateVersion "23.11" 81 then pkgs.netbox_3_6 82 else if lib.versionAtLeast config.system.stateVersion "23.05" 83 then pkgs.netbox_3_5 84 else pkgs.netbox_3_3; 85 defaultText = lib.literalExpression '' 86 if lib.versionAtLeast config.system.stateVersion "24.05" 87 then pkgs.netbox_3_7 88 else if lib.versionAtLeast config.system.stateVersion "23.11" 89 then pkgs.netbox_3_6 90 else if lib.versionAtLeast config.system.stateVersion "23.05" 91 then pkgs.netbox_3_5 92 else pkgs.netbox_3_3; 93 ''; 94 description = '' 95 NetBox package to use. 96 ''; 97 }; 98 99 port = lib.mkOption { 100 type = lib.types.port; 101 default = 8001; 102 description = '' 103 Port the server will listen on. 104 ''; 105 }; 106 107 plugins = lib.mkOption { 108 type = with lib.types; functionTo (listOf package); 109 default = _: []; 110 defaultText = lib.literalExpression '' 111 python3Packages: with python3Packages; []; 112 ''; 113 description = '' 114 List of plugin packages to install. 115 ''; 116 }; 117 118 dataDir = lib.mkOption { 119 type = lib.types.str; 120 default = "/var/lib/netbox"; 121 description = '' 122 Storage path of netbox. 123 ''; 124 }; 125 126 secretKeyFile = lib.mkOption { 127 type = lib.types.path; 128 description = '' 129 Path to a file containing the secret key. 130 ''; 131 }; 132 133 extraConfig = lib.mkOption { 134 type = lib.types.lines; 135 default = ""; 136 description = '' 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. 139 ''; 140 }; 141 142 enableLdap = lib.mkOption { 143 type = lib.types.bool; 144 default = false; 145 description = '' 146 Enable LDAP-Authentication for Netbox. 147 148 This requires a configuration file being pass through `ldapConfigPath`. 149 ''; 150 }; 151 152 ldapConfigPath = lib.mkOption { 153 type = lib.types.path; 154 default = ""; 155 description = '' 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. 158 ''; 159 example = '' 160 import ldap 161 from django_auth_ldap.config import LDAPSearch, PosixGroupType 162 163 AUTH_LDAP_SERVER_URI = "ldaps://ldap.example.com/" 164 165 AUTH_LDAP_USER_SEARCH = LDAPSearch( 166 "ou=accounts,ou=posix,dc=example,dc=com", 167 ldap.SCOPE_SUBTREE, 168 "(uid=%(user)s)", 169 ) 170 171 AUTH_LDAP_GROUP_SEARCH = LDAPSearch( 172 "ou=groups,ou=posix,dc=example,dc=com", 173 ldap.SCOPE_SUBTREE, 174 "(objectClass=posixGroup)", 175 ) 176 AUTH_LDAP_GROUP_TYPE = PosixGroupType() 177 178 # Mirror LDAP group assignments. 179 AUTH_LDAP_MIRROR_GROUPS = True 180 181 # For more granular permissions, we can map LDAP groups to Django groups. 182 AUTH_LDAP_FIND_GROUP_PERMS = True 183 ''; 184 }; 185 keycloakClientSecret = lib.mkOption { 186 type = with lib.types; nullOr path; 187 default = null; 188 description = '' 189 File that contains the keycloak client secret. 190 ''; 191 }; 192 }; 193 194 config = lib.mkIf cfg.enable { 195 services.netbox = { 196 plugins = lib.mkIf cfg.enableLdap (ps: [ ps.django-auth-ldap ]); 197 settings = { 198 STATIC_ROOT = staticDir; 199 MEDIA_ROOT = "${cfg.dataDir}/media"; 200 REPORTS_ROOT = "${cfg.dataDir}/reports"; 201 SCRIPTS_ROOT = "${cfg.dataDir}/scripts"; 202 203 GIT_PATH = "${pkgs.gitMinimal}/bin/git"; 204 205 DATABASE = { 206 NAME = "netbox"; 207 USER = "netbox"; 208 HOST = "/run/postgresql"; 209 }; 210 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 215 # IDs. 216 REDIS = { 217 tasks = { 218 URL = "unix://${config.services.redis.servers.netbox.unixSocket}?db=0"; 219 SSL = false; 220 }; 221 caching = { 222 URL = "unix://${config.services.redis.servers.netbox.unixSocket}?db=1"; 223 SSL = false; 224 }; 225 }; 226 227 REMOTE_AUTH_BACKEND = lib.mkIf cfg.enableLdap "netbox.authentication.LDAPBackend"; 228 229 LOGGING = lib.mkDefault { 230 version = 1; 231 232 formatters.precise.format = "[%(levelname)s@%(name)s] %(message)s"; 233 234 handlers.console = { 235 class = "logging.StreamHandler"; 236 formatter = "precise"; 237 }; 238 239 # log to console/systemd instead of file 240 root = { 241 level = "INFO"; 242 handlers = [ "console" ]; 243 }; 244 }; 245 }; 246 247 extraConfig = '' 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() 253 ''); 254 }; 255 256 services.redis.servers.netbox.enable = true; 257 258 services.postgresql = { 259 enable = true; 260 ensureDatabases = [ "netbox" ]; 261 ensureUsers = [ 262 { 263 name = "netbox"; 264 ensureDBOwnership = true; 265 } 266 ]; 267 }; 268 269 environment.systemPackages = [ netboxManageScript ]; 270 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" ]; 276 }; 277 278 systemd.services = let 279 defaultServiceConfig = { 280 WorkingDirectory = "${cfg.dataDir}"; 281 User = "netbox"; 282 Group = "netbox"; 283 StateDirectory = "netbox"; 284 StateDirectoryMode = "0750"; 285 Restart = "on-failure"; 286 RestartSec = 30; 287 }; 288 in { 289 netbox = { 290 description = "NetBox WSGI Service"; 291 documentation = [ "https://docs.netbox.dev/" ]; 292 293 wantedBy = [ "netbox.target" ]; 294 295 after = [ "network-online.target" ]; 296 wants = [ "network-online.target" ]; 297 298 environment.PYTHONPATH = pkg.pythonPath; 299 300 preStart = '' 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" 304 305 if [[ -e "$versionFile" && "$(cat "$versionFile")" == "${cfg.package.version}" ]]; then 306 exit 0 307 fi 308 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 315 ${lib.optionalString 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"} 320 321 echo "${cfg.package.version}" > "$versionFile" 322 ''; 323 324 serviceConfig = defaultServiceConfig // { 325 ExecStart = '' 326 ${pkg.gunicorn}/bin/gunicorn netbox.wsgi \ 327 --bind ${cfg.listenAddress}:${toString cfg.port} \ 328 --pythonpath ${pkg}/opt/netbox/netbox 329 ''; 330 PrivateTmp = true; 331 }; 332 }; 333 334 netbox-rq = { 335 description = "NetBox Request Queue Worker"; 336 documentation = [ "https://docs.netbox.dev/" ]; 337 338 wantedBy = [ "netbox.target" ]; 339 after = [ "netbox.service" ]; 340 341 environment.PYTHONPATH = pkg.pythonPath; 342 343 serviceConfig = defaultServiceConfig // { 344 ExecStart = '' 345 ${pkg}/bin/netbox rqworker high default low 346 ''; 347 PrivateTmp = true; 348 }; 349 }; 350 351 netbox-housekeeping = { 352 description = "NetBox housekeeping job"; 353 documentation = [ "https://docs.netbox.dev/" ]; 354 355 wantedBy = [ "multi-user.target" ]; 356 357 after = [ "network-online.target" "netbox.service" ]; 358 wants = [ "network-online.target" ]; 359 360 environment.PYTHONPATH = pkg.pythonPath; 361 362 serviceConfig = defaultServiceConfig // { 363 Type = "oneshot"; 364 ExecStart = '' 365 ${pkg}/bin/netbox housekeeping 366 ''; 367 }; 368 }; 369 }; 370 371 systemd.timers.netbox-housekeeping = { 372 description = "Run NetBox housekeeping job"; 373 documentation = [ "https://docs.netbox.dev/" ]; 374 375 wantedBy = [ "multi-user.target" ]; 376 377 after = [ "network-online.target" "netbox.service" ]; 378 wants = [ "network-online.target" ]; 379 380 timerConfig = { 381 OnCalendar = "daily"; 382 AccuracySec = "1h"; 383 Persistent = true; 384 }; 385 }; 386 387 users.users.netbox = { 388 home = "${cfg.dataDir}"; 389 isSystemUser = true; 390 group = "netbox"; 391 }; 392 users.groups.netbox = {}; 393 users.groups."${config.services.redis.servers.netbox.user}".members = [ "netbox" ]; 394 }; 395}