From 9ea0d6bed16febfd3b9919fb276d2a69b5981196 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 30 Sep 2026 03:17:13 +0800 Subject: [PATCH] docs(browser): BR-4 runbook for browser identity, UA priority, migration, CF cookies, egress Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 11 +++ docs/plan/00-first-version-plan.md | 1 + docs/runbook/browser.md | 147 ++++++++++++++++++++++++++++- docs/runbook/sites.md | 4 +- 4 files changed, 160 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5b09160..49b1f99a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ ## [Unreleased] +### 新增(BR-4 浏览器 UA 与指纹统一) +- 固定浏览器身份:浏览器镜像的 umt-cookies 插件首次启动时生成一套 Windows 指纹(UA、navigator、屏幕、WebGL、Windows 字体、随机种子)存进持久卷(`/root/.camofox/umt-identity.json`),之后每次启动复用,不再每次随机;Camoufox / Firefox 升级时只改 UA 与版本字段。修正上游字体配置路径,Windows 字体(含微软雅黑)按 Windows 渲染。新增 `GET /umt/identity`(访问凭据保护,只含 UA / OS / 版本 / 时间)。 +- 站点请求 UA 跟随浏览器:列表 / 搜索 / 详情 / 下载种子 / 健康检测、RSS、登录、用户数据都带账号 UA,不再发 `Deno/2.9.7`。优先级:账号手动 > 待切换 > 设置里的全局 UA > 跟随浏览器(读不到浏览器身份时用同型 Windows Firefox UA)。站点账号页显示 UA 来源(跟随浏览器 / 手动填写 / 待切换),原始 UA 在技术信息里。 +- 出口按站点统一:站点开了"使用代理"时浏览器会话也走同一代理,没开时直连;浏览器不再使用容器环境变量里的代理(`UMT_BROWSER_PROXY_MODE=env` 可退回)。插件新增 `PUT /umt/sessions/{userId}/proxy`。 +- Cloudflare 验证 Cookie(`cf_clearance`、`__cf_bm`)只留在浏览器:回写站点账号时剔除,只记名字,站点请求不用。 + +### 变更(BR-4) +- 设置 → 站点默认 → User-Agent 缺省改为空(跟随浏览器),原缺省 Windows Chrome 140 去掉;挂站点的 RSS 与站点其它请求同一出口(站点开代理时 RSS 也走代理)。 +- 迁移 0056:存量 Cookie / 账密账号标"待切换",继续用取得登录态时的旧 UA,下次重新登录或浏览器回写 Cookie 时 Cookie 与 UA 一起切换;设置里仍是旧缺省 UA 的清空。 +- 上线需重建 app 与 camofox(`docker compose build app camofox && docker compose up -d app camofox`);camofox 只有插件层变化,不重新下载浏览器包。见 `docs/runbook/browser.md` §11。 + ## [0.3.1] - 2026-09-30 ### 新增 diff --git a/docs/plan/00-first-version-plan.md b/docs/plan/00-first-version-plan.md index fb702e7d..fe1b5af0 100644 --- a/docs/plan/00-first-version-plan.md +++ b/docs/plan/00-first-version-plan.md @@ -199,6 +199,7 @@ NFO / 图片投影、身份巡检与挂载根因聚合、HTTP 失败自动切浏 - 偏离与补充:获取方式比任务单多一项 `list`(列表抓取 / 快照,调度需要区分);"计划"仍在概览组(用户只提了任务);公开 BT 站目前没有站点定义,实际只有 RSS(有定义后自动获得搜索 / 列表);`POST /feed-sources` 不带站点时自动归入同主机的自定义 RSS 源(兼容旧调用方,而不是 400)。截图 `apps/web/test/screenshots/ia2/`(不进 git)。 - **浏览器 UA 与指纹(BR-4,用户 2026-09-30:不着急,先研究明白)**:现状 HTTP 默认 Windows Chrome 140 UA、浏览器是 camofox(Firefox 内核)自生成 UA,同一站点账号 Cookie 两边互注 → UA 不一致。方向:每个站点账号一套固定 UA(跟随 camofox 的 Firefox)+ 固定指纹,浏览器与 HTTP 共用,不轮换,浏览器升级时同步。先做只读调研(camofox 能否按会话指定/持久化指纹、各站会话与 UA 绑定情况、MP 做法),出报告再定实现。 - 调研见 `docs/design/browser-ua-research.md`。用户决定(2026-09-30):对外伪装 **Windows**;全实例**一套固定指纹**(插件 `browser:launching` 首次生成落盘、之后复用);浏览器与 HTTP **按站点统一出口 IP**;站点适配器 / RSS 目前发 `Deno/2.9.7` UA 的问题**和 BR-4 一起做**(不单独先修)。cf_clearance 等 CF Cookie 回写时标"仅浏览器"。平滑迁移:下次重新登录 / 浏览器回写 Cookie 时原子切换 Cookie+UA。 + - **已实现(br4 分支,待合并)**:固定 Windows 身份(插件落盘 `/root/.camofox/umt-identity.json`、`GET /umt/identity`)、站点请求 UA 全链路跟随浏览器(优先级 手动 > 待切换 > 全局 > 浏览器)、迁移 0056 待切换、CF Cookie 仅浏览器、出口按站点(浏览器按上下文代理,不再用容器环境变量代理)。运维与限制见 `docs/runbook/browser.md` §11;上线需重建 app + camofox。 - **下载计划并入下载面板(DL-3,用户 2026-09-30 同意)**:acquisition_plan 后台保留(批准绑定、预算/空间预占、防重复、升级/换源追踪);界面去掉「下载计划」页签——等批准/等空间/等预算的计划作为「进行中」行显示(需操作的同时进待处理);已提交的计划并入对应下载任务,详情加「为什么下这个」;失败/过期进下载历史,细节进技术信息;用户可见处不再出现"下载计划",统一说"下载"。 - **文件预览不加 ffmpeg(用户 2026-09-30)**:视频缩略图 / 截图条不做,镜像不引入 ffmpeg(+141MB);`docs/design/file-preview-research.md` 对应项标为不做。 - 用户 2026-09-30:BR-4 直接做完上线;《征途》重复单元不再处理(刷新时自动合并即可)。 diff --git a/docs/runbook/browser.md b/docs/runbook/browser.md index 32510d83..cedd7e87 100644 --- a/docs/runbook/browser.md +++ b/docs/runbook/browser.md @@ -365,6 +365,8 @@ camofox 1.17.0 只有 `POST /sessions/:userId/cookies`(导入),而且要另一 | `POST /umt/sessions/{userId}/cookies` `{cookies}` | `context.addCookies`,并触发持久化插件立即落盘(与上游导入端点相同) | | `GET /umt/sessions/{userId}/cookies?urls=a,b` | 作用于这些地址的 Cookie:会话活着读 `context.cookies(urls)`,否则读 profile 落盘文件 | | `GET /umt/instance` | `{instanceId, startedAt}`:插件加载时生成,camofox 进程重启即变(BR-3 重启检测,§10) | +| `GET /umt/identity` | 固定浏览器身份的公开部分(UA / OS / 版本,BR-4,§11.1) | +| `PUT /umt/sessions/{userId}/proxy` `{proxy}` | 该 profile 的出口:`{server, username?, password?}` 或 `null` 直连(BR-4,§11.5) | 适配器先走插件;插件不在(404)再试上游导入端点(`CAMOFOX_API_KEY` 与访问凭据相同时可用,只能写不能读);都不行 → 会话照常打开, `credential.status = unsupported` 并说明。profile 落盘在 `/root/.camofox/profiles//storage-state.json` @@ -393,7 +395,8 @@ camofox 1.17.0 只有 `POST /sessions/:userId/cookies`(导入),而且要另一 | 定时 | 会话有动作时,距上次读回(或创建)满 10 分钟,后台读回一次 | | refresh-login 发现 Cookie 失效 | Cookie 账号:从浏览器 profile 读回(不要求会话开着,读落盘文件),与存的不同就拿它验证一次,有效才回写并标 active | -读回只取作用于站点主机的、未过期的 Cookie(统计 / CDN / 第三方域名一律丢弃),与存储比较(忽略顺序),有变化才写: +读回只取作用于站点主机的、未过期的 Cookie(统计 / CDN / 第三方域名一律丢弃),剔除 Cloudflare 的 `cf_clearance` / `__cf_bm`(仅浏览器,§11.4), +与存储比较(忽略顺序),有变化才写(同时结束 UA 待切换,§11.3): Cookie 账号替换 `secret_ref`,账密账号替换(或新建)会话 Cookie;`site_account.user_info.cookieOrigin = {source: "browser", by: "browser", at}`, `expired` / `invalid` 的账号回到 `unknown`。来源判定:`cookieOrigin.at` 不早于 secret 最近写入(`rotated_at`,留 5 秒)才算浏览器回写, 之后再手填或自动登录会让它变回 `manual` / `login`。 @@ -460,3 +463,145 @@ camofox 容器重启(`docker compose up -d` 重建、OOM、手动 restart)后, - 真机:未对真实 camofox 容器重启核验。桌面镜像要重建后才有 `GET /umt/instance`(`docker compose build camofox && docker compose up -d camofox`); 重建前只有被动发现(截图 / 操作时自愈),桌面模式要等一次截图或 Agent 操作才会重连。 + +## 11. 浏览器身份、站点请求 UA 与出口(BR-4) + +同一个站点账号的登录态在浏览器(camofox / Firefox)和 HTTP 端(Deno)之间互注(§9),所以两端要**看起来是同一台设备**: +同一个 UA、稳定的指纹、同一个出口 IP。用户决定(2026-09-30):对外伪装 **Windows**;全实例**一套固定指纹**; +浏览器与 HTTP **按站点统一出口**。调研见 `docs/design/browser-ua-research.md`。 + +代码:插件 `docker/browser-desktop/root/opt/camofox/plugins/umt-cookies/{index.js,identity.js}`; +`apps/server/src/modules/sources-identity/{runtime.ts,service/user-agent.ts,service/credentials.ts,service/account.ts}`; +`apps/server/src/modules/browser/service.ts`(`identityUserAgent` / `refreshIdentity` / `applyEgress`);迁移 `0056_browser_identity`。 + +### 11.1 固定浏览器身份(插件 `browser:launching`) + +- **首次启动**:插件用 camoufox-js 的 `launchOptions({ os: "windows" })` 生成一份完整指纹——BrowserForge 的 Windows Firefox + 指纹(navigator / screen / 硬件)、Camoufox 的 Windows 字族表、按 Windows 取样的 WebGL(vendor / renderer / 参数)、 + 字体间距 / 音频 / canvas 随机种子、`window.history.length`、canvas 抗锯齿偏移——去掉运行期字段(`humanize`、`locale:*`、 + `geolocation:*`、`timezone`、`webrtc:*`、`addons`)后落盘 **`/root/.camofox/umt-identity.json`**(compose 的持久卷 `umt-camofox`, + 与 profile 同一个卷;属主 abc、0600)。WebGL2 开关等配套 Firefox 首选项一起存。 +- **之后每次启动**(容器重启、浏览器空闲 5 分钟退出后再启动):把上游随机生成的 config 里的指纹部分整份换成身份文件里的, + 运行期字段仍取上游。浏览器进程日志 `umt identity applied {os, fingerprintId, revision}`。 +- **升级**:Camoufox 版本或 Firefox 主版本与文件里记的不同 → 只改 `navigator.*` / `headers.*` 里的版本号(与 camoufox-js 生成时 + 同一个替换规则),新版本多出的指纹字段从同 OS 新样本补齐,其余一律保留;`revision + 1`,`fingerprintId` 不变 + (日志 `umt identity upgraded`)。站点看到的是"同一台电脑升级了 Firefox"。 +- **重置**(换一台"新设备",所有站点都会看到新设备):停 camofox,删 `umt-identity.json`,再启动。 +- **开关与路径**:`UMT_BROWSER_IDENTITY=off` 关掉(回到上游每次随机的 Linux 指纹);`UMT_IDENTITY_FILE` / `UMT_STATE_DIR` 改路径。 + 文件损坏会改名 `.bad-<时间>` 后重新生成。 +- **`GET /umt/identity`**(访问凭据保护):`{userAgent, os, platform, firefoxVersion, camoufoxVersion, fingerprintId, revision, + schema, createdAt, updatedAt}`,不含指纹细节。app 存进 `browser_provider.identity`(加 `fetchedAt`):打开会话时(10 分钟内读过就不读)、 + 重连时、设置页探测浏览器服务时读;站点请求取它时超过 6 小时会重读,读不到 5 分钟内不再试。 + +**Windows 伪装的一致性**(camoufox-js 0.11.5 源码 + 2026-09-30 本地临时容器实测,只开本地页面): + +| 项 | 结果 | +| --- | --- | +| UA / 请求头 | `Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:152.0) Gecko/20100101 Firefox/152.0`;HTTP `User-Agent` 头同值 | +| navigator | `platform = Win32`、`oscpu = Windows NT 10.0; Win64; x64`;并发数、语言等来自同一份 Windows 指纹 | +| WebGL | 按 Windows 取样:`Google Inc. (Intel)` / `ANGLE (Intel, Intel(R) HD Graphics 400 Direct3D11 vs_5_0 ps_5_0)`(D3D11,Windows 才有) | +| 字体 | 字族白名单 = Camoufox 的 Windows 字体表;字形来自 Camoufox 自带的 Windows 字体(`/opt/camoufox/fonts/windows`:Arial、Segoe UI、微软雅黑、宋体、等线……)。实测"微软雅黑"可用且宽度与回退字体不同,Noto / DejaVu 这类 Linux 字体不可见;中文页面以微软雅黑渲染 | +| 跨重启 | 重启容器后 `fingerprintId`、屏幕尺寸、WebGL 不变;模拟 140 → 152 升级只改了 UA 版本 | + +发现并修正的上游问题:camoufox-js 0.11.5 把 `FONTCONFIG_PATH` 设成 `/opt/camoufox/fontconfig/lin`(不存在;Camoufox 152 的目录是 +`linux` / `macos` / `windows`),而 fontconfig 里的字体目录写的是相对当前目录的 `fonts`(浏览器进程当前目录是 `/opt/camofox`)。 +插件把 `fontconfig/windows/fonts.conf` 复制到临时目录、字体目录改成 `/opt/camoufox/fonts` 绝对路径,再指给 `FONTCONFIG_PATH`。 + +限制:渲染仍是 Linux 上的 Firefox——字体光栅化、canvas / 音频的像素级结果与真 Windows 不同(Camoufox 用固定种子加噪,稳定但不是 +Windows 的真值);TLS / HTTP2 指纹是 Firefox 152 本身(与 OS 无关)。屏幕尺寸来自指纹(如 2560×1440),桌面模式窗口是 1280×900, +相当于没最大化的窗口。时区跟容器 `TZ`(缺省 Asia/Shanghai);语言跟指纹(缺省 en-US),要中文设 `CAMOFOX_LOCALE`。 +指纹是进程级的:所有站点 / 账号共用这一套(用户决定;每账号一套要每账号一个浏览器进程,不做)。 + +### 11.2 站点请求 UA + +站点请求的 UA 按账号解析,**优先级从高到低**: + +| 来源(`SiteAccount.userAgentSource`) | 取值 | 界面 | +| --- | --- | --- | +| `manual` | 账号手动填的 UA(`site_account.user_agent`) | 手动填写 | +| `pending` | 待切换:取得当前登录态时用的旧 UA(`site_account.ua_pending`,§11.3) | 待切换:… | +| `global` | 设置 → 站点默认 → User-Agent(`settings.sites.userAgent`,非空时) | 使用设置里的全局 UA | +| `browser` | 浏览器固定身份的 UA(`browser_provider.identity.userAgent`);读不到时用常量 `FALLBACK_SITE_USER_AGENT`(同型 Windows Firefox 152) | 跟随浏览器 | + +- 覆盖面:站点适配器(列表 / 搜索 / 详情 / 下载 .torrent / 健康检测)、RSS、账密登录、用户数据、Cookie 失效时的浏览器读回验证—— + 全部经 `SourcesRuntime.siteFetch`:请求自己没设 UA 就补上;适配器每条请求现场按"与站点凭证同一个账号"(第一个启用、未失效、非 Passkey) + 取 UA,与 Cookie 同一时刻读库,不缓存。之前适配器与 RSS 发的是 Deno 缺省 UA `Deno/2.9.7`,已不再出现 + (测试 `apps/server/test/sources-identity/user-agent.test.ts` 逐条断言)。不挂站点的请求(通用 RSS)用全局 UA / 跟随浏览器。 +- 设置里的 User-Agent **缺省留空 = 跟随浏览器**(原缺省 Windows Chrome 140 已去掉);填了会让 HTTP 与浏览器 UA 不一致,只在确有需要时用。 +- 账号"更新凭证"里的 UA 留空 = 不覆盖;Cookie 是从别的浏览器复制的、且站点校验 UA 时才填那个浏览器的 UA。 +- 不在范围:TMDB、图片代理(不带凭据的公开请求)、下载器、浏览器服务本身。 +- 升级 camofox 镜像(Firefox 大版本变)时:浏览器身份自动升级(§11.1),app 最迟 6 小时(或下次打开会话 / 重连)跟上; + 同时把 `packages/contracts/src/settings.ts` 的 `FALLBACK_SITE_USER_AGENT` 改成新版本号。 + +### 11.3 平滑迁移(待切换) + +改 UA 一般不会让 NexusPHP 的 Cookie 失效,但前置 Cloudflare / WAF 的站点可能要求重新过挑战。所以**不立即给存量账号换 UA**: + +- 迁移 `0056_browser_identity`:Cookie / 账密账号(有凭证、没手动 UA)记 `ua_pending` = 取得当前登录态时的 UA——设置里改过全局 UA + 就是那个值,否则是旧缺省 Chrome 140;API Key / Passkey 账号与网页登录态无关,直接跟随浏览器;设置里的 UA 仍是旧缺省值时删掉 + (= 跟随浏览器),改过的保留为全局 UA。 +- 待切换期间,这个账号的所有站点请求(包括原来发 Deno UA 的适配器 / RSS)都用旧 UA,与登录态一致。 +- **切换时机**(Cookie 与 UA 同一事务写,下一条站点请求两者一起生效): + 1. 重新登录:账密账号 refresh-login 走 takelogin 时用切换后的 UA,拿到会话 Cookie 后清 `ua_pending`; + 2. 浏览器回写 Cookie(§9.2 的交还 / 关闭 / 定时 / refresh-login 读回):浏览器是用自己的 UA 取得 / 使用这份登录态的,回写时清 `ua_pending`; + Cookie 没变但浏览器不在登录页、正用着这份登录态,也清; + 3. 换主凭证(填新 Cookie / 新密码):新登录态,直接跟随浏览器。 +- Cookie 账号如果一直不开原站视图、也不换 Cookie,会一直待切换(界面写"待切换:下次刷新登录,或在原站视图里用过后改为跟随浏览器")。 + 刷新登录对 Cookie 账号只校验,不切换。 + +### 11.4 Cloudflare Cookie 仅浏览器 + +`cf_clearance`、`__cf_bm`(及挑战过程中的 `cf_chl_*`)与取得它的浏览器绑定(UA + IP + TLS 指纹),Deno 的 TLS 指纹与 Firefox 必然不同, +HTTP 端拿去用多半无效还可能触发风控。所以: + +- 浏览器回写账号时剔除这些 Cookie,只在 `site_account.user_info.browserOnlyCookies` 记名字(不含值);它们留在浏览器 profile 里照常用; +- 比较"有没有变化"时也不算它们(CF Cookie 轮换不会触发回写);refresh-login 从浏览器读回的 Cookie 同样剔除; +- 站点账号页技术信息里显示"仅浏览器使用的 Cookie"; +- 用户手填的 Cookie 里带 `cf_clearance` 不处理(用户自己的选择)。 + +### 11.5 出口 IP 按站点统一 + +实测(本地临时容器):**Firefox 会用容器的 `HTTP_PROXY` / `HTTPS_PROXY` 环境变量**(Firefox 缺省"使用系统代理",Linux 下没有 +GSettings 就读环境变量)。所以本机栈此前的状态是:`docker-compose.override.yml` 给 camofox 设了 `HTTP_PROXY=http://172.17.0.1:7891`, +浏览器**所有**站点都走代理;HTTP 端只有开了"使用代理"的站点走代理——同一账号两端出口 IP 不同。 + +现在(`UMT_BROWSER_PROXY_MODE=site`,缺省): + +- 插件在启动时设 `network.proxy.type = 0`:浏览器**不再用**容器环境变量里的代理,缺省直连; +- app 在打开会话 / 重连前调 `PUT /umt/sessions/{userId}/proxy`:站点开了"使用代理"且设置了代理地址 → 该站点 profile 的 + BrowserContext 走同一个代理(Playwright 按上下文代理,Firefox / Juggler 原生支持;URL 里的账号口令拆成 username / password); + 否则 `null` 直连。插件把这张表存在 `/root/.camofox/umt-proxies.json`(0600,代理带口令时含口令),camofox 重启后照用; +- 该 profile 已有活动上下文、出口不同 → 插件先关掉它(持久化插件先落盘登录状态),下一次操作按新出口重建; +- 实测:直连 profile 访问测试域名走不到环境变量里的代理;代理 profile 的请求到了指定代理;改出口时活动上下文被重建。 + +限制与注意: + +- **代理地址要让 app 与 camofox 两个容器都能连**(如本机的 socat 中继 `http://172.17.0.1:7891` 可以;`http://127.0.0.1:7890` 在 + camofox 容器里连不到)。 +- 站点"使用代理"开关或代理地址改了,已打开的浏览器会话在**下一次打开 / 重连**时才按新出口;要立即生效就关掉会话重开。 +- 匿名会话与 Agent 打开的站点会话同样按站点出口(浏览器会话都属于某个站点)。 +- 旧镜像 / 插件没有出口接口:app 只记日志,浏览器照旧按环境变量出网。`UMT_BROWSER_PROXY_MODE=env` 可退回旧行为。 +- SOCKS 代理的 DNS 解析位置未核验(HTTP 代理由代理端解析,与 Deno 的代理客户端一致)。 +- 本机栈重建 camofox 后,憨憨(未开代理)的浏览器会话会从"经代理"变为**直连**,与 HTTP 端一致;override 里 camofox 的 + `HTTP_PROXY` 可以留着(Firefox 不再用它),也可以删掉。 + +### 11.6 上线步骤与核验 + +需要重建 **app**(代码 + 迁移 0056)和 **camofox**(插件): + +```sh +docker compose --env-file .env.deploy build app camofox +docker compose --env-file .env.deploy up -d app camofox +docker compose exec camofox curl -s -H "Authorization: Bearer $CAMOFOX_ACCESS_KEY" http://127.0.0.1:9377/umt/identity +``` + +camofox 镜像只有 `COPY root/ /` 与其后改插件清单的一层变化;前面的 apt 层与从 `camofox-browser:1.17.0` 复制 node / camofox / +Camoufox 浏览器包的层都走构建缓存,**不会重新下载 Camoufox(约 630 MB)**。2026-09-30 在本机用临时标签实测:带 +`--build-arg APT_MIRROR=mirrors.ustc.edu.cn`(与 `.env.deploy` 相同)时 apt / 复制层全部 `CACHED`,只重跑插件两层(< 1 秒)。 +注意**构建参数要与上次一致**(用同一个 `--env-file .env.deploy`):不带 `APT_MIRROR` 构建会让 apt 层缓存失效,重新 apt 安装(浏览器包 +仍从本地 `camofox-browser:1.17.0` 复制,不从网上下)。首次启动生成身份,之后 profile 卷里的登录状态照常可用。 + +核验:`apps/server/test/sources-identity/user-agent.test.ts`(每条出站请求的 UA、优先级、待切换、CF Cookie)、 +`migration-browser-identity.test.ts`、`apps/server/src/modules/browser/__tests__/browser-identity.test.ts`(出口下发、身份存库)、 +`packages/adapters/src/browser/umt-plugin.test.ts`(插件纯函数、适配器接口);真机:本地临时容器(`--network none`,只开容器内本地 +页面)核验了 §11.1 表格与 §11.5 出口行为,未对真实站点发请求。 diff --git a/docs/runbook/sites.md b/docs/runbook/sites.md index 47b71e15..98c993ad 100644 --- a/docs/runbook/sites.md +++ b/docs/runbook/sites.md @@ -96,8 +96,8 @@ | `defaultSnapshotMaxPages` | 1 | 定时 / 手动快照最多抓几页(与定义取较小);补漏有自己的页数预算,实时补查固定 1 页 | | `defaultHealthCheckIntervalSec` | 21600 | 定时健康检测的最短间隔(与定义取较长) | | `defaultDetailCacheSec` | 86400 | 详情缓存:期内 `GET /sites/{id}/releases/{rid}?refresh=true` 与 `release.inspect` 不再请求站点(与定义取较长);0 = 不缓存 | -| `userAgent` | 固定桌面 Chrome UA | 账号未设 UA 时用于登录与用户数据 | -| `proxyUrl` | null | 站点 `proxy=true` 时,适配器、登录、用户数据请求都经此代理(`Deno.createHttpClient`) | +| `userAgent` | 空(跟随浏览器) | 全局 UA 覆盖:空 = 站点请求跟随浏览器固定身份的 UA(Windows Firefox);填了则账号没手动 UA 时都用它。优先级:账号手动 > 待切换 > 全局 > 跟随浏览器,见 browser.md §11.2 | +| `proxyUrl` | null | 站点 `proxy=true` 时,适配器、登录、用户数据、RSS 请求都经此代理(`Deno.createHttpClient`),浏览器会话也走同一代理(browser.md §11.5);地址要让 app 与 camofox 容器都能连 | | `userDataMaxSeedingPages` | 10 | 用户数据做种分页上限 | 运行时每 30 s 最多重读一次设置;设置变化会让站点适配器按新签名重建。