From c18d26fd0255abeb207f981dfb68dc3fb721749c Mon Sep 17 00:00:00 2001 From: Franck DAKIA Date: Sat, 6 Jun 2026 02:06:55 +0000 Subject: [PATCH 1/2] feat(sys): Add deployment configuration --- Dockerfile | 119 +++++++++++++++++++++++++++++ docker-compose.yml | 118 ++++++++++++++++++++++++++++ system/crontab | 1 + system/frankenphp/Caddyfile | 71 +++++++++++++++++ system/frankenphp/entrypoint.sh | 22 ++++++ system/frankenphp/supervisord.conf | 54 +++++++++++++ system/nginx/nginx-site.conf | 78 +++++++++++++++++++ system/nginx/nginx.conf | 33 ++++++++ system/nginx/start.sh | 31 ++++++++ system/nginx/supervisord.conf | 29 +++++++ system/php.ini | 88 +++++++++++++++++++++ 11 files changed, 644 insertions(+) create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 system/crontab create mode 100644 system/frankenphp/Caddyfile create mode 100644 system/frankenphp/entrypoint.sh create mode 100644 system/frankenphp/supervisord.conf create mode 100644 system/nginx/nginx-site.conf create mode 100644 system/nginx/nginx.conf create mode 100644 system/nginx/start.sh create mode 100644 system/nginx/supervisord.conf create mode 100644 system/php.ini diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2f64e6f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,119 @@ +FROM php:8.4-fpm-alpine AS php + +# Install PHP extensions and system dependencies +RUN apk add --no-cache \ + nginx \ + supervisor \ + nodejs \ + npm \ + libzip-dev \ + libpng-dev \ + libjpeg-turbo-dev \ + libwebp-dev \ + libxpm-dev \ + freetype-dev \ + imagemagick \ + imagemagick-dev \ + oniguruma-dev \ + icu-dev \ + linux-headers \ + bash \ + curl \ + git \ + certbot \ + certbot-nginx \ + postgresql-dev + +RUN docker-php-ext-configure gd \ + --with-freetype \ + --with-jpeg \ + --with-webp + +# Install core PHP extensions +RUN docker-php-ext-install -j$(nproc) \ + bcmath \ + mbstring \ + gd \ + pcntl \ + intl \ + pdo_mysql \ + pdo_pgsql \ + pgsql \ + exif \ + sockets \ + opcache \ + zip \ + ftp + +# Install Redis extension via PECL +RUN apk add --no-cache --virtual .build-deps $PHPIZE_DEPS \ + && pecl install redis \ + && docker-php-ext-enable redis \ + && apk del .build-deps + +# Copy application code +WORKDIR /var/www/html +COPY . . + +# Composer install (if composer.lock present) +RUN if [ -f composer.lock ]; then \ + curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer && \ + composer install --no-dev --no-scripts --no-interaction --no-progress --optimize-autoloader; \ + fi + +# Nginx config +RUN mkdir -p /etc/nginx/conf.d /var/run/nginx +COPY system/nginx/nginx.conf /etc/nginx/nginx.conf +COPY system/nginx/nginx-site.conf /etc/nginx/conf.d/default.conf + +# PHP config +RUN mkdir -p /var/log/php /var/lib/php/sessions && \ + chown -R www-data:www-data /var/log/php /var/lib/php/sessions +COPY system/php.ini /usr/local/etc/php/conf.d/99-app.ini + +# Create log directories (supervisor uses /var/log/supervisor) +RUN mkdir -p /var/log/supervisor /var/log/cron + +# Copy supervisor configuration +COPY system/nginx/supervisord.conf /etc/supervisor/conf.d/supervisord.conf + +# Crontab (busybox crond reads /etc/crontabs/root for the root user) +COPY system/crontab /etc/crontabs/root +RUN chmod 0600 /etc/crontabs/root + +# Certbot (Let's Encrypt) setup +RUN mkdir -p /etc/letsencrypt /var/lib/letsencrypt /var/log/letsencrypt && \ + chmod 700 /etc/letsencrypt + +# Validate nginx config +RUN nginx -t + +# Environment variables +ENV SKIP_COMPOSER=1 \ + PHP_ERRORS_STDERR=1 \ + RUN_SCRIPTS=1 \ + REAL_IP_HEADER=1 \ + PHP_MEM_LIMIT=1024 \ + PHP_POST_MAX_SIZE=250 \ + PHP_UPLOAD_MAX_FILESIZE=250 \ + APP_ENV=production \ + APP_DEBUG=false \ + LOG_CHANNEL=stderr \ + COMPOSER_ALLOW_SUPERUSER=1 \ + SUPERVISOR_LOG_LEVEL=info + +# Define public volume +VOLUME /var/www/html/public + +# Expose HTTP and HTTPS ports +EXPOSE 80 443 + +# Entrypoint script to start Supervisor, Nginx, and PHP-FPM +COPY system/nginx/start.sh /start.sh +RUN chmod +x /start.sh + +# Download MinIO client +RUN curl -o /var/www/html/mc https://dl.min.io/client/mc/release/linux-amd64/archive/mc \ + && chmod +x /var/www/html/mc + +CMD ["/start.sh"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..0bbb767 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,118 @@ +services: + mysql: + image: mysql:8 + container_name: bowphp-mysql + restart: unless-stopped + environment: + MYSQL_DATABASE: bowphp + MYSQL_USER: bowphp + MYSQL_PASSWORD: bowphp + MYSQL_ROOT_PASSWORD: bowphp + ports: + - "3306:3306" + volumes: + - mysql_data:/var/lib/mysql + networks: + - bowphp-network + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-ubowphp", "-pbowphp"] + interval: 10s + timeout: 5s + retries: 5 + + postgres: + image: postgres:16-alpine + container_name: bowphp-postgres + restart: unless-stopped + environment: + POSTGRES_DB: bowphp + POSTGRES_USER: bowphp + POSTGRES_PASSWORD: bowphp + ports: + - "5433:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + - bowphp-network + healthcheck: + test: ["CMD-SHELL", "pg_isready -U bowphp -d bowphp"] + interval: 10s + timeout: 5s + retries: 5 + + adminer: + image: adminer:latest + container_name: bowphp-adminer + restart: unless-stopped + ports: + - "8080:8080" + environment: + ADMINER_DEFAULT_SERVER: postgres + networks: + - bowphp-network + depends_on: + - mysql + - postgres + + redis: + image: redis:7-alpine + container_name: bowphp-redis + restart: unless-stopped + ports: + - "6379:6379" + volumes: + - redis_data:/data + networks: + - bowphp-network + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + beanstalkd: + image: schickling/beanstalkd:latest + container_name: bowphp-beanstalkd + restart: unless-stopped + ports: + - "11300:11300" + volumes: + - beanstalkd_data:/var/lib/beanstalkd + networks: + - bowphp-network + healthcheck: + test: ["CMD-SHELL", "echo 'stats' | nc -w 1 localhost 11300 | grep -q current-jobs-ready"] + interval: 10s + timeout: 5s + retries: 5 + + rabbitmq: + image: rabbitmq:3-management-alpine + container_name: bowphp-rabbitmq + restart: unless-stopped + environment: + RABBITMQ_DEFAULT_USER: guest + RABBITMQ_DEFAULT_PASS: guest + ports: + - "5672:5672" + - "15672:15672" + volumes: + - rabbitmq_data:/var/lib/rabbitmq + networks: + - bowphp-network + healthcheck: + test: ["CMD", "rabbitmq-diagnostics", "ping"] + interval: 10s + timeout: 5s + retries: 5 + +volumes: + mysql_data: + postgres_data: + redis_data: + beanstalkd_data: + rabbitmq_data: + +networks: + bowphp-network: + driver: bridge diff --git a/system/crontab b/system/crontab new file mode 100644 index 0000000..792d600 --- /dev/null +++ b/system/crontab @@ -0,0 +1 @@ +# diff --git a/system/frankenphp/Caddyfile b/system/frankenphp/Caddyfile new file mode 100644 index 0000000..0fbec7d --- /dev/null +++ b/system/frankenphp/Caddyfile @@ -0,0 +1,71 @@ +{ + admin off + auto_https off + skip_install_trust + persist_config off +} + +:80 { + # Health check endpoint + @health path /health + respond @health "OK" 200 + + log { + level {$LOG_LEVEL:WARN} + format json + output stdout + } + + root * /app/public + + encode zstd br gzip + + header { + -Server + -X-Powered-By + X-Content-Type-Options "nosniff" + X-Frame-Options "SAMEORIGIN" + Referrer-Policy "strict-origin-when-cross-origin" + Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=()" + } + + @blocked { + path *.env* *.ini *.conf *.sql *.bak *.orig *.old *.git* *.lock .htaccess + path /vendor/* /node_modules/* /.git/* /storage/* /var/* + path composer.* package*.json *.yml *.yaml + } + respond @blocked 403 + + @static { + file + path *.js *.css *.jpg *.jpeg *.png *.gif *.ico *.svg *.woff *.woff2 *.ttf *.eot *.webp *.avif *.map + } + handle @static { + header Cache-Control "public, max-age=31536000, immutable" + file_server + } + + @media { + file + path *.mp4 *.webm *.mp3 *.ogg *.wav *.pdf *.zip *.tar *.gz + } + handle @media { + header Cache-Control "public, max-age=86400" + file_server + } + + php_server { + root /app/public + index index.php + } + + handle_errors { + @404 expression {http.error.status_code} == 404 + respond @404 "404 Not Found" 404 + + @5xx expression {http.error.status_code} >= 500 + respond @5xx "Server Error" {http.error.status_code} + + respond "{http.error.status_code} {http.error.status_text}" + } +} diff --git a/system/frankenphp/entrypoint.sh b/system/frankenphp/entrypoint.sh new file mode 100644 index 0000000..b83e940 --- /dev/null +++ b/system/frankenphp/entrypoint.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -e + +echo "Starting Papac & Co Application..." + +# Create required directories +mkdir -p /app/var/cache /app/var/logs /app/var/sessions /app/var/storage + +# Ensure .env.json exists +if [ ! -f /app/.env.json ]; then + echo '{}' > /app/.env.json +fi + +# Run migrations +echo "Running migrations..." +php bow migrate 2>&1 || { + echo "Migration command failed. Continuing..." +} + +# Start supervisor and wait +echo "Starting supervisord..." +exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf diff --git a/system/frankenphp/supervisord.conf b/system/frankenphp/supervisord.conf new file mode 100644 index 0000000..f86d098 --- /dev/null +++ b/system/frankenphp/supervisord.conf @@ -0,0 +1,54 @@ +[supervisord] +nodaemon=true +user=root +logfile=/app/var/logs/supervisord.log +pidfile=/app/var/supervisord.pid +childlogdir=/app/var/logs +loglevel=info + +[program:crond] +command=/usr/sbin/crond -f -l 8 +autostart=true +autorestart=true +startsecs=5 +startretries=3 +stderr_logfile=/app/var/logs/crond.log +stdout_logfile=/app/var/logs/crond.log +stdout_logfile_maxbytes=10MB +stderr_logfile_maxbytes=10MB + +[program:frankenphp] +command=/usr/local/bin/frankenphp run --config /etc/caddy/Caddyfile +user=root +autostart=true +autorestart=true +startretries=3 +stdout_logfile=/app/var/logs/frankenphp.log +stderr_logfile=/app/var/logs/frankenphp_error.log +stdout_logfile_maxbytes=10MB +stderr_logfile_maxbytes=10MB +priority=1 + +[program:beanstalkd] +command=/usr/bin/beanstalkd -l 0.0.0.0 -p 11300 +user=root +autostart=true +autorestart=true +startretries=3 +stdout_logfile=/app/var/logs/beanstalkd.log +stderr_logfile=/app/var/logs/beanstalkd_error.log +stdout_logfile_maxbytes=10MB +stderr_logfile_maxbytes=10MB +priority=2 + +[program:microservice] +directory=/var/www/html +command=php bow microservice:listen +autostart=true +autorestart=true +startsecs=5 +startretries=3 +stderr_logfile=/var/log/supervisor/microservice_worker.log +stdout_logfile=/var/log/supervisor/microservice_worker.log +stdout_logfile_maxbytes=10MB +stderr_logfile_maxbytes=10MB diff --git a/system/nginx/nginx-site.conf b/system/nginx/nginx-site.conf new file mode 100644 index 0000000..7e0e508 --- /dev/null +++ b/system/nginx/nginx-site.conf @@ -0,0 +1,78 @@ +server { + listen 80; + + # Make site accessible from http://localhost/ + server_name _; + + root /var/www/html/public; + index index.html index.htm index.php; + + # Disable sendfile as per https://docs.vagrantup.com/v2/synced-folders/virtualbox.html + sendfile off; + + # Add stdout logging + error_log /dev/stdout; + access_log /dev/stdout; + + # block access to sensitive information about git + location /.git { + deny all; + return 403; + } + + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-XSS-Protection "1; mode=block"; + add_header X-Content-Type-Options "nosniff"; + + charset utf-8; + + location / { + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; + + try_files $uri $uri/ /index.php?$query_string; + } + + location = /favicon.ico { + access_log off; + log_not_found off; + } + + location = /robots.txt { + access_log off; + log_not_found off; + } + + error_page 404 /index.php; + + location ~* \.(jpg|jpeg|gif|png|css|js|ico|webp|tiff|ttf|svg)$ { + expires 5d; + } + + location ~ \.php$ { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass 127.0.0.1:9000; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param SCRIPT_NAME $fastcgi_script_name; + include fastcgi_params; + } + + # deny access to . files + location ~ /\. { + log_not_found off; + deny all; + } + + location ~ /\.(?!well-known).* { + deny all; + } + + location ^~ /.well-known/ { + allow all; + default_type application/json; + root /var/www/html/public; + try_files $uri =404; + } +} diff --git a/system/nginx/nginx.conf b/system/nginx/nginx.conf new file mode 100644 index 0000000..9c31096 --- /dev/null +++ b/system/nginx/nginx.conf @@ -0,0 +1,33 @@ +user nginx; +worker_processes auto; +error_log /dev/stdout; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml; + + include /etc/nginx/conf.d/*.conf; +} diff --git a/system/nginx/start.sh b/system/nginx/start.sh new file mode 100644 index 0000000..c3a11af --- /dev/null +++ b/system/nginx/start.sh @@ -0,0 +1,31 @@ +#!/bin/sh +set -e + +# Set proper permissions for web root +echo "Setting up file permissions..." +chown -R www-data:www-data /var/www/html +chmod -R 755 /var/www/html +chmod -R 775 /var/www/html/var 2>/dev/null || true + +# Configure PHP settings based on environment variables +echo "memory_limit=${PHP_MEM_LIMIT}M" >> /usr/local/etc/php/conf.d/docker-php-ext-custom.ini +echo "post_max_size=${PHP_POST_MAX_SIZE}M" >> /usr/local/etc/php/conf.d/docker-php-ext-custom.ini +echo "upload_max_filesize=${PHP_UPLOAD_MAX_FILESIZE}M" >> /usr/local/etc/php/conf.d/docker-php-ext-custom.ini + +# Start PHP-FPM in the background +echo "Starting PHP-FPM..." +php-fpm & +PHP_FPM_PID=$! + +# Run Bow migration +echo "Running Bow migrations..." +php bow migrate || true + +# Start supervisord in the background (manages crond + microservice worker) +echo "Starting supervisord..." +/usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf & +SUPERVISORD_PID=$! + +# Start Nginx in the foreground (replace current process) +echo "Starting Nginx..." +exec nginx -g "daemon off;" diff --git a/system/nginx/supervisord.conf b/system/nginx/supervisord.conf new file mode 100644 index 0000000..33fc54a --- /dev/null +++ b/system/nginx/supervisord.conf @@ -0,0 +1,29 @@ +[supervisord] +nodaemon=true +user=root +logfile=/var/log/supervisor/supervisord.log +pidfile=/var/run/supervisord.pid +loglevel=info + +[program:crond] +command=/usr/sbin/crond -f -l 8 +autostart=true +autorestart=true +startsecs=5 +startretries=3 +stderr_logfile=/var/log/supervisor/crond.log +stdout_logfile=/var/log/supervisor/crond.log +stdout_logfile_maxbytes=10MB +stderr_logfile_maxbytes=10MB + +[program:microservice] +directory=/var/www/html +command=php bow microservice:listen +autostart=true +autorestart=true +startsecs=5 +startretries=3 +stderr_logfile=/var/log/supervisor/microservice_worker.log +stdout_logfile=/var/log/supervisor/microservice_worker.log +stdout_logfile_maxbytes=10MB +stderr_logfile_maxbytes=10MB diff --git a/system/php.ini b/system/php.ini new file mode 100644 index 0000000..d64183f --- /dev/null +++ b/system/php.ini @@ -0,0 +1,88 @@ +[PHP] +; Production PHP Configuration + +; Display Settings +display_errors = Off +display_startup_errors = Off +log_errors = On +error_log = /var/log/php/error.log +log_errors_max_len = 1024 + +; Error Reporting +error_reporting = E_ALL +; Production: Report all errors except deprecated and strict notices +; error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT + +; Memory and Limits +memory_limit = 1024M +max_execution_time = 300 +max_input_time = 300 +max_input_vars = 5000 + +; Upload Settings +post_max_size = 250M +upload_max_filesize = 250M +upload_tmp_dir = /tmp + +; Session Settings +session.name = PHPSESSID +session.save_handler = files +session.save_path = /var/lib/php/sessions +session.use_cookies = 1 +session.use_only_cookies = 1 +session.cookie_httponly = 1 +session.cookie_secure = 1 +session.cookie_samesite = Strict +session.gc_maxlifetime = 3600 +session.gc_probability = 1 +session.gc_divisor = 100 + +; Security Settings +expose_php = Off +default_charset = UTF-8 + +; File Uploads +file_uploads = On +upload_tmp_dir = /tmp + +; Resource Limits +default_socket_timeout = 60 +max_file_uploads = 20 + +; Date and Time +date.timezone = UTC + +; Opcache Settings (if enabled) +opcache.enable=1 +opcache.enable_cli=0 +opcache.memory_consumption=512 +opcache.interned_strings_buffer=16 +opcache.max_accelerated_files=20000 +opcache.validate_timestamps=0 +opcache.revalidate_freq=0 + +; Realpath cache +realpath_cache_size=4096K +realpath_cache_ttl=600 + +; Performance Tuning +realpath_cache_size = 4096K +realpath_cache_ttl = 600 + +; Cache Settings +; APCu Cache (if extension is enabled) +apc.enabled = 1 +apc.enable_cli = 0 +apc.shm_size = 256M +apc.ttl = 0 +apc.user_ttl = 0 +apc.serializer = php +apc.max_file_size = 1M + +; Redis Session Handler (if Redis is available) +; Uncomment to use Redis for sessions instead of files +; session.save_handler = redis +; session.save_path = "tcp://redis:6379?prefix=laravel_session_" + +; Output Buffering for caching +output_buffering = 4096 From 5502ce50eb47127b3b81d5bf8845998ebb36a605 Mon Sep 17 00:00:00 2001 From: Franck DAKIA Date: Sun, 7 Jun 2026 18:16:21 +0000 Subject: [PATCH 2/2] Update .github --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 92a6e0f..b02d88e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: operating-system: [ ubuntu-latest ] - php-versions: [ '8.1', '8.2' ] + php-versions: [ '8.2', '8.3' ] name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} steps: