meshtasticd: build from source, nixos/meshtastic: init (#466509)

This commit is contained in:
Doron Behar
2025-12-11 13:49:46 +00:00
committed by GitHub
9 changed files with 398 additions and 32 deletions
@@ -10,6 +10,10 @@
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
- [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`.
+1
View File
@@ -1248,6 +1248,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
@@ -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
Meshtastics default `udev` rules.
@@ -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 <https://github.com/meshtastic/firmware/blob/develop/bin/config-dist.yaml>
'';
};
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";
};
};
};
}
+1
View File
@@ -941,6 +941,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;
+45
View File
@@ -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'")
'';
}
@@ -0,0 +1,41 @@
{
stdenv,
lib,
fetchurl,
}:
stdenv.mkDerivation (finalAttrs: {
pname = "meshtastic-web";
version = "2.6.7";
src = fetchurl {
url = "https://github.com/meshtastic/web/releases/download/v${finalAttrs.version}/build.tar";
hash = "sha256-o09DYKBIZUOmmN4g3lM1V0kudjq0Wfwn/OqV0ElRRO0=";
};
sourceRoot = ".";
buildPhase = ''
runHook preBuild
gzip -dr .
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p $out
cp -ar . $out/
runHook postInstall
'';
meta = {
description = "Meshtastic Web Client/JS Monorepo";
homepage = "https://github.com/meshtastic/web";
license = lib.licenses.gpl3Plus;
platforms = lib.platforms.all;
maintainers = with lib.maintainers; [ drupol ];
};
})
+7
View File
@@ -0,0 +1,7 @@
{ lib, python3Packages }:
python3Packages.toPythonApplication (
python3Packages.meshtastic.overridePythonAttrs (prev: {
dependencies = prev.dependencies ++ lib.concatAttrValues prev.optional-dependencies;
})
)
+118 -32
View File
@@ -1,9 +1,13 @@
{
lib,
stdenv,
fetchurl,
autoPatchelfHook,
dpkg,
lib,
fetchFromGitHub,
fetchzip,
libarchive,
pkg-config,
platformio-core,
writableTmpDirAsHomeHook,
bluez,
i2c-tools,
libX11,
libgpiod_1,
@@ -11,65 +15,146 @@
libusb1,
libuv,
libxkbcommon,
udevCheckHook,
ulfius,
openssl,
gnutls,
jansson,
zlib,
libmicrohttpd,
orcania,
yder,
yaml-cpp,
udevCheckHook,
versionCheckHook,
makeBinaryWrapper,
python3Packages,
enableDefaultConfig ? false,
meshtastic-web, # Only used when `enableDefaultConfig` is set to `true`.
nixosTests,
}:
assert builtins.isBool enableDefaultConfig;
let
version = "2.7.16.a597230";
platformio-deps-native = fetchzip {
url = "https://github.com/meshtastic/firmware/releases/download/v${version}/platformio-deps-native-tft-${version}.zip";
hash = "sha256-Jo7e6zsCaiJs6NyIRmD6BWJFwbs0xVlUih206ePUpwk=";
};
in
stdenv.mkDerivation (finalAttrs: {
pname = "meshtasticd";
version = "2.6.11.25";
inherit version;
src = fetchurl {
url = "https://download.opensuse.org/repositories/network:/Meshtastic:/beta/Debian_12/amd64/meshtasticd_${finalAttrs.version}~obs60ec05e~beta_amd64.deb";
hash = "sha256-7JCv+1YgsCLwboGE/2f+8iyLLoUsKn3YdJ9Atnfj7Zw=";
src = fetchFromGitHub {
owner = "meshtastic";
repo = "firmware";
hash = "sha256-oU3Z8qjBNeNGPGT74VStAPHgsGqsQJKngHJR6m2CBa0=";
tag = "v${finalAttrs.version}";
fetchSubmodules = true;
};
nativeBuildInputs = [
autoPatchelfHook
dpkg
];
dontConfigure = true;
dontBuild = true;
strictDeps = true;
nativeBuildInputs = [
libarchive
pkg-config
# This has been advised by the Meshtastic's developer.
# Without it, it will try to install grpcio-tools by itself and fail.
(platformio-core.overridePythonAttrs (oldAttrs: {
dependencies = oldAttrs.dependencies ++ [
python3Packages.grpcio-tools
];
}))
writableTmpDirAsHomeHook
makeBinaryWrapper
];
buildInputs = [
bluez
gnutls
i2c-tools
jansson
libX11
libgpiod_1
libinput
libmicrohttpd
libusb1
libuv
libxkbcommon
openssl
orcania
ulfius
yaml-cpp
yder
zlib
];
autoPatchelfIgnoreMissingDeps = [
"libyaml-cpp.so.0.7"
];
preConfigure = ''
mkdir -p platformio-deps-native
cp -ar ${platformio-deps-native}/. platformio-deps-native
chmod +w -R platformio-deps-native
export PLATFORMIO_CORE_DIR=platformio-deps-native/core
export PLATFORMIO_LIBDEPS_DIR=platformio-deps-native/libdeps
export PLATFORMIO_PACKAGES_DIR=platformio-deps-native/packages
'';
buildPhase = ''
runHook preBuild
platformio run --environment native-tft
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p {$out,$out/bin}
cp -r {usr,lib} $out/
install -d $out/share/meshtasticd/config.d
install -d $out/share/meshtasticd/available.d
cp -R bin/config.d/* $out/share/meshtasticd/available.d
patchelf --replace-needed libyaml-cpp.so.0.7 libyaml-cpp.so.0.8 $out/usr/bin/meshtasticd
install -Dm644 bin/org.meshtastic.meshtasticd.svg -t $out/share/icons/hicolor/scalable/apps/
install -Dm644 bin/org.meshtastic.meshtasticd.desktop -t $out/share/applications/
install -Dm755 .pio/build/native-tft/program $out/bin/meshtasticd
ln -s $out/usr/bin/meshtasticd $out/bin/meshtasticd
substituteInPlace $out/lib/systemd/system/meshtasticd.service \
--replace-fail "/usr/bin/meshtasticd" "$out/bin/meshtasticd" \
--replace-fail 'User=meshtasticd' 'DynamicUser=yes' \
--replace-fail 'Group=meshtasticd' ""
install -Dm644 bin/99-meshtasticd-udev.rules -t $out/etc/udev/rules.d
''
+ lib.optionalString enableDefaultConfig ''
install -Dm644 bin/config-dist.yaml $out/share/meshtasticd/config.yaml
substituteInPlace $out/share/meshtasticd/config.yaml \
--replace-fail "/etc/meshtasticd/config.d" "$out/share/meshtasticd/config.d" \
--replace-fail "/etc/meshtasticd/available.d" "$out/share/meshtasticd/available.d"
wrapProgram $out/bin/meshtasticd \
--add-flags "-c $out/share/meshtasticd/config.yaml"
install -Dm644 bin/config.d/MUI/X11_480x480.yaml $out/share/meshtasticd/config.d/MUI.yaml
substituteInPlace $out/share/meshtasticd/config.yaml \
--replace-fail "/usr/share/meshtasticd/web" "${meshtastic-web}"
install -d $out/share/meshtasticd/maps
for file in pio/libdeps/native-tft/meshtastic-device-ui/maps/*.zip; do
bsdtar -xf "$file" --no-same-owner --strip-components=1 -C $out/share/meshtasticd/maps;
done
''
+ ''
runHook postInstall
'';
doInstallCheck = true;
nativeInstallCheckInputs = [ udevCheckHook ];
nativeInstallCheckInputs = [
udevCheckHook
versionCheckHook
];
versionCheckProgramArg = "--version";
preVersionCheck = ''
version="${lib.versions.major finalAttrs.version}.${lib.versions.minor finalAttrs.version}.${lib.versions.patch finalAttrs.version}"
'';
passthru.tests = {
inherit (nixosTests) meshtasticd;
};
meta = {
description = "Meshtastic daemon for communicating with Meshtastic devices";
@@ -77,12 +162,13 @@ stdenv.mkDerivation (finalAttrs: {
This package has `udev` rules installed as part of the package.
Add `services.udev.packages = [ pkgs.meshtasticd ]` into your NixOS
configuration to enable them.
To enable the default configuration, set the `enableDefaultConfig` parameter to true.
'';
homepage = "https://github.com/meshtastic/firmware";
mainProgram = "meshtasticd";
license = lib.licenses.gpl3Plus;
platforms = [ "x86_64-linux" ];
sourceProvenance = with lib.sourceTypes; [ binaryNativeCode ];
platforms = lib.platforms.linux;
maintainers = with lib.maintainers; [ drupol ];
};
})