# secrets 跨设备密钥与配置管理 CLI,基于 Rust + PostgreSQL 18。 将服务器信息、服务凭据统一存入数据库,供本地工具和 AI 读取上下文。每个敏感字段单独行存储(`secrets` 子表),字段名、类型、长度以明文保存便于 AI 理解,仅值本身使用 AES-256-GCM 加密;主密钥由 Argon2id 从主密码派生并存入系统钥匙串。 ## 安装 ```bash cargo build --release # 或从 Release 页面下载预编译二进制 ``` 已有旧版本时,可执行 `secrets upgrade` 自动下载最新版并替换。该命令会校验 Release 附带的 `.sha256` 摘要后再安装。 ## 首次使用(每台设备各执行一次) ```bash # 1. 配置数据库连接(会先验证连接可用再写入) secrets config set-db "postgres://postgres:@:/secrets" # 2. 初始化主密钥(提示输入至少 8 位的主密码,派生后存入 OS 钥匙串) secrets init ``` 主密码不会存储,仅用于派生主密钥,且至少需 8 位。同一主密码在所有设备上会得到相同主密钥(salt 存于数据库,首台设备生成后共享)。 **主密钥存储**:macOS → Keychain;Windows → Credential Manager;Linux → keyutils(会话级,重启后需再次 `secrets init`)。 **从旧版(明文存储)升级**:升级后首次运行需执行 `secrets init` 即可(明文记录需手动重新 add 或通过 update 更新)。 ## AI Agent 快速指南 这个 CLI 以 AI 使用优先设计。核心路径只有一条:**读取用 `search`,写入用 `add` / `update`**。 ### 第一步:发现有哪些数据 ```bash # 列出所有记录摘要(默认最多 50 条,安全起步) secrets search --summary --limit 20 # 按 namespace 过滤 secrets search -n refining --summary --limit 20 # 按最近更新排序 secrets search --sort updated --limit 10 --summary ``` `--summary` 只返回轻量字段(namespace、kind、name、tags、desc、updated_at),不含完整 metadata 和 secrets。 ### 第二步:精确读取单条记录 ```bash # 精确定位(namespace + kind + name 三元组) secrets search -n refining --kind service --name gitea # 获取完整记录(含 secrets 字段 schema:field_name、field_type、value_len,无需 master_key) secrets search -n refining --kind service --name gitea -o json # 直接提取单个 metadata 字段值(最短路径) secrets search -n refining --kind service --name gitea -f metadata.url # 同时提取多个 metadata 字段 secrets search -n refining --kind service --name gitea \ -f metadata.url -f metadata.default_org # 需要 secrets 时,改用 inject / run secrets inject -n refining --kind service --name gitea secrets run -n refining --kind service --name gitea -- printenv ``` `search` 展示 metadata 与 secrets 的字段 schema(字段名、类型、长度),不展示 secret 值本身;需要值时用 `inject` / `run`。 ### 输出格式 | 场景 | 推荐命令 | |------|----------| | AI 解析 / 管道处理 | `-o json` 或 `-o json-compact` | | 注入 secrets 到环境变量 | `inject` / `run` | | 人类查看 | 默认 `text`(TTY 下自动启用) | | 非 TTY(管道/重定向) | 自动 `json-compact` | 说明:`text` 输出中的时间会按当前机器本地时区显示;`json/json-compact` 继续使用 UTC(RFC3339 风格)以便脚本和 AI 稳定解析。 ```bash # 管道直接 jq 解析(非 TTY 自动 json-compact) secrets search -n refining --kind service | jq '.[].name' # 需要 secrets 时,使用 inject / run secrets inject -n refining --kind service --name gitea > ~/.config/gitea/secrets.env secrets run -n refining --kind service --name gitea -- ./deploy.sh ``` ## 完整命令参考 ```bash # 查看帮助(包含各子命令 EXAMPLES) secrets --help secrets init --help # 主密钥初始化 secrets search --help secrets add --help secrets update --help secrets delete --help secrets config --help secrets upgrade --help # 检查并更新 CLI 版本 secrets export --help # 批量导出(JSON/TOML/YAML) secrets import --help # 批量导入(JSON/TOML/YAML) # ── search ────────────────────────────────────────────────────────────────── secrets search --summary --limit 20 # 发现概览 secrets search -n refining --kind service # 按 namespace + kind secrets search -n refining --kind service --name gitea # 精确查找 secrets search -q mqtt # 关键词模糊搜索 secrets search --tag hongkong # 按 tag 过滤 secrets search -n refining --kind service --name gitea -f metadata.url # 提取 metadata 字段 secrets search -n refining --kind service --name gitea -o json # 完整记录(含 secrets schema) secrets search --sort updated --limit 10 --summary # 最近改动 secrets search -n refining --summary --limit 10 --offset 10 # 翻页 # ── add ────────────────────────────────────────────────────────────────────── secrets add -n refining --kind server --name my-server \ --tag aliyun --tag shanghai \ -m ip=10.0.0.1 -m desc="Example ECS" \ -s username=root -s ssh_key=@./keys/server.pem # 多行文件直接写入嵌套 secret 字段 secrets add -n refining --kind server --name my-server \ -s credentials:content@./keys/server.pem # 使用 typed JSON 写入 secret(布尔、数字、数组、对象) secrets add -n refining --kind service --name deploy-bot \ -s enabled:=true \ -s retry_count:=3 \ -s scopes:='["repo","workflow"]' \ -s extra:='{"region":"ap-east-1","verify_tls":true}' secrets add -n refining --kind service --name gitea \ --tag gitea \ -m url=https://code.example.com -m default_org=myorg \ -s token= # ── update ─────────────────────────────────────────────────────────────────── secrets update -n refining --kind server --name my-server -m ip=10.0.0.1 secrets update -n refining --kind service --name gitea --add-tag production -s token= secrets update -n refining --kind service --name mqtt --remove-meta old_port --remove-secret old_key secrets update -n refining --kind server --name my-server --remove-secret credentials:content # ── delete ─────────────────────────────────────────────────────────────────── secrets delete -n refining --kind service --name legacy-mqtt # 精确删除单条(--kind 必填) secrets delete -n refining --dry-run # 预览批量删除(不写入) secrets delete -n ricnsmart # 批量删除整个 namespace secrets delete -n ricnsmart --kind server # 批量删除指定 kind # ── init ───────────────────────────────────────────────────────────────────── secrets init # 主密钥初始化(每台设备一次,主密码至少 8 位,派生后存钥匙串) # ── config ─────────────────────────────────────────────────────────────────── secrets config set-db "postgres://postgres:@:/secrets" # 先验证再写入 secrets config show # 密码脱敏展示 secrets config path # 打印配置文件路径 # ── upgrade ────────────────────────────────────────────────────────────────── secrets upgrade --check # 仅检查是否有新版本 secrets upgrade # 下载、校验 SHA-256 并安装最新版(可通过 SECRETS_UPGRADE_URL 自托管) # ── export ──────────────────────────────────────────────────────────────────── secrets export --file backup.json # 全量导出到 JSON secrets export -n refining --file refining.toml # 按 namespace 导出为 TOML secrets export -n refining --kind service --file svc.yaml # 按 kind 导出为 YAML secrets export --tag production --file prod.json # 按 tag 过滤 secrets export -q mqtt --file mqtt.json # 模糊搜索导出 secrets export --no-secrets --file schema.json # 仅导出 schema(无需主密钥) secrets export -n refining --format yaml # 输出到 stdout,指定格式 # ── import ──────────────────────────────────────────────────────────────────── secrets import backup.json # 导入(冲突时报错) secrets import --force refining.toml # 冲突时覆盖已有记录 secrets import --dry-run backup.yaml # 预览将要执行的操作(不写入) # ── 调试 ────────────────────────────────────────────────────────────────────── secrets --verbose search -q mqtt RUST_LOG=secrets=trace secrets search ``` ## 数据模型 主表 `entries`(namespace、kind、name、tags、metadata)+ 子表 `secrets`(每个加密字段一行,含 field_name、field_type、value_len、encrypted)。首次连接自动建表;同时创建 `audit_log`、`entries_history`、`secrets_history` 等表。 | 位置 | 字段 | 说明 | |------|------|------| | entries | namespace | 一级隔离,如 `refining`、`ricnsmart` | | entries | kind | 记录类型,如 `server`、`service`、`key`(可自由扩展) | | entries | name | 人类可读唯一标识 | | entries | tags | 多维标签,如 `["aliyun","hongkong"]` | | entries | metadata | 明文描述(ip、desc、domains、key_ref 等) | | secrets | field_name / field_type / value_len | 明文,search 可见,AI 可推断 inject 会生成什么变量 | | secrets | encrypted | 仅加密值本身,AES-256-GCM | `-m` / `--meta` 写入 `metadata`,`-s` / `--secret` 写入 `secrets` 表的独立行。支持 `key=value`、`key=@file`、`key:=`,也支持 `credentials:content@./key.pem` 这类嵌套字段文件写入;删除时支持 `--remove-secret credentials:content`。加解密使用主密钥(由 `secrets init` 设置)。 **PEM 共享**:同一 PEM 被多台服务器共享时,可存为 `kind=key` 记录,服务器通过 `metadata.key_ref` 引用;轮换只需 update 一条 key 记录,所有引用自动生效。详见 [AGENTS.md](AGENTS.md)。 ### `-m` / `--meta` JSON 语法速查 `-m` 和 `-s` 走的是同一套解析规则,只是写入位置不同:`-m` 写到明文 `metadata`,适合端口、开关、标签、描述性配置等非敏感信息。 | 目标值 | 写法示例 | 实际存入 | |------|------|------| | 普通字符串 | `-m url=https://code.example.com` | `"https://code.example.com"` | | 文件内容字符串 | `-m notes=@./service-notes.txt` | `"..."` | | 布尔值 | `-m enabled:=true` | `true` | | 数字 | `-m port:=3000` | `3000` | | `null` | `-m deprecated_at:=null` | `null` | | 数组 | `-m domains:='["code.example.com","git.example.com"]'` | `["code.example.com","git.example.com"]` | | 对象 | `-m tls:='{"enabled":true,"redirect_http":true}'` | `{"enabled":true,"redirect_http":true}` | | 嵌套路径 + JSON | `-m deploy:strategy:='{"type":"rolling","batch":2}'` | `{"deploy":{"strategy":{"type":"rolling","batch":2}}}` | 常见规则: - `=` 表示按字符串存储。 - `:=` 表示按 JSON 解析。 - shell 中数组和对象建议整体用单引号包住。 - 嵌套字段继续用冒号分隔:`-m runtime:max_open_conns:=20`。 示例:新增一条带 typed metadata 的记录 ```bash secrets add -n refining --kind service --name gitea \ -m url=https://code.example.com \ -m port:=3000 \ -m enabled:=true \ -m domains:='["code.example.com","git.example.com"]' \ -m tls:='{"enabled":true,"redirect_http":true}' ``` 示例:更新已有记录中的嵌套 metadata ```bash secrets update -n refining --kind service --name gitea \ -m deploy:strategy:='{"type":"rolling","batch":2}' \ -m runtime:max_open_conns:=20 ``` ### `-s` / `--secret` JSON 语法速查 当你希望写入的不是普通字符串,而是 `true`、`123`、`null`、数组或对象时,用 `:=`,右侧按 JSON 解析。 | 目标值 | 写法示例 | 实际存入 | |------|------|------| | 普通字符串 | `-s token=abc123` | `"abc123"` | | 文件内容字符串 | `-s ssh_key=@./id_ed25519` | `"-----BEGIN ..."` | | 布尔值 | `-s enabled:=true` | `true` | | 数字 | `-s retry_count:=3` | `3` | | `null` | `-s deprecated_at:=null` | `null` | | 数组 | `-s scopes:='["repo","workflow"]'` | `["repo","workflow"]` | | 对象 | `-s extra:='{"region":"ap-east-1","verify_tls":true}'` | `{"region":"ap-east-1","verify_tls":true}` | | 嵌套路径 + JSON | `-s auth:policy:='{"mfa":true,"ttl":3600}'` | `{"auth":{"policy":{"mfa":true,"ttl":3600}}}` | 常见规则: - `=` 表示按字符串存储,不做 JSON 解析。 - `:=` 表示按 JSON 解析,适合布尔、数字、数组、对象、`null`。 - shell 里对象和数组通常要整体加引号,推荐单引号:`-s flags:='["a","b"]'`。 - 嵌套字段继续用冒号分隔:`-s credentials:enabled:=true`。 - 如果你就是想存一个“JSON 字符串字面量”,可以写成 `-s note:='"hello"'`,但大多数字符串场景直接用 `=` 更直观。 示例:新增一条同时包含字符串、文件、布尔、数组、对象的记录 ```bash secrets add -n refining --kind service --name deploy-bot \ -s token=abc123 \ -s ssh_key=@./keys/deploy-bot.pem \ -s enabled:=true \ -s scopes:='["repo","workflow"]' \ -s policy:='{"ttl":3600,"mfa":true}' ``` 示例:更新已有记录中的嵌套 JSON 字段 ```bash secrets update -n refining --kind service --name deploy-bot \ -s auth:config:='{"issuer":"gitea","rotate":true}' \ -s auth:retry:=5 ``` ## 审计日志 `add`、`update`、`delete` 操作成功后自动向 `audit_log` 表写入一条记录,包含操作类型、操作对象和变更摘要(不含 secret 值)。操作者取自 `$USER` 环境变量。 ```sql -- 查看最近 20 条审计记录 SELECT action, namespace, kind, name, actor, detail, created_at FROM audit_log ORDER BY created_at DESC LIMIT 20; ``` ## 项目结构 ``` src/ main.rs # CLI 入口(clap),含各子命令 after_help 示例 output.rs # OutputMode 枚举 + TTY 检测 config.rs # 配置读写(~/.config/secrets/config.toml) db.rs # 连接池 + auto-migrate(entries + secrets + entries_history + secrets_history + audit_log + kv_config) crypto.rs # AES-256-GCM 加解密、Argon2id 派生、OS 钥匙串 models.rs # Entry + SecretField 结构体 audit.rs # 审计日志写入(audit_log 表) commands/ init.rs # 主密钥初始化(首次/新设备) add.rs # upsert entries + secrets 行,支持 -o json config.rs # config set-db/show/path search.rs # 多条件查询,展示 secrets schema(-f/-o/--summary/--limit/--offset/--sort) delete.rs # 删除(CASCADE 删除 secrets) update.rs # 增量更新(tags/metadata + secrets 行级 UPSERT/DELETE) rollback.rs # rollback / history:按 entry_version 恢复 run.rs # inject / run,逐字段解密 + key_ref 引用解析 upgrade.rs # 从 Gitea Release 自更新 export_cmd.rs # export:批量导出,支持 JSON/TOML/YAML,含解密明文 import_cmd.rs # import:批量导入,冲突检测,dry-run,重新加密写入 scripts/ setup-gitea-actions.sh # 配置 Gitea Actions 变量与 Secrets ``` ## CI/CD(Gitea Actions) 推送 `main` 分支时自动:fmt/clippy/test 检查 → Linux/macOS/Windows 构建 → 上传二进制与 `.sha256` 摘要 → 所有平台成功后发布 Release。 **首次使用需配置 Actions 变量和 Secrets:** ```bash # 需有 ~/.config/gitea/config.env(GITEA_URL、GITEA_TOKEN、GITEA_WEBHOOK_URL) ./scripts/setup-gitea-actions.sh ``` - `RELEASE_TOKEN`(Secret):Gitea PAT,用于创建 Release 上传二进制 - `WEBHOOK_URL`(Variable):飞书通知,可选 - **注意**:Secret/Variable 的 `data`/`value` 字段需传入原始值,不要 base64 编码 当前 Release 预编译产物覆盖: - Linux `x86_64-unknown-linux-musl` - macOS Apple Silicon `aarch64-apple-darwin` - macOS Intel `x86_64-apple-darwin`(由 ARM mac runner 交叉编译) - Windows `x86_64-pc-windows-msvc` 详见 [AGENTS.md](AGENTS.md)。