diff --git a/Apps/home-assistant-matter-hub/.npmignore b/Apps/home-assistant-matter-hub/.npmignore new file mode 100644 index 000000000..e48b42bc5 --- /dev/null +++ b/Apps/home-assistant-matter-hub/.npmignore @@ -0,0 +1,13 @@ +# Exclude everything +test/ +*.docker-entrypoint.sh +*.Dockerfile +build.js +tsconfig.json +package.tgz + +# EXPLICITLY INCLUDE +!dist/ +!package.json +!README.md +!LICENSE diff --git a/Apps/home-assistant-matter-hub/addon.Dockerfile b/Apps/home-assistant-matter-hub/addon.Dockerfile new file mode 100644 index 000000000..eb9d5cbe7 --- /dev/null +++ b/Apps/home-assistant-matter-hub/addon.Dockerfile @@ -0,0 +1,34 @@ +ARG NODE_VERSION="22" + +FROM node:${NODE_VERSION}-alpine AS nodebuild + +FROM ghcr.io/hassio-addons/base:18.2.1 + +# Install Node.js +RUN apk add --no-cache libstdc++ bash +COPY --from=nodebuild /usr/local/bin/node /usr/local/bin/ +COPY --from=nodebuild /usr/local/lib/node_modules /usr/local/lib/node_modules +RUN \ + ln -s ../lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm && \ + ln -s ../lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx && \ + ln -s ../lib/node_modules/corepack/dist/corepack.js /usr/local/bin/corepack +RUN corepack enable + +ENV SUPERVISOR_TOKEN="" +VOLUME /config + +COPY addon.docker-entrypoint.sh /docker-entrypoint.sh +RUN chmod +x /docker-entrypoint.sh + +ARG PACKAGE_VERSION="unknown" +LABEL \ + io.hass.version="$PACKAGE_VERSION" \ + io.hass.type="addon" \ + io.hass.arch="armhf|aarch64|i386|amd64" + +RUN mkdir /install +COPY package.tgz /install/package.tgz +RUN npm install -g /install/package.tgz +RUN rm -rf /install + +CMD /docker-entrypoint.sh diff --git a/Apps/home-assistant-matter-hub/addon.docker-entrypoint.sh b/Apps/home-assistant-matter-hub/addon.docker-entrypoint.sh new file mode 100644 index 000000000..1dff5c391 --- /dev/null +++ b/Apps/home-assistant-matter-hub/addon.docker-entrypoint.sh @@ -0,0 +1,11 @@ +#!/usr/bin/with-contenv bashio + +exec home-assistant-matter-hub start \ + --log-level=$(bashio::config 'app_log_level') \ + --disable-log-colors=$(bashio::config 'disable_log_colors') \ + --mdns-network-interface="$(bashio::config 'mdns_network_interface')" \ + --storage-location=/config/data \ + --web-port=$(bashio::addon.ingress_port) \ + --home-assistant-url='http://supervisor/core' \ + --home-assistant-access-token="$SUPERVISOR_TOKEN" \ + --http-ip-whitelist="172.30.32.2" diff --git a/Apps/home-assistant-matter-hub/build.js b/Apps/home-assistant-matter-hub/build.js new file mode 100644 index 000000000..351a8f556 --- /dev/null +++ b/Apps/home-assistant-matter-hub/build.js @@ -0,0 +1,42 @@ +import * as fs from "node:fs"; +import * as path from "node:path"; +import { fileURLToPath } from "node:url"; +import { rimraf } from "rimraf"; + +const projectRoot = path.join(import.meta.dirname, "../.."); + +const frontend = packageDir("@home-assistant-matter-hub/frontend", "dist"); +const backend = packageDir("@home-assistant-matter-hub/backend", "dist"); + +const dist = path.resolve(import.meta.dirname, "dist"); +await rimraf(dist); + +fs.cpSync(frontend, path.join(dist, "frontend"), { + recursive: true, +}); +fs.cpSync(backend, path.join(dist, "backend"), { + recursive: true, +}); + +fs.cpSync( + path.join(projectRoot, "README.md"), + path.join(import.meta.dirname, "README.md"), +); +fs.cpSync( + path.join(projectRoot, "LICENSE"), + path.join(import.meta.dirname, "LICENSE"), +); + +/** + * Resolve a directory in a package + * @param {string} packageName The path of the package json + * @param {string} directory The dist dir in the package + * @returns {string} + */ +function packageDir(packageName, directory) { + const packageJsonPath = fileURLToPath( + import.meta.resolve(path.join(packageName, "package.json")), + ); + const packagePath = path.dirname(packageJsonPath); + return path.join(packagePath, directory); +} diff --git a/Apps/home-assistant-matter-hub/docker-compose.yml b/Apps/home-assistant-matter-hub/docker-compose.yml new file mode 100644 index 000000000..ee4d195ba --- /dev/null +++ b/Apps/home-assistant-matter-hub/docker-compose.yml @@ -0,0 +1,67 @@ +name: home-assistant-matter-hub +services: + home-assistant-matter-hub: + image: panwang/home-assistant-matter-hub:latest + container_name: home-assistant-matter-hub + restart: unless-stopped + network_mode: host + volumes: + - /DATA/AppData/$AppID/data:/data + environment: + - HAMH_HOME_ASSISTANT_URL=${HAMH_HOME_ASSISTANT_URL} + - HAMH_HOME_ASSISTANT_ACCESS_TOKEN=${HAMH_HOME_ASSISTANT_ACCESS_TOKEN} + - HAMH_STORAGE_LOCATION=/data + - TZ=${TZ} + - PUID=${PUID} + - PGID=${PGID} + x-casaos: + envs: + - container: HAMH_HOME_ASSISTANT_URL + default: "http://localhost:8123" + description: + en_US: "Home Assistant URL (Must start with http:// or https://)" + zh_CN: "Home Assistant 访问地址 (必须以 http:// 或 https:// 开头)" + - container: HAMH_HOME_ASSISTANT_ACCESS_TOKEN + description: + en_US: "Long-lived Access Token (Required! Generate in HA Profile)" + zh_CN: "HA 长期访问令牌 (必填!在 HA 个人资料页底部生成)" + ports: + - container: "8482" + host: "8482" + description: + en_US: Web UI Port + zh_CN: 网页管理界面端口 + - container: "5540" + description: + en_US: Matter Communication Port + zh_CN: Matter 通信端口 + volumes: + - container: /data + description: + en_US: Data directory + zh_CN: 数据持久化目录 + +x-casaos: + architectures: + - amd64 + - arm + - arm64 + main: home-assistant-matter-hub + scheme: http + port_map: "8482" + index: / + author: symi-daguo + category: Home Automation + developer: t0bst4r + description: + en_US: "Simulate Matter bridges to publish your entities from Home Assistant to any Matter-compatible controller like Alexa, Apple Home, or Google Home. Completely local communication, no port forwarding required." + zh_CN: "模拟 Matter 桥接器,将 Home Assistant 中的实体发布到任何兼容 Matter 的控制器(如 Alexa、Apple Home 或 Google Home)。完全本地化通信,无需配置端口转发。" + tagline: + en_US: "Bridge Home Assistant entities to Matter (HomeKit/Alexa/Google)" + zh_CN: "将 Home Assistant 实体桥接到 Matter (HomeKit/Alexa/Google)" + title: + en_US: "HA Matter Hub" + zh_CN: "HA Matter 枢纽" + icon: https://raw.githubusercontent.com/symi-daguo/home-assistant-matter-hub/main/Apps/home-assistant-matter-hub/icon.png + screenshot_link: + - https://raw.githubusercontent.com/symi-daguo/home-assistant-matter-hub/main/Apps/home-assistant-matter-hub/screenshot-1.png diff --git a/Apps/home-assistant-matter-hub/icon.png b/Apps/home-assistant-matter-hub/icon.png new file mode 100644 index 000000000..5d4745447 Binary files /dev/null and b/Apps/home-assistant-matter-hub/icon.png differ diff --git a/Apps/home-assistant-matter-hub/package.json b/Apps/home-assistant-matter-hub/package.json new file mode 100644 index 000000000..fc8e17359 --- /dev/null +++ b/Apps/home-assistant-matter-hub/package.json @@ -0,0 +1,103 @@ +{ + "name": "home-assistant-matter-hub", + "description": "", + "version": "0.0.0", + "private": false, + "type": "module", + "bin": { + "home-assistant-matter-hub": "./dist/backend/cli.js" + }, + "author": { + "email": "t0bst4r@outlook.com", + "name": "t0bst4r", + "url": "https://github.com/t0bst4r" + }, + "keywords": [ + "home-assistant", + "homeassistant", + "home", + "assistant", + "apple home", + "google home", + "apple", + "google", + "alexa", + "matter", + "matter.js", + "matterjs", + "project-chip", + "smart", + "smarthome", + "smart-home" + ], + "bugs": { + "url": "https://github.com/t0bst4r/home-assistant-matter-hub/issues" + }, + "license": "Apache-2.0", + "repository": "github:t0bst4r/home-assistant-matter-hub", + "scripts": { + "cleanup": "npx rimraf node_modules dist pack LICENSE README.md", + "build": "node build.js", + "bundle": "pnpm pack --out package.tgz", + "postbuild": "pnpm run bundle", + "test": "vitest run", + "prestart": "pnpm run build", + "start": "dotenvx run -f ../../.env -- ./dist/backend/cli.js start" + }, + "dependencies": { + "@matter/main": "0.15.6", + "@matter/nodejs": "0.15.6", + "@matter/general": "0.15.6", + "ajv": "8.17.1", + "color": "5.0.3", + "debounce": "3.0.0", + "express": "5.2.1", + "express-basic-auth": "1.2.1", + "express-ip-access-control": "1.1.3", + "home-assistant-js-websocket": "9.6.0", + "lodash-es": "4.17.21", + "nocache": "4.0.0", + "rxjs": "7.8.2", + "strip-color": "0.1.0", + "yargs": "18.0.0", + "ws": "8.18.3" + }, + "devDependencies": { + "@home-assistant-matter-hub/backend": "workspace:*", + "@home-assistant-matter-hub/frontend": "workspace:*", + "@home-assistant-matter-hub/common": "workspace:*", + "@types/lodash-es": "^4.17.12" + }, + "nx": { + "targets": { + "start": { + "cache": false, + "dependsOn": [ + "build" + ] + }, + "pack": { + "cache": true, + "dependsOn": [ + "build" + ], + "outputs": [ + "{projectRoot}/pack" + ] + }, + "build": { + "inputs": [ + "^default", + "default", + "{workspaceRoot}/README.md", + "{workspaceRoot}/LICENSE" + ], + "outputs": [ + "{projectRoot}/dist", + "{projectRoot}/README.md", + "{projectRoot}/LICENSE" + ] + } + } + } +} diff --git a/Apps/home-assistant-matter-hub/screenshot-1.png b/Apps/home-assistant-matter-hub/screenshot-1.png new file mode 100644 index 000000000..5d4745447 Binary files /dev/null and b/Apps/home-assistant-matter-hub/screenshot-1.png differ diff --git a/Apps/home-assistant-matter-hub/standalone.Dockerfile b/Apps/home-assistant-matter-hub/standalone.Dockerfile new file mode 100644 index 000000000..631531c06 --- /dev/null +++ b/Apps/home-assistant-matter-hub/standalone.Dockerfile @@ -0,0 +1,18 @@ +ARG NODE_VERSION="22" +ARG PACKAGE_VERSION="unknown" + +FROM node:${NODE_VERSION}-alpine +ARG PACKAGE_VERSION +RUN apk add --no-cache netcat-openbsd + +ENV HAMH_STORAGE_LOCATION="/data" +VOLUME /data + +LABEL package.version="${PACKAGE_VERSION}" + +RUN mkdir /install +COPY package.tgz /install/package.tgz +RUN npm install -g /install/package.tgz +RUN rm -rf /install + +CMD ["home-assistant-matter-hub", "start"] diff --git a/Apps/home-assistant-matter-hub/test/package.test.ts b/Apps/home-assistant-matter-hub/test/package.test.ts new file mode 100644 index 000000000..69abcff8b --- /dev/null +++ b/Apps/home-assistant-matter-hub/test/package.test.ts @@ -0,0 +1,26 @@ +import backend from "@home-assistant-matter-hub/backend/package.json" with { + type: "json", +}; +import common from "@home-assistant-matter-hub/common/package.json" with { + type: "json", +}; +import { mapValues, pickBy } from "lodash-es"; +import { describe, expect, it } from "vitest"; +import own from "../package.json" with { type: "json" }; + +describe("home-assistant-matter-hub", () => { + it("should include all necessary dependencies", () => { + const expected = pickBy( + { ...backend.dependencies, ...common.dependencies }, + (_, key) => !key.startsWith("@home-assistant-matter-hub/"), + ); + expect(own.dependencies).toEqual(expected); + }); + + it("should pin all dependencies", () => { + const expected = mapValues(own.dependencies, (value) => + value.replace(/^\D+/, ""), + ); + expect(own.dependencies).toEqual(expected); + }); +}); diff --git a/Apps/home-assistant-matter-hub/tsconfig.json b/Apps/home-assistant-matter-hub/tsconfig.json new file mode 100644 index 000000000..7066fc543 --- /dev/null +++ b/Apps/home-assistant-matter-hub/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + // Compiler + "target": "ES2020", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "noEmit": true, + + // Lint + "resolveJsonModule": true, + "strict": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true + }, + "include": ["./test/"] +}