19 KiB
Web / Docker 部署
Web 模式复用现有 React 界面,由 nyaterm-web 同时提供静态资源和 HTTP / WebSocket / SSE API。此 Beta 面向一个可信管理员:不同登录的终端、认证提示和运行中的 AI 请求隔离,但保存的连接、凭据、设置、known_hosts 和 AI 历史在同一实例内共享。
本地运行
需要 Node 24、pnpm 11.10 和 Rust 1.97.1(本次实际验证的版本)。
pnpm install --frozen-lockfile
pnpm build:web
pnpm web:build:server
生成两个独立随机秘密并写入工作区外的受限目录。以下命令不打印秘密;登录密码可通过密码管理器读取保存。目录和文件的访问权限应限制为服务运行用户。
$secretDir = Join-Path $env:LOCALAPPDATA 'NyaTermWebSecrets'
New-Item -ItemType Directory -Force -Path $secretDir | Out-Null
$loginBytes = [System.Security.Cryptography.RandomNumberGenerator]::GetBytes(32)
$keyBytes = [System.Security.Cryptography.RandomNumberGenerator]::GetBytes(32)
[System.IO.File]::WriteAllText((Join-Path $secretDir 'login'), [Convert]::ToBase64String($loginBytes))
[System.IO.File]::WriteAllText((Join-Path $secretDir 'encryption'), [Convert]::ToBase64String($keyBytes))
$env:NYATERM_WEB_PASSWORD_FILE = Join-Path $secretDir 'login'
$env:NYATERM_WEB_ENCRYPTION_KEY_FILE = Join-Path $secretDir 'encryption'
$env:NYATERM_WEB_PUBLIC_URL = 'http://localhost:8080/'
$env:NYATERM_WEB_BIND = '127.0.0.1:8080'
$env:NYATERM_WEB_DATA_DIR = Join-Path $env:LOCALAPPDATA 'NyaTermWebData'
pnpm web:serve
访问 http://localhost:8080/,使用生成的登录密码。PowerShell 示例要求 PowerShell 7 / .NET 的 GetBytes(int) API。Linux 可用 umask 077 后分别执行 openssl rand -base64 32 > login 和 openssl rand -base64 32 > encryption,然后配置相应 _FILE 变量。
环境变量
| 变量 | 含义 |
|---|---|
NYATERM_WEB_PASSWORD / NYATERM_WEB_PASSWORD_FILE |
登录密码,至少 32 字符;推荐随机生成。指定 _FILE 时优先读取文件。 |
NYATERM_WEB_ENCRYPTION_KEY / NYATERM_WEB_ENCRYPTION_KEY_FILE |
标准 Base64 编码的 32 字节随机密钥,必须持久保存,禁止重新生成后直接复用旧数据。 |
NYATERM_WEB_PUBLIC_URL |
浏览器实际访问地址,含端口和 base path;默认 http://localhost:8080/。非回环地址默认必须为 HTTPS。 |
NYATERM_WEB_BIND |
服务监听地址,默认 127.0.0.1:8080,容器为 0.0.0.0:8080。 |
NYATERM_WEB_DATA_DIR |
redb 数据目录,默认 ./nyaterm-web-data,容器为 /data。 |
NYATERM_WEB_DIST |
已构建 React 资源目录,默认 dist,容器为 /app/dist。 |
NYATERM_WEB_BASE_PATH |
前端构建时使用的路径,默认 /,必须与 PUBLIC_URL 的路径一致并以 / 结尾。 |
NYATERM_WEB_ALLOW_INSECURE_HTTP=true 可显式允许局域网 HTTP 地址;同时将 NYATERM_WEB_PUBLIC_URL 设置为浏览器实际使用的 IP/域名和端口,并将监听地址设为 0.0.0.0:8080。前端请求 ID 使用 crypto.getRandomValues 兼容 HTTP,但剪贴板等浏览器能力仍可能要求 HTTPS。Host/Origin 校验继续生效,访问地址必须与 PUBLIC_URL 一致。
服务启动会验证数据密钥;错误密钥会阻止启动。文件方式会去掉末尾换行。登录密码可独立替换,服务器重启使所有旧登录失效;加密密钥目前没有在线轮换 API。
Docker
部署文件集中在 deploy/web/。以下 Docker 和 Compose 命令均在仓库根目录执行;构建上下文仍是仓库根目录,使用根目录的 .dockerignore。
docker build -f deploy/web/Dockerfile -t nyaterm-web .
docker volume create nyaterm-data
docker run -d --name nyaterm-web --init \
-p 127.0.0.1:8080:8080 \
-e NYATERM_WEB_PUBLIC_URL=http://localhost:8080/ \
-e NYATERM_WEB_PASSWORD_FILE=/run/secrets/login \
-e NYATERM_WEB_ENCRYPTION_KEY_FILE=/run/secrets/encryption \
--mount type=bind,src=/absolute/private/secrets,dst=/run/secrets,readonly \
--mount type=volume,src=nyaterm-data,dst=/data \
--read-only --tmpfs /tmp --cap-drop ALL \
--security-opt no-new-privileges:true \
nyaterm-web
容器使用 UID / GID 10001:10001。绑定目录时,确保该用户可以读取秘密文件和写入数据目录;新 named volume 会继承镜像中 /data 的所有权。默认端口映射仅暴露本机。如需远程访问,使用下面的 HTTPS 反向代理配置。
仓库还提供 deploy/web/docker-compose.yml,它通过外部环境变量注入密码和密钥:
docker compose -f deploy/web/docker-compose.yml up --build -d
提前在当前 shell 或仓库根目录的受限 .env 中设置两个必填秘密。使用外部环境文件时,在 Compose 命令中显式添加 --env-file /absolute/private/nyaterm.env。.env* 已排除在 Git 和 Docker build context 外。生产环境优先将 Compose 改为 secrets 挂载和 _FILE 变量,避免把秘密写入镜像。Dockerfile 使用 Node、Rust、Debian 三阶段构建,最终镜像只含 dist、服务程序和运行依赖。
Compose 默认项目名显式设为 nyaterm,与原先在名为 nyaterm 的仓库根目录运行的默认项目名一致,数据卷仍为 nyaterm_nyaterm-data。如果旧部署使用其他仓库目录名、-p 或 COMPOSE_PROJECT_NAME,迁移后须通过 -p <原项目名> 或 COMPOSE_PROJECT_NAME 沿用原值,确保继续使用原数据卷和镜像;原登录密码和加密密钥也须保持一致。
HTTPS 和子路径
例如部署到 https://terminal.example.com/nyaterm/:
docker build -f deploy/web/Dockerfile --build-arg NYATERM_WEB_BASE_PATH=/nyaterm/ -t nyaterm-web .
运行时设置 NYATERM_WEB_PUBLIC_URL=https://terminal.example.com/nyaterm/。前端和后端必须使用相同的 origin 和路径;后端会校验原始 Host / Origin,不使用转发头放宽校验。代理应保留完整路径,不能剥掉 /nyaterm。
Nginx 示例(TLS 证书配置按已有站点设置):
# http 块
map $http_upgrade $nyaterm_connection_upgrade {
default upgrade;
'' close;
}
# 已配置 TLS 的 server 块
location = /nyaterm { return 308 /nyaterm/; }
location /nyaterm/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header Origin $http_origin;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $nyaterm_connection_upgrade;
proxy_buffering off;
proxy_request_buffering off;
proxy_read_timeout 650s;
client_max_body_size 0;
}
关闭响应缓冲以支持 SSE / 下载,关闭请求缓冲以支持流式上传。可根据部署需求调整上传大小和代理超时。Web 不提供跨源 API 或 Vite 开发代理;验证运行模式应使用构建后的 dist。
数据与能力范围
停服后备份整个数据目录,并单独备份加密密钥与登录密码。密钥丢失将无法解密凭据。不要让多个进程同时写同一个 redb volume,也不要把桌面用户数据目录直接作为服务器 volume。保存的私人密钥、密码和 AI provider 密钥使用原有 AES-GCM 存储。AI 历史和连接元数据属于实例共享数据,未作为整个数据库加密。
支持保存/临时 SSH、Telnet 和 VNC,密码/私钥与手工交互认证、主机指纹确认、终端输入/输出/resize、SFTP 浏览与单文件流式上传/下载、文本编辑、基础 AI Ask、设置持久化以及资源/GPU/NPU/进程监控。监控使用独立 SSH exec 通道,Docker 管理仍不支持。Telnet 支持原有字符编码、自动登录、本地回显、行编辑、NAWS 和 raw TCP;VNC 支持现有认证与服务器密钥确认、缩放、共享连接、只读、文本剪贴板和重连。三种协议均支持 SOCKS5、HTTP CONNECT 及代理认证、SSH 跳板(最多 8 层,使用跳板自身网络配置)。代理失败不回退到直连。插件部分提供兼容性及权限检查框架,当前没有浏览器插件安装/执行器。
Web 当前不支持本地 Shell、Serial、RDP、ProxyCommand、SSH agent/X11/证书登录、SSH 启动命令、非标准 SSH profile、非 UTF-8 SSH 终端/文件名、SFTP compatibility mode、CWD 自动跟踪、录制、SCP/Zmodem、本地文件 watcher、传输暂停/重试、同步备份和 AI Agent/MCP/本地附件。系统托盘、原生窗口、OS credential manager、系统全局快捷键、桌面通知、自动更新也不提供。浏览器文件、剪贴板、链接和 iframe 页面代替相应原生入口,仍受浏览器权限约束。
刷新页面时,按工作区 pane ID 重新附着当前登录的有效会话;旧连接租期结束后,保存的连接按原有工作区恢复流程重新创建。SFTP 仅对 SSH 会话提供。浏览器剪贴板需要 HTTPS 和浏览器权限,未授权时显示提示,画面和键鼠仍可使用。网络面板可管理代理及分组,Web 子窗口使用同源 iframe。单镜像部署,无需 noVNC、Guacamole 或额外服务。
测试与架构见 web-architecture.md。当前环境未安装 Docker CLI;镜像构建、容器内日志和 OpenSSH 部署验收仍需在具备 Docker 的环境执行。更新后请重新构建镜像并重建容器,旧镜像不会自动获得协议支持。
Web Beta 能力矩阵与验收
| 能力 | Web Beta | 桌面兼容 |
|---|---|---|
| SSH/Telnet | 登录 owner 隔离、输入输出、刷新附着、有限重试恢复 | 原生协议与生命周期保持 |
| VNC | 现有 Web 认证、输入、剪贴板及恢复 | 保持 |
| 单文件上传/下载 | 流式原始上传;浏览器 Blob 下载并提示失败 | 保持原生传输 |
| 同名上传 | ask/skip/rename/overwrite;POSIX 原子覆盖,拒绝目录覆盖 | 共用 duplicate_strategy 设置 |
| 文件编辑 | 默认内部编辑器,工作区或 iframe;二进制/不支持编码下载 | 保留外部编辑器、watcher 和本地路径 |
| 传输设置 | 仅冲突策略与内部编辑显示方式 | 隐藏字段完整保留,无迁移 |
| 目录传输、调优、暂停/重试 | 未实现,隐藏入口;含目录选择禁用下载 | 保持 |
| 资源/GPU/NPU/进程监控 | 独立 SSH exec 与白名单进程信号 | 保持 |
| RDP、录制、云同步、AI Agent/MCP | 当前不支持 | 保持 |
上传继续使用 POST api/sessions/{id}/upload?path=... 和原始文件正文;返回 { "bytes": 123, "status": "completed", "path": "/final/path" }。跳过返回 bytes=0、status=skipped;改名返回 UUID 后缀的最终 path。覆盖依赖远端 posix-rename@openssh.com;不支持时明确失败并保留原文件,不回退到先删后写。
SSH/Telnet 初次附着及断线恢复的每轮预算为 25 秒,单次握手最多 5 秒;退避依次 1、2、4 秒,之后不超过 5 秒。握手失败会查询当前会话;401/404 停止恢复,其余临时失败继续到预算结束。显式关闭、注销、服务端关闭均取消恢复。注销从 File 菜单进入。
本轮检查结果、只读 CI 和真实 Docker/OpenSSH/Playwright 验收命令见 Web Beta 合并准备。Docker/OpenSSH 验收需要 Docker CLI;本机还可运行下述独立 Chromium 验收,但它不代替容器和反向代理验收。
密码备份与文件导入
File 菜单的导出在浏览器中设置并确认本次备份密码,下载密码保护的 .nya 文件;导入选择文件、输入该文件的原备份密码,并确认覆盖实例配置。取消选择不会报错,执行期间不能重复提交。备份密码与 Web 登录密码、部署加密密钥相互独立,仅在本次请求中使用。
.nya 延续桌面已有格式和旧版解码能力。Web 导出将凭据包装到独立的备份密钥中;Web 导入解开源密钥,把凭据重新加密到目标实例。可以在使用不同部署密钥的 Web 实例之间迁移,不需要复制或替换目标 NYATERM_WEB_ENCRYPTION_KEY。桌面导出的备份密码是导出时的桌面主密码;当前桌面导入入口也使用桌面主密码解密文件,因此导入 Web 备份前需将桌面主密码设为该备份密码。
导入先校验密码、完整性、数据关系和凭据,再用单个 redb 事务恢复;失败保留原配置。备份中的 Serial、本地 Shell 等桌面配置会保留,Web 能力检查控制使用。导入成功后刷新连接、设置、快捷命令、历史和笔记。运行中的会话仍使用已建立的连接。
桌面备份中的旧连接内联明文密码会在导入时转换为目标实例的加密凭据,空字符串按未保存密码处理。符合 AES-GCM 密文格式的字段仍必须通过解密校验,不会在解密失败后当作明文保存;私钥、托管账号、OTP 等其他凭据继续严格校验。
连接导入支持 Xshell、MobaXterm、SecureCRT、WindTerm、Electerm、NyaTerm JSON 的单文件导出。服务端只解析上传内容,不读取上传内容指定的服务器路径。WindTerm 如果依赖外部配置或私钥,需先在桌面完整导入再迁移 .nya;FinalShell 目录、Termius 本机数据库和自动扫描 ~/.ssh/config 仅桌面支持。Xshell、MobaXterm、SecureCRT 的导出通常不包含密码,导入后会提示补充凭据。主题、快捷命令、关键词规则和私钥的可见文件入口使用浏览器选择与下载。
| API | 正文与结果 | 上限 |
|---|---|---|
POST api/backups/export |
JSON { "password": "…" };返回 .nya 二进制 |
输出 50 MiB |
POST api/backups/import |
multipart file、password;成功返回 status=completed |
文件 50 MiB,解压 50 MiB |
POST api/imports/connections |
multipart file、source;返回 imported、warnings |
文件 10 MiB,归档展开 50 MiB |
上述接口要求当前登录、正确的 Origin 和 CSRF;source 只接受 xshell、mobaxterm、securecrt、windterm、electerm、nyaterm_json,不接受服务器文件路径。
部署日志与诊断
服务默认输出 JSON 日志到 stdout,可用 docker logs --tail 200 nyaterm-web 或 docker compose -f deploy/web/docker-compose.yml logs --tail 200 nyaterm 查看。同时写入数据目录的 logs/ 子目录。日志按 UTC 日期或 10 MiB 轮转,总量不超过 100 MiB;诊断设置中的级别和保留天数保存后立即生效,保留天数为 1–30 天。文件写入失败时继续 stdout 日志并保持业务运行,正常停机刷新日志。
Compose 使用 Docker json-file 日志驱动,单文件 10 MiB、最多 3 个。单独 docker run 时也可添加 --log-opt max-size=10m --log-opt max-file=3。Docker stdout 日志与应用数据目录日志分别轮转。
每个 API 响应包含 X-Nyaterm-Request-Id;JSON 错误也带 request_id。前端 invoke 的请求 ID 传入服务端,日志记录路由模板、操作、状态码、耗时和相关会话 ID。异步连接任务延续请求关联。底层错误先记录类别、错误码、固定原因及原始错误指纹,再返回通用错误。终端帧和命令正文不会逐条记录。
浏览器未登录时仅保留有界内存队列,登录后批量上传现有 logger、未捕获异常和 Promise rejection。每批最多 50 条、256 KiB,服务端每个登录每分钟最多接受 120 批;断网上报最多重试两次(1 秒、2 秒退避),丢弃数量合并到后续批次,不递归记录上报失败。POST api/logs/frontend 和 GET api/diagnostics/export 要求登录;POST 同时校验 Origin 与 CSRF。
在 Web 设置的诊断区下载 ZIP 包,包含最多 20 MiB 的近期脱敏日志,以及版本、系统、架构和能力清单。任意错误文本和不可信文本以指纹记录;密码、Cookie、CSRF、私钥、命令正文、终端内容不会写入诊断包,数据库和备份也不会收录。Web 隐藏打开本地日志目录入口。本次不提供页面日志查看器。
本机浏览器验收
安装 Chromium 后,以本机编译的 Web 服务和真实 dist 执行登录、主题/窄屏截图、备份往返、诊断下载及会话恢复:
pnpm exec playwright install chromium
pnpm build:web
cargo build --manifest-path src-tauri/crates/nyaterm-web/Cargo.toml --locked
node scripts/web-local-e2e.mjs
子路径验收先用 NYATERM_WEB_BASE_PATH=/nyaterm/ 重新构建前端,再设置 NYATERM_LOCAL_E2E_BASE_PATH=/nyaterm/ 执行同一脚本。测试使用临时数据目录和随机秘密,退出后清理服务进程。截图保存在 artifacts/web-local-e2e/。
本轮本机验证(2026-10-04):Core 413 项测试通过;Web Rust 单元及集成测试通过;相关 Vitest、桌面 cargo check --locked、Web 根路径/子路径构建、lint、四语言键检查和能力审计通过。lint 保留 CommandSuggestions.tsx 原有的一项依赖提示,构建保留原有资源体积提示。
真实 Chromium 已分别通过根路径和 /nyaterm/ 的登录错误、限流/断网/SSE 故障注入、会话恢复、密码备份往返、主题与快捷命令文件往返、诊断下载;已检查四语言、浅深主题及 360px 截图。迁移测试使用真实 Web 进程和独立部署密钥,验证密码、私钥和 OTP 解密、损坏/错误密码/超限拒绝、事务回滚及重启后读取,并检查日志脱敏、请求关联、上报频率和级别即时生效。
本机没有 Docker CLI,因此没有执行镜像构建、容器内轮转或 Docker/OpenSSH/反向代理验收;使用 pnpm test:web:e2e 在具备 Docker 的环境继续验证。桌面 .nya 兼容验证使用共享编解码和原桌面快照流程,未执行桌面 UI 导入验收。