Files
stack/scripts/lib/docker.sh
Jason Woltje ab52827d9c
All checks were successful
ci/woodpecker/push/infra Pipeline was successful
ci/woodpecker/push/api Pipeline was successful
chore: add install scripts, doctor command, and AGENTS.md
- Add one-line installer (scripts/install.sh) with platform detection
- Add doctor command (scripts/commands/doctor.sh) for environment diagnostics
- Add shared libraries: dependencies, docker, platform, validation
- Update README with quick-start installer instructions
- Add AGENTS.md with codebase patterns for AI agent context

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 11:04:36 -06:00

492 lines
13 KiB
Bash

#!/bin/bash
# Docker-specific functions for Mosaic Stack installer
# Handles Docker Compose operations, health checks, and service management
# shellcheck source=lib/platform.sh
source "${BASH_SOURCE[0]%/*}/platform.sh"
# ============================================================================
# Docker Compose Helpers
# ============================================================================
# Get the docker compose command (handles both plugin and standalone)
docker_compose_cmd() {
if docker compose version &>/dev/null; then
echo "docker compose"
elif command -v docker-compose &>/dev/null; then
echo "docker-compose"
else
return 1
fi
}
# Run docker compose with all arguments
docker_compose() {
local cmd
cmd=$(docker_compose_cmd) || {
echo -e "${ERROR}Error: Docker Compose not available${NC}"
return 1
}
# shellcheck disable=SC2086
$cmd "$@"
}
# ============================================================================
# Service Management
# ============================================================================
# Pull all images defined in docker-compose.yml
docker_pull_images() {
local compose_file="${1:-docker-compose.yml}"
local env_file="${2:-.env}"
echo -e "${WARN}${NC} Pulling Docker images..."
if [[ -f "$env_file" ]]; then
docker_compose -f "$compose_file" --env-file "$env_file" pull
else
docker_compose -f "$compose_file" pull
fi
}
# Start services with Docker Compose
docker_compose_up() {
local compose_file="${1:-docker-compose.yml}"
local env_file="${2:-.env}"
local profiles="${3:-}"
local detached="${4:-true}"
echo -e "${WARN}${NC} Starting services..."
local args=("-f" "$compose_file")
if [[ -f "$env_file" ]]; then
args+=("--env-file" "$env_file")
fi
if [[ -n "$profiles" ]]; then
args+=("--profile" "$profiles")
fi
if [[ "$detached" == "true" ]]; then
args+=("up" "-d")
else
args+=("up")
fi
docker_compose "${args[@]}"
}
# Stop services
docker_compose_down() {
local compose_file="${1:-docker-compose.yml}"
local env_file="${2:-.env}"
echo -e "${WARN}${NC} Stopping services..."
if [[ -f "$env_file" ]]; then
docker_compose -f "$compose_file" --env-file "$env_file" down
else
docker_compose -f "$compose_file" down
fi
}
# Restart services
docker_compose_restart() {
local compose_file="${1:-docker-compose.yml}"
local env_file="${2:-.env}"
echo -e "${WARN}${NC} Restarting services..."
if [[ -f "$env_file" ]]; then
docker_compose -f "$compose_file" --env-file "$env_file" restart
else
docker_compose -f "$compose_file" restart
fi
}
# ============================================================================
# Health Checks
# ============================================================================
# Wait for a container to be healthy
wait_for_healthy_container() {
local container_name="$1"
local timeout="${2:-120}"
local interval="${3:-5}"
echo -e "${INFO}i${NC} Waiting for ${INFO}$container_name${NC} to be healthy..."
local elapsed=0
while [[ $elapsed -lt $timeout ]]; do
local status
status=$(docker inspect --format='{{.State.Health.Status}}' "$container_name" 2>/dev/null || echo "not_found")
case "$status" in
healthy)
echo -e "${SUCCESS}${NC} $container_name is healthy"
return 0
;;
unhealthy)
echo -e "${ERROR}${NC} $container_name is unhealthy"
return 1
;;
not_found)
echo -e "${WARN}${NC} Container $container_name not found"
return 1
;;
esac
sleep "$interval"
((elapsed += interval))
done
echo -e "${ERROR}${NC} Timeout waiting for $container_name to be healthy"
return 1
}
# Wait for multiple containers to be healthy
wait_for_healthy_containers() {
local containers=("$@")
local timeout="${containers[-1]}"
unset 'containers[-1]'
for container in "${containers[@]}"; do
if ! wait_for_healthy_container "$container" "$timeout"; then
return 1
fi
done
return 0
}
# Wait for a service to respond on a port
wait_for_service() {
local host="$1"
local port="$2"
local name="$3"
local timeout="${4:-60}"
echo -e "${INFO}i${NC} Waiting for ${INFO}$name${NC} at $host:$port..."
local elapsed=0
while [[ $elapsed -lt $timeout ]]; do
if docker run --rm --network host alpine:latest nc -z "$host" "$port" 2>/dev/null; then
echo -e "${SUCCESS}${NC} $name is responding"
return 0
fi
sleep 2
((elapsed += 2))
done
echo -e "${ERROR}${NC} Timeout waiting for $name"
return 1
}
# ============================================================================
# Container Status
# ============================================================================
# Get container status
get_container_status() {
local container_name="$1"
docker inspect --format='{{.State.Status}}' "$container_name" 2>/dev/null || echo "not_found"
}
# Check if container is running
is_container_running() {
local container_name="$1"
local status
status=$(get_container_status "$container_name")
[[ "$status" == "running" ]]
}
# List all Mosaic containers
list_mosaic_containers() {
docker ps -a --filter "name=mosaic-" --format "{{.Names}}\t{{.Status}}"
}
# Get container logs
get_container_logs() {
local container_name="$1"
local lines="${2:-100}"
docker logs --tail "$lines" "$container_name" 2>&1
}
# Tail container logs
tail_container_logs() {
local container_name="$1"
docker logs -f "$container_name"
}
# ============================================================================
# Database Operations
# ============================================================================
# Wait for PostgreSQL to be ready
wait_for_postgres() {
local container_name="${1:-mosaic-postgres}"
local user="${2:-mosaic}"
local database="${3:-mosaic}"
local timeout="${4:-60}"
echo -e "${INFO}i${NC} Waiting for PostgreSQL to be ready..."
local elapsed=0
while [[ $elapsed -lt $timeout ]]; do
if docker exec "$container_name" pg_isready -U "$user" -d "$database" &>/dev/null; then
echo -e "${SUCCESS}${NC} PostgreSQL is ready"
return 0
fi
sleep 2
((elapsed += 2))
done
echo -e "${ERROR}${NC} Timeout waiting for PostgreSQL"
return 1
}
# Run database migrations
run_database_migrations() {
local api_container="${1:-mosaic-api}"
echo -e "${WARN}${NC} Running database migrations..."
if ! docker exec "$api_container" npx prisma migrate deploy &>/dev/null; then
echo -e "${WARN}${NC} Could not run migrations via API container"
echo -e "${INFO}i${NC} Migrations will run automatically when API starts"
return 0
fi
echo -e "${SUCCESS}${NC} Database migrations complete"
}
# ============================================================================
# Service URLs
# ============================================================================
# Get the URL for a service
get_service_url() {
local service="$1"
local port="${2:-}"
local host="localhost"
# Check if we're in WSL and need to use Windows host
if is_wsl; then
host=$(cat /etc/resolv.conf 2>/dev/null | grep nameserver | awk '{print $2}' | head -1)
fi
if [[ -n "$port" ]]; then
echo "http://${host}:${port}"
else
echo "http://${host}"
fi
}
# Get all service URLs
get_all_service_urls() {
local env_file="${1:-.env}"
declare -A urls=()
if [[ -f "$env_file" ]]; then
# shellcheck source=/dev/null
source "$env_file"
fi
urls[web]="http://localhost:${WEB_PORT:-3000}"
urls[api]="http://localhost:${API_PORT:-3001}"
urls[postgres]="localhost:${POSTGRES_PORT:-5432}"
urls[valkey]="localhost:${VALKEY_PORT:-6379}"
if [[ "${OIDC_ENABLED:-false}" == "true" ]]; then
urls[authentik]="http://localhost:${AUTHENTIK_PORT_HTTP:-9000}"
fi
if [[ "${OLLAMA_MODE:-disabled}" != "disabled" ]]; then
urls[ollama]="http://localhost:${OLLAMA_PORT:-11434}"
fi
for service in "${!urls[@]}"; do
echo "$service: ${urls[$service]}"
done
}
# ============================================================================
# Docker Cleanup
# ============================================================================
# Remove unused Docker resources
docker_cleanup() {
echo -e "${WARN}${NC} Cleaning up unused Docker resources..."
# Remove dangling images
docker image prune -f
# Remove unused networks
docker network prune -f
echo -e "${SUCCESS}${NC} Docker cleanup complete"
}
# Remove all Mosaic containers and volumes
docker_remove_all() {
local compose_file="${1:-docker-compose.yml}"
local env_file="${2:-.env}"
echo -e "${WARN}${NC} Removing all Mosaic containers and volumes..."
if [[ -f "$env_file" ]]; then
docker_compose -f "$compose_file" --env-file "$env_file" down -v --remove-orphans
else
docker_compose -f "$compose_file" down -v --remove-orphans
fi
echo -e "${SUCCESS}${NC} All containers and volumes removed"
}
# ============================================================================
# Docker Info
# ============================================================================
# Print Docker system info
print_docker_info() {
echo -e "${BOLD}Docker Information:${NC}"
echo ""
echo -e " Docker Version:"
docker --version 2>/dev/null | sed 's/^/ /'
echo ""
echo -e " Docker Compose:"
docker_compose version 2>/dev/null | sed 's/^/ /'
echo ""
echo -e " Docker Storage:"
docker system df 2>/dev/null | sed 's/^/ /'
echo ""
echo -e " Running Containers:"
docker ps --format " {{.Names}}\t{{.Status}}" 2>/dev/null | head -10
}
# ============================================================================
# Volume Management
# ============================================================================
# List all Mosaic volumes
list_mosaic_volumes() {
docker volume ls --filter "name=mosaic" --format "{{.Name}}"
}
# Backup a Docker volume
backup_volume() {
local volume_name="$1"
local backup_file="${2:-${volume_name}-backup-$(date +%Y%m%d-%H%M%S).tar.gz}"
echo -e "${WARN}${NC} Backing up volume ${INFO}$volume_name${NC}..."
docker run --rm \
-v "$volume_name":/source:ro \
-v "$(pwd)":/backup \
alpine:latest \
tar czf "/backup/$backup_file" -C /source .
echo -e "${SUCCESS}${NC} Backup created: $backup_file"
}
# Restore a Docker volume
restore_volume() {
local volume_name="$1"
local backup_file="$2"
echo -e "${WARN}${NC} Restoring volume ${INFO}$volume_name${NC} from $backup_file..."
# Create volume if it doesn't exist
docker volume create "$volume_name" &>/dev/null || true
docker run --rm \
-v "$volume_name":/target \
-v "$(pwd)":/backup \
alpine:latest \
tar xzf "/backup/$backup_file" -C /target
echo -e "${SUCCESS}${NC} Volume restored"
}
# ============================================================================
# Network Management
# ============================================================================
# Create a Docker network if it doesn't exist
ensure_network() {
local network_name="$1"
if ! docker network inspect "$network_name" &>/dev/null; then
echo -e "${WARN}${NC} Creating network ${INFO}$network_name${NC}..."
docker network create "$network_name"
echo -e "${SUCCESS}${NC} Network created"
fi
}
# Check if a network exists
network_exists() {
local network_name="$1"
docker network inspect "$network_name" &>/dev/null
}
# ============================================================================
# Build Operations
# ============================================================================
# Build Docker images
docker_build() {
local compose_file="${1:-docker-compose.yml}"
local env_file="${2:-.env}"
local parallel="${3:-true}"
echo -e "${WARN}${NC} Building Docker images..."
local args=("-f" "$compose_file")
if [[ -f "$env_file" ]]; then
args+=("--env-file" "$env_file")
fi
args+=("build")
if [[ "$parallel" == "true" ]]; then
args+=("--parallel")
fi
docker_compose "${args[@]}"
}
# Check if buildx is available
check_buildx() {
docker buildx version &>/dev/null
}
# Set up buildx builder
setup_buildx() {
if ! check_buildx; then
echo -e "${WARN}${NC} buildx not available"
return 1
fi
# Create or use existing builder
if ! docker buildx inspect mosaic-builder &>/dev/null; then
echo -e "${WARN}${NC} Creating buildx builder..."
docker buildx create --name mosaic-builder --use
else
docker buildx use mosaic-builder
fi
}