公网/NAS 稳定性专项扫描的产出: ① 登录限速:面板常被直接暴露公网,/api/auth/login 原先无任何限速可被无限爆破。 双键计数(IP+用户名 5 次、IP 20 次 / 15 分钟窗口),触发回 429 + 面板日志告警; 用 socket 直连地址而非可伪造的 XFF;成功登录清零。已实测:5×401 → 第 6 次 429。 ② 会话滑动续期:固定 30 天硬过期在"常年挂着"的 NAS 场景表现为"隔三差五要重登"。 现剩余不足一半自动续满(服务端),鉴权层顺手刷新 cookie maxAge(浏览器侧同步)—— 活跃用户永不掉线,闲置超时才需重登。 ③ DNS-rebind 钩子补 return reply(async 钩子已 send 后的规范终止写法)。 ④ 运维文档新增「公网访问 / 反代部署」章节:nginx 完整示例(ws 升级头 + proxy_read_timeout 3600s——默认 60s 是"桌面挂着不动 1 分钟就断线"的元凶 + client_max_body_size)、Caddy/frp/Cloudflare 注意事项、PANEL_ALLOWED_HOSTS 提醒。 ⑤ 管理页重构:7 个区块一条长滚动 → 「实例 / 用户 / 系统」三 Tab (实例=日常操作;用户=子账号+本账号;系统=残留容器+数据卷+诊断日志+关于)。 牛奶布艺分段选择器(凹槽+浮起胶囊),Tab 角标点提示待处理事项(可升级/有残留), 记住上次停留 Tab;子账号(非管理员)视图不变。
8.5 KiB
部署与运维
返回 ← README
数据持久化、常见问题排查、忘记超管密码的离线找回,以及仓库目录结构。
数据持久化
- 面板数据(用户、实例元信息、密码哈希):容器内
/data,映射到宿主./data-panel。 - 每个微信实例:独立的 docker 命名卷
woc-data-<id>,挂到该实例容器的/config(微信本体在/config/wechat,登录态与消息缓存在/config其余位置)。
要点:
- 删除实例默认保留其数据卷,下次同名重建可复用;只有显式勾选「彻底清除」才会删卷。
- 备份某实例 = 备份对应的
woc-data-<id>卷(docker volume系列命令)。 - 卷需支持执行权限(微信本体直接从卷里运行);放在
noexec卷上微信将无法启动。 - 备份面板 = 备份
./data-panel。
从旧版(单微信容器 +
./data绑定挂载)迁移:旧形态把微信数据放在宿主./data。新版用 docker 命名卷,结构不同,无自动迁移。如需保留旧会话,最简单是新建一个实例、重新扫码登录;或手动把旧./data内容拷进新实例的woc-data-<id>卷。
常见问题
| 现象 | 排查 |
|---|---|
| 新建实例失败 | 多为面板拉不到微信镜像或连不上 docker.sock。确认 docker.sock 已挂载、宿主能访问 GHCR;看面板日志 docker logs woc-panel |
| 界面/消息显示成方块 | 中文字体没装好,确认实例镜像含 fonts-noto-cjk |
| 微信起不来 / 黑屏 | 看实例日志 docker logs woc-wx-<id>;确认 seccomp=unconfined 与 shm_size 生效。微信 deb 漏声明的运行时依赖已在 Dockerfile 内置 |
| 排查缺哪个库 | docker exec woc-wx-<id> ldd /config/wechat/opt/wechat/wechat,看 not found 项补进 Dockerfile 依赖层 |
| 多人同时操作很乱 | 已内置「操作控制权」软锁:当前操作者每数秒心跳续约,其余端自动转为只读遮罩(仍可看画面),空闲超时(约 10s)自动释放,他人可点「申请控制」接管。仍建议同一时刻一人操作 |
| 过段时间掉登录 | 微信桌面会话会定期失效,需手机重新扫码(见技术方案 6.2) |
| 下载 / 更新微信失败 | 腾讯 CDN 偶发波动,重新点「下载并安装 / 更新」即可;脚本已内置主/备 CDN 自动回退 |
| 架构不支持报错 | 微信仅提供 x86_64 / arm64;其他架构下载时会在面板状态里报错 |
| 忘记超管密码 | 见下方「重置超管密码」离线找回 |
查看面板日志:docker logs -f woc-panel;查看某实例日志:docker logs -f woc-wx-<id>(实例 ID 可在面板看到,或 docker ps | grep woc-wx)。也可在面板的「管理」页或实例页点「查看日志」直接导出实例容器日志。
重置超管密码(离线找回)
管理员密码无法被他人重置,忘记时按以下步骤离线找回:
docker compose stop panel # 1) 先停面板,避免覆盖你的手动修改
- 编辑
./data-panel/accounts.json,给对应用户对象加一行"resetPassword": true:
{
"id": "...", "username": "admin", "role": "admin",
"passwordHash": "...", "disabled": false,
"resetPassword": true
}
docker compose up -d # 3) 启动,面板初始化时会重置该账号
⚠️ 重置逻辑只在面板进程启动时执行。若你没先
stop而面板仍在运行,直接docker compose up -d会因「容器无变化」而空操作(输出Running而非Started),重置不会发生。此时执行docker compose restart panel(或docker restart woc-panel)强制重启即可生效。
重启后该账号密码被重置为 PANEL_ADMIN_PASSWORD(即 .env 的 WOC_PASSWORD,默认 wechat),并自动解禁、清除该标记;用此密码登录后请立即在「修改密码」改掉。日志会打印 [store] 已重置用户 '<用户名>' 的密码。
目录结构
WechatOnCloud/
├── .github/workflows/
│ └── release.yml # 打 tag / 发 Release 时构建多架构镜像并推送 GHCR
├── docker/ # 微信实例镜像(ghcr.io/<owner>/wechat-on-cloud)
│ ├── Dockerfile # KasmVNC base + 中文字体 + 微信运行时依赖 + xdotool + IME 补丁(不打包微信本体)
│ ├── woc-www-patch.sh # 构建期补丁:开启 KasmVNC IME 模式 + 修复 noVNC 中文输入(配合 woc-ime.pl)
│ ├── woc-ime.pl # noVNC 键盘 IME 逻辑的 perl 补丁(合成提交时整串发送,规避丢字)
│ ├── wechat-ctl.sh # 运行时下载/解压/更新微信(面板经 docker exec 触发,状态写 /config/.woc-state)
│ ├── autostart # openbox 会话启动:常驻拉起微信(崩溃自重启)+ 最小化窗口自动复原看守
│ └── woc-update-autostart # 启动钩子:每次启动用镜像内最新 autostart 覆盖数据卷旧副本
├── panel/ # 自研面板(ghcr.io/<owner>/woc-panel,唯一对外入口)
│ ├── Dockerfile # 前端 Vite 打包 + 后端 Fastify 网关(多架构)
│ ├── server/ # Fastify:cookie 鉴权 + 账号/实例/权限/生命周期 API + dockerode + 反代
│ └── web/ # React + TS + PWA(微信 PC 式布局,牛奶布艺 + 微信绿主题)
├── fnos/ # 飞牛 fnOS 应用打包(.fpk 工程 + 构建说明)
├── scripts/
│ └── build-local.sh # 本地构建面板+微信镜像(发布前自测 / 自托管自构建)
├── doc/ # 文档与素材
│ ├── 技术方案.md # 完整设计文档
│ ├── 运行原理.md # 工作原理 + Docker 运行模式详解(新手向)
│ ├── 发布到GHCR.md # 镜像发布到 GHCR 的两种方式
│ ├── 部署与运维.md # 本文档:数据持久化 / 常见问题 / 密码找回 / 目录结构
│ └── img/ # logo 与界面截图
├── docker-compose.yml # 单服务:panel(挂 docker.sock,按需创建实例)
├── .env.example # 可选配置(账号密码、镜像版本、PUID/PGID、端口、时区)
└── README.md
数据:面板账号(含密码哈希)在 ./data-panel,各微信实例在 docker 命名卷 woc-data-<id>;./data-panel 已在 .gitignore 中。
公网访问 / 反代部署(nginx 示例)
把面板暴露到公网(域名 + HTTPS)时,有三件事必须做对,否则表现为「登录 400 / 桌面黑屏 / 挂一会儿就断线」:
- 把对外域名加进
PANEL_ALLOWED_HOSTS(.env,逗号分隔,支持*.example.com),然后docker compose up -d重建(不是 restart)。这是防 DNS-rebinding 的白名单,不加会被面板以 400 拒绝。 - 反代必须透传 WebSocket(远程桌面全靠它)。
- 把空闲超时调大。nginx 默认
proxy_read_timeout 60s——桌面静止不动 1 分钟没有数据流过,nginx 就掐断连接,表现为「挂着不动就断线重连」。
server {
listen 443 ssl;
server_name woc.example.com;
# ssl_certificate / ssl_certificate_key ...
location / {
proxy_pass http://<NAS 内网 IP>:36080;
proxy_http_version 1.1;
# WebSocket 升级(远程桌面/音频都走 ws)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 透传真实 Host(与 PANEL_ALLOWED_HOSTS 对得上)
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 关键:空闲超时调大,否则桌面静止 60s 即断(nginx 默认值)
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
# 大文件上传(传文件到桌面 / 卷导入)
client_max_body_size 3g;
}
}
其它反代同理:Caddy 默认无空闲超时(开箱即用);frp 建议 transport.heartbeatInterval 保持默认并确认 proxy_read_timeout 类配置未收紧;Cloudflare 免费版对 WebSocket 空闲约 100s 断开,桌面场景建议用「灰云」(仅 DNS)或自建隧道。
安全提示:面板已内建登录限速(15 分钟窗口内同源失败 5 次即暂时锁定),但公网暴露仍建议:强密码、改默认端口、优先用 VPN/内网穿透而非直接开放。