1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8with lib;
9
10let
11 ts3 = pkgs.teamspeak_server;
12 cfg = config.services.teamspeak3;
13 user = "teamspeak";
14 group = "teamspeak";
15in
16
17{
18
19 ###### interface
20
21 options = {
22
23 services.teamspeak3 = {
24 enable = mkOption {
25 type = types.bool;
26 default = false;
27 description = ''
28 Whether to run the Teamspeak3 voice communication server daemon.
29 '';
30 };
31
32 dataDir = mkOption {
33 type = types.path;
34 default = "/var/lib/teamspeak3-server";
35 description = ''
36 Directory to store TS3 database and other state/data files.
37 '';
38 };
39
40 logPath = mkOption {
41 type = types.path;
42 default = "/var/log/teamspeak3-server/";
43 description = ''
44 Directory to store log files in.
45 '';
46 };
47
48 voiceIP = mkOption {
49 type = types.nullOr types.str;
50 default = null;
51 example = "[::]";
52 description = ''
53 IP on which the server instance will listen for incoming voice connections. Defaults to any IP.
54 '';
55 };
56
57 defaultVoicePort = mkOption {
58 type = types.port;
59 default = 9987;
60 description = ''
61 Default UDP port for clients to connect to virtual servers - used for first virtual server, subsequent ones will open on incrementing port numbers by default.
62 '';
63 };
64
65 fileTransferIP = mkOption {
66 type = types.nullOr types.str;
67 default = null;
68 example = "[::]";
69 description = ''
70 IP on which the server instance will listen for incoming file transfer connections. Defaults to any IP.
71 '';
72 };
73
74 fileTransferPort = mkOption {
75 type = types.port;
76 default = 30033;
77 description = ''
78 TCP port opened for file transfers.
79 '';
80 };
81
82 queryIP = mkOption {
83 type = types.nullOr types.str;
84 default = null;
85 example = "0.0.0.0";
86 description = ''
87 IP on which the server instance will listen for incoming ServerQuery connections. Defaults to any IP.
88 '';
89 };
90
91 queryPort = mkOption {
92 type = types.port;
93 default = 10011;
94 description = ''
95 TCP port opened for ServerQuery connections using the raw telnet protocol.
96 '';
97 };
98
99 querySshPort = mkOption {
100 type = types.port;
101 default = 10022;
102 description = ''
103 TCP port opened for ServerQuery connections using the SSH protocol.
104 '';
105 };
106
107 queryHttpPort = mkOption {
108 type = types.port;
109 default = 10080;
110 description = ''
111 TCP port opened for ServerQuery connections using the HTTP protocol.
112 '';
113 };
114
115 openFirewall = mkOption {
116 type = types.bool;
117 default = false;
118 description = "Open ports in the firewall for the TeamSpeak3 server.";
119 };
120
121 openFirewallServerQuery = mkOption {
122 type = types.bool;
123 default = false;
124 description = "Open ports in the firewall for the TeamSpeak3 serverquery (administration) system. Requires openFirewall.";
125 };
126
127 };
128
129 };
130
131 ###### implementation
132
133 config = mkIf cfg.enable {
134 users.users.teamspeak = {
135 description = "Teamspeak3 voice communication server daemon";
136 group = group;
137 uid = config.ids.uids.teamspeak;
138 home = cfg.dataDir;
139 createHome = true;
140 };
141
142 users.groups.teamspeak = {
143 gid = config.ids.gids.teamspeak;
144 };
145
146 systemd.tmpfiles.rules = [
147 "d '${cfg.logPath}' - ${user} ${group} - -"
148 ];
149
150 networking.firewall = mkIf cfg.openFirewall {
151 allowedTCPPorts =
152 [ cfg.fileTransferPort ]
153 ++ (map (port: mkIf cfg.openFirewallServerQuery port) [
154 cfg.queryPort
155 cfg.querySshPort
156 cfg.queryHttpPort
157 ]);
158 # subsequent vServers will use the incremented voice port, let's just open the next 10
159 allowedUDPPortRanges = [
160 {
161 from = cfg.defaultVoicePort;
162 to = cfg.defaultVoicePort + 10;
163 }
164 ];
165 };
166
167 systemd.services.teamspeak3-server = {
168 description = "Teamspeak3 voice communication server daemon";
169 after = [ "network.target" ];
170 wantedBy = [ "multi-user.target" ];
171
172 serviceConfig = {
173 ExecStart = ''
174 ${ts3}/bin/ts3server \
175 dbsqlpath=${ts3}/lib/teamspeak/sql/ \
176 logpath=${cfg.logPath} \
177 license_accepted=1 \
178 default_voice_port=${toString cfg.defaultVoicePort} \
179 filetransfer_port=${toString cfg.fileTransferPort} \
180 query_port=${toString cfg.queryPort} \
181 query_ssh_port=${toString cfg.querySshPort} \
182 query_http_port=${toString cfg.queryHttpPort} \
183 ${optionalString (cfg.voiceIP != null) "voice_ip=${cfg.voiceIP}"} \
184 ${optionalString (cfg.fileTransferIP != null) "filetransfer_ip=${cfg.fileTransferIP}"} \
185 ${optionalString (cfg.queryIP != null) "query_ip=${cfg.queryIP}"} \
186 ${optionalString (cfg.queryIP != null) "query_ssh_ip=${cfg.queryIP}"} \
187 ${optionalString (cfg.queryIP != null) "query_http_ip=${cfg.queryIP}"} \
188 '';
189 WorkingDirectory = cfg.dataDir;
190 User = user;
191 Group = group;
192 Restart = "on-failure";
193 };
194 };
195 };
196
197 meta.maintainers = with lib.maintainers; [ arobyn ];
198}