diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 746c8b05a964..17c41960c5b3 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -728,6 +728,7 @@ ./services/home-automation/esphome.nix ./services/home-automation/evcc.nix ./services/home-automation/govee2mqtt.nix + ./services/home-automation/home-assistant-matter-hub.nix ./services/home-automation/home-assistant.nix ./services/home-automation/homebridge.nix ./services/home-automation/matter-server.nix diff --git a/nixos/modules/services/home-automation/home-assistant-matter-hub.nix b/nixos/modules/services/home-automation/home-assistant-matter-hub.nix new file mode 100644 index 000000000000..465707642f15 --- /dev/null +++ b/nixos/modules/services/home-automation/home-assistant-matter-hub.nix @@ -0,0 +1,100 @@ +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.services.home-assistant-matter-hub; + settingsFormat = pkgs.formats.json { }; + configFile = settingsFormat.generate "home-assistant-matter-hub.json" cfg.settings; +in +{ + meta.maintainers = with lib.maintainers; [ + kranzes + marie + ]; + + options.services.home-assistant-matter-hub = { + enable = lib.mkEnableOption "home-assistant-matter-hub, a Matter bridge for Home Assistant"; + + package = lib.mkPackageOption pkgs "home-assistant-matter-hub" { }; + + openFirewall = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Whether to open the Matter commissioning ports (UDP/TCP 5540) in the + firewall. + ''; + }; + + accessTokenFile = lib.mkOption { + type = lib.types.externalPath; + example = "/run/secrets/home-assistant-matter-hub-token"; + description = '' + Path to a file containing a Home Assistant long-lived access token. + The file is loaded as a systemd credential and read into + `HAMH_HOME_ASSISTANT_ACCESS_TOKEN` at service start. + ''; + }; + + settings = lib.mkOption { + type = lib.types.submodule { + freeformType = settingsFormat.type; + options = { + homeAssistantUrl = lib.mkOption { + type = lib.types.str; + example = lib.literalExpression "config.services.home-assistant.config.homeassistant.internal_url"; + description = "HTTP URL of the Home Assistant instance to bridge."; + }; + + httpPort = lib.mkOption { + type = lib.types.port; + default = 8482; + description = "Port the web interface listens on."; + }; + }; + }; + default = { }; + example = lib.literalExpression '' + { + homeAssistantUrl = config.services.home-assistant.config.homeassistant.internal_url; + } + ''; + description = '' + Configuration written to a JSON file and passed to + `home-assistant-matter-hub start --config`. Keys use camelCase, matching + the long-form CLI flags. See + + for the full list of options. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + networking.firewall = lib.mkIf cfg.openFirewall { + allowedTCPPorts = [ 5540 ]; + allowedUDPPorts = [ 5540 ]; + }; + + systemd.services.home-assistant-matter-hub = { + description = "Home Assistant Matter Hub"; + documentation = [ "https://riddix.github.io/home-assistant-matter-hub/" ]; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + script = '' + export HAMH_HOME_ASSISTANT_ACCESS_TOKEN=$(systemd-creds cat HAMH_HOME_ASSISTANT_ACCESS_TOKEN) + exec ${lib.getExe cfg.package} start --config=${configFile} --storage-location="$1" + ''; + scriptArgs = "%S/home-assistant-matter-hub"; + + serviceConfig = { + LoadCredential = [ "HAMH_HOME_ASSISTANT_ACCESS_TOKEN:${cfg.accessTokenFile}" ]; + DynamicUser = true; + StateDirectory = "home-assistant-matter-hub"; + }; + }; + }; +} diff --git a/pkgs/by-name/ho/home-assistant-matter-hub/package.nix b/pkgs/by-name/ho/home-assistant-matter-hub/package.nix new file mode 100644 index 000000000000..6323bcf13e15 --- /dev/null +++ b/pkgs/by-name/ho/home-assistant-matter-hub/package.nix @@ -0,0 +1,104 @@ +{ + lib, + stdenvNoCC, + fetchFromGitHub, + nodejs, + pnpm_10, + pnpmConfigHook, + fetchPnpmDeps, + makeWrapper, +}: + +stdenvNoCC.mkDerivation (finalAttrs: { + pname = "home-assistant-matter-hub"; + version = "2.0.41"; + + src = fetchFromGitHub { + owner = "RiDDiX"; + repo = "home-assistant-matter-hub"; + tag = "v${finalAttrs.version}"; + hash = "sha256-+W0HjULERuW+RY7938xGnEXwmCpkumLpUlZmvfuhX2Y="; + }; + + # The bundled cli.js imports transitive dependencies (e.g. @noble/curves) + # directly, so we need a flat node_modules tree for Node's resolver to + # find them. + pnpmInstallFlags = [ "--shamefully-hoist" ]; + pnpmWorkspaces = [ "home-assistant-matter-hub..." ]; + pnpmDeps = fetchPnpmDeps { + inherit (finalAttrs) + pname + version + src + pnpmInstallFlags + pnpmWorkspaces + ; + fetcherVersion = 3; + hash = "sha256-1DaR4q1QH07UR+EchfDI92bTdRY4C2IaGIRj426DURI="; + }; + + __structuredAttrs = true; + strictDeps = true; + + # Workspace package.json files all carry "0.0.0"; the real version is + # injected at release time via APP_VERSION (consumed by vite for the + # frontend bundle and by the backend at runtime). + env.APP_VERSION = finalAttrs.version; + + nativeBuildInputs = [ + nodejs + pnpm_10 + pnpmConfigHook + makeWrapper + ]; + + buildPhase = '' + runHook preBuild + + pnpm --filter home-assistant-matter-hub... run build + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + mkdir -p $out/bin $out/share/apps/home-assistant-matter-hub + + tar -xf apps/home-assistant-matter-hub/package.tgz \ + --strip-components=1 \ + -C $out/share/apps/home-assistant-matter-hub + + # pnpm cannot recursively prune monorepos, so we follow pnpm's + # recommendation of removing all node_modules and reinstalling + # only the production dependencies we need. + find . -name node_modules -type d -prune -exec rm -rf {} + + pnpm install --offline --prod --filter-prod home-assistant-matter-hub --shamefully-hoist + + # Workspace symlinks under apps/home-assistant-matter-hub/node_modules use + # relative paths into the top-level node_modules/.pnpm store, so we keep + # the original directory layout under $out/share. + mv node_modules $out/share/ + mv apps/home-assistant-matter-hub/node_modules $out/share/apps/home-assistant-matter-hub/ + + makeWrapper ${lib.getExe nodejs} "$out/bin/home-assistant-matter-hub" \ + --add-flags "$out/share/apps/home-assistant-matter-hub/dist/backend/cli.js" \ + --set NODE_ENV production \ + --set APP_VERSION ${finalAttrs.version} + + runHook postInstall + ''; + + meta = { + description = "Publish your home-assistant instance using Matter"; + homepage = "https://riddix.github.io/home-assistant-matter-hub/"; + changelog = "https://github.com/RiDDiX/home-assistant-matter-hub/releases/tag/v${finalAttrs.version}"; + license = lib.licenses.asl20; + maintainers = with lib.maintainers; [ + kranzes + marie + ]; + mainProgram = "home-assistant-matter-hub"; + inherit (nodejs.meta) platforms; + }; +})