nixos/lxmd: init
This commit is contained in:
@@ -65,6 +65,24 @@
|
||||
"module-services-keycloak-unix-socket": [
|
||||
"index.html#module-services-keycloak-unix-socket"
|
||||
],
|
||||
"module-services-lxmd": [
|
||||
"index.html#module-services-lxmd"
|
||||
],
|
||||
"module-services-lxmd-communication-backbone": [
|
||||
"index.html#module-services-lxmd-communication-backbone"
|
||||
],
|
||||
"module-services-lxmd-communication-rpc": [
|
||||
"index.html#module-services-lxmd-communication-rpc"
|
||||
],
|
||||
"module-services-lxmd-communication-with-rnsd": [
|
||||
"index.html#module-services-lxmd-communication-with-rnsd"
|
||||
],
|
||||
"module-services-lxmd-health-check": [
|
||||
"index.html#module-services-lxmd-health-check"
|
||||
],
|
||||
"module-services-lxmd-quickstart": [
|
||||
"index.html#module-services-lxmd-quickstart"
|
||||
],
|
||||
"module-services-mautrix-discord": [
|
||||
"index.html#module-services-mautrix-discord"
|
||||
],
|
||||
|
||||
@@ -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).
|
||||
|
||||
- [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}
|
||||
|
||||
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
|
||||
|
||||
@@ -1290,6 +1290,7 @@
|
||||
./services/networking/logmein-hamachi.nix
|
||||
./services/networking/lokinet.nix
|
||||
./services/networking/lxd-image-server.nix
|
||||
./services/networking/lxmd.nix
|
||||
./services/networking/magic-wormhole-mailbox-server.nix
|
||||
./services/networking/matterbridge.nix
|
||||
./services/networking/meshtasticd.nix
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
# LXMD {#module-services-lxmd}
|
||||
|
||||
[LXMF](https://github.com/markqvist/lxmf) daemon (`lxmd`).
|
||||
|
||||
This module runs `lxmd` as a systemd service with `DynamicUser = true` and
|
||||
`StateDirectory = "lxmd"` directives.
|
||||
|
||||
## Quickstart {#module-services-lxmd-quickstart}
|
||||
|
||||
A minimal setup:
|
||||
|
||||
```nix
|
||||
{
|
||||
services.lxmd.enable = true;
|
||||
}
|
||||
```
|
||||
|
||||
With custom LXMD and RNSD settings:
|
||||
|
||||
```nix
|
||||
{
|
||||
services.lxmd = {
|
||||
enable = true;
|
||||
|
||||
identityFile = "<path-to-lxmd-identity-file>";
|
||||
|
||||
settings = {
|
||||
propagation-node = {
|
||||
autopeer = true;
|
||||
};
|
||||
};
|
||||
|
||||
rnsd = {
|
||||
identityFile = "<path-to-rnsd-identity-file>";
|
||||
|
||||
settings = {
|
||||
reticulum = {
|
||||
require_shared_instance = true;
|
||||
is_shared_instance = true;
|
||||
enable_transport = true;
|
||||
instance_name = "default";
|
||||
shared_instance_type = "unix";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
At startup, settings provided through
|
||||
[`services.lxmd.settings`](#opt-services.lxmd.settings),
|
||||
[`services.lxmd.identityFile`](#opt-services.lxmd.identityFile), and
|
||||
[`services.lxmd.rnsd.settings`](#opt-services.lxmd.rnsd.settings) are copied
|
||||
into the service state directory.
|
||||
|
||||
## Health Check {#module-services-lxmd-health-check}
|
||||
|
||||
You can optionally wait for `lxmd` to become responsive during startup using
|
||||
`lxmd --status`:
|
||||
|
||||
```nix
|
||||
{
|
||||
services.lxmd = {
|
||||
enable = true;
|
||||
|
||||
healthCheck = {
|
||||
enable = true;
|
||||
intervalSeconds = 2;
|
||||
timeoutSeconds = 120;
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
When enabled, startup fails if `lxmd --status` does not succeed before
|
||||
[`services.lxmd.healthCheck.timeoutSeconds`](#opt-services.lxmd.healthCheck.timeoutSeconds).
|
||||
|
||||
## Communicating With RNSD {#module-services-lxmd-communication-with-rnsd}
|
||||
|
||||
Even though both services are hardened (via Systemd's `DynamicUser = true` and
|
||||
`StateDirectory = "lxmd"` directives), `lxmd` can still communicate with `rnsd`
|
||||
in several ways.
|
||||
|
||||
### Option 1: Shared Instance Over Unix RPC {#module-services-lxmd-communication-rpc}
|
||||
|
||||
Use a shared Reticulum instance exposed through a local Unix RPC socket:
|
||||
|
||||
```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;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.lxmd = {
|
||||
enable = true;
|
||||
|
||||
identityFile = "<path-to-lxmd-identity-file>";
|
||||
|
||||
settings = {
|
||||
# LXMD settings, run `lxmd --exampleconfig` for details
|
||||
};
|
||||
|
||||
rnsd = {
|
||||
identityFile = "<path-to-rnsd-identity-file>";
|
||||
|
||||
settings = {
|
||||
reticulum = {
|
||||
require_shared_instance = true;
|
||||
is_shared_instance = true;
|
||||
enable_transport = true;
|
||||
instance_name = "default";
|
||||
shared_instance_type = "unix";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
In this model, `lxmd` points to an RNS configuration that joins the shared
|
||||
instance. Both RNS transport identity files in `rnsd` and `lxmd` must be the
|
||||
same. If you use different transport identity files, then set the same `rpc_key`
|
||||
in both `rnsd` configurations to allow them to communicate securely.
|
||||
|
||||
It is also possible to use a shared instance over TCP, please refer to the
|
||||
[Reticulum documentation](https://reticulum.network/manual/) for details.
|
||||
|
||||
### Option 2: Isolated Instances Connected Through BackboneInterface {#module-services-lxmd-communication-backbone}
|
||||
|
||||
Reticulum applications (`rnsd`, `lxmd`, and others) can be isolated by running:
|
||||
|
||||
- one primary `rnsd` service instance as a non-privileged service account,
|
||||
- one instance per application, each with its own user and configuration,
|
||||
- a `BackboneInterface` between each isolated application instance and the
|
||||
primary instance.
|
||||
|
||||
On the primary `rnsd` instance, the `BackboneInterface` is configured to accept
|
||||
connections from the isolated instances, as such:
|
||||
|
||||
```nix
|
||||
{
|
||||
services.rnsd = {
|
||||
enable = true;
|
||||
|
||||
identityFile = "<path-to-rnsd-identity-file>";
|
||||
|
||||
settings = {
|
||||
reticulum = {
|
||||
# RNSD settings, run `rnsd --exampleconfig` for details
|
||||
};
|
||||
interfaces = {
|
||||
root = {
|
||||
type = "BackboneInterface";
|
||||
enabled = true;
|
||||
mode = "gateway";
|
||||
discoverable = true;
|
||||
listen_ip = "127.0.0.1";
|
||||
listen_port = 4242;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Then, on each isolated application instance, the `BackboneInterface` is
|
||||
configured to connect to the primary instance:
|
||||
|
||||
```nix
|
||||
{
|
||||
services.lxmd = {
|
||||
enable = true;
|
||||
|
||||
identityFile = "<path-to-lxmd-identity-file>";
|
||||
|
||||
settings = {
|
||||
# LXMD settings, run `lxmd --exampleconfig` for details
|
||||
};
|
||||
|
||||
rnsd = {
|
||||
identityFile = "<path-to-rnsd-identity-file>";
|
||||
|
||||
settings = {
|
||||
reticulum = {
|
||||
# RNSD settings, run `rnsd --exampleconfig` for details
|
||||
};
|
||||
interfaces = {
|
||||
local = {
|
||||
type = "BackboneInterface";
|
||||
enabled = true;
|
||||
target_host = "127.0.0.1";
|
||||
target_port = 4242;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
The primary `rnsd` instance serves system-local `BackboneInterface` links for
|
||||
applications and external interfaces for the wider network. Each isolated app
|
||||
uses its own RNS configuration directory (for example with
|
||||
`--rnsconfig <path>`), and that local instance connects to the root instance
|
||||
through `BackboneInterface`.
|
||||
|
||||
Running multiple instances increases memory usage and adds one extra hop, but
|
||||
the performance impact is generally negligible.
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.services.lxmd;
|
||||
settingsFormat = pkgs.formats.configobj { };
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.lxmd = {
|
||||
enable = lib.mkEnableOption "Enable Reticulum Network Lightweight Extensible Message Format Daemon (lxmd)";
|
||||
|
||||
package = lib.mkPackageOption pkgs "lxmf" { };
|
||||
packageBinaryName = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "lxmd";
|
||||
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 lxmd configuration. The generated file is copied to the state directory on service start. Use `lxmd --exampleconfig` to get an example config file.";
|
||||
};
|
||||
|
||||
identityFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Path to lxmd identity file. This file will be copied to the state directory on service start.";
|
||||
};
|
||||
|
||||
rnsd = {
|
||||
settings = lib.mkOption {
|
||||
type = lib.types.nullOr settingsFormat.type;
|
||||
default = null;
|
||||
description = "Structured rnsd configuration. The generated file is copied to the state directory 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 state directory on service start.";
|
||||
};
|
||||
|
||||
identities = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.str;
|
||||
default = { };
|
||||
description = "Map of identity names to paths of identity files.";
|
||||
};
|
||||
};
|
||||
|
||||
extraGroups = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "Additional groups for the lxmd service user.";
|
||||
};
|
||||
|
||||
healthCheck = {
|
||||
enable = lib.mkEnableOption "wait for lxmd to become healthy after startup";
|
||||
|
||||
intervalSeconds = lib.mkOption {
|
||||
type = lib.types.ints.positive;
|
||||
default = 2;
|
||||
description = "Delay in seconds between each `lxmd --status` health check attempt.";
|
||||
};
|
||||
|
||||
timeoutSeconds = lib.mkOption {
|
||||
type = lib.types.ints.positive;
|
||||
default = 120;
|
||||
description = "Maximum time in seconds to wait for `lxmd --status` to succeed during startup.";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
systemd.services.lxmd = {
|
||||
description = "Reticulum Network Lightweight Extensible Message Format Daemon";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = lib.optionals config.services.rnsd.enable [ "rnsd.service" ];
|
||||
wants = lib.optionals config.services.rnsd.enable [ "rnsd.service" ];
|
||||
|
||||
preStart =
|
||||
let
|
||||
copySettings = lib.optionalString (cfg.settings != null) ''
|
||||
install -Dm600 ${settingsFormat.generate "lxmd.conf" cfg.settings} "$STATE_DIRECTORY"/lxmd/config
|
||||
'';
|
||||
copyIdentity = lib.optionalString (cfg.identityFile != null) ''
|
||||
install -Dm600 ${cfg.identityFile} "$STATE_DIRECTORY"/lxmd/identity
|
||||
'';
|
||||
copyRnsdSettings = lib.optionalString (cfg.rnsd.settings != null) ''
|
||||
install -Dm600 ${settingsFormat.generate "rnsd.conf" cfg.rnsd.settings} "$STATE_DIRECTORY"/rnsd/config
|
||||
'';
|
||||
copyRnsdIdentity = lib.optionalString (cfg.rnsd.identityFile != null) ''
|
||||
install -Dm600 ${cfg.rnsd.identityFile} "$STATE_DIRECTORY"/rnsd/storage/transport_identity
|
||||
'';
|
||||
copyRnsdIdentities = lib.concatStringsSep "\n" (
|
||||
lib.mapAttrsToList (name: file: ''
|
||||
install -Dm600 ${file} "$STATE_DIRECTORY"/rnsd/storage/identities/${name}
|
||||
'') cfg.rnsd.identities
|
||||
);
|
||||
in
|
||||
copyRnsdSettings + copyRnsdIdentity + copySettings + copyIdentity + copyRnsdIdentities;
|
||||
|
||||
serviceConfig =
|
||||
let
|
||||
waitForHealthy = pkgs.writeShellApplication {
|
||||
name = "lxmd-wait-for-health";
|
||||
runtimeInputs = [ pkgs.lxmf ];
|
||||
text = ''
|
||||
deadline=$((SECONDS + ${toString cfg.healthCheck.timeoutSeconds}))
|
||||
|
||||
until lxmd --status --config "$STATE_DIRECTORY"/lxmd --rnsconfig "$STATE_DIRECTORY"/rnsd >/dev/null 2>&1; do
|
||||
if [ "$SECONDS" -ge "$deadline" ]; then
|
||||
echo "lxmd 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 = "lxmd";
|
||||
SupplementaryGroups = cfg.extraGroups;
|
||||
RuntimeDirectory = "lxmd";
|
||||
CacheDirectory = "lxmd";
|
||||
ProtectSystem = "strict";
|
||||
|
||||
ExecStart = ''
|
||||
${lib.getExe' cfg.package cfg.packageBinaryName} --config ''${STATE_DIRECTORY}/lxmd --rnsconfig ''${STATE_DIRECTORY}/rnsd
|
||||
'';
|
||||
}
|
||||
// lib.optionalAttrs cfg.healthCheck.enable {
|
||||
ExecStartPost = lib.getExe waitForHealthy;
|
||||
TimeoutStartSec = cfg.healthCheck.timeoutSeconds + 5;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
meta = {
|
||||
doc = ./lxmd.md;
|
||||
maintainers = with lib.maintainers; [ drupol ];
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user