Commit Graph

29 Commits

Author SHA1 Message Date
19b4238cb1 feat(deploy): zd-host.sh 支持部署时轮换管理/同步令牌
渗透报告证明旧 NAVI_ADMIN_TOKEN / NAVI_SYNC_TOKEN 已在公网泄露
(/api/sync-info 曾明文回显 syncToken)。但本脚本只从旧容器继承 env,
轮换没有落地入口。

新增 NAVI_ROTATE_ADMIN_TOKEN / NAVI_ROTATE_SYNC_TOKEN:传入后覆盖
env 文件里的旧值,只打印长度不回显内容,最短 32 字符。
2026-10-22 14:09:28 +08:00
96e771d546 feat(cert): cert-agent 支持多证书组 + 多目标推送(全量接管)
用户要求「部署时接管全部证书」。原来只签一组(self-only)。

## 多证书组(按证书而非按域名组织)
CERT_GROUPS 每行 '<组名>:<域名1,域名2,...>',例如:
  wild:*.jianfgit.xyz,jianfgit.xyz
  introduce:introduce.homeagent.jianfgit.xyz
  sdk:sdk.homeagent.jianfgit.xyz
每组独立落到 /app/data/certs/<组名>/,签发/续期/推送都以证书为单位
(同一张证书覆盖多个 SAN)。不配置时回落 gateway.yml 的 host(旧行为)。

## 多目标推送
CERT_PUSH_TARGETS 每行 '<host>:<远端cert>:<远端key>:<reload 命令>',
把证书推到其它机器(云主机 SafeLine 等)并触发其 reload。
认证走 CERT_PUSH_SSH_KEY / CERT_PUSH_SSH_PASS,都没有则免密。

## 途中修掉的 bug
① for 循环按**空格**分词,把含空格的 reload 命令("nginx -s reload")
   连同整行目标拆成 host/"-s"/"reload" 三片,产生三条无意义推送。
   改为 IFS=换行 + for,恢复 IFS。
② 凭据检查要求 CERT_DNS_API 与 CERT_DNS_CRED 同时存在,但插件名本来
   就能按凭据键名推断 —— 只需 CERT_DNS_CRED。
③ 改造时误删 LE_RELOAD_CMD / le_reload 定义(set -u 下直接
   "parameter not set" 中断)。已补回。

## 测试
沙箱 stub 覆盖:三组各自签发、通配符 SAN 正确、产出目录结构完整、
推送四步(mkdir→写cert→写key+chmod600→reload)全部执行。
真实签发此前已用 staging CA 验证通过(CN=dashy…, LE_ReloadCmd 已写入)。
2026-10-04 08:52:47 +08:00
12c07034a0 fix(cert): cert-agent 真实签发验证后修的三处问题
在测试容器里用真实凭据跑通了完整签发(staging CA),暴露三个真问题:

## ① CERT_DNS_CRED 没导出成 acme.sh 期望的变量名(致命)
脚本只把凭据解析出插件名 dns_ali,却从没导出 Ali_Key/Ali_Secret。
acme.sh 的 dns_ali.sh 读的是 $Ali_Key/$Ali_Secret,所以 DNS 步骤
报 'Error adding TXT record',而 ACME 其它步骤全部正常 ——
极易误判成"凭据无效/网络问题"。**stub 测试完全没暴露这个**,
只有真跑才看得见。现改为把 CERT_DNS_CRED 按逗号拆开逐个 export。

## ② 镜像内置 acme.sh,不再运行时下载
installer 内部从 raw.githubusercontent.com 取源码,而该域名境内直连超时
(实测直连 8.2s 失败 / 经 .60:7890 代理 0.53s 成功),
首次启动会静默失败、之后永远起不来。Dockerfile 新增 acmebuild stage
内置 acme.sh v3.1.6,运行时安装降级为兜底。

## ③ 下载重试 + 安装后校验
容器出网偶发 Connection reset by peer(同一 URL 隔几秒即 200),
加了 3 次重试与「安装后确认可执行」,避免半截状态。

