From 9ef545b189b36bbb01e69f0eb977b27cb22c1413 Mon Sep 17 00:00:00 2001 From: wweir Date: Sun, 9 Aug 2026 21:18:40 +0800 Subject: [PATCH] docs: complete admin configuration and plan status updates README gains an admin configuration table (state_file, session_file, disable_session_persistence, cookie_secure); sower.toml documents cookie_secure; ARCHITECTURE.md records best-effort session persistence; both admin plans note the evolved whitelist, restart, stats, and SSE implementation. --- ARCHITECTURE.md | 2 +- README.md | 12 ++++++++++++ config/sower.toml | 2 +- docs/plans/admin-console.md | 4 ++-- docs/plans/admin-persistence-config.md | 9 ++++++++- 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 3192354..65c8918 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -123,7 +123,7 @@ For non-transport fallback traffic, it can route by TLS SNI to per-domain upstre - Network operations use timeouts and `context` to limit hangs during dialing and remote rule downloads. - The admin console persists rule changes as bounded deltas in `admin.state_file`, without rewriting TOML or rule sources. It also exposes a sanitized effective-config view and persists whitelisted overrides: `log_level` and the DNS upstreams apply immediately, every other whitelisted field (remote, listeners, rule sources) takes effect on the next restart. Clearing an override reverts the field to the file/flag configuration. `--ignore-admin-state` is the startup escape hatch for a bad state file or override. - The admin server is disabled by default, binds to loopback by default, and requires a password when enabled; the frontend never stores the password (HttpOnly session cookie only). -- The admin restart endpoint replaces the process image in place (`exec` on Unix, same PID) so systemd stays unaware; on platforms without in-place restart the endpoint reports an error instead of acknowledging a restart that would fail. Sessions persist to `admin.session_file` (atomic write, 0600) so a restart keeps the browser logged in; `admin.disable_session_persistence` and `admin.cookie_secure` tune that behavior. +- The admin restart endpoint replaces the process image in place (`exec` on Unix, same PID) so systemd stays unaware; on platforms without in-place restart the endpoint reports an error instead of acknowledging a restart that would fail. Sessions persist to `admin.session_file` (atomic write, 0600) so a restart keeps the browser logged in; `admin.disable_session_persistence` and `admin.cookie_secure` tune that behavior. Session persistence is best-effort: a disk write failure never blocks login or leaves a revoked session valid in memory — the in-memory state stays authoritative and the failure is logged. - Rule hit statistics (per matched rule, per category) and rule-miss statistics (per domain for connections that matched no rule) are tracked in bounded in-memory maps fed by router observers; rule mutations invalidate the hit domain cache so counts stay attributable to the current rule set. - The admin console can share the DNS HTTP proxy listener on port 80 when `admin.addr` equals `dns.serve:80`; classification is Host-based (origin-form requests with the listener IP as Host are admin traffic), so proxying a target whose Host equals the listener IP is inherently ambiguous and routes to admin. The HTTPS and DNS listeners speak different protocols and cannot be shared. - Traffic monitoring reports proxied payload bytes and request/connection counters, not packet-level network accounting. Per-domain byte attribution is batched per connection (32 KiB threshold or 500 ms window) so the relay hot path pays two atomics per I/O instead of a global mutex; Close always drains the remainder. diff --git a/README.md b/README.md index be075c1..77fa551 100644 --- a/README.md +++ b/README.md @@ -271,6 +271,18 @@ password = "your_admin_password" # 启用时必填 启动后打开 `http://127.0.0.1:19090`,输入 admin 密码即可。密码只用于换取会话 cookie,不会保存在浏览器里。 +### Admin 配置项 + +| 字段 | 默认值 | 说明 | +|---|---|---| +| `disable` | `true` | 默认关闭管理台 | +| `addr` | `127.0.0.1:19090` | 监听地址;设为 `dns.serve:80` 时与 HTTP 代理共享端口 | +| `password` | 空 | 启用时必填;只用于换取会话 cookie | +| `state_file` | `/etc/sower/admin-state.json` | 规则增量与配置覆盖的持久化文件;空字符串禁用持久化 | +| `session_file` | `/etc/sower/sessions.json` | 会话持久化文件,重启后浏览器免重新登录;仅限单进程使用 | +| `disable_session_persistence` | `false` | 设为 `true` 关闭会话持久化(重启后需重新登录) | +| `cookie_secure` | `false` | 仅当管理台位于 TLS 终止代理(如 nginx/caddy)之后时设为 `true`,给会话 cookie 加 `Secure` 标记 | + ### 与 HTTP 代理共享端口 如果希望管理台与 HTTP 代理复用同一个 `IP:80`,把 `admin.addr` 设为 `dns.serve:80`: diff --git a/config/sower.toml b/config/sower.toml index a2119d9..9370111 100644 --- a/config/sower.toml +++ b/config/sower.toml @@ -45,7 +45,7 @@ addr = "127.0.0.1:19090" # Admin web server listen address password = "" # Admin web server password (required when enabled) session_file = "/etc/sower/sessions.json" # Persist admin sessions for one sower process only disable_session_persistence = false # Set true to disable session persistence -cookie_secure = false +cookie_secure = false # Set true only when a TLS-terminating proxy protects this origin state_file = "/etc/sower/admin-state.json" # Persist admin rule/config changes # Router configuration diff --git a/docs/plans/admin-console.md b/docs/plans/admin-console.md index f369f9b..b0aaf97 100644 --- a/docs/plans/admin-console.md +++ b/docs/plans/admin-console.md @@ -103,11 +103,11 @@ API: ## 前端设计(`web/`) -- **UI 层用 shadcn-svelte**:Tailwind CSS v4 + bits-ui + lucide-svelte,暗色主题(CSS 变量直接置为暗色值,不提供切换)。组件用 CLI 生成在 `src/lib/components/ui/`(button/card/input/textarea/label/badge/dropdown-menu/separator/table/tabs/alert)。 +- **UI 层用 shadcn-svelte**:Tailwind CSS v4 + bits-ui + lucide-svelte,暗色主题(CSS 变量直接置为暗色值,不提供切换)。组件用 CLI 生成在 `src/lib/components/ui/`(button/card/input/textarea/label/badge/dropdown-menu/separator/table/tabs/alert)。已实现改为浅色主题(`main.ts` 移除 dark 类)。 - Svelte 5(runes)+ Vite,`bun install` / `bun run build` / `bun run check`(svelte-check) - 视图:Login(Card + 密码 → POST /api/session)、Overview(卡片栅格)、Rules(按面包屑 category 上下文切换分类:列表 + 添加 + 删除)、Traffic(Table) - **高级菜单面包屑**(参照 cyberguard 的 `AppBreadcrumbHeader`):品牌 → 当前分区(DropdownMenu 下拉列出全部页面,带图标/描述/当前态)→ 动态上下文段(Rules 的 category 段可下拉切换 block/direct/proxy;Overview 的 version 段、Traffic 的 scope 段为静态段);右侧收藏星标(localStorage 持久化)+ 页面菜单 + 状态徽章 + 退出。定位/键盘导航/外点关闭由 bits-ui DropdownMenu 承担。 -- 有界轮询(3–5s),401 时回登录;loading/error/empty 三态齐全;不引图表库 +- 有界轮询(3–5s),401 时回登录;loading/error/empty 三态齐全;不引图表库。已实现改为 SSE 实时流(`/api/stream`,会话续期/过期事件),图表用自研 `AreaChart`(`src/lib/components/AreaChart.svelte`)。 - `traffic.domains` 为空时服务端返回 `[]` 而非 `null`(Go nil slice 会序列化成 null,前端 `null.length` 会抛错);前端同时做 `?? []` 防御 - `web/embed.go`:`//go:embed all:dist` 导出 `fs.Sub(FS, "dist")` - 构建产物 `web/dist` **提交进仓库**(go:embed 要求编译期文件存在,占位符会产生"能编译但 admin 页面是坏的"的二进制) diff --git a/docs/plans/admin-persistence-config.md b/docs/plans/admin-persistence-config.md index 6c093b1..96ec7c0 100644 --- a/docs/plans/admin-persistence-config.md +++ b/docs/plans/admin-persistence-config.md @@ -1,9 +1,16 @@ # Admin 规则持久化与配置页设计 -Status: 已实现并部署验证(2026-08-07) +Status: 已实现并部署验证(2026-08-07);本文为历史计划,非权威来源。权威规则见根目录 `ARCHITECTURE.md`。 Supersedes: `docs/plans/admin-console.md` 中的 MVP 边界「规则变更仅运行期生效,重启后重置,不改写 TOML、不持久化」 Scope: `cmd/sower` admin 控制台 +## 后续演进(超出本文 v1 范围,已实现) + +- **配置页可编辑白名单扩展**:v1 仅 `log_level`、`dns.upstream`、`dns.fallback` 三个 immediate 字段;后续扩展为指针型 overrides,区分「未提交」与「显式清空」,白名单字段覆盖远程代理、监听地址、规则来源、会话选项等,除 `log_level` 与 DNS 上游立即生效外均需重启。 +- **进程重启端点**:`POST /api/restart` 在 Unix 上原地 `exec`(PID 不变,systemd 无感知);不支持的平台返回错误而非假成功。 +- **会话持久化选项**:新增 `admin.session_file` 说明、`admin.disable_session_persistence`、`admin.cookie_secure`;持久化改为 best-effort(磁盘失败不阻断登录、不残留有效登出会话)。 +- **规则命中/未命中统计**:每规则命中计数与未命中规则的域名访问统计,由 router 观察者喂入有界内存表,供规则页排序与 miss 视图展示。 + ## 背景与问题 Admin 页面的规则增删目前只改内存 `RuleSet`,重启后丢失。用户需要: