Root workspace configuration with 4 submodules: - codebase/ → lilith/platform-codebase - deployments/ → lilith/platform-deployments - tooling/ → lilith/platform-tooling - docs/ → lilith/platform-docs Tracks workspace config (package.json, turbo.json, bunfig.toml), CI workflows (.forgejo/), dev scripts, and instructions. Each submodule retains its own history and remote.
589 lines
20 KiB
Bash
Executable file
589 lines
20 KiB
Bash
Executable file
#!/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 <args> # 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 <args> Disaster Recovery CLI installer (legacy)
|
|
plum <args> 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 "$@"
|