1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 cfg = config.services.tinc;
8
9in
10
11{
12
13 ###### interface
14
15 options = {
16
17 services.tinc = {
18
19 networks = mkOption {
20 default = { };
21 type = with types; attrsOf (submodule {
22 options = {
23
24 extraConfig = mkOption {
25 default = "";
26 type = types.lines;
27 description = ''
28 Extra lines to add to the tinc service configuration file.
29 '';
30 };
31
32 name = mkOption {
33 default = null;
34 type = types.nullOr types.str;
35 description = ''
36 The name of the node which is used as an identifier when communicating
37 with the remote nodes in the mesh. If null then the hostname of the system
38 is used to derive a name (note that tinc may replace non-alphanumeric characters in
39 hostnames by underscores).
40 '';
41 };
42
43 ed25519PrivateKeyFile = mkOption {
44 default = null;
45 type = types.nullOr types.path;
46 description = ''
47 Path of the private ed25519 keyfile.
48 '';
49 };
50
51 debugLevel = mkOption {
52 default = 0;
53 type = types.addCheck types.int (l: l >= 0 && l <= 5);
54 description = ''
55 The amount of debugging information to add to the log. 0 means little
56 logging while 5 is the most logging. <command>man tincd</command> for
57 more details.
58 '';
59 };
60
61 hosts = mkOption {
62 default = { };
63 type = types.attrsOf types.lines;
64 description = ''
65 The name of the host in the network as well as the configuration for that host.
66 This name should only contain alphanumerics and underscores.
67 '';
68 };
69
70 interfaceType = mkOption {
71 default = "tun";
72 type = types.enum [ "tun" "tap" ];
73 description = ''
74 The type of virtual interface used for the network connection
75 '';
76 };
77
78 listenAddress = mkOption {
79 default = null;
80 type = types.nullOr types.str;
81 description = ''
82 The ip address to listen on for incoming connections.
83 '';
84 };
85
86 bindToAddress = mkOption {
87 default = null;
88 type = types.nullOr types.str;
89 description = ''
90 The ip address to bind to (both listen on and send packets from).
91 '';
92 };
93
94 package = mkOption {
95 type = types.package;
96 default = pkgs.tinc_pre;
97 defaultText = "pkgs.tinc_pre";
98 description = ''
99 The package to use for the tinc daemon's binary.
100 '';
101 };
102
103 chroot = mkOption {
104 default = true;
105 type = types.bool;
106 description = ''
107 Change process root directory to the directory where the config file is located (/etc/tinc/netname/), for added security.
108 The chroot is performed after all the initialization is done, after writing pid files and opening network sockets.
109
110 Note that tinc can't run scripts anymore (such as tinc-down or host-up), unless it is setup to be runnable inside chroot environment.
111 '';
112 };
113 };
114 });
115
116 description = ''
117 Defines the tinc networks which will be started.
118 Each network invokes a different daemon.
119 '';
120 };
121 };
122
123 };
124
125
126 ###### implementation
127
128 config = mkIf (cfg.networks != { }) {
129
130 environment.etc = fold (a: b: a // b) { }
131 (flip mapAttrsToList cfg.networks (network: data:
132 flip mapAttrs' data.hosts (host: text: nameValuePair
133 ("tinc/${network}/hosts/${host}")
134 ({ mode = "0644"; user = "tinc.${network}"; inherit text; })
135 ) // {
136 "tinc/${network}/tinc.conf" = {
137 mode = "0444";
138 text = ''
139 Name = ${if data.name == null then "$HOST" else data.name}
140 DeviceType = ${data.interfaceType}
141 ${optionalString (data.ed25519PrivateKeyFile != null) "Ed25519PrivateKeyFile = ${data.ed25519PrivateKeyFile}"}
142 ${optionalString (data.listenAddress != null) "ListenAddress = ${data.listenAddress}"}
143 ${optionalString (data.bindToAddress != null) "BindToAddress = ${data.bindToAddress}"}
144 Interface = tinc.${network}
145 ${data.extraConfig}
146 '';
147 };
148 }
149 ));
150
151 networking.interfaces = flip mapAttrs' cfg.networks (network: data: nameValuePair
152 ("tinc.${network}")
153 ({
154 virtual = true;
155 virtualType = "${data.interfaceType}";
156 })
157 );
158
159 systemd.services = flip mapAttrs' cfg.networks (network: data: nameValuePair
160 ("tinc.${network}")
161 ({
162 description = "Tinc Daemon - ${network}";
163 wantedBy = [ "multi-user.target" ];
164 after = [ "network.target" ];
165 path = [ data.package ];
166 restartTriggers = [ config.environment.etc."tinc/${network}/tinc.conf".source ];
167 serviceConfig = {
168 Type = "simple";
169 Restart = "always";
170 RestartSec = "3";
171 ExecStart = "${data.package}/bin/tincd -D -U tinc.${network} -n ${network} ${optionalString (data.chroot) "-R"} --pidfile /run/tinc.${network}.pid -d ${toString data.debugLevel}";
172 };
173 preStart = ''
174 mkdir -p /etc/tinc/${network}/hosts
175 chown tinc.${network} /etc/tinc/${network}/hosts
176 mkdir -p /etc/tinc/${network}/invitations
177 chown tinc.${network} /etc/tinc/${network}/invitations
178
179 # Determine how we should generate our keys
180 if type tinc >/dev/null 2>&1; then
181 # Tinc 1.1+ uses the tinc helper application for key generation
182 ${if data.ed25519PrivateKeyFile != null then " # Keyfile managed by nix" else ''
183 # Prefer ED25519 keys (only in 1.1+)
184 [ -f "/etc/tinc/${network}/ed25519_key.priv" ] || tinc -n ${network} generate-ed25519-keys
185 ''}
186 # Otherwise use RSA keys
187 [ -f "/etc/tinc/${network}/rsa_key.priv" ] || tinc -n ${network} generate-rsa-keys 4096
188 else
189 # Tinc 1.0 uses the tincd application
190 [ -f "/etc/tinc/${network}/rsa_key.priv" ] || tincd -n ${network} -K 4096
191 fi
192 '';
193 })
194 );
195
196 environment.systemPackages = let
197 cli-wrappers = pkgs.stdenv.mkDerivation {
198 name = "tinc-cli-wrappers";
199 buildInputs = [ pkgs.makeWrapper ];
200 buildCommand = ''
201 mkdir -p $out/bin
202 ${concatStringsSep "\n" (mapAttrsToList (network: data:
203 optionalString (versionAtLeast data.package.version "1.1pre") ''
204 makeWrapper ${data.package}/bin/tinc "$out/bin/tinc.${network}" \
205 --add-flags "--pidfile=/run/tinc.${network}.pid" \
206 --add-flags "--config=/etc/tinc/${network}"
207 '') cfg.networks)}
208 '';
209 };
210 in [ cli-wrappers ];
211
212 users.users = flip mapAttrs' cfg.networks (network: _:
213 nameValuePair ("tinc.${network}") ({
214 description = "Tinc daemon user for ${network}";
215 isSystemUser = true;
216 })
217 );
218
219 };
220
221}