#!/usr/bin/env bash # Platform Services Configuration Setup Script # Generates and manages platform service configurations in provisioning/config/runtime/ # # Usage: ./setup-platform-config.sh [options] # ./setup-platform-config.sh --service orchestrator --mode solo # ./setup-platform-config.sh --mode multiuser --backend web # ./setup-platform-config.sh --list-modes # # Features: # - Interactive TypeDialog configuration via provisioning/.typedialog/platform/scripts/configure.nu # - Quick mode setup (solo, multiuser, cicd, enterprise) from defaults # - Automatic TOML export for Rust service consumption # - Runtime config detection and management (clean, update, preserve) set -euo pipefail # Ensure TypeDialog is installed SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${SCRIPT_DIR}/ensure-typedialog.sh" || true # Check TypeDialog availability if ! command -v typedialog &> /dev/null; then echo -e "\033[1;33m⚠️ TypeDialog not found. Attempting installation...\033[0m" ensure_typedialog_installed true || { echo -e "\033[0;31m❌ Failed to install TypeDialog\033[0m" exit 1 } fi # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Script paths SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" TYPEDIALOG_SCRIPTS="${PROJECT_ROOT}/.typedialog/platform/scripts" SCHEMAS_PLATFORM="${PROJECT_ROOT}/schemas/platform" CONFIG_RUNTIME="${PROJECT_ROOT}/config/runtime" CONFIG_GENERATED="${CONFIG_RUNTIME}/generated" # Available services and modes SERVICES=("orchestrator" "control-center" "mcp-server" "vault-service" "extension-registry" "rag" "ai-service" "provisioning-daemon") MODES=("solo" "multiuser" "cicd" "enterprise") # Default values BACKEND="${BACKEND:-web}" SERVICE="" MODE="" ACTION="" FORCE=false # ============================================================================ # Helper Functions # ============================================================================ print_header() { echo -e "${BLUE}════════════════════════════════════════════════════════════${NC}" echo -e "${BLUE}$1${NC}" echo -e "${BLUE}════════════════════════════════════════════════════════════${NC}" } print_info() { echo -e "${BLUE}ℹ️ $1${NC}" } print_success() { echo -e "${GREEN}✅ $1${NC}" } print_warning() { echo -e "${YELLOW}⚠️ $1${NC}" } print_error() { echo -e "${RED}❌ $1${NC}" >&2 } # Check if directory/file exists check_exists() { if [[ ! -e "$1" ]]; then print_error "Not found: $1" return 1 fi } # ============================================================================ # Service/Mode Validation # ============================================================================ validate_service() { local service="$1" # "all" is handled by bash script itself, not passed to Nushell if [[ "$service" == "all" ]]; then return 0 fi if [[ ! " ${SERVICES[*]} " =~ " ${service} " ]]; then print_error "Invalid service: $service" echo "Valid services: ${SERVICES[*]}, all" return 1 fi } validate_mode() { local mode="$1" if [[ ! " ${MODES[*]} " =~ " ${mode} " ]]; then print_error "Invalid mode: $mode" echo "Valid modes: ${MODES[*]}" return 1 fi } # ============================================================================ # Directory Setup # ============================================================================ ensure_runtime_dir() { if [[ ! -d "$CONFIG_RUNTIME" ]]; then print_info "Creating runtime config directory: $CONFIG_RUNTIME" mkdir -p "$CONFIG_RUNTIME" "$CONFIG_GENERATED" fi # Create .gitignore if not present if [[ ! -f "$CONFIG_RUNTIME/.gitignore" ]]; then cat > "$CONFIG_RUNTIME/.gitignore" </dev/null 2>&1; then echo "existing" return 0 fi echo "empty" return 0 } list_runtime_services() { local services=() for file in "$CONFIG_RUNTIME"/*.ncl; do if [[ -f "$file" ]]; then local basename=$(basename "$file" .ncl) services+=("$basename") fi done if [[ ${#services[@]} -gt 0 ]]; then printf '%s\n' "${services[@]}" fi } # ============================================================================ # Interactive Prompts # ============================================================================ prompt_action_existing_config() { while true; do echo "" print_warning "Runtime configuration already exists in: $CONFIG_RUNTIME" echo "" echo "Choose action:" echo " 1) Clean up and start fresh (removes all .ncl and .toml files)" echo " 2) Use TypeDialog to update configuration [default]" echo " 3) Setup quick mode (solo/multiuser/cicd/enterprise)" echo " 4) List existing configurations" echo " 5) Cancel" echo "" echo "Press CTRL-C to cancel at any time" echo "" read -rp "Enter choice [1-5] (default: 2): " choice # Default to 2 (TypeDialog update) choice="${choice:-2}" case "$choice" in 1) ACTION="clean-start"; return 0 ;; 2) ACTION="typedialog"; return 0 ;; 3) ACTION="quick-mode"; return 0 ;; 4) ACTION="list"; return 0 ;; 5) print_info "Cancelled."; exit 0 ;; *) print_error "Invalid choice. Please enter 1-5 (or press CTRL-C to abort)." ;; esac done } prompt_action_empty_config() { while true; do echo "" echo "Choose how to setup platform configuration:" echo " 1) Interactive TypeDialog (recommended, with UI form) [default]" echo " 2) Quick mode setup (choose solo/multiuser/cicd/enterprise)" echo " 3) Cancel" echo "" echo "Press CTRL-C to cancel at any time" echo "" read -rp "Enter choice [1-3] (default: 1): " choice # Default to 1 if empty choice="${choice:-1}" case "$choice" in 1) ACTION="typedialog"; return 0 ;; 2) ACTION="quick-mode"; return 0 ;; 3) print_info "Cancelled."; exit 0 ;; *) print_error "Invalid choice. Please enter 1, 2, or 3 (or press CTRL-C to abort)." ;; esac done } prompt_for_service() { local max_choice=$((${#SERVICES[@]}+1)) while true; do echo "" echo "Select service to configure:" for i in "${!SERVICES[@]}"; do echo " $((i+1))) ${SERVICES[$i]}" done echo " $max_choice) Configure all services [default]" echo "" echo "Press CTRL-C to cancel" echo "" read -rp "Enter choice [1-$max_choice] (default: $max_choice): " choice # Default to "all services" choice="${choice:-$max_choice}" # Validate numeric input if ! [[ "$choice" =~ ^[0-9]+$ ]]; then print_error "Invalid input. Please enter a number (or press CTRL-C to abort)." continue fi if [[ "$choice" -ge 1 && "$choice" -le "$max_choice" ]]; then if [[ "$choice" == "$max_choice" ]]; then SERVICE="all" else SERVICE="${SERVICES[$((choice-1))]}" fi return 0 else print_error "Invalid choice. Please enter a number between 1 and $max_choice (or press CTRL-C to abort)." fi done } prompt_for_mode() { local max_choice=${#MODES[@]} while true; do echo "" echo "Select deployment mode:" for i in "${!MODES[@]}"; do local marker="" # Mark solo as default if [[ "${MODES[$i]}" == "solo" ]]; then marker=" [default]" fi echo " $((i+1))) ${MODES[$i]}$marker" done echo "" echo "Press CTRL-C to cancel" echo "" read -rp "Enter choice [1-$max_choice] (default: 1): " choice # Default to 1 (solo) choice="${choice:-1}" # Validate numeric input if ! [[ "$choice" =~ ^[0-9]+$ ]]; then print_error "Invalid input. Please enter a number (or press CTRL-C to abort)." continue fi if [[ "$choice" -ge 1 && "$choice" -le "$max_choice" ]]; then MODE="${MODES[$((choice-1))]}" return 0 else print_error "Invalid choice. Please enter a number between 1 and $max_choice (or press CTRL-C to abort)." fi done } prompt_for_backend() { while true; do echo "" echo "Select TypeDialog backend:" echo " 1) web (browser-based, recommended) [default]" echo " 2) tui (terminal UI)" echo " 3) cli (command-line)" echo "" echo "Press CTRL-C to cancel" echo "" read -rp "Enter choice [1-3] (default: 1): " choice # Default to 1 (web) choice="${choice:-1}" case "$choice" in 1) BACKEND="web"; return 0 ;; 2) BACKEND="tui"; return 0 ;; 3) BACKEND="cli"; return 0 ;; *) print_error "Invalid choice. Please enter 1, 2, or 3 (or press CTRL-C to abort)." ;; esac done } # ============================================================================ # Configuration Generation # ============================================================================ generate_toml_for_service() { local service="$1" local mode="$2" local ncl_file="${CONFIG_RUNTIME}/${service}.${mode}.ncl" if [[ ! -f "$ncl_file" ]]; then print_warning "Nickel config not found: $ncl_file" return 1 fi print_info "Exporting TOML for $service ($mode)..." local toml_file="${CONFIG_GENERATED}/${service}.${mode}.toml" # Generate TOML from Nickel if nickel export --format toml "$ncl_file" > "$toml_file" 2>/dev/null; then print_success "Generated: $toml_file" return 0 else print_error "Failed to generate TOML for $service ($mode)" return 1 fi } generate_all_tomls() { echo "" print_header "Generating TOML Exports" local generated_count=0 local failed_count=0 # Scan for all .ncl files in runtime for ncl_file in "$CONFIG_RUNTIME"/*.ncl; do if [[ -f "$ncl_file" ]]; then local basename=$(basename "$ncl_file" .ncl) local service="${basename%.*}" # Remove mode suffix local mode="${basename##*.}" # Extract mode if generate_toml_for_service "$service" "$mode"; then ((generated_count++)) else ((failed_count++)) fi fi done echo "" print_success "Generated $generated_count TOML files" if [[ $failed_count -gt 0 ]]; then print_warning "$failed_count files failed" fi } # ============================================================================ # TypeDialog Configuration # ============================================================================ configure_via_typedialog() { check_exists "$TYPEDIALOG_SCRIPTS/configure.nu" || return 1 echo "" print_header "TypeDialog Configuration" # Prompt for service if not provided if [[ -z "$SERVICE" ]]; then prompt_for_service else validate_service "$SERVICE" || return 1 fi # Prompt for mode if not provided if [[ -z "$MODE" ]]; then prompt_for_mode else validate_mode "$MODE" || return 1 fi # Prompt for backend if using web if [[ "$BACKEND" == "default" ]]; then prompt_for_backend fi # Handle "all" services by iterating if [[ "$SERVICE" == "all" ]]; then print_info "Configuring all services for mode: $MODE" echo "" local success_count=0 local fail_count=0 for svc in "${SERVICES[@]}"; do print_info "Launching TypeDialog ($BACKEND backend) for $svc ($MODE)..." # Execute from scripts dir so relative module imports work if (cd "$TYPEDIALOG_SCRIPTS" && nu configure.nu "$svc" "$MODE" --backend "$BACKEND"); then print_success "Configuration completed for $svc" generate_toml_for_service "$svc" "$MODE" && ((success_count++)) || ((fail_count++)) else print_warning "Configuration skipped or failed for $svc" ((fail_count++)) fi echo "" done print_info "Configured $success_count services, $fail_count failed/skipped" return 0 fi print_info "Launching TypeDialog ($BACKEND backend) for $SERVICE ($MODE)..." echo "" # Run TypeDialog via Nushell script (execute from scripts dir so module imports work) if (cd "$TYPEDIALOG_SCRIPTS" && nu configure.nu "$SERVICE" "$MODE" --backend "$BACKEND"); then print_success "Configuration completed for $SERVICE" # Auto-generate TOML generate_toml_for_service "$SERVICE" "$MODE" print_info "" print_info "Next steps:" print_info " - Review config: cat $CONFIG_RUNTIME/$SERVICE.$MODE.ncl" print_info " - Export TOML: $TYPEDIALOG_SCRIPTS/generate-configs.nu $SERVICE $MODE" print_info " - Run service: ORCHESTRATOR_MODE=$MODE cargo run -p $SERVICE" return 0 else print_error "TypeDialog configuration cancelled or failed" return 1 fi } # ============================================================================ # Quick Mode Setup (from defaults) # ============================================================================ setup_quick_mode() { echo "" print_header "Quick Mode Setup" # Prompt for mode if not provided if [[ -z "$MODE" ]]; then prompt_for_mode else validate_mode "$MODE" || return 1 fi print_info "Setting up all services for mode: $MODE" echo "" local created_count=0 local failed_count=0 # Create config for each service by composing defaults + mode overlay for service in "${SERVICES[@]}"; do local ncl_file="$CONFIG_RUNTIME/${service}.${MODE}.ncl" local schema_file="$SCHEMAS_PLATFORM/schemas/${service}.ncl" local defaults_file="$SCHEMAS_PLATFORM/defaults/${service}-defaults.ncl" local mode_defaults="$SCHEMAS_PLATFORM/defaults/deployment/${MODE}-defaults.ncl" if [[ ! -f "$schema_file" ]] || [[ ! -f "$defaults_file" ]]; then print_warning "Schema or defaults not found for $service (skipping)" ((failed_count++)) continue fi # Create Nickel config that composes: schema + defaults + mode overlay cat > "$ncl_file" <<'EOF' # Generated configuration for SERVICE (MODE mode) # Generated: $(date -u +"%Y-%m-%dT%H:%M:%SZ") # DO NOT EDIT MANUALLY - Re-run setup-platform-config.sh to update # # This configuration is composed from: # - Schema: provisioning/schemas/platform/schemas/SERVICE.ncl # - Defaults: provisioning/schemas/platform/defaults/SERVICE-defaults.ncl # - Mode: provisioning/schemas/platform/defaults/deployment/MODE-defaults.ncl let helpers = import "provisioning/schemas/platform/common/helpers.ncl" let schema = import "provisioning/schemas/platform/schemas/SERVICE.ncl" let defaults = import "provisioning/schemas/platform/defaults/SERVICE-defaults.ncl" let mode_config = import "provisioning/schemas/platform/defaults/deployment/MODE-defaults.ncl" # Compose: base defaults + mode overlay + validators helpers.compose_config defaults mode_config {} EOF # Replace placeholders with actual values sed -i.bak "s|SERVICE|$service|g" "$ncl_file" sed -i.bak "s|MODE|$MODE|g" "$ncl_file" rm -f "${ncl_file}.bak" if [[ -f "$ncl_file" ]]; then print_success "Created config: $service ($MODE)" # Generate TOML if generate_toml_for_service "$service" "$MODE"; then ((created_count++)) else ((failed_count++)) fi fi done echo "" print_success "Setup complete: $created_count services configured" if [[ $failed_count -gt 0 ]]; then print_warning "$failed_count services failed" fi } # ============================================================================ # Cleanup Operations # ============================================================================ cleanup_runtime_config() { echo "" print_warning "This will delete all runtime configurations" read -rp "Are you sure? (yes/no): " confirm if [[ "$confirm" != "yes" ]]; then print_info "Cancelled" return 0 fi print_info "Removing runtime configurations..." rm -f "$CONFIG_RUNTIME"/*.ncl "$CONFIG_GENERATED"/*.toml print_success "Cleanup complete" } # ============================================================================ # Listing Operations # ============================================================================ list_deployment_modes() { echo "" print_info "Available deployment modes:" for mode in "${MODES[@]}"; do echo " - $mode" done } list_available_services() { echo "" print_info "Available services:" for service in "${SERVICES[@]}"; do echo " - $service" done } list_runtime_configs() { echo "" print_header "Runtime Configurations" local status=$(detect_runtime_config) if [[ "$status" == "empty" ]]; then print_info "No runtime configurations found" return 0 fi echo "Configured services:" list_runtime_services | while read -r service; do echo " ✓ $service" done echo "" echo "Location: $CONFIG_RUNTIME" } # ============================================================================ # Help Text # ============================================================================ show_help() { cat </dev/null; then print_error "Nickel not found. Please install Nickel (https://nickel-lang.org/)" exit 1 fi if ! command -v nu &>/dev/null; then print_error "Nushell not found. Please install Nushell" exit 1 fi # Execute requested action case "${ACTION:-}" in typedialog) configure_via_typedialog || exit 1 ;; quick-mode) setup_quick_mode || exit 1 ;; clean) cleanup_runtime_config ;; list) list_runtime_configs ;; list-modes) list_deployment_modes ;; list-services) list_available_services ;; generate-toml) generate_all_tomls ;; *) # No action specified - interactive mode print_header "Platform Services Configuration Setup" local runtime_status=$(detect_runtime_config) if [[ "$runtime_status" == "existing" ]]; then prompt_action_existing_config else prompt_action_empty_config fi # Execute chosen action case "$ACTION" in typedialog) configure_via_typedialog || exit 1 ;; quick-mode) setup_quick_mode || exit 1 ;; clean) cleanup_runtime_config ;; list) list_runtime_configs ;; *) print_error "No action selected"; exit 1 ;; esac ;; esac echo "" print_success "Done!" } # Run main function main "$@"