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 = types.loaOf types.optionSet;
22 description = ''
23 Defines the tinc networks which will be started.
24 Each network invokes a different daemon.
25 '';
26 options = {
27
28 extraConfig = mkOption {
29 default = "";
30 type = types.lines;
31 description = ''
32 Extra lines to add to the tinc service configuration file.
33 '';
34 };
35
36 name = mkOption {
37 default = null;
38 type = types.nullOr types.str;
39 description = ''
40 The name of the node which is used as an identifier when communicating
41 with the remote nodes in the mesh. If null then the hostname of the system
42 is used.
43 '';
44 };
45
46 ed25519PrivateKeyFile = mkOption {
47 default = null;
48 type = types.nullOr types.path;
49 description = ''
50 Path of the private ed25519 keyfile.
51 '';
52 };
53
54 debugLevel = mkOption {
55 default = 0;
56 type = types.addCheck types.int (l: l >= 0 && l <= 5);
57 description = ''
58 The amount of debugging information to add to the log. 0 means little
59 logging while 5 is the most logging. <command>man tincd</command> for
60 more details.
61 '';
62 };
63
64 hosts = mkOption {
65 default = { };
66 type = types.loaOf types.lines;
67 description = ''
68 The name of the host in the network as well as the configuration for that host.
69 This name should only contain alphanumerics and underscores.
70 '';
71 };
72
73 interfaceType = mkOption {
74 default = "tun";
75 type = types.addCheck types.str (n: n == "tun" || n == "tap");
76 description = ''
77 The type of virtual interface used for the network connection
78 '';
79 };
80
81 listenAddress = mkOption {
82 default = null;
83 type = types.nullOr types.str;
84 description = ''
85 The ip adress to bind to.
86 '';
87 };
88
89 package = mkOption {
90 type = types.package;
91 default = pkgs.tinc_pre;
92 defaultText = "pkgs.tinc_pre";
93 description = ''
94 The package to use for the tinc daemon's binary.
95 '';
96 };
97
98 chroot = mkOption {
99 default = true;
100 type = types.bool;
101 description = ''
102 Change process root directory to the directory where the config file is located (/etc/tinc/netname/), for added security.
103 The chroot is performed after all the initialization is done, after writing pid files and opening network sockets.
104
105 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.
106 '';
107 };
108 };
109 };
110 };
111
112 };
113
114
115 ###### implementation
116
117 config = mkIf (cfg.networks != { }) {
118
119 environment.etc = fold (a: b: a // b) { }
120 (flip mapAttrsToList cfg.networks (network: data:
121 flip mapAttrs' data.hosts (host: text: nameValuePair
122 ("tinc/${network}/hosts/${host}")
123 ({ mode = "0444"; inherit text; })
124 ) // {
125 "tinc/${network}/tinc.conf" = {
126 mode = "0444";
127 text = ''
128 Name = ${if data.name == null then "$HOST" else data.name}
129 DeviceType = ${data.interfaceType}
130 ${optionalString (data.ed25519PrivateKeyFile != null) "Ed25519PrivateKeyFile = ${data.ed25519PrivateKeyFile}"}
131 ${optionalString (data.listenAddress != null) "BindToAddress = ${data.listenAddress}"}
132 Device = /dev/net/tun
133 Interface = tinc.${network}
134 ${data.extraConfig}
135 '';
136 };
137 }
138 ));
139
140 networking.interfaces = flip mapAttrs' cfg.networks (network: data: nameValuePair
141 ("tinc.${network}")
142 ({
143 virtual = true;
144 virtualType = "${data.interfaceType}";
145 })
146 );
147
148 systemd.services = flip mapAttrs' cfg.networks (network: data: nameValuePair
149 ("tinc.${network}")
150 ({
151 description = "Tinc Daemon - ${network}";
152 wantedBy = [ "network.target" ];
153 after = [ "network-interfaces.target" ];
154 path = [ data.package ];
155 restartTriggers = [ config.environment.etc."tinc/${network}/tinc.conf".source ]
156 ++ mapAttrsToList (host: _ : config.environment.etc."tinc/${network}/hosts/${host}".source) data.hosts;
157 serviceConfig = {
158 Type = "simple";
159 PIDFile = "/run/tinc.${network}.pid";
160 };
161 preStart = ''
162 mkdir -p /etc/tinc/${network}/hosts
163
164 # Determine how we should generate our keys
165 if type tinc >/dev/null 2>&1; then
166 # Tinc 1.1+ uses the tinc helper application for key generation
167 ${if data.ed25519PrivateKeyFile != null then " # Keyfile managed by nix" else ''
168 # Prefer ED25519 keys (only in 1.1+)
169 [ -f "/etc/tinc/${network}/ed25519_key.priv" ] || tinc -n ${network} generate-ed25519-keys
170 ''}
171 # Otherwise use RSA keys
172 [ -f "/etc/tinc/${network}/rsa_key.priv" ] || tinc -n ${network} generate-rsa-keys 4096
173 else
174 # Tinc 1.0 uses the tincd application
175 [ -f "/etc/tinc/${network}/rsa_key.priv" ] || tincd -n ${network} -K 4096
176 fi
177 '';
178 script = ''
179 tincd -D -U tinc.${network} -n ${network} ${optionalString (data.chroot) "-R"} --pidfile /run/tinc.${network}.pid -d ${toString data.debugLevel}
180 '';
181 })
182 );
183
184 users.extraUsers = flip mapAttrs' cfg.networks (network: _:
185 nameValuePair ("tinc.${network}") ({
186 description = "Tinc daemon user for ${network}";
187 isSystemUser = true;
188 })
189 );
190
191 };
192
193}