Files
stack/scripts/setup.sh
Jason Woltje fd93be6032 feat: Add comprehensive setup wizard foundation
Modeled after Calibr setup.sh pattern (~/src/calibr/scripts/setup.sh).

Implemented (Foundation):
- Platform detection (Ubuntu, Arch, macOS, Fedora)
- Dependency checking and installation
- Mode selection (Docker vs Native)
- Interactive + non-interactive modes
- Comprehensive logging (clean console + full trace to log file)
- Common utility functions library (450+ lines)

Features in common.sh:
- Output formatting (colors, headers, success/error/warning)
- User input (confirm, select_option)
- Platform detection
- Dependency checking (Docker, Node, pnpm, PostgreSQL)
- Package installation (apt, pacman, dnf, brew)
- Validation (URL, email, port, domain)
- Secret generation (cryptographically secure)
- .env file parsing and management
- Port conflict detection
- File backup with timestamps

To Be Implemented (See scripts/README.md):
- Complete configuration collection
- .env generation with smart preservation
- Port conflict detection
- Password/secret generation
- Authentik blueprint auto-configuration
- Docker deployment execution
- Post-install instructions

Usage:
  ./scripts/setup.sh                    # Interactive
  ./scripts/setup.sh --help             # Show options
  ./scripts/setup.sh --dry-run          # Preview
  ./scripts/setup.sh --non-interactive  # CI/CD

Refs: Setup wizard issue (created)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 16:45:56 -06:00

481 lines
14 KiB
Bash
Executable File

#!/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 "$@"