zhaimingyou
aisync
aisync多电脑统一入口: SSH 反向隧道 + nginx + Authelia 把多台电脑的 DeepSeek Harness Web GUI 聚合到一个域名下(含 ECS 控制面/机器侧 agent/一键部署脚本)
- Stars
- 1
- Language
- Shell
- Created
- Aug 17, 2026
- Updated
- Aug 17, 2026
Introduction
aisync 多电脑统一入口
一个入口(选择器页)→ 每台电脑一个子域名 → 服务器上的 SSH 隧道反代到各电脑的 DeepSeek Harness(DSH)Web GUI。
本文档以作者自己的部署为例(域名 aisync.club、ECS 阿里云)。换成你自己的域名/服务器见下方「部署到自己的域名/服务器」——仓库已参数化,占位符见「部署前替换清单」。
架构
📱 手机/任意设备
│ https://aisync.club ← 电脑选择页 + 控制面接口 (/register /heartbeat)
│
├── https://mac.aisync.club → ECS 127.0.0.1:10080 → MacBook DSH :3080
├── https://dgx.aisync.club → ECS 127.0.0.1:10081 → dgxspark DSH :3080
└── https://pc.aisync.club → ECS 127.0.0.1:10082 → 高性能PC DSH :3080
每台电脑 ──心跳 POST /heartbeat (60s)──▶ ECS aisyncd ──▶ machines.json ──▶ 选择器页
──SSH 反向隧道 ──────────────▶ ECS 回环端口 ──▶ nginx map 反代
- DNS:泛域名
*.aisync.clubA 记录 →REPLACE_ECS_PUBLIC_IP(加新电脑/改名不用再动 DNS) - 证书:泛域名证书
*.aisync.club+aisync.club(certbot + 阿里云 DNS-01) - 认证:Authelia,cookie 域
.aisync.club,一次登录三台通用 - 隧道:每台电脑 autossh/ssh 反向隧道,连 ECS 的回环端口;ECS 无需开放任何新公网端口
- 控制面:ECS 上的
aisyncd(Python 标准库守护进程,仅回环监听),负责机器注册、心跳聚合、nginx map 渲染 - 昵称同步:ECS 上的
labels-api(仅回环监听,受 Authelia 保护),✏️ 昵称按账号存储、跨设备生效 - 仪表盘:首页除选机器卡片外,还显示每台机器的 DSH 活动连接数(机器侧统计 TCP 到 DSH 端口)和 24 小时连接数;下方"近期连接"表展示最近连接(时间/机器/用户/来源 IP/方式),数据来自 nginx 机器子域名 JSON access log → aisyncd 聚合
控制面(aisyncd)与机器记录
机器清单的唯一数据源是 ECS 上的 /etc/aisync/machines/*.json(每台机器一份记录:
name / label / icon / port / heartbeat_token / last_seen / last_hb)。以下产物全部由 aisyncd 从记录派生:
| 产物 | 路径 | 说明 |
|---|---|---|
| nginx map 反代 | /etc/nginx/conf.d/machines.conf | 按记录渲染(map 版,见 ecs/nginx-machines.conf) |
| 选择器数据 | /var/www/aisync-selector/machines.json | 分层健康:隧道 → 心跳 → DSH + 活动连接数 |
| 连接日志 | /var/www/aisync-selector/connections.json | 仪表盘近期连接聚合(来源:nginx 机器子域名 JSON access log) |
| 兼容数据 | /var/www/aisync-selector/status.json | 旧格式(name/port/icon/label/online) |
接口(HTTPS,经 nginx 反代到回环,token 鉴权,带限流):
POST /register:机器用一次性 bootstrap token(/etc/aisync/register.token)注册 → 自动分配端口池 10080-10089 空闲端口 → 生成机器记录与心跳 token → 渲染 nginx map →nginx -t+ reload(失败自动回滚)POST /heartbeat:机器用各自心跳 token 上报 load/内存/GPU/DSH 存活/活动连接数,服务器时间戳记为last_seenGET /machines.json/GET /connections.json:首页仪表盘数据(选择器 + 近期连接日志),受 Authelia 保护,由 nginx 以静态文件服务
部署前替换清单(仓库已参数化)
本项目仓库已把个人化信息参数化为占位符,部署到你的环境前全局替换:
| 占位符 | 含义 | 出现在 |
|---|---|---|
REPLACE.DOMAIN | 你的域名(推荐用环境变量 AISYNC_DOMAIN 注入,aisyncd-install.sh 与机器侧 install 脚本会自动替换;也可以全局 sed) | nginx 配置、Authelia 配置、证书/DNS 脚本、--trusted-host、各 install 脚本 |
REPLACE_ECS_PUBLIC_IP | 服务器公网 IP(推荐环境变量 ECS_PUBLIC_IP) | ecs/dns-setup.sh、ecs/run-remote-diagnose.sh、各机器的隧道 service/plist |
REPLACE_USER | 各机器的登录用户名(机器侧 install 脚本会自动用 whoami 替换) | mac 的 launchd plist(文件名与 Label)、dgx/pc 的 systemd service |
REPLACE_AK_ID / REPLACE_AK_SECRET | 阿里云 DNS 权限的 AccessKey(仅 acme-wildcard.sh/DNS 脚本需要) | ecs/acme-wildcard.sh |
ecs/pubkeys/*.pub | 各机器隧道公钥(换成你自己的公钥后再跑 harden-authorized-keys.sh) | pubkeys 目录 |
aisync_ecs / id_ed25519 | 各机器 SSH 私钥文件名 | 隧道 service/plist 的 -i 参数 |
替换方式:grep -rn 'REPLACE' . 找到全部位置;推荐把 export AISYNC_DOMAIN=<你的域名> 与 export ECS_PUBLIC_IP=<服务器IP> 写进你的 shell 配置,各 install 脚本运行时自动替换,无需改文件。
开源
- 协议:MIT(见
LICENSE),可自由使用、修改、商用,保留版权声明即可 - 仓库内容都是部署代码与配置模板,不含任何真实密钥:token 一律运行时生成(
/etc/aisync/register.token、机器记录、Authelia 密钥走 systemd 环境注入),占位符见上表 - 贡献欢迎以 PR 形式提交;涉及
aisyncd.py的 NGINX_TEMPLATE 改动请同步ecs/nginx-machines.conf(两者需保持同构,见下方关键约束)
部署到自己的域名/服务器(完整流程)
不需要阿里云也能跑:核心(aisyncd.py 纯标准库 Python + nginx + Authelia + 机器侧 agent)任何 Linux 服务器可用。只有三个便利脚本绑定阿里云(均可自行替换):dns-setup.sh(阿里云 DNS)、acme-wildcard.sh(阿里云 DNS-01 签证书,acme.sh 支持几十家 DNS 服务商)、run-remote-diagnose.sh(云助手,可选)。
export AISYNC_DOMAIN=你的域名 # 例: example.com
export ECS_PUBLIC_IP=你的服务器公网IP # 例: 1.2.3.4
- 域名 + DNS:买域名,加泛域名 A 记录
*.<域名>→ 服务器公网 IP(阿里云域名可跑ecs/dns-setup.sh,其他注册商在面板加)。 - 证书:服务器上装 acme.sh,跑
ecs/acme-wildcard.sh(改REPLACE_AK_ID/SECRET为你的 DNS AccessKey,或换其他 DNS API);证书落盘到/etc/nginx/ssl/<域名>/。 - 控制面:仓库拷到服务器,
cd ecs && ./aisyncd-install.sh—— 脚本用AISYNC_DOMAIN自动替换 nginx/Authelia/systemd 里的REPLACE.DOMAIN,生成 bootstrap token,渲染 machines.conf,nginx -t失败自动回滚。 - Authelia 用户:
/etc/authelia/users.yml建你的账号(argon2 哈希),跑./authelia-secrets.sh注入密钥并重启 authelia。 - 每台电脑接入:
machines/common/aisync-agent.sh register <名字> <昵称> <图标>(交互输入 bootstrap token)→install装心跳;DSH 启动加--trusted-host <名字>.<域名>(systemd 模板dsh-web.service里的REPLACE.DOMAIN由机器侧 install 脚本自动替换)。 - 公钥硬化:各机器公钥追加进
/root/.ssh/authorized_keys后跑./harden-authorized-keys.sh --apply(每把钥匙只允许开自己的隧道端口)。 - 验证:
curl -s http://127.0.0.1:8787/healthz→ ok;浏览器开https://<域名>登录后卡片显示"在线 · DSH 运行中"。 - 选择器页自动从当前域名推断子域名(
location.hostname取后两段),无需任何配置。
机器接入(新电脑,全程一条命令)
- ECS 已部署 aisyncd(见下方"部署与迁移"),管理员读出 bootstrap token:
cat /etc/aisync/register.token - 新电脑装 autossh、生成 SSH 密钥(
aisync-agent.sh register自动完成),执行:machines/common/aisync-agent.sh register <name> <label> <icon> # 交互输入 bootstrap token machines/common/aisync-agent.sh install # 装心跳定时器(systemd/launchd) - ECS 上:把公钥追加到
/root/.ssh/authorized_keys后跑./harden-authorized-keys.sh --apply(自动给每台机器的钥匙加restrict,port-forwarding,permitlisten="127.0.0.1:<端口>"前缀,只能开自己的隧道端口) - 那台电脑的 DSH 启动参数加
--trusted-host <名字>.aisync.club
注册成功后 selector 页自动出现新卡片;心跳上线后显示"在线 · DSH 运行中 + 负载/GPU"。
给电脑改名
两种名字,成本不同:
- 显示名(卡片上的标题):改 ECS 记录
/etc/aisync/machines/<name>.json的label后systemctl kill -s HUP aisyncd,最迟 30 秒全端生效;或直接在选择器页点卡片上的 ✏️ 改昵称 —— 部署 labels-api 后按账号同步、所有设备通用(未部署时退化为保存在当前浏览器 localStorage),清空回车恢复默认。 两者都不用碰 DNS / 证书 / nginx / 隧道 / trusted-host。 - 子域名(mac/dgx/pc):泛域名 DNS + 泛域名证书依然不用动,改两处:
- ECS:
mv /etc/aisync/machines/<旧名>.json <新名>.json(改文件内name),systemctl kill -s HUP aisyncd(nginx map 自动重渲染 + reload 需手动systemctl reload nginx) - 那台电脑:DSH 启动参数
--trusted-host换成新名字并重启 DSH(隧道不用动,只关心端口,与域名无关)
- ECS:
部署与迁移(ECS 上,root)
cd ecs
./aisyncd-install.sh # 幂等: 安装 aisyncd + labels-api + legacy 记录 + nginx 收口 + 停旧探针
安装脚本会:备份现有 nginx 配置到 /etc/nginx/conf.d/backup-aisyncd-<时间戳>/;
把现有三台机器转成 legacy 记录(保留原端口,打印每台的心跳 token);
nginx -t 失败自动回滚。labels-api 单独部署(可选,昵称跨设备同步依赖它):
install -m 755 labels-api.py /usr/local/bin/aisync-labels-api.py
install -m 644 labels-api.service /etc/systemd/system/aisync-labels-api.service
systemctl daemon-reload && systemctl enable --now aisync-labels-api
# apex 配置已含 /api/ 反代块; 若已部署过需重装该 conf:
# install -m 644 aisync-club-final.conf /etc/nginx/conf.d/aisync.conf && nginx -t && systemctl reload nginx
# 校验(未带用户头应 401): curl -s http://127.0.0.1:8645/api/mylabels
安全加固(部署后建议立即执行)
./harden-authorized-keys.sh # 预览将写入的硬化行
./harden-authorized-keys.sh --apply # 自动备份并改写 authorized_keys
./authelia-secrets.sh # 生成密钥 → systemd 注入 → 重启 authelia(重新登录一次)
每台老电脑认领一次心跳(token 见安装输出或 ./aisync-token-show.sh <name>):
# 各机器上 (dgx: <dgx用户> / mac: <mac用户> / pc: WSL2 内)
sudo cp machines/common/aisync-agent.sh /usr/local/bin/ && sudo chmod 755 /usr/local/bin/aisync-agent.sh
aisync-agent.sh adopt <name> <心跳token>
aisync-agent.sh install # 装心跳定时器; pc 见 machines/pc-windows/README.md
回滚:恢复备份目录里的 nginx 文件 + systemctl disable --now aisyncd aisync-labels-api。
旧探针(status-check)已随 art 域名一并移除。
目录结构
ecs/ ECS 侧部署文件(放到 ECS 上执行)
aisyncd.py 控制面守护进程(注册/心跳/状态聚合/nginx map 渲染, 标准库 only)
aisyncd.service systemd 单元
aisyncd-install.sh 一键部署(备份 + legacy 迁移 + 校验 + 回滚, 含 labels-api)
labels-api.py 每用户昵称同步 API(受 Authelia 保护, 仅回环监听)
labels-api.service systemd 单元
aisync-register-legacy.sh 老机器记录迁移(读 /etc/aisync-machines.conf 或传参)
aisync-token-show.sh 查看/轮换机器心跳 token
aisync-club-final.conf aisync.club apex 配置(备案主域; 选择器 + /register /heartbeat /api/ 反代 + 限流 + HSTS)
nginx-machines.conf nginx map 版反代模板(aisyncd 按记录渲染同构文件, 含 TLS 加固)
harden-authorized-keys.sh 隧道公钥加固(restrict,port-forwarding,permitlisten, 可 dry-run)
authelia-secrets.sh Authelia 密钥生成/轮换(EnvironmentFile 注入, 不落明文)
diagnose.sh 云助手/SSH 诊断脚本(排查 sshd 卡死)
run-remote-diagnose.sh 本机定位实例 → 云助手执行 diagnose.sh → 取回结果
fix-image-upload-body-size.sh DSH 贴图发送 413 修复(nginx 请求体上限 150m, 幂等+自动回滚)
acme-wildcard.sh 用 acme.sh 签发泛域名证书 (DNS-01)
selector/ 选择器页(数据驱动 + 分层健康 + ✏️ 改昵称)
machines/
common/ 机器侧通用 agent(register/adopt/heartbeat/install)+ timer 模板
mac/ Mac 本机 launchd 隧道(含 ControlMaster 加固)+ DSH 目录选择器 browse 补丁
dgxspark/ dgxspark: 隧道 + DSH 安装 + systemd
pc-windows/ Windows 高性能 PC 的 WSL2/原生方案 + 心跳计划任务
tests/
smoke.sh 本地全链路冒烟(不碰 ECS/nginx)
关键约束(踩过的坑)
- DSH 前端资源是绝对路径,必须用子域名不能用子路径
- 每台电脑的 DSH 必须带
--trusted-host <自己的子域名>,否则 Host 校验拒绝访问 - ECS 的 sshd 保持默认
GatewayPorts no:隧道绑 ECS 回环,nginx 再反代,公网不暴露 - nginx 反代必须支持 WebSocket upgrade(DSH 事件流依赖),超时调大
nginx-machines.conf(及 aisyncd 渲染版)依赖 apex 顶部的map $http_upgrade $connection_upgrade(现由aisync-club-final.conf自带,部署为aisync.conf)/register与/heartbeat是 exact location,不受 Authelia 拦截,安全性靠 token + nginx 限流; bootstrap token 泄露可增删机器,用后建议轮换(openssl rand -hex 16 > /etc/aisync/register.token+ 重启 aisyncd)/api/(labels-api)是用户侧接口,走 Authelia 认证;nginx 经auth_request_set把Remote-User透传后端,labels-api 无用户头一律 401,数据存/var/lib/aisync-labels/labels.json- 机器记录目录
/etc/aisync/权限保持 root-only(含心跳 token);心跳 token 泄露只会被冒报状态,可aisync-token-show.sh <name> rotate轮换 - TLS 加固:apex 配置均带 HSTS(
includeSubDomains)+ssl_session_cache;aisyncd 渲染的 machines.conf 与ecs/nginx-machines.conf同构,加固项需同步进aisyncd.py的 NGINX_TEMPLATE - authorized_keys 硬化依赖 OpenSSH 8.5+(
permitlisten),版本不足时脚本自动降级为command="",port-forwarding;⚠️ 实测阿里云 Linux 3 的 OpenSSH 8.0p1-29.al8 构建会 静默丢弃restrict(调试日志 key options 只剩 port-forwarding),所以脚本一律用command=""拦 exec(-N隧道不触发命令,不受影响); 执行前确保保留至少一把无限制的管理员钥匙,脚本会先备份 - Authelia 的
session.secret/storage.encryption_key不再写配置文件,由authelia-secrets.sh - nginx 机器子域名配置与 aisyncd NGINX_TEMPLATE 必须同构:任何改动(含本次新增的
log_format aisync_conn、access_log、auth_request_set $auth_user)必须同时修改ecs/nginx-machines.conf和aisyncd.py的NGINX_TEMPLATE,否则注册新机器时渲染的配置会丢失这些字段。连接日志文件/var/log/nginx/aisync-machines.log被 nginx 默认 logrotate 自动覆盖。 生成到/etc/authelia/authelia.env(600)经 systemd EnvironmentFile 注入;--rotate会使所有会话失效 - DSH 目录选择器与远端访问:mac 的 DSH 以
--host 127.0.0.1本地启动、由远端经隧道访问, darwin 平台会被自适应选择器(directory-picker-auto)误判为"操作者在本机屏幕前"而挂载 native 后端——远端浏览器点"添加工作区"时 osascript 弹窗开在 Mac 屏幕上(远端看不到),目录浏览/ 新建文件夹 API 也返回不可用。修复:把machines/mac/dsh-web.cordis.patch.yml复制为~/.dsh/cordis.patch.yml(固定 browse 交互;用户层 patch 热加载,无需重启 DSH)。 dgx/pc 走 headless Linux 自动解析为 browse,不受影响;若 pc 改在原生 Windows 跑 DSH,同样需要此 patch。 - DSH 贴图发送被 nginx 413 拒绝:机器子域名反代块没有
client_max_body_size(默认 1m), 而 DSH 图片以 base64 进session.prompt请求体(单条消息上限 100MB),超过 ~750KB 的截图 一发送就被 413。修复:ECS 上跑ecs/fix-image-upload-body-size.sh(在 location / 加client_max_body_size 150m,并同步进aisyncd.py的 NGINX_TEMPLATE,幂等、失败自动回滚)。 /register /heartbeat 的 256k 限制是有意保留的。