device-center 0.0.0-stage → 0.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CONTRIBUTING.md +9 -0
  2. package/Dockerfile +10 -0
  3. package/LICENSE +21 -0
  4. package/README.md +158 -2
  5. package/SECURITY.md +23 -0
  6. package/bin/device-center.mjs +73 -0
  7. package/compose.yaml +22 -0
  8. package/docs/architecture.md +66 -0
  9. package/docs/connection-routing.md +65 -0
  10. package/docs/driver-binding.md +64 -0
  11. package/docs/npm.md +53 -0
  12. package/docs/onboarding.md +40 -0
  13. package/docs/roadmap.md +36 -0
  14. package/docs/security-and-sync.md +72 -0
  15. package/docs/troubleshooting.md +59 -0
  16. package/index.html +15 -0
  17. package/login.html +5 -0
  18. package/package.json +52 -4
  19. package/scripts/driver.mjs +87 -0
  20. package/scripts/release-check.mjs +18 -0
  21. package/scripts/service.mjs +103 -0
  22. package/server/adapters/identity/localDemo.mjs +8 -0
  23. package/server/cloudAccess.mjs +73 -0
  24. package/server/connector.mjs +64 -0
  25. package/server/database.mjs +64 -0
  26. package/server/drivers/cloudLink.mjs +85 -0
  27. package/server/drivers/cloudRegistry.mjs +123 -0
  28. package/server/drivers/connectionPlans.mjs +32 -0
  29. package/server/drivers/driverProtocol.mjs +66 -0
  30. package/server/drivers/inventory.mjs +153 -0
  31. package/server/drivers/local.mjs +28 -0
  32. package/server/drivers/sshConfig.mjs +123 -0
  33. package/server/drivers/sshInitialization.mjs +23 -0
  34. package/server/drivers/sshProbe.mjs +207 -0
  35. package/server/drivers/sshRoute.mjs +37 -0
  36. package/server/index.mjs +210 -0
  37. package/server/mcp.mjs +28 -0
  38. package/server/modules/devices/repository.mjs +42 -0
  39. package/server/unifiedAccess.mjs +113 -0
  40. package/src/api.js +22 -0
  41. package/src/device-state.js +9 -0
  42. package/src/login.css +16 -0
  43. package/src/login.js +55 -0
  44. package/src/main.js +483 -0
  45. package/src/styles.css +250 -0
