#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" MODELS_ENV_FILE="${MODELS_ENV_FILE:-$ROOT_DIR/config/models.env}" PROFILES_CONFIG="${MODEL_PROFILES_CONFIG:-$ROOT_DIR/config/model-profiles.config.json}" SCHEDULE_CONFIG="${MODEL_SCHEDULE_CONFIG:-$ROOT_DIR/config/model-schedule.config.json}" BUDGET_CONFIG="${MODEL_GUARD_CONFIG:-$ROOT_DIR/config/model-budget-guard.config.json}" if ! command -v jq >/dev/null 2>&1; then echo "[model-sync] jq is required" >&2 exit 1 fi usage() { cat <<'USAGE' Usage: bash ./scripts/sync_models_from_env.sh Env source: config/models.env (override with MODELS_ENV_FILE) Required variables: PAID_PRIMARY PAID_FALLBACKS FREE_PRIMARY FREE_FALLBACKS SCHEDULE # dayProfile,nightProfile,dayStartHour,nightStartHour Optional variables: SCHEDULE_ENABLED # true/false (default: true) USAGE } trim() { local s="$1" s="${s#"${s%%[![:space:]]*}"}" s="${s%"${s##*[![:space:]]}"}" printf '%s' "$s" } csv_to_json_array() { local csv="$1" jq -cn --arg csv "$csv" ' $csv | split(",") | map(gsub("^\\s+|\\s+$"; "")) | map(select(length > 0)) ' } if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then usage exit 0 fi if [[ ! -f "$MODELS_ENV_FILE" ]]; then echo "[model-sync] missing env file: $MODELS_ENV_FILE" >&2 exit 1 fi if [[ ! -f "$PROFILES_CONFIG" || ! -f "$SCHEDULE_CONFIG" || ! -f "$BUDGET_CONFIG" ]]; then echo "[model-sync] required config files are missing" >&2 exit 1 fi set -a # shellcheck disable=SC1090 source "$MODELS_ENV_FILE" set +a required=(PAID_PRIMARY PAID_FALLBACKS FREE_PRIMARY FREE_FALLBACKS SCHEDULE) for key in "${required[@]}"; do if [[ -z "${!key:-}" ]]; then echo "[model-sync] missing required variable: $key" >&2 exit 1 fi done paid_fallbacks_json="$(csv_to_json_array "$PAID_FALLBACKS")" free_fallbacks_json="$(csv_to_json_array "$FREE_FALLBACKS")" if [[ "$(jq -r 'length' <<<"$paid_fallbacks_json")" == "0" ]]; then echo "[model-sync] PAID_FALLBACKS cannot be empty" >&2 exit 1 fi if [[ "$(jq -r 'length' <<<"$free_fallbacks_json")" == "0" ]]; then echo "[model-sync] FREE_FALLBACKS cannot be empty" >&2 exit 1 fi IFS=',' read -r day_profile_raw night_profile_raw day_start_raw night_start_raw <<<"$SCHEDULE" day_profile="$(trim "${day_profile_raw:-}")" night_profile="$(trim "${night_profile_raw:-}")" day_start="$(trim "${day_start_raw:-}")" night_start="$(trim "${night_start_raw:-}")" if [[ -z "$day_profile" || -z "$night_profile" || -z "$day_start" || -z "$night_start" ]]; then echo "[model-sync] SCHEDULE must be: dayProfile,nightProfile,dayStartHour,nightStartHour" >&2 exit 1 fi if ! [[ "$day_start" =~ ^[0-9]+$ && "$night_start" =~ ^[0-9]+$ ]]; then echo "[model-sync] schedule hours must be integers" >&2 exit 1 fi if (( day_start < 0 || day_start > 23 || night_start < 0 || night_start > 23 )); then echo "[model-sync] schedule hours must be in range 0..23" >&2 exit 1 fi schedule_enabled="$(trim "${SCHEDULE_ENABLED:-true}")" if [[ "$schedule_enabled" != "true" && "$schedule_enabled" != "false" ]]; then echo "[model-sync] SCHEDULE_ENABLED must be true or false" >&2 exit 1 fi tmp_profiles="$(mktemp)" jq \ --arg paid_primary "$PAID_PRIMARY" \ --arg free_primary "$FREE_PRIMARY" \ --argjson paid_fallbacks "$paid_fallbacks_json" \ --argjson free_fallbacks "$free_fallbacks_json" \ ' .profiles.paid.primary = $paid_primary | .profiles.paid.fallbacks = $paid_fallbacks | .profiles.free.primary = $free_primary | .profiles.free.fallbacks = $free_fallbacks ' "$PROFILES_CONFIG" > "$tmp_profiles" mv "$tmp_profiles" "$PROFILES_CONFIG" tmp_schedule="$(mktemp)" jq \ --arg day_profile "$day_profile" \ --arg night_profile "$night_profile" \ --argjson day_start "$day_start" \ --argjson night_start "$night_start" \ --arg enabled "$schedule_enabled" \ ' .enabled = ($enabled == "true") | .dayProfile = $day_profile | .nightProfile = $night_profile | .dayStartHour = $day_start | .nightStartHour = $night_start ' "$SCHEDULE_CONFIG" > "$tmp_schedule" mv "$tmp_schedule" "$SCHEDULE_CONFIG" tmp_budget="$(mktemp)" jq \ --arg low_model "$FREE_PRIMARY" \ '.lowModel = $low_model' \ "$BUDGET_CONFIG" > "$tmp_budget" mv "$tmp_budget" "$BUDGET_CONFIG" echo "[model-sync] updated:" echo " - $PROFILES_CONFIG" echo " - $SCHEDULE_CONFIG" echo " - $BUDGET_CONFIG" echo "" echo "[model-sync] paid primary: $(jq -r '.profiles.paid.primary' "$PROFILES_CONFIG")" echo "[model-sync] free primary: $(jq -r '.profiles.free.primary' "$PROFILES_CONFIG")" echo "[model-sync] schedule: $(jq -r '.enabled, .dayProfile, .nightProfile, .dayStartHour, .nightStartHour' "$SCHEDULE_CONFIG" | paste -sd ',' -)" echo "[model-sync] budget lowModel: $(jq -r '.lowModel' "$BUDGET_CONFIG")"