No description
  • CSS 65.6%
  • Rust 31.6%
  • Shell 1.7%
  • Nix 1%
  • Just 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
gjz010's OpenCode agent 094a11714e
feat(web): adopt daisyUI and add GPG paste login
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.
2026-09-27 22:29:29 +08:00
assets feat(web): adopt daisyUI and add GPG paste login 2026-09-27 22:29:29 +08:00
docs feat(web): adopt daisyUI and add GPG paste login 2026-09-27 22:29:29 +08:00
nix feat(nix): package server CLI and Dioxus assets 2026-09-27 19:47:47 +08:00
src feat(web): adopt daisyUI and add GPG paste login 2026-09-27 22:29:29 +08:00
tests feat(web): adopt daisyUI and add GPG paste login 2026-09-27 22:29:29 +08:00
.envrc Initial commit 2026-09-25 14:50:50 +00:00
.gitignore feat: implement Clan Secrets Manager 2026-09-27 18:16:35 +08:00
.muxrun.toml Initial commit 2026-09-25 14:50:50 +00:00
Cargo.lock feat: implement Clan Secrets Manager 2026-09-27 18:16:35 +08:00
Cargo.toml feat: implement Clan Secrets Manager 2026-09-27 18:16:35 +08:00
CONTEXT.md docs: keep CONTEXT.md at repository root 2026-09-26 02:24:46 +08:00
default.nix feat(nix): package server CLI and Dioxus assets 2026-09-27 19:47:47 +08:00
devenv.lock feat: implement Clan Secrets Manager 2026-09-27 18:16:35 +08:00
devenv.nix feat: implement Clan Secrets Manager 2026-09-27 18:16:35 +08:00
devenv.yaml feat: implement Clan Secrets Manager 2026-09-27 18:16:35 +08:00
Dioxus.toml feat: implement Clan Secrets Manager 2026-09-27 18:16:35 +08:00
flake.lock feat(nix): package server CLI and Dioxus assets 2026-09-27 19:47:47 +08:00
flake.nix feat(nix): package server CLI and Dioxus assets 2026-09-27 19:47:47 +08:00
justfile feat: implement Clan Secrets Manager 2026-09-27 18:16:35 +08:00
README.md feat(web): adopt daisyUI and add GPG paste login 2026-09-27 22:29:29 +08:00

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 提供。