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}