Self-host your own digital island
1{ config, pkgs, lib, ... }:
2
3with lib;
4let
5 cfg = config.eilean;
6 domain = config.networking.domain;
7 subdomain = "turn.${domain}";
8 staticAuthSecretFile = "/run/coturn/static-auth-secret";
9in {
10 options.eilean.turn = { enable = mkEnableOption "TURN server"; };
11
12 config = mkIf cfg.turn.enable {
13 security.acme-eon.certs."${subdomain}" = lib.mkIf cfg.acme-eon {
14 group = "turnserver";
15 reloadServices = [ "coturn" ];
16 };
17
18 services.coturn = let
19 certDir = if cfg.acme-eon then
20 config.security.acme-eon.certs.${subdomain}.directory
21 else
22 config.security.acme.certs.${subdomain}.directory;
23 in {
24 enable = true;
25 no-cli = true;
26 no-tcp-relay = true;
27 secure-stun = true;
28 use-auth-secret = true;
29 static-auth-secret-file = staticAuthSecretFile;
30 realm = subdomain;
31 relay-ips = with config.eilean; [ serverIpv4 serverIpv6 ];
32 cert = "${certDir}/fullchain.pem";
33 pkey = "${certDir}/key.pem";
34 };
35
36 systemd.services = {
37 coturn-static-auth-secret-generator = {
38 description = "Generate coturn static auth secret file";
39 script = ''
40 if [ ! -f '${staticAuthSecretFile}' ]; then
41 umask 077
42 tr -dc A-Za-z0-9 </dev/urandom | head -c 32 > '${staticAuthSecretFile}'
43 chown ${config.systemd.services.coturn.serviceConfig.User}:${config.systemd.services.coturn.serviceConfig.Group} '${staticAuthSecretFile}'
44 fi
45 '';
46 serviceConfig.Type = "oneshot";
47 serviceConfig.RemainAfterExit = true;
48 };
49 "coturn" = {
50 after = [ "coturn-static-auth-secret-generator.service" ]
51 ++ lib.lists.optional cfg.acme-eon "acme-eon-${subdomain}.service";
52 requires = [ "coturn-static-auth-secret-generator.service" ];
53 wants = lib.lists.optional cfg.acme-eon "acme-eon-${subdomain}.service";
54 };
55 };
56
57 networking.firewall = with config.services.coturn;
58 let
59 turn-range = {
60 from = min-port;
61 to = max-port;
62 };
63 stun-ports = [
64 listening-port
65 tls-listening-port
66 # these are only used if server has more than one IP address (of the same family
67 #alt-listening-port
68 #alt-tls-listening-port
69 ];
70 in {
71 allowedTCPPorts = stun-ports;
72 allowedTCPPortRanges = [ turn-range ];
73 allowedUDPPorts = stun-ports;
74 allowedUDPPortRanges = [ turn-range ];
75 };
76
77 security.acme.certs.${config.services.coturn.realm} =
78 lib.mkIf (!cfg.acme-eon) {
79 postRun =
80 "systemctl reload nginx.service; systemctl restart coturn.service";
81 group = "turnserver";
82 };
83 services.nginx.enable = lib.mkIf (!cfg.acme-eon) true;
84 services.nginx.virtualHosts = lib.mkIf (!cfg.acme-eon) {
85 "${config.services.coturn.realm}" = {
86 forceSSL = true;
87 enableACME = true;
88 };
89 };
90 users.groups."turnserver".members =
91 lib.mkIf (!cfg.acme-eon) [ config.services.nginx.user ];
92
93 eilean.dns.enable = true;
94 eilean.services.dns.zones.${config.networking.domain}.records = [{
95 name = "turn";
96 type = "CNAME";
97 value = cfg.domainName;
98 }];
99 };
100}