#!/usr/bin/env bash # TypeDialog Installation Script # Installs TypeDialog (main + backends + tools) for interactive configuration and provisioning # # TypeDialog Suite Components: # BACKENDS (interactive forms): # - typedialog CLI backend (step-by-step prompts) # - typedialog-tui Terminal UI backend (3-panel interface) # - typedialog-web Web backend (browser-based interface) # TOOLS (provisioning): # - typedialog-ag Type-safe AI agents for automation # - typedialog-ai AI configuration assistant (HTTP server backend) # - typedialog-prov-gen Provisioning structure generator # # Usage: # ./install-typedialog.sh # Interactive mode # ./install-typedialog.sh --version 0.1.0 # Specify version # ./install-typedialog.sh --components all # Install all (default) # ./install-typedialog.sh --components backends # Only: cli,tui,web # ./install-typedialog.sh --components tools # Only: ag,ai,prov-gen # ./install-typedialog.sh --components cli,tui,ag # Custom selection # ./install-typedialog.sh --install-dir /usr/local/bin # Custom directory # ./install-typedialog.sh --help # Show help set -euo pipefail # 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 configuration SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" VERSIONS_FILE="${PROJECT_ROOT}/core/versions.ncl" # All available components BACKENDS=("cli" "tui" "web") TOOLS=("ag" "ai" "prov-gen") ALL_COMPONENTS=("${BACKENDS[@]}" "${TOOLS[@]}") # Default values TYPEDIALOG_VERSION="" INSTALL_DIR="" INSTALL_COMPONENTS="all" FORCE=false VERBOSE=false SKIP_VALIDATION=false INTERACTIVE=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 } debug() { if [[ "$VERBOSE" == "true" ]]; then echo -e "${BLUE}🔍 $1${NC}" fi } # ============================================================================ # Platform Detection # ============================================================================ detect_os() { local os="" case "$(uname -s)" in Darwin*) os="macos" ;; Linux*) os="linux" ;; MINGW*|MSYS*|CYGWIN*) os="windows" ;; *) print_error "Unsupported OS: $(uname -s)"; return 1 ;; esac echo "$os" } detect_arch() { local arch="" case "$(uname -m)" in x86_64|amd64) arch="x86_64" ;; aarch64|arm64) arch="aarch64" ;; *) print_error "Unsupported architecture: $(uname -m)"; return 1 ;; esac echo "$arch" } get_binary_name() { local component=$1 local os=$2 # Map component name to binary name local binary="typedialog" case "$component" in tui) binary="typedialog-tui" ;; web) binary="typedialog-web" ;; ag) binary="typedialog-ag" ;; ai) binary="typedialog-ai" ;; prov-gen) binary="typedialog-prov-gen" ;; *) binary="typedialog" ;; esac case "$os" in macos|linux) echo "${binary}" ;; windows) echo "${binary}.exe" ;; *) echo "${binary}" ;; esac } # ============================================================================ # Version Management # ============================================================================ extract_version_from_ncl() { local tool=$1 if [[ ! -f "$VERSIONS_FILE" ]]; then debug "versions.ncl not found at $VERSIONS_FILE" return 1 fi # Extract version for tool using grep local version_line=$(grep -A 2 "name = \"$tool\"" "$VERSIONS_FILE" | grep "current =" | head -1) if [[ -z "$version_line" ]]; then return 1 fi # Extract version string (format: current = "X.Y.Z",) local version=$(echo "$version_line" | sed 's/.*current = "\([^"]*\)".*/\1/') if [[ -z "$version" || "$version" == "$version_line" ]]; then return 1 fi echo "$version" } # ============================================================================ # Installation Functions # ============================================================================ ensure_install_dir() { local dir=$1 if [[ ! -d "$dir" ]]; then print_info "Creating installation directory: $dir" mkdir -p "$dir" || { print_error "Failed to create directory: $dir" return 1 } fi # Verify directory is writable if [[ ! -w "$dir" ]]; then print_error "Installation directory is not writable: $dir" return 1 fi } download_binary() { local binary=$1 local version=$2 local os=$3 local arch=$4 local output_path=$5 # GitHub release URL pattern local github_repo="typedialog/typedialog" local release_url="https://github.com/${github_repo}/releases/download/v${version}" # Determine binary name local binary_name=$(get_binary_name "$binary" "$os") local download_url="${release_url}/${binary_name}-${os}-${arch}" print_info "Downloading: ${binary} (${version}) for ${os}/${arch}" debug "URL: $download_url" # Download with curl if ! curl -fsSL --progress-bar "$download_url" -o "$output_path"; then print_error "Failed to download from: $download_url" return 1 fi # Verify downloaded file if [[ ! -f "$output_path" ]]; then print_error "Downloaded file not found: $output_path" return 1 fi # Check file size (should not be tiny) local file_size=$(stat -f%z "$output_path" 2>/dev/null || stat -c%s "$output_path" 2>/dev/null || echo 0) if [[ $file_size -lt 1000000 ]]; then # Less than 1MB = likely error print_warning "Downloaded file seems too small ($file_size bytes), may be error page" return 1 fi print_success "Downloaded: $output_path" } install_binary() { local source=$1 local dest=$2 local binary_name=$3 # Copy binary if ! cp "$source" "$dest"; then print_error "Failed to copy binary from $source to $dest" return 1 fi # Make executable if ! chmod +x "$dest"; then print_error "Failed to make binary executable: $dest" return 1 fi print_success "Installed: $dest" } validate_installation() { local binary=$1 local expected_version=$2 # Check if binary exists in PATH if ! command -v "$binary" &> /dev/null; then print_error "Binary not found in PATH: $binary" return 1 fi # Get installed version local installed_version=$("$binary" --version 2>&1 | head -1 | sed 's/.*\s\([0-9.]*\)$/\1/' || echo "") if [[ -z "$installed_version" ]]; then print_warning "Could not determine installed version of $binary" if [[ "$SKIP_VALIDATION" == "false" ]]; then return 1 fi return 0 fi # Check version match if [[ "$installed_version" != "$expected_version" ]]; then print_warning "Version mismatch: expected $expected_version, got $installed_version" if [[ "$FORCE" != "true" ]]; then return 1 fi fi print_success "Validated: $binary ($installed_version)" } # ============================================================================ # Main Installation Flow # ============================================================================ install_component() { local component=$1 local version=$2 local install_dir=$3 local os=$4 local arch=$5 local component_type=${6:-""} # "backend" or "tool" for logging local binary_name=$(get_binary_name "$component" "$os") local display_name="typedialog" if [[ "$component" != "cli" ]]; then display_name="typedialog-${component}" fi print_info "Installing: ${display_name}${component_type:+ ($component_type)}" # Create temp directory for downloads local temp_dir=$(mktemp -d) trap "rm -rf $temp_dir" EXIT # Download component binary local temp_binary="${temp_dir}/${binary_name}" download_binary "$component" "$version" "$os" "$arch" "$temp_binary" || { print_warning "Failed to download ${display_name} (may not be available for this version/platform)" return 0 # Don't fail if component unavailable } # Install component binary install_binary "$temp_binary" "${install_dir}/${binary_name}" "${display_name}" || { print_warning "Failed to install ${display_name}" return 0 # Don't fail if installation fails } return 0 } install_components() { local components=$1 local version=$2 local install_dir=$3 local os=$4 local arch=$5 if [[ -z "$components" || "$components" == "none" ]]; then print_info "No components specified for installation" return 0 fi # Expand "all" and "backends"/"tools" aliases local expanded_components="" if [[ "$components" == "all" ]]; then expanded_components="${ALL_COMPONENTS[*]}" elif [[ "$components" == "backends" ]]; then expanded_components="${BACKENDS[*]}" elif [[ "$components" == "tools" ]]; then expanded_components="${TOOLS[*]}" else # Assume comma-separated list expanded_components=$(echo "$components" | sed 's/,/ /g') fi print_header "Installing TypeDialog Components" local installed_count=0 local failed_count=0 for component in $expanded_components; do component=$(echo "$component" | xargs) # trim whitespace if [[ -z "$component" ]]; then continue fi # Validate component name if [[ ! " ${ALL_COMPONENTS[*]} " =~ " ${component} " ]]; then print_warning "Unknown component: $component" continue fi # Determine component type for logging local ctype="" if [[ " ${BACKENDS[*]} " =~ " ${component} " ]]; then ctype="backend" elif [[ " ${TOOLS[*]} " =~ " ${component} " ]]; then ctype="tool" fi if install_component "$component" "$version" "$install_dir" "$os" "$arch" "$ctype"; then ((installed_count++)) else ((failed_count++)) fi done echo "" print_success "Installation completed: ${installed_count} component(s) installed" if [[ $failed_count -gt 0 ]]; then print_warning "${failed_count} component(s) failed" fi } validate_all() { local install_dir=$1 print_header "Validating Installation" # Validate main binary validate_installation "typedialog" "$TYPEDIALOG_VERSION" || return 1 # Validate installed backends for backend in tui web; do if command -v "typedialog-${backend}" &> /dev/null; then validate_installation "typedialog-${backend}" "$TYPEDIALOG_VERSION" || true fi done return 0 } # ============================================================================ # Help and Configuration # ============================================================================ show_help() { cat <" print_info " 4. Generate configs: typedialog-prov-gen generate --help" print_info " 5. Use AI assistant: typedialog-ai serve" echo "" print_info "Documentation:" print_info " - provisioning/.typedialog/platform/README.md" print_info " - provisioning/.typedialog/provisioning/form.toml" echo "" return 0 } # Run main function main "$@"