diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0c908e6..55f94a8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,12 +18,32 @@ on: name: build jobs: - codeception: - uses: php-forge/actions/.github/workflows/codeception.yml@main - secrets: - AUTH_TOKEN: ${{ secrets.AUTH_TOKEN }} - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - with: - codeception-command: vendor/bin/codecept run --env php-builtin --coverage-xml - coverage-file: runtime/output/coverage.xml - extensions: gd, intl, pcov + nginx: + runs-on: ubuntu-latest + + steps: + - name: Checkout. + uses: actions/checkout@v5 + + - name: Show docker version. + run: | + docker version + docker compose version + + - name: Build and start containers. + run: docker compose up -d --build + + - name: Wait for readiness. + run: | + for i in {1..60}; do + if docker exec yii2-nginx sh -lc "curl -ksS -o /dev/null -w '%{http_code}' https://localhost | grep -qE '200|302'"; then + echo "Service is ready"; exit 0; fi + sleep 2 + done + echo "Service not ready"; docker logs yii2-nginx; exit 1 + + - name: Codeceptcion build. + run: docker exec yii2-nginx vendor/bin/codecept build + + - name: Run codeception tests. + run: docker exec yii2-nginx vendor/bin/codecept run diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dec76f..b03a453 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ - Bug #172: Update badge label from `PHPUnit` to `Codeception` in `README.md` (@terabytesoftw) - Bug #175: Update allowed IPs in configuration for development purposes in `configuration.md` (@terabytesoftw) - Bug #177: Add missing config path to ECS configuration in `ecs.php` (@terabytesoftw) +- Enh #181: Implement `Nginx` stack (@terabytesoftw) +- Bug #182: Improve clarity in deployment options description and add `Nginx` badge in `README.md` (@terabytesoftw) ## 0.1.0 August 31, 2025 diff --git a/README.md b/README.md index c8bc47d..a4aee63 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,10 @@ Yii 22.0.x - Codeception + Codeception - PHPStan + PHPStan

