#!/bin/bash # Colores para la salida RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' NC='\033[0m' # No Color # Funciones de mensajes error() { echo -e "${RED}Error: $1${NC}" >&2; } success() { echo -e "${GREEN}$1${NC}"; } info() { echo -e "${BLUE}$1${NC}"; } warning() { echo -e "${YELLOW}$1${NC}" >&2; } process() { echo -e "${CYAN}$1${NC}"; } # Variables globales DOCKER_DIR="docker" PROJECT_NAME="" FRAMEWORK="" PHP_VERSION="" WEB_PORT="" DB_PORT="" CURRENT_DIR=$(pwd) # Función para verificar y corregir permisos check_and_fix_permissions() { local target_dir="$1" # Verificar si el directorio existe if [ ! -d "$target_dir" ]; then return 0 fi process "Verificando permisos de $target_dir..." # Verificar si tenemos permisos de escritura if [ ! -w "$target_dir" ]; then warning "No tenemos permisos de escritura en $target_dir" process "Intentando corregir permisos..." # Intentar con sudo si está disponible if command -v sudo &> /dev/null; then if sudo chmod 755 "$target_dir" && sudo chown "$USER:$USER" "$target_dir" 2>/dev/null; then success "Permisos corregidos con sudo" return 0 fi fi # Intentar sin sudo if chmod 755 "$target_dir" 2>/dev/null; then success "Permisos corregidos" else error "No se pueden corregir los permisos automáticamente" info "Por favor, ejecuta manualmente:" echo " sudo chmod 755 \"$target_dir\"" echo " sudo chown \$USER:\$USER \"$target_dir\"" return 1 fi else success "Permisos correctos en $target_dir" fi return 0 } # Función para limpiar directorio de forma segura safe_clean_directory() { local dir_path="$1" if [ ! -d "$dir_path" ]; then return 0 fi process "Limpiando directorio $dir_path..." # Verificar permisos primero if ! check_and_fix_permissions "$dir_path"; then return 1 fi # Intentar eliminar contenido del directorio if [ -w "$dir_path" ]; then # Usar find para eliminar contenido de forma segura if find "$dir_path" -mindepth 1 -maxdepth 1 -exec rm -rf {} + 2>/dev/null; then success "Directorio limpiado correctamente" return 0 else # Si falla, intentar con sudo if command -v sudo &> /dev/null; then if sudo rm -rf "${dir_path:?}/"* 2>/dev/null; then success "Directorio limpiado con sudo" return 0 fi fi fi fi error "No se pudo limpiar el directorio $dir_path" return 1 } # Verificar instalación de Docker y Docker Compose check_docker_installation() { process "Verificando instalación de Docker..." if ! command -v docker &> /dev/null; then error "Docker no está instalado. Instala Docker primero." echo "Visita: https://docs.docker.com/get-docker/" exit 1 fi success "Docker está instalado." process "Verificando Docker Compose..." if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then error "Docker Compose no está instalado." echo "Visita: https://docs.docker.com/compose/install/" exit 1 fi success "Docker Compose está instalado." } # Verificar permisos de Docker check_docker_permissions() { process "Verificando permisos de Docker..." if ! docker info &> /dev/null; then error "No tienes permisos para acceder a Docker." warning "Solución:" echo " sudo usermod -aG docker \$USER" echo " newgrp docker" exit 1 fi success "Permisos de Docker verificados." } # Verificar conexión a internet check_internet_connection() { process "Verificando conexión a internet..." if ! curl -s --connect-timeout 10 https://packagist.org > /dev/null; then error "No hay conexión a internet o Packagist no está disponible" warning "El script requiere conexión a internet para descargar dependencias" read -p "¿Continuar de todos modos? (y/N): " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1 fi else success "Conexión a internet verificada" fi } # Encontrar puertos disponibles find_available_ports() { local web_port=8081 local db_port=5433 # Encontrar puerto web disponible while ss -tuln 2>/dev/null | grep -q ":${web_port} "; do web_port=$((web_port + 1)) done # Encontrar puerto DB disponible (empezando desde 5433) while ss -tuln 2>/dev/null | grep -q ":${db_port} "; do db_port=$((db_port + 1)) done WEB_PORT="$web_port" DB_PORT="$db_port" success "Puertos disponibles encontrados: Web → ${WEB_PORT}, DB → ${DB_PORT}" } # Seleccionar framework CON VERSIONES CORRECTAS select_framework() { echo "" info "🎯 SELECCIÓN DE FRAMEWORK" echo "=========================" echo "" echo " 1) Yii2 Basic - Framework tradicional (PHP 7.4)" echo " 2) Yii2 Advanced - Framework avanzado (PHP 7.4)" echo " 3) Yii3 - Framework moderno (PHP 8.4+)" echo " 4) Laravel - Framework elegante (PHP 8.3+)" echo "" while true; do read -p "Selecciona el framework (1-4): " choice case $choice in 1) FRAMEWORK="yii2-basic" PHP_VERSION="7.4" success "Seleccionado: Yii2 Basic con PHP 7.4" break ;; 2) FRAMEWORK="yii2-advanced" PHP_VERSION="7.4" success "Seleccionado: Yii2 Advanced con PHP 7.4" break ;; 3) FRAMEWORK="yii3" PHP_VERSION="8.4" success "Seleccionado: Yii3 con PHP 8.4" break ;; 4) FRAMEWORK="laravel" PHP_VERSION="8.3" success "Seleccionado: Laravel con PHP 8.3" break ;; *) error "Opción inválida. Por favor, selecciona 1, 2, 3 o 4" ;; esac done } # Validar nombre del proyecto get_project_name() { echo "" info "📝 NOMBRE DEL PROYECTO" echo "======================" echo "Elige un nombre para tu proyecto:" echo " - Debe comenzar con una letra" echo " - Puede contener letras, números, guiones y guiones bajos" echo "" while true; do read -p "Ingresa el nombre del proyecto: " project_name if [ -z "$project_name" ]; then error "El nombre no puede estar vacío." continue fi if [[ ! "$project_name" =~ ^[a-zA-Z][a-zA-Z0-9_-]*$ ]]; then error "El nombre debe comenzar con una letra y solo puede contener letras, números, guiones y guiones bajos." continue fi PROJECT_NAME="$project_name" success "Nombre del proyecto validado: $PROJECT_NAME" break done } # Crear estructura de directorios MEJORADA con gestión de permisos create_directory_structure() { process "Creando estructura de directorios en '$DOCKER_DIR'..." # Verificar y corregir permisos del directorio docker si existe if [ -d "$DOCKER_DIR" ]; then warning "El directorio $DOCKER_DIR ya existe. Verificando permisos..." if ! check_and_fix_permissions "$DOCKER_DIR"; then error "No se pueden corregir los permisos de $DOCKER_DIR" return 1 fi # Verificar y corregir permisos de subdirectorios if [ -d "$DOCKER_DIR/src" ]; then if ! check_and_fix_permissions "$DOCKER_DIR/src"; then error "No se pueden corregir los permisos de $DOCKER_DIR/src" return 1 fi # Limpiar el directorio src de forma segura if ! safe_clean_directory "$DOCKER_DIR/src"; then error "No se pudo limpiar el directorio $DOCKER_DIR/src" return 1 fi else # Crear directorio src con permisos correctos if ! mkdir -p "$DOCKER_DIR/src"; then error "No se pudo crear el directorio $DOCKER_DIR/src" return 1 fi chmod 755 "$DOCKER_DIR/src" fi else # Crear toda la estructura desde cero if ! mkdir -p "$DOCKER_DIR/src" "$DOCKER_DIR/apache-logs" "$DOCKER_DIR/postgres-backups"; then error "No se pudo crear la estructura de directorios" return 1 fi # Establecer permisos correctos chmod 755 "$DOCKER_DIR" "$DOCKER_DIR/src" "$DOCKER_DIR/apache-logs" "$DOCKER_DIR/postgres-backups" success "Estructura de directorios creada en: $DOCKER_DIR/" fi # Crear subcarpeta con nombre del proyecto dentro de src process "Creando directorio del proyecto: $DOCKER_DIR/src/$PROJECT_NAME" if ! mkdir -p "$DOCKER_DIR/src/$PROJECT_NAME"; then error "No se pudo crear la subcarpeta del proyecto" return 1 fi # Establecer permisos correctos para la carpeta del proyecto if ! chmod 755 "$DOCKER_DIR/src/$PROJECT_NAME"; then warning "No se pudieron establecer los permisos automáticamente en $DOCKER_DIR/src/$PROJECT_NAME" info "Puedes establecerlos manualmente después:" echo " chmod 755 \"$DOCKER_DIR/src/$PROJECT_NAME\"" fi success "Estructura de directorios creada: $DOCKER_DIR/src/$PROJECT_NAME/" return 0 } # Crear archivo .env create_env_file() { process "Creando archivo de configuración .env..." find_available_ports cat > "$DOCKER_DIR/.env" << EOF # Configuración de la aplicación APP_NAME=$PROJECT_NAME APP_ENV=local APP_DEBUG=true APP_URL=http://localhost:${WEB_PORT} # Configuración de base de datos DB_CONNECTION=pgsql DB_HOST=db DB_PORT=5432 # Puerto INTERNO del contenedor (SIEMPRE 5432) DB_DATABASE=${PROJECT_NAME}_db DB_USERNAME=postgres DB_PASSWORD=password # Configuración de PostgreSQL POSTGRES_DB=${PROJECT_NAME}_db POSTGRES_USER=postgres POSTGRES_PASSWORD=password # Configuración de Docker PHP_VERSION=${PHP_VERSION} WEB_PORT=${WEB_PORT} DB_EXTERNAL_PORT=${DB_PORT} # Puerto EXPUESTO en el host # Configuración específica del framework FRAMEWORK=${FRAMEWORK} PROJECT_NAME=${PROJECT_NAME} EOF # Verificar que el archivo se creó y establecer permisos if [ ! -f "$DOCKER_DIR/.env" ]; then error "No se pudo crear el archivo .env" return 1 fi chmod 644 "$DOCKER_DIR/.env" success "Archivo .env creado con puertos: Web → ${WEB_PORT}, DB Host → ${DB_PORT}" return 0 } # Crear configuración de Apache create_apache_config() { process "Creando configuración de Apache..." # Configuración diferente para Yii2 Advanced if [ "$FRAMEWORK" = "yii2-advanced" ]; then cat > "$DOCKER_DIR/000-default.conf" << EOF <VirtualHost *:80> ServerAdmin webmaster@localhost DocumentRoot /var/www/html/frontend/web <Directory /var/www/html/frontend/web> AllowOverride All Require all granted Options Indexes FollowSymLinks </Directory> ErrorLog \${APACHE_LOG_DIR}/error.log CustomLog \${APACHE_LOG_DIR}/access.log combined </VirtualHost> EOF else cat > "$DOCKER_DIR/000-default.conf" << EOF <VirtualHost *:80> ServerAdmin webmaster@localhost DocumentRoot /var/www/html/public <Directory /var/www/html> AllowOverride All Require all granted Options Indexes FollowSymLinks </Directory> ErrorLog \${APACHE_LOG_DIR}/error.log CustomLog \${APACHE_LOG_DIR}/access.log combined </VirtualHost> EOF fi if [ ! -f "$DOCKER_DIR/000-default.conf" ]; then error "No se pudo crear la configuración de Apache" return 1 fi chmod 644 "$DOCKER_DIR/000-default.conf" success "Configuración de Apache creada" return 0 } # Crear Dockerfile específico para cada framework - CORREGIDO create_dockerfile() { process "Creando Dockerfile para PHP $PHP_VERSION..." case "$FRAMEWORK" in "yii2-basic"|"yii2-advanced") create_yii2_dockerfile ;; "yii3") create_yii3_dockerfile ;; "laravel") create_laravel_dockerfile ;; esac if [ ! -f "$DOCKER_DIR/Dockerfile" ]; then error "No se pudo crear el Dockerfile" return 1 fi chmod 644 "$DOCKER_DIR/Dockerfile" return 0 } create_yii2_dockerfile() { cat > "$DOCKER_DIR/Dockerfile" << 'EOF' ARG PHP_VERSION=7.4 FROM php:${PHP_VERSION}-apache # Actualizar lista de paquetes y instalar dependencias RUN apt-get update && apt-get install -y \ libpq-dev \ libzip-dev \ libpng-dev \ libjpeg-dev \ libfreetype6-dev \ zip \ unzip \ git \ curl \ gnupg \ ca-certificates \ && rm -rf /var/lib/apt/lists/* # Configurar extensiones de PHP para PHP 7.4 - CONFIGURACIÓN CORREGIDA RUN docker-php-ext-configure gd --with-freetype --with-jpeg \ && docker-php-ext-install -j$(nproc) \ pdo \ pdo_pgsql \ pgsql \ zip \ gd \ bcmath \ opcache \ && a2enmod rewrite # Instalar Composer RUN curl -sS https://getcomposer.org/installer | php -- \ --install-dir=/usr/local/bin \ --filename=composer # Configurar Composer para evitar timeouts RUN composer config -g process-timeout 1200 && \ composer config -g repo.packagist composer https://packagist.org # Instalar extensiones adicionales necesarias para Yii2 RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli COPY 000-default.conf /etc/apache2/sites-available/000-default.conf RUN a2ensite 000-default.conf RUN chown -R www-data:www-data /var/www/html WORKDIR /var/www/html # Configuraciones PHP optimizadas para PHP 7.4 RUN echo "memory_limit = 512M" > /usr/local/etc/php/conf.d/memory.ini && \ echo "upload_max_filesize = 64M" >> /usr/local/etc/php/conf.d/uploads.ini && \ echo "post_max_size = 64M" >> /usr/local/etc/php/conf.d/uploads.ini && \ echo "max_execution_time = 300" >> /usr/local/etc/php/conf.d/timeouts.ini && \ echo "max_input_time = 120" >> /usr/local/etc/php/conf.d/timeouts.ini EXPOSE 80 EOF success "Dockerfile para Yii2 creado" } create_yii3_dockerfile() { cat > "$DOCKER_DIR/Dockerfile" << 'EOF' ARG PHP_VERSION=8.4 FROM php:${PHP_VERSION}-apache # Actualizar lista de paquetes y instalar dependencias RUN apt-get update && apt-get install -y \ libpq-dev \ libzip-dev \ libpng-dev \ libjpeg-dev \ libfreetype6-dev \ zip \ unzip \ git \ curl \ gnupg \ ca-certificates \ && rm -rf /var/lib/apt/lists/* # Configurar extensiones de PHP - CONFIGURACIÓN CORREGIDA RUN docker-php-ext-configure gd --with-freetype --with-jpeg \ && docker-php-ext-install -j$(nproc) \ pdo \ pdo_pgsql \ pgsql \ zip \ gd \ bcmath \ opcache \ && a2enmod rewrite # Instalar Composer RUN curl -sS https://getcomposer.org/installer | php -- \ --install-dir=/usr/local/bin \ --filename=composer # Configurar Composer para evitar timeouts RUN composer config -g process-timeout 1200 && \ composer config -g repo.packagist composer https://packagist.org COPY 000-default.conf /etc/apache2/sites-available/000-default.conf RUN a2ensite 000-default.conf RUN chown -R www-data:www-data /var/www/html WORKDIR /var/www/html # Configuraciones PHP optimizadas RUN echo "memory_limit = 512M" > /usr/local/etc/php/conf.d/memory.ini && \ echo "upload_max_filesize = 128M" >> /usr/local/etc/php/conf.d/uploads.ini && \ echo "post_max_size = 128M" >> /usr/local/etc/php/conf.d/uploads.ini && \ echo "max_execution_time = 300" >> /usr/local/etc/php/conf.d/timeouts.ini && \ echo "max_input_time = 120" >> /usr/local/etc/php/conf.d/timeouts.ini && \ echo "opcache.enable=1" >> /usr/local/etc/php/conf.d/opcache.ini && \ echo "opcache.memory_consumption=256" >> /usr/local/etc/php/conf.d/opcache.ini EXPOSE 80 EOF success "Dockerfile para Yii3 creado" } create_laravel_dockerfile() { cat > "$DOCKER_DIR/Dockerfile" << 'EOF' ARG PHP_VERSION=8.3 FROM php:${PHP_VERSION}-apache # Actualizar lista de paquetes y instalar dependencias RUN apt-get update && apt-get install -y \ libpq-dev \ libzip-dev \ libpng-dev \ libjpeg-dev \ libfreetype6-dev \ zip \ unzip \ git \ curl \ gnupg \ ca-certificates \ && rm -rf /var/lib/apt/lists/* # Configurar extensiones de PHP - CONFIGURACIÓN CORREGIDA RUN docker-php-ext-configure gd --with-freetype --with-jpeg \ && docker-php-ext-install -j$(nproc) \ pdo \ pdo_pgsql \ pgsql \ zip \ gd \ bcmath \ opcache \ && a2enmod rewrite # Instalar Composer RUN curl -sS https://getcomposer.org/installer | php -- \ --install-dir=/usr/local/bin \ --filename=composer # Configurar Composer para evitar timeouts RUN composer config -g process-timeout 1200 && \ composer config -g repo.packagist composer https://packagist.org # Extensiones adicionales para Laravel RUN docker-php-ext-install exif && docker-php-ext-enable exif COPY 000-default.conf /etc/apache2/sites-available/000-default.conf RUN a2ensite 000-default.conf RUN chown -R www-data:www-data /var/www/html WORKDIR /var/www/html # Configuraciones PHP optimizadas RUN echo "memory_limit = 512M" > /usr/local/etc/php/conf.d/memory.ini && \ echo "upload_max_filesize = 128M" >> /usr/local/etc/php/conf.d/uploads.ini && \ echo "post_max_size = 128M" >> /usr/local/etc/php/conf.d/uploads.ini && \ echo "max_execution_time = 300" >> /usr/local/etc/php/conf.d/timeouts.ini && \ echo "max_input_time = 120" >> /usr/local/etc/php/conf.d/timeouts.ini && \ echo "opcache.enable=1" >> /usr/local/etc/php/conf.d/opcache.ini EXPOSE 80 EOF success "Dockerfile para Laravel creado" } # Crear docker-compose.yml MEJORADO create_docker_compose() { process "Creando docker-compose.yml..." # Configurar volumen según el tipo de proyecto local volume_config="./src/${PROJECT_NAME}:/var/www/html" cat > "$DOCKER_DIR/docker-compose.yml" << EOF version: '3.8' services: web: build: context: . dockerfile: Dockerfile args: PHP_VERSION: \${PHP_VERSION} ports: - "\${WEB_PORT}:80" volumes: - ${volume_config} - ./apache-logs:/var/log/apache2 depends_on: db: condition: service_healthy networks: - backend-network env_file: - .env container_name: ${PROJECT_NAME}-web restart: unless-stopped dns: - 8.8.8.8 - 8.8.4.4 healthcheck: test: ["CMD", "curl", "-f", "http://localhost:80"] interval: 30s timeout: 10s retries: 3 start_period: 40s db: image: postgres:15 environment: POSTGRES_DB: \${POSTGRES_DB} POSTGRES_USER: \${POSTGRES_USER} POSTGRES_PASSWORD: \${POSTGRES_PASSWORD} volumes: - pgdata:/var/lib/postgresql/data - ./postgres-backups:/backups networks: - backend-network container_name: ${PROJECT_NAME}-db restart: unless-stopped ports: - "\${DB_EXTERNAL_PORT}:5432" dns: - 8.8.8.8 - 8.8.4.4 healthcheck: test: ["CMD-SHELL", "pg_isready -U \${POSTGRES_USER} -d \${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 10 start_period: 30s networks: backend-network: driver: bridge volumes: pgdata: driver: local EOF if [ ! -f "$DOCKER_DIR/docker-compose.yml" ]; then error "No se pudo crear el archivo docker-compose.yml" return 1 fi chmod 644 "$DOCKER_DIR/docker-compose.yml" success "Archivo docker-compose.yml creado" return 0 } # Función para ejecutar comandos en el directorio docker run_in_docker_dir() { local cmd="$1" if [ ! -d "$DOCKER_DIR" ]; then error "Directorio $DOCKER_DIR no encontrado" return 1 fi # Verificar permisos antes de continuar if ! check_and_fix_permissions "$DOCKER_DIR"; then error "No se puede acceder a $DOCKER_DIR por problemas de permisos" return 1 fi cd "$DOCKER_DIR" || { error "No se puede acceder al directorio $DOCKER_DIR"; return 1; } eval "$cmd" local result=$? cd "$CURRENT_DIR" || return 1 return $result } # Función MEJORADA para esperar a que PostgreSQL esté listo wait_for_postgres() { process "Esperando a que PostgreSQL esté listo..." local max_attempts=30 local attempt=1 while [ $attempt -le $max_attempts ]; do if run_in_docker_dir "docker-compose exec -T db pg_isready -U postgres > /dev/null 2>&1"; then success "PostgreSQL está listo después de $attempt intentos" # Esperar un poco más para asegurar que esté completamente listo sleep 5 # Verificar que la base de datos específica existe, si no crearla process "Verificando base de datos ${PROJECT_NAME}_db..." if ! run_in_docker_dir "docker-compose exec -T db psql -U postgres -d ${PROJECT_NAME}_db -c 'SELECT 1' > /dev/null 2>&1"; then process "Creando base de datos ${PROJECT_NAME}_db..." if run_in_docker_dir "docker-compose exec -T db createdb -U postgres ${PROJECT_NAME}_db"; then success "Base de datos ${PROJECT_NAME}_db creada exitosamente" else error "Error creando la base de datos ${PROJECT_NAME}_db" return 1 fi else success "Base de datos ${PROJECT_NAME}_db ya existe" fi sleep 2 return 0 fi if [ $attempt -eq 1 ]; then process "Esperando a que PostgreSQL se inicialice..." else process "Intento $attempt/$max_attempts - PostgreSQL aún no está listo..." fi sleep 3 attempt=$((attempt + 1)) done error "PostgreSQL no está respondiendo después de $max_attempts intentos" run_in_docker_dir 'docker-compose logs db' return 1 } # Construir y levantar contenedores CON MEJOR MANEJO DE ERRORES build_docker_containers() { process "Construyendo contenedores Docker..." if [ ! -d "$DOCKER_DIR" ]; then error "El directorio $DOCKER_DIR no existe. No se pueden construir los contenedores." return 1 fi # Verificar permisos antes de continuar if ! check_and_fix_permissions "$DOCKER_DIR"; then error "No se pueden construir los contenedores por problemas de permisos" return 1 fi # Parar contenedores existentes process "Deteniendo contenedores existentes..." if ! run_in_docker_dir 'docker-compose down 2>/dev/null'; then warning "No se pudieron detener contenedores existentes (puede ser normal si no hay contenedores)" fi # Construir imágenes con mejor manejo de errores process "Construyendo imágenes Docker..." # Primero intentar construir sin cache para detectar errores temprano if run_in_docker_dir "docker-compose build --no-cache --build-arg PHP_VERSION=${PHP_VERSION} 2>&1 | tee build.log"; then success "Imágenes construidas exitosamente sin cache" else error "Error construyendo las imágenes Docker sin cache" process "Analizando errores de construcción..." # Mostrar los últimos errores del log if [ -f "$DOCKER_DIR/build.log" ]; then error "Últimas líneas del log de construcción:" tail -20 "$DOCKER_DIR/build.log" | while read -r line; do echo " $line" done fi # Intentar con cache como fallback warning "Intentando construcción con cache..." if ! run_in_docker_dir "docker-compose build --build-arg PHP_VERSION=${PHP_VERSION}"; then error "Error crítico en la construcción de imágenes Docker" return 1 fi fi # Levantar contenedores process "Iniciando contenedores..." if ! run_in_docker_dir 'docker-compose up -d'; then error "Error iniciando contenedores" run_in_docker_dir 'docker-compose logs' return 1 fi # Esperar a que PostgreSQL esté listo if ! wait_for_postgres; then error "La base de datos no se inicializó correctamente" return 1 fi # Verificar estado de los contenedores process "Verificando estado de los contenedores..." if run_in_docker_dir 'docker-compose ps | grep -q "Up"'; then success "Contenedores Docker listos y ejecutándose" return 0 else error "Los contenedores no se iniciaron correctamente" run_in_docker_dir 'docker-compose ps' return 1 fi } # Ejecutar comandos en el contenedor web con reintentos MEJORADO exec_in_web() { local cmd="$1" local max_attempts=3 local attempt=1 sleep 10 while [ $attempt -le $max_attempts ]; do process "Ejecutando comando (intento $attempt/$max_attempts)..." if run_in_docker_dir "docker-compose exec -T web bash -c \"$cmd\""; then return 0 fi if [ $attempt -lt $max_attempts ]; then warning "Intento $attempt falló. Reintentando en 10 segundos..." sleep 10 fi attempt=$((attempt + 1)) done error "Error ejecutando comando después de $max_attempts intentos: $cmd" return 1 } # Función MEJORADA para crear proyectos con Composer create_project_with_composer() { local framework_cmd="$1" local project_name="$2" local install_dir="$3" process "Descargando proyecto $framework_cmd..." exec_in_web "composer config -g process-timeout 1200" exec_in_web "composer config -g repo.packagist composer https://packagist.org" exec_in_web "composer clear-cache" # Opciones específicas por framework if [[ "$FRAMEWORK" == yii2* ]]; then local composer_options=( "--prefer-dist --no-progress --no-interaction" "--prefer-dist --no-dev --no-progress --no-interaction" "--prefer-source --no-progress --no-interaction" ) else local composer_options=( "--prefer-dist --no-dev --no-progress --no-interaction" "--prefer-dist --no-progress --no-interaction" "--prefer-source --no-progress --no-interaction" ) fi for options in "${composer_options[@]}"; do process "Intentando con opciones: $options" # Limpiar directorio temporal si existe exec_in_web "rm -rf /tmp/${project_name} 2>/dev/null || true" if exec_in_web "composer create-project $options $framework_cmd /tmp/${project_name}"; then # VERIFICAR que el proyecto se creó correctamente if exec_in_web "[ -d /tmp/${project_name} ] && [ -f /tmp/${project_name}/composer.json ]"; then success "Proyecto descargado correctamente" # Mover a la ubicación final exec_in_web "rm -rf ${install_dir}/* 2>/dev/null || true" exec_in_web "sh -c 'mv /tmp/${project_name}/* ${install_dir}/ 2>/dev/null || true'" exec_in_web "sh -c 'mv /tmp/${project_name}/.* ${install_dir}/ 2>/dev/null || true'" exec_in_web "rm -rf /tmp/${project_name}" # Instalar dependencias si es necesario if exec_in_web "[ ! -d ${install_dir}/vendor ] && [ -f ${install_dir}/composer.json ]"; then process "Instalando dependencias..." if exec_in_web "cd ${install_dir} && composer install --no-dev --no-progress --no-interaction"; then success "Dependencias instaladas correctamente" else warning "No se pudieron instalar las dependencias automáticamente" fi fi return 0 fi fi warning "Intento falló con opciones: $options" exec_in_web "rm -rf /tmp/${project_name} 2>/dev/null || true" done return 1 } # Crear proyecto Yii2 Basic create_yii2_basic_project() { process "Creando proyecto Yii2 Basic: ${PROJECT_NAME}" if ! create_project_with_composer "yiisoft/yii2-app-basic" "${PROJECT_NAME}" "/var/www/html"; then error "Error al crear proyecto Yii2 Basic después de múltiples intentos" return 1 fi # Configuraciones adicionales para Yii2 Basic exec_in_web "chown -R www-data:www-data /var/www/html" exec_in_web "chmod -R 755 /var/www/html/runtime" exec_in_web "chmod -R 755 /var/www/html/web/assets" success "Proyecto Yii2 Basic '${PROJECT_NAME}' creado correctamente" return 0 } # Crear proyecto Yii2 Advanced create_yii2_advanced_project() { process "Creando proyecto Yii2 Advanced: ${PROJECT_NAME}" if ! create_project_with_composer "yiisoft/yii2-app-advanced" "${PROJECT_NAME}" "/var/www/html"; then error "Error al crear proyecto Yii2 Advanced después de múltiples intentos" return 1 fi # Configuraciones adicionales para Yii2 Advanced exec_in_web "chown -R www-data:www-data /var/www/html" exec_in_web "chmod -R 755 /var/www/html/frontend/runtime" exec_in_web "chmod -R 755 /var/www/html/backend/runtime" exec_in_web "chmod -R 755 /var/www/html/frontend/web/assets" exec_in_web "chmod -R 755 /var/www/html/backend/web/assets" # Inicializar el proyecto advanced process "Inicializando Yii2 Advanced..." if exec_in_web "cd /var/www/html && php init --env=Development --overwrite=All"; then success "Yii2 Advanced inicializado correctamente" else warning "No se pudo inicializar Yii2 Advanced automáticamente" fi success "Proyecto Yii2 Advanced '${PROJECT_NAME}' creado correctamente" return 0 } # Crear proyecto Yii3 create_yii3_project() { process "Creando proyecto Yii3: ${PROJECT_NAME}" if ! create_project_with_composer "yiisoft/yii-project-template" "${PROJECT_NAME}" "/var/www/html"; then error "Error al crear proyecto Yii3 después de múltiples intentos" return 1 fi exec_in_web "chown -R www-data:www-data /var/www/html" success "Proyecto Yii3 '${PROJECT_NAME}' creado correctamente" return 0 } # Crear proyecto Laravel create_laravel_project() { process "Creando proyecto Laravel: ${PROJECT_NAME}" if ! create_project_with_composer "laravel/laravel" "${PROJECT_NAME}" "/var/www/html"; then error "Error al crear proyecto Laravel después de múltiples intentos" return 1 fi exec_in_web "chown -R www-data:www-data /var/www/html" exec_in_web "chmod -R 775 /var/www/html/storage" exec_in_web "chmod -R 775 /var/www/html/bootstrap/cache" # Configurar .env para Laravel process "Configurando Laravel..." exec_in_web "cd /var/www/html && cp .env.example .env 2>/dev/null || true" exec_in_web "cd /var/www/html && cat > .env << 'ENDFILE' APP_NAME=${PROJECT_NAME} APP_ENV=local APP_KEY= APP_DEBUG=true APP_URL=http://localhost:${WEB_PORT} DB_CONNECTION=pgsql DB_HOST=db DB_PORT=5432 DB_DATABASE=${PROJECT_NAME}_db DB_USERNAME=postgres DB_PASSWORD=password LOG_CHANNEL=stack LOG_DEPRECATIONS_CHANNEL=null LOG_LEVEL=debug QUEUE_CONNECTION=sync SESSION_DRIVER=file SESSION_LIFETIME=120 MEMCACHED_HOST=127.0.0.1 REDIS_HOST=127.0.0.1 REDIS_PASSWORD=null REDIS_PORT=6379 MAIL_MAILER=smtp MAIL_HOST=mailhog MAIL_PORT=1025 MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null MAIL_FROM_ADDRESS=null MAIL_FROM_NAME=\"\${APP_NAME}\" ENDFILE" exec_in_web "cd /var/www/html && php artisan key:generate" success "Proyecto Laravel '${PROJECT_NAME}' creado correctamente" return 0 } # Crear el proyecto según el framework seleccionado create_framework_project() { process "Iniciando creación del proyecto ${PROJECT_NAME} con ${FRAMEWORK}..." process "Probando conexión a internet desde el contenedor..." if ! exec_in_web "curl -s --connect-timeout 30 https://packagist.org > /dev/null"; then warning "El contenedor no puede conectarse a Packagist. Esto puede causar problemas." exec_in_web "cat /etc/resolv.conf" else success "Conexión a internet verificada desde el contenedor" fi case "$FRAMEWORK" in "yii2-basic") create_yii2_basic_project ;; "yii2-advanced") create_yii2_advanced_project ;; "yii3") create_yii3_project ;; "laravel") create_laravel_project ;; *) error "Framework no válido: $FRAMEWORK" return 1 ;; esac } # Configuración MEJORADA de base de datos configure_database() { process "Configurando base de datos para $FRAMEWORK..." sleep 10 case "$FRAMEWORK" in "yii2-basic") configure_yii2_basic_db ;; "yii2-advanced") configure_yii2_advanced_db ;; "yii3") configure_yii3_db ;; "laravel") configure_laravel_db ;; esac } configure_laravel_db() { process "Configurando base de datos para Laravel..." process "Verificando conexión a la base de datos..." if exec_in_web "cd /var/www/html && php -r \" try { \\\$pdo = new PDO('pgsql:host=db;port=5432;dbname=${PROJECT_NAME}_db', 'postgres', 'password'); \\\$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); echo 'Conexión a BD exitosa' . PHP_EOL; } catch (PDOException \\\$e) { echo 'Error de conexión: ' . \\\$e->getMessage() . PHP_EOL; exit(1); } \""; then success "Conexión a la base de datos verificada" else error "No se pudo conectar a la base de datos" return 1 fi process "Ejecutando migraciones de Laravel..." if exec_in_web "cd /var/www/html && php artisan migrate --force"; then success "Migraciones de Laravel ejecutadas correctamente" else warning "Las migraciones de Laravel fallaron o no son necesarias" fi return 0 } configure_yii3_db() { process "Configurando base de datos para Yii3..." if exec_in_web "cd /var/www/html && php ./yii migrate/up --interactive=0"; then success "Migraciones de Yii3 ejecutadas correctamente" else warning "Las migraciones de Yii3 fallaron o no son necesarias" fi return 0 } # Configuración MEJORADA para Yii2 Basic configure_yii2_basic_db() { process "Configurando base de datos para Yii2 Basic..." # Primero verificar que Yii2 esté instalado correctamente process "Verificando instalación de Yii2 Basic..." if ! exec_in_web "[ -f /var/www/html/vendor/yiisoft/yii2/Yii.php ]"; then error "Yii2 Basic no está instalado correctamente. No se puede configurar la base de datos." info "Instala las dependencias manualmente:" echo " cd $DOCKER_DIR && docker-compose exec web composer install" return 1 fi process "Configurando conexión a base de datos para Yii2 Basic..." exec_in_web "cd /var/www/html && cp config/db.php config/db.php.backup 2>/dev/null || true" exec_in_web "cd /var/www/html && cat > config/db.php << 'ENDFILE' <?php return [ 'class' => 'yii\db\Connection', 'dsn' => 'pgsql:host=db;port=5432;dbname=${PROJECT_NAME}_db', 'username' => 'postgres', 'password' => 'password', 'charset' => 'utf8', ]; ENDFILE" # Deshabilitar GII en producción o si no está instalado exec_in_web "cd /var/www/html && cp config/web.php config/web.php.backup 2>/dev/null || true" exec_in_web "cd /var/www/html && sed -i \"s/'modules' => \[/'modules' => \[\\n /*\\n 'gii' => [\\n 'class' => 'yii\\\gii\\\Module',\\n ],\\n */\\n/g\" config/web.php 2>/dev/null || true" process "Intentando ejecutar migraciones de Yii2 Basic..." if exec_in_web "cd /var/www/html && php yii migrate/up --interactive=0"; then success "Migraciones de Yii2 Basic ejecutadas correctamente" else warning "Las migraciones de Yii2 Basic fallaron o no son necesarias" fi return 0 } # Configuración para Yii2 Advanced configure_yii2_advanced_db() { process "Configurando base de datos para Yii2 Advanced..." # Verificar instalación if ! exec_in_web "[ -f /var/www/html/vendor/yiisoft/yii2/Yii.php ]"; then error "Yii2 Advanced no está instalado correctamente." return 1 fi # Configurar base de datos común exec_in_web "cd /var/www/html && cat > common/config/main-local.php << 'ENDFILE' <?php return [ 'components' => [ 'db' => [ 'class' => 'yii\db\Connection', 'dsn' => 'pgsql:host=db;port=5432;dbname=${PROJECT_NAME}_db', 'username' => 'postgres', 'password' => 'password', 'charset' => 'utf8', ], ], ]; ENDFILE" # Configurar base de datos para frontend y backend for app in frontend backend; do exec_in_web "cd /var/www/html && cat > ${app}/config/main-local.php << 'ENDFILE' <?php return [ 'components' => [ 'db' => [ 'class' => 'yii\db\Connection', 'dsn' => 'pgsql:host=db;port=5432;dbname=${PROJECT_NAME}_db', 'username' => 'postgres', 'password' => 'password', 'charset' => 'utf8', ], ], ]; ENDFILE" done process "Ejecutando migraciones de Yii2 Advanced..." if exec_in_web "cd /var/www/html && php yii migrate/up --interactive=0"; then success "Migraciones de Yii2 Advanced ejecutadas correctamente" else warning "Las migraciones de Yii2 Advanced fallaron o no son necesarias" fi return 0 } # Mostrar resumen final MEJORADO show_final_summary() { echo "" success "🎉 ¡PROYECTO CREADO EXITOSAMENTE!" echo "==================================" echo "" info "📊 RESUMEN FINAL:" echo "-----------------" echo " 📂 Proyecto: $PROJECT_NAME" echo " 🚀 Framework: $FRAMEWORK" echo " 🐘 PHP: $PHP_VERSION" echo " 🌐 URL: http://localhost:${WEB_PORT}" echo " 🗄️ Base de datos: localhost:${DB_PORT}" echo " 📁 Directorio: $DOCKER_DIR/src/$PROJECT_NAME/" echo "" # Información específica por framework case "$FRAMEWORK" in "yii2-advanced") info "🌐 URLs de Yii2 Advanced:" echo " Frontend: http://localhost:${WEB_PORT}" echo " Backend: http://localhost:${WEB_PORT}/backend/web" ;; *) info "🌐 URL de la aplicación:" echo " http://localhost:${WEB_PORT}" ;; esac echo "" info "🚀 COMANDOS ÚTILES:" echo "------------------" echo " Iniciar proyecto: cd $DOCKER_DIR && docker-compose up -d" echo " Detener proyecto: cd $DOCKER_DIR && docker-compose down" echo " Ver logs: cd $DOCKER_DIR && docker-compose logs" echo " Acceder a la consola: cd $DOCKER_DIR && docker-compose exec web bash" echo "" info "🔗 CONEXIÓN A LA BASE DE DATOS:" echo "------------------------------" echo " Host: localhost" echo " Puerto: ${DB_PORT}" echo " Base de datos: ${PROJECT_NAME}_db" echo " Usuario: postgres" echo " Contraseña: password" echo "" # Instrucciones específicas por framework case "$FRAMEWORK" in "yii2-basic"|"yii2-advanced") info "🔧 INSTRUCCIONES ESPECÍFICAS PARA YII2:" echo "-------------------------------------" echo " Si hay problemas con dependencias:" echo " cd $DOCKER_DIR && docker-compose exec web composer install" if [ "$FRAMEWORK" = "yii2-advanced" ]; then echo " Para inicializar el proyecto:" echo " cd $DOCKER_DIR && docker-compose exec web php init" fi echo " Para ejecutar migraciones:" echo " cd $DOCKER_DIR && docker-compose exec web php yii migrate/up --interactive=0" echo " Para instalar módulos de desarrollo:" echo " cd $DOCKER_DIR && docker-compose exec web composer require --dev yiisoft/yii2-gii" echo " cd $DOCKER_DIR && docker-compose exec web composer require --dev yiisoft/yii2-debug" echo "" ;; "yii3") info "🔧 INSTRUCCIONES ESPECÍFICAS PARA YII3:" echo "-------------------------------------" echo " Para ejecutar migraciones: cd $DOCKER_DIR && docker-compose exec web php ./yii migrate/up --interactive=0" echo "" ;; "laravel") info "🔧 INSTRUCCIONES ESPECÍFICAS PARA LARAVEL:" echo "----------------------------------------" echo " Para ejecutar migraciones: cd $DOCKER_DIR && docker-compose exec web php artisan migrate" echo " Para generar key: cd $DOCKER_DIR && docker-compose exec web php artisan key:generate" echo "" ;; esac info "📝 PRÓXIMOS PASOS:" echo "-----------------" echo " 1. Accede a la URL mostrada arriba para ver tu aplicación" echo " 2. Configura la base de datos si es necesario" echo " 3. ¡Comienza a desarrollar!" echo "" } # Función principal main() { clear echo "========================================" echo " GENERADOR DE PROYECTOS CON DOCKER v1.1" echo "========================================" echo "" check_docker_installation check_docker_permissions check_internet_connection select_framework get_project_name if ! create_directory_structure; then error "Error creando la estructura de directorios" exit 1 fi if ! create_env_file; then error "Error creando el archivo .env" exit 1 fi if ! create_apache_config; then error "Error creando la configuración de Apache" exit 1 fi if ! create_dockerfile; then error "Error creando el Dockerfile" exit 1 fi if ! create_docker_compose; then error "Error creando docker-compose.yml" exit 1 fi if build_docker_containers; then process "Esperando inicialización completa de servicios..." sleep 20 if create_framework_project; then process "Configurando la aplicación..." if configure_database; then show_final_summary else warning "Proyecto creado pero la configuración de BD tuvo problemas menores" show_final_summary fi else error "Error al crear el proyecto del framework" exit 1 fi else error "Error al construir los contenedores Docker" exit 1 fi } if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@" fi
Preview:
downloadDownload PNG
downloadDownload JPEG
downloadDownload SVG
Tip: You can change the style, width & colours of the snippet with the inspect tool before clicking Download!
Click to optimize width for Twitter