diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 9ccb6dd205e4..7327d4ac4dfd 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -620,6 +620,7 @@
./services/networking/supplicant.nix
./services/networking/supybot.nix
./services/networking/syncthing.nix
+ ./services/networking/syncthing-relay.nix
./services/networking/tcpcrypt.nix
./services/networking/teamspeak3.nix
./services/networking/tinc.nix
diff --git a/nixos/modules/services/networking/syncthing-relay.nix b/nixos/modules/services/networking/syncthing-relay.nix
new file mode 100644
index 000000000000..f5ca63e78930
--- /dev/null
+++ b/nixos/modules/services/networking/syncthing-relay.nix
@@ -0,0 +1,121 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.syncthing.relay;
+
+ dataDirectory = "/var/lib/syncthing-relay";
+
+ relayOptions =
+ [
+ "--keys=${dataDirectory}"
+ "--listen=${cfg.listenAddress}:${toString cfg.port}"
+ "--status-srv=${cfg.statusListenAddress}:${toString cfg.statusPort}"
+ "--provided-by=${escapeShellArg cfg.providedBy}"
+ ]
+ ++ optional (cfg.pools != null) "--pools=${escapeShellArg (concatStringsSep "," cfg.pools)}"
+ ++ optional (cfg.globalRateBps != null) "--global-rate=${toString cfg.globalRateBps}"
+ ++ optional (cfg.perSessionRateBps != null) "--per-session-rate=${toString cfg.perSessionRateBps}"
+ ++ cfg.extraOptions;
+in {
+ ###### interface
+
+ options.services.syncthing.relay = {
+ enable = mkEnableOption "Syncthing relay service";
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "";
+ example = "1.2.3.4";
+ description = ''
+ Address to listen on for relay traffic.
+ '';
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = 22067;
+ description = ''
+ Port to listen on for relay traffic. This port should be added to
+ networking.firewall.allowedTCPPorts.
+ '';
+ };
+
+ statusListenAddress = mkOption {
+ type = types.str;
+ default = "";
+ example = "1.2.3.4";
+ description = ''
+ Address to listen on for serving the relay status API.
+ '';
+ };
+
+ statusPort = mkOption {
+ type = types.port;
+ default = 22070;
+ description = ''
+ Port to listen on for serving the relay status API. This port should be
+ added to networking.firewall.allowedTCPPorts.
+ '';
+ };
+
+ pools = mkOption {
+ type = types.nullOr (types.listOf types.str);
+ default = null;
+ description = ''
+ Relay pools to join. If null, uses the default global pool.
+ '';
+ };
+
+ providedBy = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Human-readable description of the provider of the relay (you).
+ '';
+ };
+
+ globalRateBps = mkOption {
+ type = types.nullOr types.ints.positive;
+ default = null;
+ description = ''
+ Global bandwidth rate limit in bytes per second.
+ '';
+ };
+
+ perSessionRateBps = mkOption {
+ type = types.nullOr types.ints.positive;
+ default = null;
+ description = ''
+ Per session bandwidth rate limit in bytes per second.
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Extra command line arguments to pass to strelaysrv.
+ '';
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ systemd.services.syncthing-relay = {
+ description = "Syncthing relay service";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ DynamicUser = true;
+ StateDirectory = baseNameOf dataDirectory;
+
+ Restart = "on-failure";
+ ExecStart = "${pkgs.syncthing-relay}/bin/strelaysrv ${concatStringsSep " " relayOptions}";
+ };
+ };
+ };
+}
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index ec7178ec9cad..0d5a747b5c56 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -193,6 +193,7 @@ in
strongswan-swanctl = handleTest ./strongswan-swanctl.nix {};
sudo = handleTest ./sudo.nix {};
switchTest = handleTest ./switch-test.nix {};
+ syncthing-relay = handleTest ./syncthing-relay.nix {};
systemd = handleTest ./systemd.nix {};
taskserver = handleTest ./taskserver.nix {};
tomcat = handleTest ./tomcat.nix {};
diff --git a/nixos/tests/syncthing-relay.nix b/nixos/tests/syncthing-relay.nix
new file mode 100644
index 000000000000..f1ceb4993337
--- /dev/null
+++ b/nixos/tests/syncthing-relay.nix
@@ -0,0 +1,22 @@
+import ./make-test.nix ({ lib, pkgs, ... }: {
+ name = "syncthing-relay";
+ meta.maintainers = with pkgs.stdenv.lib.maintainers; [ delroth ];
+
+ machine = {
+ environment.systemPackages = [ pkgs.jq ];
+ services.syncthing.relay = {
+ enable = true;
+ providedBy = "nixos-test";
+ pools = []; # Don't connect to any pool while testing.
+ port = 12345;
+ statusPort = 12346;
+ };
+ };
+
+ testScript = ''
+ $machine->waitForUnit("syncthing-relay.service");
+ $machine->waitForOpenPort(12345);
+ $machine->waitForOpenPort(12346);
+ $machine->succeed("curl http://localhost:12346/status | jq -r '.options.\"provided-by\"'") =~ /nixos-test/ or die;
+ '';
+})