#!/bin/bash # vault-sync.sh - Sync Vaultwarden items to Butler's persistent cache volume. # Runtime credentials live in .vault-sync.env (mode 0600, never in Git). set -euo pipefail ENV_FILE="${VAULT_SYNC_ENV:-/app-config/homelab-butler/.vault-sync.env}" if [[ ! -r "$ENV_FILE" ]]; then echo "vault-sync: protected environment file missing or unreadable" >&2 exit 1 fi set -a # shellcheck disable=SC1090 source "$ENV_FILE" set +a : "${BW_CLIENTID:?BW_CLIENTID missing}" : "${BW_CLIENTSECRET:?BW_CLIENTSECRET missing}" if [[ -z "${BW_PASSWORD:-}" && -n "${BW_MASTER_PASSWORD:-}" ]]; then export BW_PASSWORD="$BW_MASTER_PASSWORD" fi : "${BW_PASSWORD:?BW_PASSWORD missing}" CACHE_DIR=$(sudo docker inspect homelab-butler --format '{{range .Mounts}}{{if eq .Destination "/data/vault-cache"}}{{.Source}}{{end}}{{end}}' 2>/dev/null) if [[ -z "$CACHE_DIR" ]]; then echo "vault-sync: Butler cache volume not found" >&2 exit 1 fi BW_STATE=$(bw status 2>/dev/null | python3 -c 'import json,sys; print(json.load(sys.stdin).get("status", "unknown"))') if [[ "$BW_STATE" == "unauthenticated" ]]; then bw login --apikey --raw >/dev/null fi SESSION=$(bw unlock --passwordenv BW_PASSWORD --raw 2>/dev/null) if [[ -z "$SESSION" ]]; then echo "vault-sync: Vault unlock failed" >&2 exit 1 fi bw sync --session "$SESSION" >/dev/null bw list items --session "$SESSION" | sudo python3 -c " import json import os import re import sys items = json.load(sys.stdin) cache_dir = '$CACHE_DIR' os.makedirs(cache_dir, exist_ok=True) count = 0 for item in items: name = item.get('name', '') notes = item.get('notes') or '' safe = re.sub(r'[^a-z0-9._-]+', '-', name.lower()).strip('.-') if not safe or not notes: continue path = os.path.join(cache_dir, safe) tmp = path + '.tmp' with open(tmp, 'w') as handle: handle.write(notes.strip()) os.chmod(tmp, 0o600) os.replace(tmp, path) count += 1 print(f'vault-sync: {count} items written') "