at 25.11-pre 7.7 kB view raw
1{ 2 config, 3 options, 4 lib, 5 pkgs, 6 utils, 7 ... 8}: 9let 10 cfg = config.services.unifi; 11 stateDir = "/var/lib/unifi"; 12 cmd = lib.escapeShellArgs ( 13 [ 14 "@${cfg.jrePackage}/bin/java" 15 "java" 16 "--add-opens=java.base/java.lang=ALL-UNNAMED" 17 "--add-opens=java.base/java.time=ALL-UNNAMED" 18 "--add-opens=java.base/sun.security.util=ALL-UNNAMED" 19 "--add-opens=java.base/java.io=ALL-UNNAMED" 20 "--add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED" 21 ] 22 ++ (lib.optional (cfg.initialJavaHeapSize != null) "-Xms${(toString cfg.initialJavaHeapSize)}m") 23 ++ (lib.optional (cfg.maximumJavaHeapSize != null) "-Xmx${(toString cfg.maximumJavaHeapSize)}m") 24 ++ cfg.extraJvmOptions 25 ++ [ 26 "-jar" 27 "${stateDir}/lib/ace.jar" 28 ] 29 ); 30in 31{ 32 33 options = { 34 35 services.unifi.enable = lib.mkOption { 36 type = lib.types.bool; 37 default = false; 38 description = '' 39 Whether or not to enable the unifi controller service. 40 ''; 41 }; 42 43 services.unifi.jrePackage = lib.mkPackageOption pkgs "jdk" { 44 default = "jdk17_headless"; 45 extraDescription = '' 46 Check the UniFi controller release notes to ensure it is supported. 47 ''; 48 }; 49 50 services.unifi.unifiPackage = lib.mkPackageOption pkgs "unifi" { }; 51 52 services.unifi.mongodbPackage = lib.mkPackageOption pkgs "mongodb" { 53 default = "mongodb-7_0"; 54 }; 55 56 services.unifi.openFirewall = lib.mkOption { 57 type = lib.types.bool; 58 default = false; 59 description = '' 60 Whether or not to open the minimum required ports on the firewall. 61 62 This is necessary to allow firmware upgrades and device discovery to 63 work. For remote login, you should additionally open (or forward) port 64 8443. 65 ''; 66 }; 67 68 services.unifi.initialJavaHeapSize = lib.mkOption { 69 type = with lib.types; nullOr int; 70 default = null; 71 example = 1024; 72 description = '' 73 Set the initial heap size for the JVM in MB. If this option isn't set, the 74 JVM will decide this value at runtime. 75 ''; 76 }; 77 78 services.unifi.maximumJavaHeapSize = lib.mkOption { 79 type = with lib.types; nullOr int; 80 default = null; 81 example = 4096; 82 description = '' 83 Set the maximum heap size for the JVM in MB. If this option isn't set, the 84 JVM will decide this value at runtime. 85 ''; 86 }; 87 88 services.unifi.extraJvmOptions = lib.mkOption { 89 type = with lib.types; listOf str; 90 default = [ ]; 91 example = lib.literalExpression ''["-Xlog:gc"]''; 92 description = '' 93 Set extra options to pass to the JVM. 94 ''; 95 }; 96 97 }; 98 99 config = lib.mkIf cfg.enable { 100 101 assertions = [ 102 { 103 assertion = 104 lib.versionAtLeast config.system.stateVersion "24.11" 105 || ( 106 options.services.unifi.unifiPackage.highestPrio < (lib.mkOptionDefault { }).priority 107 && options.services.unifi.mongodbPackage.highestPrio < (lib.mkOptionDefault { }).priority 108 ); 109 message = '' 110 Support for UniFi < 8 has been dropped; please explicitly set 111 `services.unifi.unifiPackage` and `services.unifi.mongodbPackage`. 112 113 Note that the previous default MongoDB version was 5.0 and MongoDB 114 only supports migrating one major version at a time; therefore, you 115 may wish to set `services.unifi.mongodbPackage = pkgs.mongodb-6_0;` 116 and activate your configuration before upgrading again to the default 117 `mongodb-7_0` supported by `unifi`. 118 119 For more information, see the MongoDB upgrade notes: 120 <https://www.mongodb.com/docs/manual/release-notes/7.0-upgrade-standalone/#upgrade-recommendations-and-checklists> 121 ''; 122 } 123 ]; 124 125 users.users.unifi = { 126 isSystemUser = true; 127 group = "unifi"; 128 description = "UniFi controller daemon user"; 129 home = "${stateDir}"; 130 }; 131 users.groups.unifi = { }; 132 133 networking.firewall = lib.mkIf cfg.openFirewall { 134 # https://help.ubnt.com/hc/en-us/articles/218506997 135 allowedTCPPorts = [ 136 8080 # Port for UAP to inform controller. 137 8880 # Port for HTTP portal redirect, if guest portal is enabled. 138 8843 # Port for HTTPS portal redirect, ditto. 139 6789 # Port for UniFi mobile speed test. 140 ]; 141 allowedUDPPorts = [ 142 3478 # UDP port used for STUN. 143 10001 # UDP port used for device discovery. 144 ]; 145 }; 146 147 systemd.services.unifi = { 148 description = "UniFi controller daemon"; 149 wantedBy = [ "multi-user.target" ]; 150 after = [ "network.target" ]; 151 152 # This a HACK to fix missing dependencies of dynamic libs extracted from jars 153 environment.LD_LIBRARY_PATH = with pkgs.stdenv; "${cc.cc.lib}/lib"; 154 # Make sure package upgrades trigger a service restart 155 restartTriggers = [ 156 cfg.unifiPackage 157 cfg.mongodbPackage 158 ]; 159 160 serviceConfig = { 161 Type = "notify"; 162 ExecStart = "${cmd} start"; 163 ExecStop = "${cmd} stop"; 164 Restart = "always"; 165 TimeoutSec = "5min"; 166 User = "unifi"; 167 UMask = "0077"; 168 WorkingDirectory = "${stateDir}"; 169 # the stop command exits while the main process is still running, and unifi 170 # wants to manage its own child processes. this means we have to set KillSignal 171 # to something the main process ignores, otherwise every stop will have unifi.service 172 # fail with SIGTERM status. 173 KillSignal = "SIGCONT"; 174 175 # Hardening 176 AmbientCapabilities = ""; 177 CapabilityBoundingSet = ""; 178 # ProtectClock= adds DeviceAllow=char-rtc r 179 DeviceAllow = ""; 180 DevicePolicy = "closed"; 181 LockPersonality = true; 182 NoNewPrivileges = true; 183 PrivateDevices = true; 184 PrivateMounts = true; 185 PrivateTmp = true; 186 PrivateUsers = true; 187 ProtectClock = true; 188 ProtectControlGroups = true; 189 ProtectHome = true; 190 ProtectHostname = true; 191 ProtectKernelLogs = true; 192 ProtectKernelModules = true; 193 ProtectKernelTunables = true; 194 ProtectSystem = "strict"; 195 RemoveIPC = true; 196 RestrictNamespaces = true; 197 RestrictRealtime = true; 198 RestrictSUIDSGID = true; 199 SystemCallErrorNumber = "EPERM"; 200 SystemCallFilter = [ "@system-service" ]; 201 202 StateDirectory = "unifi"; 203 RuntimeDirectory = "unifi"; 204 LogsDirectory = "unifi"; 205 CacheDirectory = "unifi"; 206 207 TemporaryFileSystem = [ 208 # required as we want to create bind mounts below 209 "${stateDir}/webapps:rw" 210 ]; 211 212 # We must create the binary directories as bind mounts instead of symlinks 213 # This is because the controller resolves all symlinks to absolute paths 214 # to be used as the working directory. 215 BindPaths = [ 216 "/var/log/unifi:${stateDir}/logs" 217 "/run/unifi:${stateDir}/run" 218 "${cfg.unifiPackage}/dl:${stateDir}/dl" 219 "${cfg.unifiPackage}/lib:${stateDir}/lib" 220 "${cfg.mongodbPackage}/bin:${stateDir}/bin" 221 "${cfg.unifiPackage}/webapps/ROOT:${stateDir}/webapps/ROOT" 222 ]; 223 224 # Needs network access 225 PrivateNetwork = false; 226 # Cannot be true due to OpenJDK 227 MemoryDenyWriteExecute = false; 228 }; 229 }; 230 231 }; 232 imports = [ 233 (lib.mkRemovedOptionModule [ 234 "services" 235 "unifi" 236 "dataDir" 237 ] "You should move contents of dataDir to /var/lib/unifi/data") 238 (lib.mkRenamedOptionModule [ "services" "unifi" "openPorts" ] [ "services" "unifi" "openFirewall" ]) 239 ]; 240}