From 08ac14c089d3e2b5384225b6b42068019278c41f Mon Sep 17 00:00:00 2001 From: sascha Date: Wed, 22 Jul 2026 11:23:54 +0200 Subject: [PATCH] sync: vault-sync.sh for Butler 2.3.2 --- vault-sync.sh | 64 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/vault-sync.sh b/vault-sync.sh index 8f03bc0..06a92c1 100644 --- a/vault-sync.sh +++ b/vault-sync.sh @@ -1,20 +1,51 @@ #!/bin/bash -# vault-sync.sh - Sync Vaultwarden items to Butler cache volume -# Run via cron: */30 * * * * /app-config/homelab-butler/vault-sync.sh +# 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 -export BW_PASSWORD="8yRG5LADfoTLHdC1Oj" -CACHE_DIR=$(sudo docker inspect homelab-butler --format '{{range .Mounts}}{{if eq .Destination "/data/vault-cache"}}{{.Source}}{{end}}{{end}}' 2>/dev/null) +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 -[ -z "$CACHE_DIR" ] && echo "Butler container not found" && exit 1 +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) -[ -z "$SESSION" ] && echo "Vault unlock failed" && exit 1 +if [[ -z "$SESSION" ]]; then + echo "vault-sync: Vault unlock failed" >&2 + exit 1 +fi -bw sync --session "$SESSION" >/dev/null 2>&1 +bw sync --session "$SESSION" >/dev/null + +bw list items --session "$SESSION" | sudo python3 -c " +import json +import os +import re +import sys -bw list items --session "$SESSION" 2>/dev/null | sudo python3 -c " -import sys, json, os items = json.load(sys.stdin) cache_dir = '$CACHE_DIR' os.makedirs(cache_dir, exist_ok=True) @@ -22,10 +53,15 @@ count = 0 for item in items: name = item.get('name', '') notes = item.get('notes') or '' - if name and notes: - safe = name.lower().replace(' ', '-') - with open(f'{cache_dir}/{safe}', 'w') as f: - f.write(notes.strip()) - count += 1 + 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') "