nixos/rnsd: init
This commit is contained in:
@@ -122,6 +122,18 @@
|
||||
"module-services-onedrive": [
|
||||
"index.html#module-services-onedrive"
|
||||
],
|
||||
"module-services-rnsd": [
|
||||
"index.html#module-services-rnsd"
|
||||
],
|
||||
"module-services-rnsd-hardware-access": [
|
||||
"index.html#module-services-rnsd-hardware-access"
|
||||
],
|
||||
"module-services-rnsd-health-check": [
|
||||
"index.html#module-services-rnsd-health-check"
|
||||
],
|
||||
"module-services-rnsd-quickstart": [
|
||||
"index.html#module-services-rnsd-quickstart"
|
||||
],
|
||||
"module-services-tandoor-recipes-migrating-media-option-move": [
|
||||
"index.html#module-services-tandoor-recipes-migrating-media-option-move",
|
||||
"index.html#module-services-tandoor-recipes-migrating-media-option-1"
|
||||
|
||||
@@ -58,6 +58,8 @@
|
||||
|
||||
- [NordVPN](https://github.com/NordSecurity/nordvpn-linux), a NordVPN client for linux. Available as [services.nordvpn](options.html#opt-services.nordvpn.enable).
|
||||
|
||||
- [RNSD](https://reticulum.network/), the Reticulum Network Stack Daemon. It provides a secure and efficient way to communicate over the Reticulum Network. Available as [services.rnsd](#opt-services.rnsd.enable).
|
||||
|
||||
- [LXMD](https://github.com/markqvist/LXMF), a universal, distributed and secure messaging protocol for Reticulum. Available as [services.lxmd](#opt-services.lxmd.enable).
|
||||
|
||||
## Backward Incompatibilities {#sec-release-26.11-incompatibilities}
|
||||
|
||||
@@ -1388,6 +1388,7 @@
|
||||
./services/networking/redsocks.nix
|
||||
./services/networking/reframe.nix
|
||||
./services/networking/resilio.nix
|
||||
./services/networking/rnsd.nix
|
||||
./services/networking/robustirc-bridge.nix
|
||||
./services/networking/rosenpass.nix
|
||||
./services/networking/routedns.nix
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
# RNSD {#module-services-rnsd}
|
||||
|
||||
[Reticulum Network Stack](https://reticulum.network/) daemon (`rnsd`).
|
||||
|
||||
This module manages `rnsd` as a systemd service and stores runtime state under
|
||||
`/var/lib/rnsd`.
|
||||
|
||||
## Quickstart {#module-services-rnsd-quickstart}
|
||||
|
||||
A minimal setup:
|
||||
|
||||
```nix
|
||||
{
|
||||
services.rnsd.enable = true;
|
||||
}
|
||||
```
|
||||
|
||||
With custom settings and a persistent identity:
|
||||
|
||||
```nix
|
||||
{
|
||||
services.rnsd = {
|
||||
enable = true;
|
||||
|
||||
identityFile = "<path-to-rnsd-identity-file>";
|
||||
|
||||
settings = {
|
||||
reticulum = {
|
||||
enable_transport = true;
|
||||
share_instance = true;
|
||||
instance_name = "default";
|
||||
shared_instance_type = "unix";
|
||||
};
|
||||
interfaces = {
|
||||
auto = {
|
||||
type = "AutoInterface";
|
||||
enabled = true;
|
||||
};
|
||||
};
|
||||
openMulticastPorts = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
At startup, settings provided through
|
||||
[`services.rnsd.settings`](#opt-services.rnsd.settings) and
|
||||
[`services.rnsd.identityFile`](#opt-services.rnsd.identityFile)
|
||||
are copied into the service state directory with restrictive permissions.
|
||||
|
||||
## Hardware Access {#module-services-rnsd-hardware-access}
|
||||
|
||||
If `rnsd` must access serial devices (for example `/dev/ttyACM0`), add the
|
||||
service user to additional groups:
|
||||
|
||||
```nix
|
||||
{
|
||||
services.rnsd = {
|
||||
enable = true;
|
||||
extraGroups = [ "dialout" ];
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
See [`services.rnsd.extraGroups`](#opt-services.rnsd.extraGroups) for details.
|
||||
|
||||
## Health Check {#module-services-rnsd-health-check}
|
||||
|
||||
You can optionally wait for `rnsd` to become responsive during startup using
|
||||
`rnstatus`:
|
||||
|
||||
```nix
|
||||
{
|
||||
services.rnsd = {
|
||||
enable = true;
|
||||
|
||||
healthCheck = {
|
||||
enable = true;
|
||||
intervalSeconds = 2;
|
||||
timeoutSeconds = 120;
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
When enabled, startup fails if `rnstatus` does not succeed before
|
||||
[`services.rnsd.healthCheck.timeoutSeconds`](#opt-services.rnsd.healthCheck.timeoutSeconds).
|
||||
@@ -0,0 +1,171 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.services.rnsd;
|
||||
settingsFormat = pkgs.formats.configobj { };
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.rnsd = {
|
||||
enable = lib.mkEnableOption "Enable Reticulum Network Stack Daemon (rnsd)";
|
||||
|
||||
package = lib.mkPackageOption pkgs "rns" { };
|
||||
packageBinaryName = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "rnsd";
|
||||
description = "Name of the program to use. Useful in case of using an alternative implementation.";
|
||||
};
|
||||
|
||||
settings = lib.mkOption {
|
||||
type = lib.types.nullOr settingsFormat.type;
|
||||
default = null;
|
||||
description = "Structured rnsd configuration. The generated file is copied to the dataDir on service start. Use `rnsd --exampleconfig` to get an example config file.";
|
||||
};
|
||||
|
||||
identityFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Path to rnsd identity file. This file will be copied to the dataDir on service start.";
|
||||
};
|
||||
|
||||
identities = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.str;
|
||||
default = { };
|
||||
description = "Map of identity names to paths of identity files. Each identity file will be copied to $STATE_DIRECTORY/storage/identities/{name}.";
|
||||
};
|
||||
|
||||
extraGroups = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "Additional groups for the rnsd service user. E.g., add the `dialout` group to allow rnsd to access serial devices (e.g., `/dev/ttyACM0`).";
|
||||
};
|
||||
|
||||
healthCheck = {
|
||||
enable = lib.mkEnableOption "wait for rnsd to become healthy after startup";
|
||||
|
||||
intervalSeconds = lib.mkOption {
|
||||
type = lib.types.ints.positive;
|
||||
default = 2;
|
||||
description = "Delay in seconds between each `rnstatus` health check attempt.";
|
||||
};
|
||||
|
||||
timeoutSeconds = lib.mkOption {
|
||||
type = lib.types.ints.positive;
|
||||
default = 120;
|
||||
description = "Maximum time in seconds to wait for `rnstatus` to succeed during startup.";
|
||||
};
|
||||
};
|
||||
|
||||
openMulticastPorts = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to open the UDP ports (27916, 42671) used for multicast peer discovery when the AutoInterface is enabled.
|
||||
'';
|
||||
};
|
||||
|
||||
enableUdevRules = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether to install udev rules for automatically symlinking RNode devices (e.g., Heltec HT-n5262, RAK4631) to /dev/rnode.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
services.udev.extraRules = lib.mkIf cfg.enableUdevRules (
|
||||
let
|
||||
devices = [
|
||||
{
|
||||
idVendor = "239a";
|
||||
idProduct = "8071"; # Heltec HT-n5262
|
||||
}
|
||||
{
|
||||
idVendor = "239a";
|
||||
idProduct = "8029"; # RAK4631
|
||||
}
|
||||
];
|
||||
|
||||
makeRule = dev: ''
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="${dev.idVendor}", ATTRS{idProduct}=="${dev.idProduct}", SYMLINK+="rnode%n", MODE="0660", GROUP="dialout"
|
||||
'';
|
||||
in
|
||||
builtins.concatStringsSep "\n" (map makeRule devices)
|
||||
);
|
||||
|
||||
systemd.services.rnsd = {
|
||||
description = "Reticulum Network Stack Daemon";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
|
||||
preStart =
|
||||
let
|
||||
copySettings = lib.optionalString (cfg.settings != null) ''
|
||||
install -Dm600 ${settingsFormat.generate "rnsd.conf" cfg.settings} "$STATE_DIRECTORY"/config
|
||||
'';
|
||||
copyIdentity = lib.optionalString (cfg.identityFile != null) ''
|
||||
install -Dm600 ${cfg.identityFile} "$STATE_DIRECTORY"/storage/transport_identity
|
||||
'';
|
||||
copyIdentities = lib.concatStringsSep "\n" (
|
||||
lib.mapAttrsToList (name: file: ''
|
||||
install -Dm600 ${file} "$STATE_DIRECTORY"/storage/identities/${name}
|
||||
'') cfg.identities
|
||||
);
|
||||
in
|
||||
copySettings + copyIdentity + copyIdentities;
|
||||
|
||||
serviceConfig =
|
||||
let
|
||||
waitForHealthy = pkgs.writeShellApplication {
|
||||
name = "rnsd-wait-for-health";
|
||||
runtimeInputs = [ pkgs.rns ];
|
||||
text = ''
|
||||
deadline=$((SECONDS + ${toString cfg.healthCheck.timeoutSeconds}))
|
||||
|
||||
until rnstatus --config "$STATE_DIRECTORY" >/dev/null 2>&1; do
|
||||
if [ "$SECONDS" -ge "$deadline" ]; then
|
||||
echo "rnsd did not become healthy before timeout (${toString cfg.healthCheck.timeoutSeconds}s)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sleep ${toString cfg.healthCheck.intervalSeconds}
|
||||
done
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
DynamicUser = true;
|
||||
StateDirectory = "rnsd";
|
||||
SupplementaryGroups = cfg.extraGroups;
|
||||
RuntimeDirectory = "rnsd";
|
||||
CacheDirectory = "rnsd";
|
||||
ProtectSystem = "strict";
|
||||
|
||||
ExecStart = "${lib.getExe' cfg.package cfg.packageBinaryName} --config $STATE_DIRECTORY";
|
||||
}
|
||||
// lib.optionalAttrs cfg.healthCheck.enable {
|
||||
ExecStartPost = lib.getExe waitForHealthy;
|
||||
TimeoutStartSec = cfg.healthCheck.timeoutSeconds + 5;
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = lib.mkIf cfg.openMulticastPorts {
|
||||
extraCommands = lib.optionalString (!config.networking.nftables.enable) ''
|
||||
ip46tables -A nixos-fw -p udp -m pkttype --pkt-type multicast -m multiport --dports 27916,42671 -j nixos-fw-accept
|
||||
'';
|
||||
|
||||
extraInputRules = lib.optionalString config.networking.nftables.enable ''
|
||||
meta l4proto udp pkttype multicast udp dport { 27916, 42671 } accept
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
meta = {
|
||||
doc = ./rnsd.md;
|
||||
maintainers = with lib.maintainers; [ drupol ];
|
||||
};
|
||||
}
|
||||
@@ -1503,6 +1503,7 @@ in
|
||||
};
|
||||
rkvm = handleTest ./rkvm { };
|
||||
rmfakecloud = runTest ./rmfakecloud.nix;
|
||||
rnsd = runTest ./networking/rnsd.nix;
|
||||
robustirc-bridge = runTest ./robustirc-bridge.nix;
|
||||
rosenpass = runTest ./rosenpass.nix;
|
||||
roundcube = runTest ./roundcube.nix;
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
mainPort = 4242;
|
||||
in
|
||||
{
|
||||
name = "rnsd";
|
||||
meta.maintainers = [ lib.maintainers.drupol ];
|
||||
|
||||
nodes.machine = {
|
||||
services.rnsd = {
|
||||
enable = true;
|
||||
settings = {
|
||||
reticulum = {
|
||||
enable_transport = "Yes";
|
||||
share_instance = "Yes";
|
||||
instance_name = "default";
|
||||
discover_interfaces = "No";
|
||||
panic_on_interface_error = "No";
|
||||
};
|
||||
logging = {
|
||||
loglevel = 5;
|
||||
};
|
||||
interfaces = {
|
||||
test = {
|
||||
type = "BackboneInterface";
|
||||
enabled = "yes";
|
||||
discoverable = "yes";
|
||||
listen_ip = "0.0.0.0";
|
||||
listen_port = mainPort;
|
||||
discovery_name = "Apollo RNS";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
with subtest("Test rnsd service"):
|
||||
machine.wait_for_unit("rnsd.service")
|
||||
machine.wait_for_open_port(${toString mainPort})
|
||||
'';
|
||||
}
|
||||
Reference in New Issue
Block a user