From 095da00b2d9701fa85828d4ad165760ea6d1f9d5 Mon Sep 17 00:00:00 2001 From: 3JlOy_PYCCKUI <3jl0y_pycckui@riseup.net> Date: Thu, 6 Mar 2025 15:37:18 +0300 Subject: [PATCH] nixos/umurmur: init --- nixos/doc/manual/redirects.json | 6 + .../manual/release-notes/rl-2505.section.md | 2 + nixos/modules/module-list.nix | 1 + nixos/modules/services/networking/umurmur.md | 33 +++ nixos/modules/services/networking/umurmur.nix | 244 ++++++++++++++++++ nixos/tests/all-tests.nix | 1 + nixos/tests/umurmur.nix | 99 +++++++ pkgs/by-name/um/umurmur/package.nix | 7 + 8 files changed, 393 insertions(+) create mode 100644 nixos/modules/services/networking/umurmur.md create mode 100644 nixos/modules/services/networking/umurmur.nix create mode 100644 nixos/tests/umurmur.nix diff --git a/nixos/doc/manual/redirects.json b/nixos/doc/manual/redirects.json index e8eb5a3058f7..1acabd14592f 100644 --- a/nixos/doc/manual/redirects.json +++ b/nixos/doc/manual/redirects.json @@ -1289,6 +1289,12 @@ "module-services-foundationdb-full-docs": [ "index.html#module-services-foundationdb-full-docs" ], + "module-service-umurmur": [ + "index.html#module-service-umurmur" + ], + "module-service-umurmur-quick-start": [ + "index.html#module-service-umurmur-quick-start" + ], "module-borgbase": [ "index.html#module-borgbase" ], diff --git a/nixos/doc/manual/release-notes/rl-2505.section.md b/nixos/doc/manual/release-notes/rl-2505.section.md index e3c61c882cb4..abb3db36026b 100644 --- a/nixos/doc/manual/release-notes/rl-2505.section.md +++ b/nixos/doc/manual/release-notes/rl-2505.section.md @@ -77,6 +77,8 @@ - [Yggdrasil-Jumper](https://github.com/one-d-wide/yggdrasil-jumper) is an independent project that aims to transparently reduce latency of a connection over Yggdrasil network, utilizing NAT traversal to automatically bypass intermediary nodes. +- [uMurmur](https://umurmur.net), minimalistic Mumble server primarily targeted to run on embedded computers. Available as [services.umurmur](options.html#opt-services.umurmur). + - [Zenoh](https://zenoh.io/), a pub/sub/query protocol with low overhead. The Zenoh router daemon is available as [services.zenohd](options.html#opt-services.zenohd.enable) - [ytdl-sub](https://github.com/jmbannon/ytdl-sub), a tool that downloads media via yt-dlp and prepares it for your favorite media player, including Kodi, Jellyfin, Plex, Emby, and modern music players. Available as [services.ytdl-sub](options.html#opt-services.ytdl-sub.instances). diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index efa81f833fb5..3918a875ad32 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1315,6 +1315,7 @@ ./services/networking/trickster.nix ./services/networking/twingate.nix ./services/networking/ucarp.nix + ./services/networking/umurmur.nix ./services/networking/unbound.nix ./services/networking/unifi.nix ./services/networking/uptermd.nix diff --git a/nixos/modules/services/networking/umurmur.md b/nixos/modules/services/networking/umurmur.md new file mode 100644 index 000000000000..825806bf5198 --- /dev/null +++ b/nixos/modules/services/networking/umurmur.md @@ -0,0 +1,33 @@ +# uMurmur {#module-service-umurmur} + +[uMurmur](http://umurmur.net/) is a minimalistic Mumble server primarily targeted to run on embedded computers. This module enables it (`umurmurd`). + +## Quick Start {#module-service-umurmur-quick-start} + +```nix +{ + services.umurmur = { + enable = true; + openFirewall = true; + settings = { + port = 7365; + channels = [ + { + name = "root"; + parent = ""; + description = "Root channel. No entry."; + noenter = true; + } + { + name = "lobby"; + parent = "root"; + description = "Lobby channel"; + } + ]; + default_channel = "lobby"; + }; + }; +} +``` + +See a full configuration in [umurmur.conf.example](https://github.com/umurmur/umurmur/blob/master/umurmur.conf.example) diff --git a/nixos/modules/services/networking/umurmur.nix b/nixos/modules/services/networking/umurmur.nix new file mode 100644 index 000000000000..5d4719c6e522 --- /dev/null +++ b/nixos/modules/services/networking/umurmur.nix @@ -0,0 +1,244 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.services.umurmur; + dumpAttrset = + x: top_level: + (lib.optionalString (!top_level) "{") + + (lib.concatLines (lib.mapAttrsToList (name: value: "${name} = ${toConfigValue value false};") x)) + + (lib.optionalString (!top_level) "}"); + dumpList = x: top_level: "(${lib.concatStringsSep ",\n" (map (y: "${toConfigValue y false}") x)})"; + + toConfigValue = + x: top_level: + if builtins.isList x then + dumpList x top_level + else if builtins.isAttrs x then + dumpAttrset x top_level + else + builtins.toJSON x; + dumpCfg = x: toConfigValue x true; + configAttrs = lib.filterAttrsRecursive (name: value: value != null) cfg.settings; + configFile = pkgs.writeTextFile { + name = "umurmur.conf"; + checkPhase = '' + ${lib.getExe cfg.package} -t -c "$target" + ''; + text = "\n" + (dumpCfg configAttrs) + "\n"; + }; +in +{ + options = { + services.umurmur = { + enable = lib.mkEnableOption "uMurmur Mumble server"; + + package = lib.mkPackageOption pkgs "umurmur" { }; + + openFirewall = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Open ports in the firewall for the uMurmur Mumble server. + ''; + }; + + settings = lib.mkOption { + type = lib.types.submodule { + freeformType = + let + valueType = + with lib.types; + oneOf [ + bool + int + float + str + path + (listOf (attrsOf valueType)) + ] + // { + description = "uMurmur config value"; + }; + in + valueType; + options = { + welcometext = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = "Welcome to uMurmur!"; + description = "Welcome message for connected clients."; + }; + + bindaddr = lib.mkOption { + type = lib.types.str; + default = "0.0.0.0"; + description = "IPv4 address to bind to. Defaults binding on all addresses."; + }; + + bindaddr6 = lib.mkOption { + type = lib.types.str; + default = "::"; + description = "IPv6 address to bind to. Defaults binding on all addresses."; + }; + + bindport = lib.mkOption { + type = lib.types.port; + default = 64739; + description = "Port to bind to (UDP and TCP)."; + }; + + password = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Required password to join server, if specified."; + }; + + max_bandwidth = lib.mkOption { + type = lib.types.int; + default = 48000; + description = '' + Maximum bandwidth (in bits per second) that clients may send + speech at. + ''; + }; + + max_users = lib.mkOption { + type = lib.types.int; + default = 10; + description = "Maximum number of concurrent clients allowed."; + }; + + certificate = lib.mkOption { + type = lib.types.str; + default = "/var/lib/private/umurmur/cert.crt"; + description = "Path to your SSL certificate. Generates self-signed automatically if not exists."; + }; + + private_key = lib.mkOption { + type = lib.types.str; + default = "/var/lib/private/umurmur/key.key"; + description = "Path to your SSL key. Generates self-signed automatically if not exists."; + }; + + ca_path = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Path to your SSL CA certificate."; + }; + + channels = lib.mkOption { + type = lib.types.listOf lib.types.attrs; + default = [ + { + name = "root"; + parent = ""; + description = "Root channel."; + noenter = false; + } + ]; + description = "Channel tree definitions."; + }; + + channel_links = lib.mkOption { + type = lib.types.listOf lib.types.attrs; + default = [ ]; + example = [ + { + source = "Lobby"; + destination = "Red team"; + } + ]; + description = "Channel tree definitions."; + }; + + default_channel = lib.mkOption { + type = lib.types.str; + default = "root"; + description = "The channel in which users will appear in when connecting."; + }; + + }; + }; + default = { }; + description = "Settings of uMurmur. For reference see https://github.com/umurmur/umurmur/blob/master/umurmur.conf.example"; + }; + + configFile = lib.mkOption rec { + type = lib.types.path; + default = configFile; + description = "Configuration file, default is generated from config.service.umurmur.settings"; + defaultText = description; + }; + }; + }; + + config = lib.mkIf cfg.enable { + networking.firewall = lib.mkIf cfg.openFirewall { + allowedTCPPorts = [ cfg.settings.bindport ]; + allowedUDPPorts = [ cfg.settings.bindport ]; + }; + + systemd.services.umurmur = { + description = "uMurmur Mumble Server"; + wants = [ "network.target" ]; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "exec"; + ExecStart = "${lib.getExe cfg.package} -d -c ${cfg.configFile}"; + Restart = "on-failure"; + DynamicUser = true; + StateDirectory = "umurmur"; + ReadWritePaths = "/dev/shm"; + + # hardening + UMask = 27; + MemoryDenyWriteExecute = true; + AmbientCapabilities = [ "" ]; + CapabilityBoundingSet = [ "" ]; + DevicePolicy = "closed"; + LockPersonality = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateTmp = true; + PrivateUsers = true; + ProtectSystem = "full"; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProcSubset = "pid"; + ProtectProc = "invisible"; + RemoveIPC = true; + RestrictAddressFamilies = [ + "AF_UNIX" + "AF_INET" + "AF_INET6" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@cpu-emulation" + "~@debug" + "~@mount" + "~@obsolete" + "~@privileged" + "~@resources" + ]; + }; + }; + }; + + meta.maintainers = with lib.maintainers; [ _3JlOy-PYCCKUi ]; + meta.doc = ./umurmur.md; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 809440bfbf97..999bfd08af55 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1186,6 +1186,7 @@ in { ucarp = handleTest ./ucarp.nix {}; udisks2 = handleTest ./udisks2.nix {}; ulogd = handleTest ./ulogd/ulogd.nix {}; + umurmur = handleTest ./umurmur.nix {}; unbound = handleTest ./unbound.nix {}; unifi = handleTest ./unifi.nix {}; unit-php = handleTest ./web-servers/unit-php.nix {}; diff --git a/nixos/tests/umurmur.nix b/nixos/tests/umurmur.nix new file mode 100644 index 000000000000..0fcdeb1847ff --- /dev/null +++ b/nixos/tests/umurmur.nix @@ -0,0 +1,99 @@ +import ./make-test-python.nix ( + { pkgs, ... }: + + let + client = + { pkgs, ... }: + { + imports = [ ./common/x11.nix ]; + environment.systemPackages = [ pkgs.mumble ]; + }; + port = 56457; + in + { + name = "mumble"; + meta = with pkgs.lib.maintainers; { + maintainers = [ _3JlOy-PYCCKUi ]; + }; + + nodes = { + server = + { ... }: + { + services.umurmur = { + enable = true; + openFirewall = true; + settings = { + password = "testpassword"; + channels = [ + { + name = "root"; + parent = ""; + description = "Root channel. No entry."; + noenter = true; + } + { + name = "lobby"; + parent = "root"; + description = "Lobby channel"; + } + ]; + default_channel = "lobby"; + bindport = port; + }; + }; + }; + + client1 = client; + client2 = client; + }; + + testScript = '' + start_all() + + server.wait_for_unit("umurmur.service") + client1.wait_for_x() + client2.wait_for_x() + + client1.execute("mumble mumble://client1:testpassword\@server:${toString port}/lobby >&2 &") + client2.execute("mumble mumble://client2:testpassword\@server:${toString port}/lobby >&2 &") + + # cancel client audio configuration + client1.wait_for_window(r"Audio Tuning Wizard") + client2.wait_for_window(r"Audio Tuning Wizard") + server.sleep(5) # wait because mumble is slow to register event handlers + client1.send_key("esc") + client2.send_key("esc") + + # cancel client cert configuration + client1.wait_for_window(r"Certificate Management") + client2.wait_for_window(r"Certificate Management") + server.sleep(5) # wait because mumble is slow to register event handlers + client1.send_key("esc") + client2.send_key("esc") + + # accept server certificate + client1.wait_for_window(r"^Mumble$") + client2.wait_for_window(r"^Mumble$") + server.sleep(5) # wait because mumble is slow to register event handlers + client1.send_chars("y") + client2.send_chars("y") + server.sleep(5) # wait because mumble is slow to register event handlers + + # sometimes the wrong of the 2 windows is focused, we switch focus and try pressing "y" again + client1.send_key("alt-tab") + client2.send_key("alt-tab") + server.sleep(5) # wait because mumble is slow to register event handlers + client1.send_chars("y") + client2.send_chars("y") + + # Find clients in logs + server.wait_until_succeeds( + "journalctl -eu umurmur -o cat | grep -q 'User client1 authenticated'" + ) + server.wait_until_succeeds( + "journalctl -eu umurmur -o cat | grep -q 'User client2 authenticated'" + ) + ''; + } +) diff --git a/pkgs/by-name/um/umurmur/package.nix b/pkgs/by-name/um/umurmur/package.nix index 60f42bf77bdc..640ca232a3da 100644 --- a/pkgs/by-name/um/umurmur/package.nix +++ b/pkgs/by-name/um/umurmur/package.nix @@ -6,6 +6,7 @@ openssl, protobufc, libconfig, + nixosTests, }: stdenv.mkDerivation rec { @@ -36,6 +37,12 @@ stdenv.mkDerivation rec { "--enable-shmapi" ]; + passthru = { + tests = { + inherit (nixosTests) umurmur; + }; + }; + meta = with lib; { description = "Minimalistic Murmur (Mumble server)"; license = licenses.bsd3;