Files
agent-secret-vault/scripts/install-vault-pass.sh

281 lines
7.8 KiB
Bash
Executable File

#!/usr/bin/env bash
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}"
ENV_FILE="${INSTALL_ENV_FILE:-$REPO_DIR/install.env}"
load_env_file() {
if [ -f "$ENV_FILE" ]; then
set -a
# shellcheck disable=SC1090
. "$ENV_FILE"
set +a
fi
}
load_env_file
# Re-apply env-configurable paths after loading install.env.
DEST="${VAULT_PASS_FILE:-$HOME/.config/vault-pass.txt}"
ARCHIVE="${VAULT_PASS_ARCHIVE:-${1:-$REPO_DIR/secrets/vault-pass.txt.zip}}"
VAULT_FILE="${VAULT_FILE:-$REPO_DIR/secrets/vault.yml}"
# Optional non-interactive controls:
# INSTALL_VAULT_PASS_METHOD=create|manual|url|archive
# VAULT_PASS_CONTENT=<content> (for method=manual)
# VAULT_PASS_URL=<https-url> (for method=url)
# VAULT_PASS_ZIP_PASSWORD=<password> (for method=archive; avoid chat/log)
# VAULT_PASS_ZIP_PASSWORD_FILE=<path> (for method=archive; safer than env)
usage() {
cat <<USAGE
Usage: scripts/install-vault-pass.sh [archive.zip]
Loads installer env from:
${INSTALL_ENV_FILE:-$REPO_DIR/install.env}
Override with:
INSTALL_ENV_FILE=/path/to/install.env ./scripts/install-vault-pass.sh
Installs the Ansible Vault password file to:
${VAULT_PASS_FILE:-$HOME/.config/vault-pass.txt}
Interactive behavior:
1. If the password file already exists, keep it and verify permissions.
2. If missing, prompt the user to choose one of four setup methods:
[1] Create a new vault password and initialize/re-encrypt vault.yml
[2] Paste/type vault-pass.txt content manually
[3] Download vault-pass.txt from a user-provided URL
[4] Extract vault-pass.txt from a password-protected zip archive
Non-interactive agent mode (via install.env or environment variables):
INSTALL_VAULT_PASS_METHOD=create ./scripts/install-vault-pass.sh
VAULT_PASS_CONTENT='...' INSTALL_VAULT_PASS_METHOD=manual ./scripts/install-vault-pass.sh
VAULT_PASS_URL='https://...' INSTALL_VAULT_PASS_METHOD=url ./scripts/install-vault-pass.sh
VAULT_PASS_ZIP_PASSWORD_FILE=/secure/pass INSTALL_VAULT_PASS_METHOD=archive ./scripts/install-vault-pass.sh
VAULT_PASS_ZIP_PASSWORD='...' INSTALL_VAULT_PASS_METHOD=archive ./scripts/install-vault-pass.sh
Default archive path for method [4]:
$REPO_DIR/secrets/vault-pass.txt.zip
USAGE
}
ensure_dest_dir() {
umask 077
mkdir -p "$(dirname "$DEST")"
chmod 700 "$(dirname "$DEST")" || true
}
secure_dest() { chmod 600 "$DEST"; }
verify_existing() {
if [ -f "$DEST" ]; then
secure_dest
echo "Vault password file already exists: $DEST"
return 0
fi
return 1
}
require_cmd() {
if ! command -v "$1" >/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 <<WARN
WARNING: $VAULT_FILE exists but is not readable with the new password.
To avoid destroying existing encrypted secrets, this script will NOT overwrite it automatically.
If this is a brand-new install, create a plaintext YAML file and run:
./scripts/vault.sh encrypt /path/to/plaintext.yml
If this is an existing vault, choose method [2], [3], or [4] with the correct password instead.
WARN
fi
else
mkdir -p "$(dirname "$VAULT_FILE")"
tmp="$(mktemp)"
chmod 600 "$tmp"
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
if [ -n "${VAULT_PASS_CONTENT:-}" ]; then
umask 077
printf '%s\n' "$VAULT_PASS_CONTENT" > "$DEST"
else
cat <<MSG
Paste/type the vault password content now, then press Enter.
Input is hidden. The content will be written to:
$DEST
MSG
read -r -s pass
printf '\n'
if [ -z "$pass" ]; then
echo "Empty password is not allowed." >&2
exit 4
fi
umask 077
printf '%s\n' "$pass" > "$DEST"
fi
secure_dest
echo "Installed manually provided vault password file: $DEST"
}
download_from_url() {
ensure_dest_dir
url="${VAULT_PASS_URL:-}"
if [ -z "$url" ]; then
printf 'Enter vault-pass.txt URL: '
read -r url
fi
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 <<ERR
Missing archive: $ARCHIVE
Create/provide a password-protected archive that contains one file named:
vault-pass.txt
ERR
exit 2
fi
tmpdir="$(mktemp -d)"
cleanup() { rm -rf "$tmpdir"; }
trap cleanup EXIT
if [ -n "${VAULT_PASS_ZIP_PASSWORD_FILE:-}" ]; then
if [ ! -f "$VAULT_PASS_ZIP_PASSWORD_FILE" ]; then
echo "Missing VAULT_PASS_ZIP_PASSWORD_FILE: $VAULT_PASS_ZIP_PASSWORD_FILE" >&2
exit 4
fi
zip_pass="$(cat "$VAULT_PASS_ZIP_PASSWORD_FILE")"
unzip -P "$zip_pass" -q "$ARCHIVE" -d "$tmpdir"
elif [ -n "${VAULT_PASS_ZIP_PASSWORD:-}" ]; then
unzip -P "$VAULT_PASS_ZIP_PASSWORD" -q "$ARCHIVE" -d "$tmpdir"
else
# unzip will prompt for the archive password interactively.
unzip -q "$ARCHIVE" -d "$tmpdir"
fi
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 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
}
run_method() {
case "$1" in
create|1) create_new_password ;;
manual|2) manual_create ;;
url|3) download_from_url ;;
archive|4) extract_from_archive ;;
*) echo "Invalid setup method: $1" >&2; exit 4 ;;
esac
}
if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
usage
exit 0
fi
if verify_existing; then
verify_vault_readable_if_possible || true
exit 0
fi
if [ -n "${INSTALL_VAULT_PASS_METHOD:-}" ]; then
run_method "$INSTALL_VAULT_PASS_METHOD"
verify_vault_readable_if_possible || true
exit 0
fi
cat <<MENU
Vault password file does not exist:
$DEST
Choose setup method:
1) Create a new vault password and initialize/re-encrypt vault.yml if needed
2) Paste/type vault-pass.txt content manually
3) Download vault-pass.txt from a URL
4) Extract vault-pass.txt from password-protected zip archive
MENU
printf 'Enter choice [1-4]: '
read -r choice
run_method "$choice"
verify_vault_readable_if_possible || true