feat: implement RNS transport (Smol Mail 1.2) over microReticulum
This commit is contained in:
parent
af1d31be83
commit
7203556186
19 changed files with 1939 additions and 1141 deletions
164
flake.nix
164
flake.nix
|
|
@ -4,32 +4,96 @@
|
|||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
|
||||
# RNS carrier sources (RNS.md sec 8): the build sandbox has no network,
|
||||
# so every microReticulum FetchContent dependency is vendored as its own
|
||||
# flake input and handed over via RNS_<DEP>_SOURCE_DIR. microReticulum
|
||||
# is pinned at the commit RNS.md sec 5 verified against.
|
||||
microReticulum = {
|
||||
url = "github:attermann/microReticulum/40fa628809d57140180c1c833559ab96fec992c1";
|
||||
flake = false;
|
||||
};
|
||||
rns-arduinojson = {
|
||||
url = "github:bblanchon/ArduinoJson/ed69feadb95182dc53ac978975d310122060a767";
|
||||
flake = false;
|
||||
};
|
||||
rns-msgpack = {
|
||||
url = "github:hideakitai/MsgPack/1f552c31b940d6e9063ee17a4b3fa10c47b27169";
|
||||
flake = false;
|
||||
};
|
||||
rns-arxcontainer = {
|
||||
url = "github:hideakitai/ArxContainer/d6affcd0bc83219b863c20abf7c269214db8db2a";
|
||||
flake = false;
|
||||
};
|
||||
rns-arxtypetraits = {
|
||||
url = "github:hideakitai/ArxTypeTraits/702de9cc59c7e047cdc169ae3547718b289d2c02";
|
||||
flake = false;
|
||||
};
|
||||
rns-debuglog = {
|
||||
url = "github:hideakitai/DebugLog/b581f7dde6c276c5df684e2328f406d9754d2f46";
|
||||
flake = false;
|
||||
};
|
||||
rns-crypto = {
|
||||
url = "github:attermann/Crypto/984dc891330986c302a86c4e312d4f5abcc28359";
|
||||
flake = false;
|
||||
};
|
||||
rns-microstore = {
|
||||
url = "github:attermann/microStore/0f28567fe00ab8ab14624a34c2e9e0a000a44c46";
|
||||
flake = false;
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
outputs = { self, nixpkgs, flake-utils, microReticulum, rns-arduinojson
|
||||
, rns-msgpack, rns-arxcontainer, rns-arxtypetraits, rns-debuglog
|
||||
, rns-crypto, rns-microstore }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
inherit (pkgs) lib;
|
||||
|
||||
bunshin = pkgs.rustPlatform.buildRustPackage {
|
||||
# build.rs consumes these directly (RNS.md sec 7.1); with every
|
||||
# dependency vendored, the cmake configure step never fetches.
|
||||
rnsSourceEnv = {
|
||||
MICRORETICULUM_SOURCE_DIR = "${microReticulum}";
|
||||
RNS_ARDUINOJSON_SOURCE_DIR = "${rns-arduinojson}";
|
||||
RNS_MSGPACK_SOURCE_DIR = "${rns-msgpack}";
|
||||
RNS_ARXCONTAINER_SOURCE_DIR = "${rns-arxcontainer}";
|
||||
RNS_ARXTYPETRAITS_SOURCE_DIR = "${rns-arxtypetraits}";
|
||||
RNS_DEBUGLOG_SOURCE_DIR = "${rns-debuglog}";
|
||||
RNS_CRYPTO_SOURCE_DIR = "${rns-crypto}";
|
||||
RNS_MICROSTORE_SOURCE_DIR = "${rns-microstore}";
|
||||
};
|
||||
|
||||
bunshin = { rns ? false }: pkgs.rustPlatform.buildRustPackage {
|
||||
pname = "bunshin";
|
||||
version = "0.1.0";
|
||||
src = ./.;
|
||||
cargoLock.lockFile = ./Cargo.lock;
|
||||
nativeBuildInputs = [ pkgs.pkg-config ];
|
||||
nativeBuildInputs = [ pkgs.pkg-config ]
|
||||
++ lib.optionals rns [ pkgs.cmake pkgs.ninja pkgs.gcc ];
|
||||
buildInputs = [ pkgs.sqlite ];
|
||||
buildFeatures = lib.optionals rns [ "rns" ];
|
||||
env = lib.optionalAttrs rns rnsSourceEnv;
|
||||
# ninja's setup hook claims buildPhase before the cargo hooks
|
||||
# can, which would run ninja against no build.ninja instead of
|
||||
# cargo; ninja here is only for build.rs to invoke through cmake.
|
||||
dontUseNinjaBuild = rns;
|
||||
dontUseNinjaCheck = rns;
|
||||
dontUseNinjaInstall = rns;
|
||||
};
|
||||
in
|
||||
{
|
||||
packages.default = bunshin;
|
||||
packages.default = bunshin { };
|
||||
packages.rns = bunshin { rns = true; };
|
||||
|
||||
apps.default = flake-utils.lib.mkApp {
|
||||
drv = bunshin;
|
||||
drv = bunshin { };
|
||||
name = "bunshin";
|
||||
};
|
||||
|
||||
devShells.default = pkgs.mkShell {
|
||||
packages = [ pkgs.cargo pkgs.rustc pkgs.rustfmt pkgs.clippy pkgs.pkg-config pkgs.gcc pkgs.sqlite ];
|
||||
packages = with pkgs; [ cargo rustc rustfmt clippy pkg-config gcc sqlite cmake ninja ];
|
||||
env = rnsSourceEnv;
|
||||
};
|
||||
}) // {
|
||||
nixosModules.default = { config, lib, pkgs, ... }:
|
||||
|
|
@ -44,7 +108,10 @@
|
|||
package = mkOption {
|
||||
type = types.package;
|
||||
default = self.packages.${pkgs.system}.default;
|
||||
description = "bunshin package to run.";
|
||||
description = ''
|
||||
bunshin package to run. Use `packages.rns` when the RNS
|
||||
carrier is enabled: the default package is built without it.
|
||||
'';
|
||||
};
|
||||
|
||||
host = mkOption {
|
||||
|
|
@ -152,6 +219,72 @@
|
|||
default = false;
|
||||
description = "Open the configured TCP port in the firewall.";
|
||||
};
|
||||
|
||||
rns = {
|
||||
enable = mkEnableOption "the RNS carrier alongside TCP (needs an rns-built package)";
|
||||
|
||||
keyFile = mkOption {
|
||||
type = types.path;
|
||||
description = ''
|
||||
Path to the server's Reticulum identity (64 raw bytes,
|
||||
x25519 || ed25519 private halves, generated with
|
||||
`bunshin rns-keygen`). RNS writes the private key
|
||||
unencrypted, so protect the file like any long-term key.
|
||||
'';
|
||||
};
|
||||
|
||||
maxEnvelope = mkOption {
|
||||
type = types.ints.positive;
|
||||
default = 32768;
|
||||
description = "Maximum accepted envelope size over RNS, in bytes (RNS.md sec 3).";
|
||||
};
|
||||
|
||||
fetchBudget = mkOption {
|
||||
type = types.ints.positive;
|
||||
default = 32768;
|
||||
description = "Bytes one FETCH response may total over RNS.";
|
||||
};
|
||||
|
||||
maxLinks = mkOption {
|
||||
type = types.ints.positive;
|
||||
default = 100;
|
||||
description = "Maximum concurrent Reticulum links; further links are refused.";
|
||||
};
|
||||
|
||||
rateLinkRequests = mkOption {
|
||||
type = types.ints.positive;
|
||||
default = 60;
|
||||
description = "Max requests per minute, per link.";
|
||||
};
|
||||
|
||||
rateLinkBytes = mkOption {
|
||||
type = types.ints.positive;
|
||||
default = 1048576;
|
||||
description = "Max request bytes per minute, per link.";
|
||||
};
|
||||
|
||||
udpListenPort = mkOption {
|
||||
type = types.port;
|
||||
default = 4242;
|
||||
description = ''
|
||||
UDP port the Reticulum interface listens on. RNS reaches
|
||||
the mesh through a configured interface rather than a
|
||||
listening TCP port; only this UDP port needs a firewall
|
||||
opening.
|
||||
'';
|
||||
};
|
||||
|
||||
udpForward = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Optional host[:port] the interface forwards every packet
|
||||
to. Without it, replies go to the source address of the
|
||||
last datagram received, which suits a listen-only
|
||||
point-to-point setup.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
|
@ -186,6 +319,20 @@
|
|||
--rate-sends ${toString cfg.rateSends}
|
||||
--rate-tokens ${toString cfg.rateTokens}
|
||||
)
|
||||
${lib.optionalString cfg.rns.enable ''
|
||||
args+=(
|
||||
--rns
|
||||
--rns-key ${cfg.rns.keyFile}
|
||||
--rns-max-envelope ${toString cfg.rns.maxEnvelope}
|
||||
--rns-fetch-budget ${toString cfg.rns.fetchBudget}
|
||||
--rns-max-links ${toString cfg.rns.maxLinks}
|
||||
--rns-rate-link-requests ${toString cfg.rns.rateLinkRequests}
|
||||
--rns-rate-link-bytes ${toString cfg.rns.rateLinkBytes}
|
||||
--rns-udp 0.0.0.0:${toString cfg.rns.udpListenPort}
|
||||
)
|
||||
''}
|
||||
${lib.optionalString (cfg.rns.enable && cfg.rns.udpForward != null)
|
||||
''args+=(--rns-udp-forward ${lib.escapeShellArg cfg.rns.udpForward})''}
|
||||
${lib.optionalString (cfg.inviteToken != null)
|
||||
''args+=(--invite-token ${lib.escapeShellArg cfg.inviteToken})''}
|
||||
${lib.optionalString (cfg.inviteTokenFile != null)
|
||||
|
|
@ -201,6 +348,9 @@
|
|||
};
|
||||
};
|
||||
|
||||
# RNS needs no TCP port; only its UDP interface may be opened.
|
||||
networking.firewall.allowedUDPPorts =
|
||||
mkIf (cfg.rns.enable && cfg.openFirewall) [ cfg.rns.udpListenPort ];
|
||||
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
|
||||
};
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue