diff --git a/nixos/doc/manual/release-notes/rl-2605.section.md b/nixos/doc/manual/release-notes/rl-2605.section.md index 4fee9fb92e5e..1c19a0d57a4d 100644 --- a/nixos/doc/manual/release-notes/rl-2605.section.md +++ b/nixos/doc/manual/release-notes/rl-2605.section.md @@ -10,6 +10,10 @@ +- [Meshtastic](https://meshtastic.org), an open-source, off-grid, decentralised mesh network + designed to run on affordable, low-power devices. Available as [services.meshtasticd] + (#opt-services.meshtasticd.enable). + - [knot-resolver](https://www.knot-resolver.cz/) in version 6. Available as `services.knot-resolver`. A module for knot-resolver 5 was already available as `services.kresd`. - [ImmichFrame](https://immichframe.dev/), display your photos from Immich as a digital photo frame. Available as `services.immichframe`. diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 430872242d57..8d233529326e 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1247,6 +1247,7 @@ ./services/networking/lxd-image-server.nix ./services/networking/magic-wormhole-mailbox-server.nix ./services/networking/matterbridge.nix + ./services/networking/meshtasticd.nix ./services/networking/microsocks.nix ./services/networking/mihomo.nix ./services/networking/minidlna.nix diff --git a/nixos/modules/services/networking/meshtastic.md b/nixos/modules/services/networking/meshtastic.md new file mode 100644 index 000000000000..d16c59fe3917 --- /dev/null +++ b/nixos/modules/services/networking/meshtastic.md @@ -0,0 +1,57 @@ +# Meshtasticd {#module-services-meshtasticd} + +[Meshtasticd](https://meshtastic.org/) daemon. + +Meshtastic is an open-source, off-grid, decentralised mesh network designed to +run on affordable, low-power devices. + +Meshtastic is a project that enables you to use inexpensive LoRa radios as a +long range off-grid communication platform in areas without existing or reliable +communications infrastructure. This project is 100% community driven and open +source! + +## Quickstart {#module-services-meshtasticd-quickstart} + +A minimal configuration: + +```nix +{ + services.meshtasticd = { + enable = true; + port = 4403; + settings = { + Lora = { + Module = "auto"; + }; + Webserver = { + Port = 9443; + RootPath = pkgs.meshtastic-web; + }; + General = { + MaxNodes = 200; + MaxMessageQueue = 100; + MACAddressSource = "eth0"; + }; + }; + }; +} +``` + +By default Meshtasticd listens on all network interfaces. The example above +binds the daemon to port `4403` and the web UI to `9443`. This module +intentionally does not configure an reverse proxy for you, keeping the module +focused on the Meshtastic service itself. If you need to restrict access, use +firewall rules or put the web UI behind a reverse proxy (e.g.: Caddy, Nginx) +that binds to `127.0.0.1` and exposes only the proxy. This approach leaves proxy +choice and TLS configuration to the operator while documenting how to securely +expose the web UI when required. + +## Configuration {#module-services-meshtasticd-config} + +All available configuration directives are documented in the +[standard Meshtastic configuration file](https://github.com/meshtastic/firmware/blob/develop/bin/config-dist.yaml). + +The service uses a dedicated user and group account (`meshtasticd`) by default. +If you override the service user, ensure it is a member of the `spi` and `gpio` +groups so it can access the required hardware devices, as mandated by +Meshtastic’s default `udev` rules. diff --git a/nixos/modules/services/networking/meshtasticd.nix b/nixos/modules/services/networking/meshtasticd.nix new file mode 100644 index 000000000000..d72affb8b6df --- /dev/null +++ b/nixos/modules/services/networking/meshtasticd.nix @@ -0,0 +1,124 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.services.meshtasticd; + format = pkgs.formats.yaml { }; + configFile = format.generate "config.yaml" cfg.settings; +in +{ + options.services.meshtasticd = { + enable = lib.mkEnableOption "Meshtastic daemon"; + package = lib.mkPackageOption pkgs "meshtasticd" { }; + + user = lib.mkOption { + default = "meshtasticd"; + description = "User meshtasticd runs as."; + type = lib.types.str; + }; + + group = lib.mkOption { + default = "meshtasticd"; + description = "Group meshtasticd runs as."; + type = lib.types.str; + }; + + port = lib.mkOption { + type = lib.types.port; + default = 4403; + description = "Port to listen on"; + }; + + settings = lib.mkOption { + type = format.type; + example = lib.literalExpression '' + Lora = { + Module = "auto"; + }; + Webserver = { + Port = 9443; + RootPath = pkgs.meshtastic-web; + }; + General = { + MaxNodes = 200; + MaxMessageQueue = 100; + MACAddressSource = "eth0"; + }; + ''; + description = '' + The Meshtastic configuration file. + + An example of configuration can be found at + ''; + }; + + dataDir = lib.mkOption { + default = "/var/lib/meshtasticd"; + type = lib.types.path; + description = '' + The data directory. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + # Creation of the `meshtasticd` privilege user. + users = { + users = lib.mkIf (cfg.user == "meshtasticd") { + meshtasticd = { + home = cfg.dataDir; + description = "meshtasticd-daemon privilege user"; + group = cfg.group; + isSystemUser = true; + extraGroups = [ + "spi" + "gpio" + ]; + }; + }; + groups = lib.mkIf (cfg.group == "meshtasticd") { + meshtasticd = { }; + # These groups are required for udev rules to work properly. + spi = { }; + gpio = { }; + }; + }; + + # The `meshtasticd` package provides udev rules. + services.udev.packages = [ + cfg.package + ]; + + # Creation of the `meshtasticd` service. + # Based on the official meshtasticd service file: https://github.com/meshtastic/firmware/blob/develop/bin/meshtasticd.service + systemd.services.meshtasticd = { + description = "Meshtastic Native Daemon"; + after = [ + "network-online.target" + "network.target" + ]; + wants = [ + "network-online.target" + "network.target" + ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + User = cfg.user; + Group = cfg.group; + Type = "simple"; + StateDirectory = "meshtasticd"; + AmbientCapabilities = [ + "CAP_NET_BIND_SERVICE" + ]; + ExecStart = "${lib.getExe cfg.package} --port=${builtins.toString cfg.port} --fsdir=${cfg.dataDir} --config=${configFile} --verbose"; + Restart = "always"; + RestartSec = "3"; + }; + }; + }; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index ba951d160591..988cf1c0c76c 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -939,6 +939,7 @@ in meilisearch = runTest ./meilisearch.nix; memcached = runTest ./memcached.nix; merecat = runTest ./merecat.nix; + meshtasticd = runTest ./networking/meshtasticd.nix; metabase = runTest ./metabase.nix; mihomo = runTest ./mihomo.nix; mimir = runTest ./mimir.nix; diff --git a/nixos/tests/networking/meshtasticd.nix b/nixos/tests/networking/meshtasticd.nix new file mode 100644 index 000000000000..121886e7910a --- /dev/null +++ b/nixos/tests/networking/meshtasticd.nix @@ -0,0 +1,45 @@ +{ + lib, + pkgs, + ... +}: +let + mainPort = 9445; + webPort = 9446; +in +{ + name = "meshtasticd"; + meta.maintainers = [ lib.maintainers.drupol ]; + + nodes.machine = { + services.meshtasticd = { + enable = true; + port = mainPort; + + settings = { + Lora = { + Module = "sim"; + DIO2_AS_RF_SWITCH = false; + spiSpeed = "2000000"; + }; + Webserver = { + Port = webPort; + RootPath = pkgs.meshtastic-web; + }; + General = { + MaxNodes = 200; + MaxMessageQueue = 100; + MACAddressSource = "eth0"; + }; + }; + }; + }; + + testScript = '' + with subtest("Test meshtasticd service"): + machine.wait_for_unit("meshtasticd.service") + machine.wait_for_open_port(${builtins.toString mainPort}) + machine.wait_for_open_port(${builtins.toString webPort}) + machine.succeed("curl -fvvv -Ls http://localhost:${builtins.toString webPort} | grep -q 'Meshtastic Web Client'") + ''; +}