provisioning/scripts/install-typedialog.sh

641 lines
19 KiB
Bash
Raw Permalink Normal View History

2026-01-08 21:22:57 +00:00
#!/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 <<EOF
${BLUE}TypeDialog Installation Script${NC}
Complete installation of TypeDialog suite: backends (CLI/TUI/Web) + tools (AG/AI/ProvGen)
Usage: $(basename "$0") [OPTIONS]
OPTIONS:
-v, --version VERSION TypeDialog version to install (default: from versions.ncl)
-c, --components COMPONENTS Components to install (see COMPONENTS below)
-d, --install-dir DIR Installation directory (default: ~/.local/bin)
-i, --interactive Interactive mode (select components)
--force Force installation even on version mismatch
--skip-validation Skip validation checks after installation
--verbose Enable verbose output
-h, --help Show this help message
COMPONENTS:
Aliases (install multiple):
all All 6 components (backends + tools) - DEFAULT
backends All backends: cli, tui, web
tools All tools: ag, ai, prov-gen
Individual:
BACKENDS (interactive forms):
cli CLI backend (step-by-step prompts)
tui Terminal UI backend (3-panel interface)
web Web backend (browser-based)
TOOLS (provisioning):
ag AI agents for automation & transpilation
ai AI configuration assistant (HTTP server)
prov-gen Provisioning structure generator
Examples:
all typedialog + typedialog-tui + typedialog-web + typedialog-ag + typedialog-ai + typedialog-prov-gen
backends typedialog + typedialog-tui + typedialog-web
cli,tui,ag typedialog + typedialog-tui + typedialog-ag (custom selection)
EXAMPLES:
# Interactive mode (select what to install)
$(basename "$0") --interactive
# Install all components with defaults
$(basename "$0") --components all
# Install only backends
$(basename "$0") --components backends
# Install only tools
$(basename "$0") --components tools
# Custom selection
$(basename "$0") --components cli,tui,prov-gen
# Install specific version to system directory
$(basename "$0") --version 0.1.0 --components all --install-dir /usr/local/bin
# Verbose installation with validation
$(basename "$0") --verbose --components all
ENVIRONMENT VARIABLES:
TYPEDIALOG_INSTALL_DIR Override default installation directory
TYPEDIALOG_VERSION Override version detection
EOF
}
# ============================================================================
# Parse Arguments
# ============================================================================
prompt_for_components() {
echo ""
print_info "Select what to install:"
echo ""
echo " 1) All components (backends + tools) - RECOMMENDED"
echo " 2) Backends only (CLI, TUI, Web)"
echo " 3) Tools only (AG, AI, ProvGen)"
echo " 4) Custom selection"
echo " 5) Cancel"
echo ""
read -rp "Choice [1-5]: " choice
case "$choice" in
1) INSTALL_COMPONENTS="all" ;;
2) INSTALL_COMPONENTS="backends" ;;
3) INSTALL_COMPONENTS="tools" ;;
4)
echo ""
echo "Available components:"
echo " Backends: cli, tui, web"
echo " Tools: ag, ai, prov-gen"
echo ""
read -rp "Enter comma-separated list (e.g., cli,tui,ag): " custom
INSTALL_COMPONENTS="$custom"
;;
5)
print_info "Installation cancelled"
exit 0
;;
*)
print_error "Invalid choice"
exit 1
;;
esac
}
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
-v|--version)
TYPEDIALOG_VERSION="$2"
shift 2
;;
-c|--components)
INSTALL_COMPONENTS="$2"
shift 2
;;
-d|--install-dir)
INSTALL_DIR="$2"
shift 2
;;
-i|--interactive)
INTERACTIVE=true
shift
;;
--force)
FORCE=true
shift
;;
--skip-validation)
SKIP_VALIDATION=true
shift
;;
--verbose)
VERBOSE=true
shift
;;
-h|--help)
show_help
exit 0
;;
*)
print_error "Unknown option: $1"
show_help
exit 1
;;
esac
done
}
# ============================================================================
# Main Entry Point
# ============================================================================
main() {
parse_args "$@"
print_header "TypeDialog Installation Suite"
# Interactive mode
if [[ "$INTERACTIVE" == "true" ]]; then
prompt_for_components
fi
# Detect platform
local os=$(detect_os) || exit 1
local arch=$(detect_arch) || exit 1
print_info "Platform: $os/$arch"
# Determine version
if [[ -z "$TYPEDIALOG_VERSION" ]]; then
TYPEDIALOG_VERSION=$(extract_version_from_ncl "typedialog")
if [[ -z "$TYPEDIALOG_VERSION" ]]; then
print_error "Could not determine TypeDialog version"
print_info "Please specify with: --version X.Y.Z"
exit 1
fi
fi
print_info "Version: $TYPEDIALOG_VERSION"
# Determine install directory
if [[ -z "$INSTALL_DIR" ]]; then
INSTALL_DIR="${TYPEDIALOG_INSTALL_DIR:-$HOME/.local/bin}"
fi
print_info "Install directory: $INSTALL_DIR"
# Display what will be installed
print_info "Components to install: $INSTALL_COMPONENTS"
echo ""
# Add install directory to PATH if not already there
if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
export PATH="${INSTALL_DIR}:$PATH"
debug "Added $INSTALL_DIR to PATH"
fi
# Ensure install directory exists
ensure_install_dir "$INSTALL_DIR" || exit 1
# Install selected components
install_components "$INSTALL_COMPONENTS" "$TYPEDIALOG_VERSION" "$INSTALL_DIR" "$os" "$arch" || true
# Validate installation
if [[ "$SKIP_VALIDATION" != "true" ]]; then
validate_all "$INSTALL_DIR" || {
print_warning "Validation failed, but TypeDialog may still be functional"
}
fi
echo ""
print_success "Installation complete!"
echo ""
print_info "Next steps:"
print_info " 1. Verify: typedialog --version && typedialog-tui --version && typedialog-prov-gen --version"
print_info " 2. Setup: provisioning/scripts/setup-platform-config.sh"
print_info " 3. Test forms: typedialog form <config.toml>"
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 "$@"