nixos/tinc: add settings and hostSettings for RFC42-style options

Changed files
+224 -13
nixos
modules
services
networking
+224 -13
nixos/modules/services/networking/tinc.nix
···
{ config, lib, pkgs, ... }:
with lib;
+
let
+
cfg = config.services.tinc;
-
let
+
mkValueString = value:
+
if value == true then "yes"
+
else if value == false then "no"
+
else generators.mkValueStringDefault { } value;
+
+
toTincConf = generators.toKeyValue {
+
listsAsDuplicateKeys = true;
+
mkKeyValue = generators.mkKeyValueDefault { inherit mkValueString; } "=";
+
};
+
+
tincConfType = with types;
+
let
+
valueType = oneOf [ bool str int ];
+
in
+
attrsOf (either valueType (listOf valueType));
+
+
addressSubmodule = {
+
options = {
+
address = mkOption {
+
type = types.str;
+
description = "The external IP address or hostname where the host can be reached.";
+
};
+
+
port = mkOption {
+
type = types.nullOr types.port;
+
default = null;
+
description = ''
+
The port where the host can be reached.
+
+
If no port is specified, the default Port is used.
+
'';
+
};
+
};
+
};
+
+
subnetSubmodule = {
+
options = {
+
address = mkOption {
+
type = types.str;
+
description = ''
+
The subnet of this host.
+
+
Subnets can either be single MAC, IPv4 or IPv6 addresses, in which case
+
a subnet consisting of only that single address is assumed, or they can
+
be a IPv4 or IPv6 network address with a prefix length.
+
+
IPv4 subnets are notated like 192.168.1.0/24, IPv6 subnets are notated
+
like fec0:0:0:1::/64. MAC addresses are notated like 0:1a:2b:3c:4d:5e.
+
+
Note that subnets like 192.168.1.1/24 are invalid.
+
'';
+
};
+
+
prefixLength = mkOption {
+
type = with types; nullOr (addCheck int (n: n >= 0 && n <= 128));
+
default = null;
+
description = ''
+
The prefix length of the subnet.
+
+
If null, a subnet consisting of only that single address is assumed.
+
+
This conforms to standard CIDR notation as described in RFC1519.
+
'';
+
};
+
+
weight = mkOption {
+
type = types.ints.unsigned;
+
default = 10;
+
description = ''
+
Indicates the priority over identical Subnets owned by different nodes.
+
+
Lower values indicate higher priority. Packets will be sent to the
+
node with the highest priority, unless that node is not reachable, in
+
which case the node with the next highest priority will be tried, and
+
so on.
+
'';
+
};
+
};
+
};
+
+
hostSubmodule = { config, ... }: {
+
options = {
+
addresses = mkOption {
+
type = types.listOf (types.submodule addressSubmodule);
+
default = [ ];
+
description = ''
+
The external address where the host can be reached. This will set this
+
host's <option>settings.Address</option> option.
+
+
This variable is only required if you want to connect to this host.
+
'';
+
};
+
+
subnets = mkOption {
+
type = types.listOf (types.submodule subnetSubmodule);
+
default = [ ];
+
description = ''
+
The subnets which this tinc daemon will serve. This will set this
+
host's <option>settings.Subnet</option> option.
+
+
Tinc tries to look up which other daemon it should send a packet to by
+
searching the appropriate subnet. If the packet matches a subnet, it
+
will be sent to the daemon who has this subnet in his host
+
configuration file.
+
'';
+
};
+
+
rsaPublicKey = mkOption {
+
type = types.str;
+
default = "";
+
description = ''
+
Legacy RSA public key of the host in PEM format, including start and
+
end markers.
+
+
This will be appended as-is in the host's configuration file.
+
+
The ed25519 public key can be specified using the
+
<option>settings.Ed25519PublicKey</option> option instead.
+
'';
+
};
-
cfg = config.services.tinc;
+
settings = mkOption {
+
default = { };
+
type = types.submodule { freeformType = tincConfType; };
+
description = ''
+
Configuration for this host.
-
in
+
See <link xlink:href="https://tinc-vpn.org/documentation-1.1/Host-configuration-variables.html"/>
+
for supported values.
+
'';
+
};
+
};
+
config.settings = {
+
Address = mkDefault (map
+
(address: "${address.address} ${toString address.port}")
+
config.addresses);
+
+
Subnet = mkDefault (map
+
(subnet:
+
if subnet.prefixLength == null then "${subnet.address}#${toString subnet.weight}"
+
else "${subnet.address}/${toString subnet.prefixLength}#${toString subnet.weight}")
+
config.subnets);
+
};
+
};
+
+
in
{
###### interface
···
networks = mkOption {
default = { };
-
type = with types; attrsOf (submodule {
+
type = with types; attrsOf (submodule ({ config, ... }: {
options = {
extraConfig = mkOption {
···
type = types.lines;
description = ''
Extra lines to add to the tinc service configuration file.
+
+
Note that using the declarative <option>service.tinc.networks.&lt;name&gt;.settings</option>
+
option is preferred.
'';
};
···
description = ''
The name of the host in the network as well as the configuration for that host.
This name should only contain alphanumerics and underscores.
+
+
Note that using the declarative <option>service.tinc.networks.&lt;name&gt;.hostSettings</option>
+
option is preferred.
+
'';
+
};
+
+
hostSettings = mkOption {
+
default = { };
+
example = literalExample ''
+
{
+
host1 = {
+
addresses = [
+
{ address = "192.168.1.42"; }
+
{ address = "192.168.1.42"; port = 1655; }
+
];
+
subnets = [ { address = "10.0.0.42"; } ];
+
rsaPublicKey = "...";
+
settings = {
+
Ed25519PublicKey = "...";
+
};
+
};
+
host2 = {
+
subnets = [ { address = "10.0.1.0"; prefixLength = 24; weight = 2; } ];
+
rsaPublicKey = "...";
+
settings = {
+
Compression = 10;
+
};
+
};
+
}
+
'';
+
type = types.attrsOf (types.submodule hostSubmodule);
+
description = ''
+
The name of the host in the network as well as the configuration for that host.
+
This name should only contain alphanumerics and underscores.
'';
};
···
default = "tun";
type = types.enum [ "tun" "tap" ];
description = ''
-
The type of virtual interface used for the network connection
+
The type of virtual interface used for the network connection.
'';
};
···
The chroot is performed after all the initialization is done, after writing pid files and opening network sockets.
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.
+
'';
+
};
+
+
settings = mkOption {
+
default = { };
+
type = types.submodule { freeformType = tincConfType; };
+
example = literalExample ''
+
{
+
Interface = "custom.interface";
+
DirectOnly = true;
+
Mode = "switch";
+
}
+
'';
+
description = ''
+
Configuration of the Tinc daemon for this network.
+
+
See <link xlink:href="https://tinc-vpn.org/documentation-1.1/Main-configuration-variables.html"/>
+
for supported values.
'';
};
};
-
});
+
+
config = {
+
hosts = mapAttrs
+
(hostname: host: ''
+
${toTincConf host.settings}
+
${host.rsaPublicKey}
+
'')
+
config.hostSettings;
+
+
settings = {
+
DeviceType = mkDefault config.interfaceType;
+
Name = mkDefault (if config.name == null then "$HOST" else config.name);
+
Ed25519PrivateKeyFile = mkIf (config.ed25519PrivateKeyFile != null) (mkDefault config.ed25519PrivateKeyFile);
+
PrivateKeyFile = mkIf (config.rsaPrivateKeyFile != null) (mkDefault config.rsaPrivateKeyFile);
+
ListenAddress = mkIf (config.listenAddress != null) (mkDefault config.listenAddress);
+
BindToAddress = mkIf (config.bindToAddress != null) (mkDefault config.bindToAddress);
+
};
+
};
+
}));
description = ''
Defines the tinc networks which will be started.
···
"tinc/${network}/tinc.conf" = {
mode = "0444";
text = ''
-
Name = ${if data.name == null then "$HOST" else data.name}
-
DeviceType = ${data.interfaceType}
-
${optionalString (data.ed25519PrivateKeyFile != null) "Ed25519PrivateKeyFile = ${data.ed25519PrivateKeyFile}"}
-
${optionalString (data.rsaPrivateKeyFile != null) "PrivateKeyFile = ${data.rsaPrivateKeyFile}"}
-
${optionalString (data.listenAddress != null) "ListenAddress = ${data.listenAddress}"}
-
${optionalString (data.bindToAddress != null) "BindToAddress = ${data.bindToAddress}"}
-
Interface = tinc.${network}
+
${toTincConf ({ Interface = "tinc.${network}"; } // data.settings)}
${data.extraConfig}
'';
};
···
};
+
meta.maintainers = with maintainers; [ minijackson ];
}