@@ -40,23 +40,6 @@ A modern, Bootstrap 5-powered Yii2 application template designed for rapid web-a - ✅ **Modern Bootstrap 5 UI** - Responsive, mobile-first design with latest Bootstrap components. - ✅ **Testing Ready** - Codeception test suite with examples for functional and unit testing. -## Available deployment options - -### Traditional Web Servers - -Classic request-per-process model; simple, widely supported (for example, Apache). - -[![Apache](https://img.shields.io/badge/apache-%23D42029.svg?style=for-the-badge&logo=apache&logoColor=white)](https://github.com/yii2-extensions/app-basic/tree/apache) - -### High-Performance Worker Mode - -Long-running PHP workers for higher throughput and lower latency. - -[![FrankenPHP](https://img.shields.io/badge/FrankenPHP-777BB4?style=for-the-badge&logo=php&logoColor=white)](https://github.com/yii2-extensions/app-basic/tree/franken-php) -[![RoadRunner](https://img.shields.io/badge/RoadRunner-%23FF6B35.svg?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEyIDJMMjIgMTJMMTIgMjJMMiAxMkwxMiAyWiIgZmlsbD0iI0ZGRkZGRiIvPgo8cGF0aCBkPSJNOCAyTDE2IDEwTDggMThaIiBmaWxsPSIjRkY2QjM1Ii8+CjxwYXRoIGQ9Ik0xNiA2TDIwIDEwTDE2IDE0WiIgZmlsbD0iI0ZGNkIzNSIvPgo8L3N2Zz4K&logoColor=white)](https://github.com/yii2-extensions/app-basic/tree/road-runner) - -> For setup instructions, see `README.md` in each branch. - ## How it works The Yii2 Web Application Basic template provides a complete foundation for building modern web applications. Unlike starting from scratch, this template includes. @@ -78,23 +61,19 @@ The Yii2 Web Application Basic template provides a complete foundation for build ### Installation ```bash -composer create-project --prefer-dist yii2-extensions/app-basic:^0.1 app-basic +composer create-project --prefer-dist yii2-extensions/app-basic:dev-nginx app-basic cd app-basic ``` ### Quick start -Start development server +Start Nginx ```bash -# Using built-in PHP server -php -S localhost:8080 -t web - -# Or using Yii console command -./yii serve +docker-compose up -d ``` -> Your application will be available at `http://localhost:8080` or at the address set in `--address` option. +> Your application will be available at `https://localhost:8443`. #### Directory structure @@ -153,15 +132,13 @@ final class SiteController extends Controller ## Package information -[![Latest Stable Version](https://img.shields.io/packagist/v/yii2-extensions/app-basic.svg?style=for-the-badge&logo=packagist&logoColor=white&label=Stable)](https://packagist.org/packages/yii2-extensions/app-basic) -[![Total Downloads](https://img.shields.io/packagist/dt/yii2-extensions/app-basic.svg?style=for-the-badge&logo=packagist&logoColor=white&label=Downloads)](https://packagist.org/packages/yii2-extensions/app-basic) - +[![Development Status](https://img.shields.io/badge/Status-Dev-orange.svg?style=for-the-badge&logo=packagist&logoColor=white)](https://github.com/yii2-extensions/app-basic/tree/nginx) ## Quality code -[![Codecov](https://img.shields.io/codecov/c/github/yii2-extensions/app-basic.svg?branch=main&style=for-the-badge&logo=codecov&logoColor=white&label=Coverage)](https://codecov.io/github/yii2-extensions/app-basic) +[![Codecov](https://img.shields.io/codecov/c/github/yii2-extensions/app-basic.svg?branch=nginx&style=for-the-badge&logo=codecov&logoColor=white&label=Coverage)](https://codecov.io/github/yii2-extensions/app-basic) [![PHPStan Level Max](https://img.shields.io/badge/PHPStan-Level%20Max-4F5D95.svg?style=for-the-badge&logo=php&logoColor=white)](https://github.com/yii2-extensions/app-basic/actions/workflows/static.yml) -[![StyleCI](https://img.shields.io/badge/StyleCI-Passed-44CC11.svg?style=for-the-badge&logo=styleci&logoColor=white)](https://github.styleci.io/repos/165419144?branch=main) +[![StyleCI](https://img.shields.io/badge/StyleCI-Passed-44CC11.svg?style=for-the-badge&logo=styleci&logoColor=white)](https://github.styleci.io/repos/165419144?branch=nginx) ## Documentation diff --git a/codeception.yml b/codeception.yml index 9c300d2..bb00d71 100644 --- a/codeception.yml +++ b/codeception.yml @@ -6,7 +6,6 @@ paths: output: runtime/output data: tests/Support/data support: tests/Support - envs: tests/_envs actor_suffix: Tester settings: memory_limit: 1024M diff --git a/composer.lock b/composer.lock index 49a6f96..f78b6ed 100644 --- a/composer.lock +++ b/composer.lock @@ -77,12 +77,12 @@ "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "719026bb30813accb68271fee7e39552a58e9f65" + "reference": "40f256d6df5e9fab5618583f81b105d2e4143249" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/719026bb30813accb68271fee7e39552a58e9f65", - "reference": "719026bb30813accb68271fee7e39552a58e9f65", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/40f256d6df5e9fab5618583f81b105d2e4143249", + "reference": "40f256d6df5e9fab5618583f81b105d2e4143249", "shasum": "" }, "require": { @@ -130,7 +130,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.5.8" + "source": "https://github.com/composer/ca-bundle/tree/main" }, "funding": [ { @@ -142,7 +142,7 @@ "type": "github" } ], - "time": "2025-08-20T18:49:47+00:00" + "time": "2025-09-03T07:43:57+00:00" }, { "name": "composer/class-map-generator", @@ -220,12 +220,12 @@ "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "b88968cf6ad5098de2d5510e42216c4d912940cc" + "reference": "a9226c2c08f641720390f5750229c730fa4e1b83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/b88968cf6ad5098de2d5510e42216c4d912940cc", - "reference": "b88968cf6ad5098de2d5510e42216c4d912940cc", + "url": "https://api.github.com/repos/composer/composer/zipball/a9226c2c08f641720390f5750229c730fa4e1b83", + "reference": "a9226c2c08f641720390f5750229c730fa4e1b83", "shasum": "" }, "require": { @@ -323,7 +323,7 @@ "type": "github" } ], - "time": "2025-08-27T20:54:37+00:00" + "time": "2025-09-03T07:33:19+00:00" }, { "name": "composer/metadata-minifier", @@ -331,12 +331,12 @@ "source": { "type": "git", "url": "https://github.com/composer/metadata-minifier.git", - "reference": "0a38292ff395f3716454613e3a6c6eaa422456c8" + "reference": "9178fb630c1f69cb0f3b56e2052ed3d347fa171a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/metadata-minifier/zipball/0a38292ff395f3716454613e3a6c6eaa422456c8", - "reference": "0a38292ff395f3716454613e3a6c6eaa422456c8", + "url": "https://api.github.com/repos/composer/metadata-minifier/zipball/9178fb630c1f69cb0f3b56e2052ed3d347fa171a", + "reference": "9178fb630c1f69cb0f3b56e2052ed3d347fa171a", "shasum": "" }, "require": { @@ -387,13 +387,9 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2025-02-17T12:51:19+00:00" + "time": "2025-09-03T06:59:18+00:00" }, { "name": "composer/pcre", @@ -478,12 +474,12 @@ "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + "reference": "b52829022cb18210bb84e44e457bd4e890f8d2a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", - "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "url": "https://api.github.com/repos/composer/semver/zipball/b52829022cb18210bb84e44e457bd4e890f8d2a7", + "reference": "b52829022cb18210bb84e44e457bd4e890f8d2a7", "shasum": "" }, "require": { @@ -536,7 +532,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.4" + "source": "https://github.com/composer/semver/tree/main" }, "funding": [ { @@ -548,7 +544,7 @@ "type": "github" } ], - "time": "2025-08-20T19:15:30+00:00" + "time": "2025-09-03T06:59:12+00:00" }, { "name": "composer/spdx-licenses", @@ -1573,12 +1569,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "dc8d57e6d71f26f368f80053b4e5bc92f6c395e5" + "reference": "cd6aabaab6fb2bf642488d3f769adf24569e329e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/dc8d57e6d71f26f368f80053b4e5bc92f6c395e5", - "reference": "dc8d57e6d71f26f368f80053b4e5bc92f6c395e5", + "url": "https://api.github.com/repos/symfony/console/zipball/cd6aabaab6fb2bf642488d3f769adf24569e329e", + "reference": "cd6aabaab6fb2bf642488d3f769adf24569e329e", "shasum": "" }, "require": { @@ -1663,7 +1659,7 @@ "type": "tidelift" } ], - "time": "2025-08-29T07:40:56+00:00" + "time": "2025-09-01T13:42:51+00:00" }, { "name": "symfony/deprecation-contracts", @@ -2775,12 +2771,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "b87a95c24475492ac665704843e341873eea1b28" + "reference": "3bd28ceb20216559209766b2ad3154d7d9f3254d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/b87a95c24475492ac665704843e341873eea1b28", - "reference": "b87a95c24475492ac665704843e341873eea1b28", + "url": "https://api.github.com/repos/symfony/string/zipball/3bd28ceb20216559209766b2ad3154d7d9f3254d", + "reference": "3bd28ceb20216559209766b2ad3154d7d9f3254d", "shasum": "" }, "require": { @@ -2858,7 +2854,7 @@ "type": "tidelift" } ], - "time": "2025-08-26T09:23:28+00:00" + "time": "2025-09-01T09:26:06+00:00" }, { "name": "ui-awesome/html-attribute", @@ -5373,12 +5369,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "b3ae3d27beb3625f29c7a01799d3f45506c53745" + "reference": "e0c4844a5a3cc0b7f18861d2121b22fe731bce2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b3ae3d27beb3625f29c7a01799d3f45506c53745", - "reference": "b3ae3d27beb3625f29c7a01799d3f45506c53745", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e0c4844a5a3cc0b7f18861d2121b22fe731bce2d", + "reference": "e0c4844a5a3cc0b7f18861d2121b22fe731bce2d", "shasum": "" }, "require": { @@ -5424,7 +5420,7 @@ "type": "github" } ], - "time": "2025-08-31T15:28:24+00:00" + "time": "2025-09-02T16:58:05+00:00" }, { "name": "phpstan/phpstan-strict-rules", @@ -5432,12 +5428,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "b043bd81404b8e4e4783c8b348c05bc08fafeb02" + "reference": "e8616380171ea0b85a98d3dabbbf9cfdc461f47d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/b043bd81404b8e4e4783c8b348c05bc08fafeb02", - "reference": "b043bd81404b8e4e4783c8b348c05bc08fafeb02", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e8616380171ea0b85a98d3dabbbf9cfdc461f47d", + "reference": "e8616380171ea0b85a98d3dabbbf9cfdc461f47d", "shasum": "" }, "require": { @@ -5473,7 +5469,7 @@ "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.x" }, - "time": "2025-08-30T08:29:42+00:00" + "time": "2025-09-01T15:58:43+00:00" }, { "name": "phpunit/php-code-coverage", @@ -5481,12 +5477,12 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "96dc0466673e215bf5536301039017f03cd45c6b" + "reference": "da2cdaff87220fa641e7652364281b736e4347e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/96dc0466673e215bf5536301039017f03cd45c6b", - "reference": "96dc0466673e215bf5536301039017f03cd45c6b", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/da2cdaff87220fa641e7652364281b736e4347e0", + "reference": "da2cdaff87220fa641e7652364281b736e4347e0", "shasum": "" }, "require": { @@ -5543,7 +5539,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.3.5" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.3.6" }, "funding": [ { @@ -5563,7 +5559,7 @@ "type": "tidelift" } ], - "time": "2025-09-01T08:07:42+00:00" + "time": "2025-09-02T05:23:14+00:00" }, { "name": "phpunit/php-file-iterator", @@ -5868,12 +5864,12 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "fb9037c729d0298d9052e92303a8bd4213659116" + "reference": "864e93c73b6e4caf189461cdbdf63853c54271e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fb9037c729d0298d9052e92303a8bd4213659116", - "reference": "fb9037c729d0298d9052e92303a8bd4213659116", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/864e93c73b6e4caf189461cdbdf63853c54271e2", + "reference": "864e93c73b6e4caf189461cdbdf63853c54271e2", "shasum": "" }, "require": { @@ -5887,7 +5883,7 @@ "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.3", - "phpunit/php-code-coverage": "^12.3.4", + "phpunit/php-code-coverage": "^12.3.6", "phpunit/php-file-iterator": "^6.0.0", "phpunit/php-invoker": "^6.0.0", "phpunit/php-text-template": "^5.0.0", @@ -5966,7 +5962,7 @@ "type": "tidelift" } ], - "time": "2025-08-29T11:33:58+00:00" + "time": "2025-09-03T06:27:18+00:00" }, { "name": "psr/event-dispatcher", diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..8049ddc --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,30 @@ +services: + yii2-nginx: + build: + args: + USER_ID: ${USER_ID:-1000} + GROUP_ID: ${GROUP_ID:-1000} + USER_NAME: ${USER_NAME:-www-data} + GROUP_NAME: ${GROUP_NAME:-www-data} + context: . + dockerfile: docker/nginx/Dockerfile + container_name: yii2-nginx + entrypoint: ["/usr/local/bin/entrypoint.sh"] + env_file: + - .env + environment: + TZ: "UTC" + YII_DEBUG: "${YII_DEBUG:-false}" + YII_ENV: "${YII_ENV:-prod}" + ports: + - '8080:80' + - '8443:443' + restart: always + tty: true + volumes: + - ./:/app + - composer_cache:/var/www/.composer/cache + working_dir: /app + +volumes: + composer_cache: diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 0000000..7ce7385 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -euo pipefail + +echo "=== Container Starting ===" +echo "Running initialization script..." + +# Ensure init script is executable +chmod +x /usr/local/bin/init.sh + +# Execute init script; replace the PID 1 shell +exec /usr/local/bin/init.sh + +# If we get here, everything went well +echo "=== Container ready ===" diff --git a/docker/init.sh b/docker/init.sh new file mode 100644 index 0000000..88ce0c4 --- /dev/null +++ b/docker/init.sh @@ -0,0 +1,101 @@ +#!/bin/bash + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +echo -e "${GREEN}Starting container setup...${NC}" + +# Create PHP-FPM socket directory with proper permissions +echo -e "${YELLOW}Creating PHP-FPM socket directory...${NC}" +mkdir -p /var/run/php +chown www-data:www-data /var/run/php +chmod 755 /var/run/php + +# Create necessary Yii2 directories if they don't exist +echo -e "${YELLOW}Creating Yii2 directories...${NC}" +mkdir -p /app/runtime/cache +mkdir -p /app/runtime/logs +mkdir -p /app/web/assets + +# Configure permissions for Yii2 directories +echo -e "${YELLOW}Setting up permissions...${NC}" + +# Try to set permissions and ownership - handle both mounted volumes and container-only scenarios +if chown -R www-data:www-data /app/runtime 2>/dev/null; then + chmod -R 775 /app/runtime + echo -e "${GREEN}✓ Runtime directory configured correctly${NC}" +else + # If chown fails (mounted volume), try chmod only + if chmod -R 777 /app/runtime 2>/dev/null; then + echo -e "${YELLOW}⚠ Runtime directory permissions set to 777 (mounted volume)${NC}" + else + echo -e "${RED}✗ Error: Could not configure runtime directory${NC}" + fi +fi + +if chown -R www-data:www-data /app/web/assets 2>/dev/null; then + chmod -R 775 /app/web/assets + echo -e "${GREEN}✓ Assets directory configured correctly${NC}" +else + # If chown fails (mounted volume), try chmod only + if chmod -R 777 /app/web/assets 2>/dev/null; then + echo -e "${YELLOW}⚠ Assets directory permissions set to 777 (mounted volume)${NC}" + else + echo -e "${RED}✗ Error: Could not configure assets directory${NC}" + fi +fi + +echo -e "${GREEN}Setup completed.${NC}" + +# Check if composer.json exists and vendor directory doesn't exist +if [ -f "/app/composer.json" ] && [ ! -d "/app/vendor" ]; then + echo -e "${YELLOW}Installing Composer dependencies...${NC}" + + # Give www-data write access without exposing the tree to everyone + chown -R www-data:www-data /app && \ + chmod -R u+rwX,g+rwX /app + + # Create and configure npm cache directory for www-data + mkdir -p /var/www/.npm + chown -R www-data:www-data /var/www/.npm + + # Install dependencies with proper environment variables + if [ "$YII_ENV" = "prod" ]; then + # Production: exclude dev dependencies and optimize autoloader + gosu www-data env \ + HOME=/var/www \ + COMPOSER_HOME=/var/www/.composer \ + COMPOSER_CACHE_DIR=/var/www/.composer/cache \ + npm_config_cache=/var/www/.npm \ + composer install --no-dev --optimize-autoloader --no-interaction + else + # Development: include dev dependencies + gosu www-data env \ + HOME=/var/www \ + COMPOSER_HOME=/var/www/.composer \ + COMPOSER_CACHE_DIR=/var/www/.composer/cache \ + npm_config_cache=/var/www/.npm \ + composer install --optimize-autoloader --no-interaction + fi + + echo -e "${GREEN}✓ Composer dependencies installed successfully${NC}" +fi + +# Copy supervisor configuration +echo -e "${YELLOW}Configuring supervisor...${NC}" + +if [ -f "/app/docker/supervisor/supervisord.conf" ]; then + cp /app/docker/supervisor/supervisord.conf /etc/supervisor/supervisord.conf + echo -e "${GREEN}✓ Supervisor configuration copied successfully${NC}" +else + echo -e "${RED}✗ Error: Supervisor configuration file not found${NC}" + exit 1 +fi + +echo -e "${GREEN}Starting supervisor daemon...${NC}" + +# Start supervisor daemon +exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf diff --git a/docker/nginx/Dockerfile b/docker/nginx/Dockerfile new file mode 100644 index 0000000..b266f32 --- /dev/null +++ b/docker/nginx/Dockerfile @@ -0,0 +1,60 @@ +FROM yiisoftware/yii2-php:8.4-fpm-nginx + +# Build arguments for user/group +ARG USER_ID=1000 +ARG GROUP_ID=1000 +ARG USER_NAME=www-data +ARG GROUP_NAME=www-data + +# Change nginx config +COPY docker/nginx/nginx.conf /etc/nginx/nginx.conf +COPY docker/nginx/default.conf /etc/nginx/conf.d/default.conf + +# Change PHP config +COPY docker/php/php.ini /usr/local/etc/php/conf.d/base.ini + +# Set document root to /app/web (Yii2 structure) +WORKDIR /app + +# Set composer environment +ENV COMPOSER_ALLOW_SUPERUSER=1 + +# Install supervisor, gosu, and Node.js (version simple) +RUN apt-get update && apt-get install -y --no-install-recommends \ + supervisor \ + curl \ + gosu \ + && curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - \ + && apt-get install -y nodejs \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +# Apply the user/group IDs to www-data +RUN usermod -u ${USER_ID} www-data && groupmod -g ${GROUP_ID} www-data + +# Create composer and npm cache directories with proper ownership +RUN mkdir -p /var/www/.composer/cache /var/www/.npm && \ + chown -R www-data:www-data /var/www/.composer /var/www/.npm + +# Copy supervisor program configs +COPY docker/supervisor/conf.d/nginx.conf /etc/supervisor/conf.d/nginx.conf +COPY docker/supervisor/conf.d/php-fpm.conf /etc/supervisor/conf.d/php-fpm.conf + +# Copy queue worker config uncommented for use with yii2-queue +#COPY docker/supervisor/conf.d/queue.conf /etc/supervisor/conf.d/queue.conf + +# Copy scripts +COPY docker/init.sh /usr/local/bin/init.sh +COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh + +# Make scripts executable and validate +RUN chmod +x /usr/local/bin/init.sh /usr/local/bin/entrypoint.sh && \ + # Convert any Windows line endings + sed -i 's/\r$//' /usr/local/bin/init.sh /usr/local/bin/entrypoint.sh && \ + # Test that scripts have valid syntax + bash -n /usr/local/bin/init.sh && \ + bash -n /usr/local/bin/entrypoint.sh && \ + echo "✓ Scripts validated successfully..." + +# Use ENTRYPOINT to guarantee execution +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] diff --git a/docker/nginx/default.conf b/docker/nginx/default.conf new file mode 100644 index 0000000..c09bc07 --- /dev/null +++ b/docker/nginx/default.conf @@ -0,0 +1,94 @@ +# HTTP server - Redirect to HTTPS +server { + listen 80; + server_name localhost; + + # Redirect all HTTP to HTTPS + return 301 https://$host$request_uri; +} + +# HTTPS server +server { + charset utf-8; + client_max_body_size 128M; + listen 443 ssl http2; + server_name localhost; + + # SSL Configuration + ssl_certificate /app/docker/ssl/localhost.pem; + ssl_certificate_key /app/docker/ssl/localhost-key.pem; + + # SSL Security Settings + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers off; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + + # Document root and index file + root /app/web; + index index.php; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + + # Logging + access_log /var/log/nginx/ssl_access.log; + error_log /var/log/nginx/ssl_error.log; + + # Yii URL rewriting (CRITICAL for Yii2) + location / { + try_files $uri $uri/ /index.php$is_args$args; + } + + # Deny PHP execution in /assets before the generic handler (Yii2 security) + location ~ ^/assets/.*\.php$ { + deny all; + return 403; + } + + # PHP handling via PHP-FPM + location ~ \.php$ { + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param HTTPS on; + fastcgi_pass 127.0.0.1:9000; + try_files $uri =404; + + # PHP execution settings + fastcgi_read_timeout 300; + fastcgi_buffer_size 128k; + fastcgi_buffers 4 256k; + fastcgi_busy_buffers_size 256k; + } + + # Block access to sensitive files + location ~ /\.git { + deny all; + } + + location ~ /\.ht { + deny all; + } + + # Block access to hidden files/directories + location ~* /\. { + deny all; + } + + # Static files handling with caching + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + try_files $uri =404; + } + + # Deny access to PHP files in specific directories + location ~ ^/(vendor|runtime)/.+\.php$ { + deny all; + return 404; + } +} diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf new file mode 100644 index 0000000..8a6b419 --- /dev/null +++ b/docker/nginx/nginx.conf @@ -0,0 +1,58 @@ +user www-data; +worker_processes auto; +pid /run/nginx.pid; +include /etc/nginx/modules-enabled/*.conf; + +events { + worker_connections 768; + multi_accept on; + use epoll; +} + +http { + # Basic Settings + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + # Security headers + server_tokens off; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # SSL Settings + ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM'; + ssl_ecdh_curve X25519:P-256; + ssl_prefer_server_ciphers on; + + # Logging Settings + 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; + error_log /var/log/nginx/error.log warn; + + # Gzip Settings + 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/atom+xml + image/svg+xml; + + # Virtual Host Configs + include /etc/nginx/conf.d/*.conf; + include /etc/nginx/sites-enabled/*; +} diff --git a/docker/php/php.ini b/docker/php/php.ini new file mode 100644 index 0000000..aa22113 --- /dev/null +++ b/docker/php/php.ini @@ -0,0 +1,25 @@ +# Global PHP configuration for the Docker container +date.timezone = UTC +display_errors = Off +expose_php = Off +log_errors = On +error_log = /proc/self/fd/2 +memory_limit = 512M +post_max_size = 150M +session.auto_start = Off +short_open_tag = Off +upload_max_filesize = 15M + +# https://symfony.com/doc/current/performance.html +# OPcache optimizations +opcache.enable = 1 +opcache.enable_cli = 1 +opcache.interned_strings_buffer = 16 +opcache.jit = tracing +opcache.jit_buffer_size = 64M +opcache.max_accelerated_files = 20000 +opcache.memory_consumption = 256 +opcache.revalidate_freq = 2 +opcache.validate_timestamps = 1 +realpath_cache_size = 4096K +realpath_cache_ttl = 120 diff --git a/docker/ssl/localhost-key.pem b/docker/ssl/localhost-key.pem new file mode 100644 index 0000000..4677e62 --- /dev/null +++ b/docker/ssl/localhost-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDJXRl0fj4a5Myo +WpAN8jevuPIlvS19Yk2243GIXf9rNt7/th4ujMYa7DyGmFE4UiXz/gj5y9vq6/3d +JNrVPqfcBHKgDdGt4V+c6bauzxhs+QilYzft/9/y/Mglt6Z9ibsvIGnRn6R8gc6W +dV2KPRUNDTdb83R0Y7CFRydTkTEYl1/mldAz5MNrlIYrr55PbQTG2fZlMXcfmJOh +TzGvux3skPRD4igTV8rxd0x5RDvcVdSCOx3vpI2ZJ3Vh28Bp934mTNo4Ihsmkn4A +nzUJsyRNAcXp//8xz9upvmy4FfqXhhR5/oOit4INMjFxkEA2CrX7CYz/EiRhyxWt +jGmxKXSVAgMBAAECggEARIxC7BtFvRYCbZVW0MiSGGnXVO2PnM1oM0g+D/QfzTnC +tD4lDCe0ENo2yIL1szyT2vmxyIeTd+eaZjvzmwBXWFjTRJUvuges6tfM7rllUUN9 +o/+gvI6MiEVxRRW9waKvbB2WqFu2PQRbdXDCxRqCHg1PuSepjy2Ym+ZbBuG4Wuwh +q9vbbqe0isp0YZr8oCeNTbR2Mi2q2DPUTP+WdA0TXulx+IwlIlYnIk0a2IGcdGpX +io9Mt3/aspZhx7uQf3yBoVMiOWRx/04t4Yc0oxuwLP4atGd7jhKZ2dfgCo6jPPet +ojIpJgN7vW3o4xRnrv0UuTq9vm2W0ZtV5Zfx4sp/gQKBgQDMBGV5s5itMXSdqoXg +4xtELye0oMYEd1ih8+ql+VP08nUeflV7vD6+Ci8/x++Azqpr9SU1KBG/9Cj2jK8k +uLdeKcEuac8DjU1eELGmDnIk4m0supEP3JyrTQCVQYw9Pwvv+jGNz2zi0xppIzoP +eObU/ftoV3Ue32c9tjMMvzlpBQKBgQD8q57knuAqTVJiIqo1LsRj+j5XQ3IpCYY7 +bVHHi607JywBE8JyerkycYCs6BU4AyZNe8Wp6YJM6qINHgvD13j7CjzSg/YEQVla +O196Ka08Latvu6D3BqR9oKy+5vto/QNxD0hGToWySZCVjbsc5QRxCbq77GZ05EpI +owj0Wz9yUQKBgEpFJ3Pgj6otINjs5QPzaU+vIvM8vBmQIPIES93UIF5BjaVmNFRx +OR4RsxWzAVuQ4LWgbsUlKyEID4mBuZjSrd5XsP3mgvg2Dn458ZIUHDWVQE1SNdDi +bR5nT9kGHbmGJCBeon+PjYIde7XenxpW1yGevFFF1VtB1OHAXGE9k/bJAoGBAJDa +wylGfDVERurD/NioiybWHE9i91vXfgEr1yqTRSkYd/3WGjAPc4Ub1S1LkH6gL6FU +SCn1GSKHkYf7pbsNGOqzH9OAm9YssNEuILqkkhBiDlrsMofsYvky2FZJjfDOvN5g +aIsGEVf5HM7ghlk2Yql7bna2PLbe+kdSUJvmhCVBAoGBAMY9+yNm9zpHYDLfgmeG +mUjUrw73G7j+ucRQI6/owgaqJWKlfULG2CQ18XhcpjJcI7R1gADwNwlJN7AjGDtg +gskDn8yoAGBvaBgkkkv3Z8rKTCGR8mh4bY6G89agY9NphigsaUJrn+4iX59K2/eK +DNFgybL+X7Mh7P8tmBt1Adua +-----END PRIVATE KEY----- diff --git a/docker/ssl/localhost.pem b/docker/ssl/localhost.pem new file mode 100644 index 0000000..7338a1f --- /dev/null +++ b/docker/ssl/localhost.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEYjCCAsqgAwIBAgIRAMJ3dsZ62GwPUcMuKwB/pBQwDQYJKoZIhvcNAQELBQAw +gYcxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEuMCwGA1UECwwlVGVy +YWJ5dGVzb2Z0d1xkZXZlbG9wZXJAVGVyYWJ5dGVzb2Z0dzE1MDMGA1UEAwwsbWtj +ZXJ0IFRlcmFieXRlc29mdHdcZGV2ZWxvcGVyQFRlcmFieXRlc29mdHcwHhcNMjUw +NzEwMjIxNjI2WhcNMjcxMDEwMjExNjI2WjBZMScwJQYDVQQKEx5ta2NlcnQgZGV2 +ZWxvcG1lbnQgY2VydGlmaWNhdGUxLjAsBgNVBAsMJVRlcmFieXRlc29mdHdcZGV2 +ZWxvcGVyQFRlcmFieXRlc29mdHcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQDJXRl0fj4a5MyoWpAN8jevuPIlvS19Yk2243GIXf9rNt7/th4ujMYa7DyG +mFE4UiXz/gj5y9vq6/3dJNrVPqfcBHKgDdGt4V+c6bauzxhs+QilYzft/9/y/Mgl +t6Z9ibsvIGnRn6R8gc6WdV2KPRUNDTdb83R0Y7CFRydTkTEYl1/mldAz5MNrlIYr +r55PbQTG2fZlMXcfmJOhTzGvux3skPRD4igTV8rxd0x5RDvcVdSCOx3vpI2ZJ3Vh +28Bp934mTNo4Ihsmkn4AnzUJsyRNAcXp//8xz9upvmy4FfqXhhR5/oOit4INMjFx +kEA2CrX7CYz/EiRhyxWtjGmxKXSVAgMBAAGjdjB0MA4GA1UdDwEB/wQEAwIFoDAT +BgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBRlhRQpnl7nIy6O/r3UOcaY +LgXBSzAsBgNVHREEJTAjgglsb2NhbGhvc3SHBH8AAAGHEAAAAAAAAAAAAAAAAAAA +AAEwDQYJKoZIhvcNAQELBQADggGBAIR0dkwyUST+W8TATCZbYThwiYuTSLYyQvyX +7ieIYWW31nAfcL/WvMt5d80p67mLPvGoGLZSayId7uf6j0LVY2Y94G1z7HOdRd7s +D7InIrLI0G7pKSXI8mSCczgpx2FD2yIkHaDCdZIYXpkkAhJ6I4hghSG2KZoJ0vhA +JPTd9VW8ydYykfE619wjZ68RUlve4wtNkmQCrJAWOZMWCNl8O/iz5cWafTr0p4kD +6qdE/gKvfyWQdc1XgPpZJkpYNw+CGVcpfE7i/++FfspTTY+XyYZClbQjMD1hNGq/ +ATsl8DR4xH5ztUaAkLBRDXpXGal7VyNoBpBot9scDR71YMsTTKf/1NdIt8gPLF4P +ULwW7UvaHQAj3klPcanEaTv/bgQxor6b8tSwMLqHcU+SdQBjYnWLuAzKTCvL9m73 +MQRRt/N+Z9McSApps0F82eSR3a1HaU5lyH6Es6dLkVnY/ksmzqwi5FADsORSH95M +GvP9WbrnCQvIW/n3T3pvmuV/BeRkVg== +-----END CERTIFICATE----- diff --git a/docker/supervisor/conf.d/nginx.conf b/docker/supervisor/conf.d/nginx.conf new file mode 100644 index 0000000..3df5802 --- /dev/null +++ b/docker/supervisor/conf.d/nginx.conf @@ -0,0 +1,12 @@ +[program:nginx] +command=/usr/sbin/nginx -g "daemon off;" +autostart=true +autorestart=true +priority=10 +killasgroup=true +stopasgroup=true +stopsignal=QUIT +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 diff --git a/docker/supervisor/conf.d/php-fpm.conf b/docker/supervisor/conf.d/php-fpm.conf new file mode 100644 index 0000000..39399ab --- /dev/null +++ b/docker/supervisor/conf.d/php-fpm.conf @@ -0,0 +1,13 @@ + +[program:php-fpm] +command=/usr/local/sbin/php-fpm --nodaemonize +autorestart=true +autostart=true +killasgroup=true +priority=5 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stopasgroup=true +stopsignal=QUIT diff --git a/docker/supervisor/conf.d/queue.conf b/docker/supervisor/conf.d/queue.conf new file mode 100644 index 0000000..f6f2f60 --- /dev/null +++ b/docker/supervisor/conf.d/queue.conf @@ -0,0 +1,16 @@ +[program:yii-queue-worker] +process_name=%(program_name)s_%(process_num)02d +command=/usr/local/bin/php /app/yii queue/listen --verbose=1 --color=0 +autorestart=true +# Enable only after installing yiisoft/yii2-queue; flipped on via image build or entrypoint. +autostart=false +# Keep demo light; can be raised later. +numprocs=1 +stopasgroup=true +killasgroup=true +stopsignal=TERM +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +user=www-data diff --git a/docker/supervisor/supervisord.conf b/docker/supervisor/supervisord.conf new file mode 100644 index 0000000..d209121 --- /dev/null +++ b/docker/supervisor/supervisord.conf @@ -0,0 +1,14 @@ +[supervisord] +logfile = /proc/self/fd/1 +logfile_maxbytes = 0 +loglevel = info +nodaemon = true +pidfile = /var/run/supervisord.pid +silent = true +user = root + +[include] +files = /etc/supervisor/conf.d/*.conf + +[supervisorctl] +serverurl = unix:///var/run/supervisor.sock diff --git a/tests/Acceptance.suite.yml b/tests/Acceptance.suite.yml index 03e52e8..2e0af2a 100644 --- a/tests/Acceptance.suite.yml +++ b/tests/Acceptance.suite.yml @@ -7,5 +7,5 @@ actor: AcceptanceTester modules: enabled: - PhpBrowser: - url: localhost:8080 + url: https://localhost:443/ step_decorators: ~ diff --git a/tests/_envs/dockerized.yml b/tests/_envs/dockerized.yml deleted file mode 100644 index 546f0cc..0000000 --- a/tests/_envs/dockerized.yml +++ /dev/null @@ -1,4 +0,0 @@ -modules: - config: - PhpBrowser: - url: https://localhost:443 diff --git a/tests/_envs/php-builtin.yml b/tests/_envs/php-builtin.yml deleted file mode 100644 index ce9c804..0000000 --- a/tests/_envs/php-builtin.yml +++ /dev/null @@ -1,9 +0,0 @@ -extensions: - enabled: - - Codeception\Extension\RunProcess: - 0: php -d variables_order=EGPCS -S localhost:8085 -t web - sleep: 1 -modules: - config: - PhpBrowser: - url: http://localhost:8085