Skip to content

Commit

Permalink
openthread-border-router: add nixos module
Browse files Browse the repository at this point in the history
This can be used to use compatible Thread radios (such as
HomeAssistant's SkyConnect) in order to create a thread border router.
  • Loading branch information
mrene committed Aug 5, 2024
1 parent 0e34922 commit a83e1b9
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 0 deletions.
2 changes: 2 additions & 0 deletions nixos/doc/manual/release-notes/rl-2411.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@

- [OpenGFW](https://github.com/apernet/OpenGFW), an implementation of the Great Firewall on Linux. Available as [services.opengfw](#opt-services.opengfw.enable).

- [OpenThread Border Router](https://github.com/openthread/ot-br-posix), a border router briding Thread mesh networks with IPv6 networks. Available as [services.openthread-border-router](#opt-services.openthread-border-router.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:
Expand Down
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,7 @@
./services/home-automation/govee2mqtt.nix
./services/home-automation/home-assistant.nix
./services/home-automation/matter-server.nix
./services/home-automation/openthread-border-router.nix
./services/home-automation/wyoming/faster-whisper.nix
./services/home-automation/wyoming/openwakeword.nix
./services/home-automation/wyoming/piper.nix
Expand Down
169 changes: 169 additions & 0 deletions nixos/modules/services/home-automation/openthread-border-router.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
{
lib,
config,
pkgs,
...
}:

let
cfg = config.services.openthread-border-router;
in
{
meta.maintainers = with lib.maintainers; [ mrene ];
options.services.openthread-border-router = {
enable = lib.mkEnableOption "Enable the OpenThread Border Router";

package = lib.mkPackageOption pkgs "openthread-border-router" { };

radioDevice = lib.mkOption {
type = lib.types.str;
default = "/dev/ttyUSB0";
description = "The device name of the serial port of the radio device";
};

backboneInterface = lib.mkOption {
type = lib.types.str;
default = "eth0";
description = "The network interface on which to advertise the thread ipv6 mesh prefix";
};

interfaceName = lib.mkOption {
type = lib.types.str;
default = "wpan0";
description = "The network interface to create for thread packets";
};

logLevel = lib.mkOption {
type = lib.types.int;
default = 3;
description = ''
The logging level to use:
EMERG 0
ALERT 1
CRIT 2
ERR 3
WARNING 4
NOTICE 5
INFO 6
DEBUG 7
'';
};

rest = {
listenAddress = lib.mkOption {
type = lib.types.str;
default = "";
description = "Theaddress on which to listen for REST API requests";
example = "0.0.0.0";
};

listenPort = lib.mkOption {
type = lib.types.int;
default = 8081;
description = "The port on which to listen for REST API requests";
};
};

radio = {
device = lib.mkOption {
type = lib.types.str;
default = "/dev/ttyUSB0";
description = "The device name of the serial port of the radio device. Ignored if url is manually set.";
};

baudRate = lib.mkOption {
type = lib.types.int;
default = 115200;
description = "The baud rate of the radio device. Ignored if url is manually set.";
};

flowControl = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Enable hardware flow control. Ignored if url is manually set.";
};

extraUrlParams = lib.mkOption {
type = lib.types.str;
default = "";
description = "Extra URL query string parameters";
example = "bus-latency=100&region=ca";
};

url = lib.mkOption {
type = lib.types.str;
description = "The URL of the radio device to use";
};

extraDevices = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "Extra devices to add to the radio device";
example = "[ \"trel://eth0\" ]";
};
};
};

config = lib.mkIf cfg.enable {

services.openthread-border-router.radio.url =
"spinel+hdlc+uart://${cfg.radio.device}?"
+ lib.concatStringsSep "&" (
(lib.optional (cfg.radio.baudRate != 115200) "uart-baudrate=${toString cfg.radio.baudRate}")
++ (lib.optional cfg.radio.flowControl "uart-flow-control")
++ (lib.optional (cfg.radio.extraUrlParams != "") cfg.radio.extraUrlParams)
);

# ot-ctl can be used to query the router instance
environment.systemPackages = [ cfg.package ];

boot.kernel.sysctl = {
# Make sure we have ipv6 support, and that forwarding is enabled
"net.ipv6.conf.all.disable_ipv6" = 0;
"net.ipv4.conf.all.forwarding" = 1;
"net.ipv6.conf.all.forwarding" = 1;
"net.ipv6.ip_forward" = 1;

# Make sure we accept IPv6 router advertisements from the local network interface
"net.ipv6.conf.${cfg.backboneInterface}.accept_ra" = 2;
"net.ipv6.conf.${cfg.backboneInterface}.accept_ra_rt_info_max_plen" = 64;
};

# OTBR needs to publish its addresses via avahi
services.avahi.enable = true;
services.avahi.publish.enable = true;
services.avahi.publish.userServices = true;

# Synchronize the services with the unit files defined in the source pacakge
systemd.services = {
# src/agent/otbr-agent.service.in
# The agent keeps its local state in /var/lib/thread
otbr-agent = {
description = "OpenThread Border Router Agent";
wantedBy = [ "multi-user.target" ];
requires = [ "dbus.socket" ];
after = [ "dbus.socket" ];
serviceConfig = {
ExecStart = (
lib.concatStringsSep " " (
[ (lib.getExe' cfg.package "otbr-agent") ]
++ [ "--verbose" ]
++ [ "--backbone-ifname ${cfg.backboneInterface}" ]
++ [ "--thread-ifname ${cfg.interfaceName}" ]
++ [ "--debug-level ${toString cfg.logLevel}" ]
++ (lib.optional (cfg.rest.listenPort != 0) "--rest-listen-port ${toString cfg.rest.listenPort}")
++ (lib.optional (cfg.rest.listenAddress != "") "--rest-listen-address ${cfg.rest.listenAddress}")
++ [ cfg.radio.url ]
++ cfg.radio.extraDevices
)
);
KillMode = "mixed";
Restart = "on-failure";
RestartSec = 5;
RestartPreventExitStatus = "SIGKILL";
};
path = [ pkgs.ipset ];
};
};
};
}

0 comments on commit a83e1b9

Please sign in to comment.