@@ -0,0 +1,9 @@
1
+ # Contributing
2
+
3
+ Source distributed in the npm beta package is licensed under MIT (see LICENSE). The GitHub repository publication plan and a private security reporting channel are still pending. This remains a prototype, not an audited remote execution system.
4
+
5
+ Use Node.js 24, `npm run dev`, `npm run check`, and `npm test`. There is no third-party runtime dependency or frontend build step. Native inventory includes the real local Driver by default, with no seeded records. SSH references require explicit user selection. Test fixtures must stay in temporary test directories/databases, never appear in product startup.
6
+
7
+ Preserve the compact sidebar, optional MCP setup, truthful device states, and separate memory/disk observations. The local process represents its machine; a container cannot claim its host. SSH configuration discovery must not execute commands or read keys, and association must not imply connectivity or task authority. Do not restore sample projects, hosted quota displays or simulated cloud synchronization.
8
+
9
+ Never add real secrets to prompts, packages, fixture data or logs. SSH observation is an explicit owner action using a fixed script, isolated configuration, existing authentication and strict host trust; GET and MCP must never trigger connections. Tests inject subprocess results and do not contact real hosts. Resource enrollment, Agent installation, network discovery, tunnels, cloud deployment and remote tasks require separately scoped implementation and review. Do not run service installation or Docker deployment merely to validate a UI change. Keep user data and unrelated services intact.
package/Dockerfile ADDED
@@ -0,0 +1,10 @@
1
+ # Optional local-only control plane, not a host/resource Agent.
2
+ FROM node:24-bookworm-slim
3
+ WORKDIR /app
4
+ COPY --chown=node:node package.json index.html login.html ./
5
+ COPY --chown=node:node server ./server
6
+ COPY --chown=node:node src ./src
7
+ RUN mkdir -p /app/data && chown node:node /app/data
8
+ USER node
9
+ ENV PORT=5173 DEVICE_CENTER_RUNTIME=container DEVICE_CENTER_DB_PATH=/app/data/device-center.sqlite
10
+ CMD ["node", "server/index.mjs"]
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 owenshen0907
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,159 @@
1
- # Temporary Holding Version
1
+ # Device Center
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 让主力电脑上的智能体,通过一个本机连接器使用自己闲置的 Mac、Windows 或 Linux 电脑。目标用途包括:在 Windows 上构建程序、读取经批准的日志、分担有额度限制的计算。
4
+
5
+ 当前实现的是**本机 Driver + SSH 配置关联 + 按需只读检查**,以及**个人云端登录、管理电脑配对与只读回报**。原生本机部署的这台电脑自动成为设备,展示真实本机内存和用户目录所在卷的磁盘读数;已有 SSH 主机可以关联到清单,按需读取 macOS/Linux 的真实资源快照。云端不会把服务器当作管理电脑;用户在自己的电脑运行接入命令并在面板批准后,才显示真实回报。没有默认示例设备、项目、用量或模拟云环境。指定日志、远程任务、独立设备 Agent 和配置迁移尚未实现。源码采用 [MIT 许可证](LICENSE),当前为 beta 原型。
6
+
7
+ ## npm 接入
8
+
9
+ 本包版本为 `device-center@0.1.0-beta.1`,使用 npm 的 `beta` 分发标签。macOS / Linux 用户安装 Node.js 24,在官方 registry 核对对应版本后,即可在任意目录运行:
10
+
11
+ ```sh
12
+ # 只看帮助和范围,不产生身份或联网
13
+ npx --yes device-center@0.1.0-beta.1 --help
14
+ npx --yes device-center@0.1.0-beta.1 plan --cloud https://你的控制面域名
15
+ # 用户主动接入;配对码由终端产生,再单独输入面板核对批准
16
+ npx --yes device-center@0.1.0-beta.1 connect --cloud https://你的控制面域名 --confirm
17
+ ```
18
+
19
+ 仅本机使用可运行 `npx --yes device-center@0.1.0-beta.1 start`,面板与只读 MCP 默认位于 `http://127.0.0.1:5174/` 和 `/mcp`。数据留在用户应用数据目录。默认不分享 SSH 信息、不安装后台服务;常驻应先固定版本全局安装,不能依赖 npx 缓存。见 [npm 使用与发布说明](docs/npm.md)。当前源码可先用 `node bin/device-center.mjs --help`,Windows 原生管理 Driver 尚未支持。
20
+
21
+ ## 从源码在本机用起来
22
+
23
+ 需要 Node.js 24,无第三方运行依赖、无前端构建步骤。
24
+
25
+ ```sh
26
+ npm run dev
27
+ ```
28
+
29
+ 打开 [本机面板](http://127.0.0.1:5173),点“开始使用”,按 **管理电脑 → 智能体 → 设备** 完成设置。本机自动加入设备列表,已有近期接入记录可直接进入导入;复制 MCP 地址到 Codex 设置并 Restart,再一键导入已有 SSH 主机。也可稍后连接智能体、先导入,界面不会伪造接入成功。后续手动新增从“添加设备”进入。见[本机、服务器与新设备的完整流程](docs/onboarding.md)。
30
+
31
+ 已配置不等于已连通。点击导入或关联后,新增设备自动排队检查一次当前 SSH 路径,最多两台并发,标记等待、检查中、连通或失败原因;失败可在卡片点“检查 SSH(只读)”重试。目标机无需安装 Node 或本项目。取得资源后在线五分钟,之后显示“待刷新”(数据较早,不代表设备断线或密钥过期)。SSH 连通性与内存、磁盘采集分别显示:认证已通过但目标不支持采集时,指标为空、设备状态仍未知。刷新列表只读取缓存,不会重连。
32
+
33
+ 每台设备名称下显示访问路径摘要,点击可看跳板、目标入口、认证与密钥说明。路径来自已核验的配置,详情图区分上次成功检查与未知的后段映射;未启用自动局域网选路。参见[访问路径与局域网优先方案](docs/connection-routing.md)。
34
+
35
+ “开始使用”的设备步骤会检查运行 Driver 的电脑的本用户 SSH 配置,一键导入可用条目。筛选复用当前安全解析器,要求明确且完整的 Host / 命名 ProxyJump、可读取的本机身份文件引用;仅检查文件元数据,不读取私钥内容。缺少参数、身份文件不可用或暂不支持的配置会跳过并说明原因,不删除配置。“可导入”不等于已连通或认证通过,导入只保存别名引用,随后执行固定只读检查;检查成功前保持未知。已有同名引用不会重复创建或再次排队。服务器部署不能自动读取用户正在操作的电脑;管理 Driver 可经域名配对,默认只回报本机资源;SSH 缓存分享需要另行申请和批准,配置迁移仍待实现。Docker 不读取宿主配置,需在宿主运行原生 Driver。
36
+
37
+ 在“连接方式”中,同一台 SSH 设备可同时登记局域网地址/端口和中转方案。中转有三种:用户自建服务器、Device Center 提供的服务、Cloudflare;支持保存局域网优先或仅中转的计划。保存到本机 SQLite,重新打开后恢复;入口仍待验证,中继适配器和自动选路尚未启用,不会修改已有 SSH 配置或托管私钥。
38
+
39
+ 智能体通过 MCP 使用本机 Driver。已有 MCP 配置可以继续使用;首次配置时,从侧栏“智能体连接”复制地址,在 Codex 设置的 MCP servers 中添加 Streamable HTTP 服务并重新启动连接。
40
+
41
+ 也可以在本机终端配置一次:
42
+
43
+ ```sh
44
+ codex mcp add device-center-connector --url http://127.0.0.1:5173/mcp
45
+ ```
46
+
47
+ 回到连接设置点“检查接入”。只有服务收到真实 MCP `initialize` 请求后才显示接入记录;这个记录不证明客户端身份或持续在线,也不表示远端已连接。五分钟后记录显示过期。让 Codex 调用 `device_center_list_resources`,返回本机 Driver 和已关联的 SSH 主机;`device_center_list_ssh_candidates` 返回候选及关联状态。
48
+
49
+ ```mermaid
50
+ flowchart LR
51
+ AI[本机智能体] --> MCP[MCP]
52
+ MCP --> Driver[本机 Driver 与真实资源读数]
53
+ Driver --> Ref[已关联 SSH 主机引用]
54
+ Ref --> Snapshot[导入后检查或手动重试 · SSH 只读快照]
55
+ Ref -.后续单独授权.-> Task[指定日志或受限任务]
56
+ ```
57
+
58
+ SSH 发现只读取 `~/.ssh/config` 和受限范围内的无条件 Include(`~/.ssh/` 内的 `config` / `.conf`)。不会读取私钥、密码、ssh-agent 或 known_hosts,不运行 `ssh` / `ssh -G`,不执行 `Match exec`、代理或远程命令;不解析完整生效配置。条件 Include、目录外文件、动态路径和通配 Host 不作为自动发现设备,界面会说明读取限制。依据 [OpenSSH 配置语义](https://man.openbsd.org/ssh_config.5),解析配置并不等于验证可达性和身份。
59
+
60
+ **主动检查与配置发现分开。** 检查支持 macOS/Linux 原生管理 Driver,根配置中的完整明确 Host 和命名 ProxyJump;目标需要 POSIX shell 和 macOS/Linux。程序生成临时隔离配置,不完整继承通配、全局或 Include 配置,影响目标或跳板的 Match 条件或自定义命令需要人工审核;仅与它们完全无关的静态 originalhost 条件不会误伤该条目。OpenSSH 使用本机现有身份文件引用和 known_hosts 做认证,程序不读取或导入私钥内容。主机指纹严格校验,不自动接受新主机、不关闭校验。固定采集只用 `uname`、`hostname`、`sysctl`/`vm_stat` 或 `/proc/meminfo`、`df`;不读取普通文件内容、安装服务或接收任意命令。超时 25 秒、输出上限 32 KiB、最多两个并发。SQLite 保存别名、时间、配置摘要及归一化资源结果,不保存配置文件、身份路径或原始输出。见 [排查说明](docs/troubleshooting.md)。
61
+
62
+ 本机“在线”表示服务进程正在本机运行;每次刷新重新采集。内存为系统总量减空闲(含缓存,区别于系统活动监视器的内存压力);Linux SSH 快照使用总量减 MemAvailable。磁盘为用户目录所在卷,不能相加所有分区或代表单个项目占用。读取失败时对应指标显示失败,不影响另一项。
63
+
64
+ 原有 stdio 配置继续可用,但不会在 HTTP 面板里产生接入记录;建议将同名配置改为上面的 HTTP 地址。当前对话不会因修改配置自动加载工具,需要在客户端重启连接或重开会话。接入方法见 [OpenAI 官方 MCP 文档](https://learn.chatgpt.com/docs/extend/mcp?surface=cli)。
65
+
66
+ 端口可用 `PORT=5174 npm run dev` 调整。复制命令会使用当前页面端口,不带数据库路径、密钥或令牌。数据默认为仓库 `data/device-center.sqlite`,可以用 `DEVICE_CENTER_DB_PATH` 指定已有数据库;不会自动清空数据或写入种子。旧版 `is_demo=1` 记录被排除,不自动删除。
67
+
68
+ ## 本机常驻:推荐方向
69
+
70
+ 关闭网页不影响后台服务。现在提供 macOS `launchd` 和 Linux `systemd --user` 安装脚本:
71
+
72
+ ```sh
73
+ npm run service -- plan
74
+ npm run service -- install --confirm
75
+ npm run service -- status
76
+ ```
77
+
78
+ - `plan` 只展示计划,不修改系统。
79
+ - `install --confirm` 是用户主动安装:登记用户级后台服务,登录后启动,失败时由系统服务管理器重启。Node 及仓库必须保留在稳定路径。
80
+ - macOS 数据和日志位于 `~/Library/Application Support/Device Center/`;Linux 数据位于 `~/.local/share/device-center/`,日志由用户 journal 管理。
81
+ - 后台服务使用独立持久数据目录,不自动复制开发数据库。请先停止自己启动的同端口前台进程;脚本不会自动结束其他进程或覆盖已有服务配置。
82
+ - `--port 5174` 可以加在各命令后,安装、状态和卸载应使用相同端口。
83
+ - `npm run service -- uninstall --confirm` 只卸载这份配置对应的用户服务,保留数据目录;修改过的配置须人工处理。
84
+ - Windows 原生 Driver 和后台安装尚未实现;下方 Docker 方案只表示容器资源,不能替代宿主 Driver。
85
+
86
+ 本轮验证仅运行 `plan`、前台预览和接口测试,**没有安装常驻服务**。系统服务安装与 Docker 运行还需在各目标平台实际验证。当前仍是源码运行方式,不是给普通用户分发的签名安装包。
87
+
88
+ ## 已有 Docker 的用户
89
+
90
+ 仓库提供可审查的 `Dockerfile` 与 `compose.yaml`,没有在本轮构建镜像或启动容器。后续用户主动选择时可运行:
91
+
92
+ ```sh
93
+ docker compose up -d --build
94
+ ```
95
+
96
+ 本机面板和 MCP 地址仍是 `http://127.0.0.1:5173` 与 `/mcp`;用 `DEVICE_CENTER_PORT=5174` 可调整宿主端口。不要同时占用同一个前台/后台端口。
97
+
98
+ ```mermaid
99
+ flowchart LR
100
+ AI[宿主机上的 Codex] --> URL[127.0.0.1:端口/mcp]
101
+ URL --> DC[容器中的 Device Center]
102
+ DC --> DB[容器数据卷]
103
+ ```
104
+
105
+ Codex 主动访问容器服务,不需要把 AI、Codex 配置、Docker socket 或宿主机目录挂载进容器。此方向使用本机端口映射;`host.docker.internal` 是容器访问宿主机服务的相反方向,这里不需要。参见 [Docker 网络方向](https://docs.docker.com/desktop/features/networking/networking-how-tos/) 与 [仅发布到回环地址](https://docs.docker.com/get-started/docker-concepts/running-containers/publishing-ports/)。
106
+
107
+ Compose 只映射 `127.0.0.1`、持久化一个数据卷、设置重启策略,并使用非 root 用户。Docker 引擎需要持续运行,并应使用 28.0.0 或更新版本;旧版本存在同局域网访问回环发布端口的问题,见 [Docker 发布端口说明](https://docs.docker.com/engine/network/port-publishing/)。容器只表示容器 Driver,不显示宿主内存/磁盘,也不读取宿主 SSH 配置。需要宿主设备和已有 SSH 关联时优先在宿主原生运行 Driver;将来可设计独立宿主 Driver 对接容器,不挂载整个 HOME、密钥目录或 Docker socket。不要把没有鉴权的本机模式直接暴露到公网或局域网。
108
+
109
+ ## 个人云端面板
110
+
111
+ 云端模式必须同时设置 `DEVICE_CENTER_RUNTIME=cloud`、明确的 `DEVICE_CENTER_PUBLIC_ORIGIN=https://你的域名` 和 `DEVICE_CENTER_AUTH_FILE`。使用现有 App Services/个人站统一账号时,在仓库外保存权限为 `0600` 的认证配置:
112
+
113
+ ```json
114
+ {
115
+ "provider": "app-services",
116
+ "backendOrigin": "https://appapi.example.com",
117
+ "accountOrigin": "https://example.com",
118
+ "ownerEmail": "owner@example.com"
119
+ }
120
+ ```
121
+
122
+ 这些值是配置示例。控制面、账号站和 App Services 必须使用同一受信任的父域名,账号站已经设置共享的 HttpOnly `sessionToken` cookie。服务只向配置的 App Services `/v1/me` 验证身份,并检查所有者邮箱;不接受浏览器自报的用户 ID、匿名或手机号会话,不复制账号服务的签名密钥。统一登录只支持这份个人访问限制,不是多用户托管。
123
+
124
+ 用户点击“使用统一账号登录”,在个人站通过邮箱或 Google 登录,回到设备面板会自动继续;个人站已登录时直接进入。退出设备面板仅退出本面板;个人站退出或切换账号会使本面板会话失效。身份结果最多缓存10秒,账号服务不可用时拒绝受保护访问,不回退到密码。
125
+
126
+ 独立部署仍可显式使用 `provider: "password"`(旧配置可省略 provider):私有文件只保存用户名、16字节盐的十六进制值及 scrypt 哈希,参数 N=32768、r=8、p=1、输出64字节。统一账号模式拒绝本地密码接口;认证配置和凭据不进入源码、部署压缩包或日志。
127
+
128
+ 后端继续只监听回环地址,由 HTTPS 反向代理访问;代理必须保留配置的 Host 并覆盖 `X-Forwarded-Proto=https`,不能转发客户端伪造的值。访问者先登录,网页、设备列表、批准/撤销与下载入口受保护。机器配对申请公开但限速,轮询、回报和断开需独立身份签名。云端 MCP / SSH 操作仍不可用。登录、继续和退出要求同源 Origin;本面板会话使用 Secure、HttpOnly、SameSite=Strict cookie,8小时失效,进程重启后重建。统一登录每个会话凭据每分钟最多8次继续尝试,校验并发/缓存大小有上限。此模式只用于个人访问,没有公开注册或多用户权限管理。
129
+
130
+ **面板上线不等于设备接通。** 云端不读取服务器 SSH、不复制本机数据库,云端 MCP 与 SSH 操作仍不可用。点“连接我的电脑”,下载源码、复制命令并在自己的电脑运行,再单独输入配对码、核对指纹和范围;批准并首次回报后才显示真实资源。当前支持 Node.js 24、macOS/Linux 源码 Driver,命令不含凭据。详见[域名绑定、常驻与排查](docs/driver-binding.md)及[部署记录与恢复](docs/deployment-readiness.md)。
131
+
132
+ ## 当前真实功能与边界
133
+
134
+ | 真实本地功能 | 尚未实现 |
135
+ | --- | --- |
136
+ | 内置本机 Driver、真实内存/卷磁盘、状态筛选 | 独立设备 Agent、签名安装包 |
137
+ | SSH 候选、显式关联、严格验钥的一次只读资源检查 | 指定日志、构建/算力任务、任意远程命令 |
138
+ | 同一服务上的 HTTP MCP,四个只读工具 | 任务授权与审计 |
139
+ | 真实 MCP 初始化记录、复制本机地址或命令 | 设备可信启动、系统钥匙串封装 |
140
+ | macOS/Linux 管理 Driver 本机身份、一次性配对、签名回报及撤销 | Windows Driver、独立安装器、中继、网络限速、配置迁移 |
141
+ | 用户服务脚本、Docker 配置、个人统一登录与 HTTPS 入口 | 跨平台实际安装验证、多用户托管 |
142
+
143
+ 项目位置保留在侧栏,目前没有项目管理或示例项目。“添加设备”只关联用户选中的已配置 SSH 主机;导入后执行一次固定只读检查,后续可在卡片重试,数据库保存引用与采集结果。不复制 HostName、私钥路径或凭据,不把关联当成信任或任务授权。候选只是 SSH 配置条目,可能有多个别名指向同一物理主机。删除系统 SSH 配置后,已有引用保留并提示配置不可用。明确移除产品内引用时,同时删除其快照与连接计划,并记录本地排除项,后续读取或一键导入不会重新加入;不修改系统 SSH 配置或身份文件。排除按租户与具体别名记录,不按客户端名称全局过滤。
144
+
145
+ 接入资源设备的后续设计:设备在本机生成身份私钥,服务只登记公钥;短时一次性登记码由用户单独输入,不写入 prompt、脚本、压缩包或日志。Agent 建立安全出站连接。获准的局域网端点可尝试认证直连,失败时回退用户选择的中继。连接与任务授权分开,构建、指定日志和计算各自有目标及额度限制。
146
+
147
+ ## 开发与后续
148
+
149
+ ```sh
150
+ npm run check
151
+ npm test
152
+ ```
153
+
154
+ - [架构](docs/architecture.md) · [接入排查](docs/troubleshooting.md)
155
+ - [访问路径与局域网优先](docs/connection-routing.md)
156
+ - [安全与配置迁移设计](docs/security-and-sync.md) · [路线图](docs/roadmap.md)
157
+ - [安全边界](SECURITY.md) · [贡献说明](CONTRIBUTING.md)
158
+
159
+ 这些开发检查需要完整源码仓库;npm 运行包不含测试及发布归档。检查不能代替加密协议、远程授权或跨平台安装验证。MIT 源码随 npm 包分发;GitHub 仓库公开与安全报告渠道仍需另外安排。
package/SECURITY.md ADDED
@@ -0,0 +1,23 @@
1
+ # Security
2
+
3
+ Device Center is a single-user Driver prototype with read-only local MCP, explicit SSH-reference association and owner-triggered fixed SSH resource checks. It observes real local metrics and bounded SSH metadata. An opt-in personal cloud panel requires HTTPS and owner login, and supports owner-approved macOS/Linux management Driver pairing, signed read-only telemetry and revocation. It has no multi-user hosting, SSH key custody, resource Agent, encrypted relay, remote task authority or configuration migration. Do not place secrets in its database or expose unauthenticated local mode to a LAN or Internet.
4
+
5
+ Native startup binds to loopback. Host/Origin checks reject cross-site requests and JSON input is bounded. Association and check POSTs require explicit same-origin Origin and currently discovered aliases; checks additionally require prior association and accept no command/path. MCP exposes only inventory/status/candidate/description tools and never connects to targets. Initialization traffic is not an authenticated identity or heartbeat. Other local programs can access this unauthenticated endpoint and trigger fixed SSH checks with the service user's existing authentication. Browser isolation and tenant namespaces are not production access control.
6
+
7
+ Cloud mode fails closed without an exact HTTPS origin and private owner authentication file. The process remains on loopback behind a proxy that fixes Host and HTTPS scheme. Unknown authentication providers fail startup. Inventory, owner approval/revocation and source download require browser authentication. Machine enrollment start is public and bounded; poll/report/disconnect require independent device signatures. Cloud SSH and MCP routes reject remote operations even after binding; cloud mode never reads server SSH or claims the server is the user's computer. This is personal access protection, not an audited multi-user hosting or device trust protocol.
8
+
9
+ The App Services adapter uses the existing shared HttpOnly `sessionToken` cookie. It sends that credential only as a Bearer header to the configured HTTPS `/v1/me`, with a five-second timeout, no redirects and bounded JSON. The verified response must identify a non-anonymous email/Google/Apple session and the configured owner's email. Client `userId`, decoded JWT claims, phone/guest/service sessions and other accounts grant no access. No upstream signing secret, password or token is copied to the configuration, deployment archive, database, URL or localStorage. Tokens are present in request memory during verification; retained session/cache keys contain only their SHA256 digests. The shared cookie makes all participating subdomains part of the existing account trust boundary; this adapter is for a trusted common parent domain, not arbitrary cross-domain SSO.
10
+
11
+ Panel sessions last eight hours and use a separate Secure/HttpOnly/SameSite=Strict host-only cookie; in-memory records are bounded to 32. Requests require the same current shared account credential. Removing/changing it revokes the panel session. Verified profiles are cached for at most ten seconds, bounded to 64; upstream outages fail closed after a cached result expires. SSO continuation is limited to eight attempts per token per minute, at most eight concurrent upstream checks, and a bounded attempt cache. Login/continuation/logout require explicit same-origin requests. Panel logout clears only panel state and prevents silent re-entry; central login and other products remain signed in. Restart clears panel sessions, but an existing verified central session can establish a new one. Account/device permissions remain separate.
12
+
13
+ Explicit standalone password mode remains available, using salted scrypt (N=32768, r=8, p=1, 64-byte output), private hashes, eight attempts per minute and the same eight-hour panel session boundary. App Services mode rejects this endpoint before reading password input and never falls back to it during an outage.
14
+
15
+ Passive discovery enumerates exact Host aliases from `~/.ssh/config` and restricted unconditional Includes under `~/.ssh/`. Unsupported Includes are skipped with notices. Discovery never reads keys, passwords, ssh-agent or known_hosts, or invokes ssh/ssh -G/Match exec. Only alias/source metadata is returned; effective options are not resolved. An alias is neither device identity, connectivity nor task authority; several aliases can represent one host.
16
+
17
+ Active checks use `/usr/bin/ssh` with a temporary isolated configuration for explicit root Host profiles and named ProxyJump aliases. Custom command, conditional and special authentication profiles stop for review; wildcard/global/Include options are not fully inherited. OpenSSH uses existing identity references and known_hosts, strictly verifies host keys, disables new forwarding and host-trust updates, and runs only the fixed macOS/Linux POSIX resource script. No target installation, arbitrary command/path input or key-content import occurs. Checks have a 25-second timeout, 32 KiB output limit and two-operation concurrency cap. SQLite stores alias, timestamp, configuration digest and normalized resources/failure code; raw outputs, identity paths and configuration contents are not stored. Success expires after five minutes; failure or profile changes invalidate online telemetry. This is a one-time observation, not a persistent connection or task grant.
18
+
19
+ The optional Docker recipe publishes to host loopback only, uses a non-root process and its own data volume, and has no host or Docker socket mounts. Its Driver represents the container, not host telemetry or SSH access. Actual container runtime and user-level background installation have not been validated in this task. `DEVICE_CENTER_RUNTIME=container` is a deployment setting, not proof of isolation.
20
+
21
+ Management Driver identity keys are generated only when the user explicitly runs `connect --confirm`. Private keys remain in owner-only 0600 files in 0700 directories; this is not OS keychain/TPM protection. The server stores public keys and fingerprints. Ed25519 requests bind origin, path, ID, time, nonce and payload digest; persistent nonce retention and revocation checks reject replay. Owner approval consumes a five-minute request transactionally. No bearer credential is issued. See [protocol, scope and limitations](docs/driver-binding.md). Short-lived single-use enrollment credentials are entered separately, not put into prompts, scripts, archives or diagnostic logs. Remote operations need independent owner grants scoped to a device, capability, path, time and quota. Hosted service key custody and configuration migration remain design work in `docs/security-and-sync.md`.
22
+
23
+ Tests cover local observations, SSH metadata/profile parsing, fixed checks with injected subprocesses, cache expiry/failures, association, service plans and personal cloud access boundaries. Ephemeral fixture identities exercise signatures, proof boundaries, replay, approval, revocation and expiry through isolated HTTP/SQLite tests. They do not contact real hosts, establish a production identity, validate remote task authority or constitute a protocol audit. External deployment verification is separately recorded in `docs/deployment-readiness.md`. A private security-reporting channel, license and public release plan remain to be selected. Do not post credentials, host metadata or private logs in public issues.
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, realpathSync } from 'node:fs';
3
+ import { fileURLToPath, pathToFileURL } from 'node:url';
4
+
5
+ const ROOT = fileURLToPath(new URL('..', import.meta.url));
6
+ const version = () => JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
7
+ export function parseCli(argv = []) {
8
+ const command = argv[0] ?? 'help';
9
+ if (['help', '--help', '-h', '--version', '-v'].includes(command)) {
10
+ if (argv.length > 1) throw new Error('帮助和版本命令不接受其他参数。');
11
+ return { command: command === '-v' ? '--version' : ['-h', '--help'].includes(command) ? 'help' : command };
12
+ }
13
+ if (!['start', 'plan', 'connect', 'run', 'status', 'disconnect', 'service'].includes(command)) throw new Error('未知命令。运行 device-center --help 查看用法。');
14
+ const args = argv.slice(1);
15
+ if (command === 'connect' && !args.includes('--cloud')) throw new Error('请用 --cloud 明确指定自己的 HTTPS 控制面域名。');
16
+ if (command === 'start') {
17
+ let port = 5174;
18
+ if (args.length) {
19
+ if (args.length !== 2 || args[0] !== '--port') throw new Error('用法:device-center start [--port 5174]');
20
+ port = Number(args[1]);
21
+ }
22
+ if (!Number.isInteger(port) || port < 1024 || port > 65535) throw new Error('端口须为 1024 到 65535。');
23
+ return { command, port };
24
+ }
25
+ if (command === 'service') {
26
+ if (!['plan', 'status', 'install', 'uninstall'].includes(args[0] ?? 'plan')) throw new Error('用法:device-center service plan|status|install|uninstall [--port 5174] [--confirm]');
27
+ return { command, args: args.includes('--port') ? args : [...(args.length ? args : ['plan']), '--port', '5174'] };
28
+ }
29
+ return { command, args };
30
+ }
31
+ export function helpText() {
32
+ return `Device Center ${version()} · 只读管理 Driver 源码原型
33
+
34
+ 需要 Node.js 24;管理 Driver 支持 macOS / Linux。
35
+
36
+ device-center start [--port 5174] 启动本机面板与只读 MCP
37
+ device-center plan --cloud https://你的域名 预览回报范围,不生成身份或联网
38
+ device-center connect --cloud https://你的域名 --confirm
39
+ 在本机生成身份,申请配对后等待你核对批准
40
+ device-center status 查看已有绑定,不显示私钥
41
+ device-center run [--port 5174] 前台恢复已绑定 Driver
42
+ device-center disconnect --confirm 通知云端撤销,保留本机身份
43
+ device-center service plan|status|install|uninstall [--port 5174] [--confirm]
44
+
45
+ 默认端口5174,数据位于本用户的应用数据目录。关闭网页不会停止前台进程。
46
+ 默认回报本机名称、系统、内存和磁盘;--share-ssh 需命令申请及面板另行批准。
47
+ 远程任务、自动选路和 Windows Driver 尚未实现。安装包不含身份或配对码。
48
+ 使用 npx 时只运行前台;常驻请先将本包固定版本安装到长期目录,再单独 install。
49
+ `;
50
+ }
51
+ export async function main(argv = process.argv.slice(2)) {
52
+ const plan = parseCli(argv);
53
+ if (plan.command === 'help') return helpText();
54
+ if (plan.command === '--version') return version();
55
+ if (Number(process.versions.node.split('.')[0]) < 24) throw new Error('需要 Node.js 24 或更高版本。');
56
+ if (plan.command === 'service') {
57
+ if (plan.args[0] === 'install' && /(?:^|[\\/])_npx[\\/]/.test(ROOT)) throw new Error('npx 缓存会被清理,不能用作常驻目录。请先 npm install -g device-center@固定版本,再运行 device-center service install --confirm。');
58
+ return (await import('../scripts/service.mjs')).main(plan.args);
59
+ }
60
+ const driver = await import('../scripts/driver.mjs');
61
+ if (plan.command === 'start') {
62
+ if (process.platform === 'win32') throw new Error('Windows 原生管理 Driver 尚未支持。');
63
+ await driver.runLocal({ port: plan.port }); return;
64
+ }
65
+ return driver.main([plan.command, ...plan.args]);
66
+ }
67
+ // npm links bin entries through a symlink; compare the actual script, not the link path.
68
+ if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
69
+ try {
70
+ const result = await main();
71
+ if (result != null) console.log(typeof result === 'string' ? result : JSON.stringify(result, null, 2));
72
+ } catch (error) { console.error(error.message); process.exitCode = 1; }
73
+ }
package/compose.yaml ADDED
@@ -0,0 +1,22 @@
1
+ services:
2
+ device-center:
3
+ build: .
4
+ ports:
5
+ - "127.0.0.1:${DEVICE_CENTER_PORT:-5173}:5173"
6
+ environment:
7
+ DEVICE_CENTER_SERVICE_MANAGER: docker
8
+ volumes:
9
+ - device-center-data:/app/data
10
+ restart: unless-stopped
11
+ init: true
12
+ security_opt:
13
+ - no-new-privileges:true
14
+ cap_drop:
15
+ - ALL
16
+ healthcheck:
17
+ test: ["CMD", "node", "--input-type=module", "-e", "const r = await fetch('http://127.0.0.1:5173/api/health'); process.exit(r.ok ? 0 : 1)"]
18
+ interval: 30s
19
+ timeout: 5s
20
+ retries: 3
21
+ volumes:
22
+ device-center-data:
@@ -0,0 +1,66 @@
1
+ # 架构
2
+
3
+ ## 当前实现
4
+
5
+ 一个 Node.js 24 进程提供网页、SQLite 设备关联 API 和 `/mcp` Streamable HTTP 端点。MCP 与网页读取同一个库存适配器:实时本机 Driver + 显式保存的 SSH 引用和检查快照 + 既有非示例设备。运行依赖为零,原生 JavaScript/CSS 保留紧凑侧栏。
6
+
7
+ - `server/database.mjs` 只建表,不创建任何设备或示例数据,不做自动清理。
8
+ - `server/modules/devices/repository.mjs` 排除旧版 `is_demo=1` 行。在线状态来自回报时间;未回报为未知,超过五分钟为过期。内存与磁盘独立序列化。
9
+ - `server/drivers/local.mjs` 用 Node OS 接口与 `statfs` 读取真实本机指标;进程运行中为在线。内存含缓存,磁盘范围为用户目录所在卷;两项独立处理失败。容器只表示容器 Driver,不报告宿主指标。
10
+ - `server/drivers/sshConfig.mjs` 有界读取配置、枚举明确 Host 别名;不读取密钥,不调用 OpenSSH、解析动态条件或连接目标。最多 32 个配置文件、每文件 128 KiB、总计 512 KiB、递归 5 层、200 个别名;安全范围外 Include 和条件 Include 跳过并提示。
11
+ - `server/drivers/inventory.mjs` 保存用户勾选的 SSH 别名及时间,按租户命名空间隔离,事务内幂等插入。检查成功五分钟内为在线,之后过期;失败、配置缺失或配置摘要改变为未知。GET 和 MCP 只读取缓存,不连接目标。别名不是物理设备唯一身份。
12
+ - `server/drivers/sshProbe.mjs` 对根配置中的完整明确 Host 和命名 ProxyJump 建立隔离 SSH 配置;涉及 Match、条件 Include 或自定义命令的条目停止审核。OpenSSH 复用现有身份引用、严格校验已有 known_hosts,不写主机信任。固定 POSIX 脚本读取 macOS/Linux 基础资源;25 秒超时、32 KiB 输出、最多两个并发,不接受命令或文件路径。
13
+ - `server/connector.mjs` 共享四个只读 MCP 工具:Driver 状态、实际库存、SSH 配置候选、能力边界说明。没有命令、任意文件、日志或设备操作工具。
14
+ - `server/index.mjs` 提供无状态 HTTP MCP 的 JSON 响应,通知返回 202,不创建会话令牌,不提供 SSE(GET 返回 405)。记录最近有效初始化时间;不将记录当成鉴权、持续在线或设备心跳。
15
+ - `server/mcp.mjs` 兼容原有 stdio 配置,只读同一实际库存。stdio 进程与 HTTP 服务不共享运行状态,所以不会出现在面板的 HTTP 初始化记录中。
16
+ - `scripts/service.mjs` 默认只输出计划;用户显式执行安装才登记 macOS/Linux 用户级服务。
17
+ - Docker 是可选的本机运行容器,MCP 仍由宿主智能体主动连接。没有宿主挂载或 Docker socket 权限。
18
+
19
+ ## 请求边界
20
+
21
+ 首次使用界面统一为管理电脑、智能体、设备三步,复用同一组本机状态与 API。原生本机资源可读时自动跳过管理电脑步骤;只有近期真实 MCP 初始化记录才跳过智能体步骤。允许稍后接入后直接导入引用,不生成握手或任务操作授权。点击导入会为新增设备排队执行一次固定 SSH 只读检查。独立管理 Driver 可向 HTTPS 域名申请配对,所有者单独输入码、核对指纹并批准后,按固定字段回报资源;未取得回报仍为未知。完整流程见[使用引导](onboarding.md)。
22
+
23
+ 页面外框固定为视口高度,侧栏与顶部不参与设备列表滚动。设备用紧凑行展示,内存和磁盘仍为独立列;小屏内部换行,空间不足时仅列表滚动。打开/关闭弹窗、刷新和快照过期重绘保留列表位置;改变搜索或状态筛选回到列表顶部。采集口径移到数值提示和可展开状态说明,失败仍明确显示。
24
+
25
+ `server/drivers/sshRoute.mjs` 从用于只读检查的已核验配置生成非秘密路径节点,面板与 MCP 共用。每台设备显示路径摘要,可打开图示与认证说明;`lastVerifiedRoute` 只在配置摘要与经过认证的检查结果一致时提供,资源采集是否可用单独判断。内部 `expired` 在面板显示“待刷新”,只说明数据时效。当前固定按配置连接,未实现自动 LAN 选路;具体方案与验收见[访问路径与局域网优先](connection-routing.md)。
26
+
27
+ `server/drivers/connectionPlans.mjs` 校验并保存每台已关联 SSH 设备的本机计划:可同时登记 LAN 地址/端口与用户自建、项目托管或 Cloudflare 中转类型,以及 LAN 优先/仅中转策略。写接口要求明确同源 Origin;不接受凭据、任意 URL 或额外字段。新增表不清理或覆盖已有设备、配置与快照。库存与 MCP 读取 `connections`,其中 `effective: false`;未验证入口及未接入中继不会改变在线状态,现有检查仍仅使用原 SSH 配置。
28
+
29
+ 原生进程固定监听 `127.0.0.1`。仅接受回环 Host 和同源 Origin,拒绝跨站请求;静态内容有 CSP,不启用 CORS。MCP JSON 请求上限 16 KiB,检查类型、Accept、协议版本和工具参数。MCP 协议按 [2025-11-25 transport](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports) 实现本机无状态只读子集。
30
+
31
+ 底层仅保存引用的 `POST /api/devices/ssh`:必须明确同源 Origin、JSON、小于 16 KiB;只接收 aliases 数组,重新核对当前候选,其他字段拒绝。只写本地别名引用,不执行远端操作。MCP 没有关联写入或网络检查工具。
32
+
33
+ 初始化预览 `GET /api/ssh/initialization` 在发现结果上增加本机配置检查:`sshInitialization.mjs` 复用隔离配置解析器,并用 stat/access 检查目标与跳板身份文件的引用是否可读取;不读身份内容,不执行 SSH。返回 importable / reason / routeMode,不返回身份路径或配置原文。`POST /api/ssh/initialize` 要求明确同源 Origin、严格 aliases 字段和最多 200 个别名;提交时重新核对可导入状态,再通过原有关联事务保存引用。同名导入幂等,未通过检查时整批拒绝、提示重读;不写快照、连接计划或主机配置,不自动探测、删去无效条目或授予任务权限。此处的初始化与 MCP initialize 握手是两个独立概念。
34
+
35
+ 面板使用组合写接口 `POST /api/devices/ssh/associate-and-check` 与 `POST /api/ssh/initialize-and-check`,保存引用后只为新增别名启动一次固定检查。`POST /api/devices/ssh/check-batch` 接受严格的 aliases 数组,最多 200 个、必须已关联且当前仍存在;队列按别名去重,所有检查共享两个并发限制。关闭网页不取消队列,进程重启不恢复未完成检查;已完成结果持久化。GET / MCP 不排队、不重试。前端仅在队列有工作时每秒读取库存,失败三次停止读取并提示刷新。
36
+
37
+ `POST /api/devices/ssh/remove` 也要求明确同源 Origin 和严格 aliases;拒绝移除正在检查或排队的条目。事务内删除指定引用、快照和计划,再写 `ssh_exclusions`。仅排除当前租户指定别名,不改系统配置或身份文件,也不自动删无效候选。排除项跨重启保留,其他租户不受影响。
38
+
39
+ 库存 `ssh.connectivity` 区分 unobserved / queued / checking / passed / failed / stale / configuration-changed,并提供检查时间和失败码。通过认证后资源脚本不支持或失败,仍可标记 SSH 连通,指标与设备状态保持未知。只在已通过验钥的 OpenSSH 检查完成或收到远端命令退出状态时作此判断;SSH 错误、超时和资源错误不混淆。成功资源快照的原有五分钟状态仍保留,检查不授予远程任务权限。
40
+
41
+ 只读 SSH 解析器按独立范围处理 `Match`。明确且纯静态的 `Match originalhost alias[,alias]` 只让它命中的目标或跳板配置进入人工核验;不将这段条件错误归入紧邻的前一个 Host。条件选项不会进入隔离检查配置;动态、通配或其他复杂条件仍按原规则停止核验,不执行条件命令。
42
+
43
+ `POST /api/devices/ssh/check` 同样要求明确同源 Origin,只接受已关联且当前仍存在的 alias;由所有者主动发起。它会真实执行固定远端只读采集,不能传入任意 shell、路径或命令。仅支持非 Windows 原生管理端;容器没有宿主认证权限。结果保存在独立快照表,原始 stdout/stderr 与私钥内容不进入数据库或响应。
44
+
45
+ 本机模式不实施用户鉴权。可信范围仅是同一用户管理的本机,允许读取指标、登记引用和发起固定 SSH 采集;服务使用运行用户已有 SSH 认证,其他本机程序也可能访问端点并触发该采集。租户命名空间不是认证。初始化名称不用于识别所有者。不能只把监听地址改成 `0.0.0.0`。
46
+
47
+ 个人云端模式由 `cloudAccess.mjs` 提供请求边界和显式认证方式;`unifiedAccess.mjs` 负责 App Services 身份核验。私有配置指定同父域名的账号站、HTTPS `/v1/me` 服务及唯一所有者邮箱。复用共享 HttpOnly 会话,不解码 JWT 作为信任、不复制签名密钥;身份结果缓存10秒,验证超时/异常拒绝访问,不回退密码。面板会话8小时、只存凭据摘要,需匹配当前共享身份;面板退出不退出其他应用,账号站退出/切换则使面板失效。未登录静态登录页与方式接口可访问;机器配对申请公开并限速,轮询/回报/断开要求独立身份签名,其余网页、库存及所有者写入口受保护;继续/退出严格同源,缓存与验证并发有上限。独立密码方式仍需显式私有scrypt配置,未知方式启动失败。后端回环监听,Nginx覆盖Host/协议头。云端库存与本机库存分开,未绑定时返回空设备,拒绝SSH/MCP设备接口,不读取服务器SSH。管理电脑身份、配对与回报由 driverProtocol.mjs、cloudRegistry.mjs、cloudLink.mjs 独立处理;浏览器会话不能代替机器签名。默认本机指标,SSH 名称/缓存分享需双重显式批准,不上传认证配置或私钥。云端不提供远程操作或多用户权限协议。参见[协议与生命周期](driver-binding.md)。
48
+
49
+ `DEVICE_CENTER_RUNTIME=container` 只供本机 Docker recipe 在容器网络中监听,Compose 对宿主机只发布回环端口。该环境变量并不能证明进程真的位于容器,不是安全鉴权开关;不要在原生启动时设置。Host/Origin 检查仍启用。Docker 默认端口映射经明确 Host IP 限制;实际运行未验证。
50
+
51
+ ## 后续角色
52
+
53
+ ```mermaid
54
+ flowchart LR
55
+ AI[管理电脑 AI] --> MCP[MCP]
56
+ MCP --> Driver[本机 Driver]
57
+ Driver --> Ref[SSH 配置引用]
58
+ Ref --> Snapshot[显式只读检查与缓存]
59
+ Snapshot --> Host[已有 SSH 主机]
60
+ Ref -.单独授权的任务适配器.-> Task[指定日志与受限任务]
61
+ Driver -.设备主动连接.-> Agent[资源设备 Agent]
62
+ Agent -.主动安全出站.-> Cloud[控制面与中继]
63
+ MCP -.获准 LAN 直连或中继.-> Cloud
64
+ ```
65
+
66
+ 虚线能力尚未实现。资源 Agent 本地生成身份私钥,只登记公钥。一次性短期登记码单独交互输入。安全连接只提供可达性,不能授予远程任务权限。首次有效观测后才展示在线和资源读数。线上同步只迁移获准非秘密登记元数据,不能复制设备私钥、任务权限或在线状态。
@@ -0,0 +1,65 @@
1
+ # 访问路径与局域网优先
2
+
3
+ ## 当前能做什么
4
+
5
+ 面板的每台设备显示配置路径,点击路径可查看节点、地址和端口。MCP 库存读取同一份信息。原生本机由进程直接采集;SSH 设备的路径来自保守解析并用于检查的配置,读取与展示时不调用 `ssh -G`、不解析动态命令、不探测 DNS 或网络。导入后一次检查、或用户手动重试,才实际访问已配置路径。
6
+
7
+ - `ssh.route`:当前经过核验的配置路径;包含目标和跳板的别名、地址、端口、地址类别,不含登录用户名、身份文件引用或私钥内容。
8
+ - `ssh.lastVerifiedRoute`:仅当成功检查的配置摘要仍与当前相同,才给出该检查的时间和路径。配置改变时旧快照失效,不能把新路径冒充上次用过的路径。
9
+ - `selection: configured-only`、`liveConnection: false`:当前固定按配置访问,没有自动选路或常驻连接。
10
+ - 地址类别只依据字面值。私网地址不证明同一局域网、可达性或主机身份;主机名未解析,其他 IP 不声明一定是公网。
11
+
12
+ 快照超过五分钟的内部状态仍为 `expired`,面板显示“待刷新”。它表示内存/磁盘数据较早,不表示 SSH 密钥过期或设备断线。刷新列表不重连;导入后检查一次,后续点“检查 SSH(只读)”才重新采集。
13
+
14
+ ## 跳板为什么能访问设备
15
+
16
+ ```mermaid
17
+ flowchart TD
18
+ Local[本机 SSH 客户端\n使用本机已有身份] --> Jump[已配置的 SSH 跳板\n转发 TCP 流量]
19
+ Jump --> Entry[目标 HostName 与端口\n若为回环地址,属于跳板端]
20
+ Entry -.既有链路,机制未核验.-> Target[目标设备 SSH 服务]
21
+ Local -.校验跳板和目标身份,分别认证.-> Target
22
+ ```
23
+
24
+ SSH `ProxyJump` 先连接跳板,再从跳板建立到目标入口的转发。目标 SSH 的认证仍由本机客户端完成;无需把目标客户端私钥复制到跳板。目标的 `authorized_keys` 保存允许登录的客户端公钥,公钥不能代替私钥登录。设备、跳板也各自有用于证明服务器身份的主机密钥,不能与用户的登录密钥混为一谈。
25
+
26
+ 若目标入口是跳板上的回环端口,它可能由既有反向转发或其他映射提供。仅凭本机配置与一次成功检查,无法确认后段由谁建立、其具体部署方式或密钥放在哪里。界面对此使用虚线节点并标为未核验;不把推测画成事实。
27
+
28
+ 参考:[OpenSSH ProxyJump](https://man.openbsd.org/ssh_config#ProxyJump)、[HostKeyAlias](https://man.openbsd.org/ssh_config#HostKeyAlias)。
29
+
30
+ ## 下一步实现:已知入口的认证直连与回退
31
+
32
+ 以下是方案,当前不执行。
33
+
34
+ 1. **设备与线路分开登记。** 一个稳定设备记录可以有“已批准的 LAN 入口”和“已批准的云端跳板入口”,避免同一电脑因两个别名变成两台。初次关联由用户确认,不凭别名、hostname 或相同登录私钥自动合并。
35
+ 2. **先绑定同一主机身份。** 通过可信渠道核对目标的主机公钥或指纹,把两条路径绑定到同一身份。保留原有严格验钥;按核验结果使用受限 known_hosts / HostKeyAlias 方案。不能仅填写同一个 HostKeyAlias 就宣称身份验证完成,不从云端下载私钥。
36
+ 3. **按需尝试获准入口。** 在资源检查或已授权操作开始时,最多尝试明确登记的 LAN 端点,以短超时完成 SSH 主机校验与客户端认证。不开新端口、不枚举网段、不探测未知地址。TCP 能连不代表认证成功。
37
+ 4. **选择与回退。** 认证直连成功则使用该连接;连接超时、拒绝或网络不可达,才可回退已经获准的跳板入口。未知/变化的主机身份、认证失败、配置错误和权限问题停止并提示,不用回退隐藏问题。
38
+ 5. **对用户解释结果。** 显示实际选中的线路、验证时间、有限的握手耗时及回退原因。局域网优先是策略,不宣称它总有最高吞吐;当前没有速度或带宽测量。网络变化时短期缓存失效,下次按需验证。
39
+ 6. **固定一次任务的连接。** 大文件传输沿选定连接传输,不在中途静默换路。失败返回已完成进度;只有经过验证的续传协议才能继续。安装、构建等操作不能自动重放;连接可达不授予操作权限。
40
+ 7. **接入智能体操作。** 智能体通过连接器请求范围明确的日志/文件/任务能力,由本机 Driver 使用同一个选路器和认证边界。智能体自行执行原 SSH 别名仍按原配置走,不会因面板出现路径图而自动优化。
41
+
42
+ 验收应覆盖:同一已批准设备直连成功、不在家时回退、无可达线路、身份变化拒绝、认证失败不回退、局域网地址变化需复核,以及传输中断不重放。使用受控夹具与已授权目标分别验证,不用模拟结果宣称真实网络已打通。
43
+
44
+ 当前缺少两台目标的准确局域网入口及同设备身份绑定;“同一局域网”本身不足以补全地址。既有 SSH 检查、路径展示和只读 MCP 可用;自动选路、文件传输和远程任务尚未实现。
45
+
46
+ ## 每台设备的两种连接方式
47
+
48
+ 已关联的 SSH 设备可以同时保存局域网入口和中转方案,界面不再将不同线路登记成不同设备。当前仍以已关联主 SSH 别名作为本地记录键,不将它冒充硬件唯一身份。
49
+
50
+ | 连接方式 | 配置与用途 | 当前状态 |
51
+ | --- | --- | --- |
52
+ | 局域网直连 | 明确设备地址、SSH 端口;在家优先使用 | 可以保存入口,未认证或探测;显示待验证 |
53
+ | 中转:用户自建服务器 | 用户自己拥有的 VPS / 服务器 | 可保存方案;既有 SSH 跳板仍沿原配置使用;新中继适配器未接入 |
54
+ | 中转:Device Center 服务 | 项目运营方提供的托管服务 | 可保存选择;没有部署服务、分配额度或托管凭据 |
55
+ | 中转:Cloudflare | 用户选择 Cloudflare Tunnel / Access 接入 | 可保存选择;没有注册账号、安装 cloudflared 或建立隧道 |
56
+
57
+ 连接策略可保存为 `lan-first`(局域网优先、中转备用)或 `relay-only`。这些是计划,`effective: false`,不会因保存而启用自动连接,也不会改变已有 SSH 检查、资源状态或权限。服务停止后重启仍可读取本机配置;MCP 只读同一计划,没有写入工具。
58
+
59
+ `POST /api/devices/ssh/connections` 必须有明确同源 Origin、JSON,并指向当前用户已关联的别名。只接受别名、设备地址、端口、中转类型和连接策略;写入 SQLite 的 `ssh_connection_plans`。地址不接受 URL、用户名、命令或回环入口;端口为 1–65535。计划不含私钥、令牌、登录口令或新增认证权限,字段超出范围会拒绝。空地址或空中转选择可以保存,用于清空这些计划字段;不删除设备或 SSH 配置。
60
+
61
+ ### Cloudflare 的接入条件
62
+
63
+ Cloudflare 的客户端 `cloudflared` SSH 模式需要在能访问目标 SSH 的设备侧运行隧道连接器,并在管理端配置客户端和 Access 身份策略。也可以另行选择 Cloudflare One 客户端的私网接入模式。不能把它视为无需客户端的通用 SSH 中继,不能承诺所有传输免费或不限量。
64
+
65
+ 参考:[客户端 cloudflared SSH 接入](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/use-cases/ssh/ssh-cloudflared-authentication/)、[Cloudflare One SSH 接入](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/use-cases/ssh/ssh-infrastructure-access/)。当前保守 SSH 检查不执行任意 `ProxyCommand`,Cloudflare 需要独立、范围明确的适配器;选择该方案不会绕过这条边界。
@@ -0,0 +1,64 @@
1
+ # 通过域名连接管理电脑
2
+
3
+ 当前支持个人控制面与 macOS / Linux 管理 Driver 的配对、只读资源回报和撤销。它不是远程终端或隧道;Windows Driver、独立安装器、系统钥匙串封装、远程任务和自动选路仍待实现。
4
+
5
+ ## 三步接入
6
+
7
+ 1. 登录自己的 HTTPS 控制面,点 **连接我的电脑**。下载 Driver 源码包,在管理电脑解压到长期保留的目录;需要 Node.js 24,无第三方运行依赖。已有最新版源码仓库可直接使用。
8
+ 2. 在该目录的终端运行面板复制的命令。下面是占位域名,请替换为自己的 HTTPS 域名。npm 分发候选见 [npm 使用与发布说明](npm.md):
9
+
10
+ ```sh
11
+ npm run driver -- plan --cloud https://control.example.com
12
+ npm run driver -- connect --cloud https://control.example.com --confirm
13
+ ```
14
+
15
+ `plan` 不生成身份、不写文件、不联网。`connect --confirm` 在本机生成 Ed25519 身份,并请求五分钟有效的配对码。终端显示配对码和完整公钥指纹,保持终端打开。
16
+ 3. 回到控制面,单独输入配对码,核对电脑名称、系统以及与终端一致的完整指纹,再点 **核对一致,绑定这台电脑**。只批准自己刚在手边电脑发起的请求。批准之前不回报资源;批准后等待首次回报,面板才由未知转为在线。
17
+
18
+ 命令和源码包不含配对码、长期凭据、私钥或本机配置。配对码只在本机终端与认证后的面板交互中使用,不交给智能体,不放入 URL、脚本、压缩包或诊断日志。终端显示属于必要交互,请勿录制或转发这一段。
19
+
20
+ 默认回报本机名称、系统类型、内存、用户目录所在卷磁盘及观测时间。需要分享已有 SSH 清单时,在命令中显式增加 `--share-ssh`,面板还需另行勾选批准;至多分享 15 个已关联 SSH 主机的名称和缓存资源状态,不含地址、用户名、路径、配置、原始错误或 SSH 私钥。云端不会要求管理电脑自动检查 SSH。
21
+
22
+ ## 本机使用与常驻
23
+
24
+ 批准后命令会启动回环地址上的前台 Driver,默认端口 5174。保持终端打开,访问 `http://127.0.0.1:5174`,在那里连接本机智能体、导入已有 SSH,并按需检查。云端不读取服务器 SSH,也不能读取浏览器电脑的文件。
25
+
26
+ 已有新版原生 Driver 在相同端口运行时复用它;不会结束旧进程。端口占用时改用 `--port`,MCP 地址与服务安装端口保持一致。
27
+
28
+ ```sh
29
+ npm run driver -- status
30
+ npm run driver -- run --port 5174
31
+ ```
32
+
33
+ 前台退出后,可自行重新 `run`。若要登录系统后常驻,先停止自己的前台进程,再执行:
34
+
35
+ ```sh
36
+ npm run service -- plan --port 5174
37
+ npm run service -- install --confirm --port 5174
38
+ ```
39
+
40
+ 服务安装是另一个显式操作。它使用独立持久数据库,不自动搬运开发预览数据库;在该本机面板重新导入 SSH 引用。macOS / Linux 用户服务安装脚本已有,实际安装仍需用户执行并在目标系统验证。
41
+
42
+ 身份文件:macOS 为 `~/Library/Application Support/Device Center/driver-link.json`;Linux 为 `~/.local/share/device-center/driver-link.json`。父目录要求 0700、文件 0600、当前用户所有,不接受符号链接或多硬链接身份文件。私钥目前是本机私有文件,并非 Keychain / TPM,不防护已控制本用户的恶意程序。不要上传、共享或直接编辑该文件。
43
+
44
+ ## 状态、断开与排查
45
+
46
+ - Driver 每约 30 秒主动向固定 HTTPS 域名 POST 回报,面板约 15 秒刷新。无需开放管理电脑的入站端口。网络失败退避至最多五分钟,恢复后自动重试。
47
+ - 未回报为未知;管理电脑最后回报超过 90 秒,或 SSH 缓存超过五分钟,显示待刷新。旧 SSH 观测时间不会被重复回报刷新;管理电脑失联后,其 SSH 快照也标为待刷新。
48
+ - 面板的 **管理连接 → 撤销 → 确认撤销** 会拒绝后续回报。本机看到撤销会禁用回报;不会删除身份文件、SSH 配置或设备记录。
49
+ - 本机主动断开:`npm run driver -- disconnect --confirm`。需要网络可达来通知云端;失败时先在云端撤销。身份文件保留,可再次向同一控制面申请配对。更换控制面不能自动覆盖已有身份。
50
+ - 无请求:检查运行目录、Node 版本、域名 DNS、可信 HTTPS、出站网络;不关闭证书校验。
51
+ - 码失效或已用:在本机重新 `connect`,勿重复批准旧请求。批准成功但本机未及时取得结果时,先撤销那条等待回报的登记,再重新配对。
52
+ - 签名时钟异常:校准系统时间;客户端与服务端偏差大于 90 秒拒绝请求。
53
+ - 私有文件错误:检查所属用户、目录和文件权限;不要输出文件内容到日志或工单。此原型不会自动修复权限、重置身份或清理数据。
54
+ - SSH 未知:回到本机面板检查;云端只显示缓存,不把管理电脑在线当作其他主机连通。
55
+
56
+ ## 协议与限制
57
+
58
+ 这是项目自定义的 `device-center-driver-v1` 协议,不宣称符合 OAuth Device Grant 或通过生产安全审计。配对体验参考 [RFC 8628 的设备确认和远程钓鱼提醒](https://www.rfc-editor.org/rfc/rfc8628.html#section-5.4),不能只凭设备名称批准陌生请求。
59
+
60
+ 云端保存公钥和 SHA256 指纹;配对码只保存 SHA256 摘要。所有者在现有个人登录边界内一次性批准,SQLite 事务消费请求,最多 16 个有效 Driver。机器轮询和回报需本机私钥签名,不依赖浏览器 cookie 或长期 Bearer token。签名涵盖固定协议、HTTPS Origin、POST 路径、Driver / 配对 ID、时间、随机数及排序后 JSON 的 SHA256;使用 Node 内置 Ed25519。随机数在 SQLite 保存三分钟,时间窗口 90 秒,拒绝重放,包括进程重启后的重放。
61
+
62
+ 请求与响应上限 16 KiB,固定字段白名单;全实例每分钟最多八个配对申请、八次配对码查找,最多 32 个待处理请求;配对轮询至少约五秒,回报至少 15 秒。公共申请入口仍可能遭遇拒绝服务,不适合直接扩为公开多用户托管。过期的配对请求与防重放记录按协议时效淘汰;已登记设备和回报不会自动清理。
63
+
64
+ 当前测试验证隔离环境中的协议和 HTTP 接口。实际身份生成、用户批准、目标平台常驻、真实网络下连续回报需要由使用者执行接入后验收;不能以测试数据冒充已绑定设备。远程命令、文件传输与中继任务权限没有因配对而授予。