Merge pull request #330522 from xokdvium/dev/init-rathole
nixos/rathole: init module
This commit is contained in:
@@ -72,6 +72,8 @@
|
||||
|
||||
- [OpenGFW](https://github.com/apernet/OpenGFW), an implementation of the Great Firewall on Linux. Available as [services.opengfw](#opt-services.opengfw.enable).
|
||||
|
||||
- [Rathole](https://github.com/rapiz1/rathole), a lightweight and high-performance reverse proxy for NAT traversal. Available as [services.rathole](#opt-services.rathole.enable).
|
||||
|
||||
## Backward Incompatibilities {#sec-release-24.11-incompatibilities}
|
||||
|
||||
- `transmission` package has been aliased with a `trace` warning to `transmission_3`. Since [Transmission 4 has been released last year](https://github.com/transmission/transmission/releases/tag/4.0.0), and Transmission 3 will eventually go away, it was decided perform this warning alias to make people aware of the new version. The `services.transmission.package` defaults to `transmission_3` as well because the upgrade can cause data loss in certain specific usage patterns (examples: [#5153](https://github.com/transmission/transmission/issues/5153), [#6796](https://github.com/transmission/transmission/issues/6796)). Please make sure to back up to your data directory per your usage:
|
||||
|
||||
@@ -1165,6 +1165,7 @@
|
||||
./services/networking/r53-ddns.nix
|
||||
./services/networking/radicale.nix
|
||||
./services/networking/radvd.nix
|
||||
./services/networking/rathole.nix
|
||||
./services/networking/rdnssd.nix
|
||||
./services/networking/realm.nix
|
||||
./services/networking/redsocks.nix
|
||||
|
||||
165
nixos/modules/services/networking/rathole.nix
Normal file
165
nixos/modules/services/networking/rathole.nix
Normal file
@@ -0,0 +1,165 @@
|
||||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.rathole;
|
||||
settingsFormat = pkgs.formats.toml { };
|
||||
py-toml-merge =
|
||||
pkgs.writers.writePython3Bin "py-toml-merge"
|
||||
{
|
||||
libraries = with pkgs.python3Packages; [
|
||||
tomli-w
|
||||
mergedeep
|
||||
];
|
||||
}
|
||||
''
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import tomli_w
|
||||
import tomllib
|
||||
from mergedeep import merge
|
||||
|
||||
parser = argparse.ArgumentParser(description="Merge multiple TOML files")
|
||||
parser.add_argument(
|
||||
"files",
|
||||
type=Path,
|
||||
nargs="+",
|
||||
help="List of TOML files to merge",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
merged: dict[str, Any] = {}
|
||||
|
||||
for file in args.files:
|
||||
with open(file, "rb") as fh:
|
||||
loaded_toml = tomllib.load(fh)
|
||||
merged = merge(merged, loaded_toml)
|
||||
|
||||
print(tomli_w.dumps(merged))
|
||||
'';
|
||||
in
|
||||
|
||||
{
|
||||
options = {
|
||||
services.rathole = {
|
||||
enable = lib.mkEnableOption "Rathole";
|
||||
|
||||
package = lib.mkPackageOption pkgs "rathole" { };
|
||||
|
||||
role = lib.mkOption {
|
||||
type = lib.types.enum [
|
||||
"server"
|
||||
"client"
|
||||
];
|
||||
description = ''
|
||||
Select whether rathole needs to be run as a `client` or a `server`.
|
||||
Server is a machine with a public IP and client is a device behind NAT,
|
||||
but running some services that need to be exposed to the Internet.
|
||||
'';
|
||||
};
|
||||
|
||||
credentialsFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = "/dev/null";
|
||||
description = ''
|
||||
Path to a TOML file to be merged with the settings.
|
||||
Useful to set secret config parameters like tokens, which
|
||||
should not appear in the Nix Store.
|
||||
'';
|
||||
example = "/var/lib/secrets/rathole/config.toml";
|
||||
};
|
||||
|
||||
settings = lib.mkOption {
|
||||
type = settingsFormat.type;
|
||||
default = { };
|
||||
description = ''
|
||||
Rathole configuration, for options reference
|
||||
see the [example](https://github.com/rapiz1/rathole?tab=readme-ov-file#configuration) on GitHub.
|
||||
Both server and client configurations can be specified at the same time, regardless of the selected role.
|
||||
'';
|
||||
example = {
|
||||
server = {
|
||||
bind_addr = "0.0.0.0:2333";
|
||||
services.my_nas_ssh = {
|
||||
token = "use_a_secret_that_only_you_know";
|
||||
bind_addr = "0.0.0.0:5202";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
systemd.services.rathole = {
|
||||
requires = [ "network.target" ];
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
description = "Rathole ${cfg.role} Service";
|
||||
|
||||
serviceConfig =
|
||||
let
|
||||
name = "rathole";
|
||||
configFile = settingsFormat.generate "${name}.toml" cfg.settings;
|
||||
runtimeDir = "/run/${name}";
|
||||
ratholePrestart =
|
||||
"+"
|
||||
+ (pkgs.writeShellScript "rathole-prestart" ''
|
||||
DYNUSER_UID=$(stat -c %u ${runtimeDir})
|
||||
DYNUSER_GID=$(stat -c %g ${runtimeDir})
|
||||
${lib.getExe py-toml-merge} ${configFile} '${cfg.credentialsFile}' |
|
||||
install -m 600 -o $DYNUSER_UID -g $DYNUSER_GID /dev/stdin ${runtimeDir}/${mergedConfigName}
|
||||
'');
|
||||
mergedConfigName = "merged.toml";
|
||||
in
|
||||
{
|
||||
Type = "simple";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 5;
|
||||
ExecStartPre = ratholePrestart;
|
||||
ExecStart = "${lib.getExe cfg.package} --${cfg.role} ${runtimeDir}/${mergedConfigName}";
|
||||
DynamicUser = true;
|
||||
LimitNOFILE = "1048576";
|
||||
RuntimeDirectory = name;
|
||||
RuntimeDirectoryMode = "0700";
|
||||
# Hardening
|
||||
AmbientCapabilities = "CAP_NET_BIND_SERVICE";
|
||||
CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
PrivateDevices = true;
|
||||
PrivateMounts = true;
|
||||
PrivateTmp = true;
|
||||
# PrivateUsers=true breaks AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
ProcSubset = "pid";
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectProc = "invisible";
|
||||
ProtectSystem = "strict";
|
||||
RemoveIPC = true;
|
||||
RestrictAddressFamilies = [
|
||||
"AF_INET"
|
||||
"AF_INET6"
|
||||
];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
SystemCallArchitectures = "native";
|
||||
UMask = "0066";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ xokdvium ];
|
||||
}
|
||||
@@ -830,6 +830,7 @@ in {
|
||||
radicle = runTest ./radicle.nix;
|
||||
ragnarwm = handleTest ./ragnarwm.nix {};
|
||||
rasdaemon = handleTest ./rasdaemon.nix {};
|
||||
rathole = handleTest ./rathole.nix {};
|
||||
readarr = handleTest ./readarr.nix {};
|
||||
realm = handleTest ./realm.nix {};
|
||||
redis = handleTest ./redis.nix {};
|
||||
|
||||
89
nixos/tests/rathole.nix
Normal file
89
nixos/tests/rathole.nix
Normal file
@@ -0,0 +1,89 @@
|
||||
import ./make-test-python.nix (
|
||||
{ pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
successMessage = "Success 3333115147933743662";
|
||||
in
|
||||
{
|
||||
name = "rathole";
|
||||
meta.maintainers = with lib.maintainers; [ xokdvium ];
|
||||
nodes = {
|
||||
server = {
|
||||
networking = {
|
||||
useNetworkd = true;
|
||||
useDHCP = false;
|
||||
firewall.enable = false;
|
||||
};
|
||||
|
||||
systemd.network.networks."01-eth1" = {
|
||||
name = "eth1";
|
||||
networkConfig.Address = "10.0.0.1/24";
|
||||
};
|
||||
|
||||
services.rathole = {
|
||||
enable = true;
|
||||
role = "server";
|
||||
settings = {
|
||||
server = {
|
||||
bind_addr = "0.0.0.0:2333";
|
||||
services = {
|
||||
success-message = {
|
||||
bind_addr = "0.0.0.0:80";
|
||||
token = "hunter2";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
client = {
|
||||
networking = {
|
||||
useNetworkd = true;
|
||||
useDHCP = false;
|
||||
};
|
||||
|
||||
systemd.network.networks."01-eth1" = {
|
||||
name = "eth1";
|
||||
networkConfig.Address = "10.0.0.2/24";
|
||||
};
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
virtualHosts."127.0.0.1" = {
|
||||
root = pkgs.writeTextDir "success-message.txt" successMessage;
|
||||
};
|
||||
};
|
||||
|
||||
services.rathole = {
|
||||
enable = true;
|
||||
role = "client";
|
||||
credentialsFile = pkgs.writeText "rathole-credentials.toml" ''
|
||||
[client.services.success-message]
|
||||
token = "hunter2"
|
||||
'';
|
||||
settings = {
|
||||
client = {
|
||||
remote_addr = "10.0.0.1:2333";
|
||||
services.success-message = {
|
||||
local_addr = "127.0.0.1:80";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
server.wait_for_unit("rathole.service")
|
||||
server.wait_for_open_port(2333)
|
||||
client.wait_for_unit("rathole.service")
|
||||
server.wait_for_open_port(80)
|
||||
response = server.succeed("curl http://127.0.0.1/success-message.txt")
|
||||
assert "${successMessage}" in response, "Got invalid response"
|
||||
response = client.succeed("curl http://10.0.0.1/success-message.txt")
|
||||
assert "${successMessage}" in response, "Got invalid response"
|
||||
'';
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user