#!/bin/bash # CI/CD Setup Script for UnitForge # Sets up local environment for testing CI/CD workflows set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Helper functions log_info() { echo -e "${GREEN}[INFO]${NC} $1" } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } log_step() { echo -e "${BLUE}[STEP]${NC} $1" } # Check if command exists command_exists() { command -v "$1" >/dev/null 2>&1 } # Check Docker and Docker Buildx check_docker() { log_step "Checking Docker installation..." if ! command_exists docker; then log_error "Docker is not installed" echo "Please install Docker from: https://docs.docker.com/get-docker/" return 1 fi log_info "Docker found: $(docker --version)" # Check if Docker daemon is running if ! docker info >/dev/null 2>&1; then log_error "Docker daemon is not running" echo "Please start Docker daemon" return 1 fi # Check Docker Buildx if ! docker buildx version >/dev/null 2>&1; then log_warn "Docker Buildx not found, installing..." docker buildx install 2>/dev/null || true fi log_info "Docker Buildx found: $(docker buildx version)" return 0 } # Setup Docker Buildx for multi-arch builds setup_buildx() { log_step "Setting up Docker Buildx for multi-arch builds..." # Create builder if it doesn't exist if ! docker buildx ls | grep -q "unitforge-builder"; then log_info "Creating unitforge-builder..." docker buildx create --name unitforge-builder --use else log_info "Using existing unitforge-builder" docker buildx use unitforge-builder fi # Bootstrap the builder log_info "Bootstrapping builder..." docker buildx inspect --bootstrap log_info "Builder setup complete" return 0 } # Check container registry access check_registry() { log_step "Checking container registry configuration..." if [ -f ".env" ]; then log_info "Found .env file" if grep -q "CONTAINER_REGISTRY_URL" .env; then local registry_url registry_url=$(grep '^CONTAINER_REGISTRY_URL=' .env | cut -d'=' -f2) log_info "Registry URL: $registry_url" else log_warn "CONTAINER_REGISTRY_URL not found in .env" fi if grep -q "CONTAINER_TAG" .env; then local container_tag container_tag=$(grep '^CONTAINER_TAG=' .env | cut -d'=' -f2) log_info "Container tag: $container_tag" else log_warn "CONTAINER_TAG not found in .env" fi else log_warn ".env file not found" log_info "Creating sample .env file..." cat > .env << EOF # Container Registry Configuration CONTAINER_REGISTRY_URL=gitea-http.taildb3494.ts.net/will/unitforge CONTAINER_TAG=latest # Development Configuration DEBUG=true LOG_LEVEL=debug EOF log_info "Sample .env file created. Please update with your registry details." fi return 0 } # Verify vendor assets check_vendor_assets() { log_step "Checking vendor assets..." local assets=( "frontend/static/vendor/bootstrap/css/bootstrap.min.css" "frontend/static/vendor/bootstrap/js/bootstrap.bundle.min.js" "frontend/static/vendor/fontawesome/css/all.min.css" "frontend/static/vendor/fontawesome/webfonts/fa-solid-900.woff2" "frontend/static/img/osi-logo.svg" ) local missing_assets=0 for asset in "${assets[@]}"; do if [ ! -f "$asset" ]; then log_warn "Missing asset: $asset" missing_assets=$((missing_assets + 1)) else log_info "Found asset: $asset" fi done if [ $missing_assets -gt 0 ]; then log_error "$missing_assets vendor assets are missing" log_info "Please ensure all vendor assets are downloaded and committed" log_info "Run: make setup-dev to download missing assets" return 1 fi log_info "All vendor assets found" return 0 } # Test local build test_local_build() { log_step "Testing local Docker build..." if docker build -t unitforge:test . >/dev/null 2>&1; then log_info "Local Docker build successful" # Test container startup log_info "Testing container startup..." if docker run -d --name unitforge-test -p 8080:8000 unitforge:test >/dev/null 2>&1; then sleep 5 if curl -s -f http://localhost:8080/ >/dev/null 2>&1; then log_info "Container startup test successful" else log_warn "Container started but not responding on port 8080" fi docker stop unitforge-test >/dev/null 2>&1 docker rm unitforge-test >/dev/null 2>&1 else log_warn "Container startup test failed" fi # Clean up test image docker rmi unitforge:test >/dev/null 2>&1 || true return 0 else log_error "Local Docker build failed" return 1 fi } # Test multi-arch build test_multiarch_build() { log_step "Testing multi-architecture build..." if docker buildx build --platform linux/amd64,linux/arm64 -t unitforge:multiarch-test . >/dev/null 2>&1; then log_info "Multi-architecture build successful" # Clean up docker buildx rm --force >/dev/null 2>&1 || true return 0 else log_error "Multi-architecture build failed" return 1 fi } # Check development environment check_dev_environment() { log_step "Checking development environment..." # Check uv if ! command_exists uv; then log_error "uv is not installed" echo "Install with: curl -LsSf https://astral.sh/uv/install.sh | sh" return 1 fi log_info "uv found: $(uv --version)" # Check Python if ! command_exists python3; then log_error "Python 3 is not installed" return 1 fi log_info "Python found: $(python3 --version)" # Check if virtual environment exists if [ -d ".venv" ]; then log_info "Virtual environment exists" else log_warn "Virtual environment not found" log_info "Run: make setup-dev to create it" fi return 0 } # Test CI/CD workflow syntax check_workflow_syntax() { log_step "Checking workflow syntax..." local workflows_dir=".gitea/workflows" if [ ! -d "$workflows_dir" ]; then log_error "Workflows directory not found: $workflows_dir" return 1 fi local yaml_files=("$workflows_dir"/*.yml) if [ ${#yaml_files[@]} -eq 0 ]; then log_warn "No YAML workflow files found" return 0 fi local syntax_errors=0 for file in "${yaml_files[@]}"; do if [ -f "$file" ]; then log_info "Checking syntax: $(basename "$file")" # Basic YAML syntax check (if python3 is available) if command_exists python3; then if python3 -c "import yaml; yaml.safe_load(open('$file'))" 2>/dev/null; then log_info "✓ $(basename "$file") syntax OK" else log_error "✗ $(basename "$file") has syntax errors" syntax_errors=$((syntax_errors + 1)) fi else log_warn "Python3 not available, skipping YAML syntax check" fi fi done if [ $syntax_errors -eq 0 ]; then log_info "All workflow files have valid syntax" return 0 else log_error "$syntax_errors workflow files have syntax errors" return 1 fi } # Generate CI/CD documentation generate_docs() { log_step "Generating CI/CD documentation..." local docs_dir="docs/ci-cd" mkdir -p "$docs_dir" cat > "$docs_dir/local-testing.md" << 'EOF' # Local CI/CD Testing This guide helps you test CI/CD workflows locally before pushing to the repository. ## Prerequisites - Docker with Buildx support - uv package manager - Python 3.8+ ## Local Testing Commands ```bash # Test local build make docker-build # Test multi-arch build make docker-buildx-local # Test full development workflow make dev # Run health checks ./scripts/health_check.sh ``` ## Workflow Testing Use `act` to test GitHub/Gitea workflows locally: ```bash # Install act curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash # Test PR workflow act pull_request -s CONTAINER_REGISTRY_USERNAME=test -s CONTAINER_REGISTRY_PASSWORD=test # Test release workflow act push -e tests/fixtures/release-event.json ``` ## Troubleshooting ### Build Issues - Ensure all vendor assets are committed - Check Docker daemon is running - Verify buildx is properly configured ### Registry Issues - Check .env file configuration - Verify registry credentials - Test registry connectivity ### Performance Issues - Use build cache: `--cache-from type=gha` - Optimize Docker layers - Use multi-stage builds ``` EOF log_info "Local testing documentation generated: $docs_dir/local-testing.md" return 0 } # Main setup function main() { echo -e "${BLUE}========================================${NC}" echo -e "${BLUE} UnitForge CI/CD Setup${NC}" echo -e "${BLUE}========================================${NC}" echo "" local failed_checks=0 # Run all checks check_docker || failed_checks=$((failed_checks + 1)) setup_buildx || failed_checks=$((failed_checks + 1)) check_registry || true # Don't fail on registry issues check_vendor_assets || failed_checks=$((failed_checks + 1)) check_dev_environment || failed_checks=$((failed_checks + 1)) check_workflow_syntax || failed_checks=$((failed_checks + 1)) # Optional tests if [ "$1" = "--test-build" ]; then test_local_build || failed_checks=$((failed_checks + 1)) test_multiarch_build || failed_checks=$((failed_checks + 1)) fi # Generate documentation generate_docs || true echo "" echo -e "${BLUE}========================================${NC}" if [ $failed_checks -eq 0 ]; then log_info "✅ CI/CD setup completed successfully!" echo "" echo "Next steps:" echo "1. Update .env with your registry details" echo "2. Test local build: make docker-buildx-local" echo "3. Run full test suite: make dev" echo "4. Check workflow syntax: ./scripts/setup-ci.sh" echo "" echo "For testing builds:" echo " ./scripts/setup-ci.sh --test-build" else log_error "❌ CI/CD setup completed with $failed_checks issues" echo "" echo "Please fix the issues above before proceeding." exit 1 fi echo -e "${BLUE}========================================${NC}" } # Usage information usage() { echo "Usage: $0 [OPTIONS]" echo "" echo "Options:" echo " --test-build Run local and multi-arch build tests" echo " --help Show this help message" echo "" echo "This script sets up your local environment for CI/CD development." echo "It checks Docker, Buildx, dependencies, and workflow syntax." } # Parse command line arguments case "${1:-}" in --help) usage exit 0 ;; --test-build) main --test-build ;; "") main ;; *) echo "Unknown option: $1" usage exit 1 ;; esac