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 Restart = "on-failure";
161 };
162 preStart = ''
163 mkdir -p /etc/tinc/${network}/hosts
164
165 # Determine how we should generate our keys
166 if type tinc >/dev/null 2>&1; then
167 # Tinc 1.1+ uses the tinc helper application for key generation
168 ${if data.ed25519PrivateKeyFile != null then " # Keyfile managed by nix" else ''
169 # Prefer ED25519 keys (only in 1.1+)
170 [ -f "/etc/tinc/${network}/ed25519_key.priv" ] || tinc -n ${network} generate-ed25519-keys
171 ''}
172 # Otherwise use RSA keys
173 [ -f "/etc/tinc/${network}/rsa_key.priv" ] || tinc -n ${network} generate-rsa-keys 4096
174 else
175 # Tinc 1.0 uses the tincd application
176 [ -f "/etc/tinc/${network}/rsa_key.priv" ] || tincd -n ${network} -K 4096
177 fi
178 '';
179 script = ''
180 tincd -D -U tinc.${network} -n ${network} ${optionalString (data.chroot) "-R"} --pidfile /run/tinc.${network}.pid -d ${toString data.debugLevel}
181 '';
182 })
183 );
184
185 users.extraUsers = flip mapAttrs' cfg.networks (network: _:
186 nameValuePair ("tinc.${network}") ({
187 description = "Tinc daemon user for ${network}";
188 isSystemUser = true;
189 })
190 );
191
192 };
193
194}