diff --git a/.gitignore b/.gitignore index e735fcc..ddb72f3 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,6 @@ yarn-error.log /.vscode /.zed CLAUDE.md +/Docker/QA/.env.qa +/Docker/Dev/.env.dev +/Docker/Prod/.env.prod \ No newline at end of file diff --git a/Docker/Dev/.env.qa.example b/Docker/Dev/.env.qa.example new file mode 100644 index 0000000..3b10f62 --- /dev/null +++ b/Docker/Dev/.env.qa.example @@ -0,0 +1,97 @@ +APP_NAME="Holos" +APP_ENV=local +APP_KEY= +APP_DEBUG=true +APP_TIMEZONE=America/Mexico_City +APP_URL=http://backend.holos.test +APP_FRONTEND_URL=http://frontend.holos.test +APP_PAGINATION=25 + +APP_LOCALE=es +APP_FALLBACK_LOCALE=es +APP_FAKER_LOCALE=es_MX + +APP_MAINTENANCE_DRIVER=file +# APP_MAINTENANCE_STORE=database + +CORS_ALLOWED_ORIGINS=* + +PULSE_ENABLED=false +TELESCOPE_ENABLED=false + +PHP_CLI_SERVER_WORKERS=4 + +BCRYPT_ROUNDS=12 + +LOG_CHANNEL=stack +LOG_STACK=single +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=debug + +DB_CONNECTION=mysql +DB_HOST=mysql +DB_PORT=3306 +DB_DATABASE=holos-backend +DB_USERNAME=notsoweb +DB_PASSWORD= +DB_ROOT_PASSWORD= +PMA_PORT=8081 # Puerto para phpMyAdmin + +NGINX_PORT=8080 # Puerto para Nginx + +SESSION_DRIVER=database +SESSION_LIFETIME=120 +SESSION_ENCRYPT=false +SESSION_PATH=/ +SESSION_DOMAIN=null + +BROADCAST_CONNECTION=reverb +FILESYSTEM_DISK=local +QUEUE_CONNECTION=database + +CACHE_STORE=database +CACHE_PREFIX= + +MEMCACHED_HOST=127.0.0.1 + +REDIS_CLIENT=phpredis +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 + +MAIL_MAILER=smtp +MAIL_HOST=mail.smtp2go.com +MAIL_PORT=465 +MAIL_DOMAIN=notsoweb.com +MAIL_USERNAME=no-reply@notsoweb.com +MAIL_PASSWORD= +MAIL_ENCRYPTION=ssl +MAIL_FROM_ADDRESS="no-reply@notsoweb.com" +MAIL_FROM_NAME="${APP_NAME}" + +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_DEFAULT_REGION=us-east-1 +AWS_BUCKET= +AWS_USE_PATH_STYLE_ENDPOINT=false + +# REPUVE FEDERAL +REPUVE_FED_BASE_URL= +REPUVE_FED_USERNAME= +REPUVE_FED_PASSWORD= + +# REPUVE ESTATAL +REPUVE_EST_URL= + +REVERB_APP_ID= +REVERB_APP_KEY= +REVERB_APP_SECRET= +REVERB_HOST="localhost" +REVERB_PORT=8080 +REVERB_SCHEME=http + +VITE_APP_NAME="${APP_NAME}" +VITE_REVERB_APP_KEY="${REVERB_APP_KEY}" +VITE_REVERB_HOST="${REVERB_HOST}" +VITE_REVERB_PORT="${REVERB_PORT}" +VITE_REVERB_SCHEME="${REVERB_SCHEME}" diff --git a/Docker/Dev/docker-compose.yml b/Docker/Dev/docker-compose.yml index 7463fd6..ed7dc7d 100644 --- a/Docker/Dev/docker-compose.yml +++ b/Docker/Dev/docker-compose.yml @@ -1,6 +1,7 @@ name: repuve-backend-dev services: repuve-backend: + container_name: backend-dev build: context: ../../ dockerfile: Docker/Dev/dockerfile @@ -17,14 +18,15 @@ services: volumes: - ../../storage:/var/www/repuve-backend-v1/storage networks: - - repuve-network - mem_limit: 512M + - repuve-dev-network + mem_limit: 256M restart: unless-stopped depends_on: mysql: condition: service_healthy nginx: + container_name: repuve-nginx-dev image: nginx:alpine ports: - "${NGINX_PORT}:80" @@ -39,13 +41,14 @@ services: max-size: "50m" max-file: "10" networks: - - repuve-network - mem_limit: 256m + - repuve-dev-network + mem_limit: 128M restart: unless-stopped depends_on: - repuve-backend mysql: + container_name: repuve-mysql-dev image: mysql:8.0 environment: MYSQL_DATABASE: ${DB_DATABASE} @@ -53,12 +56,12 @@ services: MYSQL_PASSWORD: ${DB_PASSWORD} MYSQL_USER: ${DB_USERNAME} ports: - - "${DB_PORT}:3306" + - "${DB_PORT_FORWARD}:3306" volumes: - mysql_data:/var/lib/mysql networks: - - repuve-network - mem_limit: 512m + - repuve-dev-network + mem_limit: 256M restart: unless-stopped healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] @@ -70,5 +73,5 @@ volumes: driver: local networks: - repuve-network: + repuve-dev-network: driver: bridge diff --git a/Docker/Dev/nginx.conf b/Docker/Dev/nginx.conf new file mode 100644 index 0000000..97eed02 --- /dev/null +++ b/Docker/Dev/nginx.conf @@ -0,0 +1,144 @@ +user nginx; +worker_processes auto; + +# Log de errores con máximo nivel de detalle +error_log /var/log/nginx/error.log debug; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # ─── FORMATO DE LOG FORENSE EXTENDIDO ─────────────────────────────────────── + log_format forensic_main + '$remote_addr - $remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent" ' + '"$http_x_forwarded_for" "$http_x_real_ip" ' + 'rt=$request_time ' # Tiempo total de la petición + 'uct="$upstream_connect_time" ' # Tiempo de conexión upstream + 'uht="$upstream_header_time" ' # Tiempo de cabeceras upstream + 'urt="$upstream_response_time" ' # Tiempo de respuesta upstream + 'cs=$upstream_cache_status ' # Estado de caché + 'ssl_protocol="$ssl_protocol" ' # Protocolo SSL usado + 'ssl_cipher="$ssl_cipher" ' # Cifrado SSL + 'ssl_session_id="$ssl_session_id" ' # ID sesión TLS (rastreo) + 'conn=$connection ' # ID de conexión + 'conn_reqs=$connection_requests ' # Peticiones por conexión + 'pipe=$pipe ' # Pipelining (y/n) + 'host="$host" ' # Host solicitado + 'server_name="$server_name" ' + 'scheme="$scheme" ' + 'request_method="$request_method" ' + 'request_uri="$request_uri" ' + 'server_port="$server_port" ' + 'http_version="$server_protocol" ' + 'bytes_sent=$bytes_sent ' # Total bytes enviados + 'request_length=$request_length ' # Tamaño de la petición + 'req_id="$request_id"'; # ID único por petición + + # Formato adicional para headers sensibles / seguridad + log_format forensic_headers + '$remote_addr [$time_local] req_id="$request_id" ' + 'Authorization="$http_authorization" ' + 'Cookie="$http_cookie" ' + 'Content-Type="$content_type" ' + 'Content-Length="$content_length" ' + 'Accept="$http_accept" ' + 'Accept-Language="$http_accept_language" ' + 'Accept-Encoding="$http_accept_encoding" ' + 'Origin="$http_origin" ' + 'Sec-Fetch-Site="$http_sec_fetch_site" ' + 'Sec-Fetch-Mode="$http_sec_fetch_mode" ' + 'Sec-Fetch-Dest="$http_sec_fetch_dest" ' + 'X-Custom-Header="$http_x_custom_header"'; + + # ─── ARCHIVOS DE LOG ──────────────────────────────────────────────────────── + access_log /var/log/nginx/access.log forensic_main buffer=16k flush=1s; + access_log /var/log/nginx/headers.log forensic_headers buffer=16k flush=1s; + error_log /var/log/nginx/error.log debug; + + # ─── OPCIONES GENERALES ───────────────────────────────────────────────────── + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + server_tokens off; # No revelar versión en respuestas (buena práctica) + + # Añadir request_id único a cada petición + add_header X-Request-ID $request_id always; + + # ─── SERVER BLOCK LARAVEL ─────────────────────────────────────────────────── + server { + listen 80; + server_name _; + root /var/www/repuve-backend-v1/public; + index index.php index.html; + + # Logging con formatos forenses (definidos en nginx.conf principal) + error_log /var/log/nginx/error.log debug; + access_log /var/log/nginx/access.log forensic_main; + + # Handle Laravel routes (Front Controller) + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + # Handle PHP files + location ~ \.php$ { + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass backend-dev:9000; + fastcgi_index index.php; + + # Timeouts importantes para evitar errores 500 + fastcgi_read_timeout 300; + fastcgi_connect_timeout 300; + fastcgi_send_timeout 300; + + # Carga los parámetros por defecto + include fastcgi_params; + + # Parámetros críticos para Laravel + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param REQUEST_URI $request_uri; + fastcgi_param QUERY_STRING $query_string; + fastcgi_param REQUEST_METHOD $request_method; + fastcgi_param CONTENT_TYPE $content_type; + fastcgi_param CONTENT_LENGTH $content_length; + fastcgi_param HTTP_HOST $http_host; + fastcgi_param HTTPS $https if_not_empty; + fastcgi_param HTTP_PROXY ""; + + # Añadir Request ID al backend para tracking + fastcgi_param HTTP_X_REQUEST_ID $request_id; + } + + client_max_body_size 150M; + + # Handle storage files (Laravel storage link) + location /storage/ { + alias /var/www/repuve-backend-v1/storage/app/public/; + } + + location /profile { + alias /var/www/repuve-backend-v1/storage/app/profile; + try_files $uri =404; + } + + location /images { + alias /var/www/repuve-backend-v1/storage/app/images; + try_files $uri =404; + } + + # Denegar acceso a archivos ocultos como .htaccess + location ~ /\.ht { + deny all; + } +} +} diff --git a/Docker/QA/.env.qa.example b/Docker/QA/.env.qa.example new file mode 100644 index 0000000..3b10f62 --- /dev/null +++ b/Docker/QA/.env.qa.example @@ -0,0 +1,97 @@ +APP_NAME="Holos" +APP_ENV=local +APP_KEY= +APP_DEBUG=true +APP_TIMEZONE=America/Mexico_City +APP_URL=http://backend.holos.test +APP_FRONTEND_URL=http://frontend.holos.test +APP_PAGINATION=25 + +APP_LOCALE=es +APP_FALLBACK_LOCALE=es +APP_FAKER_LOCALE=es_MX + +APP_MAINTENANCE_DRIVER=file +# APP_MAINTENANCE_STORE=database + +CORS_ALLOWED_ORIGINS=* + +PULSE_ENABLED=false +TELESCOPE_ENABLED=false + +PHP_CLI_SERVER_WORKERS=4 + +BCRYPT_ROUNDS=12 + +LOG_CHANNEL=stack +LOG_STACK=single +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=debug + +DB_CONNECTION=mysql +DB_HOST=mysql +DB_PORT=3306 +DB_DATABASE=holos-backend +DB_USERNAME=notsoweb +DB_PASSWORD= +DB_ROOT_PASSWORD= +PMA_PORT=8081 # Puerto para phpMyAdmin + +NGINX_PORT=8080 # Puerto para Nginx + +SESSION_DRIVER=database +SESSION_LIFETIME=120 +SESSION_ENCRYPT=false +SESSION_PATH=/ +SESSION_DOMAIN=null + +BROADCAST_CONNECTION=reverb +FILESYSTEM_DISK=local +QUEUE_CONNECTION=database + +CACHE_STORE=database +CACHE_PREFIX= + +MEMCACHED_HOST=127.0.0.1 + +REDIS_CLIENT=phpredis +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 + +MAIL_MAILER=smtp +MAIL_HOST=mail.smtp2go.com +MAIL_PORT=465 +MAIL_DOMAIN=notsoweb.com +MAIL_USERNAME=no-reply@notsoweb.com +MAIL_PASSWORD= +MAIL_ENCRYPTION=ssl +MAIL_FROM_ADDRESS="no-reply@notsoweb.com" +MAIL_FROM_NAME="${APP_NAME}" + +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_DEFAULT_REGION=us-east-1 +AWS_BUCKET= +AWS_USE_PATH_STYLE_ENDPOINT=false + +# REPUVE FEDERAL +REPUVE_FED_BASE_URL= +REPUVE_FED_USERNAME= +REPUVE_FED_PASSWORD= + +# REPUVE ESTATAL +REPUVE_EST_URL= + +REVERB_APP_ID= +REVERB_APP_KEY= +REVERB_APP_SECRET= +REVERB_HOST="localhost" +REVERB_PORT=8080 +REVERB_SCHEME=http + +VITE_APP_NAME="${APP_NAME}" +VITE_REVERB_APP_KEY="${REVERB_APP_KEY}" +VITE_REVERB_HOST="${REVERB_HOST}" +VITE_REVERB_PORT="${REVERB_PORT}" +VITE_REVERB_SCHEME="${REVERB_SCHEME}" diff --git a/Docker/QA/docker-compose.yml b/Docker/QA/docker-compose.yml new file mode 100644 index 0000000..de213b7 --- /dev/null +++ b/Docker/QA/docker-compose.yml @@ -0,0 +1,77 @@ +name: repuve-backend-qa +services: + repuve-backend: + container_name: backend-qa + build: + context: ../../ + dockerfile: Docker/QA/dockerfile + working_dir: /var/www/repuve-backend-v1 + environment: + - APP_ENV=qa + - APP_DEBUG=true + - APP_KEY=${APP_KEY} + - DB_HOST=${DB_HOST} + - DB_USERNAME=${DB_USERNAME} + - DB_PASSWORD=${DB_PASSWORD} + - DB_DATABASE=${DB_DATABASE} + - DB_PORT=${DB_PORT} + volumes: + - ../../storage:/var/www/repuve-backend-v1/storage + networks: + - repuve-qa-network + mem_limit: 256M + restart: unless-stopped + depends_on: + qa-mysql: + condition: service_healthy + + nginx: + container_name: repuve-nginx-qa + image: nginx:alpine + ports: + - "${NGINX_PORT}:80" + volumes: + - ../../public:/var/www/repuve-backend-v1/public + - ../../storage:/var/www/repuve-backend-v1/storage + - ./nginx.conf:/etc/nginx/nginx.conf + - /var/log/nginx:/var/log/nginx + logging: + driver: "local" + options: + max-size: "50m" + max-file: "10" + networks: + - repuve-qa-network + mem_limit: 128M + restart: unless-stopped + depends_on: + - repuve-backend + + qa-mysql: + container_name: repuve-mysql-qa + image: mysql:8.0 + environment: + MYSQL_DATABASE: ${DB_DATABASE} + MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} + MYSQL_PASSWORD: ${DB_PASSWORD} + MYSQL_USER: ${DB_USERNAME} + ports: + - "${DB_PORT}:3306" + volumes: + - qa_mysql_data:/var/lib/mysql + networks: + - repuve-qa-network + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + interval: 10s + timeout: 5s + retries: 5 + mem_limit: 256M + restart: unless-stopped + +networks: + repuve-qa-network: + driver: bridge + +volumes: + qa_mysql_data: diff --git a/Docker/QA/dockerfile b/Docker/QA/dockerfile new file mode 100644 index 0000000..315e6be --- /dev/null +++ b/Docker/QA/dockerfile @@ -0,0 +1,47 @@ +FROM php:8.3-fpm-alpine + +WORKDIR /var/www/repuve-backend-v1 + +RUN apk add --no-cache \ + git \ + curl \ + libpng-dev \ + oniguruma-dev \ + libxml2-dev \ + zip \ + unzip \ + libzip-dev \ + nano \ + openssl \ + bash \ + mysql-client \ + libreoffice \ + ttf-dejavu \ + supervisor \ + && docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip \ + && echo "upload_max_filesize=150M" > /usr/local/etc/php/conf.d/uploads.ini \ + && echo "post_max_size=150M" >> /usr/local/etc/php/conf.d/uploads.ini + +COPY --from=composer:latest /usr/bin/composer /usr/bin/composer + +COPY composer.json composer.lock ./ + +RUN composer install --optimize-autoloader --no-interaction --no-scripts + +COPY . . + +COPY entrypoint-dev.sh /usr/local/bin/entrypoint-dev.sh +RUN chmod +x /usr/local/bin/entrypoint-dev.sh + +COPY Docker/supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf +RUN mkdir -p /var/log/supervisor + +RUN mkdir -p storage/app/keys storage/logs bootstrap/cache + +RUN chown -R www-data:www-data /var/www/repuve-backend-v1/storage /var/www/repuve-backend-v1/bootstrap/cache +RUN chmod -R 775 /var/www/repuve-backend-v1/storage /var/www/repuve-backend-v1/bootstrap/cache + +EXPOSE 9000 + +ENTRYPOINT ["/usr/local/bin/entrypoint-dev.sh"] +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] diff --git a/Docker/QA/nginx.conf b/Docker/QA/nginx.conf new file mode 100644 index 0000000..ae901d0 --- /dev/null +++ b/Docker/QA/nginx.conf @@ -0,0 +1,144 @@ +user nginx; +worker_processes auto; + +# Log de errores con máximo nivel de detalle +error_log /var/log/nginx/error.log debug; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # ─── FORMATO DE LOG FORENSE EXTENDIDO ─────────────────────────────────────── + log_format forensic_main + '$remote_addr - $remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent" ' + '"$http_x_forwarded_for" "$http_x_real_ip" ' + 'rt=$request_time ' # Tiempo total de la petición + 'uct="$upstream_connect_time" ' # Tiempo de conexión upstream + 'uht="$upstream_header_time" ' # Tiempo de cabeceras upstream + 'urt="$upstream_response_time" ' # Tiempo de respuesta upstream + 'cs=$upstream_cache_status ' # Estado de caché + 'ssl_protocol="$ssl_protocol" ' # Protocolo SSL usado + 'ssl_cipher="$ssl_cipher" ' # Cifrado SSL + 'ssl_session_id="$ssl_session_id" ' # ID sesión TLS (rastreo) + 'conn=$connection ' # ID de conexión + 'conn_reqs=$connection_requests ' # Peticiones por conexión + 'pipe=$pipe ' # Pipelining (y/n) + 'host="$host" ' # Host solicitado + 'server_name="$server_name" ' + 'scheme="$scheme" ' + 'request_method="$request_method" ' + 'request_uri="$request_uri" ' + 'server_port="$server_port" ' + 'http_version="$server_protocol" ' + 'bytes_sent=$bytes_sent ' # Total bytes enviados + 'request_length=$request_length ' # Tamaño de la petición + 'req_id="$request_id"'; # ID único por petición + + # Formato adicional para headers sensibles / seguridad + log_format forensic_headers + '$remote_addr [$time_local] req_id="$request_id" ' + 'Authorization="$http_authorization" ' + 'Cookie="$http_cookie" ' + 'Content-Type="$content_type" ' + 'Content-Length="$content_length" ' + 'Accept="$http_accept" ' + 'Accept-Language="$http_accept_language" ' + 'Accept-Encoding="$http_accept_encoding" ' + 'Origin="$http_origin" ' + 'Sec-Fetch-Site="$http_sec_fetch_site" ' + 'Sec-Fetch-Mode="$http_sec_fetch_mode" ' + 'Sec-Fetch-Dest="$http_sec_fetch_dest" ' + 'X-Custom-Header="$http_x_custom_header"'; + + # ─── ARCHIVOS DE LOG ──────────────────────────────────────────────────────── + access_log /var/log/nginx/access.log forensic_main buffer=16k flush=1s; + access_log /var/log/nginx/headers.log forensic_headers buffer=16k flush=1s; + error_log /var/log/nginx/error.log debug; + + # ─── OPCIONES GENERALES ───────────────────────────────────────────────────── + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + server_tokens off; # No revelar versión en respuestas (buena práctica) + + # Añadir request_id único a cada petición + add_header X-Request-ID $request_id always; + + # ─── SERVER BLOCK LARAVEL ─────────────────────────────────────────────────── + server { + listen 80; + server_name _; + root /var/www/repuve-backend-v1/public; + index index.php index.html; + + # Logging con formatos forenses (definidos en nginx.conf principal) + error_log /var/log/nginx/error.log debug; + access_log /var/log/nginx/access.log forensic_main; + + # Handle Laravel routes (Front Controller) + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + # Handle PHP files + location ~ \.php$ { + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass backend-qa:9000; + fastcgi_index index.php; + + # Timeouts importantes para evitar errores 500 + fastcgi_read_timeout 300; + fastcgi_connect_timeout 300; + fastcgi_send_timeout 300; + + # Carga los parámetros por defecto + include fastcgi_params; + + # Parámetros críticos para Laravel + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param REQUEST_URI $request_uri; + fastcgi_param QUERY_STRING $query_string; + fastcgi_param REQUEST_METHOD $request_method; + fastcgi_param CONTENT_TYPE $content_type; + fastcgi_param CONTENT_LENGTH $content_length; + fastcgi_param HTTP_HOST $http_host; + fastcgi_param HTTPS $https if_not_empty; + fastcgi_param HTTP_PROXY ""; + + # Añadir Request ID al backend para tracking + fastcgi_param HTTP_X_REQUEST_ID $request_id; + } + + client_max_body_size 150M; + + # Handle storage files (Laravel storage link) + location /storage/ { + alias /var/www/repuve-backend-v1/storage/app/public/; + } + + location /profile { + alias /var/www/repuve-backend-v1/storage/app/profile; + try_files $uri =404; + } + + location /images { + alias /var/www/repuve-backend-v1/storage/app/images; + try_files $uri =404; + } + + # Denegar acceso a archivos ocultos como .htaccess + location ~ /\.ht { + deny all; + } +} +}