- CSS 65.6%
- Rust 31.6%
- Shell 1.7%
- Nix 1%
- Just 0.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Restyle the Dioxus management pages with locally bundled daisyUI components and responsive navigation. Add a GPG-only challenge command and browser signature submission that reuse the existing authentication protocol, with session and origin checks, regression tests, and usage documentation. |
||
| assets | ||
| docs | ||
| nix | ||
| src | ||
| tests | ||
| .envrc | ||
| .gitignore | ||
| .muxrun.toml | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CONTEXT.md | ||
| default.nix | ||
| devenv.lock | ||
| devenv.nix | ||
| devenv.yaml | ||
| Dioxus.toml | ||
| flake.lock | ||
| flake.nix | ||
| justfile | ||
| README.md | ||
Clan Secrets Manager
一个面向单个本地 Clan 仓库的 Secrets 管理与授权 WebUI。Rust + Dioxus 0.7 Fullstack(typed server functions + Axum),事实来源是 Clan 仓库本身与 Clan CLI, 不建立第二套身份、inventory 或用户数据库。
本仓库同时是 gjz010/harness 项目:环境由 devenv + direnv 提供,OpenCode 由 harness 包装。harness 的安全启动边界见文末。
1. 当前范围
已实现(bounded 交付):
- Secrets 只读视图:列表、存在状态、recipient 元数据(直接授权与经组授权)。
- Secret 写操作:新建、替换(覆盖内容并保持名称与授权)、删除;UTF-8 文本与 原始二进制字节两种输入。
- 授权增删:对已有 Clan 用户、机器身份、组增删某个 Secret 的授权。
- 机器 recipient 身份:列举、以已有 age 公钥登记、更换公钥、移除,并显示从现有 metadata 聚合的授权影响。WebUI 不生成或保管机器私钥,不做 SSH 采集。
- Secret inspection:认证用户在详情页显式确认后,临时查看明文(不进入任务、 不自动解密、不持久化)。
- GPG 认证:浏览器发起 pairing,
clanwebuictl在本机用用户私钥签名 challenge, 浏览器获得会话。
明确不做(本次范围之外):
- Vars、Services 配置管理。
- build / deploy / update 等任何执行动作;机器没有实时状态。
- 任意 Nix 编辑、消费者配置自动生成、sops 消费配置(imports/owner/path/templates) 修改。
- 用户/组身份的新增 CRUD:用户与组只读,只作为授权对象。
- 把 recipient 授权当作完整的 Nix 消费者分析;消费者由用户自行在 Nix 配置,本 应用不生成也不解析消费者配置。
2. 先跑起来(开发流程)
宿主机需要支持 flakes 的 Nix、devenv 和 direnv。当前固定的 harness 仅支持
x86_64-linux,其启动器要求宿主内核/容器策略允许 bubblewrap 创建所需
namespaces(含非特权 user namespace、PID 与 IPC namespace)。受限容器可能拒绝
启动;just check 通过并不证明沙箱可运行。
direnv allow # 首次必须由宿主人工审查并授权 .envrc
just check # 校验配置、Nix 与 shell/recipe 语法
just build # 编译 native server 与 clanwebuictl
just dev # dx serve --fullstack,默认 http://127.0.0.1:8080
direnv allow 是宿主侧的安全授权动作,agent 不会代为执行;不要为绕过权限失败而
放宽 .git 或 Nix 的访问边界。首次进入 shell 会让 devenv 求值并下载 harness;
完成后 harness 与项目工具可用。
修改 devenv.nix / devenv.yaml / devenv.lock 后,在宿主终端执行(agent 不代为
授权,也不需要重新 direnv allow):
direnv reload
direnv export json >/dev/null # 立即求值以暴露错误,不打印环境值
然后让当前终端经 direnv shell hook 加载新环境。只有 .envrc 改动才需要重新审查
授权。
3. Just 入口
| 命令 | 作用 |
|---|---|
just |
列出全部 recipe |
just check |
bash -n .envrc、nixfmt --check、just --fmt --check、harness lock 断言 |
just fmt |
cargo fmt,就地格式化 Rust 源码 |
just check-rust |
cargo fmt --check、cargo clippy --all-targets --all-features、cargo test、cargo test --all-features |
just test |
cargo test --all-features |
just build |
cargo build --features server(native server + CLI) |
just dev [ARGS...] |
dx serve --fullstack,开发服务器 |
just integration |
跑真实 CLI / sops-nix 的 #[ignore] 集成测试(隔离 fixtures) |
just capabilities |
可选的隔离 Clan CLI 能力检查(合成 fixture,一次性密钥) |
just opencode / just opencode-web |
启动 harness 包装的 OpenCode |
just update-harness |
更新固定的 gjz010/harness input 与 lock |
just build / just dev / 任何 recipe 都不会自动 commit、deploy 或运行真实
secrets 操作。just integration 只在隔离临时仓库上用一次性测试密钥运行。
默认 dx serve 会打开浏览器;无头环境可用 just dev --open false。
4. 服务端配置(CLANWEBUI_*)
所有配置在启动时解析一次。字段与默认值来自 src/server/config.rs。
| 变量 | 默认 | 说明 |
|---|---|---|
CLANWEBUI_PUBLIC_ORIGIN |
http://127.0.0.1:8080 |
公开 origin,必须是裸的绝对 http(s)://host[:port]。同时决定 cookie secure 标志,并作为浏览器 Origin/Host 校验的期望值 |
CLANWEBUI_CLAN_FLAKE |
当前目录 | Clan flake 根,必须存在且是目录 |
CLANWEBUI_CLAN_DIR |
未设置 | 显式 Clan 目录覆盖(sops/ 的父目录),必须在 flake 根内;设置后跳过较慢的 Clan/Nix 解析,推荐本地开发与测试 |
CLANWEBUI_GPGBIN |
gpg |
GPG 可执行文件 |
CLANWEBUI_GNUPGHOME |
未设置(再回退 GNUPGHOME) |
只被认证用的 GPG client 读取,见第 6 节 |
CLANWEBUI_CLANBIN |
clan |
Clan CLI 可执行文件 |
CLANWEBUI_SOPSBIN |
sops |
inspection 只读适配器使用的 SOPS 可执行文件 |
IP / PORT |
127.0.0.1 / 8080 |
dx serve 实际绑定的内部地址;后端额外接受这个精确的 bind authority 作为 Host |
根 flake 与 Clan 目录的区别
CLANWEBUI_CLAN_FLAKE 是 flake 根(含 flake.nix 的目录)。CLANWEBUI_CLAN_DIR
是 Clan 目录,即 sops/ 的父目录(后端实际读取 $CLANWEBUI_CLAN_DIR/sops),
不是 sops/ 本身。例如布局为 ROOT/clan/sops 时 CLANWEBUI_CLAN_FLAKE=ROOT、
CLANWEBUI_CLAN_DIR=ROOT/clan;sops/ 直接位于 flake 根下(ROOT/sops)时两者
均为 ROOT。未设置 CLANWEBUI_CLAN_DIR 时,启动会调用:
clan select clanInternals.inventoryClass.relativeDirectory --flake ROOT
失败则回退到 nix eval --raw 取同一属性。该解析只在启动时执行一次。它在非隔离
环境可能较慢;本地开发与测试推荐显式设置 CLANWEBUI_CLAN_DIR 指向 flake 根内包含
sops/ 的 Clan 目录。
生产默认与同源 origin
默认配置面向 loopback 开发:CLANWEBUI_PUBLIC_ORIGIN=http://127.0.0.1:8080,
dx serve 默认绑定 127.0.0.1:8080。浏览器地址、CLANWEBUI_PUBLIC_ORIGIN 与
Host 校验必须同一个 origin。特别注意不要混用 localhost 与 127.0.0.1:
它们字符串不同、Host 校验不匹配,会导致 400。需要对外服务时,把 origin 与
绑定的 IP/PORT 一起改为对外地址,并使用 HTTPS(https:// origin 会启用
secure cookie)。
dx serve 的 devserver 会把请求代理到内部 IP:PORT,后端看到的 Host 是内部
bind authority;因此 Host 校验在配置 origin 之外额外接受精确的 IP:PORT,其它
主机名仍被拒绝。
5. 运行 server 与 CLI
native server(带 server feature 的主二进制)由 just build 产出,也可直接:
cargo build --features server --bin clan-webui
CLANWEBUI_CLAN_FLAKE=/path/to/clan-repo \
CLANWEBUI_CLAN_DIR=/path/to/clan-repo \
./target/debug/clan-webui
这条命令用于运行 server/API;静态前端资源与完整的 public/ 由 dx build /
just dev 产出。缺少 public/ 时服务器只提供 API 与 SSR,并给出警告。
登录确认 CLI 是独立编译的 clanwebuictl:
cargo build --features server --bin clanwebuictl
./target/debug/clanwebuictl login --server http://127.0.0.1:8080 --request <uuid>
<uuid> 是浏览器登录页显示的 pairing request id;请替换为用户自己的值。CLI 只接受
与 --server 相符、未过期且 protocol 正确的 challenge,禁止跨 origin 重定向,并在
展示完整元数据与原始签名字节后要求确认。
也可以完全不用 clanwebuictl。登录页的**“方式二:仅使用 GPG”会展示一条只依赖 POSIX
shell 内建 printf 与 gpg 的命令(形如 printf '%s' '<challenge>' | gpg --armor --detach-sign --output - --local-user '<指纹>')。在持有该私钥的机器**上用 sh / bash /
zsh 执行它:命令不需要 clanwebuictl、curl、base64、Python 或临时文件,私钥不离开
本机,gpg-agent 依旧遵循你的 PIN/touch 策略。然后把 gpg 的完整输出(包含
BEGIN PGP SIGNATURE 与 END PGP SIGNATURE 两行)粘贴回登录页提交即可;签名前请先核对
challenge 中的 origin 与 user 是否与本页一致。challenge 2 分钟后过期,过期请重新开始。
6. 认证、密钥与服务端解密环境
- 服务端只需要公钥 ring:认证侧只需运行环境有可用的
gpg,并在其 keyring 中 具有该 Clan 用户具备可用签名能力的完整公钥证书,不需要私钥。是否可签通过gpg --list-keys --with-colons判定。 - 私钥留在用户本机:真实签名由
clanwebuictl调用本机 gpg-agent 完成,或在登录页 按“仅使用 GPG”方式,把本机gpg --armor --detach-sign的输出粘贴回来;两者都遵循 PIN/touch 策略,不绕过、不导出私钥、不上传证书。 CLANWEBUI_GNUPGHOME的边界:它只会被认证用的 GPG client 读取 (GpgClient以GNUPGHOME传给gpg子进程)。它不会自动传给 Clan CLI 或 SOPS 子进程。若希望整个服务端(认证 + Clan + SOPS)统一使用同一个 keyring, 应设置标准的GNUPGHOME进程环境变量;clan与sops子进程继承进程环境。- SOPS 写/查看需要服务端可解密:Secret 替换与 inspection 由服务端侧的 Clan CLI
与 SOPS 完成,因此服务端必须实际具备解密环境(同机 gpg/age、必要的
SOPS_*变量)。仅服务端“能认证”不代表“能解密”。 - challenge 与会话:pairing challenge 2 分钟有效;登录会话固定 8 小时绝对到期 (不滑动续期)。二者都只在内存中,进程重启即丢失。
- 所有 PGP 用户同权:任何登记为 Clan 用户、且服务端 keyring 有可用签名公钥的 用户,登录后拥有相同的管理能力;应用不实现角色或权限分级。
7. 运行语义与限制
- 单进程串行写队列:所有写操作进入一个进程内队列串行执行。只有 queued 任务 可取消,Running 任务不可取消,没有自动回滚。
- 重启即失忆:任务历史、会话、challenge 都只存在于内存;重启后清空。
- 不显示 raw 日志:Tasks 页只显示任务类型、目标、状态、耗时、退出码与稳定 错误码,不 dump Clan/SOPS 的原始 stdout/stderr。明文永远不进入日志、任务输出、 URL、localStorage/sessionStorage 或服务端缓存。
- Clan 自动 commit,但本应用不 push:Clan CLI 会按变更路径自动 commit 到本地 仓库;本应用不会 push。更新成功只表示仓库发生变化,不代表目标机器已部署 或已应用。
- 删除/撤销的后果:删除 Secret 可能破坏仍引用它的 Nix/SOPS 配置;移除授权不会 清除已经分发到目标机器的明文;申请 inspection 不会改变任何授权。
- 不要并行运行外部 CLI:在 WebUI 写操作进行时,避免同时用其它
clan secrets命令修改同一仓库,以免产生非预期的仓库状态。 - 不要用真实
clan secrets get打印明文来“体检”:诊断请用clan secrets list、metadata 视图、任务状态与 inspection 入口;不要为了排障把真实明文输出到 终端或日志。 - inspection 的明文响应带
Cache-Control: no-store,成功后最多显示 60 秒,可手动 关闭或离开路由隐藏;这是尽力清理,不承诺浏览器端 zeroize,也不防御被攻陷的 浏览器或扩展。
示例中的路径一律是占位符;测试命令只指向隔离 fixtures,不要指向真实配置或真实 密钥仓库。不要读取或打印真实 secrets/keys。
8. 本机 Cargo mirror
.cargo/config.toml 是本机网络设置,已在 .gitignore 中被忽略,不应提交。当前
本机用它把 crates.io 换成 rsproxy 源(不含任何 credentials);其它开发者可按自己的
网络自行配置 mirror,项目不强制所有用户使用 rsproxy,也不要修改用户的全局
Cargo config。文档不复制本机实际配置。
9. 文档与验收状态
| 文件 | 内容 |
|---|---|
docs/environment.md |
实际工具版本、devenv 依赖与复现方式 |
docs/verification.md |
分层验收证据与真实浏览器验收记录 |
docs/browser-verification.md |
真实 Firefox/WebDriver BiDi 验收矩阵、红色证据与修复 |
docs/auth.md |
GPG challenge 登录、CLI 与配置细节 |
docs/inspection.md |
inspection 明文边界与隐私不变量 |
docs/sops-integration.md |
sops-nix user-mode 消费集成证据 |
docs/clan-capabilities.md |
M0 Clan CLI 能力矩阵 |
docs/PLAN.md / docs/SPEC.md |
实施计划与工程规格 |
CONTEXT.md |
领域术语 |
当前状态:unit / HTTP / 真实 CLI / sops-nix user-mode 集成均已真实运行并记录;浏览器内
完整登录与交互已由专职 interaction worker 在真实 Firefox(独立 profile、headless、
原生 WebDriver BiDi)中验收通过(矩阵见 docs/browser-verification.md)。即计划范围内
的工作已验收;未包含的仍是真实 clan-core 模块 eval、真实机器部署与 root NixOS 安装。
10. Nix 打包与导入
本仓库导出一个包含服务端、登录 CLI 与前端资源的 Nix flake。宿主机需要支持 flakes
的 Nix(nix-command + flakes 实验特性)。导出 x86_64-linux 与
aarch64-linux 的包,目前仅实际构建验证了 x86_64-linux。消费端不需要安装
Rust、dx 或手工拼装依赖。
nix build .#default # 等价别名:nix build .#clan-webui
产物是同一个 derivation,布局为:
bin/clan-webui # native fullstack server 的包装器
bin/clanwebuictl # 本地 GPG 登录确认 CLI
share/clan-webui/public/ # index.html、JS、Wasm 与内容哈希资产(assets/)
bin/clan-webui 的包装器把 clan(固定在 flake 的 Clan revision,并带
age/git/nix/sops/gnupg 运行时依赖)、age、git、nix、sops、gpg
放进 PATH,并设置 DIOXUS_PUBLIC_PATH 指向 store 中的 public/。它不改变工作目录,
因此 CLANWEBUI_CLAN_FLAKE 仍默认当前目录;CLANWEBUI_{CLANBIN,GPGBIN,SOPSBIN}
等环境变量仍可覆盖默认命令。运行时指定可访问的 Clan 仓库;仓库、密钥和运行身份由
部署环境提供,不会打进包中:
CLANWEBUI_CLAN_FLAKE=/path/to/clan-repo \
CLANWEBUI_CLAN_DIR=/path/to/clan-repo \
"$(nix build .#default --print-out-paths)/bin/clan-webui"
在 NixOS 配置中引用 flake input 的包即可,无需本项目提供 NixOS 模块:
environment.systemPackages = [
inputs.clan-webui.packages.${pkgs.stdenv.hostPlatform.system}.default
];
也可直接 import 得到同一个 derivation,而无需 flake-compat
或宿主 <nixpkgs>:
import ./clan-webui { system = "x86_64-linux"; }
default.nix 从仓库的 flake.lock 读取顶层已锁定输入(仅支持本 flake 使用的
github 与 tarball 类型),经 builtins.getFlake 重建同一依赖图后调用
flake.nix 的 outputs,与 nix build .#default 共用构建表达式和 pins。
可重复 smoke 检查(installed CLI、资源内容、启动服务后的 HTTP HTML/JS/Wasm/CSS):
nix flake check
# 或单独运行:nix build .#checks.x86_64-linux.service
当前固定工具链的 wasm-opt 优化步骤会报告非致命错误,Dioxus 随后继续完成构建。
产出的 Wasm 已通过有效性及编译检查;HTTP 检查逐字节核对所有静态资源。浏览器交互
不在这些 smoke 检查的覆盖范围内。
11. Harness 环境与安全边界(保留)
本项目使用 gjz010/harness 启动 OpenCode:
- harness 的 runtime 环境变量默认从
${XDG_CONFIG_HOME:-$HOME/.config}/gjz010/harness/env读取;需要固定其他位置时 设置GJZ010_HARNESS_ENV_FILE。该文件由用户自行创建与维护,是可执行 shell 代码,只放可信内容;默认文件不存在不阻止启动,显式指定的文件必须存在。 - 宿主的 OpenCode config、auth、data 与 cache 由 harness 共享,不由本项目隔离或 覆盖。
- 修改 config、skills、instructions、MCP、agents、commands、plugins 或安装包后, 需要重启 OpenCode;新建 chat session 不等于重载。
- 不要把秘密写入 Nix 表达式、store、日志或提交文件。
direnv allow由宿主人工审查 授权,agent 不代为执行,也不为绕过权限失败而放宽.git或 Nix 访问边界。 - 旧的 profile 选择机制(Git 身份匹配、
VIBE_DEVELOPER、项目内 bubblewrap 启动器、 项目内 skills 同步与签名)已整体移除,签名与 skills 由 harness 提供。