#!/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" SCHEMAS_VALUES="${SCHEMAS_PLATFORM}/values" SCHEMAS_CONFIGS="${SCHEMAS_PLATFORM}/configs" PLATFORM_CONFIG="${PROJECT_ROOT}/../platform/config" # User platform config path (set by CLI via PROVISIONING_USER_PLATFORM or fallback to OS-specific default) # CLI sets: PROVISIONING_USER_PLATFORM="${HOME}/Library/Application Support/provisioning/platform" (darwin) # PROVISIONING_USER_PLATFORM="${HOME}/.config/provisioning/platform" (linux) if [[ -n "${PROVISIONING_USER_PLATFORM:-}" ]]; then PLATFORM_CONFIG_BASE="${PROVISIONING_USER_PLATFORM}" elif [[ "$OSTYPE" == "darwin"* ]]; then PLATFORM_CONFIG_BASE="${HOME}/Library/Application Support/provisioning/platform" else PLATFORM_CONFIG_BASE="${HOME}/.config/provisioning/platform" fi # User config directory for service-specific configs (*.ncl files) USER_CONFIG_DIR="${PLATFORM_CONFIG_BASE}/config" # 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_config_dirs() { # Ensure schemas/platform/values directory exists for user configs if [[ ! -d "$SCHEMAS_VALUES" ]]; then print_info "Creating values directory: $SCHEMAS_VALUES" mkdir -p "$SCHEMAS_VALUES" fi # Ensure platform/config directory exists for TOML outputs if [[ ! -d "$PLATFORM_CONFIG" ]]; then print_info "Creating platform config directory: $PLATFORM_CONFIG" mkdir -p "$PLATFORM_CONFIG" fi # Ensure user platform config directory exists if [[ ! -d "$USER_CONFIG_DIR" ]]; then print_info "Creating user config directory: $USER_CONFIG_DIR" mkdir -p "$USER_CONFIG_DIR" fi # Create .gitignore in values directory if not present if [[ ! -f "$SCHEMAS_VALUES/.gitignore" ]]; then cat >"$SCHEMAS_VALUES/.gitignore" <"$deployment_mode_file" <<'EOF' # Platform Deployment Mode Configuration # Generated by setup-platform-config.sh on initial setup # # Modes: "local" | "docker-compose" | "kubernetes" # This determines HOW platform services are deployed, not WHAT features are enabled { # Deployment mode: local | docker-compose | kubernetes mode = "local", # Manager configuration (adapts based on mode) manager = { # Local: localhost or custom hostname hostname = "localhost", port = 9090, }, # User service configurations directory config_dir = "$USER_CONFIG_DIR", # Enable health checks and monitoring health_checks_enabled = true, # Timeout for service startup (seconds) startup_timeout = 60, # External infrastructure services (databases, registries, CI/CD, etc.) external_services = [], # Metadata description = "Local development with binaries in ~/.local/bin", created_at = "$(date -u +"%Y-%m-%dT%H:%M:%SZ")", } EOF if [[ -f "$deployment_mode_file" ]]; then print_success "Created deployment mode config: $deployment_mode_file" print_info "Mode: local (binaries in ~/.local/bin)" print_info "To change mode, edit: $deployment_mode_file" return 0 else print_error "Failed to create deployment mode config" return 1 fi } print_deployment_mode_help() { cat <"$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 } # ============================================================================ # Interactive Prompts # ============================================================================ prompt_action_existing_config() { while true; do echo "" print_warning "User configurations already exist in: $USER_CONFIG_DIR" 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 } # ============================================================================ # User Config Management # ============================================================================ get_or_create_user_config() { local service="$1" local mode="$2" local user_config_file="${USER_CONFIG_DIR}/${service}.${mode}.ncl" # Ensure user config directory exists if [[ ! -d "$USER_CONFIG_DIR" ]]; then mkdir -p "$USER_CONFIG_DIR" || { print_error "Failed to create user config directory: $USER_CONFIG_DIR" return 1 } fi # If user config exists, return path if [[ -f "$user_config_file" ]]; then echo "$user_config_file" return 0 fi # User config doesn't exist - initialize from template local template_path="${SCHEMAS_PLATFORM}/defaults/${service}-defaults.ncl" local mode_defaults="${SCHEMAS_PLATFORM}/defaults/deployment/${mode}-defaults.ncl" if [[ ! -f "$template_path" ]]; then print_error "Default template not found: $template_path" return 1 fi if [[ ! -f "$mode_defaults" ]]; then print_error "Mode defaults not found: $mode_defaults" return 1 fi print_info "Initializing user config from defaults: $user_config_file" # Create initial config by composing defaults + mode overlay cat >"$user_config_file" </dev/null; then print_error "typedialog-web not found. Install with: cargo install typedialog --features web" return 1 fi typedialog_cmd="typedialog-web" ;; tui) if ! command -v typedialog-tui &>/dev/null; then print_error "typedialog-tui not found. Install with: cargo install typedialog --features tui" return 1 fi typedialog_cmd="typedialog-tui" ;; cli) if ! command -v typedialog &>/dev/null; then print_error "typedialog not found. Install with: cargo install typedialog" return 1 fi ;; *) print_error "Invalid backend: $backend" return 1 ;; esac # Run TypeDialog with proper argument ordering # Arguments: nickel-roundtrip --output --ncl-template