| 1 | #!/usr/bin/env bash
|
| 2 | #
|
| 3 | # Build the encrypted Ansible vault from the values that are live on the VPS
|
| 4 | #
|
| 5 | # Pulls /srv/app/production/.env and .env.production, extracts the secrets,
|
| 6 | # generates a logs UI credential, and writes the encrypted group_vars vault.
|
| 7 | # Read-only on the server
|
| 8 | #
|
| 9 | # ansible/scripts/import-vault.sh
|
| 10 | #
|
| 11 | # The vault password lives in ansible/.vault_pass (git-ignored); this script
|
| 12 | # creates it on first run and prints it, so store it somewhere safe. Connection
|
| 13 | # details come from ansible/inventory/hosts.yml, or the VPS_HOST / VPS_USER /
|
| 14 | # VPS_KEY environment variables
|
| 15 |
|
| 16 | set -euo pipefail
|
| 17 |
|
| 18 | repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
| 19 | ansible_dir="$repo_root/ansible"
|
| 20 | inventory="$ansible_dir/inventory/hosts.yml"
|
| 21 | vault_file="$ansible_dir/inventory/group_vars/all/vault.yml"
|
| 22 | vault_pass_file="$ansible_dir/.vault_pass"
|
| 23 |
|
| 24 | field() { awk -F': *' -v k="^[[:space:]]*$1" '$0 ~ k {print $2; exit}' "$inventory"; }
|
| 25 |
|
| 26 | HOST="${VPS_HOST:-}"
|
| 27 | USER="${VPS_USER:-}"
|
| 28 | KEY="${VPS_KEY:-}"
|
| 29 |
|
| 30 | if [ -f "$inventory" ]; then
|
| 31 | [ -n "$HOST" ] || HOST="$(field 'ansible_host:')"
|
| 32 | [ -n "$USER" ] || USER="$(field 'ansible_user:')"
|
| 33 | [ -n "$KEY" ] || KEY="$(field 'ansible_ssh_private_key_file:')"
|
| 34 | fi
|
| 35 |
|
| 36 | KEY="${KEY/#\~/$HOME}"
|
| 37 | KEY="${KEY:-$HOME/.ssh/id_ed25519}"
|
| 38 | [ -n "$HOST" ] || { echo "no host: set VPS_HOST or provide inventory/hosts.yml" >&2; exit 2; }
|
| 39 | [ -n "$USER" ] || { echo "no user: set VPS_USER or provide inventory/hosts.yml" >&2; exit 2; }
|
| 40 |
|
| 41 | tmp="$(mktemp -d)"
|
| 42 | trap 'rm -rf "$tmp"' EXIT
|
| 43 | ssh_opts=(-i "$KEY" -o StrictHostKeyChecking=accept-new)
|
| 44 |
|
| 45 | echo "Pulling live env from ${USER}@${HOST} ..."
|
| 46 | scp "${ssh_opts[@]}" "${USER}@${HOST}:/srv/app/production/.env" "$tmp/env" >/dev/null
|
| 47 | scp "${ssh_opts[@]}" "${USER}@${HOST}:/srv/app/production/.env.production" "$tmp/env.production" >/dev/null
|
| 48 |
|
| 49 | # The env files are plain KEY=value; sourcing them is the least fussy parse
|
| 50 | set -a
|
| 51 | # shellcheck disable=SC1091
|
| 52 | . "$tmp/env"
|
| 53 | # shellcheck disable=SC1091
|
| 54 | . "$tmp/env.production"
|
| 55 | set +a
|
| 56 |
|
| 57 | if [ ! -f "$vault_pass_file" ]; then
|
| 58 | umask 077
|
| 59 | openssl rand -base64 32 >"$vault_pass_file"
|
| 60 | echo "==> generated $vault_pass_file"
|
| 61 | echo " vault password: $(cat "$vault_pass_file")"
|
| 62 | fi
|
| 63 |
|
| 64 | logs_pass="$(openssl rand -base64 18)"
|
| 65 | logs_hash="$(openssl passwd -apr1 "$logs_pass")"
|
| 66 |
|
| 67 | cat >"$vault_file.plain" <<EOF
|
| 68 | vault_postgres_password: "${POSTGRES_PASSWORD:?missing POSTGRES_PASSWORD}"
|
| 69 | vault_jwt_secret: "${JWT_SECRET:?missing JWT_SECRET}"
|
| 70 | vault_resend_api_key: "${RESEND_API_KEY:?missing RESEND_API_KEY}"
|
| 71 | vault_s3_access_key: "${S3_ACCESS_KEY:?missing S3_ACCESS_KEY}"
|
| 72 | vault_s3_access_secret: "${S3_ACCESS_SECRET:?missing S3_ACCESS_SECRET}"
|
| 73 | vault_nginx_logs_htpasswd: "logs:${logs_hash}"
|
| 74 | EOF
|
| 75 |
|
| 76 | ansible-vault encrypt \
|
| 77 | --vault-password-file "$vault_pass_file" \
|
| 78 | --output "$vault_file" \
|
| 79 | "$vault_file.plain"
|
| 80 | rm -f "$vault_file.plain"
|
| 81 |
|
| 82 | echo
|
| 83 | echo "Wrote $vault_file (encrypted)."
|
| 84 | echo "Logs UI credential: user 'logs', password '${logs_pass}'"
|
| 85 | echo "Back up both the vault password and this credential."
|