diff --git a/Dockerfile b/Dockerfile index 429e180..01ebb37 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,19 +36,17 @@ COPY --from=composer:latest /usr/bin/composer /usr/bin/composer # Copy pre-built application (vendor + node_modules/public built on host) COPY --chown=www-data:www-data --chmod=755 . . -# Clear stale caches from build context +# Clear stale caches and fix permissions RUN rm -rf bootstrap/cache/*.php \ storage/framework/cache/data/* \ storage/framework/sessions/* \ - storage/framework/views/* + storage/framework/views/* \ + && chmod -R 777 storage bootstrap/cache # Optimise autoloader + discover packages RUN composer dump-autoload --optimize \ && php artisan package:discover --ansi -# Scorer binary (EaaS content scoring — no HTTP server needed) -COPY --chmod=755 bin/scorer /usr/local/bin/scorer - # Runtime scripts COPY --chmod=755 utils/scripts/redis-entrypoint.sh /usr/local/bin/redis-entrypoint.sh COPY --chmod=755 utils/scripts/laravel-entrypoint.sh /usr/local/bin/laravel-entrypoint.sh diff --git a/composer.json b/composer.json index 84d210a..bd2f639 100644 --- a/composer.json +++ b/composer.json @@ -15,8 +15,10 @@ "php": "^8.4", "guzzlehttp/guzzle": "^7.9", "laravel/framework": "^12.0", + "laravel/octane": "^2.17", "laravel/pennant": "^1.23", - "livewire/livewire": "^4.2" + "livewire/livewire": "^4.2", + "predis/predis": "^3.4" }, "require-dev": { "larastan/larastan": "^3.0", diff --git a/composer.lock b/composer.lock index dcb62a9..ff06b8a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bba8f005feba947387d7719ec55fc7dc", + "content-hash": "0c4002658f386c7fcaf38cece94ea724", "packages": [ { "name": "brick/math", @@ -1053,6 +1053,94 @@ ], "time": "2025-08-22T14:27:06+00:00" }, + { + "name": "laminas/laminas-diactoros", + "version": "3.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "60c182916b2749480895601649563970f3f12ec4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/60c182916b2749480895601649563970f3f12ec4", + "reference": "60c182916b2749480895601649563970f3f12ec4", + "shasum": "" + }, + "require": { + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^1.1 || ^2.0" + }, + "conflict": { + "amphp/amp": "<2.6.4" + }, + "provide": { + "psr/http-factory-implementation": "^1.0", + "psr/http-message-implementation": "^1.1 || ^2.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^2.2.0", + "laminas/laminas-coding-standard": "~3.1.0", + "php-http/psr7-integration-tests": "^1.4.0", + "phpunit/phpunit": "^10.5.36", + "psalm/plugin-phpunit": "^0.19.5", + "vimeo/psalm": "^6.13" + }, + "type": "library", + "extra": { + "laminas": { + "module": "Laminas\\Diactoros", + "config-provider": "Laminas\\Diactoros\\ConfigProvider" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2025-10-12T15:31:36+00:00" + }, { "name": "laravel/framework", "version": "v12.56.0", @@ -1275,6 +1363,95 @@ }, "time": "2026-03-26T14:51:54+00:00" }, + { + "name": "laravel/octane", + "version": "v2.17.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/octane.git", + "reference": "eb6150b9aa30956e3a2c04dfebf3a03c5d963a3a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/octane/zipball/eb6150b9aa30956e3a2c04dfebf3a03c5d963a3a", + "reference": "eb6150b9aa30956e3a2c04dfebf3a03c5d963a3a", + "shasum": "" + }, + "require": { + "laminas/laminas-diactoros": "^3.0", + "laravel/framework": "^10.10.1|^11.0|^12.0|^13.0", + "laravel/prompts": "^0.1.24|^0.2.0|^0.3.0", + "laravel/serializable-closure": "^1.3|^2.0", + "nesbot/carbon": "^2.66.0|^3.0", + "php": "^8.1.0", + "symfony/console": "^6.0|^7.0|^8.0", + "symfony/psr-http-message-bridge": "^2.2.0|^6.4|^7.0|^8.0" + }, + "conflict": { + "spiral/roadrunner": "<2023.1.0", + "spiral/roadrunner-cli": "<2.6.0", + "spiral/roadrunner-http": "<3.3.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.6.1", + "inertiajs/inertia-laravel": "^1.3.2|^2.0", + "laravel/scout": "^10.2.1", + "laravel/socialite": "^5.6.1", + "livewire/livewire": "^2.12.3|^3.0", + "nunomaduro/collision": "^6.4.0|^7.5.2|^8.0", + "orchestra/testbench": "^8.21|^9.0|^10.0|^11.0", + "phpstan/phpstan": "^2.1.7", + "phpunit/phpunit": "^10.4|^11.5|^12.0|^13.0", + "spiral/roadrunner-cli": "^2.6.0", + "spiral/roadrunner-http": "^3.3.0" + }, + "bin": [ + "bin/roadrunner-worker", + "bin/swoole-server" + ], + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Octane": "Laravel\\Octane\\Facades\\Octane" + }, + "providers": [ + "Laravel\\Octane\\OctaneServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\Octane\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Supercharge your Laravel application's performance.", + "keywords": [ + "frankenphp", + "laravel", + "octane", + "roadrunner", + "swoole" + ], + "support": { + "issues": "https://github.com/laravel/octane/issues", + "source": "https://github.com/laravel/octane" + }, + "time": "2026-03-18T14:14:24+00:00" + }, { "name": "laravel/pennant", "version": "v1.23.0", @@ -2634,6 +2811,69 @@ ], "time": "2025-12-27T19:41:33+00:00" }, + { + "name": "predis/predis", + "version": "v3.4.2", + "source": { + "type": "git", + "url": "https://github.com/predis/predis.git", + "reference": "2033429520d8997a7815a2485f56abe6d2d0e075" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/predis/predis/zipball/2033429520d8997a7815a2485f56abe6d2d0e075", + "reference": "2033429520d8997a7815a2485f56abe6d2d0e075", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "psr/http-message": "^1.0|^2.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.3", + "phpstan/phpstan": "^1.9", + "phpunit/phpcov": "^6.0 || ^8.0", + "phpunit/phpunit": "^8.0 || ~9.4.4" + }, + "suggest": { + "ext-relay": "Faster connection with in-memory caching (>=0.6.2)" + }, + "type": "library", + "autoload": { + "psr-4": { + "Predis\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Till Krüss", + "homepage": "https://till.im", + "role": "Maintainer" + } + ], + "description": "A flexible and feature-complete Redis/Valkey client for PHP.", + "homepage": "http://github.com/predis/predis", + "keywords": [ + "nosql", + "predis", + "redis" + ], + "support": { + "issues": "https://github.com/predis/predis/issues", + "source": "https://github.com/predis/predis/tree/v3.4.2" + }, + "funding": [ + { + "url": "https://github.com/sponsors/tillkruss", + "type": "github" + } + ], + "time": "2026-03-09T20:33:04+00:00" + }, { "name": "psr/clock", "version": "1.0.0", @@ -5134,6 +5374,93 @@ ], "time": "2026-03-24T13:12:05+00:00" }, + { + "name": "symfony/psr-http-message-bridge", + "version": "v8.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/psr-http-message-bridge.git", + "reference": "94facc221260c1d5f20e31ee43cd6c6a824b4a19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/94facc221260c1d5f20e31ee43cd6c6a824b4a19", + "reference": "94facc221260c1d5f20e31ee43cd6c6a824b4a19", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "psr/http-message": "^1.0|^2.0", + "symfony/http-foundation": "^7.4|^8.0" + }, + "conflict": { + "php-http/discovery": "<1.15" + }, + "require-dev": { + "nyholm/psr7": "^1.1", + "php-http/discovery": "^1.15", + "psr/log": "^1.1.4|^2|^3", + "symfony/browser-kit": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/runtime": "^7.4|^8.0" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\PsrHttpMessage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "PSR HTTP message bridge", + "homepage": "https://symfony.com", + "keywords": [ + "http", + "http-message", + "psr-17", + "psr-7" + ], + "support": { + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T15:14:47+00:00" + }, { "name": "symfony/routing", "version": "v7.4.8", diff --git a/docker-compose.yml b/docker-compose.yml index 55f44f0..542df68 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,11 +16,10 @@ services: APP_ENV: production APP_DEBUG: "false" OCTANE_SERVER: frankenphp - QUEUE_CONNECTION: redis - CACHE_STORE: redis - SESSION_DRIVER: redis - REDIS_HOST: 127.0.0.1 - REDIS_PORT: 6379 + CACHE_STORE: file + SESSION_DRIVER: file + extra_hosts: + - "host.docker.internal:host-gateway" volumes: - ./.env:/app/.env:ro labels: diff --git a/utils/docker/config/supervisord.prod.conf b/utils/docker/config/supervisord.prod.conf index 7e7b07f..540df4d 100644 --- a/utils/docker/config/supervisord.prod.conf +++ b/utils/docker/config/supervisord.prod.conf @@ -1,6 +1,5 @@ -# Production supervisord config for Host Hub -# Note: supervisord runs as root to manage processes and bind port 80. -# Child processes (horizon, scheduler) run as 'nobody' for security. +# lthn.io supervisord config +# FrankenPHP Octane + Redis (cache/session) [supervisord] nodaemon=true @@ -9,6 +8,12 @@ logfile=/dev/null logfile_maxbytes=0 pidfile=/run/supervisord.pid +[unix_http_server] +file=/var/run/supervisor.sock + +[supervisorctl] +serverurl=unix:///var/run/supervisor.sock + [program:redis-setup] command=/usr/local/bin/redis-entrypoint.sh autostart=true @@ -32,50 +37,20 @@ stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 [program:octane] -command=sh -c 'php artisan octane:start --server=frankenphp --host=0.0.0.0 --port=${OCTANE_PORT:-80} --admin-port=2019' -directory=/app +command=php /app/artisan octane:frankenphp --host=0.0.0.0 --port=%(ENV_OCTANE_PORT)s --workers=4 --max-requests=500 autostart=true autorestart=true -startsecs=5 priority=10 stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 -[program:horizon] -command=php artisan horizon -directory=/app -autostart=true -autorestart=true -startsecs=5 -priority=15 -user=nobody -stdout_logfile=/dev/stdout -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/stderr -stderr_logfile_maxbytes=0 - [program:scheduler] -command=sh -c "while true; do php artisan schedule:run --verbose --no-interaction; sleep 60; done" -directory=/app +command=/bin/sh -c "while true; do php /app/artisan schedule:run --no-interaction >> /dev/null 2>&1; sleep 60; done" autostart=true autorestart=true -startsecs=0 -priority=20 -user=nobody -stdout_logfile=/dev/stdout -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/stderr -stderr_logfile_maxbytes=0 - -[program:reverb] -command=php artisan reverb:start --host=0.0.0.0 --port=8080 -directory=/app -autostart=true -autorestart=true -startsecs=5 -priority=25 +priority=15 stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr