chore: add Vault bootstrap support

This commit is contained in:
Ruslan Bakiev
2026-03-31 11:35:52 +07:00
parent 3028023b71
commit 5b5f6250d3
2 changed files with 176 additions and 1 deletions

View File

@@ -16,12 +16,17 @@ FROM node:22-bookworm-slim AS runtime
WORKDIR /app
RUN apt-get update -y \
&& apt-get install -y --no-install-recommends curl jq ca-certificates \
&& rm -rf /var/lib/apt/lists/*
ENV NODE_ENV=production
ENV NITRO_HOST=0.0.0.0
ENV PORT=3000
COPY --from=build /app/.output ./.output
COPY --from=build /app/scripts ./scripts
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]
CMD ["sh", "-c", ". /app/scripts/load-vault-env.sh && node .output/server/index.mjs"]

170
scripts/load-vault-env.sh Executable file
View File

@@ -0,0 +1,170 @@
#!/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
return 0 2>/dev/null || 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."
return 1 2>/dev/null || exit 1
fi
return 0 2>/dev/null || 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."
return 1 2>/dev/null || 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}"
return 1 2>/dev/null || 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."
return 1 2>/dev/null || exit 1
fi
wait_for_http \
"Hatchet engine" \
"${hatchet_wait_url%/}/" \
"$HATCHET_WAIT_TIMEOUT_SECONDS" \
"$HATCHET_WAIT_INTERVAL_SECONDS" \
"connect"
fi
fi