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>
This commit is contained in:
2026-01-31 16:44:25 -06:00
parent 0eb3abc12c
commit fd93be6032
26 changed files with 1255 additions and 7491 deletions

265
scripts/README.md Normal file
View File

@@ -0,0 +1,265 @@
# Mosaic Stack Setup Scripts
Comprehensive setup wizard for Mosaic Stack, inspired by the [Calibr setup pattern](https://github.com/noahwoltje/calibr).
## Quick Start
```bash
# Interactive setup (recommended)
./scripts/setup.sh
# Non-interactive Docker with SSO
./scripts/setup.sh --non-interactive --mode docker --enable-sso --bundled-authentik
# Dry run to see what would happen
./scripts/setup.sh --dry-run --mode docker
```
## Current Status
**✅ Implemented (Foundation):**
- Platform detection (Ubuntu, Arch, macOS)
- Dependency checking (Docker, Node.js, pnpm, PostgreSQL)
- Dependency installation
- Mode selection (Docker vs Native)
- Argument parsing
- Comprehensive logging
- Common utility functions library
**🚧 To Be Implemented:**
- Complete configuration collection (SSO, Ollama, MoltBot, URLs)
- .env file generation with smart preservation
- Port conflict detection
- Password/secret generation
- Authentik blueprint auto-configuration
- Docker deployment execution
- Post-install instructions
See full implementation plan in [issue tracking](https://git.mosaicstack.dev/mosaic/stack/issues).
## Files
```
scripts/
├── setup.sh # Main setup wizard (foundation complete)
├── lib/
│ ├── common.sh # Utility functions (complete)
│ └── docker.sh # Docker-specific functions (TODO)
└── templates/
└── .env.template # Template for .env generation (TODO)
```
## Features
### Platform Detection
Automatically detects:
- Operating system (Ubuntu, Arch, Fedora, macOS)
- Package manager (apt, pacman, dnf, brew)
- Installed dependencies
### Dependency Management
Checks and optionally installs:
- **Docker mode:** Docker, Docker Compose
- **Native mode:** Node.js 18+, pnpm, PostgreSQL
### Interactive Mode
User-friendly wizard with:
- Color-coded output
- Clear prompts and defaults
- Yes/no confirmations
- Numbered menu selections
### Non-Interactive Mode
Suitable for CI/CD:
```bash
./scripts/setup.sh \
--non-interactive \
--mode docker \
--enable-sso \
--bundled-authentik \
--ollama-mode local
```
### Dry Run
Preview changes without execution:
```bash
./scripts/setup.sh --dry-run --mode docker
```
## Common Functions (lib/common.sh)
### Output Functions
- `print_header()` - Section headers
- `print_success()` - Success messages (green ✓)
- `print_error()` - Error messages (red ✗)
- `print_warning()` - Warnings (yellow ⚠)
- `print_info()` - Info messages (blue )
- `print_step()` - Step indicators (cyan →)
### User Input
- `confirm()` - Yes/no prompts with defaults
- `select_option()` - Numbered menu selection
### Platform Detection
- `detect_os()` - Detect operating system
- `detect_package_manager()` - Detect package manager
- `get_os_name()` - Human-readable OS name
### Dependency Checking
- `check_command()` - Check if command exists
- `check_docker()` - Check Docker installation and daemon
- `check_docker_compose()` - Check Docker Compose
- `check_node()` - Check Node.js version
- `check_pnpm()` - Check pnpm installation
- `check_postgres()` - Check PostgreSQL
### Package Installation
- `get_package_name()` - Get package name for specific manager
- `install_package()` - Install package via detected manager
- `check_sudo()` - Check/request sudo access
### Validation
- `validate_url()` - Validate URL format
- `validate_email()` - Validate email format
- `validate_port()` - Validate port number (1-65535)
- `validate_domain()` - Validate domain name
### Secret Generation
- `generate_secret()` - Cryptographically secure random (with special chars)
- `generate_password()` - User-friendly password (alphanumeric only)
- `mask_value()` - Mask sensitive values for display
- `is_placeholder()` - Detect placeholder values
### .env Management
- `parse_env_file()` - Parse .env into associative array
- `get_env_value()` - Get value from parsed env
- `set_env_value()` - Set value in env array
### File Operations
- `backup_file()` - Create timestamped backup
### Port Checking
- `check_port_in_use()` - Check if port is in use
- `suggest_alternative_port()` - Suggest alternative if conflict
## Logging
All output is logged to `logs/setup-YYYYMMDD_HHMMSS.log`:
- Clean output to console
- Full trace (set -x) to log file only
- Errors and warnings to both
## CLI Options
```
--non-interactive Run without prompts (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
--ollama-mode MODE Ollama mode: local, remote, disabled (default)
--ollama-url URL Ollama server URL (if remote)
--enable-moltbot Enable MoltBot integration
--base-url URL Mosaic base URL (e.g., https://mosaic.example.com)
--help Show help
```
## Next Steps for Implementation
### 1. Configuration Collection (High Priority)
- [ ] URL and domain configuration
- [ ] SSO setup (bundled vs external Authentik)
- [ ] Ollama configuration (local/remote/disabled)
- [ ] MoltBot toggle
- [ ] Existing config detection and smart updates
### 2. .env Generation (High Priority)
- [ ] Create .env template
- [ ] Generate passwords/secrets securely
- [ ] Preserve existing non-placeholder values
- [ ] Apply port overrides
- [ ] Write .admin-credentials file
### 3. Port Conflict Detection
- [ ] Check: 3000, 3001 (web), 4000 (API), 5432 (PostgreSQL), 6379 (Valkey), 9000/9443 (Authentik)
- [ ] Suggest alternatives automatically
- [ ] Update .env with overrides
### 4. Authentik Blueprint Auto-Configuration
- [ ] Copy blueprint to authentik-blueprints/
- [ ] Create docker-compose.authentik.yml overlay
- [ ] Mount blueprint volume
- [ ] Document post-install steps (initial setup, client secret)
### 5. Deployment Execution
- [ ] Docker: docker compose up -d
- [ ] Native: pnpm install, database setup, migrations
- [ ] Health checks
### 6. Post-Install Instructions
- [ ] Show URLs (web, API, Authentik)
- [ ] Show credentials location
- [ ] Next steps checklist
- [ ] Troubleshooting tips
### 7. Testing
- [ ] Test on Ubuntu, Arch, macOS
- [ ] Test interactive mode
- [ ] Test non-interactive mode
- [ ] Test dry-run mode
- [ ] Test existing .env preservation
- [ ] Test port conflict handling
## Contributing
When extending this setup script:
1. Follow the Calibr pattern for consistency
2. Add utility functions to `lib/common.sh`
3. Keep main `setup.sh` focused on orchestration
4. Test both interactive and non-interactive modes
5. Update this README with new features
## Reference
Original pattern: `~/src/calibr/scripts/setup.sh` (1,327 lines)
Key patterns to follow:
- Comprehensive error handling
- Clear user communication
- Smart existing config detection
- Secure secret generation
- Dry-run capability
- Both interactive and non-interactive modes

510
scripts/lib/common.sh Normal file
View File

@@ -0,0 +1,510 @@
#!/bin/bash
# Common utility functions for Mosaic Stack setup
# Adapted from Calibr setup pattern
# ============================================================================
# Output Formatting
# ============================================================================
# Colors
if [[ -t 1 ]]; then
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' # No Color
else
RED=''
GREEN=''
YELLOW=''
BLUE=''
CYAN='\033[0;36m'
BOLD=''
NC=''
fi
print_header() {
echo ""
echo -e "${BOLD}${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BOLD}${CYAN} $1${NC}"
echo -e "${BOLD}${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
}
print_success() {
echo -e "${GREEN}${NC} $1"
}
print_error() {
echo -e "${RED}${NC} $1" >&2
}
print_warning() {
echo -e "${YELLOW}${NC} $1"
}
print_info() {
echo -e "${BLUE}${NC} $1"
}
print_step() {
echo -e "${CYAN}${NC} $1"
}
# ============================================================================
# User Input
# ============================================================================
confirm() {
local prompt="$1"
local default="${2:-n}"
local response
if [[ "$default" == "y" ]]; then
prompt="$prompt [Y/n]: "
else
prompt="$prompt [y/N]: "
fi
while true; do
read -r -p "$prompt" response
response=${response:-$default}
case "$response" in
[Yy]|[Yy][Ee][Ss])
return 0
;;
[Nn]|[Nn][Oo])
return 1
;;
*)
echo "Please answer yes or no."
;;
esac
done
}
select_option() {
local prompt="$1"
shift
local options=("$@")
local num_options=${#options[@]}
echo "$prompt"
for i in "${!options[@]}"; do
printf " %d) %s\n" "$((i + 1))" "${options[$i]}"
done
echo ""
local selection
while true; do
read -r -p "Enter selection [1-$num_options]: " selection
if [[ "$selection" =~ ^[0-9]+$ ]] && \
[ "$selection" -ge 1 ] && \
[ "$selection" -le "$num_options" ]; then
echo "${options[$((selection - 1))]}"
return 0
else
print_error "Invalid selection. Please enter a number between 1 and $num_options."
fi
done
}
# ============================================================================
# Platform Detection
# ============================================================================
detect_os() {
case "$OSTYPE" in
linux-gnu*)
if [[ -f /etc/os-release ]]; then
source /etc/os-release
case "$ID" in
ubuntu|debian)
echo "debian"
;;
arch|manjaro|endeavouros)
echo "arch"
;;
fedora|rhel|centos)
echo "fedora"
;;
*)
echo "linux"
;;
esac
else
echo "linux"
fi
;;
darwin*)
echo "macos"
;;
*)
echo "unknown"
;;
esac
}
detect_package_manager() {
local os="$1"
case "$os" in
debian)
echo "apt"
;;
arch)
echo "pacman"
;;
fedora)
echo "dnf"
;;
macos)
if command -v brew >/dev/null 2>&1; then
echo "brew"
else
echo "none"
fi
;;
*)
echo "unknown"
;;
esac
}
get_os_name() {
local os="$1"
case "$os" in
debian)
echo "Debian/Ubuntu"
;;
arch)
echo "Arch Linux"
;;
fedora)
echo "Fedora/RHEL"
;;
macos)
echo "macOS"
;;
*)
echo "Unknown OS"
;;
esac
}
# ============================================================================
# Command and Dependency Checking
# ============================================================================
check_command() {
command -v "$1" >/dev/null 2>&1
}
check_docker() {
check_command docker && docker info >/dev/null 2>&1
}
check_docker_compose() {
if docker compose version >/dev/null 2>&1; then
return 0
elif check_command docker-compose; then
return 0
else
return 1
fi
}
check_docker_buildx() {
docker buildx version >/dev/null 2>&1
}
check_node() {
local min_version="${1:-18}"
if ! check_command node; then
return 1
fi
local node_version
node_version=$(node --version | sed 's/v//' | cut -d. -f1)
if [[ "$node_version" -ge "$min_version" ]]; then
return 0
else
return 1
fi
}
check_pnpm() {
check_command pnpm
}
check_postgres() {
check_command psql
}
check_sudo() {
if ! command -v sudo >/dev/null 2>&1; then
print_error "sudo is not installed"
return 1
fi
if ! sudo -n true 2>/dev/null; then
print_warning "This script may need elevated privileges"
print_info "You may be prompted for your password"
sudo -v
fi
}
# ============================================================================
# Package Installation
# ============================================================================
get_package_name() {
local pkg_manager="$1"
local package="$2"
case "$pkg_manager" in
apt)
case "$package" in
docker) echo "docker.io" ;;
docker-compose) echo "docker-compose" ;;
node) echo "nodejs" ;;
postgres) echo "postgresql postgresql-contrib" ;;
*) echo "$package" ;;
esac
;;
pacman)
case "$package" in
docker) echo "docker" ;;
docker-compose) echo "docker-compose" ;;
node) echo "nodejs" ;;
postgres) echo "postgresql" ;;
*) echo "$package" ;;
esac
;;
dnf)
case "$package" in
docker) echo "docker" ;;
docker-compose) echo "docker-compose" ;;
node) echo "nodejs" ;;
postgres) echo "postgresql-server" ;;
*) echo "$package" ;;
esac
;;
brew)
case "$package" in
docker) echo "docker" ;;
node) echo "node" ;;
postgres) echo "postgresql@17" ;;
*) echo "$package" ;;
esac
;;
*)
echo "$package"
;;
esac
}
install_package() {
local pkg_manager="$1"
local package="$2"
print_step "Installing $package..."
case "$pkg_manager" in
apt)
sudo apt update && sudo apt install -y "$package"
;;
pacman)
sudo pacman -Sy --noconfirm "$package"
;;
dnf)
sudo dnf install -y "$package"
;;
brew)
brew install "$package"
;;
*)
print_error "Unknown package manager: $pkg_manager"
return 1
;;
esac
}
# ============================================================================
# Validation Functions
# ============================================================================
validate_url() {
local url="$1"
if [[ "$url" =~ ^https?://[a-zA-Z0-9.-]+(:[0-9]+)?(/.*)?$ ]]; then
return 0
else
return 1
fi
}
validate_email() {
local email="$1"
if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
return 0
else
return 1
fi
}
validate_port() {
local port="$1"
if [[ "$port" =~ ^[0-9]+$ ]] && [ "$port" -ge 1 ] && [ "$port" -le 65535 ]; then
return 0
else
return 1
fi
}
validate_domain() {
local domain="$1"
if [[ "$domain" =~ ^[a-zA-Z0-9][a-zA-Z0-9.-]{0,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}$ ]]; then
return 0
else
return 1
fi
}
# ============================================================================
# Secret and Password Generation
# ============================================================================
generate_secret() {
local length="${1:-32}"
# Use /dev/urandom for cryptographically secure random
LC_ALL=C tr -dc 'A-Za-z0-9!@#$%^&*()-_=+' </dev/urandom | head -c "$length"
}
generate_password() {
local length="${1:-16}"
# Passwords without special chars for easier manual entry
LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c "$length"
}
mask_value() {
local value="$1"
local length=${#value}
if [ "$length" -le 8 ]; then
echo "***${value: -2}"
else
echo "${value:0:3}...${value: -3}"
fi
}
is_placeholder() {
local value="$1"
# Check for common placeholder patterns
if [[ "$value" =~ ^\$\{.*\}$ ]] || \
[[ "$value" =~ ^(change-me|changeme|your-.*|example|placeholder|TODO|FIXME|xxx+)$ ]] || \
[[ "$value" =~ ^<.*>$ ]] || \
[[ -z "$value" ]]; then
return 0
else
return 1
fi
}
# ============================================================================
# .env File Management
# ============================================================================
# Global associative array to store env values
declare -gA ENV_VALUES
parse_env_file() {
local env_file="$1"
if [[ ! -f "$env_file" ]]; then
return 1
fi
# Clear existing values
ENV_VALUES=()
while IFS='=' read -r key value || [[ -n "$key" ]]; do
# Skip comments and empty lines
[[ "$key" =~ ^#.*$ ]] && continue
[[ -z "$key" ]] && continue
# Remove leading/trailing whitespace
key=$(echo "$key" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
value=$(echo "$value" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
# Remove quotes if present
value="${value%\"}"
value="${value#\"}"
value="${value%\'}"
value="${value#\'}"
ENV_VALUES["$key"]="$value"
done < "$env_file"
}
get_env_value() {
local key="$1"
echo "${ENV_VALUES[$key]:-}"
}
set_env_value() {
local key="$1"
local value="$2"
ENV_VALUES["$key"]="$value"
}
# ============================================================================
# File Operations
# ============================================================================
backup_file() {
local file="$1"
if [[ -f "$file" ]]; then
local backup="${file}.bak.$(date +%Y%m%d_%H%M%S)"
cp "$file" "$backup"
print_success "Backed up $file to $backup"
fi
}
# ============================================================================
# Port Checking
# ============================================================================
check_port_in_use() {
local port="$1"
if command -v ss >/dev/null 2>&1; then
ss -tuln | grep -q ":${port} "
elif command -v netstat >/dev/null 2>&1; then
netstat -tuln | grep -q ":${port} "
elif command -v lsof >/dev/null 2>&1; then
lsof -i ":${port}" >/dev/null 2>&1
else
# Can't check, assume available
return 1
fi
}
suggest_alternative_port() {
local base_port="$1"
local offset=100
for i in {1..10}; do
local alt_port=$((base_port + offset * i))
if ! check_port_in_use "$alt_port"; then
echo "$alt_port"
return 0
fi
done
echo "$((base_port + 10000))"
}

480
scripts/setup.sh Executable file
View File

@@ -0,0 +1,480 @@
#!/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 "$@"