diff --git a/docs/agent-install-runbook.md b/docs/agent-install-runbook.md index f1031b8..0ec52b9 100644 --- a/docs/agent-install-runbook.md +++ b/docs/agent-install-runbook.md @@ -64,26 +64,39 @@ PLACEHOLDER ~/.config/vault-pass.txt ``` -安裝來源是 repo 內的密碼保護壓縮檔: - -```text -secrets/vault-pass.txt.zip -``` - -執行: +執行 installer: ```bash cd ~/projects/agent-secret-vault ./scripts/install-vault-pass.sh ``` -安裝過程會要求使用者手動輸入壓縮檔密碼;解壓成功後會寫入: +installer 會先判斷 `~/.config/vault-pass.txt` 是否已存在: -```text -~/.config/vault-pass.txt -``` +- 若已存在:保留現有檔案、修正權限為 `600`,並嘗試驗證能否解開 `secrets/vault.yml`。 +- 若不存在:提示使用者選擇 4 種建立方式。 -權限應為 `600`。 +### 缺檔時的 4 種方式 + +1. **建立新密碼並初始化 placeholder vault** + - 適合全新 repo / 全新環境。 + - installer 會產生新的 `~/.config/vault-pass.txt`。 + - 若 `secrets/vault.yml` 不存在,會建立加密 placeholder。 + - 若既有 `secrets/vault.yml` 無法用新密碼解開,installer 不會覆蓋它,避免破壞既有 secrets。 + +2. **使用者自行輸入 vault-pass.txt 內容** + - installer 會用 hidden input 讀取一行密碼內容。 + - 寫入 `~/.config/vault-pass.txt`,權限設為 `600`。 + +3. **使用者輸入 vault-pass.txt URL,自動下載** + - installer 會提示輸入 `http://` 或 `https://` URL。 + - 用 `curl` 或 `wget` 下載到 `~/.config/vault-pass.txt`。 + - 只適合可信的一次性下載 URL。 + +4. **解壓 repo 內既有密碼保護 zip** + - 預設讀取:`secrets/vault-pass.txt.zip`。 + - zip 內必須包含檔名:`vault-pass.txt`。 + - installer 會要求使用者在自己的 terminal 手動輸入 zip 密碼。 ### 若壓縮檔不存在 @@ -94,17 +107,17 @@ mkdir -p ~/projects/agent-secret-vault/secrets cat > ~/projects/agent-secret-vault/secrets/vault-pass.txt.zip.PLACEHOLDER <<'PLACEHOLDER' Missing file: secrets/vault-pass.txt.zip Purpose: password-protected archive containing vault-pass.txt -Action: ask human maintainer to provide this archive or commit it to the private repo. +Action: ask human maintainer to provide this archive or use installer method 1/2/3. PLACEHOLDER ``` -然後回報人類:缺 `secrets/vault-pass.txt.zip`,需要提供。 +然後回報人類:缺 `secrets/vault-pass.txt.zip`,或請人類選擇 installer 方法 1/2/3。 ### 若要用非預設路徑 ```bash export VAULT_PASS_FILE=/path/to/vault-pass.txt -./scripts/install-vault-pass.sh /path/to/vault-pass.txt.zip +./scripts/install-vault-pass.sh ``` ## 4. 驗證安裝 diff --git a/docs/human-guide.md b/docs/human-guide.md index 82fb91e..4f3c2cd 100644 --- a/docs/human-guide.md +++ b/docs/human-guide.md @@ -31,13 +31,20 @@ cd agent-secret-vault ./scripts/install-vault-pass.sh ``` -這會從 repo 內的密碼保護壓縮檔解出: +installer 會先檢查: ```text ~/.config/vault-pass.txt ``` -過程中需要你手動輸入壓縮檔密碼。 +如果已存在,會保留並驗證。若不存在,會讓你選 4 種方式: + +1. 建立新 vault password,並在需要時建立加密 placeholder vault。 +2. 手動輸入 vault-pass.txt 的內容。 +3. 輸入 vault-pass.txt 的 URL,讓 installer 自動下載。 +4. 解壓 repo 內既有的密碼保護檔 `secrets/vault-pass.txt.zip`。 + +若選第 4 種,請在你自己的 terminal 輸入 zip 密碼;Telegram / chat 不能輸入到 agent 的工具互動提示。 驗證: diff --git a/scripts/install-vault-pass.sh b/scripts/install-vault-pass.sh index 372ebc1..f52f754 100755 --- a/scripts/install-vault-pass.sh +++ b/scripts/install-vault-pass.sh @@ -4,6 +4,7 @@ set -euo pipefail REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" DEST="${VAULT_PASS_FILE:-$HOME/.config/vault-pass.txt}" ARCHIVE="${1:-$REPO_DIR/secrets/vault-pass.txt.zip}" +VAULT_FILE="${VAULT_FILE:-$REPO_DIR/secrets/vault.yml}" usage() { cat </dev/null 2>&1; then + echo "Missing dependency: $1" >&2 + echo "Please install it first." >&2 + exit 3 + fi +} + +create_new_password() { + require_cmd ansible-vault + require_cmd python3 + ensure_dest_dir + umask 077 + python3 - <<'PY' > "$DEST" +import secrets +print(secrets.token_urlsafe(48)) +PY + secure_dest + echo "Created new vault password file: $DEST" + + if [ -f "$VAULT_FILE" ]; then + if ansible-vault view "$VAULT_FILE" --vault-password-file "$DEST" >/dev/null 2>&1; then + echo "Existing vault is already readable with the new password. No re-encryption needed." + else + cat < "$tmp" <<'YAML' +# Initial placeholder vault. Replace with real secrets using ./scripts/vault.sh edit. +gitea: {} +openclaw_alice: + http_nodes: {} + ssh_nodes: {} +YAML + cp "$tmp" "$VAULT_FILE" + ansible-vault encrypt "$VAULT_FILE" --vault-password-file "$DEST" + rm -f "$tmp" + echo "Created encrypted placeholder vault: $VAULT_FILE" + fi +} + +manual_create() { + ensure_dest_dir + cat <&2 + exit 4 + fi + umask 077 + printf '%s\n' "$pass" > "$DEST" + secure_dest + echo "Installed manually provided vault password file: $DEST" +} + +download_from_url() { + ensure_dest_dir + printf 'Enter vault-pass.txt URL: ' + read -r url + if [ -z "$url" ]; then + echo "URL is required." >&2 + exit 4 + fi + case "$url" in + http://*|https://*) ;; + *) echo "Only http:// or https:// URLs are supported." >&2; exit 4 ;; + esac + if command -v curl >/dev/null 2>&1; then + umask 077 + curl -fsSL "$url" -o "$DEST" + elif command -v wget >/dev/null 2>&1; then + umask 077 + wget -qO "$DEST" "$url" + else + echo "Missing dependency: curl or wget" >&2 + exit 3 + fi + if [ ! -s "$DEST" ]; then + echo "Downloaded file is empty or missing." >&2 + exit 4 + fi + secure_dest + echo "Downloaded vault password file to: $DEST" +} + +extract_from_archive() { + require_cmd unzip + ensure_dest_dir + if [ ! -f "$ARCHIVE" ]; then + cat >&2 <&2 + exit 4 + fi + + install -m 600 "$src" "$DEST" + echo "Installed vault password file from archive: $DEST" +} + +verify_vault_readable_if_possible() { + if [ -f "$VAULT_FILE" ] && command -v ansible-vault >/dev/null 2>&1; then + if ansible-vault view "$VAULT_FILE" --vault-password-file "$DEST" >/dev/null 2>&1; then + echo "Verified: vault.yml is readable with $DEST" + else + echo "Warning: vault.yml is not readable with $DEST" >&2 + return 5 + fi + fi +} + if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then usage exit 0 fi -if [ ! -f "$ARCHIVE" ]; then - cat >&2 </dev/null 2>&1; then - echo "Missing dependency: unzip" >&2 - echo "Install it with: sudo apt install -y unzip" >&2 - exit 3 -fi +cat <&2; exit 4 ;; +esac -# unzip will prompt for the archive password interactively. -unzip -q "$ARCHIVE" -d "$tmpdir" - -src="$tmpdir/vault-pass.txt" -if [ ! -f "$src" ]; then - echo "Archive extracted, but vault-pass.txt was not found inside." >&2 - exit 4 -fi - -install -m 600 "$src" "$DEST" - -echo "Installed vault password file: $DEST" +verify_vault_readable_if_possible || true