diff --git a/nixos/doc/manual/release-notes/rl-2311.section.md b/nixos/doc/manual/release-notes/rl-2311.section.md index ffe977bf33b15..eca3242cebb9d 100644 --- a/nixos/doc/manual/release-notes/rl-2311.section.md +++ b/nixos/doc/manual/release-notes/rl-2311.section.md @@ -104,6 +104,8 @@ - hardware/infiniband.nix adds infiniband subnet manager support using an [opensm](https://github.com/linux-rdma/opensm) systemd-template service, instantiated on card guids. The module also adds kernel modules and cli tooling to help administrators debug and measure performance. Available as [hardware.infiniband.enable](#opt-hardware.infiniband.enable). +- [zwave-js](https://github.com/zwave-js/zwave-js-server), a small server wrapper around Z-Wave JS to access it via a WebSocket. Available as [services.zwave-js](#opt-services.zwave-js.enable). + - [Honk](https://humungus.tedunangst.com/r/honk), a complete ActivityPub server with minimal setup and support costs. Available as [services.honk](#opt-services.honk.enable). diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 2a6ca202024b1..f985de6144534 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -563,6 +563,7 @@ ./services/home-automation/home-assistant.nix ./services/home-automation/homeassistant-satellite.nix ./services/home-automation/zigbee2mqtt.nix + ./services/home-automation/zwave-js.nix ./services/logging/SystemdJournal2Gelf.nix ./services/logging/awstats.nix ./services/logging/filebeat.nix diff --git a/nixos/modules/services/home-automation/zwave-js.nix b/nixos/modules/services/home-automation/zwave-js.nix new file mode 100644 index 0000000000000..87c9b8f1ac81b --- /dev/null +++ b/nixos/modules/services/home-automation/zwave-js.nix @@ -0,0 +1,152 @@ +{config, pkgs, lib, ...}: + +with lib; + +let + cfg = config.services.zwave-js; + mergedConfigFile = "/run/zwave-js/config.json"; + settingsFormat = pkgs.formats.json {}; +in { + options.services.zwave-js = { + enable = mkEnableOption (mdDoc "the zwave-js server on boot"); + + package = mkPackageOptionMD pkgs "zwave-js-server" { }; + + port = mkOption { + type = types.port; + default = 3000; + description = mdDoc '' + Port for the server to listen on. + ''; + }; + + serialPort = mkOption { + type = types.path; + description = mdDoc '' + Serial port device path for Z-Wave controller. + ''; + example = "/dev/ttyUSB0"; + }; + + secretsConfigFile = mkOption { + type = types.path; + description = mdDoc '' + JSON file containing secret keys. A dummy example: + + ``` + { + "securityKeys": { + "S0_Legacy": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "S2_Unauthenticated": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + "S2_Authenticated": "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", + "S2_AccessControl": "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" + } + } + ``` + + See + + for details. This file will be merged with the module-generated config + file (taking precedence). + + Z-Wave keys can be generated with: + + {command}`< /dev/urandom tr -dc A-F0-9 | head -c32 ;echo` + + + ::: {.warning} + A file in the nix store should not be used since it will be readable to + all users. + ::: + ''; + example = "/secrets/zwave-js-keys.json"; + }; + + settings = mkOption { + type = lib.types.submodule { + freeformType = settingsFormat.type; + + options = { + storage = { + cacheDir = mkOption { + type = types.path; + default = "/var/cache/zwave-js"; + readOnly = true; + description = lib.mdDoc "Cache directory"; + }; + }; + }; + }; + default = {}; + description = mdDoc '' + Configuration settings for the generated config + file. + ''; + }; + + extraFlags = lib.mkOption { + type = with lib.types; listOf str; + default = [ ]; + example = [ "--mock-driver" ]; + description = lib.mdDoc '' + Extra flags to pass to command + ''; + }; + }; + + config = mkIf cfg.enable { + systemd.services.zwave-js = let + configFile = settingsFormat.generate "zwave-js-config.json" cfg.settings; + in { + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + description = "Z-Wave JS Server"; + serviceConfig = { + ExecStartPre = '' + /bin/sh -c "${pkgs.jq}/bin/jq -s '.[0] * .[1]' ${configFile} ${cfg.secretsConfigFile} > ${mergedConfigFile}" + ''; + ExecStart = lib.concatStringsSep " " [ + "${cfg.package}/bin/zwave-server" + "--config ${mergedConfigFile}" + "--port ${toString cfg.port}" + cfg.serialPort + (escapeShellArgs cfg.extraFlags) + ]; + Restart = "on-failure"; + User = "zwave-js"; + SupplementaryGroups = [ "dialout" ]; + CacheDirectory = "zwave-js"; + RuntimeDirectory = "zwave-js"; + + # Hardening + CapabilityBoundingSet = ""; + DeviceAllow = [cfg.serialPort]; + DevicePolicy = "closed"; + DynamicUser = true; + LockPersonality = true; + MemoryDenyWriteExecute = false; + NoNewPrivileges = true; + PrivateUsers = true; + PrivateTmp = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + RemoveIPC = true; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service @pkey" + "~@privileged @resources" + ]; + UMask = "0077"; + }; + }; + }; + + meta.maintainers = with lib.maintainers; [ graham33 ]; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index f7f8ac8fec880..891dc71487691 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -932,4 +932,5 @@ in { zram-generator = handleTest ./zram-generator.nix {}; zrepl = handleTest ./zrepl.nix {}; zsh-history = handleTest ./zsh-history.nix {}; + zwave-js = handleTest ./zwave-js.nix {}; } diff --git a/nixos/tests/zwave-js.nix b/nixos/tests/zwave-js.nix new file mode 100644 index 0000000000000..9239e6964fd78 --- /dev/null +++ b/nixos/tests/zwave-js.nix @@ -0,0 +1,31 @@ +import ./make-test-python.nix ({ pkgs, lib, ...} : + +let + secretsConfigFile = pkgs.writeText "secrets.json" (builtins.toJSON { + securityKeys = { + "S0_Legacy" = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; + }; + }); +in { + name = "zwave-js"; + meta.maintainers = with lib.maintainers; [ graham33 ]; + + nodes = { + machine = { config, ... }: { + services.zwave-js = { + enable = true; + serialPort = "/dev/null"; + extraFlags = ["--mock-driver"]; + inherit secretsConfigFile; + }; + }; + }; + + testScript = '' + start_all() + + machine.wait_for_unit("zwave-js.service") + machine.wait_for_open_port(3000) + machine.wait_until_succeeds("journalctl --since -1m --unit zwave-js --grep 'ZwaveJS server listening'") + ''; +}) diff --git a/pkgs/by-name/zw/zwave-js-server/package.nix b/pkgs/by-name/zw/zwave-js-server/package.nix new file mode 100644 index 0000000000000..f97e16d66135b --- /dev/null +++ b/pkgs/by-name/zw/zwave-js-server/package.nix @@ -0,0 +1,36 @@ +{ lib +, buildNpmPackage +, fetchFromGitHub +, nixosTests +}: + +buildNpmPackage rec { + pname = "zwave-js-server"; + version = "1.33.0"; + + src = fetchFromGitHub { + owner = "zwave-js"; + repo = pname; + rev = version; + hash = "sha256-Lll3yE1v4ybJTjKO8dhPXMD/3VCn+9+fpnN7XczqaE4="; + }; + + npmDepsHash = "sha256-Re9fo+9+Z/+UGyDPlNWelH/4tLxcITPYXOCddQE9YDY="; + + # For some reason the zwave-js dependency is in devDependencies + npmFlags = [ "--include=dev" ]; + + passthru = { + tests = { + inherit (nixosTests) zwave-js; + }; + }; + + meta = { + changelog = "https://github.com/zwave-js/zwave-js-server/releases/tag/${version}"; + description = "Small server wrapper around Z-Wave JS to access it via a WebSocket"; + license = lib.licenses.asl20; + homepage = "https://github.com/zwave-js/zwave-js-server"; + maintainers = with lib.maintainers; [ graham33 ]; + }; +} diff --git a/pkgs/development/node-packages/aliases.nix b/pkgs/development/node-packages/aliases.nix index 3b4bf018e6633..0bfe127efff40 100644 --- a/pkgs/development/node-packages/aliases.nix +++ b/pkgs/development/node-packages/aliases.nix @@ -49,6 +49,7 @@ mapAliases { "@mermaid-js/mermaid-cli" = pkgs.mermaid-cli; # added 2023-10-01 "@nerdwallet/shepherd" = pkgs.shepherd; # added 2023-09-30 "@nestjs/cli" = pkgs.nest-cli; # Added 2023-05-06 + "@zwave-js/server" = pkgs.zwave-js-server; # Added 2023-09-09 alloy = pkgs.titanium-alloy; # added 2023-08-17 antennas = pkgs.antennas; # added 2023-07-30 inherit (pkgs) asar; # added 2023-08-26 diff --git a/pkgs/development/node-packages/main-programs.nix b/pkgs/development/node-packages/main-programs.nix index bd7229556908d..7b57757a07cc9 100644 --- a/pkgs/development/node-packages/main-programs.nix +++ b/pkgs/development/node-packages/main-programs.nix @@ -60,5 +60,4 @@ vscode-html-languageserver-bin = "html-languageserver"; vscode-json-languageserver-bin = "json-languageserver"; webtorrent-cli = "webtorrent"; - "@zwave-js/server" = "zwave-server"; } diff --git a/pkgs/development/node-packages/node-packages.json b/pkgs/development/node-packages/node-packages.json index f0e9b379f4292..471b37c30bf07 100644 --- a/pkgs/development/node-packages/node-packages.json +++ b/pkgs/development/node-packages/node-packages.json @@ -310,5 +310,4 @@ , "@yaegassy/coc-nginx" , "yalc" , "yarn" -, "@zwave-js/server" ] diff --git a/pkgs/development/node-packages/node-packages.nix b/pkgs/development/node-packages/node-packages.nix index 5f8eaa649d468..168d6b0c3c2a5 100644 --- a/pkgs/development/node-packages/node-packages.nix +++ b/pkgs/development/node-packages/node-packages.nix @@ -102095,205 +102095,4 @@ in bypassCache = true; reconstructLock = true; }; - "@zwave-js/server" = nodeEnv.buildNodePackage { - name = "_at_zwave-js_slash_server"; - packageName = "@zwave-js/server"; - version = "1.33.0"; - src = fetchurl { - url = "https://registry.npmjs.org/@zwave-js/server/-/server-1.33.0.tgz"; - sha512 = "jHRpbeWcDVhTWidDTmln9x+lTveJ0H1cLJxl6dWIeWQ6YnB7YzRuHFDPhY+6ewAyUrc+Eq8tl+QnhjmVuevq+A=="; - }; - dependencies = [ - (sources."@alcalzone/jsonl-db-3.1.0" // { - dependencies = [ - sources."fs-extra-10.1.0" - ]; - }) - (sources."@alcalzone/pak-0.9.0" // { - dependencies = [ - sources."execa-5.0.1" - sources."fs-extra-10.1.0" - ]; - }) - sources."@alcalzone/proper-lockfile-4.1.3-0" - sources."@colors/colors-1.6.0" - sources."@dabh/diagnostics-2.0.3" - sources."@homebridge/ciao-1.1.7" - sources."@leichtgewicht/ip-codec-2.0.4" - sources."@serialport/binding-mock-10.2.2" - (sources."@serialport/bindings-cpp-12.0.1" // { - dependencies = [ - sources."@serialport/parser-delimiter-11.0.0" - sources."@serialport/parser-readline-11.0.0" - sources."node-gyp-build-4.6.0" - ]; - }) - sources."@serialport/bindings-interface-1.2.2" - sources."@serialport/parser-byte-length-12.0.0" - sources."@serialport/parser-cctalk-12.0.0" - sources."@serialport/parser-delimiter-12.0.0" - sources."@serialport/parser-inter-byte-timeout-12.0.0" - sources."@serialport/parser-packet-length-12.0.0" - sources."@serialport/parser-readline-12.0.0" - sources."@serialport/parser-ready-12.0.0" - sources."@serialport/parser-regex-12.0.0" - sources."@serialport/parser-slip-encoder-12.0.0" - sources."@serialport/parser-spacepacket-12.0.0" - sources."@serialport/stream-12.0.0" - sources."@sindresorhus/is-5.6.0" - sources."@szmarczak/http-timer-5.0.1" - sources."@types/http-cache-semantics-4.0.3" - sources."@types/triple-beam-1.3.4" - sources."@zwave-js/cc-12.3.0" - sources."@zwave-js/config-12.3.0" - sources."@zwave-js/core-12.3.0" - sources."@zwave-js/host-12.3.0" - sources."@zwave-js/nvmedit-12.3.0" - sources."@zwave-js/serial-12.3.0" - sources."@zwave-js/shared-12.2.3" - sources."@zwave-js/testing-12.3.0" - sources."alcalzone-shared-4.0.8" - sources."ansi-colors-4.1.3" - sources."ansi-regex-5.0.1" - sources."ansi-styles-4.3.0" - sources."async-3.2.4" - sources."asynckit-0.4.0" - sources."axios-0.27.2" - sources."buffer-from-1.1.2" - sources."bufferutil-4.0.8" - sources."cacheable-lookup-7.0.0" - sources."cacheable-request-10.2.14" - sources."cliui-8.0.1" - (sources."color-3.2.1" // { - dependencies = [ - sources."color-convert-1.9.3" - sources."color-name-1.1.3" - ]; - }) - sources."color-convert-2.0.1" - sources."color-name-1.1.4" - sources."color-string-1.9.1" - sources."colorspace-1.1.4" - sources."combined-stream-1.0.8" - sources."cross-spawn-7.0.3" - sources."dayjs-1.11.10" - sources."debug-4.3.4" - (sources."decompress-response-6.0.0" // { - dependencies = [ - sources."mimic-response-3.1.0" - ]; - }) - sources."defer-to-connect-2.0.1" - sources."delayed-stream-1.0.0" - sources."dns-packet-5.6.1" - sources."emoji-regex-8.0.0" - sources."enabled-2.0.0" - sources."escalade-3.1.1" - sources."eventemitter3-5.0.1" - sources."execa-5.1.1" - sources."fast-deep-equal-3.1.3" - sources."fecha-4.2.3" - sources."file-stream-rotator-0.6.1" - sources."fn.name-1.1.0" - sources."follow-redirects-1.15.3" - sources."form-data-4.0.0" - sources."form-data-encoder-2.1.4" - sources."fs-extra-11.1.1" - sources."get-caller-file-2.0.5" - sources."get-stream-6.0.1" - sources."globalyzer-0.1.0" - sources."globrex-0.1.2" - sources."got-13.0.0" - sources."graceful-fs-4.2.11" - sources."http-cache-semantics-4.1.1" - sources."http2-wrapper-2.2.0" - sources."human-signals-2.1.0" - sources."inherits-2.0.4" - sources."is-arrayish-0.3.2" - sources."is-fullwidth-code-point-3.0.0" - sources."is-stream-2.0.1" - sources."isexe-2.0.0" - sources."json-buffer-3.0.1" - sources."json-logic-js-2.0.2" - sources."json5-2.2.3" - sources."jsonfile-6.1.0" - sources."keyv-4.5.4" - sources."kuler-2.0.0" - sources."logform-2.6.0" - sources."lowercase-keys-3.0.0" - sources."lru-cache-6.0.0" - sources."mdns-server-1.0.11" - sources."merge-stream-2.0.0" - sources."mime-db-1.52.0" - sources."mime-types-2.1.35" - sources."mimic-fn-2.1.0" - sources."mimic-response-4.0.0" - sources."minimist-1.2.8" - sources."moment-2.29.4" - sources."ms-2.1.2" - sources."node-addon-api-7.0.0" - sources."node-gyp-build-4.6.1" - sources."normalize-url-8.0.0" - sources."npm-run-path-4.0.1" - sources."nrf-intel-hex-1.3.0" - sources."object-hash-2.2.0" - sources."one-time-1.0.0" - sources."onetime-5.1.2" - sources."p-cancelable-3.0.0" - sources."p-queue-7.4.1" - sources."p-timeout-5.1.0" - sources."path-key-3.1.1" - sources."proper-lockfile-4.1.2" - sources."quick-lru-5.1.1" - sources."readable-stream-3.6.2" - sources."reflect-metadata-0.1.13" - sources."require-directory-2.1.1" - sources."resolve-alpn-1.2.1" - sources."responselike-3.0.0" - sources."retry-0.12.0" - sources."safe-buffer-5.2.1" - sources."safe-stable-stringify-2.4.3" - sources."semver-7.5.4" - sources."serialport-12.0.0" - sources."shebang-command-2.0.0" - sources."shebang-regex-3.0.0" - sources."signal-exit-3.0.7" - sources."simple-swizzle-0.2.2" - sources."source-map-0.6.1" - sources."source-map-support-0.5.21" - sources."stack-trace-0.0.10" - sources."string-width-4.2.3" - sources."string_decoder-1.3.0" - sources."strip-ansi-6.0.1" - sources."strip-final-newline-2.0.0" - sources."text-hex-1.0.0" - sources."tiny-glob-0.2.9" - sources."triple-beam-1.4.1" - sources."tslib-2.6.2" - sources."universalify-2.0.1" - sources."utf-8-validate-6.0.3" - sources."util-deprecate-1.0.2" - sources."which-2.0.2" - sources."winston-3.11.0" - sources."winston-daily-rotate-file-4.7.1" - sources."winston-transport-4.6.0" - sources."wrap-ansi-7.0.0" - sources."ws-8.14.2" - sources."xstate-4.38.2" - sources."y18n-5.0.8" - sources."yallist-4.0.0" - sources."yargs-17.7.2" - sources."yargs-parser-21.1.1" - sources."zwave-js-12.3.0" - ]; - buildInputs = globalBuildInputs; - meta = { - description = "Full access to zwave-js driver through Websockets"; - homepage = "https://github.com/zwave-js/zwave-js-server#readme"; - license = "Apache-2.0"; - }; - production = true; - bypassCache = true; - reconstructLock = true; - }; }