#!/bin/bash set -euo pipefail # InternalAI Platform Installer # ============================================================================ # Configuration # ============================================================================ PLATFORM_NAME="InternalAI Platform" DEFAULT_INSTALL_DIR="$HOME/internalai" REPO_URL="https://gitea.app.monadical.io/monadical/internalai.git" REPO_SSH="git@gitea.app.monadical.io:monadical/internalai.git" CLI_INSTALL_DIR="/usr/local/bin" CLI_NAME="internalai" VERSION="1.0.0" # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' BOLD='\033[1m' NC='\033[0m' # ============================================================================ # Logging Functions # ============================================================================ log_info() { echo -e "${BLUE}ℹ${NC} $1" } log_success() { echo -e "${GREEN}✓${NC} $1" } log_warning() { echo -e "${YELLOW}⚠${NC} $1" } log_error() { echo -e "${RED}✗${NC} $1" } log_header() { echo "" echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" echo -e "${CYAN} $1${NC}" echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" echo "" } print_banner() { clear echo -e "${BOLD}${CYAN}" cat << 'EOF' ╔═════════════════════════════════════════════════════════════════════════════════════════╗ ║ ║ ║ ██╗███╗ ██╗████████╗███████╗██████╗ ███╗ ██╗ █████╗ ██╗ █████╗ ██╗ ║ ║ ██║████╗ ██║╚══██╔══╝██╔════╝██╔══██╗████╗ ██║██╔══██╗██║ ██╔══██╗██║ ║ ║ ██║██╔██╗ ██║ ██║ █████╗ ██████╔╝██╔██╗ ██║███████║██║ ███████║██║ ║ ║ ██║██║╚██╗██║ ██║ ██╔══╝ ██╔══██╗██║╚██╗██║██╔══██║██║ ██╔══██║██║ ║ ║ ██║██║ ╚████║ ██║ ███████╗██║ ██║██║ ╚████║██║ ██║███████╗ ██║ ██║██║ ║ ║ ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝╚═╝ ║ ║ ║ ║ Platform Installer v${VERSION} ║ ╚═════════════════════════════════════════════════════════════════════════════════════════╝ EOF echo -e "${NC}" } show_welcome() { print_banner log_info "This installer will:" echo " • Check system dependencies" echo " • Install missing dependencies (just, uv)" echo " • Set up Gitea authentication" echo " • Clone the InternalAI platform repository" echo " • Install the 'internalai' command-line tool" echo "" } # ============================================================================ # Utility Functions # ============================================================================ prompt() { local varname=$1 local prompt_text=$2 local default_value=$3 local is_secret=${4:-false} if [ -n "$default_value" ]; then prompt_text="$prompt_text [${default_value}]" fi if [ "$is_secret" = true ]; then echo -ne "${YELLOW}${prompt_text}: ${NC}" >&2 value="" while IFS= read -r -s -n1 char < /dev/tty; do if [[ $char == $'\0' ]]; then break fi if [[ $char == $'\177' ]] || [[ $char == $'\b' ]]; then if [ ${#value} -gt 0 ]; then value="${value%?}" echo -ne "\b \b" >&2 fi else value+="$char" echo -n "*" >&2 fi done echo "" >&2 else echo -ne "${YELLOW}${prompt_text}: ${NC}" >&2 read value < /dev/tty fi if [ -z "$value" ] && [ -n "$default_value" ]; then value="$default_value" fi eval "$varname='$value'" } # ============================================================================ # Dependency Checking and Installation # ============================================================================ check_dependencies() { log_header "Checking Dependencies" local deps_ok=true # Check Git if command -v git &> /dev/null; then log_success "git installed ($(git --version | head -n1))" else log_error "git is not installed" deps_ok=false fi # Check Docker if command -v docker &> /dev/null; then log_success "docker installed ($(docker --version | head -n1))" else log_error "docker is not installed" deps_ok=false fi # Check Docker Compose if docker compose version &> /dev/null 2>&1; then log_success "docker compose installed ($(docker compose version | head -n1))" elif command -v docker-compose &> /dev/null; then log_success "docker-compose installed ($(docker-compose --version | head -n1))" else log_error "docker compose is not installed" deps_ok=false fi # Check bash version if [ "${BASH_VERSINFO[0]}" -lt 4 ]; then log_warning "Bash version ${BASH_VERSION} detected. Version 4+ recommended." else log_success "bash ${BASH_VERSION} is installed" fi # Check Just if command -v just &> /dev/null; then log_success "just installed ($(just --version | head -n1))" else log_warning "just is not installed (will attempt to install)" install_just fi # Check uv if command -v uv &> /dev/null; then log_success "uv installed ($(uv --version | head -n1))" else log_warning "uv is not installed (will attempt to install)" install_uv fi if [ "$deps_ok" = false ]; then log_error "Please install missing dependencies and try again" echo "" log_info "Installation guides:" log_info " • Git: https://git-scm.com/downloads" log_info " • Docker: https://docs.docker.com/get-docker/" exit 1 fi } install_just() { # Try package managers first if [[ "$OSTYPE" == "darwin"* ]]; then if command -v brew &> /dev/null; then log_info "Installing just via Homebrew..." if brew install just; then log_success "just installed" return 0 else log_warning "Homebrew installation failed, trying binary download..." fi fi elif [[ "$OSTYPE" == "linux-gnu"* ]]; then if command -v cargo &> /dev/null; then log_info "Installing just via cargo..." if cargo install just; then log_success "just installed" return 0 else log_warning "Cargo installation failed, trying binary download..." fi fi fi # Fall back to downloading pre-built binary log_info "Downloading pre-built just binary..." # Detect architecture local arch=$(uname -m) local os=$(uname -s | tr '[:upper:]' '[:lower:]') # Map architecture names case "$arch" in x86_64) arch="x86_64" ;; aarch64|arm64) arch="aarch64" ;; *) log_error "Unsupported architecture: $arch" log_info "Please install just manually: https://github.com/casey/just#installation" exit 1 ;; esac # Map OS names case "$os" in darwin) os="apple-darwin" ;; linux) os="unknown-linux-musl" ;; *) log_error "Unsupported OS: $os" log_info "Please install just manually: https://github.com/casey/just#installation" exit 1 ;; esac # Get latest version from GitHub API log_info "Fetching latest version..." local latest_version=$(curl -fsSL https://api.github.com/repos/casey/just/releases/latest | grep '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/') if [ -z "$latest_version" ]; then log_error "Failed to fetch latest version" log_info "Please install just manually: https://github.com/casey/just#installation" exit 1 fi local binary_name="just-${latest_version}-${arch}-${os}" local download_url="https://github.com/casey/just/releases/download/${latest_version}/${binary_name}.tar.gz" local install_dir="$HOME/.local/bin" # Create install directory mkdir -p "$install_dir" # Download and extract local tmp_dir=$(mktemp -d) trap "rm -rf $tmp_dir" EXIT log_info "Downloading from: $download_url" if ! curl -fsSL "$download_url" -o "$tmp_dir/just.tar.gz"; then log_error "Failed to download just binary" log_info "Please install just manually: https://github.com/casey/just#installation" exit 1 fi log_info "Extracting to $install_dir..." if ! tar -xzf "$tmp_dir/just.tar.gz" -C "$tmp_dir"; then log_error "Failed to extract just binary" exit 1 fi # Install binary if ! mv "$tmp_dir/just" "$install_dir/just"; then log_error "Failed to install just to $install_dir" exit 1 fi chmod +x "$install_dir/just" # Add to PATH if not already there if [[ ":$PATH:" != *":$install_dir:"* ]]; then log_info "Adding $install_dir to PATH..." export PATH="$install_dir:$PATH" # Add to shell profile local shell_profile="" if [ -n "$BASH_VERSION" ]; then shell_profile="$HOME/.bashrc" elif [ -n "$ZSH_VERSION" ]; then shell_profile="$HOME/.zshrc" fi if [ -n "$shell_profile" ]; then echo "export PATH=\"$install_dir:\$PATH\"" >> "$shell_profile" log_info "Added to $shell_profile (restart shell or run: source $shell_profile)" fi fi # Verify installation if command -v just &> /dev/null; then log_success "just installed successfully to $install_dir/just" else log_warning "just installed to $install_dir/just but not found in PATH" log_info "You may need to restart your terminal or run:" log_info " export PATH=\"$install_dir:\$PATH\"" fi } install_uv() { log_info "Installing uv (Python package manager)..." if curl -LsSf https://astral.sh/uv/install.sh | sh; then # Add uv to PATH for current session export PATH="$HOME/.local/bin:$PATH" if command -v uv &> /dev/null; then log_success "uv installed successfully ($(uv --version | head -n1))" else log_error "uv installation succeeded but not found in PATH" log_info "You may need to restart your terminal or run:" log_info " export PATH=\"\$HOME/.local/bin:\$PATH\"" exit 1 fi else log_error "Failed to install uv" log_info "Please install manually: curl -LsSf https://astral.sh/uv/install.sh | sh" exit 1 fi } # ============================================================================ # Gitea Authentication # ============================================================================ AUTH_TYPE="" GITEA_TOKEN="" setup_gitea_auth() { log_header "Gitea Authentication" log_info "The InternalAI platform repository is private and requires authentication." echo "" log_info "Choose authentication method:" echo " 1) Personal Access Token (recommended)" echo " 2) SSH Key (if already configured)" echo "" prompt AUTH_CHOICE "Select authentication method" "1" case "$AUTH_CHOICE" in 1) AUTH_TYPE="token" echo "" log_info "To create a personal access token:" log_info " 1. Go to: https://gitea.app.monadical.io/user/settings/applications" log_info " 2. Click 'Generate New Token'" log_info " 3. Give it a name (e.g., 'InternalAI Setup')" log_info " 4. Give it Read repository scope" log_info " 5. Click 'Generate Token' and copy it" echo "" prompt GITEA_TOKEN "Enter your Gitea Personal Access Token" "" true if [ -z "$GITEA_TOKEN" ]; then log_error "Token is required" exit 1 fi ;; 2) AUTH_TYPE="ssh" log_info "Using SSH authentication" log_info "Ensure your SSH key is added to Gitea:" log_info " https://gitea.app.monadical.io/user/settings/keys" # Test SSH connection echo "" log_info "Testing SSH connection..." if ssh -T git@gitea.app.monadical.io 2>&1 | grep -q "Hi there"; then log_success "SSH connection successful" else log_warning "Could not verify SSH connection, but will try to continue..." fi ;; *) log_error "Invalid choice" exit 1 ;; esac } prepare_git_url() { if [ "$AUTH_TYPE" = "token" ] && [ -n "$GITEA_TOKEN" ]; then echo "$REPO_URL" | sed "s|https://gitea.app.monadical.io/|https://${GITEA_TOKEN}@gitea.app.monadical.io/|" elif [ "$AUTH_TYPE" = "ssh" ]; then echo "$REPO_SSH" else echo "$REPO_URL" fi } # ============================================================================ # Git Credential Caching # ============================================================================ configure_git_credentials() { log_header "Configuring Git Credentials" if [ "$AUTH_TYPE" = "ssh" ]; then log_info "Using SSH - no credential caching needed" return fi # Detect OS and configure appropriate credential helper if [[ "$OSTYPE" == "darwin"* ]]; then # macOS - use keychain log_info "Configuring git to use macOS Keychain..." git config --global credential.helper osxkeychain log_success "Git credentials will be stored in macOS Keychain" elif [[ "$OSTYPE" == "linux-gnu"* ]]; then # Linux - try to use libsecret, fall back to cache if command -v git-credential-libsecret &> /dev/null; then log_info "Configuring git to use libsecret..." git config --global credential.helper libsecret log_success "Git credentials will be stored in system keyring" else log_info "Configuring git credential cache (1 hour)..." git config --global credential.helper 'cache --timeout=3600' log_success "Git credentials will be cached in memory for 1 hour" log_info "For persistent storage, install: libsecret" fi else # Unknown OS - use cache log_info "Configuring git credential cache (1 hour)..." git config --global credential.helper 'cache --timeout=3600' log_success "Git credentials will be cached in memory for 1 hour" fi # Store credentials by triggering a git operation if [ "$AUTH_TYPE" = "token" ] && [ -n "$INSTALL_DIR" ] && [ -d "$INSTALL_DIR/.git" ]; then log_info "Caching credentials..." cd "$INSTALL_DIR" # Trigger credential storage by doing a fetch (which uses credentials) git fetch --dry-run 2>&1 | grep -v "password\|token" > /dev/null || true log_success "Credentials cached successfully" fi } # ============================================================================ # Repository Cloning # ============================================================================ clone_repository() { log_header "Cloning Repository" prompt INSTALL_DIR "Installation directory" "$DEFAULT_INSTALL_DIR" if [ -d "$INSTALL_DIR" ]; then if [ -d "$INSTALL_DIR/.git" ]; then log_warning "Directory already exists and appears to be a git repository" prompt UPDATE_CHOICE "Update existing repository? (y/n)" "y" if [[ "$UPDATE_CHOICE" =~ ^[Yy] ]]; then log_info "Pulling latest changes..." cd "$INSTALL_DIR" git pull log_success "Repository updated" return else log_info "Using existing repository" return fi else log_error "Directory exists but is not a git repository" log_info "Please remove or rename: $INSTALL_DIR" exit 1 fi fi local git_url=$(prepare_git_url) log_info "Cloning repository to: $INSTALL_DIR" log_info "This may take a few minutes..." if git clone "$git_url" "$INSTALL_DIR" 2>&1 | grep -v "password\|token"; then log_success "Repository cloned successfully" else log_error "Failed to clone repository" log_info "Please check your credentials and try again" exit 1 fi } # ============================================================================ # CLI Installation # ============================================================================ install_cli() { log_header "Installing CLI Tool" local cli_script="$INSTALL_DIR/scripts/$CLI_NAME" if [ ! -f "$cli_script" ]; then log_error "CLI script not found at: $cli_script" exit 1 fi log_info "Installing $CLI_NAME to $CLI_INSTALL_DIR..." if [ -w "$CLI_INSTALL_DIR" ]; then cp "$cli_script" "$CLI_INSTALL_DIR/$CLI_NAME" chmod +x "$CLI_INSTALL_DIR/$CLI_NAME" else log_warning "Need sudo permissions to write to $CLI_INSTALL_DIR" sudo cp "$cli_script" "$CLI_INSTALL_DIR/$CLI_NAME" sudo chmod +x "$CLI_INSTALL_DIR/$CLI_NAME" fi if command -v "$CLI_NAME" &> /dev/null; then log_success "$CLI_NAME installed successfully!" else log_warning "$CLI_NAME installed but not found in PATH" log_info "You may need to restart your terminal or run:" log_info " export PATH=\"$CLI_INSTALL_DIR:\$PATH\"" fi } # ============================================================================ # Post-Installation # ============================================================================ show_next_steps() { log_header "Installation Complete!" echo -e "${GREEN}${BOLD}The InternalAI platform has been set up successfully!${NC}" echo "" echo -e "${BOLD}Platform Location:${NC} ${CYAN}$INSTALL_DIR${NC}" echo "" echo -e "${BOLD}Next Steps:${NC}" echo "" echo " 1. Configure and start the platform:" echo -e " ${GREEN}$CLI_NAME install${NC}" echo "" echo " 2. View available commands:" echo -e " ${GREEN}$CLI_NAME help${NC}" echo "" echo " 3. Check platform status:" echo -e " ${GREEN}$CLI_NAME status${NC}" echo "" echo -e "${BOLD}Documentation:${NC}" echo " • Platform docs: $INSTALL_DIR/docs/" echo " • README: $INSTALL_DIR/README.md" echo "" } # ============================================================================ # Main Installation Flow # ============================================================================ main() { show_welcome check_dependencies setup_gitea_auth clone_repository configure_git_credentials install_cli show_next_steps echo -e "${GREEN}${BOLD}Setup completed successfully!${NC}" echo "" } main "$@"