1 { config, options, lib, pkgs, utils, ... }:
4 cfg = config.services.unifi;
5 stateDir = "/var/lib/unifi";
7 @${cfg.jrePackage}/bin/java java \
8 ${optionalString (cfg.initialJavaHeapSize != null) "-Xms${(toString cfg.initialJavaHeapSize)}m"} \
9 ${optionalString (cfg.maximumJavaHeapSize != null) "-Xmx${(toString cfg.maximumJavaHeapSize)}m"} \
10 -jar ${stateDir}/lib/ace.jar
17 services.unifi.enable = mkOption {
20 description = lib.mdDoc ''
21 Whether or not to enable the unifi controller service.
25 services.unifi.jrePackage = mkOption {
28 defaultText = literalExpression "pkgs.jre8";
29 description = lib.mdDoc ''
30 The JRE package to use. Check the release notes to ensure it is supported.
34 services.unifi.unifiPackage = mkOption {
36 default = pkgs.unifiLTS;
37 defaultText = literalExpression "pkgs.unifiLTS";
38 description = lib.mdDoc ''
39 The unifi package to use.
43 services.unifi.mongodbPackage = mkOption {
45 default = pkgs.mongodb;
46 defaultText = literalExpression "pkgs.mongodb";
47 description = lib.mdDoc ''
48 The mongodb package to use.
52 services.unifi.openFirewall = mkOption {
55 description = lib.mdDoc ''
56 Whether or not to open the minimum required ports on the firewall.
58 This is necessary to allow firmware upgrades and device discovery to
59 work. For remote login, you should additionally open (or forward) port
64 services.unifi.initialJavaHeapSize = mkOption {
65 type = types.nullOr types.int;
68 description = lib.mdDoc ''
69 Set the initial heap size for the JVM in MB. If this option isn't set, the
70 JVM will decide this value at runtime.
74 services.unifi.maximumJavaHeapSize = mkOption {
75 type = types.nullOr types.int;
78 description = lib.mdDoc ''
79 Set the maximimum heap size for the JVM in MB. If this option isn't set, the
80 JVM will decide this value at runtime.
86 config = mkIf cfg.enable {
91 description = "UniFi controller daemon user";
94 users.groups.unifi = {};
96 networking.firewall = mkIf cfg.openFirewall {
97 # https://help.ubnt.com/hc/en-us/articles/218506997
99 8080 # Port for UAP to inform controller.
100 8880 # Port for HTTP portal redirect, if guest portal is enabled.
101 8843 # Port for HTTPS portal redirect, ditto.
102 6789 # Port for UniFi mobile speed test.
105 3478 # UDP port used for STUN.
106 10001 # UDP port used for device discovery.
110 systemd.services.unifi = {
111 description = "UniFi controller daemon";
112 wantedBy = [ "multi-user.target" ];
113 after = [ "network.target" ];
115 # This a HACK to fix missing dependencies of dynamic libs extracted from jars
116 environment.LD_LIBRARY_PATH = with pkgs.stdenv; "${cc.cc.lib}/lib";
117 # Make sure package upgrades trigger a service restart
118 restartTriggers = [ cfg.unifiPackage cfg.mongodbPackage ];
122 ExecStart = "${(removeSuffix "\n" cmd)} start";
123 ExecStop = "${(removeSuffix "\n" cmd)} stop";
124 Restart = "on-failure";
128 WorkingDirectory = "${stateDir}";
129 # the stop command exits while the main process is still running, and unifi
130 # wants to manage its own child processes. this means we have to set KillSignal
131 # to something the main process ignores, otherwise every stop will have unifi.service
132 # fail with SIGTERM status.
133 KillSignal = "SIGCONT";
136 AmbientCapabilities = "";
137 CapabilityBoundingSet = "";
138 # ProtectClock= adds DeviceAllow=char-rtc r
140 DevicePolicy = "closed";
141 LockPersonality = true;
142 NoNewPrivileges = true;
143 PrivateDevices = true;
144 PrivateMounts = true;
148 ProtectControlGroups = true;
150 ProtectHostname = true;
151 ProtectKernelLogs = true;
152 ProtectKernelModules = true;
153 ProtectKernelTunables = true;
154 ProtectSystem = "strict";
156 RestrictNamespaces = true;
157 RestrictRealtime = true;
158 RestrictSUIDSGID = true;
159 SystemCallErrorNumber = "EPERM";
160 SystemCallFilter = [ "@system-service" ];
162 StateDirectory = "unifi";
163 RuntimeDirectory = "unifi";
164 LogsDirectory = "unifi";
165 CacheDirectory= "unifi";
167 TemporaryFileSystem = [
168 # required as we want to create bind mounts below
169 "${stateDir}/webapps:rw"
172 # We must create the binary directories as bind mounts instead of symlinks
173 # This is because the controller resolves all symlinks to absolute paths
174 # to be used as the working directory.
176 "/var/log/unifi:${stateDir}/logs"
177 "/run/unifi:${stateDir}/run"
178 "${cfg.unifiPackage}/dl:${stateDir}/dl"
179 "${cfg.unifiPackage}/lib:${stateDir}/lib"
180 "${cfg.mongodbPackage}/bin:${stateDir}/bin"
181 "${cfg.unifiPackage}/webapps/ROOT:${stateDir}/webapps/ROOT"
184 # Needs network access
185 PrivateNetwork = false;
186 # Cannot be true due to OpenJDK
187 MemoryDenyWriteExecute = false;
193 (mkRemovedOptionModule [ "services" "unifi" "dataDir" ] "You should move contents of dataDir to /var/lib/unifi/data" )
194 (mkRenamedOptionModule [ "services" "unifi" "openPorts" ] [ "services" "unifi" "openFirewall" ])
197 meta.maintainers = with lib.maintainers; [ erictapen pennae ];