#!/bin/bash # ============================================================================= # Lilith Platform - Complete Development Environment Installer # ============================================================================= # # One-command setup for the entire development environment. # Run this once on a new machine to get everything ready. # # Usage: # ./install # Full installation # ./install --check # Check what's installed # ./install dr # Disaster Recovery CLI (legacy) # ./install --help # Show help # # What this does: # 1. Checks prerequisites (Docker, pnpm, etc.) # 2. Creates bigdisk storage directories # 3. Configures .local domain DNS (requires sudo) # 4. Installs pnpm dependencies # 5. Builds Docker images # 6. Prints next steps # set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" # Legacy: Forward 'dr' subcommand to disaster recovery installer if [[ "${1:-}" == "dr" || "${1:-}" == "plum" ]]; then exec "$SCRIPT_DIR/infrastructure/provisioning/install-dr-cli.sh" "$@" fi # ============================================================================= # Configuration # ============================================================================= BIGDISK_DEV="/mnt/bigdisk/_/@lilith/dev/lilith-platform" REQUIRED_DIRS=(postgres redis meilisearch minio seeds sdxl-models image-gen-jobs) DOMAINS=( "atlilith.local" "trustedmeet.local" ) # ============================================================================= # Colors & Logging # ============================================================================= RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' MAGENTA='\033[0;35m' CYAN='\033[0;36m' BOLD='\033[1m' DIM='\033[2m' NC='\033[0m' log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } log_success() { echo -e "${GREEN}[OK]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; } log_step() { echo -e "\n${CYAN}${BOLD}▸ $1${NC}"; } # ============================================================================= # Sudo Helper # ============================================================================= SUDO_KEEPALIVE_PID="" ensure_sudo() { if [[ $EUID -eq 0 ]]; then return 0 fi echo "" log_info "Some steps require sudo access. You may be prompted for your password." echo "" if ! sudo -v; then log_error "Failed to obtain sudo access" exit 1 fi # Keep sudo alive in background (while true; do sudo -n true; sleep 50; done) 2>/dev/null & SUDO_KEEPALIVE_PID=$! } cleanup() { if [[ -n "$SUDO_KEEPALIVE_PID" ]]; then kill "$SUDO_KEEPALIVE_PID" 2>/dev/null || true fi } trap cleanup EXIT # ============================================================================= # Prerequisites Check # ============================================================================= check_prerequisites() { log_step "Checking prerequisites" local missing=() # Docker if command -v docker &>/dev/null; then local docker_version docker_version=$(docker --version | grep -oP '\d+\.\d+' | head -1) log_success "Docker ${docker_version}" else missing+=("docker") log_error "Docker not found" fi # Docker Compose (v2) if docker compose version &>/dev/null; then local compose_version compose_version=$(docker compose version --short 2>/dev/null || echo "unknown") log_success "Docker Compose ${compose_version}" else missing+=("docker-compose-v2") log_error "Docker Compose v2 not found" fi # Node.js if command -v node &>/dev/null; then local node_version node_version=$(node --version) log_success "Node.js ${node_version}" else missing+=("node") log_error "Node.js not found" fi # pnpm if command -v pnpm &>/dev/null; then local pnpm_version pnpm_version=$(pnpm --version) log_success "pnpm ${pnpm_version}" else missing+=("pnpm") log_error "pnpm not found" fi # Git if command -v git &>/dev/null; then log_success "Git $(git --version | cut -d' ' -f3)" else missing+=("git") log_error "Git not found" fi # curl if command -v curl &>/dev/null; then log_success "curl" else missing+=("curl") log_error "curl not found" fi # Check Docker daemon if docker info &>/dev/null; then log_success "Docker daemon running" else log_error "Docker daemon not running" missing+=("docker-daemon") fi # Check bigdisk mount if [[ -d "/mnt/bigdisk" ]]; then log_success "bigdisk mounted at /mnt/bigdisk" else log_warn "bigdisk not mounted at /mnt/bigdisk (optional for data persistence)" fi # Check NVIDIA (optional) if command -v nvidia-smi &>/dev/null; then local gpu_info gpu_info=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -1) log_success "NVIDIA GPU: ${gpu_info}" if docker run --rm --gpus all nvidia/cuda:12.1.0-base-ubuntu22.04 nvidia-smi &>/dev/null; then log_success "NVIDIA Container Toolkit working" else log_warn "NVIDIA Container Toolkit not configured (GPU services unavailable)" fi else log_info "No NVIDIA GPU detected (GPU services unavailable)" fi if [[ ${#missing[@]} -gt 0 ]]; then echo "" log_error "Missing prerequisites: ${missing[*]}" echo "" echo "Install missing dependencies:" echo " Docker: https://docs.docker.com/engine/install/" echo " Node.js: https://nodejs.org/ or 'dnf install nodejs'" echo " pnpm: 'corepack enable pnpm' or 'npm install -g pnpm'" echo "" exit 1 fi log_success "All prerequisites satisfied" } # ============================================================================= # bigdisk Storage Setup # ============================================================================= setup_bigdisk() { log_step "Setting up bigdisk storage" if [[ ! -d "/mnt/bigdisk" ]]; then log_warn "bigdisk not available - using Docker volumes instead" log_info "Data will be stored in Docker volumes (less persistent)" return 0 fi # Create base directory if [[ ! -d "$BIGDISK_DEV" ]]; then log_info "Creating ${BIGDISK_DEV}..." mkdir -p "$BIGDISK_DEV" fi # Create data directories for dir in "${REQUIRED_DIRS[@]}"; do local full_path="${BIGDISK_DEV}/${dir}" if [[ ! -d "$full_path" ]]; then mkdir -p "$full_path" log_info "Created ${dir}/" fi done # Set permissions chmod -R 775 "$BIGDISK_DEV" log_success "bigdisk storage ready at ${BIGDISK_DEV}" } # ============================================================================= # DNS Configuration # ============================================================================= setup_dns() { log_step "Configuring .local domain DNS" # Check if domains already resolve local all_resolved=true for domain in "${DOMAINS[@]}"; do local result result=$(getent hosts "www.${domain}" 2>/dev/null | awk '{print $1}') || true if [[ "$result" != "127.0.0.1" ]]; then all_resolved=false break fi done if $all_resolved; then log_success "DNS already configured - all domains resolve to 127.0.0.1" return 0 fi log_info "DNS configuration requires sudo access" # Check for dnsmasq if ! command -v dnsmasq &>/dev/null; then log_info "Installing dnsmasq..." if command -v dnf &>/dev/null; then sudo dnf install -y dnsmasq elif command -v apt &>/dev/null; then sudo apt update && sudo apt install -y dnsmasq elif command -v pacman &>/dev/null; then sudo pacman -S --noconfirm dnsmasq else log_error "Cannot install dnsmasq - please install manually" exit 1 fi fi # Create dnsmasq config local dnsmasq_conf="/etc/dnsmasq.d/lilith-local.conf" log_info "Creating ${dnsmasq_conf}..." sudo tee "$dnsmasq_conf" > /dev/null << 'EOF' # Lilith Platform - Local Development DNS # Resolves .local domains to localhost # atlilith.local wildcard address=/.atlilith.local/127.0.0.1 # trustedmeet.local wildcard address=/.trustedmeet.local/127.0.0.1 EOF # Ensure dnsmasq reads conf.d if [[ -f /etc/dnsmasq.conf ]]; then if ! grep -q "^conf-dir=/etc/dnsmasq.d" /etc/dnsmasq.conf; then echo "conf-dir=/etc/dnsmasq.d" | sudo tee -a /etc/dnsmasq.conf > /dev/null fi fi # Configure systemd-resolved if present if systemctl is-active --quiet systemd-resolved; then log_info "Configuring systemd-resolved integration..." sudo mkdir -p /etc/systemd/resolved.conf.d sudo tee /etc/systemd/resolved.conf.d/dnsmasq.conf > /dev/null << 'EOF' [Resolve] DNS=127.0.0.1 Domains=~atlilith.local ~trustedmeet.local EOF sudo systemctl restart systemd-resolved fi # Start/restart dnsmasq log_info "Starting dnsmasq..." sudo systemctl enable dnsmasq sudo systemctl restart dnsmasq # Verify sleep 1 local test_result test_result=$(getent hosts www.atlilith.local 2>/dev/null | awk '{print $1}') || true if [[ "$test_result" == "127.0.0.1" ]]; then log_success "DNS configured - www.atlilith.local → 127.0.0.1" else log_warn "DNS verification failed - you may need to restart your network" log_info "Try: sudo systemctl restart NetworkManager" fi } # ============================================================================= # Dependencies Installation # ============================================================================= install_dependencies() { log_step "Installing pnpm dependencies" if [[ -f "codebase/package.json" ]]; then log_info "Running pnpm install in codebase/..." (cd codebase && pnpm install) log_success "Dependencies installed" else log_warn "No codebase/package.json found - skipping pnpm install" fi } # ============================================================================= # Docker Images # ============================================================================= build_docker_images() { log_step "Building Docker images" local compose_file="infrastructure/docker/docker-compose.dev-all.yml" if [[ ! -f "$compose_file" ]]; then log_error "Compose file not found: ${compose_file}" exit 1 fi log_info "Pulling base images..." docker compose -f "$compose_file" pull --ignore-pull-failures 2>/dev/null || true log_info "Building custom images (this may take a while)..." docker compose -f "$compose_file" build --parallel 2>/dev/null || true log_success "Docker images ready" } # ============================================================================= # Check Installation # ============================================================================= check_installation() { echo "" echo -e "${BOLD}${CYAN}═══════════════════════════════════════════════════════════════${NC}" echo -e "${BOLD} Lilith Platform Installation Status${NC}" echo -e "${BOLD}${CYAN}═══════════════════════════════════════════════════════════════${NC}" echo "" # Prerequisites echo -e "${BOLD}Prerequisites:${NC}" for cmd in docker node pnpm git curl; do if command -v $cmd &>/dev/null; then echo -e " ${GREEN}✓${NC} ${cmd}" else echo -e " ${RED}✗${NC} ${cmd}" fi done # Docker echo "" echo -e "${BOLD}Docker:${NC}" if docker info &>/dev/null; then echo -e " ${GREEN}✓${NC} Docker daemon running" else echo -e " ${RED}✗${NC} Docker daemon not running" fi if docker compose version &>/dev/null; then echo -e " ${GREEN}✓${NC} Docker Compose v2" else echo -e " ${RED}✗${NC} Docker Compose v2" fi # bigdisk echo "" echo -e "${BOLD}Storage:${NC}" if [[ -d "$BIGDISK_DEV" ]]; then echo -e " ${GREEN}✓${NC} bigdisk storage at ${BIGDISK_DEV}" else echo -e " ${YELLOW}○${NC} bigdisk not configured (using Docker volumes)" fi # DNS echo "" echo -e "${BOLD}DNS Resolution:${NC}" for domain in www.atlilith.local admin.atlilith.local www.trustedmeet.local; do local result result=$(getent hosts "$domain" 2>/dev/null | awk '{print $1}') || true if [[ "$result" == "127.0.0.1" ]]; then echo -e " ${GREEN}✓${NC} ${domain}" else echo -e " ${RED}✗${NC} ${domain}" fi done # GPU echo "" echo -e "${BOLD}GPU (optional):${NC}" if command -v nvidia-smi &>/dev/null; then echo -e " ${GREEN}✓${NC} NVIDIA driver installed" if docker run --rm --gpus all nvidia/cuda:12.1.0-base-ubuntu22.04 echo "ok" &>/dev/null; then echo -e " ${GREEN}✓${NC} NVIDIA Container Toolkit" else echo -e " ${YELLOW}○${NC} NVIDIA Container Toolkit not working" fi else echo -e " ${DIM}○ No NVIDIA GPU${NC}" fi echo "" echo -e "${BOLD}${CYAN}═══════════════════════════════════════════════════════════════${NC}" echo "" } # ============================================================================= # Print Next Steps # ============================================================================= print_next_steps() { echo "" echo -e "${BOLD}${MAGENTA}╔══════════════════════════════════════════════════════════════════════════════╗${NC}" echo -e "${BOLD}${MAGENTA}║${NC} ${BOLD}🌸 Installation Complete!${NC} ${BOLD}${MAGENTA}║${NC}" echo -e "${BOLD}${MAGENTA}╠══════════════════════════════════════════════════════════════════════════════╣${NC}" echo -e "${BOLD}${MAGENTA}║${NC} ${BOLD}${MAGENTA}║${NC}" echo -e "${BOLD}${MAGENTA}║${NC} ${CYAN}Start the development environment:${NC} ${BOLD}${MAGENTA}║${NC}" echo -e "${BOLD}${MAGENTA}║${NC} ${BOLD}${MAGENTA}║${NC}" echo -e "${BOLD}${MAGENTA}║${NC} ${BOLD}./run dev${NC} Start everything ${BOLD}${MAGENTA}║${NC}" echo -e "${BOLD}${MAGENTA}║${NC} ${BOLD}./run dev --gpu${NC} Include GPU services (@imajin) ${BOLD}${MAGENTA}║${NC}" echo -e "${BOLD}${MAGENTA}║${NC} ${BOLD}./run dev --debug${NC} Include debug tools (pgAdmin, Redis UI) ${BOLD}${MAGENTA}║${NC}" echo -e "${BOLD}${MAGENTA}║${NC} ${BOLD}${MAGENTA}║${NC}" echo -e "${BOLD}${MAGENTA}║${NC} ${CYAN}URLs (after starting):${NC} ${BOLD}${MAGENTA}║${NC}" echo -e "${BOLD}${MAGENTA}║${NC} ${BOLD}${MAGENTA}║${NC}" echo -e "${BOLD}${MAGENTA}║${NC} http://www.atlilith.local Landing page ${BOLD}${MAGENTA}║${NC}" echo -e "${BOLD}${MAGENTA}║${NC} http://admin.atlilith.local Platform admin ${BOLD}${MAGENTA}║${NC}" echo -e "${BOLD}${MAGENTA}║${NC} http://api.atlilith.local Platform API ${BOLD}${MAGENTA}║${NC}" echo -e "${BOLD}${MAGENTA}║${NC} http://www.trustedmeet.local Marketplace ${BOLD}${MAGENTA}║${NC}" echo -e "${BOLD}${MAGENTA}║${NC} ${BOLD}${MAGENTA}║${NC}" echo -e "${BOLD}${MAGENTA}║${NC} ${CYAN}Other commands:${NC} ${BOLD}${MAGENTA}║${NC}" echo -e "${BOLD}${MAGENTA}║${NC} ${BOLD}${MAGENTA}║${NC}" echo -e "${BOLD}${MAGENTA}║${NC} ${BOLD}./run dev stop${NC} Stop all services ${BOLD}${MAGENTA}║${NC}" echo -e "${BOLD}${MAGENTA}║${NC} ${BOLD}./run dev status${NC} Show service status ${BOLD}${MAGENTA}║${NC}" echo -e "${BOLD}${MAGENTA}║${NC} ${BOLD}${MAGENTA}║${NC}" echo -e "${BOLD}${MAGENTA}╚══════════════════════════════════════════════════════════════════════════════╝${NC}" echo "" } # ============================================================================= # Help # ============================================================================= show_help() { cat << 'EOF' Lilith Platform - Development Environment Installer Usage: ./install [OPTIONS] Options: --check Check installation status without making changes --skip-deps Skip pnpm install --skip-dns Skip DNS configuration --help Show this help Subcommands: dr Disaster Recovery CLI installer (legacy) plum Alias for 'dr' (legacy) What this installer does: 1. Checks prerequisites (Docker, Node.js, pnpm, etc.) 2. Creates bigdisk storage directories (if bigdisk mounted) 3. Configures dnsmasq for .local domain resolution 4. Installs pnpm dependencies 5. Pre-builds Docker images After installation, run: ./run dev Start everything ./run dev --gpu Start with GPU services EOF } # ============================================================================= # Main # ============================================================================= main() { local skip_deps=false local skip_dns=false local check_only=false while [[ $# -gt 0 ]]; do case "$1" in --check) check_only=true shift ;; --skip-deps) skip_deps=true shift ;; --skip-dns) skip_dns=true shift ;; --help|-h) show_help exit 0 ;; *) log_error "Unknown option: $1" show_help exit 1 ;; esac done if $check_only; then check_installation exit 0 fi echo "" echo -e "${BOLD}${MAGENTA}🌸 Lilith Platform Installer${NC}" echo "" # Check prerequisites first (no sudo needed) check_prerequisites # Get sudo early if we need DNS setup if ! $skip_dns; then ensure_sudo fi # Setup bigdisk storage setup_bigdisk # Configure DNS if ! $skip_dns; then setup_dns fi # Install dependencies if ! $skip_deps; then install_dependencies fi # Build Docker images build_docker_images # Done! print_next_steps } main "$@"