#!/usr/bin/env bash # lib/config.sh — Config loading and target resolution # # Two-phase loading: # 1. parse_config() — reads .josh-sync.yml via yq+jq, exports globals + JOSH_SYNC_TARGETS # 2. load_target() — called per-target during iteration, sets target-specific env vars # # Requires: lib/core.sh sourced first, yq and jq on PATH # ─── Josh Filter File Generation ────────────────────────────────── # Generates .josh-filter-.josh for targets with exclude patterns. # These files must be committed to the monorepo — josh-proxy reads them # from the repo at clone time via the :+ stored filter syntax. # Files are at the repo root (flat naming) because josh-proxy's parser # treats "/" in :+ paths as a filter separator. _generate_josh_filters() { local has_excludes has_excludes=$(echo "$JOSH_SYNC_TARGETS" | jq '[.[] | select((.exclude // []) | length > 0)] | length') if [ "$has_excludes" -eq 0 ]; then return fi local target_name subfolder exclude_patterns filter_content while IFS= read -r target_name; do subfolder=$(echo "$JOSH_SYNC_TARGETS" | jq -r --arg n "$target_name" \ '.[] | select(.name == $n) | .subfolder') exclude_patterns=$(echo "$JOSH_SYNC_TARGETS" | jq -r --arg n "$target_name" \ '.[] | select(.name == $n) | .exclude | map(" ::" + .) | join("\n")') filter_content=":/${subfolder}:exclude[ ${exclude_patterns} ]" local filter_file=".josh-filter-${target_name}.josh" local existing="" if [ -f "$filter_file" ]; then existing=$(cat "$filter_file") fi if [ "$filter_content" != "$existing" ]; then echo "$filter_content" > "$filter_file" log "WARN" "Generated ${filter_file} — commit this file to the monorepo" fi done < <(echo "$JOSH_SYNC_TARGETS" | jq -r '.[] | select((.exclude // []) | length > 0) | .name') } # ─── Phase 1: Parse Config ───────────────────────────────────────── parse_config() { local config_file="${1:-.josh-sync.yml}" [ -f "$config_file" ] || die "Config not found: ${config_file} (run from monorepo root)" local config_json config_json=$(yq -o json "$config_file") || die "Failed to parse ${config_file}" # Export global values export JOSH_PROXY_URL JOSH_PROXY_URL=$(echo "$config_json" | jq -r '.josh.proxy_url') export MONOREPO_PATH MONOREPO_PATH=$(echo "$config_json" | jq -r '.josh.monorepo_path') export BOT_NAME BOT_NAME=$(echo "$config_json" | jq -r '.bot.name') export BOT_EMAIL BOT_EMAIL=$(echo "$config_json" | jq -r '.bot.email') export BOT_TRAILER BOT_TRAILER=$(echo "$config_json" | jq -r '.bot.trailer') [ -n "$JOSH_PROXY_URL" ] && [ "$JOSH_PROXY_URL" != "null" ] || die "josh.proxy_url missing in config" [ -n "$MONOREPO_PATH" ] && [ "$MONOREPO_PATH" != "null" ] || die "josh.monorepo_path missing in config" [ -n "$BOT_TRAILER" ] && [ "$BOT_TRAILER" != "null" ] || die "bot.trailer missing in config" # Enrich targets with derived fields (gitea_host, subrepo_repo_path, auto josh_filter) export JOSH_SYNC_TARGETS JOSH_SYNC_TARGETS=$(echo "$config_json" | jq '[.targets[] | . + # Auto-derive josh_filter from subfolder if not set # When exclude patterns are present, use a stored josh filter (:+.josh-filter-) (if (.exclude // [] | length) > 0 then {josh_filter: (":+.josh-filter-" + .name)} elif (.josh_filter // "") == "" then {josh_filter: (":/" + .subfolder)} else {} end) + # Derive gitea_host and subrepo_repo_path from subrepo_url (.subrepo_url as $url | if ($url | test("^ssh://")) then ($url | capture("ssh://[^@]*@(?[^/]+)/(?

