#!/bin/bash # vault-bootstrap.sh — Vault HA cluster bootstrap (Shamir seal, Docker secret) # Node-agnostic: uses docker exec for local replicas, falls back to the overlay # network via docker run when no local replica is found on this Swarm manager. set -euo pipefail # ─── Configuration ─────────────────────────────────────────────────── STACK_NAME="iklimco" STACK_FILE="$(cd "$(dirname "$0")" && pwd)/docker-stack-vault.yml" OUT_DIR="/tmp/vault-bootstrap" SKIP_DEPLOY="${SKIP_DEPLOY:-false}" # ───────────────────────────────────────────────────────────────────── mkdir -p "$OUT_DIR" MAIN_INIT_FILE="$OUT_DIR/main-vault-init.txt" # ─── Logging ───────────────────────────────────────────────────────── step() { echo; echo "════════════════════════════════════════════════"; echo " [$(date '+%H:%M:%S')] $*"; echo "════════════════════════════════════════════════"; } ok() { echo " [OK] $*"; } info() { echo " --> $*"; } fail() { echo; echo " [HATA] $*" >&2; exit 1; } trap 'echo; echo " [HATA] Script satir $LINENO'"'"'de beklenmedik sekilde sonlandi" >&2' ERR # ───────────────────────────────────────────────────────────────────── # ─── Helpers ───────────────────────────────────────────────────────── wait_service_running() { local svc="$1" expected="$2" timeout="${3:-180}" elapsed=0 info "Bekleniyor: $svc ($expected running task)..." while [ "$elapsed" -lt "$timeout" ]; do running=$(docker service ps "$svc" \ --filter "desired-state=running" \ --format '{{.CurrentState}}' 2>/dev/null \ | grep -c "^Running" || true) if [ "$running" -ge "$expected" ]; then ok "$svc hazir: $running/$expected" return 0 fi sleep 5; elapsed=$((elapsed+5)) echo " ${elapsed}s/${timeout}s — running: $running/$expected" done fail "$svc $timeout saniye icinde hazir olmadi" } # Run a vault CLI command — uses docker exec if a vault replica is on this node, # otherwise falls back to the overlay network via docker run. VAULT_TOKEN="" run_vault() { local cmd="$*" [ -n "$VAULT_TOKEN" ] && cmd="VAULT_TOKEN=$VAULT_TOKEN $cmd" local cid cid=$(docker ps -q -f "name=${STACK_NAME}_vault\." | head -1 || true) if [ -n "$cid" ]; then docker exec -i "$cid" sh -c "VAULT_ADDR=https://127.0.0.1:8200 VAULT_SKIP_VERIFY=true $cmd" else docker run --rm -i --network iklimco-net hashicorp/vault:2.0.1 \ sh -c "VAULT_ADDR=https://vault.iklim.co:8200 VAULT_SKIP_VERIFY=true $cmd" fi } # ───────────────────────────────────────────────────────────────────── # ━━━ ADIM 0 — On kosullar ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ step "ADIM 0 — On kosullar kontrol ediliyor" docker node ls &>/dev/null || fail "Swarm manager node gerekli" [ -f "$STACK_FILE" ] || fail "Stack dosyasi bulunamadi: $STACK_FILE" ok "On kosullar tamam" # ━━━ ADIM 1 — Placeholder secret ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ step "ADIM 1 — vault_unseal_key kontrol ediliyor" if docker secret ls --format '{{.Name}}' | grep -q '^vault_unseal_key'; then info "vault_unseal_key mevcut, atlaniyor" else echo "bootstrap" | docker secret create vault_unseal_key - >/dev/null ok "vault_unseal_key (placeholder) olusturuldu" fi # ━━━ ADIM 2 — Stack deploy ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ step "ADIM 2 — Stack deploy" if [ "$SKIP_DEPLOY" = "true" ]; then info "SKIP_DEPLOY=true — atlaniyor" else docker stack deploy --with-registry-auth -c "$STACK_FILE" "$STACK_NAME" ok "Stack deploy edildi" fi # ━━━ ADIM 3 — Vault cluster bekleniyor ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ step "ADIM 3 — Vault cluster bekleniyor" wait_service_running "${STACK_NAME}_vault" 3 300 sleep 10 # ━━━ ADIM 4 — Vault durum kontrolu ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ step "ADIM 4 — Vault durum kontrolu" VAULT_STATUS_OUT=$(run_vault "vault status 2>/dev/null" || true) VAULT_INITIALIZED=$(echo "$VAULT_STATUS_OUT" | awk '/^Initialized/{print $2}') VAULT_SEALED=$(echo "$VAULT_STATUS_OUT" | awk '/^Sealed/{print $2}') info "Initialized: ${VAULT_INITIALIZED:-unknown}, Sealed: ${VAULT_SEALED:-unknown}" if [ "$VAULT_INITIALIZED" = "true" ] && [ "$VAULT_SEALED" = "false" ]; then ok "Vault zaten initialize edilmis ve unsealed" echo echo "════════════════════════════════════════════════" echo " BOOTSTRAP TAMAMLANDI (Vault saglıklı)" echo "════════════════════════════════════════════════" exit 0 fi # ━━━ ADIM 5 — Vault initialize (gerekirse) ━━━━━━━━━━━━━━━━━━━━━━━━━ step "ADIM 5 — Vault initialize / unseal key hazirlaniyor" if [ "$VAULT_INITIALIZED" = "true" ]; then # Vault is sealed but initialized. This happens when the vault_unseal_key Docker secret # contains the wrong value (e.g., placeholder was never replaced). Provide the init file # so the real key can be extracted and pushed to the secret. info "Vault sealed ama initialize edilmis — mevcut init dosyasi kullanilacak" [ -f "$MAIN_INIT_FILE" ] && grep -q "Unseal Key 1" "$MAIN_INIT_FILE" \ || fail "Init dosyasi eksik: $MAIN_INIT_FILE\nUnseal Key'i manuel olarak su formatta dosyaya ekleyin:\n Unseal Key 1: " ok "Init dosyasi mevcut" else info "Vault initialize ediliyor..." run_vault "vault operator init -key-shares=1 -key-threshold=1" | tee "$MAIN_INIT_FILE" ok "Vault init tamamlandi: $MAIN_INIT_FILE" fi # ━━━ ADIM 6 — vault_unseal_key Docker secret guncelle ━━━━━━━━━━━━━━ # Two-step update (delete + recreate with the same name) keeps the secret name # consistent with the stack file so future 'docker stack deploy' runs do not # trigger a service restart or revert to the placeholder. step "ADIM 6 — vault_unseal_key Docker secret guncelleniyor" UNSEAL_KEY=$(awk '/Unseal Key 1:/{print $NF}' "$MAIN_INIT_FILE") [ -n "$UNSEAL_KEY" ] || fail "Unseal key '$MAIN_INIT_FILE' dosyasinda bulunamadi" info "Eski secret servis uzerinden kaldiriliyor (rolling restart 1/2)..." docker service update --secret-rm vault_unseal_key "${STACK_NAME}_vault" >/dev/null sleep 5 docker secret rm vault_unseal_key || true info "Gercek unseal key ile secret yeniden olusturuluyor (rolling restart 2/2)..." echo "$UNSEAL_KEY" | docker secret create vault_unseal_key - >/dev/null docker service update --secret-add vault_unseal_key "${STACK_NAME}_vault" >/dev/null ok "vault_unseal_key gercek degerle guncellendi" # ━━━ ADIM 7 — Unseal dogrula ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ step "ADIM 7 — Vault unseal dogrulaniyor" info "Rolling restart tamamlanmasi ve unseal bekleniyor (30s)..." sleep 30 UNSEALED=0 for i in $(seq 1 12); do STATUS=$(run_vault "vault status 2>/dev/null" | awk '/^Sealed/{print $2}' || echo "true") if [ "$STATUS" = "false" ]; then ok "Vault cluster unsealed" UNSEALED=1 break fi [ "$i" -eq 12 ] && break echo " ${i}/12 — Sealed: $STATUS, retrying in 5s..." sleep 5 done [ "$UNSEALED" -eq 1 ] || fail "Vault cluster unseal olmadi — 'docker service logs ${STACK_NAME}_vault' ile loglari kontrol edin" echo echo "════════════════════════════════════════════════" echo " BOOTSTRAP TAMAMLANDI" echo " Init cikti: $MAIN_INIT_FILE" echo " ONEMLI: Bu dosyayi guvenli yere yedekle ve" echo " produksiyon ortamindan sil!" echo "════════════════════════════════════════════════"