feat: initialize ansible-vault secret repository

This commit is contained in:
2026-04-13 15:49:48 +08:00
commit 5c46775124
5 changed files with 192 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
*.log
*.retry
.env
.env.*
__pycache__/
*.pyc
.DS_Store
.vault_pass.txt
secrets/plaintext/

19
README.md Normal file
View File

@@ -0,0 +1,19 @@
# Agent Secret Vault
這個 repo 專門存放本地 AI agent 開發會用到的機密管理機制。
核心設計:
- 使用 `ansible-vault` 作為加密格式
- 加密檔可進 git
- vault password file 只放在本機
- 多個 agent 透過統一腳本存取 secrets
## 內容
- `scripts/vault.sh`初始化、檢視、編輯、加密、解密、rekey
- `docs/secret-vault.md`:使用說明與設計原則
- `secrets/vault.yml`:加密後 secrets 檔
## 目標
- 讓 Hermes / OpenClaw / cron worker / 其他本地 agent 共用同一套 secret storage contract
- 不把明文 secret 留在 repo
- 不讓每個 agent 各自發明一套 credential 管理方式

55
docs/secret-vault.md Normal file
View File

@@ -0,0 +1,55 @@
# Secret Vault
這個 repo 使用 `ansible-vault` 來保存開發過程中需要的機密資訊。
## 設計
- 加密檔:`secrets/vault.yml`
- 本機 vault password file`~/.config/continuous-ai-workflow-spec/vault-pass.txt`
- 管理腳本:`scripts/vault.sh`
## 原則
- 加密後的 `secrets/vault.yml` 可以進 git
- `vault-pass.txt` 只放在本機,不進 git
- 解密後的暫存 plaintext 檔不要提交
## 常用指令
初始化:
```bash
./scripts/vault.sh init
```
檢視:
```bash
./scripts/vault.sh view
```
編輯:
```bash
./scripts/vault.sh edit
```
把一份 plaintext YAML 加密成 vault
```bash
./scripts/vault.sh encrypt /tmp/my-secrets.yml
```
解密到暫存檔:
```bash
./scripts/vault.sh decrypt /tmp/vault.yml
```
重置 vault key
```bash
./scripts/vault.sh rekey
```
## 建議格式
```yaml
gitea:
base_url: https://gitea.cowbay.org
ssh_url_template: ssh://git@gitea.cowbay.org:2203/{owner}/{repo}.git
account: hermes
email: hermes@ntu.edu.rs
password: ...
api_token: ...
```

79
scripts/vault.sh Executable file
View File

@@ -0,0 +1,79 @@
#!/usr/bin/env bash
set -euo pipefail
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
VAULT_FILE="${VAULT_FILE:-$REPO_DIR/secrets/vault.yml}"
VAULT_PASS_FILE="${VAULT_PASS_FILE:-$HOME/.config/continuous-ai-workflow-spec/vault-pass.txt}"
usage() {
cat <<EOF
用法:
scripts/vault.sh init 初始化 vault password file若不存在
scripts/vault.sh view 檢視加密檔內容
scripts/vault.sh edit 編輯加密檔內容
scripts/vault.sh encrypt FILE 將檔案加密成 ansible-vault 格式
scripts/vault.sh decrypt OUT 解密到指定輸出檔
scripts/vault.sh rekey 重新加密並更新 key
EOF
}
ensure_pass() {
mkdir -p "$(dirname "$VAULT_PASS_FILE")"
chmod 700 "$(dirname "$VAULT_PASS_FILE")" || true
if [ ! -f "$VAULT_PASS_FILE" ]; then
umask 177
python3 - <<'PY' > "$VAULT_PASS_FILE"
import secrets
print(secrets.token_urlsafe(48))
PY
chmod 600 "$VAULT_PASS_FILE"
echo "已建立 vault password file: $VAULT_PASS_FILE"
fi
}
cmd="${1:-}"
case "$cmd" in
init)
ensure_pass
;;
view)
ensure_pass
ansible-vault view "$VAULT_FILE" --vault-password-file "$VAULT_PASS_FILE"
;;
edit)
ensure_pass
[ -f "$VAULT_FILE" ] || { echo "找不到 $VAULT_FILE"; exit 1; }
ansible-vault edit "$VAULT_FILE" --vault-password-file "$VAULT_PASS_FILE"
;;
encrypt)
ensure_pass
src="${2:-}"
[ -n "$src" ] || { usage; exit 1; }
cp "$src" "$VAULT_FILE"
ansible-vault encrypt "$VAULT_FILE" --vault-password-file "$VAULT_PASS_FILE"
;;
decrypt)
ensure_pass
out="${2:-}"
[ -n "$out" ] || { usage; exit 1; }
ansible-vault decrypt "$VAULT_FILE" --output "$out" --vault-password-file "$VAULT_PASS_FILE"
chmod 600 "$out" || true
;;
rekey)
ensure_pass
tmp_new="$(mktemp)"
chmod 600 "$tmp_new"
python3 - <<'PY' > "$tmp_new"
import secrets
print(secrets.token_urlsafe(48))
PY
ansible-vault rekey "$VAULT_FILE" --vault-password-file "$VAULT_PASS_FILE" --new-vault-password-file "$tmp_new"
mv "$tmp_new" "$VAULT_PASS_FILE"
chmod 600 "$VAULT_PASS_FILE"
echo "已更新 vault key: $VAULT_PASS_FILE"
;;
*)
usage
exit 1
;;
esac

30
secrets/vault.yml Normal file
View File

@@ -0,0 +1,30 @@
$ANSIBLE_VAULT;1.1;AES256
35373933393361653231623331383037626334666462366635386261346662316463366264363535
3263613961616637633466646661396438383231353334360a363330343736613232626333316337
62376637633062333866343039363862613361353266326532663337663137306130633465383063
6437316262346635340a653337363536613363613762613362323636316564323433653533343963
32633532666465613930353636333533336432333261653362633738316333313532366637346265
35363138306634323462393665336330393731323132356266323037303838306233303832383337
36613264376538343030353361343638393835613465303665383135326539653434346233663036
63383536613331346631323835333933636365346465313239363536613532633837383639663832
32663730626266356364383564386634613463313833363963353832333335393339386164633138
36343065363965326237633066306137643432333836303561343530356564313465353332353634
63383338383631343239653563656632393833323033383738373236356563636361373133343065
66653239353930663633626163653830626434313132396637663635663538326166383335323365
31333833333261303433393563363266376465303431363031346234313433663737306133613836
31646134333333303864303836656235623834356131663764666330326538646265636435326266
36313839393335373231363330343664633364663536376566343964633039663037646133666464
61623837656633323332646236346362373861653237636461363366333139663531373761373464
66633536303332633464396333636164613064633462376166666463353138376231376433396361
61316665386363393163306530633966636638616261653034313430623933363135343236663137
32363634303063646264663630376532313833393665386433666533613635333432323936616533
66313031313933303032303435656436343233653966663964333635353832663631363337386539
63313339313031383964653637373566336134613338346465323235316131396131353535343332
34643936633864323130353561303933633937626234353233373033333137666565383930626535
39626561653836633661333736623934393437303934346361313265663736663037626332663534
37313139623364373235333164313662333933616630626561333061653739613665613865366537
64643132373130666233383335343361623239363232363133356233633764663435396161363038
37363962323563343836653965633935316236666434326236623362663536633136346637343637
66663237333537333738353730306539313533363635343836656262373336646262333733636564
63633934343264396331363265343166383565373630633432343337306130396464336463643435
33386332396336653336313438336563663537303364323765633062333163363735