diff --git a/nixos/doc/manual/release-notes/rl-2505.section.md b/nixos/doc/manual/release-notes/rl-2505.section.md index 1fdaef9cc7d2e..aa90d6943ec88 100644 --- a/nixos/doc/manual/release-notes/rl-2505.section.md +++ b/nixos/doc/manual/release-notes/rl-2505.section.md @@ -43,6 +43,8 @@ - [MaryTTS](https://github.com/marytts/marytts), an open-source, multilingual text-to-speech synthesis system written in pure Java. Available as [services.marytts](options.html#opt-services.marytts). +- [OpenThread Border Router](https://github.com/openthread/ot-br-posix), a border router bridging Thread mesh networks with IPv6 networks. Available as [services.openthread-border-router](#opt-services.openthread-border-router.enable). + - [networking.modemmanager](options.html#opt-networking.modemmanager) has been split out of [networking.networkmanager](options.html#opt-networking.networkmanager). NetworkManager still enables ModemManager by default, but options exist now to run NetworkManager without ModemManager. - [Conduwuit](https://conduwuit.puppyirl.gay/), a federated chat server implementing the Matrix protocol, forked from Conduit. Available as [services.conduwuit](#opt-services.conduwuit.enable). diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 92e8db5ee8e1c..14e0731a23091 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -654,6 +654,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 diff --git a/nixos/modules/services/home-automation/openthread-border-router.nix b/nixos/modules/services/home-automation/openthread-border-router.nix new file mode 100644 index 0000000000000..de23b526d1a5c --- /dev/null +++ b/nixos/modules/services/home-automation/openthread-border-router.nix @@ -0,0 +1,234 @@ +{ + lib, + config, + pkgs, + ... +}: + +let + cfg = config.services.openthread-border-router; + logLevelMappings = { + "emerg" = 0; + "alert" = 1; + "crit" = 2; + "err" = 3; + "warning" = 4; + "notice" = 5; + "info" = 6; + "debug" = 7; + }; + logLevel = lib.getAttr cfg.logLevel logLevelMappings; +in +{ + meta.maintainers = with lib.maintainers; [ mrene ]; + + options.services.openthread-border-router = { + enable = lib.mkEnableOption "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.enum [ + "emerg" + "alert" + "crit" + "err" + "warning" + "notice" + "info" + "debug" + ]; + default = "err"; + description = "The level to use when logging messages"; + }; + + rest = { + listenAddress = lib.mkOption { + type = lib.types.str; + default = "::"; + description = "The address on which to listen for REST API requests"; + example = "::"; + }; + + listenPort = lib.mkOption { + type = lib.types.port; + default = 8081; + description = "The port on which to listen for REST API requests"; + }; + }; + + web = { + enable = lib.mkEnableOption "the web interface"; + listenAddress = lib.mkOption { + type = lib.types.str; + default = "::"; + description = "The address on which the web interface should listen"; + example = "::"; + }; + + listenPort = lib.mkOption { + type = lib.types.port; + default = 8082; + description = "The port on which the web interface should listen"; + }; + }; + + radio = { + device = lib.mkOption { + type = lib.types.path; + default = "/dev/ttyUSB0"; + description = "The device name of the serial port of the radio device. Ignored if services.openthread-border-router.radio.url is set."; + }; + + baudRate = lib.mkOption { + type = lib.types.int; + default = 115200; + description = "The baud rate of the radio device. Ignored if services.openthread-border-router.radio.url is set."; + }; + + flowControl = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Enable hardware flow control. Ignored if services.openthread-border-router.radio.url is set."; + }; + + urlQueryString = lib.mkOption { + type = lib.types.str; + default = ""; + description = "Extra URL query string parameters. Ignored if services.openthread-border-router.radio.url is set."; + example = "bus-latency=100®ion=ca"; + }; + + url = lib.mkOption { + type = lib.types.nullOr lib.types.str; + description = "The URL of the radio device to use"; + example = "spinel+hdlc+uart:///dev/ttyUSB0?uart-baudrate=460800&uart-flow-control"; + }; + + 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 = lib.mkDefault ( + "spinel+hdlc+uart://${cfg.radio.device}?" + + lib.concatStringsSep "&" ( + [ "uart-baudrate=${toString cfg.radio.baudRate}" ] + ++ lib.optional cfg.radio.flowControl "uart-flow-control" + ++ lib.optional (cfg.radio.urlQueryString != "") cfg.radio.urlQueryString + ) + ); + + # ot-ctl can be used to query the router instance + environment.systemPackages = [ cfg.package ]; + + # Make sure we have ipv6 support, and that forwarding is enabled + networking.enableIPv6 = true; + boot.kernel.sysctl = { + "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; + publish = { + enable = true; + userServices = true; + }; + }; + + # Synchronize the services with the unit files defined in the source pacakge + systemd.services = { + # Sync with: src/agent/otbr-agent.service.in + # Manually added otbr-firewall calls because they are handled inside platform-specific scripts + # 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" ]; + environment = { + THREAD_IF = cfg.interfaceName; + }; + serviceConfig = { + ExecStartPre = "${lib.getExe' cfg.package "otbr-firewall"} start"; + ExecStart = ( + lib.concatStringsSep " " ( + [ (lib.getExe' cfg.package "otbr-agent") ] + ++ [ + "--verbose" + "--backbone-ifname ${cfg.backboneInterface}" + "--thread-ifname ${cfg.interfaceName}" + "--debug-level ${toString 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 + ) + ); + ExecStopPost = "${lib.getExe' cfg.package "otbr-firewall"} stop"; + KillMode = "mixed"; + Restart = "on-failure"; + RestartSec = 5; + RestartPreventExitStatus = "SIGKILL"; + }; + path = [ + pkgs.ipset + pkgs.iptables + ]; + }; + + # Sync with: src/web/otbr-web.service.in + otbr-web = { + description = "OpenThread Border Router Web"; + after = [ "otbr-agent.service" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = ( + lib.concatStringsSep " " ( + [ + (lib.getExe' cfg.package "otbr-web") + "-I" + "${cfg.interfaceName}" + "-d" + "${toString logLevel}" + ] + ++ lib.optional (cfg.web.listenAddress != "") "-a ${cfg.web.listenAddress}" + ++ lib.optional (cfg.web.listenPort != 0) "-p ${toString cfg.web.listenPort}" + ) + ); + }; + }; + }; + }; +} diff --git a/pkgs/by-name/op/openthread-border-router/dont-install-systemd-units.patch b/pkgs/by-name/op/openthread-border-router/dont-install-systemd-units.patch new file mode 100644 index 0000000000000..f700081e9f23c --- /dev/null +++ b/pkgs/by-name/op/openthread-border-router/dont-install-systemd-units.patch @@ -0,0 +1,14 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index d31b2096b1..5d2dec8049 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -104,9 +104,6 @@ include(GNUInstallDirs) + + pkg_check_modules(SYSTEMD systemd) + +-if(SYSTEMD_FOUND) +- pkg_get_variable(OTBR_SYSTEMD_UNIT_DIR systemd systemdsystemunitdir) +-endif() + + + add_subdirectory(third_party EXCLUDE_FROM_ALL) diff --git a/pkgs/by-name/op/openthread-border-router/dont-use-boost-static-libs.patch b/pkgs/by-name/op/openthread-border-router/dont-use-boost-static-libs.patch new file mode 100644 index 0000000000000..a7d40a0b69d1b --- /dev/null +++ b/pkgs/by-name/op/openthread-border-router/dont-use-boost-static-libs.patch @@ -0,0 +1,12 @@ +diff --git a/src/web/CMakeLists.txt b/src/web/CMakeLists.txt +index 37ba68ffcf..3706061de5 100644 +--- a/src/web/CMakeLists.txt ++++ b/src/web/CMakeLists.txt +@@ -27,7 +27,6 @@ + # + + pkg_check_modules(JSONCPP jsoncpp REQUIRED) +-set(Boost_USE_STATIC_LIBS ON) + set(Boost_USE_MULTITHREADED ON) + set(Boost_USE_STATIC_RUNTIME OFF) + find_package(Boost REQUIRED COMPONENTS filesystem system) diff --git a/pkgs/by-name/op/openthread-border-router/firewall-script.patch b/pkgs/by-name/op/openthread-border-router/firewall-script.patch new file mode 100644 index 0000000000000..24cde2d2ff996 --- /dev/null +++ b/pkgs/by-name/op/openthread-border-router/firewall-script.patch @@ -0,0 +1,17 @@ +diff --git a/script/otbr-firewall b/script/otbr-firewall +index 52cef42dd0b..75840f8ae12 100644 +--- a/script/otbr-firewall ++++ b/script/otbr-firewall +@@ -38,12 +38,8 @@ + # Description: This service sets up firewall for OTBR. + ### END INIT INFO + +-THREAD_IF="wpan0" + OTBR_FORWARD_INGRESS_CHAIN="OTBR_FORWARD_INGRESS" + +-. /lib/lsb/init-functions +-. /lib/init/vars.sh +- + set -euxo pipefail + + ipset_destroy_if_exist() diff --git a/pkgs/by-name/op/openthread-border-router/package.nix b/pkgs/by-name/op/openthread-border-router/package.nix new file mode 100644 index 0000000000000..bd7cba6fa4d94 --- /dev/null +++ b/pkgs/by-name/op/openthread-border-router/package.nix @@ -0,0 +1,129 @@ +{ + lib, + stdenv, + fetchFromGitHub, + cmake, + pkg-config, + systemdLibs, + avahi, + protobuf, + jsoncpp, + boost, + libnetfilter_queue, + libnfnetlink, + nodejs, + buildNpmPackage, +}: +let + pname = "ot-br-posix"; + version = "0-unstable-2024-10-18"; + + src = fetchFromGitHub { + owner = "openthread"; + repo = "ot-br-posix"; + rev = "76bfeef4e3e0242c235aa6bdf5480a0642883599"; + hash = "sha256-VtekJD++PAse+yNmWzpO8lsyw0jnEsRFrzs/wK6VqqM="; + fetchSubmodules = true; + }; + + # hass-addons contains a few patches + hass-addons = fetchFromGitHub { + owner = "home-assistant"; + repo = "addons"; + rev = "583a62a69fa92ca8fdf9a2f298270a50bb3663a1"; + hash = "sha256-u+lvU7mlJZqCCDIT4n7WnHyzAd0nZCh9Ddvqjs4SkHA="; + }; + + frontendModules = buildNpmPackage { + pname = "${pname}-frontend"; + inherit version; + src = "${src}/src/web/web-service/frontend"; + npmDepsHash = "sha256-7UVfPICyIbHEClpr3p7eDR46OUzS8mVf6P7phnDpVLk="; + dontNpmBuild = true; + }; +in +stdenv.mkDerivation { + inherit pname version src; + + # warning _FORTIFY_SOURCE requires compiling with optimization (-O) + env.NIX_CFLAGS_COMPILE = "-O"; + + patches = [ + # Skip installing systemd units since it doesn't respect the installation prefix and attempts + # to install them at their final location + ./dont-install-systemd-units.patch + ./dont-use-boost-static-libs.patch + # Patch the firewall script so we can run it within the systemd start script + ./firewall-script.patch + "${hass-addons}/openthread_border_router/0002-rest-support-deleting-the-dataset.patch" + ]; + + postPatch = '' + pushd third_party/openthread/repo + patch -p1 -i "${hass-addons}/openthread_border_router/0001-channel-monitor-disable-by-default.patch" + popd + ''; + + nativeBuildInputs = [ + pkg-config + cmake + nodejs + ]; + + # Adding npmConfigHook and manually passing fetchNpmDeps was resulting in ENOTCACHED errors + postConfigure = '' + ln -sf ${frontendModules}/lib/node_modules/otbr-web/node_modules ./src/web/web-service/frontend/ + ''; + + buildInputs = [ + avahi + systemdLibs + protobuf + jsoncpp + boost + libnetfilter_queue + libnfnetlink + ]; + + postInstall = '' + mkdir -p $out/bin + echo "PWD: "$(pwd) + echo "SRC: ${src}" + cp ../script/otbr-firewall $out/bin/ + chmod +x $out/bin/otbr-firewall + ''; + + cmakeFlags = [ + # These defaults are from "examples/platforms/raspbian/default and script/_otbr + (lib.cmakeBool "BUILD_TESTING" false) + (lib.cmakeBool "OTBR_REST" true) + + (lib.cmakeBool "OTBR_WEB" true) + (lib.cmakeBool "OTBR_NAT64" true) + (lib.cmakeBool "OTBR_BACKBONE_ROUTER" true) + (lib.cmakeBool "OTBR_BORDER_ROUTING" true) + (lib.cmakeBool "OTBR_DBUS" false) + (lib.cmakeBool "OTBR_TREL" true) + + (lib.cmakeFeature "OTBR_VERSION" version) + (lib.cmakeBool "OTBR_DNSSD_DISCOVERY_PROXY" true) + (lib.cmakeBool "OTBR_SRP_ADVERTISING_PROXY" true) + (lib.cmakeBool "OTBR_DUA_ROUTING" true) + (lib.cmakeBool "OTBR_DNS_UPSTREAM_QUERY" true) + + (lib.cmakeBool "OT_CHANNEL_MANAGER" true) + (lib.cmakeBool "OT_CHANNEL_MONITOR" true) + + # Required by protobuf + (lib.cmakeFeature "CMAKE_CXX_STANDARD" "17") + ]; + + meta = { + description = "Thread border router for POSIX-based platforms"; + homepage = "https://github.com/openthread/ot-br-posix"; + license = lib.licenses.bsd3; + maintainers = with lib.maintainers; [ mrene ]; + mainProgram = "ot-ctl"; + platforms = lib.platforms.linux; + }; +}