.+?)(\\.git)?$") // {h: "", p: ""} | {gitea_host: .h, subrepo_repo_path: .p}) elif ($url | test("^git@")) then ($url | capture("git@(?[^:/]+)[:/](?

.+?)(\\.git)?$") // {h: "", p: ""} | {gitea_host: .h, subrepo_repo_path: .p}) elif ($url | test("^https?://")) then ($url | capture("https?://(?[^/]+)/(?

.+?)(\\.git)?$") // {h: "", p: ""} | {gitea_host: .h, subrepo_repo_path: .p}) else {gitea_host: "", subrepo_repo_path: ""} end ) ]') # Generate .josh-filter-*.josh for targets with exclude patterns _generate_josh_filters # Load .env credentials (if present, not required — CI sets these via secrets) if [ -f .env ]; then # shellcheck source=/dev/null source .env fi export GITEA_TOKEN="${GITEA_TOKEN:-${SYNC_BOT_TOKEN:-}}" export BOT_USER="${BOT_USER:-${SYNC_BOT_USER:-}}" # Monorepo API URL (derived from first target's host, overridable via env) local gitea_host gitea_host=$(echo "$JOSH_SYNC_TARGETS" | jq -r '.[0].gitea_host') export MONOREPO_API="${MONOREPO_API:-https://${gitea_host}/api/v1/repos/${MONOREPO_PATH}}" log "INFO" "Config loaded: $(echo "$JOSH_SYNC_TARGETS" | jq 'length') target(s)" } # ─── Phase 2: Load Target ────────────────────────────────────────── # Called per-target during iteration. Takes a JSON object (one element # of JOSH_SYNC_TARGETS) and sets all target-specific env vars. load_target() { local tj="$1" export JOSH_SYNC_TARGET_NAME JOSH_SYNC_TARGET_NAME=$(echo "$tj" | jq -r '.name') export JOSH_FILTER JOSH_FILTER=$(echo "$tj" | jq -r '.josh_filter') export SUBREPO_URL SUBREPO_URL=$(echo "$tj" | jq -r '.subrepo_url') export SUBREPO_AUTH SUBREPO_AUTH=$(echo "$tj" | jq -r '.subrepo_auth // "https"') # API URL from pre-derived fields local gitea_host subrepo_repo_path gitea_host=$(echo "$tj" | jq -r '.gitea_host') subrepo_repo_path=$(echo "$tj" | jq -r '.subrepo_repo_path') export SUBREPO_API="https://${gitea_host}/api/v1/repos/${subrepo_repo_path}" # Per-target credential resolution (indirect variable reference) local token_var ssh_key_var token_var=$(echo "$tj" | jq -r '.subrepo_token_var // "SUBREPO_TOKEN"') ssh_key_var=$(echo "$tj" | jq -r '.subrepo_ssh_key_var // "SUBREPO_SSH_KEY"') # Resolve: per-target var → default var → SYNC_BOT_TOKEN fallback export SUBREPO_TOKEN="${!token_var:-${SUBREPO_TOKEN:-${SYNC_BOT_TOKEN:-}}}" local ssh_key_value="${!ssh_key_var:-${SUBREPO_SSH_KEY:-}}" # Clean up previous SSH state and set up new if needed if [ -n "${JOSH_SSH_DIR:-}" ]; then rm -rf "$JOSH_SSH_DIR" unset JOSH_SSH_DIR GIT_SSH_COMMAND fi if [ "$SUBREPO_AUTH" = "ssh" ] && [ -n "$ssh_key_value" ]; then JOSH_SSH_DIR=$(mktemp -d) echo "$ssh_key_value" > "${JOSH_SSH_DIR}/subrepo_key" chmod 600 "${JOSH_SSH_DIR}/subrepo_key" export GIT_SSH_COMMAND="ssh -i ${JOSH_SSH_DIR}/subrepo_key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" log "INFO" "SSH auth configured for target ${JOSH_SYNC_TARGET_NAME}" fi log "INFO" "Loaded target: ${JOSH_SYNC_TARGET_NAME} (${SUBREPO_AUTH})" }