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