Last active
November 5, 2025 20:25
-
-
Save josezy/43d920fe2b30c5ccb0ad5902f50cfe9b to your computer and use it in GitHub Desktop.
Monadical Platform Data Ingestor Setup Script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| set -euo pipefail | |
| # Monadical Platform Setup Script | |
| # Usage: bash <(curl -fsSL https://example.com/setup.sh) | |
| # ============================================================================ | |
| # Configuration | |
| # ============================================================================ | |
| PLATFORM_NAME="Monadical Platform" | |
| # Determine platform root - avoid double nesting | |
| if [[ "$(basename $(pwd))" == "platform-workspace" ]]; then | |
| PLATFORM_ROOT="$(pwd)" | |
| else | |
| PLATFORM_ROOT="$(pwd)/platform-workspace" | |
| fi | |
| CACHE_FILE="$PLATFORM_ROOT/.credentials.cache" | |
| DOCKER_NETWORK="monadical-platform" | |
| # Colors | |
| RED='\033[0;31m' | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[1;33m' | |
| BLUE='\033[0;34m' | |
| CYAN='\033[0;36m' | |
| NC='\033[0m' | |
| # ============================================================================ | |
| # Service Registry (Hardcoded) | |
| # ============================================================================ | |
| # Format: "service_id|repo_url|branch|port|description|mandatory" | |
| AVAILABLE_SERVICES=( | |
| "contactdb|https://github.com/Monadical-SAS/contactdb.git|main|42173|Unified contact management|true" | |
| "dataindex|https://github.com/Monadical-SAS/dataindex.git|main|42180|Data aggregation from multiple sources|true" | |
| "babelfish|https://github.com/Monadical-SAS/babelfish.git|authless-ux|8880|Universal communications bridge (Matrix homeserver)|false" | |
| # "crm-reply|https://github.com/Monadical-SAS/crm-reply.git|main|3001|AI-powered CRM reply assistant|false" | |
| "meeting-prep|https://github.com/Monadical-SAS/meeting-prep.git|pangolin-deployment|8081|Meeting preparation assistant|false" | |
| ) | |
| # DataIndex ingestors | |
| DATAINDEX_INGESTORS=( | |
| "calendar|ICS Calendar (Fastmail Calendar, iCal)|DATAINDEX_PERSONAL" | |
| "zulip|Zulip Chat|DATAINDEX_ZULIP" | |
| "email|Email (mbsync/notmuch)|DATAINDEX_EMAIL" | |
| "reflector|Reflector API|DATAINDEX_REFLECTOR" | |
| ) | |
| # ============================================================================ | |
| # 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 "" | |
| } | |
| # ============================================================================ | |
| # 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 | |
| read -s -p "$(echo -e ${YELLOW}${prompt_text}: ${NC})" value | |
| echo "" | |
| else | |
| read -p "$(echo -e ${YELLOW}${prompt_text}: ${NC})" value | |
| fi | |
| if [ -z "$value" ] && [ -n "$default_value" ]; then | |
| value="$default_value" | |
| fi | |
| eval "$varname='$value'" | |
| } | |
| generate_password() { | |
| openssl rand -hex 32 | |
| } | |
| # ============================================================================ | |
| # Credential Cache (Optional Encryption) | |
| # ============================================================================ | |
| CACHE_PASSWORD="" | |
| USE_ENCRYPTION=false | |
| IGNORE_CACHE=false | |
| init_cache() { | |
| mkdir -p "$(dirname "$CACHE_FILE")" | |
| # If --no-cache flag is set, clear the cache | |
| if [ "$IGNORE_CACHE" = true ]; then | |
| log_warning "Ignoring cache (--no-cache flag set)" | |
| rm -f "$CACHE_FILE" | |
| touch "$CACHE_FILE" | |
| return | |
| fi | |
| if [ -f "$CACHE_FILE" ]; then | |
| # Check if encrypted | |
| if head -n1 "$CACHE_FILE" | grep -q "^Salted__"; then | |
| USE_ENCRYPTION=true | |
| prompt CACHE_PASSWORD "Enter cache password" "" true | |
| fi | |
| else | |
| prompt USE_ENCRYPTION_INPUT "Encrypt credential cache? (recommended)" "yes" | |
| if [ "$USE_ENCRYPTION_INPUT" = "yes" ] || [ "$USE_ENCRYPTION_INPUT" = "y" ]; then | |
| USE_ENCRYPTION=true | |
| prompt CACHE_PASSWORD "Create cache password" "" true | |
| fi | |
| touch "$CACHE_FILE" | |
| fi | |
| } | |
| save_to_cache() { | |
| local key=$1 | |
| local value=$2 | |
| if [ "$USE_ENCRYPTION" = true ]; then | |
| # Decrypt, append, re-encrypt | |
| local temp_file=$(mktemp) | |
| if [ -s "$CACHE_FILE" ]; then | |
| openssl enc -d -aes-256-cbc -pbkdf2 -pass pass:"$CACHE_PASSWORD" -in "$CACHE_FILE" 2>/dev/null > "$temp_file" || true | |
| fi | |
| grep -v "^$key=" "$temp_file" > "${temp_file}.tmp" 2>/dev/null || true | |
| echo "$key=$value" >> "${temp_file}.tmp" | |
| openssl enc -aes-256-cbc -salt -pbkdf2 -pass pass:"$CACHE_PASSWORD" -in "${temp_file}.tmp" -out "$CACHE_FILE" | |
| rm -f "$temp_file" "${temp_file}.tmp" | |
| else | |
| # Plain text | |
| grep -v "^$key=" "$CACHE_FILE" > "${CACHE_FILE}.tmp" 2>/dev/null || true | |
| echo "$key=$value" >> "${CACHE_FILE}.tmp" | |
| mv "${CACHE_FILE}.tmp" "$CACHE_FILE" | |
| fi | |
| } | |
| load_from_cache() { | |
| local key=$1 | |
| if [ ! -f "$CACHE_FILE" ]; then | |
| echo "" | |
| return | |
| fi | |
| if [ "$USE_ENCRYPTION" = true ]; then | |
| openssl enc -d -aes-256-cbc -pbkdf2 -pass pass:"$CACHE_PASSWORD" -in "$CACHE_FILE" 2>/dev/null | grep "^$key=" | cut -d= -f2- | tail -1 || echo "" | |
| else | |
| grep "^$key=" "$CACHE_FILE" | cut -d= -f2- | tail -1 || echo "" | |
| fi | |
| } | |
| # Try to retrieve existing password from .env file | |
| get_existing_password_from_env() { | |
| local env_file=$1 | |
| local var_name=$2 | |
| if [ ! -f "$env_file" ]; then | |
| echo "" | |
| return | |
| fi | |
| # Extract value from .env file | |
| grep "^${var_name}=" "$env_file" | cut -d= -f2- | tail -1 || echo "" | |
| } | |
| prompt_or_cache() { | |
| local var_name=$1 | |
| local prompt_text=$2 | |
| local default_value=$3 | |
| local is_secret=${4:-false} | |
| local env_file=${5:-} | |
| # Check cache first | |
| local cached_value=$(load_from_cache "$var_name") | |
| if [ -n "$cached_value" ]; then | |
| log_info "Using cached value for $var_name" | |
| eval "$var_name='$cached_value'" | |
| return | |
| fi | |
| # If cache is empty but an .env file exists, try to get password from there | |
| if [ -n "$env_file" ] && [ -f "$env_file" ]; then | |
| local existing_value=$(get_existing_password_from_env "$env_file" "$var_name") | |
| if [ -n "$existing_value" ]; then | |
| log_info "Using existing password from $env_file for $var_name" | |
| eval "$var_name='$existing_value'" | |
| save_to_cache "$var_name" "$existing_value" | |
| return | |
| fi | |
| fi | |
| # Handle auto-generation | |
| if [ "$default_value" = "auto" ]; then | |
| local generated=$(generate_password) | |
| log_info "Auto-generated $var_name" | |
| eval "$var_name='$generated'" | |
| save_to_cache "$var_name" "$generated" | |
| return | |
| fi | |
| # Prompt user | |
| prompt "$var_name" "$prompt_text" "$default_value" "$is_secret" | |
| local value="${!var_name}" | |
| if [ -n "$value" ]; then | |
| save_to_cache "$var_name" "$value" | |
| fi | |
| } | |
| # ============================================================================ | |
| # Docker Compose Wrapper | |
| # ============================================================================ | |
| # Wrapper function to use either docker-compose (v1) or docker compose (v2) | |
| docker_compose() { | |
| if command -v docker-compose &> /dev/null; then | |
| docker-compose "$@" | |
| else | |
| docker compose "$@" | |
| fi | |
| } | |
| # ============================================================================ | |
| # Dependency Management | |
| # ============================================================================ | |
| check_dependencies() { | |
| log_header "Checking Dependencies" | |
| # Detect OS | |
| local OS_TYPE="" | |
| if [[ "$OSTYPE" == "darwin"* ]]; then | |
| OS_TYPE="macos" | |
| elif [[ "$OSTYPE" == "linux-gnu"* ]]; then | |
| OS_TYPE="linux" | |
| else | |
| OS_TYPE="unknown" | |
| fi | |
| # Check/Install Git | |
| if ! command -v git &> /dev/null; then | |
| log_info "Git not found, installing..." | |
| case "$OS_TYPE" in | |
| macos) | |
| if command -v brew &> /dev/null; then | |
| brew install git | |
| else | |
| log_error "Homebrew not found. Please install Homebrew first: https://brew.sh" | |
| exit 1 | |
| fi | |
| ;; | |
| linux) | |
| if command -v apt-get &> /dev/null; then | |
| sudo apt-get update && sudo apt-get install -y git | |
| elif command -v yum &> /dev/null; then | |
| sudo yum install -y git | |
| elif command -v dnf &> /dev/null; then | |
| sudo dnf install -y git | |
| else | |
| log_error "Package manager not found. Please install git manually." | |
| exit 1 | |
| fi | |
| ;; | |
| *) | |
| log_error "Unsupported OS. Please install git manually." | |
| exit 1 | |
| ;; | |
| esac | |
| log_success "Git installed: $(git --version)" | |
| else | |
| log_success "Git installed: $(git --version)" | |
| fi | |
| # Check/Install Docker | |
| if ! command -v docker &> /dev/null; then | |
| log_info "Docker not found, installing..." | |
| case "$OS_TYPE" in | |
| macos) | |
| if command -v brew &> /dev/null; then | |
| log_info "Installing Docker Desktop via Homebrew..." | |
| brew install --cask docker | |
| log_warning "Docker Desktop installed. Please start Docker Desktop from Applications and then re-run this script." | |
| exit 0 | |
| else | |
| log_error "Homebrew not found. Please install Docker Desktop manually: https://www.docker.com/products/docker-desktop" | |
| exit 1 | |
| fi | |
| ;; | |
| linux) | |
| log_info "Installing Docker Engine..." | |
| if command -v apt-get &> /dev/null; then | |
| # Ubuntu/Debian | |
| sudo apt-get update | |
| sudo apt-get install -y ca-certificates curl gnupg lsb-release | |
| sudo mkdir -p /etc/apt/keyrings | |
| curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg | |
| echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null | |
| sudo apt-get update | |
| sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin | |
| sudo systemctl start docker | |
| sudo systemctl enable docker | |
| # Add user to docker group | |
| sudo usermod -aG docker $USER | |
| log_warning "Docker installed. Please log out and back in for group changes to take effect, then re-run this script." | |
| exit 0 | |
| elif command -v yum &> /dev/null; then | |
| # CentOS/RHEL | |
| sudo yum install -y yum-utils | |
| sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo | |
| sudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin | |
| sudo systemctl start docker | |
| sudo systemctl enable docker | |
| sudo usermod -aG docker $USER | |
| log_warning "Docker installed. Please log out and back in for group changes to take effect, then re-run this script." | |
| exit 0 | |
| else | |
| log_error "Package manager not found. Please install Docker manually: https://docs.docker.com/engine/install/" | |
| exit 1 | |
| fi | |
| ;; | |
| *) | |
| log_error "Unsupported OS. Please install Docker manually: https://docs.docker.com/get-docker/" | |
| exit 1 | |
| ;; | |
| esac | |
| else | |
| log_success "Docker installed: $(docker --version)" | |
| # Check if Docker daemon is running | |
| if ! docker ps &> /dev/null; then | |
| log_error "Docker is installed but not running. Please start Docker and try again." | |
| if [[ "$OS_TYPE" == "macos" ]]; then | |
| log_info "Start Docker Desktop from Applications" | |
| elif [[ "$OS_TYPE" == "linux" ]]; then | |
| log_info "Run: sudo systemctl start docker" | |
| fi | |
| exit 1 | |
| fi | |
| fi | |
| # Check/Install Docker Compose | |
| if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null 2>&1; then | |
| log_info "Docker Compose not found, installing..." | |
| case "$OS_TYPE" in | |
| macos) | |
| if command -v brew &> /dev/null; then | |
| brew install docker-compose | |
| else | |
| log_error "Homebrew not found. Docker Compose should come with Docker Desktop." | |
| exit 1 | |
| fi | |
| ;; | |
| linux) | |
| # Install docker-compose standalone | |
| log_info "Installing Docker Compose standalone..." | |
| sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose | |
| sudo chmod +x /usr/local/bin/docker-compose | |
| ;; | |
| *) | |
| log_error "Unsupported OS. Please install Docker Compose manually." | |
| exit 1 | |
| ;; | |
| esac | |
| log_success "Docker Compose installed" | |
| else | |
| log_success "Docker Compose installed" | |
| fi | |
| # Check/Install make | |
| if ! command -v make &> /dev/null; then | |
| log_info "Installing make..." | |
| case "$OS_TYPE" in | |
| macos) | |
| # make comes with Xcode Command Line Tools | |
| if ! xcode-select -p &> /dev/null; then | |
| log_info "Installing Xcode Command Line Tools (includes make)..." | |
| xcode-select --install | |
| log_warning "Please complete Xcode Command Line Tools installation and re-run this script" | |
| exit 0 | |
| fi | |
| ;; | |
| linux) | |
| if command -v apt-get &> /dev/null; then | |
| sudo apt-get update && sudo apt-get install -y build-essential | |
| elif command -v yum &> /dev/null; then | |
| sudo yum groupinstall -y "Development Tools" | |
| elif command -v dnf &> /dev/null; then | |
| sudo dnf groupinstall -y "Development Tools" | |
| else | |
| log_error "Package manager not found. Please install make manually." | |
| exit 1 | |
| fi | |
| ;; | |
| *) | |
| log_error "Unsupported OS. Please install make manually." | |
| exit 1 | |
| ;; | |
| esac | |
| log_success "make installed" | |
| else | |
| log_success "make installed: $(make --version | head -n1)" | |
| fi | |
| log_success "All dependencies installed" | |
| } | |
| # ============================================================================ | |
| # GitHub Authentication | |
| # ============================================================================ | |
| setup_github_auth() { | |
| log_header "GitHub Authentication" | |
| # Check cache for auth type | |
| local cached_auth_type=$(load_from_cache "AUTH_TYPE") | |
| if [ -n "$cached_auth_type" ]; then | |
| AUTH_TYPE="$cached_auth_type" | |
| log_info "Using cached authentication method: $AUTH_TYPE" | |
| else | |
| prompt AUTH_TYPE "Authentication method (ssh/token/none)" "ssh" | |
| save_to_cache "AUTH_TYPE" "$AUTH_TYPE" | |
| fi | |
| case "$AUTH_TYPE" in | |
| token) | |
| prompt_or_cache "GITHUB_TOKEN" "GitHub Personal Access Token" "" true | |
| if [ -z "$GITHUB_TOKEN" ]; then | |
| log_error "GitHub token is required" | |
| log_info "Create token at: https://github.com/settings/tokens" | |
| exit 1 | |
| fi | |
| log_success "GitHub token configured" | |
| ;; | |
| ssh) | |
| log_info "Using SSH authentication (ensure keys are configured)" | |
| GITHUB_TOKEN="" | |
| ;; | |
| none) | |
| log_info "No authentication (public repos only)" | |
| GITHUB_TOKEN="" | |
| ;; | |
| *) | |
| log_error "Invalid auth type: $AUTH_TYPE" | |
| exit 1 | |
| ;; | |
| esac | |
| } | |
| prepare_git_url() { | |
| local git_repo=$1 | |
| if [ "$AUTH_TYPE" = "token" ] && [ -n "$GITHUB_TOKEN" ]; then | |
| if [[ "$git_repo" =~ ^https://github.com/ ]]; then | |
| echo "$git_repo" | sed "s|https://github.com/|https://${GITHUB_TOKEN}@github.com/|" | |
| return | |
| fi | |
| fi | |
| echo "$git_repo" | |
| } | |
| # ============================================================================ | |
| # Service Selection | |
| # ============================================================================ | |
| SELECTED_SERVICES=() | |
| select_services() { | |
| log_header "Service Selection" | |
| # Always add mandatory services first | |
| for service_def in "${AVAILABLE_SERVICES[@]}"; do | |
| IFS='|' read -r id repo branch port desc mandatory <<< "$service_def" | |
| if [ "$mandatory" = "true" ]; then | |
| SELECTED_SERVICES+=("$id") | |
| fi | |
| done | |
| # Check cache for optional services | |
| local cached_services=$(load_from_cache "SELECTED_OPTIONAL_SERVICES") | |
| if [ -n "$cached_services" ]; then | |
| if [ "$cached_services" != "none" ]; then | |
| IFS=',' read -ra optional_services <<< "$cached_services" | |
| SELECTED_SERVICES+=("${optional_services[@]}") | |
| fi | |
| log_info "Using cached service selection: ${SELECTED_SERVICES[*]}" | |
| return | |
| fi | |
| echo "Mandatory services: contactdb, dataindex" | |
| echo "" | |
| echo "Optional services:" | |
| echo "" | |
| local index=1 | |
| local optional_services=() | |
| for service_def in "${AVAILABLE_SERVICES[@]}"; do | |
| IFS='|' read -r id repo branch port desc mandatory <<< "$service_def" | |
| if [ "$mandatory" = "false" ]; then | |
| echo -e " ${CYAN}${index}.${NC} ${desc} (${id})" | |
| optional_services+=("$service_def") | |
| ((index++)) | |
| fi | |
| done | |
| echo "" | |
| prompt SERVICES_INPUT "Enter optional service numbers (comma-separated) or 'none'" "none" | |
| local selected_optional=() | |
| if [ "$SERVICES_INPUT" != "none" ]; then | |
| IFS=',' read -ra INDICES <<< "$SERVICES_INPUT" | |
| for idx in "${INDICES[@]}"; do | |
| idx=$(echo "$idx" | xargs) # trim | |
| service_def="${optional_services[$((idx-1))]}" | |
| IFS='|' read -r id repo branch port desc mandatory <<< "$service_def" | |
| SELECTED_SERVICES+=("$id") | |
| selected_optional+=("$id") | |
| done | |
| fi | |
| # Save optional services to cache | |
| if [ ${#selected_optional[@]} -gt 0 ]; then | |
| local optional_str=$(IFS=','; echo "${selected_optional[*]}") | |
| save_to_cache "SELECTED_OPTIONAL_SERVICES" "$optional_str" | |
| else | |
| save_to_cache "SELECTED_OPTIONAL_SERVICES" "none" | |
| fi | |
| log_success "Selected: ${SELECTED_SERVICES[*]}" | |
| } | |
| # ============================================================================ | |
| # DataIndex Ingestor Selection | |
| # ============================================================================ | |
| SELECTED_INGESTORS=() | |
| select_ingestors() { | |
| log_header "DataIndex Ingestors" | |
| # Check cache first - use it automatically if it exists | |
| local cached_ingestors=$(load_from_cache "SELECTED_INGESTORS") | |
| if [ -n "$cached_ingestors" ]; then | |
| if [ "$cached_ingestors" = "none" ]; then | |
| log_info "Using cached selection: No ingestors" | |
| return | |
| fi | |
| IFS=',' read -ra SELECTED_INGESTORS <<< "$cached_ingestors" | |
| log_info "Using cached ingestor selection: ${#SELECTED_INGESTORS[@]} ingestors" | |
| return | |
| fi | |
| echo "Available ingestors:" | |
| echo "" | |
| local index=1 | |
| for ingestor_def in "${DATAINDEX_INGESTORS[@]}"; do | |
| IFS='|' read -r id name prefix <<< "$ingestor_def" | |
| echo -e " ${CYAN}${index}.${NC} ${name}" | |
| ((index++)) | |
| done | |
| echo "" | |
| prompt INGESTORS_INPUT "Enter ingestor numbers (comma-separated) or 'none'" "none" | |
| if [ "$INGESTORS_INPUT" = "none" ]; then | |
| log_info "No ingestors selected" | |
| save_to_cache "SELECTED_INGESTORS" "none" | |
| return | |
| fi | |
| IFS=',' read -ra INDICES <<< "$INGESTORS_INPUT" | |
| for idx in "${INDICES[@]}"; do | |
| idx=$(echo "$idx" | xargs) | |
| ingestor_def="${DATAINDEX_INGESTORS[$((idx-1))]}" | |
| IFS='|' read -r id name prefix <<< "$ingestor_def" | |
| SELECTED_INGESTORS+=("$id|$name|$prefix") | |
| done | |
| # Save to cache | |
| local ingestors_str=$(IFS=','; echo "${SELECTED_INGESTORS[*]}") | |
| save_to_cache "SELECTED_INGESTORS" "$ingestors_str" | |
| log_success "Selected ingestors: ${#SELECTED_INGESTORS[@]}" | |
| } | |
| # ============================================================================ | |
| # Enrichment API Configuration (Global) | |
| # ============================================================================ | |
| setup_enrichment_apis() { | |
| log_header "Contact Enrichment APIs (Optional)" | |
| log_info "These API keys will be used across ContactDB, CRM Reply, and Meeting Prep" | |
| log_info "Press Enter to skip if you don't have these keys" | |
| echo "" | |
| prompt_or_cache "APOLLO_API_KEY" "Apollo API key (optional)" "" true | |
| prompt_or_cache "HUNTER_API_KEY" "Hunter API key (optional)" "" true | |
| if [ -n "$APOLLO_API_KEY" ] || [ -n "$HUNTER_API_KEY" ]; then | |
| log_success "Enrichment API keys configured" | |
| else | |
| log_info "Skipping enrichment API keys - services will work without them" | |
| fi | |
| } | |
| # ============================================================================ | |
| # Service Configuration | |
| # ============================================================================ | |
| configure_contactdb() { | |
| log_header "Configuring ContactDB" | |
| # Use hardcoded password from docker-compose.yml | |
| local db_user="contactdb" | |
| local db_password="contactdb" | |
| local db_name="contactdb" | |
| local db_host="postgres" # Docker service name | |
| local db_port="5432" | |
| local db_test_host="postgres_test" | |
| # Generate backend/.env for application settings (matches docker-compose.yml) | |
| mkdir -p "$PLATFORM_ROOT/contactdb/backend" | |
| cat > "$PLATFORM_ROOT/contactdb/backend/.env" <<EOF | |
| # ContactDB Application Configuration | |
| # Database connection (matches docker-compose.yml hardcoded values) | |
| DATABASE_URL=postgresql://${db_user}:${db_password}@${db_host}:${db_port}/${db_name} | |
| DATABASE_URL_TEST=postgresql://${db_user}:${db_password}@${db_test_host}:${db_port}/${db_name}_test | |
| # Optional: Override defaults | |
| # APP_NAME=ContactDB | |
| # DEBUG=false | |
| # LOG_SQL=false | |
| # Contact enrichment APIs (optional) | |
| EOF | |
| # Add Apollo API key if provided | |
| if [ -n "$APOLLO_API_KEY" ]; then | |
| echo "APOLLO_API_KEY=$APOLLO_API_KEY" >> "$PLATFORM_ROOT/contactdb/backend/.env" | |
| else | |
| echo "# APOLLO_API_KEY=" >> "$PLATFORM_ROOT/contactdb/backend/.env" | |
| fi | |
| # Add Hunter API key if provided | |
| if [ -n "$HUNTER_API_KEY" ]; then | |
| echo "HUNTER_API_KEY=$HUNTER_API_KEY" >> "$PLATFORM_ROOT/contactdb/backend/.env" | |
| else | |
| echo "# HUNTER_API_KEY=" >> "$PLATFORM_ROOT/contactdb/backend/.env" | |
| fi | |
| log_success "ContactDB configured" | |
| } | |
| configure_dataindex() { | |
| log_header "Configuring DataIndex" | |
| # Always prompt for ingestors (unless cached) | |
| select_ingestors | |
| # Auto-add babelfish ingestor if babelfish service is selected | |
| local has_babelfish=false | |
| for service_id in "${SELECTED_SERVICES[@]}"; do | |
| if [ "$service_id" = "babelfish" ]; then | |
| # Check if babelfish ingestor is already in the list | |
| if [ ${#SELECTED_INGESTORS[@]} -gt 0 ]; then | |
| for ing in "${SELECTED_INGESTORS[@]}"; do | |
| if [[ "$ing" == babelfish* ]]; then | |
| has_babelfish=true | |
| break | |
| fi | |
| done | |
| fi | |
| if [ "$has_babelfish" = false ]; then | |
| SELECTED_INGESTORS+=("babelfish|Babelfish (auto-configured)|DATAINDEX_BABELFISH") | |
| log_info "Auto-added Babelfish ingestor for DataIndex" | |
| fi | |
| break | |
| fi | |
| done | |
| # Required variables - check existing .env file if cache is empty | |
| prompt_or_cache "DATAINDEX_POSTGRES_PASSWORD" "PostgreSQL password for DataIndex" "auto" true "$PLATFORM_ROOT/dataindex/.env" | |
| prompt_or_cache "CONTACTDB_URL_FRONTEND" "ContactDB Frontend URL" "http://localhost:42173" false "$PLATFORM_ROOT/dataindex/.env" | |
| # Generate base .env | |
| cat > "$PLATFORM_ROOT/dataindex/.env" <<EOF | |
| # DataIndex Configuration | |
| CONTACTDB_URL=http://host.docker.internal:42800 | |
| CONTACTDB_URL_FRONTEND=$CONTACTDB_URL_FRONTEND | |
| REDIS_URL=redis://localhost:42170 | |
| DATABASE_URL=postgresql://dataindex:$DATAINDEX_POSTGRES_PASSWORD@localhost:42434/dataindex | |
| # Ingestors | |
| EOF | |
| # Configure each selected ingestor | |
| if [ ${#SELECTED_INGESTORS[@]} -gt 0 ]; then | |
| for ingestor_def in "${SELECTED_INGESTORS[@]}"; do | |
| IFS='|' read -r id name prefix <<< "$ingestor_def" | |
| case "$id" in | |
| calendar) | |
| echo "" >> "$PLATFORM_ROOT/dataindex/.env" | |
| echo "# ICS Calendar Ingestor" >> "$PLATFORM_ROOT/dataindex/.env" | |
| prompt_or_cache "DATAINDEX_CALENDAR_URL" "ICS Calendar URL (e.g., Fastmail Calendar iCal)" "" | |
| if [ -n "$DATAINDEX_CALENDAR_URL" ]; then | |
| echo "${prefix}_TYPE=ics_calendar" >> "$PLATFORM_ROOT/dataindex/.env" | |
| echo "${prefix}_ICS_URL=$DATAINDEX_CALENDAR_URL" >> "$PLATFORM_ROOT/dataindex/.env" | |
| fi | |
| ;; | |
| zulip) | |
| echo "" >> "$PLATFORM_ROOT/dataindex/.env" | |
| echo "# Zulip Ingestor" >> "$PLATFORM_ROOT/dataindex/.env" | |
| prompt_or_cache "DATAINDEX_ZULIP_URL" "Zulip server URL" "" | |
| prompt_or_cache "DATAINDEX_ZULIP_EMAIL" "Zulip bot email" "" | |
| prompt_or_cache "DATAINDEX_ZULIP_API_KEY" "Zulip API key" "" true | |
| if [ -n "$DATAINDEX_ZULIP_URL" ]; then | |
| echo "${prefix}_TYPE=zulip" >> "$PLATFORM_ROOT/dataindex/.env" | |
| echo "${prefix}_ZULIP_URL=$DATAINDEX_ZULIP_URL" >> "$PLATFORM_ROOT/dataindex/.env" | |
| echo "${prefix}_ZULIP_EMAIL=$DATAINDEX_ZULIP_EMAIL" >> "$PLATFORM_ROOT/dataindex/.env" | |
| echo "${prefix}_ZULIP_API_KEY=$DATAINDEX_ZULIP_API_KEY" >> "$PLATFORM_ROOT/dataindex/.env" | |
| fi | |
| ;; | |
| email) | |
| echo "" >> "$PLATFORM_ROOT/dataindex/.env" | |
| echo "# Email Ingestor" >> "$PLATFORM_ROOT/dataindex/.env" | |
| prompt_or_cache "DATAINDEX_EMAIL_IMAP_HOST" "IMAP host (e.g., imap.fastmail.com)" "imap.fastmail.com" | |
| prompt_or_cache "DATAINDEX_EMAIL_IMAP_USER" "IMAP username/email" "" | |
| prompt_or_cache "DATAINDEX_EMAIL_IMAP_PASS" "IMAP password" "" true | |
| if [ -n "$DATAINDEX_EMAIL_IMAP_HOST" ]; then | |
| echo "${prefix}_TYPE=mbsync_email" >> "$PLATFORM_ROOT/dataindex/.env" | |
| echo "${prefix}_IMAP_HOST=$DATAINDEX_EMAIL_IMAP_HOST" >> "$PLATFORM_ROOT/dataindex/.env" | |
| echo "${prefix}_IMAP_USER=$DATAINDEX_EMAIL_IMAP_USER" >> "$PLATFORM_ROOT/dataindex/.env" | |
| echo "${prefix}_IMAP_PASS=$DATAINDEX_EMAIL_IMAP_PASS" >> "$PLATFORM_ROOT/dataindex/.env" | |
| fi | |
| ;; | |
| reflector) | |
| echo "" >> "$PLATFORM_ROOT/dataindex/.env" | |
| echo "# Reflector Ingestor" >> "$PLATFORM_ROOT/dataindex/.env" | |
| prompt_or_cache "DATAINDEX_REFLECTOR_API_KEY" "Reflector API key" "" true | |
| prompt_or_cache "DATAINDEX_REFLECTOR_API_URL" "Reflector API URL" "https://api-reflector.monadical.com" | |
| if [ -n "$DATAINDEX_REFLECTOR_API_KEY" ]; then | |
| echo "${prefix}_TYPE=reflector" >> "$PLATFORM_ROOT/dataindex/.env" | |
| echo "${prefix}_API_KEY=$DATAINDEX_REFLECTOR_API_KEY" >> "$PLATFORM_ROOT/dataindex/.env" | |
| echo "${prefix}_API_URL=$DATAINDEX_REFLECTOR_API_URL" >> "$PLATFORM_ROOT/dataindex/.env" | |
| fi | |
| ;; | |
| babelfish) | |
| echo "" >> "$PLATFORM_ROOT/dataindex/.env" | |
| echo "# Babelfish Ingestor (auto-configured)" >> "$PLATFORM_ROOT/dataindex/.env" | |
| # Auto-configured, no prompt needed | |
| local babelfish_url="http://host.docker.internal:8000" | |
| echo "${prefix}_TYPE=babelfish" >> "$PLATFORM_ROOT/dataindex/.env" | |
| echo "${prefix}_BASE_URL=$babelfish_url" >> "$PLATFORM_ROOT/dataindex/.env" | |
| log_info "Babelfish ingestor configured with URL: $babelfish_url" | |
| ;; | |
| esac | |
| done | |
| fi | |
| log_success "DataIndex configured" | |
| } | |
| # ============================================================================ | |
| # Repository Management | |
| # ============================================================================ | |
| clone_services() { | |
| log_header "Cloning Repositories" | |
| mkdir -p "$PLATFORM_ROOT" | |
| for service_id in "${SELECTED_SERVICES[@]}"; do | |
| # Find service definition | |
| for service_def in "${AVAILABLE_SERVICES[@]}"; do | |
| IFS='|' read -r id repo branch port desc mandatory <<< "$service_def" | |
| if [ "$id" = "$service_id" ]; then | |
| if [ -d "$PLATFORM_ROOT/$id/.git" ]; then | |
| log_info "$id already cloned, pulling latest changes..." | |
| cd "$PLATFORM_ROOT/$id" | |
| git pull 2>&1 | grep -v "password" || { | |
| log_warning "Failed to pull latest changes for $id (might be on a different branch or have local changes)" | |
| } | |
| cd "$PLATFORM_ROOT" | |
| log_success "Updated $id" | |
| else | |
| log_info "Cloning $id from $repo..." | |
| local auth_url=$(prepare_git_url "$repo") | |
| git clone -b "$branch" "$auth_url" "$PLATFORM_ROOT/$id" 2>&1 | grep -v "password" || { | |
| log_error "Failed to clone $id" | |
| exit 1 | |
| } | |
| log_success "Cloned $id" | |
| fi | |
| break | |
| fi | |
| done | |
| done | |
| } | |
| configure_babelfish() { | |
| log_header "Configuring Babelfish" | |
| # Generate passwords - check existing .env file if cache is empty | |
| prompt_or_cache "BABELFISH_POSTGRES_PASSWORD" "PostgreSQL password for Babelfish" "auto" true "$PLATFORM_ROOT/babelfish/.env" | |
| prompt_or_cache "BABELFISH_BACKUP_KEY" "Backup encryption key for Babelfish" "auto" true "$PLATFORM_ROOT/babelfish/.env" | |
| # Matrix server name (use localhost for local development) | |
| local MATRIX_SERVER_NAME="localhost" | |
| # Generate .env | |
| cat > "$PLATFORM_ROOT/babelfish/.env" <<EOF | |
| # Babelfish Configuration | |
| MATRIX_SERVER_NAME=$MATRIX_SERVER_NAME | |
| MATRIX_ADMIN_USER=admin | |
| POSTGRES_HOST=postgres | |
| POSTGRES_PORT=5433 | |
| POSTGRES_PASSWORD=$BABELFISH_POSTGRES_PASSWORD | |
| CENTRAL_DB_HOST=babelfish-central-db | |
| CENTRAL_DB_PORT=5432 | |
| CENTRAL_DB_NAME=babelfish_central | |
| CENTRAL_DB_PASSWORD=$BABELFISH_POSTGRES_PASSWORD | |
| MATRIX_DB_HOST=postgres | |
| MATRIX_DB_PORT=5432 | |
| MATRIX_DB_NAME=synapse | |
| MATRIX_DB_PASSWORD=$BABELFISH_POSTGRES_PASSWORD | |
| SYNC_INTERVAL=300 | |
| SYNC_BATCH_SIZE=1000 | |
| PARALLEL_BRIDGES=true | |
| ENABLE_EMBEDDINGS=true | |
| FULL_SYNC_ON_STARTUP=true | |
| ENABLE_DATABASE_DISCOVERY=true | |
| WHATSAPP_ENABLED=true | |
| DISCORD_ENABLED=true | |
| SLACK_ENABLED=true | |
| TELEGRAM_ENABLED=false | |
| META_ENABLED=true | |
| SIGNAL_ENABLED=false | |
| LINKEDIN_ENABLED=false | |
| TELEGRAM_API_ID= | |
| TELEGRAM_API_HASH= | |
| API_PORT=8000 | |
| LOG_LEVEL=INFO | |
| API_CORS_ORIGINS=http://localhost:8880,http://localhost:8448,http://localhost:3000,http://localhost:3001 | |
| API_MAX_RESULTS=1000 | |
| FASTAPI_RELOAD=true | |
| BACKUP_ENCRYPTION_KEY=$BABELFISH_BACKUP_KEY | |
| BACKUP_DESTINATION=./backups | |
| EOF | |
| log_success "Babelfish configured" | |
| } | |
| configure_crm_reply() { | |
| log_header "Configuring CRM Reply" | |
| # Generate passwords - check existing .env file if cache is empty | |
| prompt_or_cache "CRM_POSTGRES_PASSWORD" "PostgreSQL password for CRM Reply" "auto" true "$PLATFORM_ROOT/crm-reply/.env" | |
| # LLM Provider selection | |
| prompt_or_cache "CRM_LLM_PROVIDER" "LLM provider (anthropic/litellm)" "litellm" | |
| if [ "$CRM_LLM_PROVIDER" = "litellm" ]; then | |
| prompt_or_cache "LITELLM_API_KEY" "LiteLLM API key" "" true | |
| prompt_or_cache "LITELLM_BASE_URL" "LiteLLM base URL" "https://litellm.app.monadical.io/v1" | |
| prompt_or_cache "LITELLM_MODEL" "LiteLLM model" "GLM-4.5-Air-FP8-dev" | |
| elif [ "$CRM_LLM_PROVIDER" = "anthropic" ]; then | |
| prompt_or_cache "ANTHROPIC_API_KEY" "Anthropic API key" "" true | |
| fi | |
| # Generate .env | |
| cat > "$PLATFORM_ROOT/crm-reply/.env" <<EOF | |
| # CRM Reply Configuration | |
| POSTGRES_PASSWORD=$CRM_POSTGRES_PASSWORD | |
| CENTRAL_DB_URL=postgresql+asyncpg://postgres:$CRM_POSTGRES_PASSWORD@crm-reply-postgres:5432/babelfish_core | |
| QDRANT_URL=http://crm-reply-qdrant:6333 | |
| REDIS_URL=redis://crm-reply-redis:6379/0 | |
| CELERY_BROKER_URL=redis://crm-reply-redis:6379/0 | |
| CELERY_RESULT_BACKEND=redis://crm-reply-redis:6379/0 | |
| LOG_LEVEL=INFO | |
| ENABLE_EMBEDDINGS=true | |
| ENABLE_LLM_URGENCY=true | |
| ENABLE_AUTO_RESPONSES=true | |
| AUTO_SEND_RESPONSES=false | |
| AUTO_SEND_CONFIDENCE_THRESHOLD=80 | |
| API_CORS_ORIGINS=http://localhost:3000,http://localhost:3001 | |
| LLM_PROVIDER=$CRM_LLM_PROVIDER | |
| POPULATE_DEMO_DATA=false | |
| EOF | |
| # Add LLM-specific config | |
| if [ "$CRM_LLM_PROVIDER" = "litellm" ]; then | |
| cat >> "$PLATFORM_ROOT/crm-reply/.env" <<EOF | |
| LITELLM_API_KEY=$LITELLM_API_KEY | |
| LITELLM_BASE_URL=$LITELLM_BASE_URL | |
| LITELLM_MODEL=$LITELLM_MODEL | |
| EOF | |
| elif [ "$CRM_LLM_PROVIDER" = "anthropic" ]; then | |
| cat >> "$PLATFORM_ROOT/crm-reply/.env" <<EOF | |
| ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY | |
| EOF | |
| fi | |
| # Add enrichment API keys | |
| cat >> "$PLATFORM_ROOT/crm-reply/.env" <<EOF | |
| # Contact enrichment APIs (optional) | |
| EOF | |
| if [ -n "$APOLLO_API_KEY" ]; then | |
| echo "APOLLO_API_KEY=$APOLLO_API_KEY" >> "$PLATFORM_ROOT/crm-reply/.env" | |
| else | |
| echo "# APOLLO_API_KEY=" >> "$PLATFORM_ROOT/crm-reply/.env" | |
| fi | |
| if [ -n "$HUNTER_API_KEY" ]; then | |
| echo "HUNTER_API_KEY=$HUNTER_API_KEY" >> "$PLATFORM_ROOT/crm-reply/.env" | |
| else | |
| echo "# HUNTER_API_KEY=" >> "$PLATFORM_ROOT/crm-reply/.env" | |
| fi | |
| log_success "CRM Reply configured" | |
| } | |
| configure_meeting_prep() { | |
| log_header "Configuring Meeting Prep" | |
| # Generate passwords and secrets - check existing .env file if cache is empty | |
| prompt_or_cache "MEETING_POSTGRES_PASSWORD" "PostgreSQL password for Meeting Prep" "postgres" true "$PLATFORM_ROOT/meeting-prep/.env" | |
| # Prompt for production URLs (public URLs only) | |
| prompt_or_cache "MEETING_FRONTEND_URL" "Meeting Prep Frontend URL" "http://localhost:8081" false "$PLATFORM_ROOT/meeting-prep/.env" | |
| prompt_or_cache "MEETING_BACKEND_URL" "Meeting Prep Backend URL" "http://localhost:8001" false "$PLATFORM_ROOT/meeting-prep/.env" | |
| # Prompt for DataIndex public URL (for browser-clickable links) | |
| prompt_or_cache "DATAINDEX_PUBLIC_URL" "DataIndex public URL (for browser links)" "http://localhost:42180" false "$PLATFORM_ROOT/meeting-prep/.env" | |
| # Construct CORS origins from frontend URL | |
| local CORS_ORIGINS="${MEETING_FRONTEND_URL},http://localhost:5173,http://localhost:5174" | |
| # Prompt for LiteLLM configuration | |
| prompt_or_cache "LITELLM_API_KEY" "LiteLLM API key" "" true "$PLATFORM_ROOT/meeting-prep/.env" | |
| prompt_or_cache "LITELLM_BASE_URL" "LiteLLM base URL" "https://litellm.app.monadical.io/v1/" false "$PLATFORM_ROOT/meeting-prep/.env" | |
| prompt_or_cache "DEFAULT_LLM_MODEL" "Default LLM model" "zai-org/GLM-4.5-Air-FP8" false "$PLATFORM_ROOT/meeting-prep/.env" | |
| # Generate .env | |
| cat > "$PLATFORM_ROOT/meeting-prep/.env" <<EOF | |
| # Meeting Prep Configuration | |
| # ============================================================================= | |
| # Database Configuration | |
| # ============================================================================= | |
| DATABASE_URL=postgresql+asyncpg://postgres:${MEETING_POSTGRES_PASSWORD}@meeting-prep-postgres-1:5432/meeting_prep | |
| # ============================================================================= | |
| # Production URLs (for Pangolin deployment) | |
| # ============================================================================= | |
| # Frontend URL (exposed via Pangolin) | |
| FRONTEND_URL=${MEETING_FRONTEND_URL} | |
| # Backend URL (exposed via Pangolin) | |
| BACKEND_URL=${MEETING_BACKEND_URL} | |
| # CORS Configuration - include your production frontend URL | |
| CORS_ORIGINS=${CORS_ORIGINS} | |
| # Apollo API Configuration | |
| APOLLO_API_URL=https://api.apollo.io/v1 | |
| EOF | |
| # Add Apollo API key if provided | |
| if [ -n "$APOLLO_API_KEY" ]; then | |
| echo "APOLLO_API_KEY=$APOLLO_API_KEY" >> "$PLATFORM_ROOT/meeting-prep/.env" | |
| else | |
| echo "# APOLLO_API_KEY=" >> "$PLATFORM_ROOT/meeting-prep/.env" | |
| fi | |
| # Add LiteLLM configuration | |
| cat >> "$PLATFORM_ROOT/meeting-prep/.env" <<EOF | |
| # LiteLLM Configuration | |
| LITELLM_API_KEY=${LITELLM_API_KEY} | |
| LITELLM_BASE_URL=${LITELLM_BASE_URL} | |
| DEFAULT_LLM_MODEL=${DEFAULT_LLM_MODEL} | |
| # DataIndex Public URL - for browser-clickable links in the UI | |
| # Backend automatically uses http://host.docker.internal:42180 for API calls | |
| DATAINDEX_PUBLIC_URL=${DATAINDEX_PUBLIC_URL} | |
| # Note: ContactDB backend access is automatic via host.docker.internal:42800 | |
| # Frontend automatically uses BACKEND_URL for API calls (via docker-compose) | |
| EOF | |
| log_success "Meeting Prep configured" | |
| } | |
| configure_all_services() { | |
| for service_id in "${SELECTED_SERVICES[@]}"; do | |
| case "$service_id" in | |
| contactdb) | |
| configure_contactdb | |
| ;; | |
| dataindex) | |
| configure_dataindex | |
| ;; | |
| babelfish) | |
| configure_babelfish | |
| ;; | |
| crm-reply) | |
| configure_crm_reply | |
| ;; | |
| meeting-prep) | |
| configure_meeting_prep | |
| ;; | |
| *) | |
| log_warning "No configuration function for $service_id" | |
| ;; | |
| esac | |
| done | |
| } | |
| # ============================================================================ | |
| # Docker Network | |
| # ============================================================================ | |
| setup_docker_network() { | |
| log_header "Setting Up Docker Network" | |
| if docker network inspect "$DOCKER_NETWORK" &> /dev/null; then | |
| log_info "Network $DOCKER_NETWORK already exists" | |
| else | |
| docker network create "$DOCKER_NETWORK" | |
| log_success "Created network $DOCKER_NETWORK" | |
| fi | |
| } | |
| # ============================================================================ | |
| # Service Startup | |
| # ============================================================================ | |
| # Global variable to track if services were started | |
| SERVICES_STARTED=false | |
| start_services() { | |
| log_header "Starting Services" | |
| # Always prompt - don't cache this preference | |
| prompt START_NOW "Start services now?" "yes" | |
| if [ "$START_NOW" != "yes" ] && [ "$START_NOW" != "y" ]; then | |
| log_info "Skipping service startup" | |
| SERVICES_STARTED=false | |
| return | |
| fi | |
| SERVICES_STARTED=true | |
| # Use initial_service_setup for first-time install (runs make setup where needed) | |
| # Start in dependency order: contactdb first, then others | |
| for service_id in "${SELECTED_SERVICES[@]}"; do | |
| if [ "$service_id" = "contactdb" ]; then | |
| initial_service_setup "$service_id" | |
| fi | |
| done | |
| for service_id in "${SELECTED_SERVICES[@]}"; do | |
| if [ "$service_id" != "contactdb" ]; then | |
| initial_service_setup "$service_id" | |
| fi | |
| done | |
| } | |
| start_service() { | |
| local service_id=$1 | |
| local service_path="$PLATFORM_ROOT/$service_id" | |
| # Check if service directory exists | |
| if [ ! -d "$service_path" ]; then | |
| log_error "Service $service_id not found at $service_path" | |
| return 1 | |
| fi | |
| log_info "Starting $service_id..." | |
| cd "$service_path" | |
| if [ -f "docker-compose.yml" ]; then | |
| # Just start services without rebuilding | |
| docker_compose up -d 2>&1 | grep -v "password" || { | |
| log_error "Failed to start $service_id" | |
| docker_compose logs --tail=50 | |
| cd "$PLATFORM_ROOT" | |
| return 1 | |
| } | |
| else | |
| log_warning "No docker-compose.yml found for $service_id" | |
| fi | |
| cd "$PLATFORM_ROOT" | |
| log_success "$service_id started" | |
| } | |
| # Initial setup for a service (builds and starts on first run) | |
| initial_service_setup() { | |
| local service_id=$1 | |
| local service_path="$PLATFORM_ROOT/$service_id" | |
| if [ ! -d "$service_path" ]; then | |
| log_error "Service $service_id not found at $service_path" | |
| return 1 | |
| fi | |
| cd "$service_path" | |
| # Babelfish needs make setup for bridge configuration | |
| if [ "$service_id" = "babelfish" ]; then | |
| log_info "Running initial setup for babelfish..." | |
| if ! make setup 2>&1; then | |
| log_error "make setup failed for babelfish" | |
| log_info "Check that .env file exists and contains required variables" | |
| cd "$PLATFORM_ROOT" | |
| return 1 | |
| fi | |
| log_success "Babelfish setup completed" | |
| # Babelfish's make setup doesn't start services, so start them | |
| log_info "Starting babelfish services..." | |
| docker_compose up -d 2>&1 | grep -v "password" || { | |
| log_error "Failed to start $service_id" | |
| docker_compose logs --tail=50 | |
| cd "$PLATFORM_ROOT" | |
| return 1 | |
| } | |
| else | |
| # All other services: just build and start directly | |
| log_info "Building and starting $service_id..." | |
| if [ -f "docker-compose.yml" ]; then | |
| docker_compose up -d --build 2>&1 | grep -v "password" || { | |
| log_error "Failed to build and start $service_id" | |
| docker_compose logs --tail=50 | |
| cd "$PLATFORM_ROOT" | |
| return 1 | |
| } | |
| else | |
| log_warning "No docker-compose.yml found for $service_id" | |
| cd "$PLATFORM_ROOT" | |
| return 1 | |
| fi | |
| fi | |
| cd "$PLATFORM_ROOT" | |
| log_success "$service_id built and started" | |
| } | |
| stop_service() { | |
| local service_id=$1 | |
| local service_path="$PLATFORM_ROOT/$service_id" | |
| # Check if service directory exists | |
| if [ ! -d "$service_path" ]; then | |
| log_error "Service $service_id not found at $service_path" | |
| return 1 | |
| fi | |
| log_info "Stopping $service_id..." | |
| cd "$service_path" | |
| if [ -f "docker-compose.yml" ]; then | |
| docker_compose down 2>&1 | grep -v "password" || { | |
| log_error "Failed to stop $service_id" | |
| cd "$PLATFORM_ROOT" | |
| return 1 | |
| } | |
| else | |
| log_warning "No docker-compose.yml found for $service_id" | |
| fi | |
| cd "$PLATFORM_ROOT" | |
| log_success "$service_id stopped" | |
| } | |
| update_service() { | |
| local service_id=$1 | |
| local service_path="$PLATFORM_ROOT/$service_id" | |
| # Check if service directory exists | |
| if [ ! -d "$service_path" ]; then | |
| log_error "Service $service_id not found at $service_path" | |
| return 1 | |
| fi | |
| log_info "Updating $service_id..." | |
| # Stop service | |
| stop_service "$service_id" || return 1 | |
| # Pull latest changes | |
| log_info "Pulling latest changes for $service_id..." | |
| cd "$service_path" | |
| git pull 2>&1 | grep -v "password" || { | |
| log_warning "Failed to pull latest changes for $service_id" | |
| } | |
| # Rebuild and start | |
| log_info "Rebuilding and starting $service_id..." | |
| if [ -f "docker-compose.yml" ]; then | |
| docker_compose up -d --build 2>&1 | grep -v "password" || { | |
| log_error "Failed to build and start $service_id" | |
| docker_compose logs --tail=50 | |
| cd "$PLATFORM_ROOT" | |
| return 1 | |
| } | |
| else | |
| log_warning "No docker-compose.yml found for $service_id" | |
| fi | |
| cd "$PLATFORM_ROOT" | |
| log_success "$service_id updated" | |
| } | |
| # ============================================================================ | |
| # Service Management Commands | |
| # ============================================================================ | |
| get_configured_services() { | |
| # Get list of configured services (directories that exist in platform root) | |
| local configured=() | |
| for service_def in "${AVAILABLE_SERVICES[@]}"; do | |
| IFS='|' read -r id repo branch port desc mandatory <<< "$service_def" | |
| if [ -d "$PLATFORM_ROOT/$id" ]; then | |
| configured+=("$id") | |
| fi | |
| done | |
| echo "${configured[@]}" | |
| } | |
| cmd_start() { | |
| local service_name=$1 | |
| if [ -z "$service_name" ]; then | |
| log_error "Usage: $0 start <service|all>" | |
| echo "" | |
| echo "Configured services:" | |
| for svc in $(get_configured_services); do | |
| echo " - $svc" | |
| done | |
| exit 1 | |
| fi | |
| if [ "$service_name" = "all" ]; then | |
| log_header "Starting All Services" | |
| local services=($(get_configured_services)) | |
| # Start contactdb first if it exists | |
| for svc in "${services[@]}"; do | |
| if [ "$svc" = "contactdb" ]; then | |
| start_service "$svc" | |
| fi | |
| done | |
| # Start others | |
| for svc in "${services[@]}"; do | |
| if [ "$svc" != "contactdb" ]; then | |
| start_service "$svc" | |
| fi | |
| done | |
| else | |
| start_service "$service_name" | |
| fi | |
| } | |
| cmd_stop() { | |
| local service_name=$1 | |
| if [ -z "$service_name" ]; then | |
| log_error "Usage: $0 stop <service|all>" | |
| echo "" | |
| echo "Configured services:" | |
| for svc in $(get_configured_services); do | |
| echo " - $svc" | |
| done | |
| exit 1 | |
| fi | |
| if [ "$service_name" = "all" ]; then | |
| log_header "Stopping All Services" | |
| for svc in $(get_configured_services); do | |
| stop_service "$svc" | |
| done | |
| else | |
| stop_service "$service_name" | |
| fi | |
| } | |
| cmd_update() { | |
| local service_name=$1 | |
| if [ -z "$service_name" ]; then | |
| log_error "Usage: $0 update <service|all>" | |
| echo "" | |
| echo "Configured services:" | |
| for svc in $(get_configured_services); do | |
| echo " - $svc" | |
| done | |
| exit 1 | |
| fi | |
| if [ "$service_name" = "all" ]; then | |
| log_header "Updating All Services" | |
| local services=($(get_configured_services)) | |
| # Update contactdb first if it exists | |
| for svc in "${services[@]}"; do | |
| if [ "$svc" = "contactdb" ]; then | |
| update_service "$svc" | |
| fi | |
| done | |
| # Update others | |
| for svc in "${services[@]}"; do | |
| if [ "$svc" != "contactdb" ]; then | |
| update_service "$svc" | |
| fi | |
| done | |
| else | |
| update_service "$service_name" | |
| fi | |
| } | |
| cmd_enable() { | |
| local service_name=$1 | |
| if [ -z "$service_name" ]; then | |
| log_error "Usage: $0 enable <service>" | |
| echo "" | |
| echo "Available services to enable:" | |
| for service_def in "${AVAILABLE_SERVICES[@]}"; do | |
| IFS='|' read -r id repo branch port desc mandatory <<< "$service_def" | |
| if [ "$mandatory" = "false" ] && [ ! -d "$PLATFORM_ROOT/$id" ]; then | |
| echo " - $id: $desc" | |
| fi | |
| done | |
| exit 1 | |
| fi | |
| # Check if service is valid | |
| local service_found=false | |
| local service_info="" | |
| for service_def in "${AVAILABLE_SERVICES[@]}"; do | |
| IFS='|' read -r id repo branch port desc mandatory <<< "$service_def" | |
| if [ "$id" = "$service_name" ]; then | |
| service_found=true | |
| service_info="$service_def" | |
| break | |
| fi | |
| done | |
| if [ "$service_found" = false ]; then | |
| log_error "Unknown service: $service_name" | |
| exit 1 | |
| fi | |
| # Check if already enabled | |
| if [ -d "$PLATFORM_ROOT/$service_name" ]; then | |
| log_warning "Service $service_name is already enabled" | |
| echo "" | |
| echo "To start it, use: $0 start $service_name" | |
| echo "" | |
| exit 0 | |
| fi | |
| log_header "Enabling Service: $service_name" | |
| # Initialize cache if needed | |
| init_cache | |
| setup_github_auth | |
| setup_enrichment_apis | |
| # Add to selected services | |
| SELECTED_SERVICES=("$service_name") | |
| # Clone the service | |
| clone_services | |
| # Configure the service | |
| case "$service_name" in | |
| babelfish) | |
| configure_babelfish | |
| ;; | |
| crm-reply) | |
| configure_crm_reply | |
| ;; | |
| meeting-prep) | |
| configure_meeting_prep | |
| ;; | |
| dataindex) | |
| configure_dataindex | |
| ;; | |
| contactdb) | |
| configure_contactdb | |
| ;; | |
| *) | |
| log_warning "No configuration function for $service_name" | |
| ;; | |
| esac | |
| # Update cache to include this service in optional services | |
| local cached_services=$(load_from_cache "SELECTED_OPTIONAL_SERVICES") | |
| if [ -z "$cached_services" ] || [ "$cached_services" = "none" ]; then | |
| save_to_cache "SELECTED_OPTIONAL_SERVICES" "$service_name" | |
| else | |
| # Check if not already in cache | |
| if [[ ",$cached_services," != *",$service_name,"* ]]; then | |
| save_to_cache "SELECTED_OPTIONAL_SERVICES" "$cached_services,$service_name" | |
| fi | |
| fi | |
| # Ask if user wants to start the service | |
| prompt START_NOW "Start $service_name now?" "yes" | |
| if [ "$START_NOW" = "yes" ] || [ "$START_NOW" = "y" ]; then | |
| initial_service_setup "$service_name" | |
| fi | |
| log_success "Service $service_name enabled successfully!" | |
| } | |
| cmd_disable() { | |
| local service_name=$1 | |
| if [ -z "$service_name" ]; then | |
| log_error "Usage: $0 disable <service>" | |
| echo "" | |
| echo "Configured services:" | |
| for svc in $(get_configured_services); do | |
| echo " - $svc" | |
| done | |
| exit 1 | |
| fi | |
| # Check if service is configured | |
| if [ ! -d "$PLATFORM_ROOT/$service_name" ]; then | |
| log_error "Service $service_name is not enabled" | |
| exit 1 | |
| fi | |
| # Check if it's a mandatory service | |
| for service_def in "${AVAILABLE_SERVICES[@]}"; do | |
| IFS='|' read -r id repo branch port desc mandatory <<< "$service_def" | |
| if [ "$id" = "$service_name" ] && [ "$mandatory" = "true" ]; then | |
| log_error "Cannot disable mandatory service: $service_name" | |
| exit 1 | |
| fi | |
| done | |
| log_header "Disabling Service: $service_name" | |
| # Stop the service if it's running | |
| stop_service "$service_name" | |
| # Ask if user wants to remove the directory | |
| prompt REMOVE_DIR "Remove $service_name directory?" "no" | |
| if [ "$REMOVE_DIR" = "yes" ] || [ "$REMOVE_DIR" = "y" ]; then | |
| log_info "Removing $PLATFORM_ROOT/$service_name..." | |
| rm -rf "$PLATFORM_ROOT/$service_name" | |
| log_success "Directory removed" | |
| else | |
| log_info "Service stopped but directory preserved at $PLATFORM_ROOT/$service_name" | |
| fi | |
| # Update cache to remove this service from optional services | |
| init_cache | |
| local cached_services=$(load_from_cache "SELECTED_OPTIONAL_SERVICES") | |
| if [ -n "$cached_services" ] && [ "$cached_services" != "none" ]; then | |
| # Remove service from comma-separated list | |
| local new_services=$(echo "$cached_services" | tr ',' '\n' | grep -v "^$service_name$" | tr '\n' ',' | sed 's/,$//') | |
| if [ -z "$new_services" ]; then | |
| save_to_cache "SELECTED_OPTIONAL_SERVICES" "none" | |
| else | |
| save_to_cache "SELECTED_OPTIONAL_SERVICES" "$new_services" | |
| fi | |
| fi | |
| log_success "Service $service_name disabled" | |
| } | |
| # ============================================================================ | |
| # Status Display | |
| # ============================================================================ | |
| show_status() { | |
| log_header "Platform Status" | |
| echo -e "${CYAN}Services:${NC}" | |
| # Services to display URLs for (only if running) | |
| local url_services=("contactdb" "babelfish" "crm-reply" "meeting-prep") | |
| for service_def in "${AVAILABLE_SERVICES[@]}"; do | |
| IFS='|' read -r id repo branch port desc mandatory <<< "$service_def" | |
| # Check if selected | |
| local selected=false | |
| for selected_id in "${SELECTED_SERVICES[@]}"; do | |
| if [ "$selected_id" = "$id" ]; then | |
| selected=true | |
| break | |
| fi | |
| done | |
| if [ "$selected" = true ]; then | |
| if [ "$SERVICES_STARTED" = true ]; then | |
| # Services are running - show checkmark and URLs | |
| echo -e " ${GREEN}✓${NC} $desc" | |
| # Only display URL for specific services | |
| local show_url=false | |
| for url_service in "${url_services[@]}"; do | |
| if [ "$url_service" = "$id" ]; then | |
| show_url=true | |
| break | |
| fi | |
| done | |
| if [ "$show_url" = true ]; then | |
| echo -e " ${BLUE}http://localhost:$port${NC}" | |
| fi | |
| else | |
| # Services are configured but not started | |
| echo -e " ${BLUE}ℹ${NC} $desc (configured, not started)" | |
| fi | |
| fi | |
| done | |
| echo "" | |
| log_info "Workspace: $PLATFORM_ROOT" | |
| log_info "Docker network: $DOCKER_NETWORK" | |
| echo "" | |
| if [ "$SERVICES_STARTED" = false ]; then | |
| echo "To manage services:" | |
| echo " $0 start <service|all> # Start services" | |
| echo " $0 stop <service|all> # Stop services" | |
| echo " $0 update <service|all> # Update services (pull, rebuild, restart)" | |
| echo " $0 enable <service> # Enable a new service" | |
| echo " $0 disable <service> # Disable a service" | |
| echo " $0 status # Show service status" | |
| echo "" | |
| fi | |
| } | |
| show_running_status() { | |
| log_header "Platform Status - Running Services" | |
| # Services to display URLs for | |
| local url_services=("contactdb" "babelfish" "crm-reply" "meeting-prep") | |
| for service_def in "${AVAILABLE_SERVICES[@]}"; do | |
| IFS='|' read -r id repo branch port desc mandatory <<< "$service_def" | |
| # Check if service directory exists (service was configured) | |
| if [ ! -d "$PLATFORM_ROOT/$id" ]; then | |
| continue | |
| fi | |
| # Use service id as container name pattern | |
| local container_pattern="$id" | |
| local running_containers=$(docker ps --filter "name=${container_pattern}" --format "{{.Names}}" 2>/dev/null | wc -l | xargs) | |
| local total_containers=$(docker ps -a --filter "name=${container_pattern}" --format "{{.Names}}" 2>/dev/null | wc -l | xargs) | |
| # Skip if service has no containers at all (not deployed) | |
| if [ "$total_containers" -eq 0 ]; then | |
| continue | |
| fi | |
| if [ "$running_containers" -gt 0 ]; then | |
| # Service has running containers | |
| echo -e " ${GREEN}✓${NC} $desc" | |
| # Show container status summary | |
| echo -e " Containers: ${GREEN}${running_containers}${NC}/${total_containers} running" | |
| # Only display URL for specific services | |
| local show_url=false | |
| for url_service in "${url_services[@]}"; do | |
| if [ "$url_service" = "$id" ]; then | |
| show_url=true | |
| break | |
| fi | |
| done | |
| if [ "$show_url" = true ]; then | |
| echo -e " URL: ${BLUE}http://localhost:$port${NC}" | |
| fi | |
| else | |
| # Service configured but not running | |
| echo -e " ${YELLOW}⚠${NC} $desc (stopped)" | |
| echo -e " Containers: ${YELLOW}0${NC}/${total_containers} running" | |
| fi | |
| done | |
| echo "" | |
| log_info "Workspace: $PLATFORM_ROOT" | |
| log_info "Docker network: $DOCKER_NETWORK" | |
| echo "" | |
| echo "To manage services:" | |
| echo " $0 start <service|all> # Start services" | |
| echo " $0 stop <service|all> # Stop services" | |
| echo " $0 update <service|all> # Update services (pull, rebuild, restart)" | |
| echo " $0 enable <service> # Enable a new service" | |
| echo " $0 disable <service> # Disable a service" | |
| echo " $0 status # Show service status" | |
| echo "" | |
| } | |
| # ============================================================================ | |
| # Main Installation Flow | |
| # ============================================================================ | |
| install() { | |
| log_header "Installing $PLATFORM_NAME" | |
| check_dependencies | |
| init_cache | |
| setup_github_auth | |
| setup_enrichment_apis | |
| select_services | |
| clone_services | |
| configure_all_services | |
| setup_docker_network | |
| start_services | |
| show_status | |
| log_success "Installation complete!" | |
| echo "" | |
| log_info "To manage your platform:" | |
| echo " cd $PLATFORM_ROOT" | |
| echo " docker compose -f <service>/docker-compose.yml logs" | |
| echo " # Or use docker-compose if you have v1 installed" | |
| echo "" | |
| } | |
| # ============================================================================ | |
| # Command Handling | |
| # ============================================================================ | |
| show_usage() { | |
| echo "Monadical Platform Setup" | |
| echo "" | |
| echo "Usage: $0 [command] [options]" | |
| echo "" | |
| echo "Commands:" | |
| echo " install - Install platform (default)" | |
| echo " status - Show running services and container statuses" | |
| echo " start <service|all> - Start specific service or all services" | |
| echo " stop <service|all> - Stop specific service or all services" | |
| echo " update <service|all> - Update service (stop, pull, build, start)" | |
| echo " enable <service> - Enable and configure a new service" | |
| echo " disable <service> - Disable a service (stops it and optionally removes directory)" | |
| echo " help - Show this help" | |
| echo "" | |
| echo "Options:" | |
| echo " --no-cache - Ignore cached selections and prompt for everything" | |
| echo "" | |
| echo "Examples:" | |
| echo " $0 start all # Start all configured services" | |
| echo " $0 stop contactdb # Stop contactdb service" | |
| echo " $0 update babelfish # Update babelfish service" | |
| echo " $0 enable crm-reply # Enable CRM Reply service" | |
| echo " $0 disable babelfish # Disable Babelfish service" | |
| echo "" | |
| } | |
| main() { | |
| local command="${1:-install}" | |
| local service_arg="${2:-}" | |
| # Parse flags | |
| for arg in "$@"; do | |
| case "$arg" in | |
| --no-cache) | |
| IGNORE_CACHE=true | |
| ;; | |
| esac | |
| done | |
| case "$command" in | |
| install) | |
| install | |
| ;; | |
| status) | |
| show_running_status | |
| ;; | |
| start) | |
| cmd_start "$service_arg" | |
| ;; | |
| stop) | |
| cmd_stop "$service_arg" | |
| ;; | |
| update) | |
| cmd_update "$service_arg" | |
| ;; | |
| enable) | |
| cmd_enable "$service_arg" | |
| ;; | |
| disable) | |
| cmd_disable "$service_arg" | |
| ;; | |
| help|--help|-h) | |
| show_usage | |
| ;; | |
| --no-cache) | |
| # Already handled above, treat as install | |
| install | |
| ;; | |
| *) | |
| log_error "Unknown command: $command" | |
| show_usage | |
| exit 1 | |
| ;; | |
| esac | |
| } | |
| # ============================================================================ | |
| # Entry Point | |
| # ============================================================================ | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment