9 cfg = config.services.rathole;
10 settingsFormat = pkgs.formats.toml { };
12 pkgs.writers.writePython3Bin "py-toml-merge"
14 libraries = with pkgs.python3Packages; [
21 from pathlib import Path
22 from typing import Any
26 from mergedeep import merge
28 parser = argparse.ArgumentParser(description="Merge multiple TOML files")
33 help="List of TOML files to merge",
36 args = parser.parse_args()
37 merged: dict[str, Any] = {}
39 for file in args.files:
40 with open(file, "rb") as fh:
41 loaded_toml = tomllib.load(fh)
42 merged = merge(merged, loaded_toml)
44 print(tomli_w.dumps(merged))
51 enable = lib.mkEnableOption "Rathole";
53 package = lib.mkPackageOption pkgs "rathole" { };
56 type = lib.types.enum [
61 Select whether rathole needs to be run as a `client` or a `server`.
62 Server is a machine with a public IP and client is a device behind NAT,
63 but running some services that need to be exposed to the Internet.
67 credentialsFile = lib.mkOption {
68 type = lib.types.path;
69 default = "/dev/null";
71 Path to a TOML file to be merged with the settings.
72 Useful to set secret config parameters like tokens, which
73 should not appear in the Nix Store.
75 example = "/var/lib/secrets/rathole/config.toml";
78 settings = lib.mkOption {
79 type = settingsFormat.type;
82 Rathole configuration, for options reference
83 see the [example](https://github.com/rapiz1/rathole?tab=readme-ov-file#configuration) on GitHub.
84 Both server and client configurations can be specified at the same time, regardless of the selected role.
88 bind_addr = "0.0.0.0:2333";
89 services.my_nas_ssh = {
90 token = "use_a_secret_that_only_you_know";
91 bind_addr = "0.0.0.0:5202";
99 config = lib.mkIf cfg.enable {
100 systemd.services.rathole = {
101 requires = [ "network.target" ];
102 after = [ "network.target" ];
103 wantedBy = [ "multi-user.target" ];
104 description = "Rathole ${cfg.role} Service";
109 configFile = settingsFormat.generate "${name}.toml" cfg.settings;
110 runtimeDir = "/run/${name}";
113 + (pkgs.writeShellScript "rathole-prestart" ''
114 DYNUSER_UID=$(stat -c %u ${runtimeDir})
115 DYNUSER_GID=$(stat -c %g ${runtimeDir})
116 ${lib.getExe py-toml-merge} ${configFile} '${cfg.credentialsFile}' |
117 install -m 600 -o $DYNUSER_UID -g $DYNUSER_GID /dev/stdin ${runtimeDir}/${mergedConfigName}
119 mergedConfigName = "merged.toml";
123 Restart = "on-failure";
125 ExecStartPre = ratholePrestart;
126 ExecStart = "${lib.getExe cfg.package} --${cfg.role} ${runtimeDir}/${mergedConfigName}";
128 LimitNOFILE = "1048576";
129 RuntimeDirectory = name;
130 RuntimeDirectoryMode = "0700";
132 AmbientCapabilities = "CAP_NET_BIND_SERVICE";
133 CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
134 LockPersonality = true;
135 MemoryDenyWriteExecute = true;
136 PrivateDevices = true;
137 PrivateMounts = true;
139 # PrivateUsers=true breaks AmbientCapabilities=CAP_NET_BIND_SERVICE
142 ProtectControlGroups = true;
144 ProtectHostname = true;
145 ProtectKernelLogs = true;
146 ProtectKernelModules = true;
147 ProtectKernelTunables = true;
148 ProtectProc = "invisible";
149 ProtectSystem = "strict";
151 RestrictAddressFamilies = [
155 RestrictNamespaces = true;
156 RestrictRealtime = true;
157 RestrictSUIDSGID = true;
158 SystemCallArchitectures = "native";
164 meta.maintainers = with lib.maintainers; [ xokdvium ];