#!/bin/sh set -eu log() { printf '%s\n' "$*" >&2 } is_enabled() { case "${1:-}" in 1|true|TRUE|yes|YES|on|ON) return 0 ;; *) return 1 ;; esac } wait_for_http() { name="$1" url="$2" timeout="$3" interval="$4" mode="${5:-strict}" start_ts="$(date +%s)" while :; do if [ "$mode" = "connect" ]; then if curl -sS --max-time 3 "$url" >/dev/null 2>&1; then log "$name is reachable at $url." return 0 fi else if curl -fsS --max-time 3 "$url" >/dev/null 2>&1; then log "$name is reachable at $url." return 0 fi fi now_ts="$(date +%s)" if [ $((now_ts - start_ts)) -ge "$timeout" ]; then log "Timeout waiting for $name at $url after ${timeout}s." return 1 fi sleep "$interval" done } wait_for_tcp() { name="$1" host="$2" port="$3" timeout="$4" interval="$5" start_ts="$(date +%s)" while :; do if node -e "const net=require('net');const host=process.argv[1];const port=Number(process.argv[2]);const socket=net.connect({host,port});const fail=()=>{socket.destroy();process.exit(1)};socket.setTimeout(2000,fail);socket.on('connect',()=>{socket.end();process.exit(0)});socket.on('error',fail);" "$host" "$port" >/dev/null 2>&1; then log "$name is reachable at ${host}:${port}." return 0 fi now_ts="$(date +%s)" if [ $((now_ts - start_ts)) -ge "$timeout" ]; then log "Timeout waiting for $name at ${host}:${port} after ${timeout}s." return 1 fi sleep "$interval" done } VAULT_ENABLED="${VAULT_ENABLED:-auto}" if [ "$VAULT_ENABLED" = "false" ] || [ "$VAULT_ENABLED" = "0" ]; then exit 0 fi if [ -z "${VAULT_ADDR:-}" ] || [ -z "${VAULT_TOKEN:-}" ]; then if [ "$VAULT_ENABLED" = "true" ] || [ "$VAULT_ENABLED" = "1" ]; then log "Vault bootstrap is required but VAULT_ADDR or VAULT_TOKEN is missing." exit 1 fi exit 0 fi if ! command -v curl >/dev/null 2>&1 || ! command -v jq >/dev/null 2>&1; then log "Vault bootstrap requires curl and jq." exit 1 fi VAULT_KV_MOUNT="${VAULT_KV_MOUNT:-secret}" VAULT_WAIT_TIMEOUT_SECONDS="${VAULT_WAIT_TIMEOUT_SECONDS:-90}" VAULT_WAIT_INTERVAL_SECONDS="${VAULT_WAIT_INTERVAL_SECONDS:-2}" wait_for_http \ "Vault" \ "${VAULT_ADDR%/}/v1/sys/health?standbyok=true&perfstandbyok=true" \ "$VAULT_WAIT_TIMEOUT_SECONDS" \ "$VAULT_WAIT_INTERVAL_SECONDS" \ "strict" load_secret_path() { path="$1" source_name="$2" if [ -z "$path" ]; then return 0 fi url="${VAULT_ADDR%/}/v1/${VAULT_KV_MOUNT}/data/${path}" response="$(curl -fsS -H "X-Vault-Token: $VAULT_TOKEN" "$url")" || { log "Failed to load Vault path ${VAULT_KV_MOUNT}/${path}." return 1 } encoded_items="$(printf '%s' "$response" | jq -r '.data.data // {} | to_entries[]? | @base64')" if [ -z "$encoded_items" ]; then return 0 fi old_ifs="${IFS}" IFS=' ' for encoded_item in $encoded_items; do key="$(printf '%s' "$encoded_item" | base64 -d | jq -r '.key')" value="$(printf '%s' "$encoded_item" | base64 -d | jq -r '.value | tostring')" export "$key=$value" done IFS="${old_ifs}" log "Loaded Vault ${source_name} secrets from ${VAULT_KV_MOUNT}/${path}." } load_secret_path "${VAULT_SHARED_PATH:-}" "shared" load_secret_path "${VAULT_PROJECT_PATH:-}" "project" if is_enabled "${WAIT_FOR_HATCHET_ENGINE:-0}"; then HATCHET_WAIT_TIMEOUT_SECONDS="${HATCHET_WAIT_TIMEOUT_SECONDS:-120}" HATCHET_WAIT_INTERVAL_SECONDS="${HATCHET_WAIT_INTERVAL_SECONDS:-2}" hatchet_wait_host_port="${HATCHET_WAIT_HOST_PORT:-${HATCHET_CLIENT_HOST_PORT:-}}" hatchet_wait_url="${HATCHET_WAIT_URL:-${HATCHET_CLIENT_API_URL:-}}" if [ -n "$hatchet_wait_host_port" ]; then case "$hatchet_wait_host_port" in *:*) hatchet_wait_host="${hatchet_wait_host_port%:*}" hatchet_wait_port="${hatchet_wait_host_port##*:}" ;; *) log "Invalid HATCHET wait host:port value: ${hatchet_wait_host_port}" exit 1 ;; esac wait_for_tcp \ "Hatchet engine" \ "$hatchet_wait_host" \ "$hatchet_wait_port" \ "$HATCHET_WAIT_TIMEOUT_SECONDS" \ "$HATCHET_WAIT_INTERVAL_SECONDS" else if [ -z "$hatchet_wait_url" ]; then log "WAIT_FOR_HATCHET_ENGINE is enabled but no HATCHET wait target is configured." exit 1 fi wait_for_http \ "Hatchet engine" \ "${hatchet_wait_url%/}/" \ "$HATCHET_WAIT_TIMEOUT_SECONDS" \ "$HATCHET_WAIT_INTERVAL_SECONDS" \ "connect" fi fi