networkConfiguration: let /* NOTE: We cannot just use simple string interpolation, because tinc's configuration format is not indentation-tolerant. Therefore, we do some programmatic concatenation magic to ensure that everything is indentation-free, without turning this file into a mess of wrong indentation. */ generateConfiguration = options: let keys = builtins.attrNames options; toPairs = map (key: {key = key; value = options.${key};}); createConfigEntries = map (item: "${item.key} = ${item.value}"); in builtins.concatStringsSep "\n" (createConfigEntries (toPairs keys)); mapAttrsetValues = mapper: attrset: builtins.listToAttrs (map (item: { name = item; value = mapper item attrset.${item}; }) (builtins.attrNames attrset)); in nodeName: { services.tinc.networks = { cryto = { debugLevel = networkConfiguration.debugLevel; ed25519PrivateKeyFile = networkConfiguration.nodes.${nodeName}.tincPrivateKeyFile; extraConfig = generateConfiguration { AutoConnect = "yes"; PingInterval = toString networkConfiguration.pingInterval; }; hosts = mapAttrsetValues (node: nodeConfiguration: generateConfiguration { Address = nodeConfiguration.ipv4; Subnet = "${nodeConfiguration.internalIpv4}/32"; Ed25519PublicKey = nodeConfiguration.tincPublicKey; }) networkConfiguration.nodes; }; }; networking.interfaces."tinc.cryto".ipv4.addresses = [{ address = networkConfiguration.nodes.${nodeName}.internalIpv4; prefixLength = 24; }]; networking.firewall = { allowedTCPPorts = [ 655 ]; allowedUDPPorts = [ 655 ]; trustedInterfaces = [ "tinc.cryto" ]; }; }