diff --git a/nixos/doc/manual/release-notes/rl-2605.section.md b/nixos/doc/manual/release-notes/rl-2605.section.md index e7bbe1501de4..830744158a85 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 new file mode 100644 index 000000000000..4195dcefbf00 --- /dev/null +++ b/pkgs/by-name/ma/matterjs-server/package.nix @@ -0,0 +1,77 @@ +{ + lib, + buildNpmPackage, + fetchFromGitHub, + makeBinaryWrapper, + nodejs, + udev, + versionCheckHook, + nix-update-script, + nixosTests, +}: + +buildNpmPackage (finalAttrs: { + pname = "matterjs-server"; + version = "0.7.1"; + __structuredAttrs = true; + strictDeps = true; + + src = fetchFromGitHub { + owner = "matter-js"; + repo = "matterjs-server"; + tag = "v${finalAttrs.version}"; + hash = "sha256-iWFhpPeqY3RVfIrZa+Y2KmwWIZtMtj8EjwjoWYaA/Ao="; + }; + + npmDepsHash = "sha256-ZjznhmsLavnLsNHNzH9IlZdLRMYpbLKz1q2O9A/ut+Y="; + + nativeBuildInputs = [ + makeBinaryWrapper + versionCheckHook + ]; + + buildInputs = [ udev ]; + + preBuild = "npm run version -- --apply"; + + dontNpmInstall = true; + + makeWrapperArgs = [ + "--set" + "NODE_OPTIONS" + "--enable-source-maps" + ]; + + installPhase = '' + runHook preInstall + + npm prune --omit=dev --no-save + + mkdir -p $out/lib/matterjs-server + cp -r node_modules packages $out/lib/matterjs-server/ + + makeWrapper ${lib.getExe nodejs} $out/bin/matterjs-server \ + --add-flags "$out/lib/matterjs-server/packages/matter-server/dist/esm/MatterServer.js" \ + "''${makeWrapperArgs[@]}" + + runHook postInstall + ''; + + doInstallCheck = true; + versionCheckProgramArg = "--version"; + + passthru = { + tests = { inherit (nixosTests) matterjs-server; }; + updateScript = nix-update-script { }; + }; + + meta = { + description = "Matter server based on Matter.js"; + homepage = "https://github.com/matter-js/matterjs-server"; + changelog = "https://github.com/matter-js/matterjs-server/blob/${finalAttrs.src.rev}/CHANGELOG.md"; + license = lib.licenses.asl20; + maintainers = with lib.maintainers; [ kranzes ]; + mainProgram = "matterjs-server"; + platforms = lib.platforms.linux; + }; +})