From 323d494615a8c4d4b540905539f8f71d6def1eab Mon Sep 17 00:00:00 2001 From: HomeDash Bot Date: Sat, 12 Sep 2026 08:54:33 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E8=A1=A5=E9=BD=90=E4=B8=80=E4=BD=93?= =?UTF-8?q?=E5=8C=96=E6=A8=A1=E5=BC=8F=E6=95=99=E7=A8=8B=E3=80=81=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E6=96=87=E4=BB=B6=E6=B8=85=E5=8D=95=E4=B8=8E?= =?UTF-8?q?=E8=84=B1=E6=95=8F=E6=B8=85=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增「一体化模式」章节:单容器即统一入口(TLS + 统一登录 + 导航 + 搜索 + 历史/管理) · 端口与挂载(内部 18080 / 对外 3080、8443 / 跳转 3081;data 卷 + 证书只读卷) · host 模式部署命令与失败自动回滚;说明 zd.sh 双端口轮换在合并后失效的原因 · 切换四步、服务转发字段(含逐条统一认证与导航绑定)、现有 nginx 配置导入 · 统一登录账号(数据卷存加盐哈希,优先级与生效方式) · 深浅色/壁纸/状态点关闭的原因(含 background 简写清掉壁纸的坑) · 回滚路径(容器、配置备份、数据卷) - 「部署需要的全部自定义文件」表补充:deploy/zd-host.sh、portal/、auth.go、 gateway*.go、portal_auth.go - 新增「绝不提交的内容(脱敏清单)」:conf.yml/备份、各类 token 与口令、portal-auth.json、 证书、运行时 env 与 nginx 备份;并给出两条发布前自检命令 - 零停机部署与 portal-nginx 两节加指引,避免照旧流程操作 --- README.md | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8df2271..ecc667a 100644 --- a/README.md +++ b/README.md @@ -282,7 +282,12 @@ curl -H "Authorization: Bearer " -X DELETE -H 'Content-Type: application/ } ``` -## 零停机部署 +## 零停机部署(合并前形态) + +> 适用前提:入口是独立容器(`portal-nginx`)。 +> **合并成单容器之后请改用 `deploy/zd-host.sh`**(见下文「一体化模式」): +> 合并后入口就是 navi 容器本身,双端口轮换换的正是入口端口,不再成立。 + 不要直接 `docker stop/rename/run`:那样有十几秒完全不可用,而且新容器里 **nginx 先就绪、SearXNG 还在启动**,这期间 `/search` 会是 502(本项目实际发生过)。 @@ -330,6 +335,83 @@ PORT= CK= VPS=<对外IP> node deploy/verify-ui.js **任何运行时异常都会判失败**(promise 里抛出的错误很容易被忽略,本项目因此漏掉过 一个 `ReferenceError` 五个版本)。 +## 一体化模式:单容器同时充当统一入口(已在本项目启用) + +> 本节描述**当前生产形态**:HomeDash 一个容器同时承担「反向代理 + 统一登录 + 导航 + 搜索 + 历史/管理」, +> 原先独立的 `portal-nginx`(TLS 与按域名分流)与 `portal-auth`(登录服务)**已停用但保留**,随时可回退。 + +### 为什么合并 + +- 一份数据:**服务转发(域名→后端、是否统一认证、是否加入导航)**都在管理后台维护,不再手写 nginx +- 一个容器:TLS 终止、登录校验、导航页、搜索、历史与管理后台同进程组,运维面缩小 +- 代价(必须知情):**入口即应用容器**,因此升级镜像会有数秒中断(host 网络下端口就是入口,无法双端口轮换) + +### 端口与挂载 + +| 用途 | 端口 | 说明 | +| --- | --- | --- | +| 内部入口 | `18080` | 容器内 nginx,Dashy/搜索/历史/管理都从这里出 | +| 对外 HTTPS | `3080`、`8443` | gateway 生成,对外主入口(frp/雷池回源指向 3080) | +| HTTP→HTTPS | `3081` | 302 跳转到 `https://$host$request_uri` | +| 管理后台 | 经 `3080` 的 `/admin/` | 由同一 nginx 反代到 :3005 | +| 统一登录 | 经 `3080` 的 `/login` | 容器内 admin-server 自带(HMAC 签名会话 cookie) | + +必须挂载:`:/app/data`(conf.yml、history.json、gateway.yml、图标、壁纸、portal-auth.json)、 +`:/etc/nginx/certs:ro`(fullchain.pem / privkey.pem)。 + +### 部署(host 模式,接受数秒中断) + +```bash +NAVI_SSH_PASS='<密码>' bash deploy/zd-host.sh v50 +``` + +流程:本地构建 → 校验(navi.js 语法、两个 Go 模块 gofmt+test)→ 上传 → **停旧容器** → 起新容器(`--network host`) +→ 自检(`/healthz`、`/conf.yml`、`/admin`、受保护域名应 302、未知域名应 404、3081 应 302)→ 改名完成。 + +失败自动回滚:删掉新容器,把 `navi-prev-<时间戳>` 改回并启动(回滚时会先删掉已占用 `navi` 名字的新容器)。 +`deploy/zd.sh`(双端口轮换的零停机脚本)**仅适用于合并之前**:它换的正是入口端口本身,合并后会失效。 + +### 切换步骤(从「portal-nginx + portal-auth + navi」到单容器) + +1. 先在旁路端口(如 18443)用隔离的数据副本验证全部域名 +2. 停 `portal-nginx`,把 gateway 端口切到 3080/3081/8443(带证书) +3. 复核:受保护 302、公开 200、未知域名 404(自定义页)、HTTP 302 跳转 +4. 停 `portal-auth`,把容器改名为 `navi`,保留旧容器与配置备份作为回滚点 + +### 服务转发(管理后台) + +一条记录 = 一个域名,字段:`kind`(反代/门户静态页/登录服务)、`upstream`、`requireLogin`、 +`websocket`、`streaming`、`insecureTls`、`bypassPaths`(免认证路径)、`injectBeian`, +以及导航属性 `navEnabled/navSection/navName/navDescription/navIcon/navAutoIcon/navUrl`。 + +- **是否走统一认证**就是每条记录上的开关(认证按路由生效,不是全局) +- 勾选「加入导航」后,域名与导航项**同一份数据**:同 host 是接管而非重复,取消勾选会清理, + 无关手工项与系统「历史访问」分组不受影响 +- 「从现有 nginx 配置导入」可把旧 portal-nginx 的配置整段贴进来,解析出全部域名→后端初稿 + (含认证状态、免认证路径、SSE、备案注入、自签证书、auth/portal 类型);**解析只读,确认后才保存** + +### 统一登录账号 + +管理后台「统一登录账号」写入数据卷 `/portal-auth.json`(**只存加盐哈希,不存明文**)。 +优先级:数据卷设置 > 环境变量 `PORTAL_USERNAME/PORTAL_PASSWORD`。改完立即生效,无需重新部署; +会话 cookie 为 HMAC 签名,改账号后旧会话自动失效。 + +### 主题与显示 + +- **深浅色**:导航页、`/minimal`、`/workspace`、登录页、后台设置页均跟随系统 `prefers-color-scheme`; + 手动选择写入 `navi-theme`,三处共用。深色实现是**整套覆盖 Dashy 主题变量**(`html.navi-dark`), + 不改写 `data-theme`(那是 Dashy 的主题作用域) +- **壁纸**:深色下由服务端写入同图 + 深色遮罩(`applyWallpaperBlock`),不要用 `background` 简写覆盖, + 否则会把 `background-image` 清成 `none`(本项目踩过) +- **可达性状态点已关闭**(`statusCheck: false`):统一入口下探测会命中登录 302,结论不可信, + 因此不显示而不是显示错的 + +### 回滚 + +- 容器:`docker start portal-nginx portal-auth` + 把 `navi` 换回 bridge 版本(镜像 `homedash:ui-*` 均在) +- 配置:`portal/nginx.conf.bak-<时间戳>`;`conf.yml` 的改动另有 `conf.yml.bak-*` +- 数据:`conf.yml`、`history.json`、`gateway.yml`、`portal-auth.json` 都在数据卷,容器重建不丢 + ## 部署需要的全部自定义文件 仓库即完整部署物料,没有额外依赖仓库外的文件: @@ -348,11 +430,19 @@ PORT= CK= VPS=<对外IP> node deploy/verify-ui.js | `web/patch-index.py` | 幂等地把 `navi.css` / `navi.js` 注入 Dashy 的 index.html | | `web/extension/install.html` | 插件安装引导页(含同步令牌展示) | | `extension/chrome/` `extension/firefox/` | 浏览器扩展(MV3 / MV2) | +| `deploy/zd-host.sh` | **合并后**的部署脚本(host 模式,接受数秒中断,失败自动回滚) | +| `portal/`(index.html、404.html、500.html、portal.css、favicon) | 门户静态页与自定义错误页,进镜像的 `/app/portal` | +| `admin-server/auth.go` | 容器内置统一登录(HMAC 会话 cookie、恒定时间比较、跳转白名单) | +| `admin-server/gateway.go` / `gateway_nav.go` / `gateway_import.go` / `portal_auth.go` | 路由表生成、导航同步、现有 nginx 配置导入、登录账号设置 | | `deploy/zd.sh` | 零停机部署脚本 | | `deploy/verify-ui.js` | 部署后浏览器验收脚本 | | `docker-compose.yml` / `.env.example` | 单机 compose 启动与变量样例 | -## 生产部署:外层统一认证反向代理 +## 生产部署:外层统一认证反向代理(可选方案) + +> 本项目的当前形态已把这一层合并进单容器(见下文「一体化模式」)。 +> 本节保留为**可选方案**:当入口需要独立于应用容器(例如希望应用升级不留任何中断)时使用。 + > 内网门户通常不直接暴露 3003 端口,而是放在一个**带统一登录认证的反向代理**后面, > 所有子域名共用一个登录入口。下面是本项目参考的 portal-nginx 方案。 @@ -690,3 +780,21 @@ data/ ## 许可 MIT + +## 绝不提交的内容(脱敏清单) + +| 类别 | 具体 | 原因 | +| --- | --- | --- | +| 导航配置 | `conf.yml`、`conf.yml.bak-*` | 含内网地址与服务清单(已 `.gitignore`) | +| 凭据 | `NAVI_SYNC_TOKEN`、`NAVI_ADMIN_TOKEN`、`PORTAL_PASSWORD`、`PORTAL_SESSION_SECRET` | 只在 `.env` / 容器环境变量里 | +| 登录账号 | `/portal-auth.json` | 加盐哈希也是凭据等价物 | +| 证书 | `fullchain.pem`、`privkey.pem` | 仅以只读卷挂载,不进仓库 | +| 部署档案 | `/tmp/navi-*.env`、`portal/nginx.conf.bak-*` | 含运行时环境与内部拓扑 | + +仓库内只应出现占位符:`.env.example` 里的空 token、`conf.example.yml` 里 `sections: []`、 +文档中的 `10.0.0.10`/`*.example.com` 示例地址。发布前自检: + +```bash +git grep -nE 'NAVI_(SYNC|ADMIN)_TOKEN=.|PORTAL_PASSWORD=.+' -- . || echo '无凭据' +git ls-files | grep -E '(^|/)conf\.yml$|\.env$|portal-auth\.json$|(fullchain|privkey)\.pem$' || echo '无敏感文件' +```