1{ config, lib, pkgs, utils, ... }:
2with lib;
3let
4 cfg = config.services.unifi;
5 stateDir = "/var/lib/unifi";
6 cmd = ''
7 @${pkgs.jre}/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
11 '';
12 mountPoints = [
13 {
14 what = "${pkgs.unifi}/dl";
15 where = "${stateDir}/dl";
16 }
17 {
18 what = "${pkgs.unifi}/lib";
19 where = "${stateDir}/lib";
20 }
21 {
22 what = "${pkgs.mongodb}/bin";
23 where = "${stateDir}/bin";
24 }
25 {
26 what = "${cfg.dataDir}";
27 where = "${stateDir}/data";
28 }
29 ];
30 systemdMountPoints = map (m: "${utils.escapeSystemdPath m.where}.mount") mountPoints;
31in
32{
33
34 options = {
35
36 services.unifi.enable = mkOption {
37 type = types.bool;
38 default = false;
39 description = ''
40 Whether or not to enable the unifi controller service.
41 '';
42 };
43
44 services.unifi.dataDir = mkOption {
45 type = types.str;
46 default = "${stateDir}/data";
47 description = ''
48 Where to store the database and other data.
49
50 This directory will be bind-mounted to ${stateDir}/data as part of the service startup.
51 '';
52 };
53
54 services.unifi.openPorts = mkOption {
55 type = types.bool;
56 default = true;
57 description = ''
58 Whether or not to open the minimum required ports on the firewall.
59
60 This is necessary to allow firmware upgrades and device discovery to
61 work. For remote login, you should additionally open (or forward) port
62 8443.
63 '';
64 };
65
66 services.unifi.initialJavaHeapSize = mkOption {
67 type = types.nullOr types.int;
68 default = null;
69 example = 1024;
70 description = ''
71 Set the initial heap size for the JVM in MB. If this option isn't set, the
72 JVM will decide this value at runtime.
73 '';
74 };
75
76 services.unifi.maximumJavaHeapSize = mkOption {
77 type = types.nullOr types.int;
78 default = null;
79 example = 4096;
80 description = ''
81 Set the maximimum heap size for the JVM in MB. If this option isn't set, the
82 JVM will decide this value at runtime.
83 '';
84 };
85
86 };
87
88 config = mkIf cfg.enable {
89
90 users.extraUsers.unifi = {
91 uid = config.ids.uids.unifi;
92 description = "UniFi controller daemon user";
93 home = "${stateDir}";
94 };
95
96 networking.firewall = mkIf cfg.openPorts {
97 # https://help.ubnt.com/hc/en-us/articles/204910084-UniFi-Change-Default-Ports-for-Controller-and-UAPs
98 allowedTCPPorts = [
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 ];
103 allowedUDPPorts = [
104 3478 # UDP port used for STUN.
105 10001 # UDP port used for device discovery.
106 ];
107 };
108
109 # We must create the binary directories as bind mounts instead of symlinks
110 # This is because the controller resolves all symlinks to absolute paths
111 # to be used as the working directory.
112 systemd.mounts = map ({ what, where }: {
113 bindsTo = [ "unifi.service" ];
114 partOf = [ "unifi.service" ];
115 unitConfig.RequiresMountsFor = stateDir;
116 options = "bind";
117 what = what;
118 where = where;
119 }) mountPoints;
120
121 systemd.services.unifi = {
122 description = "UniFi controller daemon";
123 wantedBy = [ "multi-user.target" ];
124 after = [ "network.target" ] ++ systemdMountPoints;
125 partOf = systemdMountPoints;
126 bindsTo = systemdMountPoints;
127 unitConfig.RequiresMountsFor = stateDir;
128 # This a HACK to fix missing dependencies of dynamic libs extracted from jars
129 environment.LD_LIBRARY_PATH = with pkgs.stdenv; "${cc.cc.lib}/lib";
130
131 preStart = ''
132 # Ensure privacy of state and data.
133 chown unifi "${stateDir}" "${stateDir}/data"
134 chmod 0700 "${stateDir}" "${stateDir}/data"
135
136 # Create the volatile webapps
137 rm -rf "${stateDir}/webapps"
138 mkdir -p "${stateDir}/webapps"
139 chown unifi "${stateDir}/webapps"
140 ln -s "${pkgs.unifi}/webapps/ROOT" "${stateDir}/webapps/ROOT"
141 '';
142
143 postStop = ''
144 rm -rf "${stateDir}/webapps"
145 '';
146
147 serviceConfig = {
148 Type = "simple";
149 ExecStart = "${(removeSuffix "\n" cmd)} start";
150 ExecStop = "${(removeSuffix "\n" cmd)} stop";
151 User = "unifi";
152 PermissionsStartOnly = true;
153 UMask = "0077";
154 WorkingDirectory = "${stateDir}";
155 };
156 };
157
158 };
159
160}