#!/bin/bash # Mosaic Stack Setup Wizard # Interactive installer for Mosaic Stack personal assistant platform # Supports Docker and native deployments set -e # ============================================================================ # Configuration # ============================================================================ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" # Set up logging LOG_DIR="$PROJECT_ROOT/logs" mkdir -p "$LOG_DIR" LOG_FILE="$LOG_DIR/setup-$(date +%Y%m%d_%H%M%S).log" # Redirect stdout/stderr to both console and log file exec > >(tee -a "$LOG_FILE") 2>&1 # Send trace output (set -x) ONLY to log file on fd 3 exec 3>>"$LOG_FILE" export BASH_XTRACEFD=3 # Enable verbose command tracing (only goes to log file now) set -x echo "===================================================================" echo "Mosaic Stack Setup Wizard" echo "Started: $(date)" echo "Full log: $LOG_FILE" echo "===================================================================" echo "" # Source common functions # shellcheck source=lib/common.sh source "$SCRIPT_DIR/lib/common.sh" # ============================================================================ # Global Variables # ============================================================================ NON_INTERACTIVE=false DRY_RUN=false MODE="" ENABLE_SSO=false USE_BUNDLED_AUTHENTIK=false EXTERNAL_AUTHENTIK_URL="" OLLAMA_MODE="disabled" OLLAMA_URL="" ENABLE_MOLTBOT=false MOSAIC_BASE_URL="" MOSAIC_ALLOWED_HOSTS="" AUTHENTIK_BASE_URL="" DETECTED_OS="" DETECTED_PKG_MANAGER="" PORT_OVERRIDES=() # ============================================================================ # Help and Usage # ============================================================================ show_help() { cat << EOF Mosaic Stack Setup Wizard USAGE: $0 [OPTIONS] OPTIONS: -h, --help Show this help message --non-interactive Run in non-interactive mode (requires all options) --dry-run Show what would happen without executing --mode MODE Deployment mode: docker or native --enable-sso Enable Authentik SSO (Docker mode only) --bundled-authentik Use bundled Authentik server (with --enable-sso) --external-authentik URL Use external Authentik server URL (with --enable-sso) --ollama-mode MODE Ollama mode: local, remote, disabled (default: disabled) --ollama-url URL Ollama server URL (if --ollama-mode remote) --enable-moltbot Enable MoltBot integration --base-url URL Mosaic base URL (e.g., https://mosaic.example.com) EXAMPLES: # Interactive mode (recommended for first-time setup) $0 # Non-interactive Docker deployment with bundled SSO $0 --non-interactive --mode docker --enable-sso --bundled-authentik # Non-interactive Docker with external Authentik and local Ollama $0 --non-interactive --mode docker --enable-sso --external-authentik "https://auth.example.com" --ollama-mode local # Dry run to see what would happen $0 --dry-run --mode docker --enable-sso --bundled-authentik EOF } # ============================================================================ # Argument Parsing # ============================================================================ parse_arguments() { while [[ $# -gt 0 ]]; do case $1 in -h|--help) show_help exit 0 ;; --non-interactive) NON_INTERACTIVE=true ;; --dry-run) DRY_RUN=true ;; --mode) MODE="$2" shift ;; --enable-sso) ENABLE_SSO=true ;; --bundled-authentik) USE_BUNDLED_AUTHENTIK=true ;; --external-authentik) EXTERNAL_AUTHENTIK_URL="$2" shift ;; --ollama-mode) OLLAMA_MODE="$2" shift ;; --ollama-url) OLLAMA_URL="$2" shift ;; --enable-moltbot) ENABLE_MOLTBOT=true ;; --base-url) MOSAIC_BASE_URL="$2" shift ;; *) print_error "Unknown option: $1" echo "Use --help for usage information" exit 1 ;; esac shift done # Validate non-interactive mode if [[ "$NON_INTERACTIVE" == true ]]; then if [[ -z "$MODE" ]]; then print_error "Non-interactive mode requires --mode" exit 1 fi if [[ "$MODE" != "native" && "$MODE" != "docker" ]]; then print_error "Invalid mode: $MODE (must be 'native' or 'docker')" exit 1 fi if [[ "$OLLAMA_MODE" == "remote" && -z "$OLLAMA_URL" ]]; then print_error "Remote Ollama mode requires --ollama-url" exit 1 fi fi } # ============================================================================ # Welcome Banner # ============================================================================ show_banner() { if [[ "$NON_INTERACTIVE" == true ]]; then return fi cat << "EOF" __ __ _ ____ _ _ | \/ | ___ ___ __ _(_) ___| _ \| |_ __ _ ___| | __ | |\/| |/ _ \/ __|/ _` | |/ __| |_) | __/ _` |/ __| |/ / | | | | (_) \__ \ (_| | | (__| __/| || (_| | (__| < |_| |_|\___/|___/\__,_|_|\___|_| \__\__,_|\___|_|\_\ Multi-Tenant Personal Assistant Platform EOF echo "Welcome to the Mosaic Stack Setup Wizard!" echo "" echo "This wizard will guide you through setting up Mosaic Stack on your system." echo "You can choose between Docker (production) or native (development) deployment," echo "and configure optional features like Authentik SSO and Ollama LLM integration." echo "" } # ============================================================================ # Platform Detection # ============================================================================ detect_platform() { print_header "Detecting Platform" DETECTED_OS=$(detect_os) DETECTED_PKG_MANAGER=$(detect_package_manager "$DETECTED_OS") local os_name os_name=$(get_os_name "$DETECTED_OS") if [[ "$DETECTED_OS" == "unknown" ]]; then print_warning "Could not detect operating system" print_info "Detected OS type: $OSTYPE" echo "" if [[ "$NON_INTERACTIVE" == true ]]; then print_error "Cannot proceed in non-interactive mode on unknown OS" exit 1 fi if ! confirm "Continue anyway?"; then exit 1 fi else print_success "Detected: $os_name ($DETECTED_PKG_MANAGER)" fi } # ============================================================================ # Mode Selection # ============================================================================ select_deployment_mode() { if [[ -n "$MODE" ]]; then print_header "Deployment Mode" print_info "Using mode: $MODE" return fi print_header "Deployment Mode" echo "" echo "How would you like to run Mosaic Stack?" echo "" echo " 1) Docker (Recommended)" echo " - Best for production deployment" echo " - Isolated environment with all dependencies" echo " - Includes PostgreSQL, Valkey, all services" echo " - Optional Authentik SSO integration" echo " - Turnkey deployment" echo "" echo " 2) Native" echo " - Best for development" echo " - Runs directly on your system" echo " - Requires manual dependency installation" echo " - Easier to debug and modify" echo "" local selection selection=$(select_option "Select deployment mode:" \ "Docker (production, recommended)" \ "Native (development)") if [[ "$selection" == *"Docker"* ]]; then MODE="docker" else MODE="native" fi print_success "Selected: $MODE mode" } # ============================================================================ # Dependency Checking # ============================================================================ check_and_install_dependencies() { print_header "Checking Dependencies" local missing_deps=() if [[ "$MODE" == "docker" ]]; then if ! check_docker; then missing_deps+=("Docker") fi if ! check_docker_compose; then missing_deps+=("Docker Compose") fi else if ! check_node 18; then missing_deps+=("Node.js 18+") fi if ! check_pnpm; then missing_deps+=("pnpm") fi if ! check_postgres; then missing_deps+=("PostgreSQL") fi fi if [[ ${#missing_deps[@]} -eq 0 ]]; then print_success "All dependencies satisfied" return 0 fi echo "" print_warning "Missing dependencies:" for dep in "${missing_deps[@]}"; do echo " - $dep" done echo "" if [[ "$NON_INTERACTIVE" == true ]]; then print_error "Dependency check failed in non-interactive mode" exit 1 fi if confirm "Would you like to install missing dependencies?"; then install_missing_dependencies else print_error "Cannot proceed without required dependencies" exit 1 fi } install_missing_dependencies() { print_header "Installing Dependencies" check_sudo if [[ "$MODE" == "docker" ]]; then # Install Docker if ! check_docker; then local docker_pkg docker_pkg=$(get_package_name "$DETECTED_PKG_MANAGER" "docker") if [[ -n "$docker_pkg" ]]; then install_package "$DETECTED_PKG_MANAGER" "$docker_pkg" # Start Docker service case "$DETECTED_PKG_MANAGER" in pacman|dnf) print_step "Starting Docker service..." sudo systemctl enable --now docker ;; esac # Add user to docker group print_step "Adding user to docker group..." sudo usermod -aG docker "$USER" print_warning "You may need to log out and back in for docker group membership to take effect" print_info "Or run: newgrp docker" fi fi # Install Docker Compose if ! check_docker_compose; then local compose_pkg compose_pkg=$(get_package_name "$DETECTED_PKG_MANAGER" "docker-compose") if [[ -n "$compose_pkg" ]]; then install_package "$DETECTED_PKG_MANAGER" "$compose_pkg" fi fi else # Install Node.js if ! check_node 18; then local node_pkg node_pkg=$(get_package_name "$DETECTED_PKG_MANAGER" "node") if [[ -n "$node_pkg" ]]; then install_package "$DETECTED_PKG_MANAGER" "$node_pkg" fi fi # Install pnpm if ! check_pnpm; then print_step "Installing pnpm via npm..." npm install -g pnpm fi # Install PostgreSQL if ! check_postgres; then local postgres_pkg postgres_pkg=$(get_package_name "$DETECTED_PKG_MANAGER" "postgres") if [[ -n "$postgres_pkg" ]]; then install_package "$DETECTED_PKG_MANAGER" "$postgres_pkg" fi fi fi # Re-check dependencies echo "" local still_missing=false if [[ "$MODE" == "docker" ]]; then if ! check_docker || ! check_docker_compose; then still_missing=true fi else if ! check_node 18 || ! check_pnpm; then still_missing=true fi fi if [[ "$still_missing" == true ]]; then print_error "Some dependencies are still missing after installation" print_info "You may need to install them manually" exit 1 else print_success "All dependencies installed successfully" fi } # ============================================================================ # Configuration Collection # ============================================================================ load_existing_env() { if [[ -f "$PROJECT_ROOT/.env" ]]; then print_header "Detecting Existing Configuration" parse_env_file "$PROJECT_ROOT/.env" print_success "Found existing .env file" return 0 fi return 1 } collect_configuration() { print_header "Configuration" # Try to load existing .env local has_existing_env=false if load_existing_env; then has_existing_env=true echo "" print_info "Found existing configuration. I'll ask about each setting." echo "" fi # Continue with remaining configuration... # (This would continue with URL config, SSO, Ollama, MoltBot, etc.) # Keeping this section shorter for now - can be expanded following Calibr pattern } # ============================================================================ # Main Execution # ============================================================================ main() { parse_arguments "$@" show_banner detect_platform select_deployment_mode check_and_install_dependencies collect_configuration # TODO: generate_env_file # TODO: run_deployment # TODO: show_post_install_info echo "" print_success "Setup complete (partial implementation)!" echo "" echo "===================================================================" echo "Setup completed: $(date)" echo "Full log saved to: $LOG_FILE" echo "===================================================================" echo "" print_info "This is a foundation setup script." print_info "Additional features (env generation, deployment) to be implemented." } # Run main function main "$@"