# Secrets CLI — AGENTS.md ## 提交 / 发版硬规则(优先于下文其他说明) 1. 涉及 `src/**`、`Cargo.toml`、`Cargo.lock`、CLI 行为变更的提交,默认视为**需要发版**,除非用户明确说明“本次不发版”。 2. 发版前必须先检查 `Cargo.toml` 中的 `version`,再检查是否已存在对应 tag:`git tag -l 'secrets-*'`。 3. 若当前版本对应 tag 已存在,必须先 bump `Cargo.toml` 的 `version`,再执行 `cargo build` 同步 `Cargo.lock`,然后才能提交。 4. 提交前优先运行 `./scripts/release-check.sh`;该脚本会检查重复版本并执行 `cargo fmt -- --check && cargo clippy --locked -- -D warnings && cargo test --locked`。 跨设备密钥与配置管理 CLI 工具,将服务器信息、服务凭据等存储到 PostgreSQL 18,供 AI 工具读取上下文。每个加密字段单独行存储(`secrets` 子表),字段名、类型、长度以明文保存,主密钥由 Argon2id 从主密码派生并存入平台安全存储(macOS Keychain / Windows Credential Manager / Linux keyutils)。 ## 项目结构 ``` secrets/ src/ main.rs # CLI 入口,clap 命令定义,auto-migrate,--verbose 全局参数 output.rs # OutputMode 枚举 + TTY 检测(TTY→text,非 TTY→json-compact) config.rs # 配置读写:~/.config/secrets/config.toml(database_url) db.rs # PgPool 创建 + 建表/索引(DROP+CREATE,含所有表) crypto.rs # AES-256-GCM 加解密、Argon2id 派生、OS 钥匙串 models.rs # Entry + SecretField 结构体(sqlx::FromRow + serde) audit.rs # 审计写入:log_tx(事务内) commands/ init.rs # init 命令:主密钥初始化(每台设备一次) add.rs # add 命令:upsert entries + 逐字段写入 secrets,含历史快照 config.rs # config 命令:set-db / show / path(持久化 database_url) search.rs # search 命令:多条件查询,展示 secrets 字段 schema(无需 master_key) delete.rs # delete 命令:事务化,CASCADE 删除 secrets,含历史快照 update.rs # update 命令:增量更新,secrets 行级 UPSERT/DELETE,CAS 并发保护 rollback.rs # rollback 命令:按 entry_version 恢复 entry + secrets history.rs # history 命令:查看 entry 变更历史列表 run.rs # inject / run 命令:逐字段解密 + key_ref 引用解析 upgrade.rs # upgrade 命令:检查、校验摘要并下载最新版本,自动替换二进制 export_cmd.rs # export 命令:批量导出记录,支持 JSON/TOML/YAML,含解密明文 import_cmd.rs # import 命令:批量导入记录,冲突检测,dry-run,重新加密写入 scripts/ release-check.sh # 发版前检查版本号/tag 是否重复,并执行 fmt/clippy/test setup-gitea-actions.sh # 配置 Gitea Actions 变量与 Secrets .gitea/workflows/ secrets.yml # CI:fmt + clippy + musl 构建 + Release 上传 + 飞书通知 .vscode/tasks.json # 本地测试任务(build / config / search / add+delete / update / audit 等) ``` ## 数据库 - **Host**: `:` - **Database**: `secrets` - **连接串**: `postgres://postgres:@:/secrets` - **表**: `entries`(主表)+ `secrets`(加密字段子表)+ `entries_history` + `secrets_history` + `audit_log` + `kv_config`,首次连接自动建表(auto-migrate) ### 表结构 ```sql entries ( id UUID PRIMARY KEY DEFAULT uuidv7(), -- PG18 时间有序 UUID namespace VARCHAR(64) NOT NULL, -- 一级隔离: "refining" | "ricnsmart" kind VARCHAR(64) NOT NULL, -- 类型: "server" | "service" | "key"(可扩展) name VARCHAR(256) NOT NULL, -- 人类可读标识 tags TEXT[] NOT NULL DEFAULT '{}', -- 灵活标签: ["aliyun","hongkong"] metadata JSONB NOT NULL DEFAULT '{}', -- 明文描述: ip, desc, domains, location... version BIGINT NOT NULL DEFAULT 1, -- 乐观锁版本号,每次写操作自增 created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE(namespace, kind, name) ) ``` ```sql secrets ( id UUID PRIMARY KEY DEFAULT uuidv7(), entry_id UUID NOT NULL REFERENCES entries(id) ON DELETE CASCADE, field_name VARCHAR(256) NOT NULL, -- 明文字段名: "username", "token", "ssh_key" encrypted BYTEA NOT NULL DEFAULT '\x', -- 仅加密值本身:nonce(12B)||ciphertext+tag version BIGINT NOT NULL DEFAULT 1, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE(entry_id, field_name) ) ``` ```sql kv_config ( key TEXT PRIMARY KEY, -- 如 'argon2_salt' value BYTEA NOT NULL -- Argon2id salt,首台设备 init 时生成 ) ``` ### audit_log 表结构 ```sql audit_log ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, action VARCHAR(32) NOT NULL, -- 'add' | 'update' | 'delete' | 'rollback' namespace VARCHAR(64) NOT NULL, kind VARCHAR(64) NOT NULL, name VARCHAR(256) NOT NULL, detail JSONB NOT NULL DEFAULT '{}', -- 变更摘要(tags/meta keys/secret keys,不含 value) actor VARCHAR(128) NOT NULL DEFAULT '', -- 操作者($USER 环境变量) created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ) ``` ### entries_history 表结构 ```sql entries_history ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, entry_id UUID NOT NULL, namespace VARCHAR(64) NOT NULL, kind VARCHAR(64) NOT NULL, name VARCHAR(256) NOT NULL, version BIGINT NOT NULL, -- 被快照时的版本号 action VARCHAR(16) NOT NULL, -- 'add' | 'update' | 'delete' | 'rollback' tags TEXT[] NOT NULL DEFAULT '{}', metadata JSONB NOT NULL DEFAULT '{}', actor VARCHAR(128) NOT NULL DEFAULT '', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ) ``` ### secrets_history 表结构 ```sql secrets_history ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, entry_id UUID NOT NULL, secret_id UUID NOT NULL, -- 对应 secrets.id entry_version BIGINT NOT NULL, -- 关联 entries_history 的版本号 field_name VARCHAR(256) NOT NULL, encrypted BYTEA NOT NULL DEFAULT '\x', action VARCHAR(16) NOT NULL, -- 'add' | 'update' | 'delete' | 'rollback' actor VARCHAR(128) NOT NULL DEFAULT '', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ) ``` ### 字段职责划分 | 字段 | 存什么 | 示例 | |------|--------|------| | `namespace` | 项目/团队隔离 | `refining`, `ricnsmart` | | `kind` | 记录类型 | `server`, `service`, `key` | | `name` | 唯一标识名 | `i-example0abcd1234efgh`, `gitea` | | `tags` | 多维分类标签 | `["aliyun","hongkong","ricn"]` | | `metadata` | 明文非敏感信息 | `{"ip":"192.0.2.1","desc":"Grafana","key_ref":"my-shared-key"}` | | `secrets.field_name` | 加密字段名(明文) | `"username"`, `"token"`, `"ssh_key"` | | `secrets.encrypted` | 仅加密值本身 | AES-256-GCM 密文 | ### PEM 共享机制(key_ref) 同一 PEM 被多台服务器共享时,将 PEM 存为独立的 `kind=key` 记录,服务器通过 `metadata.key_ref` 引用: ```bash # 1. 存共享 PEM secrets add -n refining --kind key --name my-shared-key \ --tag aliyun --tag hongkong \ -s content=@./keys/my-shared-key.pem # 2. 服务器通过 metadata.key_ref 引用(inject/run 时自动合并 key 的 secrets) secrets add -n refining --kind server --name i-example0xyz789 \ -m ip=192.0.2.1 -m key_ref=my-shared-key \ -s username=ecs-user # 3. 轮换只需更新 key 记录,所有引用服务器自动生效 secrets update -n refining --kind key --name my-shared-key \ -s content=@./keys/new-key.pem ``` ## 数据库配置 首次使用需显式配置数据库连接,设置一次后在该设备上持久生效: ```bash secrets config set-db "postgres://postgres:@:/secrets" secrets config show # 查看当前配置(密码脱敏) secrets config path # 打印配置文件路径 ``` `set-db` 会先验证连接可用,成功后才写入配置文件;连接失败时提示 "Database connection failed" 且不修改配置。 配置文件:`~/.config/secrets/config.toml`,权限 0600。`--db-url` 参数可一次性覆盖。 ## 主密钥与加密 首次使用(每台设备各执行一次): ```bash secrets config set-db "postgres://postgres:@:/secrets" secrets init # 提示输入主密码,Argon2id 派生主密钥后存入 OS 钥匙串 ``` 主密码不存储;salt 存于 `kv_config`,首台设备生成后共享,确保同一主密码在所有设备派生出相同主密钥。 主密钥存储后端:macOS Keychain、Windows Credential Manager、Linux keyutils(会话级,重启后需再次 `secrets init`)。 **从旧版(明文 JSONB)升级**:升级后执行 `secrets init` 即可(明文记录需手动重新 add 或通过 update 更新)。 ## CLI 命令 ### AI 使用主路径 **读取一律用 `search`,写入用 `add` / `update`,避免反复查帮助。** 输出格式规则: - TTY(终端直接运行)→ 默认 `text` - 非 TTY(管道/重定向/AI 调用)→ 自动 `json-compact` - 显式 `-o json` → 美化 JSON --- ### init — 主密钥初始化(每台设备一次) ```bash # 首次设备:生成 Argon2id salt 并存库,派生主密钥后存 OS 钥匙串 secrets init # 后续设备:复用已有 salt,派生主密钥后存钥匙串(主密码需与首台相同) secrets init ``` ### search — 发现与读取 ```bash # 参数说明(带典型值) # -n / --namespace refining | ricnsmart # --kind server | service # --name gitea | i-example0abcd1234efgh | mqtt # --tag aliyun | hongkong | production # -q / --query mqtt | grafana | gitea (模糊匹配 name/namespace/kind/tags/metadata) # secrets schema search 默认展示 secrets 字段名、类型与长度(无需 master_key) # -f / --field metadata.ip | metadata.url | metadata.default_org # --summary 不带值的 flag,仅返回摘要(name/tags/desc/updated_at) # --limit 20 | 50(默认 50) # --offset 0 | 10 | 20(分页偏移) # --sort name(默认)| updated | created # -o / --output text | json | json-compact # 发现概览(起步推荐) secrets search --summary --limit 20 secrets search -n refining --summary --limit 20 secrets search --sort updated --limit 10 --summary # 精确定位单条记录 secrets search -n refining --kind service --name gitea secrets search -n refining --kind server --name i-example0abcd1234efgh # 精确定位并获取完整内容(secrets 保持加密占位) secrets search -n refining --kind service --name gitea -o json # 直接提取 metadata 字段值(最短路径) secrets search -n refining --kind service --name gitea -f metadata.url 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 # 模糊关键词搜索 secrets search -q mqtt secrets search -q grafana secrets search -q 192.0.2 # 按条件过滤 secrets search -n refining --kind service secrets search -n ricnsmart --kind server secrets search --tag hongkong secrets search --tag aliyun --summary # 分页 secrets search -n refining --summary --limit 10 --offset 0 secrets search -n refining --summary --limit 10 --offset 10 # 管道 / AI 调用(非 TTY 自动 json-compact) secrets search -n refining --kind service | jq '.[].name' ``` --- ### add — 新增或全量覆盖(upsert) ```bash # 参数说明(带典型值) # -n / --namespace refining | ricnsmart # --kind server | service # --name gitea | i-example0abcd1234efgh # --tag aliyun | hongkong(可重复) # -m / --meta ip=10.0.0.1 | desc="ECS" | url=https://... | tls:cert@./cert.pem(可重复) # -s / --secret token= | ssh_key=@./key.pem | password=secret123 | credentials:content@./key.pem(可重复) # 添加服务器 secrets add -n refining --kind server --name i-example0abcd1234efgh \ --tag aliyun --tag shanghai \ -m ip=10.0.0.1 -m desc="Aliyun Shanghai ECS" \ -s username=root -s ssh_key=@./keys/deploy-key.pem # 添加服务凭据 secrets add -n refining --kind service --name gitea \ --tag gitea \ -m url=https://code.example.com -m default_org=refining -m username=voson \ -s token= -s runner_token= # 从文件读取 token secrets add -n ricnsmart --kind service --name mqtt \ -m host=mqtt.example.com -m port=1883 \ -s password=@./mqtt_password.txt # 多行文件直接写入嵌套 secret 字段 secrets add -n refining --kind server --name i-example0abcd1234efgh \ -s credentials:content@./keys/deploy-key.pem # 使用类型化值(key:=)存储非字符串类型 secrets add -n refining --kind service --name prometheus \ -m scrape_interval:=15 \ -m enabled:=true \ -m labels:='["prod","metrics"]' \ -s api_key=abc123 ``` --- ### update — 增量更新(记录必须已存在) 只有传入的字段才会变动,其余全部保留。 ```bash # 参数说明(带典型值) # -n / --namespace refining | ricnsmart # --kind server | service # --name gitea | i-example0abcd1234efgh # --add-tag production | backup(不影响已有 tag,可重复) # --remove-tag staging | deprecated(可重复) # -m / --meta ip=10.0.0.1 | desc="新描述" | credentials:username=root(新增或覆盖,可重复) # --remove-meta old_port | legacy_key | credentials:content(删除 metadata 字段,可重复) # -s / --secret token= | ssh_key=@./new.pem | credentials:content@./new.pem(新增或覆盖,可重复) # --remove-secret old_password | deprecated_key | credentials:content(删除 secret 字段,可重复) # 更新单个 metadata 字段 secrets update -n refining --kind server --name i-example0abcd1234efgh \ -m ip=10.0.0.1 # 轮换 token secrets update -n refining --kind service --name gitea \ -s token= # 新增 tag 并轮换 token 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_password # 从文件更新嵌套 secret 字段 secrets update -n refining --kind server --name i-example0abcd1234efgh \ -s credentials:content@./keys/deploy-key.pem # 删除嵌套字段 secrets update -n refining --kind server --name i-example0abcd1234efgh \ --remove-secret credentials:content # 移除 tag secrets update -n refining --kind service --name gitea --remove-tag staging ``` --- ### delete — 删除记录(支持单条精确删除与批量删除) 删除时会自动将 entry 与所有关联 secret 字段快照到历史表,并写入审计日志,可通过 `rollback` 命令恢复。 ```bash # 参数说明(带典型值) # -n / --namespace refining | ricnsmart(必填) # --kind server | service(指定 --name 时必填;批量时可选) # --name gitea | i-example0abcd1234efgh(精确匹配;省略则批量删除) # --dry-run 预览将删除的记录,不实际写入(仅批量模式有效) # -o / --output text | json | json-compact # 精确删除单条记录(--kind 必填) secrets delete -n refining --kind service --name legacy-mqtt secrets delete -n ricnsmart --kind server --name i-old-server-id # 预览批量删除(不写入数据库) secrets delete -n refining --dry-run secrets delete -n ricnsmart --kind server --dry-run # 批量删除整个 namespace 的所有记录 secrets delete -n ricnsmart # 批量删除 namespace 下指定 kind 的所有记录 secrets delete -n ricnsmart --kind server # JSON 输出 secrets delete -n refining --kind service -o json ``` --- ### history — 查看变更历史 ```bash # 参数说明 # -n / --namespace refining | ricnsmart # --kind server | service # --name 记录名 # --limit 返回条数(默认 20) # 查看某条记录的历史版本列表 secrets history -n refining --kind service --name gitea # 查最近 5 条 secrets history -n refining --kind service --name gitea --limit 5 # JSON 输出 secrets history -n refining --kind service --name gitea -o json ``` --- ### rollback — 回滚到指定版本 ```bash # 参数说明 # -n / --namespace refining | ricnsmart # --kind server | service # --name 记录名 # --to-version 目标版本号(省略则恢复最近一次快照) # 撤销上次修改(回滚到最近一次快照) secrets rollback -n refining --kind service --name gitea # 回滚到版本 3 secrets rollback -n refining --kind service --name gitea --to-version 3 ``` --- ### inject — 输出临时环境变量 敏感值仅打印到 stdout,不持久化、不写入当前 shell。 ```bash # 参数说明 # -n / --namespace refining | ricnsmart # --kind server | service # --name 记录名 # --tag 按 tag 过滤(可重复) # --prefix 变量名前缀(留空则以记录 name 作前缀) # -o / --output text(默认 KEY=VALUE)| json | json-compact # 打印单条记录的所有变量(KEY=VALUE 格式) secrets inject -n refining --kind service --name gitea # 自定义前缀 secrets inject -n refining --kind service --name gitea --prefix GITEA # JSON 格式(适合管道或脚本解析) secrets inject -n refining --kind service --name gitea -o json # eval 注入当前 shell(谨慎使用) eval $(secrets inject -n refining --kind service --name gitea) ``` --- ### run — 向子进程注入 secrets 并执行命令 secrets 仅作用于子进程环境,不修改当前 shell,进程退出码透传。 ```bash # 参数说明 # -n / --namespace refining | ricnsmart # --kind server | service # --name 记录名 # --tag 按 tag 过滤(可重复) # --prefix 变量名前缀 # -- 执行的命令及参数 # 向脚本注入单条记录的 secrets secrets run -n refining --kind service --name gitea -- ./deploy.sh # 按 tag 批量注入(多条记录合并) secrets run --tag production -- env | grep -i token # 验证注入了哪些变量 secrets run -n refining --kind service --name gitea -- printenv ``` --- ### upgrade — 自动更新 CLI 二进制 从 Release 服务器下载最新版本,校验对应 `.sha256` 摘要后替换当前二进制,无需数据库连接或主密钥。 **配置方式**:`SECRETS_UPGRADE_URL` 必填。优先用**构建时**:`SECRETS_UPGRADE_URL=https://... cargo build`,CI 已自动注入。或**运行时**:写在 `.env` 或 `export` 后执行。 ```bash # 检查是否有新版本(不下载) secrets upgrade --check # 下载、校验 SHA-256 并安装最新版本 secrets upgrade ``` --- ### export — 批量导出记录 将匹配的记录(含解密后的明文 secrets)导出到文件或 stdout。支持 JSON、TOML、YAML 三种格式,文件格式由扩展名自动推断。使用 `--no-secrets` 时无需主密钥。 ```bash # 参数说明 # -n / --namespace refining | ricnsmart # --kind server | service # --name gitea | i-example0abcd1234efgh # --tag aliyun | production(可重复) # -q / --query 模糊关键词 # --file 输出文件路径,格式由扩展名推断(.json / .toml / .yaml / .yml) # --format json | toml | yaml 显式指定格式(输出到 stdout 时必须指定) # --no-secrets 不导出 secrets,无需主密钥 # 全量导出到 JSON 文件 secrets export --file backup.json # 按 namespace 导出为 TOML secrets export -n refining --file refining.toml # 按 kind 导出为 YAML secrets export -n refining --kind service --file services.yaml # 按 tag 过滤导出 secrets export --tag production --file prod.json # 模糊关键词导出 secrets export -q mqtt --file mqtt.json # 仅导出 schema(不含 secrets,无需主密钥) secrets export --no-secrets --file schema.json # 输出到 stdout(必须指定 --format) secrets export -n refining --format yaml secrets export --format json | jq '.' ``` --- ### import — 批量导入记录 从导出文件读取记录并写入数据库,自动重新加密 secrets。支持 JSON、TOML、YAML 三种格式,文件格式由扩展名自动推断。 ```bash # 参数说明 # 必选,输入文件路径(格式由扩展名推断) # --force 冲突时覆盖已有记录(默认:报错并停止) # --dry-run 预览将执行的操作,不写入数据库 # -o / --output text | json | json-compact # 导入 JSON 文件(遇到已存在记录报错) secrets import backup.json # 导入 TOML 文件,冲突时覆盖 secrets import --force refining.toml # 导入 YAML 文件,冲突时覆盖 secrets import --force services.yaml # 预览将执行的操作(不写入) secrets import --dry-run backup.json # JSON 格式输出导入摘要 secrets import backup.json -o json ``` --- ### config — 配置管理(无需主密钥) ```bash # 设置数据库连接(每台设备执行一次,之后永久生效;先验证连接可用再写入) secrets config set-db "postgres://postgres:@:/secrets" # 查看当前配置(密码脱敏) secrets config show # 打印配置文件路径 secrets config path # 输出: /Users//.config/secrets/config.toml ``` --- ### 全局参数 ```bash # debug 日志(位于子命令之前) secrets --verbose search -q mqtt secrets -v add -n refining --kind service --name gitea -m url=xxx -s token=yyy # 或通过环境变量精细控制 RUST_LOG=secrets=trace secrets search # 一次性覆盖数据库连接 secrets --db-url "postgres://..." search -n refining ``` ## 代码规范 - 错误处理:统一使用 `anyhow::Result`,不用 `unwrap()` - 异步:全程 `tokio`,数据库操作 `sqlx` async - SQL:使用 `sqlx::query` / `sqlx::query_as` 绑定参数,禁止字符串拼接(搜索的动态 WHERE 子句除外,需使用参数绑定 `$1/$2`) - 新增 `kind` 类型时:只需在 `add` 调用时传入,无需改代码 - 字段命名:CLI 短标志 `-n`=namespace,`-m`=meta,`-s`=secret,`-q`=query,`-v`=verbose,`-f`=field,`-o`=output - 日志:用户可见输出用 `println!`;调试/运维信息用 `tracing::debug!`/`info!`/`warn!`/`error!` - 审计:`add`/`update`/`delete` 成功后调用 `audit::log_tx`,写入 `audit_log` 表;失败只 warn 不中断 - 加密:`encrypted` 列存储 AES-256-GCM 密文;`add`/`update`/`search`/`delete` 需主密钥(`secrets init` 后从 OS 钥匙串加载) - 输出:读命令通过 `OutputMode` 支持 text/json/json-compact/env;写命令 `add` 同样支持 `-o json` ## 提交前检查(必须全部通过) 每次提交代码前,请在本地依次执行以下检查,**全部通过后再 push**: 优先使用: ```bash ./scripts/release-check.sh ``` 它等价于先检查版本号 / tag,再执行下面的格式、Lint、测试。 ### 1. 版本号(按需) 若本次改动需要发版,请先确认 `Cargo.toml` 中的 `version` 已提升,避免 CI 打出的 Tag 与已有版本重复。**升级版本后需同时更新 `Cargo.lock`**(运行 `cargo build` 即可自动同步),否则 CI 中 `cargo clippy --locked` 会因 lock 与 manifest 不一致而失败。可通过 git tag 判断: ```bash # 查看当前 Cargo.toml 版本 grep '^version' Cargo.toml # 查看是否已存在该版本对应的 tag(CI 使用格式 secrets-) git tag -l 'secrets-*' ``` 若当前版本已被 tag(例如已有 `secrets-0.3.0` 且 `Cargo.toml` 仍为 `0.3.0`),则应在 `Cargo.toml` 中 bump 版本号,再执行 `cargo build` 同步 `Cargo.lock`,最后一并提交,以便 CI 自动打新 Tag 并发布 Release。 ### 2. 格式、Lint、测试 ```bash cargo fmt -- --check # 格式检查(不通过则运行 cargo fmt 修复) cargo clippy -- -D warnings # Lint 检查(消除所有 warning) cargo test # 单元/集成测试 ``` 或一次性执行: ```bash cargo fmt -- --check && cargo clippy -- -D warnings && cargo test ``` ## CI/CD - Gitea Actions(runners: debian / darwin-arm64 / windows) - 触发:`src/**`、`Cargo.toml`、`Cargo.lock` 变更推送到 main - 构建目标:`x86_64-unknown-linux-musl`、`aarch64-apple-darwin`、`x86_64-apple-darwin`(由 ARM mac runner 交叉编译)、`x86_64-pc-windows-msvc` - 新版本自动打 Tag(格式 `secrets-`)并上传二进制与对应 `.sha256` 摘要到 Gitea Release - Release 仅在 Linux/macOS/Windows 构建全部成功后才会从 draft 发布 - 通知:飞书 Webhook(`vars.WEBHOOK_URL`) - 所需 secrets/vars:`RELEASE_TOKEN`(Release 上传,Gitea PAT)、`vars.WEBHOOK_URL`(通知,可选) - **注意**:Gitea Actions 的 Secret/Variable 创建时,`data`/`value` 字段需传入**原始值**,不要使用 base64 编码 ## 环境变量 | 变量 | 说明 | |------|------| | `RUST_LOG` | 日志级别,如 `secrets=debug`、`secrets=trace`(默认 warn) | | `USER` | 审计日志 actor 字段来源,Shell 自动设置,通常无需手动配置 | | `SECRETS_UPGRADE_URL` | upgrade 的 Release API 地址。构建时(cargo build)或运行时(.env/export) | 数据库连接通过 `secrets config set-db` 持久化到 `~/.config/secrets/config.toml`,不支持环境变量。