diff --git a/nixos/doc/manual/release-notes/rl-2605.section.md b/nixos/doc/manual/release-notes/rl-2605.section.md index 364e2d3cfa12..3d632525c048 100644 --- a/nixos/doc/manual/release-notes/rl-2605.section.md +++ b/nixos/doc/manual/release-notes/rl-2605.section.md @@ -92,6 +92,8 @@ - [kiwix-serve](https://wiki.kiwix.org/wiki/Kiwix-serve), a service that serves ZIM files (such as Wikipedia archives) over HTTP. Available as [services.kiwix-serve](#opt-services.kiwix-serve.enable). +- [matterjs-server](https://github.com/matter-js/matterjs-server), a Matter controller with a Home Assistant compatible WebSocket API. Available as [services.matterjs-server](#opt-services.matterjs-server.enable). + - [Remark42](https://remark42.com/), a self-hosted comment engine. Available as [services.remark42](#opt-services.remark42.enable). - [LibreChat](https://www.librechat.ai/), open-source self-hostable ChatGPT clone with Agents and RAG APIs. Available as [services.librechat](#opt-services.librechat.enable). diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index c57b627e875c..cb6dbf5b41bd 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -733,6 +733,7 @@ ./services/home-automation/home-assistant.nix ./services/home-automation/homebridge.nix ./services/home-automation/matter-server.nix + ./services/home-automation/matterjs-server.nix ./services/home-automation/openthread-border-router.nix ./services/home-automation/wyoming/faster-whisper.nix ./services/home-automation/wyoming/openwakeword.nix diff --git a/nixos/modules/services/home-automation/matterjs-server.nix b/nixos/modules/services/home-automation/matterjs-server.nix new file mode 100644 index 000000000000..e6cfd2dbcfb3 --- /dev/null +++ b/nixos/modules/services/home-automation/matterjs-server.nix @@ -0,0 +1,127 @@ +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.services.matterjs-server; +in +{ + options.services.matterjs-server = { + enable = lib.mkEnableOption "matterjs-server, a Matter Controller WebSocket server based on Matter.js"; + + package = lib.mkPackageOption pkgs "matterjs-server" { }; + + listenAddress = lib.mkOption { + type = lib.types.str; + default = "127.0.0.1"; + description = "IP address the WebSocket API binds to."; + }; + + port = lib.mkOption { + type = lib.types.port; + default = 5580; + description = "TCP port the WebSocket API listens on."; + }; + + openFirewall = lib.mkEnableOption null // { + description = "Whether to open the WebSocket API port in the firewall."; + }; + + bluetoothSupport = lib.mkEnableOption '' + BLE (Bluetooth Low Energy) commissioning support. Select an adapter with + `--bluetooth-adapter=` in + {option}`services.matterjs-server.extraArgs` + ''; + + extraArgs = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ + "--primary-interface=enp11s0" + "--log-level=debug" + ]; + description = '' + Additional command-line arguments passed to `matterjs-server`. See + `matterjs-server --help` for the full list of options. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + networking.firewall = lib.mkIf cfg.openFirewall { + allowedTCPPorts = [ cfg.port ]; + }; + + systemd.services.matterjs-server = { + description = "Matter Controller WebSocket server based on Matter.js"; + documentation = [ "https://github.com/matter-js/matterjs-server" ]; + + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + + serviceConfig = + let + bluetoothCaps = [ + "CAP_NET_RAW" + "CAP_NET_ADMIN" + ]; + in + { + ExecStart = lib.escapeShellArgs ( + [ + (lib.getExe cfg.package) + "--storage-path=%S/matterjs-server" + "--listen-address=${cfg.listenAddress}" + "--port=${toString cfg.port}" + "--production-mode" + ] + ++ cfg.extraArgs + ); + + StateDirectory = "matterjs-server"; + StateDirectoryMode = "0700"; + + DynamicUser = true; + + # Required for interaction with hci devices and bluetooth sockets + AmbientCapabilities = lib.optionals cfg.bluetoothSupport bluetoothCaps; + CapabilityBoundingSet = lib.optionals cfg.bluetoothSupport bluetoothCaps; + LockPersonality = true; + NoNewPrivileges = true; + PrivateTmp = true; + PrivateUsers = !cfg.bluetoothSupport; # Prevents gaining capabilities in the host namespace + ProcSubset = "pid"; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_NETLINK" + "AF_UNIX" + ] + ++ lib.optional cfg.bluetoothSupport "AF_BLUETOOTH"; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@privileged" + ]; + UMask = "0077"; + }; + }; + }; + + meta.maintainers = with lib.maintainers; [ kranzes ]; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index f9ecf72845ed..b669f33158ee 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -966,6 +966,7 @@ in matrix-synapse-workers = runTest ./matrix/synapse-workers.nix; matrix-tuwunel = runTest ./matrix/tuwunel.nix; matter-server = runTest ./matter-server.nix; + matterjs-server = runTest ./matterjs-server.nix; mattermost = handleTest ./mattermost { }; mautrix-discord = runTest ./matrix/mautrix-discord.nix; mautrix-meta-postgres = runTest ./matrix/mautrix-meta-postgres.nix; diff --git a/nixos/tests/matterjs-server.nix b/nixos/tests/matterjs-server.nix new file mode 100644 index 000000000000..229dd5edbd11 --- /dev/null +++ b/nixos/tests/matterjs-server.nix @@ -0,0 +1,23 @@ +{ lib, ... }: + +{ + name = "matterjs-server"; + meta.maintainers = with lib.maintainers; [ kranzes ]; + + nodes.machine.services.matterjs-server.enable = true; + + testScript = + { nodes, ... }: + let + inherit (nodes.machine.services.matterjs-server) listenAddress port package; + in + '' + import json + + machine.wait_for_unit("matterjs-server.service") + machine.wait_for_open_port(${toString port}) + + health = json.loads(machine.succeed("curl -fsS http://${listenAddress}:${toString port}/health")) + assert health["version"] == "${package.version}" + ''; +} diff --git a/pkgs/by-name/ma/matterjs-server/package.nix b/pkgs/by-name/ma/matterjs-server/package.nix index 00e990d97da2..4195dcefbf00 100644 --- a/pkgs/by-name/ma/matterjs-server/package.nix +++ b/pkgs/by-name/ma/matterjs-server/package.nix @@ -7,6 +7,7 @@ udev, versionCheckHook, nix-update-script, + nixosTests, }: buildNpmPackage (finalAttrs: { @@ -59,7 +60,10 @@ buildNpmPackage (finalAttrs: { doInstallCheck = true; versionCheckProgramArg = "--version"; - passthru.updateScript = nix-update-script { }; + passthru = { + tests = { inherit (nixosTests) matterjs-server; }; + updateScript = nix-update-script { }; + }; meta = { description = "Matter server based on Matter.js";