1 { config, lib, pkgs, ... }:
5 cfg = config.services.tinc;
8 if value == true then "yes"
9 else if value == false then "no"
10 else generators.mkValueStringDefault { } value;
12 toTincConf = generators.toKeyValue {
13 listsAsDuplicateKeys = true;
14 mkKeyValue = generators.mkKeyValueDefault { inherit mkValueString; } "=";
17 tincConfType = with types;
19 valueType = oneOf [ bool str int ];
21 attrsOf (either valueType (listOf valueType));
27 description = "The external IP address or hostname where the host can be reached.";
31 type = types.nullOr types.port;
34 The port where the host can be reached.
36 If no port is specified, the default Port is used.
47 The subnet of this host.
49 Subnets can either be single MAC, IPv4 or IPv6 addresses, in which case
50 a subnet consisting of only that single address is assumed, or they can
51 be a IPv4 or IPv6 network address with a prefix length.
53 IPv4 subnets are notated like 192.168.1.0/24, IPv6 subnets are notated
54 like fec0:0:0:1::/64. MAC addresses are notated like 0:1a:2b:3c:4d:5e.
56 Note that subnets like 192.168.1.1/24 are invalid.
60 prefixLength = mkOption {
61 type = with types; nullOr (addCheck int (n: n >= 0 && n <= 128));
64 The prefix length of the subnet.
66 If null, a subnet consisting of only that single address is assumed.
68 This conforms to standard CIDR notation as described in RFC1519.
73 type = types.ints.unsigned;
76 Indicates the priority over identical Subnets owned by different nodes.
78 Lower values indicate higher priority. Packets will be sent to the
79 node with the highest priority, unless that node is not reachable, in
80 which case the node with the next highest priority will be tried, and
87 hostSubmodule = { config, ... }: {
89 addresses = mkOption {
90 type = types.listOf (types.submodule addressSubmodule);
93 The external address where the host can be reached. This will set this
94 host's {option}`settings.Address` option.
96 This variable is only required if you want to connect to this host.
101 type = types.listOf (types.submodule subnetSubmodule);
104 The subnets which this tinc daemon will serve. This will set this
105 host's {option}`settings.Subnet` option.
107 Tinc tries to look up which other daemon it should send a packet to by
108 searching the appropriate subnet. If the packet matches a subnet, it
109 will be sent to the daemon who has this subnet in his host
114 rsaPublicKey = mkOption {
118 Legacy RSA public key of the host in PEM format, including start and
121 This will be appended as-is in the host's configuration file.
123 The ed25519 public key can be specified using the
124 {option}`settings.Ed25519PublicKey` option instead.
128 settings = mkOption {
130 type = types.submodule { freeformType = tincConfType; };
132 Configuration for this host.
134 See <https://tinc-vpn.org/documentation-1.1/Host-configuration-variables.html>
135 for supported values.
141 Address = mkDefault (map
142 (address: "${address.address} ${toString address.port}")
145 Subnet = mkDefault (map
147 if subnet.prefixLength == null then "${subnet.address}#${toString subnet.weight}"
148 else "${subnet.address}/${toString subnet.prefixLength}#${toString subnet.weight}")
162 networks = mkOption {
164 type = with types; attrsOf (submodule ({ config, ... }: {
167 extraConfig = mkOption {
171 Extra lines to add to the tinc service configuration file.
173 Note that using the declarative {option}`service.tinc.networks.<name>.settings`
180 type = types.nullOr types.str;
182 The name of the node which is used as an identifier when communicating
183 with the remote nodes in the mesh. If null then the hostname of the system
184 is used to derive a name (note that tinc may replace non-alphanumeric characters in
185 hostnames by underscores).
189 ed25519PrivateKeyFile = mkOption {
191 type = types.nullOr types.path;
193 Path of the private ed25519 keyfile.
197 rsaPrivateKeyFile = mkOption {
199 type = types.nullOr types.path;
201 Path of the private RSA keyfile.
205 debugLevel = mkOption {
207 type = types.addCheck types.int (l: l >= 0 && l <= 5);
209 The amount of debugging information to add to the log. 0 means little
210 logging while 5 is the most logging. {command}`man tincd` for
217 type = types.attrsOf types.lines;
219 The name of the host in the network as well as the configuration for that host.
220 This name should only contain alphanumerics and underscores.
222 Note that using the declarative {option}`service.tinc.networks.<name>.hostSettings`
227 hostSettings = mkOption {
229 example = literalExpression ''
233 { address = "192.168.1.42"; }
234 { address = "192.168.1.42"; port = 1655; }
236 subnets = [ { address = "10.0.0.42"; } ];
237 rsaPublicKey = "...";
239 Ed25519PublicKey = "...";
243 subnets = [ { address = "10.0.1.0"; prefixLength = 24; weight = 2; } ];
244 rsaPublicKey = "...";
251 type = types.attrsOf (types.submodule hostSubmodule);
253 The name of the host in the network as well as the configuration for that host.
254 This name should only contain alphanumerics and underscores.
258 interfaceType = mkOption {
260 type = types.enum [ "tun" "tap" ];
262 The type of virtual interface used for the network connection.
266 listenAddress = mkOption {
268 type = types.nullOr types.str;
270 The ip address to listen on for incoming connections.
274 bindToAddress = mkOption {
276 type = types.nullOr types.str;
278 The ip address to bind to (both listen on and send packets from).
282 package = mkPackageOption pkgs "tinc_pre" { };
288 Change process root directory to the directory where the config file is located (/etc/tinc/netname/), for added security.
289 The chroot is performed after all the initialization is done, after writing pid files and opening network sockets.
291 Note that this currently breaks dns resolution and tinc can't run scripts anymore (such as tinc-down or host-up), unless it is setup to be runnable inside chroot environment.
295 settings = mkOption {
297 type = types.submodule { freeformType = tincConfType; };
298 example = literalExpression ''
300 Interface = "custom.interface";
306 Configuration of the Tinc daemon for this network.
308 See <https://tinc-vpn.org/documentation-1.1/Main-configuration-variables.html>
309 for supported values.
317 ${toTincConf host.settings}
323 DeviceType = mkDefault config.interfaceType;
324 Name = mkDefault (if config.name == null then "$HOST" else config.name);
325 Ed25519PrivateKeyFile = mkIf (config.ed25519PrivateKeyFile != null) (mkDefault config.ed25519PrivateKeyFile);
326 PrivateKeyFile = mkIf (config.rsaPrivateKeyFile != null) (mkDefault config.rsaPrivateKeyFile);
327 ListenAddress = mkIf (config.listenAddress != null) (mkDefault config.listenAddress);
328 BindToAddress = mkIf (config.bindToAddress != null) (mkDefault config.bindToAddress);
334 Defines the tinc networks which will be started.
335 Each network invokes a different daemon.
343 ###### implementation
345 config = mkIf (cfg.networks != { }) (
347 etcConfig = foldr (a: b: a // b) { }
348 (flip mapAttrsToList cfg.networks (network: data:
349 flip mapAttrs' data.hosts (host: text: nameValuePair
350 ("tinc/${network}/hosts/${host}")
351 ({ mode = "0644"; user = "tinc-${network}"; inherit text; })
353 "tinc/${network}/tinc.conf" = {
356 ${toTincConf ({ Interface = "tinc.${network}"; } // data.settings)}
363 environment.etc = etcConfig;
365 systemd.services = flip mapAttrs' cfg.networks (network: data: nameValuePair
367 (let version = getVersion data.package; in {
368 description = "Tinc Daemon - ${network}";
369 wantedBy = [ "multi-user.target" ];
370 path = [ data.package ];
371 reloadTriggers = mkIf (versionAtLeast version "1.1pre") [ (builtins.toJSON etcConfig) ];
372 restartTriggers = mkIf (versionOlder version "1.1pre") [ (builtins.toJSON etcConfig) ];
377 ExecReload = mkIf (versionAtLeast version "1.1pre") "${data.package}/bin/tinc -n ${network} reload";
378 ExecStart = "${data.package}/bin/tincd -D -U tinc-${network} -n ${network} ${optionalString (data.chroot) "-R"} --pidfile /run/tinc.${network}.pid -d ${toString data.debugLevel}";
381 mkdir -p /etc/tinc/${network}/hosts
382 chown tinc-${network} /etc/tinc/${network}/hosts
383 mkdir -p /etc/tinc/${network}/invitations
384 chown tinc-${network} /etc/tinc/${network}/invitations
386 # Determine how we should generate our keys
387 if type tinc >/dev/null 2>&1; then
388 # Tinc 1.1+ uses the tinc helper application for key generation
389 ${if data.ed25519PrivateKeyFile != null then " # ed25519 Keyfile managed by nix" else ''
390 # Prefer ED25519 keys (only in 1.1+)
391 [ -f "/etc/tinc/${network}/ed25519_key.priv" ] || tinc -n ${network} generate-ed25519-keys
393 ${if data.rsaPrivateKeyFile != null then " # RSA Keyfile managed by nix" else ''
394 [ -f "/etc/tinc/${network}/rsa_key.priv" ] || tinc -n ${network} generate-rsa-keys 4096
396 # In case there isn't anything to do
399 # Tinc 1.0 uses the tincd application
400 [ -f "/etc/tinc/${network}/rsa_key.priv" ] || tincd -n ${network} -K 4096
406 environment.systemPackages = let
407 cli-wrappers = pkgs.stdenv.mkDerivation {
408 name = "tinc-cli-wrappers";
409 nativeBuildInputs = [ pkgs.makeWrapper ];
412 ${concatStringsSep "\n" (mapAttrsToList (network: data:
413 optionalString (versionAtLeast data.package.version "1.1pre") ''
414 makeWrapper ${data.package}/bin/tinc "$out/bin/tinc.${network}" \
415 --add-flags "--pidfile=/run/tinc.${network}.pid" \
416 --add-flags "--config=/etc/tinc/${network}"
422 users.users = flip mapAttrs' cfg.networks (network: _:
423 nameValuePair ("tinc-${network}") ({
424 description = "Tinc daemon user for ${network}";
426 group = "tinc-${network}";
429 users.groups = flip mapAttrs' cfg.networks (network: _:
430 nameValuePair "tinc-${network}" {}
434 meta.maintainers = with maintainers; [ minijackson mic92 ];