Files
josh-sync/lib/config.sh

153 lines
7.2 KiB
Bash
Raw Normal View History

2026-02-12 09:20:55 +03:00
#!/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
# ─── 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}"
# Schema version check — required, must be 2 (v1 configs are no longer supported)
local schema_version
schema_version=$(echo "$config_json" | jq -r '.schema_version // empty')
[ -n "$schema_version" ] || die "schema_version missing in ${config_file} (required since v2.0.0 — set 'schema_version: 2' at the top of the file; see CHANGELOG.md for migration instructions)"
[ "$schema_version" = "2" ] || die "Unsupported schema_version ${schema_version} in ${config_file} (this version of josh-sync requires schema_version 2 — see CHANGELOG.md for migration instructions)"
2026-02-12 09:20:55 +03:00
# 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 MONOREPO_URL
MONOREPO_URL=$(echo "$config_json" | jq -r '.josh.monorepo_url')
export MONOREPO_AUTH
MONOREPO_AUTH=$(echo "$config_json" | jq -r '.josh.monorepo_auth // "https"')
2026-02-12 09:20:55 +03:00
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 "$MONOREPO_URL" ] && [ "$MONOREPO_URL" != "null" ] || die "josh.monorepo_url is required (added in schema_version 2). Set it to your monorepo's git clone URL, e.g. 'git@code.example.io:org/repo.git'."
[ "$MONOREPO_AUTH" = "ssh" ] || [ "$MONOREPO_AUTH" = "https" ] || die "josh.monorepo_auth must be 'ssh' or 'https' (got '${MONOREPO_AUTH}')"
2026-02-12 09:20:55 +03:00
[ -n "$BOT_TRAILER" ] && [ "$BOT_TRAILER" != "null" ] || die "bot.trailer missing in config"
# Derive monorepo gitea_host and repo_path from monorepo_url (mirrors target derivation below)
local monorepo_meta
monorepo_meta=$(jq -nr --arg url "$MONOREPO_URL" '
$url as $u |
if ($u | test("^ssh://")) then
($u | capture("ssh://[^@]*@(?<h>[^/]+)/(?<p>.+?)(\\.git)?$") // {h: "", p: ""})
elif ($u | test("^git@")) then
($u | capture("git@(?<h>[^:/]+)[:/](?<p>.+?)(\\.git)?$") // {h: "", p: ""})
elif ($u | test("^https?://")) then
($u | capture("https?://(?<h>[^/]+)/(?<p>.+?)(\\.git)?$") // {h: "", p: ""})
else
{h: "", p: ""}
end | "\(.h) \(.p)"')
export MONOREPO_GITEA_HOST="${monorepo_meta% *}"
export MONOREPO_REPO_PATH="${monorepo_meta#* }"
[ -n "$MONOREPO_GITEA_HOST" ] || die "Could not derive gitea host from josh.monorepo_url='${MONOREPO_URL}' (expected git@host:org/repo.git or https://host/org/repo.git)"
2026-02-12 09:20:55 +03:00
# 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, append inline :exclude[::p1,::p2,...] to the filter
(if (.exclude // [] | length) > 0 then
{josh_filter: (":/" + .subfolder + ":exclude[" + (.exclude | map("::" + .) | join(",")) + "]")}
elif (.josh_filter // "") == "" then
2026-02-12 09:20:55 +03:00
{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://[^@]*@(?<h>[^/]+)/(?<p>.+?)(\\.git)?$") //
{h: "", p: ""} | {gitea_host: .h, subrepo_repo_path: .p})
elif ($url | test("^git@")) then
($url | capture("git@(?<h>[^:/]+)[:/](?<p>.+?)(\\.git)?$") //
{h: "", p: ""} | {gitea_host: .h, subrepo_repo_path: .p})
elif ($url | test("^https?://")) then
($url | capture("https?://(?<h>[^/]+)/(?<p>.+?)(\\.git)?$") //
{h: "", p: ""} | {gitea_host: .h, subrepo_repo_path: .p})
else
{gitea_host: "", subrepo_repo_path: ""}
end
)
]')
# 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 monorepo_url's host, overridable via env for custom API endpoints)
export MONOREPO_API="${MONOREPO_API:-https://${MONOREPO_GITEA_HOST}/api/v1/repos/${MONOREPO_PATH}}"
2026-02-12 09:20:55 +03:00
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})"
}