- run 新增 -s/--secret 字段过滤,只注入指定字段到子进程(最小权限) - run 新增 --dry-run 模式,输出变量名与来源映射,不执行命令、不暴露值 - run 新增 -o 参数,dry-run 默认 JSON 输出 - 默认输出格式改为始终 json,移除 TTY 自动切换逻辑,-o text 供人类使用 - build_injected_env_map 签名从 &[SecretField] 改为 &[&SecretField] - 更新 AGENTS.md、README.md、.vscode/tasks.json - version: 0.9.5 → 0.9.6 Made-with: Cursor
18 KiB
secrets
跨设备密钥与配置管理 CLI,基于 Rust + PostgreSQL 18。
将服务器信息、服务凭据统一存入数据库,供本地工具和 AI 读取上下文。每个敏感字段单独行存储(secrets 子表),字段名、类型、长度以明文保存便于 AI 理解,仅值本身使用 AES-256-GCM 加密;主密钥由 Argon2id 从主密码派生并存入系统钥匙串。
安装
cargo build --release
# 或从 Release 页面下载预编译二进制
已有旧版本时,可执行 secrets upgrade 自动下载最新版并替换。该命令会校验 Release 附带的 .sha256 摘要后再安装。
首次使用(每台设备各执行一次)
# 1. 配置数据库连接(会先验证连接可用再写入)
secrets config set-db "postgres://postgres:<password>@<host>:<port>/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。
第一步:发现有哪些数据
# 列出所有记录摘要(默认最多 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。
第二步:精确读取单条记录
# 精确定位(namespace + kind + name 三元组)
secrets search -n refining --kind service --name gitea
# 获取完整记录(含 secrets 字段名,无需 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 时,改用 run(只注入 token 字段到子进程)
secrets run -n refining --kind service --name gitea -s token -- ./deploy.sh
# 预览 run 会注入哪些变量(不执行命令)
secrets run -n refining --kind service --name gitea --dry-run
search 展示 metadata 与 secrets 的字段名,不展示 secret 值本身;需要 secret 值时用 run(仅注入加密字段到子进程,不含 metadata)。用 -s 指定只注入特定字段,最小化注入范围。
输出格式
| 场景 | 推荐命令 |
|---|---|
| AI 解析 / 管道处理(默认) | json(pretty-printed) |
| 管道紧凑格式 | -o json-compact |
| 注入 secrets 到子进程环境 | run |
| 人类查看 | -o text |
默认始终输出 JSON,无论是 TTY 还是管道。text 输出中时间按本地时区显示;json/json-compact 使用 UTC(RFC3339)。
# 默认 JSON 输出,直接可 jq 解析
secrets search -n refining --kind service | jq '.[].name'
# 需要 secrets 时,使用 run(-s 指定只注入特定字段)
secrets run -n refining --kind service --name gitea -s token -- ./deploy.sh
# 预览 run 会注入哪些变量(不执行命令)
secrets run -n refining --kind service --name gitea --dry-run
完整命令参考
# 查看帮助(包含各子命令 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=<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=<new>
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:<password>@<host>:<port>/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 # 预览将要执行的操作(不写入)
# ── run ───────────────────────────────────────────────────────────────────────
secrets run -n refining --kind service --name gitea -- ./deploy.sh # 注入全部 secrets
secrets run -n refining --kind service --name gitea -s token -- ./deploy.sh # 只注入 token 字段
secrets run -n refining --kind service --name aliyun \
-s access_key_id -s access_key_secret -- aliyun ecs DescribeInstances # 只注入指定字段
secrets run --tag production -- env # 按 tag 批量注入
secrets run -n refining --kind service --name gitea --dry-run # 预览变量映射
secrets run -n refining --kind service --name gitea -s token --dry-run # 过滤后预览
secrets run -n refining --kind service --name gitea --dry-run -o text # 人类可读预览
# ── 调试 ──────────────────────────────────────────────────────────────────────
secrets --verbose search -q mqtt
RUST_LOG=secrets=trace secrets search
数据模型
主表 entries(namespace、kind、name、tags、metadata)+ 子表 secrets(每个加密字段一行,含 field_name、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 | 明文,search 可见,AI 可推断 run 会注入哪些变量 |
| secrets | encrypted | 仅加密值本身,AES-256-GCM |
-m / --meta 写入 metadata,-s / --secret 写入 secrets 表的独立行。支持 key=value、key=@file、key:=<json>,也支持 credentials:content@./key.pem 这类嵌套字段文件写入;删除时支持 --remove-secret credentials:content。加解密使用主密钥(由 secrets init 设置)。
PEM 共享:同一 PEM 被多台服务器共享时,可存为 kind=key 记录,服务器通过 metadata.key_ref 引用;轮换只需 update 一条 key 记录,所有引用自动生效。详见 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 的记录
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
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"',但大多数字符串场景直接用=更直观。
示例:新增一条同时包含字符串、文件、布尔、数组、对象的记录
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 字段
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 环境变量。
-- 查看最近 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 # run,仅 secrets 逐字段解密 + key_ref 引用解析(不含 metadata)
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:
# 需有 ~/.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。