20 cfg = config.services.rosenpass;
21 opt = options.services.rosenpass;
22 settingsFormat = pkgs.formats.toml { };
25 options.services.rosenpass =
41 enable = lib.mkEnableOption "Rosenpass";
43 package = lib.mkPackageOption pkgs "rosenpass" { };
45 defaultDevice = mkOption {
47 description = "Name of the network interface to use for all peers by default.";
53 freeformType = settingsFormat.type;
56 public_key = mkOption {
58 description = "Path to a file containing the public key of the local Rosenpass peer. Generate this by running {command}`rosenpass gen-keys`.";
61 secret_key = mkOption {
63 description = "Path to a file containing the secret key of the local Rosenpass peer. Generate this by running {command}`rosenpass gen-keys`.";
68 description = "List of local endpoints to listen for connections.";
70 example = literalExpression "[ \"0.0.0.0:10000\" ]";
73 verbosity = mkOption {
74 type = enum [ "Verbose" "Quiet" ];
76 description = "Verbosity of output produced by the service.";
82 freeformType = settingsFormat.type;
85 public_key = mkOption {
87 description = "Path to a file containing the public key of the remote Rosenpass peer.";
93 description = "Endpoint of the remote Rosenpass peer.";
98 default = cfg.defaultDevice;
99 defaultText = literalExpression "config.${opt.defaultDevice}";
100 description = "Name of the local WireGuard interface to use for this peer.";
105 description = "WireGuard public key corresponding to the remote Rosenpass peer.";
112 description = "List of peers to exchange keys with.";
118 description = "Configuration for Rosenpass, see <https://rosenpass.eu/> for further information.";
122 config = mkIf cfg.enable {
125 # NOTE: In the descriptions below, we tried to refer to e.g.
126 # options.systemd.network.netdevs."<name>".wireguardPeers.*.PublicKey
127 # directly, but don't know how to traverse "<name>" and * in this path.
130 relevant = config.systemd.network.enable;
131 root = config.systemd.network.netdevs;
132 peer = (x: x.wireguardPeers);
133 key = x: x.PublicKey or null;
134 description = "${options.systemd.network.netdevs}.\"<name>\".wireguardPeers.*.PublicKey";
137 relevant = config.networking.wireguard.enable;
138 root = config.networking.wireguard.interfaces;
140 key = (x: x.publicKey);
141 description = "${options.networking.wireguard.interfaces}.\"<name>\".peers.*.publicKey";
144 relevant = root != { };
145 root = config.networking.wg-quick.interfaces;
147 key = (x: x.publicKey);
148 description = "${options.networking.wg-quick.interfaces}.\"<name>\".peers.*.publicKey";
151 relevantExtractions = filter (x: x.relevant) extractions;
152 extract = { root, peer, key, ... }:
153 filter (x: x != null) (flatten (concatMap (x: (map key (peer x))) (attrValues root)));
154 configuredKeys = flatten (map extract relevantExtractions);
155 itemize = xs: concatLines (map (x: " - ${x}") xs);
156 descriptions = map (x: "`${x.description}`");
157 missingKeys = filter (key: !builtins.elem key configuredKeys) (map (x: x.peer) cfg.settings.peers);
159 While this may work as expected, e.g. you want to manually configure WireGuard,
160 such a scenario is unusual. Please double-check your configuration.
163 (optional (relevantExtractions != [ ] && missingKeys != [ ]) ''
164 You have configured Rosenpass peers with the WireGuard public keys:
165 ${itemize missingKeys}
166 But there is no corresponding active Wireguard peer configuration in any of:
167 ${itemize (descriptions relevantExtractions)}
171 optional (relevantExtractions == [ ]) ''
172 You have configured Rosenpass, but you have not configured Wireguard via any of:
173 ${itemize (descriptions extractions)}
177 environment.systemPackages = [ cfg.package pkgs.wireguard-tools ];
179 systemd.services.rosenpass =
181 filterNonNull = filterAttrsRecursive (_: v: v != null);
182 config = settingsFormat.generate "config.toml" (
183 filterNonNull (cfg.settings
187 credentialPath = id: "$CREDENTIALS_DIRECTORY/${id}";
188 # NOTE: We would like to remove all `null` values inside `cfg.settings`
189 # recursively, since `settingsFormat.generate` cannot handle `null`.
190 # This would require to traverse both attribute sets and lists recursively.
191 # `filterAttrsRecursive` only recurses into attribute sets, but not
192 # into values that might contain other attribute sets (such as lists,
193 # e.g. `cfg.settings.peers`). Here, we just specialize on `cfg.settings.peers`,
194 # and this may break unexpectedly whenever a `null` value is contained
195 # in a list in `cfg.settings`, other than `cfg.settings.peers`.
196 peersWithoutNulls = map filterNonNull cfg.settings.peers;
199 secret_key = credentialPath "pqsk";
200 public_key = credentialPath "pqpk";
201 peers = peersWithoutNulls;
208 wantedBy = [ "multi-user.target" ];
209 wants = [ "network-online.target" ];
210 after = [ "network-online.target" ];
211 path = [ cfg.package pkgs.wireguard-tools ];
216 RuntimeDirectory = "rosenpass";
218 AmbientCapabilities = [ "CAP_NET_ADMIN" ];
220 "pqsk:${cfg.settings.secret_key}"
221 "pqpk:${cfg.settings.public_key}"
225 # See <https://www.freedesktop.org/software/systemd/man/systemd.unit.html#Specifiers>
226 environment.CONFIG = "%t/${serviceConfig.RuntimeDirectory}/config.toml";
229 ${getExe pkgs.envsubst} -i ${config} -o "$CONFIG"
230 rosenpass exchange-config "$CONFIG"