## 真实测试证据(staging CA,零正式配额消耗)
TXT 写入 → Verifying → Success → Successfully removed → Cert success;
签出 CN=dashy.jianfgit.xyz(Let's Encrypt YE1,90 天);
Le_ReloadCmd 已写入 ⇒ 续期后自动 nginx -t && nginx -s reload。

测试环境已清理(含凭据文件),线上 v106 未受影响。
2026-10-04 08:29:35 +08:00
e7578aa607 feat(cert): cert-agent —— 容器内自签自续 HomeDash 自己的证书
用户决策:scope=self-only,strategy=full-acme(容器内跑完整 acme.sh)。

## 动机
此前 HomeDash 的证书由外部链路提供(.106 acme.sh → .60 → 云主机),
而容器是**只读**挂载 /etc/nginx/certs(RW=false),续期后没人告诉它的
nginx 重新握手。本 agent 让 HomeDash 自己闭环:签发 → 写数据卷 → reload。

## 实现
- deploy/cert-agent.sh:supervisord 新增 [program:cert-agent]
  - 域名从 /app/data/gateway.yml 的 host 提取(或 CERT_DOMAINS 显式指定)
  - acme.sh 装进**数据卷** /app/data/acme(容器重建不丢续期状态)
  - install-cert 到 /app/data/certs/,--reloadcmd 设为 nginx -t && nginx -s reload
  - 续期后比对公钥指纹,变了才 reload(不盲目 reload)
  - 未配置 CERT_DNS_CRED 时安静退出,行为与现在完全一致
- 证书目录挂载去掉 :ro(自签需要写;此前只读是刻意的保护)
- zd-host.sh 的 env 白名单加 CERT_ 前缀,否则升级时凭据传不进新容器
- install-host.sh 同步支持 CERT_DNS_CRED,避免两套脚本行为不一致
- 证书路径改为数据卷 /app/data/certs/(gateway.yml 指向它)

## 测试(沙箱 stub,非真实签发)
- 无凭据 → 安静退出、exit 0、不调 acme、不报错
- 有凭据 → 正确从 gateway.yml 提取 2 个域名、识别 dns_ali、
  签发参数正确、指纹变化后触发 reload

## 途中修掉自己的两个真 bug
① run_acme 里用 `set -- $DOMAINS` 改函数位置参数,dash 下与后续变量
   展开冲突
② LE_RELOAD 是命令串,直接当命令名执行 → "LE_RELOAD: not found",
   容器里 reload 会**静默失效**;改为函数 + eval

## 边界
只管 HomeDash 自己;introduce/sdk.homeagent 等仍走外部链路。
凭据只从环境变量读,不落盘不入库。
2026-10-04 08:02:41 +08:00
7ebebbc3a9 fix(icon): 补 /icons/favicon.ico —— 标签栏无图标的真因
上一版只改 index.html 注入 /favicon.svg,实测仍不显示。用 MutationObserver
在真实浏览器里观察到:**Dashy 前端 JS 在运行时把 <link rel=icon> 改写成
/icons/favicon.ico**,服务端注入的标签被它覆盖;而镜像里从来不存在
/icons/ 目录 ⇒ 请求落到 try_files 兜底的 index.html,content-type 是
text/html,浏览器当无效图标忽略(于是表现为「图标消失」)。

静态改 index.html 治不了本,只能把这个路径补上:
Dockerfile 与 zd-host.sh 都 COPY favicon.{ico,svg} 到 /app/dashy/icons/。
已先在 106 上热修(只 cp 不重启,无闪断)恢复显示。

实测:/icons/favicon.ico → 200 image/x-icon,浏览器解码 16x16 成功;
apple-touch-icon 180x180 正常。
2026-10-03 08:43:35 +08:00
e9097e5893 feat(icon): 纸飞机图标(站点 favicon + 扩展各尺寸)
站点首页此前显示的是 Dashy 上游的 logo —— index.html 里那几条
<link rel=icon href=/web-icons/favicon-*.png> 优先级高于我们的门户图标,
而扩展的 PNG 是占位图(128px 只有 9 种颜色、alpha 通道还是乱的)。

- portal/favicon.svg:纸飞机(圆角渐变底 + 白色折纸机头朝右上 + 两道尾迹),
  几何收在 8..56 安全区,避免贴边被圆角裁切
- 由该 SVG 派生 favicon.ico(16/32/48)、apple-touch-icon(180)、
  web-icons/favicon-{16,32,64}、扩展 icon{16,48,96,128}
- patch-index.py:删掉上游 icon link 并显式指向 /favicon.svg(幂等,
  重复执行 unchanged);上游 web-icons 那批也覆盖一份作双保险
- Dockerfile 与 zd-host.sh 都 COPY 这些图标进镜像
- 验证:nginx 沙箱实测 /favicon.svg、/web-icons/*、/apple-touch-icon.png 均 200;
  16px 下人眼仍可辨识为纸飞机
- .amo-upload-uuid(web-ext 生成的 UUID)改为忽略
2026-10-03 08:37:54 +08:00
c84461d828 fix(deploy): zd-host 增量构建重打扩展包(此前继承基镜像 3 周前的旧 zip/xpi)
实测踩坑:增量 Dockerfile 只 COPY install.html,navi-chrome.zip/navi-firefox.xpi
全部来自基镜像 jianf-navi:latest。v99 升级后容器里的 chrome 包仍是旧版
(e6b8f9bf ≠ 仓库 51347771),扩展的修复永远上不了线。
现从当前源码重打两个包并 COPY 进镜像。
2026-10-02 11:53:20 +08:00
270445a516 feat(navi): Enter 唤起搜索框 + Space 激活焦点卡片;监听器幂等守卫
- bindSearchKeys:Enter 展开搜索并聚焦(搜索框已展开/焦点在可编辑元素时放行),
  Space 激活焦点所在的 a[href]/[role=button](preventDefault 防滚动)
- bindSearchKeys/enablePeekGuard/mountViewSwitch 加幂等守卫:
  DOM 抖动场景下 HEAD 版泄漏 914 个 keydown 监听器,修复后 0
- MutationObserver 自触发加 selfWrite 计数 + 100ms 节流
- showWorkspaceBlockedModal 的 Escape 从 document 改挂 overlay
- 验收脚本:verify-search-key.js(真实按键,页面内计数断言)、
  verify-swipe.js(CDP 注入真实触摸,断言分组真的切换)
- README/AGENTS:快捷键表、手势验收方法、改样式前先确认设计基准(§9)
2026-10-02 11:24:55 +08:00
700775866a fix(deploy): 修掉远端引号嵌套导致切换中途崩溃 + 补最终名字断言
真实事故:探测域名提取用了 `tr -d '"'"'"'` 这种引号嵌套,远端 bash 报
"unexpected EOF while looking for matching `'`",脚本在"停旧容器→起新容器→
改名"的改名之前中断,容器留在 navi-next(服务正常但名字不对,
且验收里 docker inspect navi 全为空,看起来像数据丢失)。

修复:
- 改用无引号的字符类提取域名(grep -oE '[A-Za-z0-9][A-Za-z0-9.-]*\.[A-Za-z]{2,}'),
  彻底避开引号嵌套
- 收尾加断言:docker inspect navi 必须成功;若发现仍在 navi-next,
  明确提示可直接改名,而不是静默"成功"

现场已手动补齐:navi-next 完整核验(healthz/conf.yml/admin/schema 200、
入口 302/404/200/200/200、22 条路由、端口 3080/3081、门户标题与备案号完整、
env COOKIE_DOMAIN=1 明文 PASSWORD=0、数据卷凭据文件在)后改名为 navi,
回滚点 navi-prev-20260912104324(ui-v53) 保留。
2026-09-12 10:46:09 +08:00
b850a478fe fix(deploy): 入口自检改为域名无关(脱敏把探测域名换成了占位符导致误判失败)
真实部署失败原因:脱敏时把仓库里所有 example.com 换成 example.com,连
deploy/zd-host.sh 的三个入口自检探测域名也一起换了 → 自检去探测
dashy.example.com(用户路由表里不存在)→ 404 → 判定失败并回滚。

修复:
- 探测域名默认取数据卷 gateway.yml 路由表第一条的 host(或 NAVI_CHECK_HOST 显式指定),
  不再把任何具体域名写进脚本
- 已配置域名接受 200/302(门户 200、受保护 302 都算正常);
  000(未监听)/404(不在路由表)/5xx 才算失败;未知域名仍必须 404;3081 必须 302
- apply 后先轮询等待 reload 就绪再探测(异步 reload 会误判)

另修复上一轮引入的隐患:等待循环引用了脚本里不存在的 $CHECK_HOST/$ENTRY_PORT,
set -u 下会直接崩(与第一轮 HOST 未定义同类错误),本次一并纠正并改为先定义后使用。
2026-09-12 10:43:13 +08:00
dbdfe0faef fix(deploy): 默认不改门户口令 + apply 后等待监听就绪
两个问题,都由本轮真实部署暴露:

1) 口令轮换不该默认执行
   集成明文 env 清理时顺手加了"重置为强随机口令",但重置会让所有受保护域名
   (含用户访问 agent 界面的入口)立即要求新口令,用户若没读到日志就等于被锁在门外。
   而生效凭据本是数据卷里的加盐哈希(/app/data/portal-auth.json),删掉明文 env
   对登录毫无影响。→ 改为仅清理 env,轮换必须显式 NAVI_ROTATE_PORTAL_PASSWORD=1。

2) apply 触发的 nginx reload 是异步的
   本次部署因此误判失败:受保护域名探测到 404(旧配置仍在生效),脚本判定
   "应 302 实际 404" 并回滚。→ apply 之后、入口自检之前加入轮询等待就绪(最多 10s),
   与 README「接口注意事项」第 4 条同一原因。
2026-09-12 10:42:44 +08:00
2eeaabb200 fix(deploy): NAVI_COOKIE_DOMAIN/NAVI_HOME_URL 未转发到远端,升级被自家守卫拦下
现象:本地已传 NAVI_COOKIE_DOMAIN=example.com,部署仍在远端报
"缺少 PORTAL_COOKIE_DOMAIN ... "(守卫在远端执行,而远端 heredoc 是引号形式,
不会展开本地变量)→ 升级被拦,生产保持 ui-v53 未变。

修复:在 ssh 命令行显式转发这两个变量(本地展开后作为远端环境变量传入)。
实测:远端能读到传入值。
2026-09-12 10:39:59 +08:00
ff4af5fa31 fix(deploy): 必填校验插在 HOST 赋值之前,set -u 下直接崩
现象:NAVI_HOST=... bash deploy/zd-host.sh v54 直接报
line 21: HOST: unbound variable 并退出(生产未受影响,纯脚本缺陷)。

原因:上一轮加 NAVI_HOST 必填校验时,把校验插到了 STAMP 之后、HOST 赋值之前,
而脚本是 set -u,提前引用未赋值变量即中止。

修复:校验移到 HOST 赋值之后;zd-host.sh 与 zd.sh 都改。
自测:不给 NAVI_HOST → 友好提示 + 退出码 2;给了则继续执行(实测进入构建阶段)。
2026-09-12 10:38:17 +08:00
7c2bed2f2d feat: 门户页卡片可配置 + 升级前域名校验 + 清理明文门户口令
按用户选择实施三件事:

1) 门户页服务卡片可配置(不再写死真实域名)
   - portal/index.html 卡片区加 <!--NAVI_CARDS_START/END--> 标记,内置卡片改为通用示例
   - renderPortalPage 支持数据卷 <data>/portal/cards.html 覆盖标记区间(清空即回落内置)
   - 新增 GET/POST /api/portal-cards 读写该片段
   - 测试:覆盖替换成功、空白片段回落内置
   - 仓库内真实域名由此清零

2) 升级前必须提供跨子域单点登录变量(防止"去掉写死默认值"造成 SSO 静默失效)
   - zd-host.sh 在切换前检查 PORTAL_COOKIE_DOMAIN:缺失且未传 NAVI_COOKIE_DOMAIN
     时直接拒绝升级(退出码 2)并给出正确命令;PORTAL_HOME_URL 可用 NAVI_HOME_URL 补
   - README 新增「升级已有部署前必须确认的两个变量」含失败表现与正确命令

3) 清理明文门户口令
   - 迁移旧容器 env 时丢弃 PORTAL_PASSWORD(docker inspect 可见 = 泄露面)
   - 升级后写入强随机口令并通过接口生效,部署日志只打印一次;失败不阻断升级并提示后台可改
2026-09-12 10:14:22 +08:00
b99319b38b chore(sanitize): 备案号与域名一律可配置,仓库不再预置真实域名/备案号/内网地址
用户要求:备案号与域名都应当可配置,仓库内容要脱敏。

功能(不只是替换字面量):
- 被代理页面注入的备案号改为读后台「站点信息」的 icp/gongan,**未配置就不注入**;
  原先写死具体备案号,等于每个部署者都会把别人的备案信息印到自己页面上
- 注入内容做 nginx 安全处理(去引号/反斜杠/换行/美元符),并有测试覆盖
- auth.go:PORTAL_COOKIE_DOMAIN / PORTAL_HOME_URL 不再预置域名,
  cookie 默认 host-only、首页默认相对路径 "/"
- gateway.go:默认 loginUrl 不再预置域名(受保护路由缺配置会明确报错,不静默生成跳不出去的登录页)
- 首装脚本在给了 NAVI_DOMAIN 时自动注入 PORTAL_COOKIE_DOMAIN/PORTAL_HOME_URL,
  避免"去掉写死默认值"导致跨子域单点登录失效

脱敏:内网 IP(10.0.0.10)与真实备案号清零;真实域名仅剩门户页内容 8 处(待定);
测试夹具/示例/错误页/脚本/后台占位提示全部通用化。

README:新增「跨子域单点登录需要的两个环境变量」;环境变量表改为 NAVI_HOST 必填。
测试:新增备案号注入三条(用配置、未配置不注入、危险字符清理)。
2026-09-12 10:10:52 +08:00
b01e49de49 fix(gateway): http→https 跳转是否带端口改为显式开关(避免生产回归)
练兵发现:跳转写成 return 302 https://$host$request_uri(不带端口),
非 443 端口的新装会跳到 443 打不开。

但直接用'端口 != 443 就带端口'推导会把生产弄坏:生产容器监听 3080,
对外经 frp 映射为 443,带端口会把公网用户导向 https://host:3080。

因此新增 gateway.yml 字段 redirectWithPort(默认 false,行为不变):
- 默认:跳 https://host(前置 frp/443 的部署,含现生产)
- 全新安装且对外端口=入口端口:install-host.sh 自动置 true
测试补两条:开关为真时带端口、默认(生产形态)不带端口。
2026-09-12 09:52:20 +08:00
9612352bd1 fix(deploy): 首装 gateway.yml 字段名大小写错误导致 TLS 被静默忽略
TLS 练兵抓到(脚本自检 + 独立核对全挂,端口在监听但握手失败):
生成的配置里三个 listen 13080 都没有 ssl、且没有 ssl_certificate,
说明 ListenHTTPS 一直是 false。

根因:GatewayConfig 的 yaml 标签是 listenHttps / listenTlsPort / authUrl /
loginUrl(大小写敏感),而脚本写的是 listenHTTPS / listenTLSPort / authURL /
loginURL → 被 YAML 静默忽略。后果严重:HTTPS 端口实际是明文,而跳转端口
又 302 到该端口,浏览器必然报错;authUrl/loginUrl 丢失还会让受保护路由
报'未配置认证地址'。

修复:字段名对齐 + apply 后断言 TLS=1 时配置必须含 ssl_certificate(失败即停),
避免同类字段名错误再次静默通过。
2026-09-12 09:46:18 +08:00
fe5ddf274b feat(deploy): 首装脚本补 TLS 自检(跳转端口 302 / HTTP2 协商 / 证书可读)
TLS 是有证书用户的主路径,之前只在生产手工验过、脚本没覆盖。补上后
NAVI_TLS=1 的首装会自己验证:http 跳转端口是否 302 到 https、入口能否
协商 HTTP/2(对应曾踩过的 listen … ssl http2 写法)、证书是否能读出主题。
2026-09-12 09:45:13 +08:00
965c9cb41a fix(deploy): code_of 定义提到等待循环之前(自检退出码 127)
第二次练兵:路由表路径修复生效(apply routes: 2),但因等待监听的循环
调用了尚未定义的 code_of,脚本以 127 退出。把函数定义移到首次调用之前。
2026-09-12 09:41:01 +08:00
158217103d fix(deploy): 首装路由表路径写错(静默零路由)+ apply 空表安全阀
真实练兵(干净数据目录跑 install-host.sh)抓到:
1. 路由表源文件被写到 <数据卷>/gateway/gateway.yml,容器实际读取 <数据卷>/gateway.yml
   → apply 报 routes: 0,用户自备/生成的初始路由被静默忽略
2. 自检在 apply 后立即探测,nginx 还没 reload 完 → 误判连接失败;
   code_of 失败时重复输出 000
3. 缺安全阀:路由表为空时 apply 会照常生成零路由配置并覆盖,
   等于静默清空入口路由表

修复:
- install-host.sh 改写 <数据卷>/gateway.yml(并注释说明两个目录的区别)
- 自检前轮询等待入口监听就绪(最多 10s),code_of 只在成功时输出状态码
- handleGatewayApply 增加安全阀:路由表为空但现有配置含 server_name → 400 拒绝覆盖
- 新增测试 TestApplyRefusesEmptyRoutesOverExisting
2026-09-12 09:40:36 +08:00
c0cbdb5efc feat(deploy): 补首装路径,使他人/AI 可照教程从零部署
审计发现 5 处断点(3 处硬阻断):
1. Dockerfile 的 HTTP_PROXY/HTTPS_PROXY 默认指向内网代理 → 他人构建必失败
2. zd-host.sh 是升级脚本(要求已存在 host 模式 navi 容器)→ 新机器第一句过不去
3. docker-compose.yml 是桥接+端口映射,与'统一入口 host 模式'口径冲突
4. 证书文件名约定未文档化(实际来自 gateway.yml 的 certFile/keyFile)
5. 环境变量表不完整、缺 sshpass 依赖说明

本次:
- Dockerfile:代理默认置空,需要时 --build-arg 传入
- deploy/install-host.sh(新):干净机器首次安装,6 步并在任一步失败即停,
  含端口独占检查、证书检查、初始 gateway.yml 生成、apply、四项自检,
  令牌可自动生成并只打印一次;已存在 navi 容器时拒绝执行(引导去 zd-host.sh)
- docker-compose.yml:标注为试用形态,指向 install-host.sh
- README:新增「从零部署(干净机器)」——两条路径分工表、前置条件表、
  完整环境变量表、证书与初始网关配置说明、装完自检命令;自定义文件表登记新脚本

另修:脚本自检原用 /.navi-ping(该路径已按用户要求删除),改用 /conf.yml。
2026-09-12 09:37:45 +08:00
13ab2336c6 fix(deploy): host 部署后自动重新生成一次网关配置
事故:门户首页改为由 admin-server 渲染(/portal-page)后,镜像里的门户模板带
HTML 占位符,而线上 gateway.conf 仍是上一版生成的(未引用渲染器),导致
门户页占位符裸露、标题为空。

在 zd-host.sh 的入口自检之后加入 apply 步骤(用同一份 env 里的 admin token),
失败即中止并回滚,避免镜像已更新但配置没跟上这类静默不一致。
2026-09-12 09:16:38 +08:00
352aa570ec chore: 验收脚本入库(矩阵/工作空间/滚动)+ 修正脱敏自检命令
- deploy/verify-matrix.js:6 尺寸 × 3 视图 × 深浅色矩阵,断言纵向滚动(仅
  minimal/workspace)、文字亮度阈值、深色是否生效、零运行时异常
- deploy/verify-workspace.js:触发 workspace blocked 态,读弹窗卡片 computed style
- deploy/verify-scroll.js:多视口滚动复现(含 900x520 这类矮窗口)
  三者只从环境变量 PORTAL_COOKIE 取会话,无任何硬编码凭据
- README 自检命令改为排除占位符(<...>、${VAR:-}、change-me),避免误报
- 自定义文件表登记上述脚本
2026-09-12 08:55:04 +08:00
a78c5180ca fix(deploy): host 模式远端显式接收 VER
首次 host 部署在停旧容器后因远端 VER 未定义触发 unbound variable 而失败
(当时旧容器仅被改名未恢复,已手工恢复)。此处只补参数传递,不改动
heredoc 结构与回滚顺序。
2026-09-12 07:36:20 +08:00
b1e4367937 fix(theme): 深色改为整套主题变量覆盖,不再改写 data-theme
用户反馈"深色区域有的方形、有的根本没变深"。根因有两层,都是我的做法错了:

1. navi.js 会把 <html data-theme> 改成 "dark"。Dashy 的配色变量是按主题名
   作用域输出的(本站主题 jianf-glass),改掉它 → 整套主题变量失配 →
   有的区域完全没变深。
2. navi.css 之前只覆盖"页面底色 + 我们自己的悬浮件",Dashy 自己的卡片/侧栏/
   头部仍走原主题 → 与深底色拼在一起,就成了直角圆角混杂。

改法:
- navi.js 只切 <html class="navi-dark">(并设 color-scheme),绝不碰 data-theme
- navi.css 用 html.navi-dark 一次性覆盖 jianf-glass 的全部配色变量(47 个:
  背景/卡片分组/条目文字/侧栏/头部导航/搜索框/设置面板/弹层/滚动条/语义色),
  Dashy 自身界面随之整体一致变深;形状由元素自身样式决定,不再混杂
- 选择器 html.navi-dark 特异度(0,1,1) 高于 [data-theme=…](0,1,0),与注入顺序无关

需要目视确认配色层次(变量值只能看着调)。
2026-09-11 23:31:27 +08:00
53beff646a feat(admin): 现有配置导入 + 添加表单补齐认证/导航开关 + 统一登录账号设置 + 设置分组
针对反馈的五个问题:

1. 后台"没有映射":新增 /admin/api/gateway/import 与界面「从现有 nginx 配置导入」,
   把老统一入口的 nginx 配置贴进来即可解析出全部「域名 → 后端」初稿
   (含 requireLogin / 免认证路径 / SSE 流式 / 备案注入 / 自签证书 / auth 与 portal 类型)。
   解析只读不落盘,确认后才保存;导入默认不接管导航,避免一导入就改掉手工导航。
2. 添加转发时没有导航选项:添加表单补齐「同时加入 Dashy 导航 / 分组 / 名称 / 描述 /
   图标 / 自动抓取」。
3. 添加转发时没有 portal 保护选项:添加表单补齐「启用 portal 统一登录保护」,
   并补上 WebSocket 透传与上传上限。
4. 设置里无法配置 portal 账号:新增统一登录账号卡片,账号存数据卷
   (portal-auth.json,只存加盐哈希),优先级 数据卷 > 环境变量,
   改完立即生效、无需重新部署;会话校验按当前用户名,改账号后旧会话失效。
5. 设置项太乱:按用途分组(站点与外观 / 导航内容 / 历史记录 / 转发与统一登录 / 高级),
   外观卡片并入站点组。

另修两个本轮引入的缺陷:
- 容器假 unhealthy:内部端口改 18080 后,基础镜像的 HEALTHCHECK 仍指向 8080;
  部署脚本生成的 Dockerfile 现在显式覆盖为 :18080/healthz。
- kind=auth 不依赖 portal-auth 容器,指向内置登录服务;避免生成空 proxy_pass。

测试:nginx 配置解析(含兜底段忽略、多域名段、类型判定、默认不接管导航)、
垃圾输入报错、账号存储(不明文、可校验、改名后旧口令失效)、auth 类型生成。
2026-09-11 22:00:09 +08:00
994d93cc98 feat(gateway): 一体化统一入口——路由与 Dashy 导航同一份数据、内置登录、原自定义错误页
目标:一个 HomeDash 容器取代 navi + portal-nginx + portal-auth。

- Route 扩展为"反代 + 导航"一条记录:kind(proxy/portal/auth)、streaming、
  insecureTls、bypassPaths、injectBeian,以及 navEnabled/navSection/navName/
  navDescription/navUrl/navIcon/navAutoIcon
- 保存路由时**同时**写 gateway.yml 与 conf.yml 的导航项(gateway_nav.go):
  同 host 接管而不是重复、未勾选导航的会被清理、无关手工项与系统「历史访问」分组
  一律不动;两步任一步失败都回滚(避免后台显示与导航页各说各话)
- 未知域名与上游故障改用**内置自定义 404/500 页面**(portal/ 目录),
  不再是一行纯文本;HTTP→HTTPS 跳转段用 RedirectPort 复刻原 3081 设计
- 内置统一登录(auth.go):HMAC 签名 cookie、恒定时间比较、登录跳转白名单校验,
  portal-auth 容器可以退场
- 自动抓图标对托管导航项同样生效(history-server 识别 naviGatewayAutoIcon)
- 后台卡片补齐"加入导航/分组/名称/描述/图标/自动抓图标/服务类型"字段
- 内部入口端口 8080 → 18080:为一体化 host 模式让开宿主机 blog(:8080)
- 修掉上一轮编辑写坏的 deploy/zd.sh 一行(会让脚本直接失败)

测试:新增自定义错误页、portal 类型、导航字段校验、导航同步(接管/清理/保留)、
分组不存在必须报错等用例;admin-server 与 history-server 全绿。
2026-09-11 21:27:40 +08:00
507632609d fix(deploy): 历史条数守卫改为与切换前对比,而不是写死条数
写死 4 条会把「用户自己在后台删了几条记录」误判成部署失败(本轮实际发生),
同时又发现不了真正的丢数据。改成切换前读一次旧容器的条数,切换后要求
新容器 >= 旧值:同步接口只累加不删除,所以变小即为异常。
2026-09-11 20:30:52 +08:00
a6b5513f88 docs+chore: 部署脚本入库、README 补零停机部署与部署物料清单
仓库此前缺少部署脚本,只能靠临时文件操作,新人无法复现。补齐:

- deploy/zd.sh:零停机部署(新容器先在对侧端口验证到底,含 /search 预热
  SearXNG;再同 inode 覆写门户上游 + nginx -t + reload;切后哨兵"不得为 502";
  失败自动回滚,旧容器保留为回滚点)。可用环境变量覆盖主机/路径/镜像。
- deploy/verify-ui.js:部署后浏览器验收(渲染开销、后台窄屏、收边两段式、
  被拒内嵌弹层、分组增删改与「历史访问」保护)。任一运行时异常即判失败。

README 新增:
- 「零停机部署」含**单文件 bind mount 不能用 sed -i** 的坑(换 inode → 容器读到
  旧值 → 全站 502,本项目真实踩过)与正确顺序(同 inode 覆写 → 断言容器看到 →
  nginx -t → reload)
- 「部署需要的全部自定义文件」清单表
- 「管理后台」补分组自由增删、历史分组保护、完整 API 表
- 「工作空间视图」补可内嵌性三态探测(转发 Cookie、WAF 403 误判)、被拒弹层、
  悬浮控件收边与"第一次交互只滑出"
2026-09-11 20:13:44 +08:00