#!/usr/bin/env bash # Generate one untracked, installation-specific Compose override from explicit THT_WS_* bindings. set -euo pipefail usage() { echo "usage: $0 --bindings-env --operator-env --output " >&2 exit 2 } trim() { local value="$1" value="${value#"${value%%[![:space:]]*}"}" value="${value%"${value##*[![:space:]]}"}" printf '%s' "$value" } is_safe_absolute_path() { local value="$1" segment local -a segments [[ "$value" == /* && "$value" != *//* ]] || return 1 IFS=/ read -r -a segments <<<"$value" for segment in "${segments[@]}"; do [[ "$segment" != . && "$segment" != .. ]] || return 1 done } read_env_value() { local source="$1" wanted="$2" line trimmed name value result="" while IFS= read -r line || [[ -n "$line" ]]; do trimmed="$(trim "$line")" [[ -n "$trimmed" && "$trimmed" != \#* && "$trimmed" == *=* ]] || continue name="$(trim "${trimmed%%=*}")" [[ "$name" == "$wanted" ]] || continue value="$(trim "${trimmed#*=}")" value="$(trim "${value%%#*}")" value="${value#\"}"; value="${value%\"}" value="${value#\'}"; value="${value%\'}" result="$value" done <"$source" printf '%s' "$result" } bindings_env="" operator_env="" output="" while (($#)); do case "$1" in --bindings-env) bindings_env="${2:-}"; shift 2 ;; --operator-env) operator_env="${2:-}"; shift 2 ;; --output) output="${2:-}"; shift 2 ;; *) usage ;; esac done [[ -f "$bindings_env" && -f "$operator_env" && -n "$output" ]] || usage [[ ! -e "$output" ]] || { echo "refusing to overwrite connector override: $output" >&2; exit 2; } names=() targets=() sources=() while IFS=$'\t' read -r name target; do [[ "$name" =~ ^THT_WS_[A-Za-z0-9_]+_FILE$ ]] || continue if [[ "$name" == *"_VECTOR_"* || "$name" == *"_EMBEDDING_"* ]]; then echo "retired semantic secret binding is not supported: ${name%_FILE}_SOURCE" >&2 exit 2 fi [[ "$target" =~ ^/run/secrets/[A-Za-z0-9][A-Za-z0-9_.-]*$ && "$target" != *..* ]] || { echo "invalid connector secret target for $name: $target" >&2 exit 2 } source_name="${name%_FILE}_SOURCE" source_path="$(read_env_value "$operator_env" "$source_name")" if [[ -v "$source_name" ]]; then source_path="${!source_name}" fi if [[ -z "$source_path" ]]; then echo "set $source_name in $operator_env" >&2 exit 2 fi if ! is_safe_absolute_path "$source_path"; then echo "unsafe source path for $source_name in $operator_env" >&2 exit 2 fi names+=("$name") targets+=("${target#/run/secrets/}") sources+=("$source_name") done < <( awk ' function trim(value) { sub(/^[[:space:]]+/, "", value); sub(/[[:space:]]+$/, "", value); return value } { line = trim($0) if (line == "" || line ~ /^#/) next equals = index(line, "=") if (!equals) next name = trim(substr(line, 1, equals - 1)) value = trim(substr(line, equals + 1)) sub(/[[:space:]]+#.*/, "", value) if (value ~ /^".*"$/ || value ~ /^\047.*\047$/) value = substr(value, 2, length(value) - 2) print name "\t" value } ' "$bindings_env" ) ((${#names[@]})) || { echo "no THT_WS_*_FILE connector bindings found in $bindings_env" >&2; exit 2; } { printf '%s\n' '# Generated by scripts/generate-connector-secrets-override.sh; keep this file untracked.' printf '%s\n' \ 'services:' \ ' core:' \ ' env_file:' \ ' - path: ${THT_WORKSPACE_BINDINGS_ENV_FILE:?set THT_WORKSPACE_BINDINGS_ENV_FILE}' \ ' required: true' \ ' secrets:' for ((index = 0; index < ${#names[@]}; index += 1)); do printf ' - source: connector_secret_%d\n' "$((index + 1))" printf ' target: %s\n' "${targets[index]}" done printf '%s\n' '' 'secrets:' for ((index = 0; index < ${#names[@]}; index += 1)); do printf ' connector_secret_%d:\n' "$((index + 1))" printf ' file: ${%s:?set %s}\n' "${sources[index]}" "${sources[index]}" done } >"$output" printf 'generated %s connector secret mount(s) at %s\n' "${#names[@]}" "$output"