diff --git a/.cursor/rules b/.cursor/rules index a54d85222..e869b89bd 100644 --- a/.cursor/rules +++ b/.cursor/rules @@ -17,7 +17,8 @@ You are a highly skilled PHP system administrator tasked with maintaining open s - Follow the best practices for security, performance, and developer experience. - Write clean, maintainable and technically accurate code. - All entrypoint scripts for the Docker images must be POSIX compliant and able to be executed with /bin/sh. - - Bash scripts must be compatible with Linux, WSL2, and MacOS (Bash v3). + - Any /bin/sh scripts must be compatible with Debian and Alpine Linux. + - For any /bin/bash scripts, these should work with MacOS, Linux, and WSL2. - Never use an approach you're not confident about. If you're unsure about something, ask for clarity. This project is open source and the code is available on GitHub, so be sure to follow best practices to make it easy for others to understand, modify, and contribute to the project. \ No newline at end of file diff --git a/README.md b/README.md index cfdb1a332..eecd140ce 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ We'd like to specifically thank a few folks for taking the time for being a soun Please check out all of their work: - [Chris Fidao](https://twitter.com/fideloper) -- [Joel Clermont](https://twitter.com/joelclermont) +- [Joel Clermont](https://x.com/jclermont) - [Patricio](https://twitter.com/PatricioOnCode) ## About Us diff --git a/docs/content/docs/4.laravel/1.laravel-automations.md b/docs/content/docs/4.laravel/1.laravel-automations.md index f0431c727..9ce8782ff 100644 --- a/docs/content/docs/4.laravel/1.laravel-automations.md +++ b/docs/content/docs/4.laravel/1.laravel-automations.md @@ -5,43 +5,85 @@ layout: docs --- # Laravel Automations -`serversideup/php` has a "Laravel Automations" script that helps you automate certain steps during your deployments. By default, the script is **DISABLED**. We only recommend enabling this script in production environments. +`serversideup/php` has a "Laravel Automations" script that helps automate common tasks to maintain your Laravel application and improve it's performance. By default, the script is **DISABLED**. We only recommend enabling this script in production environments. + +## What the script does ::note -`AUTORUN_ENABLED` must be set to `true` to enable the script. See our [variable reference document](/docs/reference/environment-variable-specification) for more details. +In order for this script to run,`AUTORUN_ENABLED` must be set to `true`. Once the main part of the script is enabled, you can control the individual tasks by setting the corresponding environment variables to `true` or `false`. See our [variable reference document](/docs/reference/environment-variable-specification) for more details. :: -## What the script does -This script executes on container start up and performs the following tasks: +| Environment Variable | Laravel Command | Description | +| -------------------- | -------------- | ----------- | +| `AUTORUN_LARAVEL_STORAGE_LINK` | `php artisan storage:link` | Creates a symbolic link from `public/storage` to `storage/app/public`. | +| `AUTORUN_LARAVEL_MIGRATION` | `php artisan migrate` | Runs migrations. | +| `AUTORUN_LARAVEL_OPTIMIZE` | `php artisan optimize` | Optimizes the application. | +| `AUTORUN_LARAVEL_CONFIG_CACHE` | `php artisan config:cache` | Caches the configuration files into a single file. | +| `AUTORUN_LARAVEL_ROUTE_CACHE` | `php artisan route:cache` | Caches the routes. | +| `AUTORUN_LARAVEL_VIEW_CACHE` | `php artisan view:cache` | Caches the views. | +| `AUTORUN_LARAVEL_EVENT_CACHE` | `php artisan event:cache` | Creates a manifest of all your application's events and listeners. | + +## php artisan storage:link +Creates a symbolic link from `public/storage` to `storage/app/public`. + +[Read more about storage links →](https://laravel.com/docs/12.x/filesystem#the-public-disk) -### php artisan migrate +## php artisan migrate Before running migrations, we ensure the database is online and ready to accept connections. By default, we will wait 30 seconds before timing out. -You can enable the [`--isolated`](https://laravel.com/docs/11.x/migrations#running-migrations) flag by setting `AUTORUN_LARAVEL_MIGRATION_ISOLATION` to `true`, which will ensure no other containers are running a migration. +You can enable the [`--isolated`](https://laravel.com/docs/12.x/migrations#running-migrations) flag by setting `AUTORUN_LARAVEL_MIGRATION_ISOLATION` to `true`, which will ensure no other containers are running a migration. **Special Notes for Isolated Migrations:** - Requires Laravel v9.38.0+ - Your database must support database locks (meaning SQLite is not supported) -### php artisan storage:link -This command creates a symbolic link from `public/storage` to `storage/app/public`. +[Read more about migrations →](https://laravel.com/docs/12.x/migrations#running-migrations) + +## php artisan optimize +Laravel comes with an artisan command called `optimize`, which will optimize the application by caching the configuration, routes, views, and events all in one command. + +You can disable any cache features by setting the corresponding environment variable to `false` (for example, `AUTORUN_LARAVEL_CONFIG_CACHE` would disable configuration caching). + +If your application is running Laravel v11.38.0 or higher, we will utilize the `optimize --except` parameter to exclude any cache features you have disabled. Otherwise, we will run the individual optimizations separately. + +It's possible to disable the `optimize` command by setting `AUTORUN_LARAVEL_OPTIMIZE` to `false`, but the major advantage of using the `optimize` command is other dependencies may hook into this action and run other commands. -### php artisan config:cache +[Read more about optimizing Laravel →](https://laravel.com/docs/12.x/deployment#optimization) + +## php artisan config:cache This command caches all configuration files into a single file, which can then be quickly loaded by Laravel. Once the configuration is cache, the `.env` file will no longer be loaded. -[Read more about configuration caching →](https://laravel.com/docs/11.x/configuration#configuration-caching) +[Read more about configuration caching →](https://laravel.com/docs/12.x/configuration#configuration-caching) -### php artisan route:cache +## php artisan route:cache This command caches the routes, dramatically decrease the time it takes to register all of your application's routes. After running this command, your cached routes file will be loaded on every request. -[Read more about route caching →](https://laravel.com/docs/11.x/routing#route-caching) +[Read more about route caching →](https://laravel.com/docs/12.x/routing#route-caching) -### php artisan view:cache +## php artisan view:cache This command caches all of the views in your application, which can greatly decrease the time it takes to render your views. -[Read more about view caching →](https://laravel.com/docs/11.x/views#optimizing-views) +[Read more about view caching →](https://laravel.com/docs/12.x/views#optimizing-views) -### php artisan event:cache +## php artisan event:cache This command creates a manifest of all your application's events and listeners, which can greatly speed up the process of registering them with Laravel. -[Read more about event caching →](https://laravel.com/docs/11.x/events#event-discovery-in-production) \ No newline at end of file +[Read more about event caching →](https://laravel.com/docs/12.x/events#event-discovery-in-production) + +## Debugging the AUTORUN script +It's very important to understand the nature of how containerized environments work when debugging the AUTORUN script. In some cases, some users may become frustrated when they push an update but their changes are never deployed. + +In most cases, this is due to a bug in their application code that causes a migration or some other process to fail. + +::note +If a failure occurs in the Laravel Automations script, it will exit with a non-zero exit code -- preventing the container from starting. +:: + +If you are experiencing issues, you can enable the `AUTORUN_DEBUG` environment variable to get more detailed ouput of what could be going wrong. + +If you need even more information, you can set `LOG_OUTPUT_LEVEL` to `debug` to get **A TON** of output of what's exactly happening. + +### Preventing issues with the AUTORUN script +- Ensure you are running the latest version of `serversideup/php` +- Ensure you have dependencies installed before calling this script +- Use automated testing to catch issues before deploying \ No newline at end of file diff --git a/docs/content/docs/5.customizing-the-image/3.adding-your-own-start-up-scripts.md b/docs/content/docs/5.customizing-the-image/3.adding-your-own-start-up-scripts.md index 2314e936d..5121c977a 100644 --- a/docs/content/docs/5.customizing-the-image/3.adding-your-own-start-up-scripts.md +++ b/docs/content/docs/5.customizing-the-image/3.adding-your-own-start-up-scripts.md @@ -37,11 +37,6 @@ Anything in the `/etc/entrypoint.d` directory are scripts that are intended to r Instead, learn about [using S6 overlay](/docs/guide/using-s6-overlay) so your services can be properly initialized and monitored. See the [S6 Overylay project](https://github.com/just-containers/s6-overlay) for more details on how to write your own S6 service. -## Don't use `exit 0` in your script -If you use `exit 0` in your script, it will stop the execution of the rest of the entrypoint scripts. We recommend using `return 0` instead. [See this discussion](https://github.com/serversideup/docker-php/issues/481#issuecomment-2463082306) for more details on why. - -Long story short, we don't use subshells to execute your scripts, so `exit 0` will not work as expected. We do this because we want to ensure your script has access to the environment variables that are set in the entrypoint scripts. - ## Example: Create a custom entrypoint script In this example, let's create a `99-my-script.sh` so it executes after all the other default scripts. diff --git a/docs/content/docs/7.reference/1.environment-variable-specification.md b/docs/content/docs/7.reference/1.environment-variable-specification.md index 8a7f60792..0e591a2bd 100644 --- a/docs/content/docs/7.reference/1.environment-variable-specification.md +++ b/docs/content/docs/7.reference/1.environment-variable-specification.md @@ -20,7 +20,9 @@ We like to customize our images on a per app basis using environment variables. `APACHE_THREAD_LIMIT`
*Default: "64"*|Set the maximum configured value for ThreadsPerChild for the lifetime of the Apache httpd process. (Official docs)|fpm-apache `APACHE_THREADS_PER_CHILD`
*Default: "25"*|This directive sets the number of threads created by each child process. (Official docs)|fpm-apache `APP_BASE_DIR`
*Default: "/var/www/html"*|Change this only if you mount your application to a different directory within the container. ℹ️ Be sure to change `NGINX_WEBROOT`, `APACHE_DOCUMENT_ROOT`, `UNIT_WEBROOT`, etc if it applies to your use case as well.|all +`AUTORUN_DEBUG`
*Default: "false"*|Enable debug mode for the Laravel automations. | all `AUTORUN_ENABLED`
*Default: "false"*|Enable or disable all automations. It's advised to set this to `false` in certain CI environments (especially during a composer install). If this is set to `false`, all `AUTORUN_*` behaviors will also be disabled.| all +`AUTORUN_LARAVEL_OPTIMIZE`
*Default: "false"*|Automatically run "php artisan optimize" on container, attempting to `--except` in Laravel > `v11.38.0` (Official docs)
ℹ️ Requires `AUTORUN_ENABLED = true` to run. | all `AUTORUN_LARAVEL_CONFIG_CACHE`
*Default: "true"*|Automatically run "php artisan config:cache" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_EVENT_CACHE`
*Default: "true"*|Automatically run "php artisan event:cache" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_MIGRATION`
*Default: "true"*|Automatically run `php artisan migrate --force` on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all diff --git a/src/common/etc/entrypoint.d/0-container-info.sh b/src/common/etc/entrypoint.d/0-container-info.sh index 28d52a018..a71de57ca 100644 --- a/src/common/etc/entrypoint.d/0-container-info.sh +++ b/src/common/etc/entrypoint.d/0-container-info.sh @@ -4,9 +4,22 @@ if [ "$SHOW_WELCOME_MESSAGE" = "false" ] || [ "$LOG_OUTPUT_LEVEL" = "off" ] || [ echo "👉 $0: Container info was display was skipped." fi # Skip the rest of the script - return 0 + exit 0 fi +# Get OPcache status +PHP_OPCACHE_STATUS=$(php -r 'echo ini_get("opcache.enable");') + +if [ "$PHP_OPCACHE_STATUS" = "1" ]; then + PHP_OPCACHE_MESSAGE="✅ Enabled" +else + PHP_OPCACHE_MESSAGE="❌ Disabled" +fi + +# Get memory limits +MEMORY_LIMIT=$(php -r 'echo ini_get("memory_limit");') +UPLOAD_LIMIT=$(php -r 'echo ini_get("upload_max_filesize");') + echo ' -------------------------------------------------------------------- ____ ____ _ _ _ _ @@ -17,32 +30,33 @@ echo ' |_| Brought to you by serversideup.net ---------------------------------------------------------------------' - -PHP_OPCACHE_STATUS=$(php -r 'echo ini_get("opcache.enable");') - -if [ "$PHP_OPCACHE_STATUS" = "1" ]; then - PHP_OPCACHE_MESSAGE="✅ Enabled" -else - PHP_OPCACHE_MESSAGE="❌ Disabled" -fi +-------------------------------------------------------------------- -echo ' -🙌 To support Server Side Up projects visit: -https://serversideup.net/sponsor +📚 Documentation: https://serversideup.net/php/docs +💬 Get Help: https://serversideup.net/php/community +🙌 Sponsor: https://serversideup.net/sponsor ------------------------------------- ℹ️ Container Information --------------------------------------' -echo " -OS: $(. /etc/os-release; echo "${PRETTY_NAME}") -Docker user: $(whoami) -Docker uid: $(id -u) -Docker gid: $(id -g) -OPcache: $PHP_OPCACHE_MESSAGE -PHP Version: $(php -r 'echo phpversion();') -Image Version: $(cat /etc/serversideup-php-version) -" +------------------------------------- +📦 Versions +• Image: '"$(cat /etc/serversideup-php-version)"' +• PHP: '"$(php -r 'echo phpversion();')"' +• OS: '"$(. /etc/os-release; echo "${PRETTY_NAME}")"' + +👤 Container User +• User: '"$(whoami)"' +• UID: '"$(id -u)"' +• GID: '"$(id -g)"' + +⚡ Performance +• OPcache: '"$PHP_OPCACHE_MESSAGE"' +• Memory Limit: '"$MEMORY_LIMIT"' +• Upload Limit: '"$UPLOAD_LIMIT"' + +🔄 Runtime +• Docker CMD: '"$DOCKER_CMD"' +' if [ "$PHP_OPCACHE_STATUS" = "0" ]; then echo "👉 [NOTICE]: Improve PHP performance by setting PHP_OPCACHE_ENABLE=1 (recommended for production)." diff --git a/src/common/etc/entrypoint.d/1-log-output-level.sh b/src/common/etc/entrypoint.d/1-log-output-level.sh index 44f6cd9a3..6f93c6a0d 100644 --- a/src/common/etc/entrypoint.d/1-log-output-level.sh +++ b/src/common/etc/entrypoint.d/1-log-output-level.sh @@ -5,7 +5,7 @@ if [ "$DISABLE_DEFAULT_CONFIG" = true ]; then if [ "$LOG_OUTPUT_LEVEL" = "debug" ]; then echo "👉 $script_name: DISABLE_DEFAULT_CONFIG does not equal \"false\", so debug mode will NOT be automatically set." fi - return 0 # Exit if DISABLE_DEFAULT_CONFIG is true + exit 0 # Exit if DISABLE_DEFAULT_CONFIG is true fi ####################################### diff --git a/src/common/etc/entrypoint.d/50-laravel-automations.sh b/src/common/etc/entrypoint.d/50-laravel-automations.sh index b6f217de7..5b33e2b85 100644 --- a/src/common/etc/entrypoint.d/50-laravel-automations.sh +++ b/src/common/etc/entrypoint.d/50-laravel-automations.sh @@ -1,6 +1,263 @@ #!/bin/sh script_name="laravel-automations" +# Global configurations +: "${DISABLE_DEFAULT_CONFIG:=false}" +: "${APP_BASE_DIR:=/var/www/html}" + +# Set default values for Laravel automations +: "${AUTORUN_ENABLED:=false}" +: "${AUTORUN_DEBUG:=false}" + +# Set default values for storage link +: "${AUTORUN_LARAVEL_STORAGE_LINK:=true}" + +# Set default values for optimizations +: "${AUTORUN_LARAVEL_OPTIMIZE:=true}" +: "${AUTORUN_LARAVEL_CONFIG_CACHE:=true}" +: "${AUTORUN_LARAVEL_ROUTE_CACHE:=true}" +: "${AUTORUN_LARAVEL_VIEW_CACHE:=true}" +: "${AUTORUN_LARAVEL_EVENT_CACHE:=true}" + +# Set default values for Migrations +: "${AUTORUN_LARAVEL_MIGRATION:=true}" +: "${AUTORUN_LARAVEL_MIGRATION_ISOLATION:=false}" +: "${AUTORUN_LARAVEL_MIGRATION_TIMEOUT:=30}" + +# Set default values for Laravel version +INSTALLED_LARAVEL_VERSION="" + +############################################################################ +# Sanity Checks +############################################################################ + +debug_log() { + if [ "$LOG_OUTPUT_LEVEL" = "debug" ] || [ "$AUTORUN_DEBUG" = "true" ]; then + echo "👉 DEBUG ($script_name): $1" >&2 + fi +} + +if [ "$DISABLE_DEFAULT_CONFIG" = "true" ] || [ "$AUTORUN_ENABLED" = "false" ]; then + debug_log "Skipping Laravel automations because DISABLE_DEFAULT_CONFIG is true or AUTORUN_ENABLED is false." + exit 0 +fi + +############################################################################ +# Functions +############################################################################ + +artisan_migrate() { + count=0 + timeout=$AUTORUN_LARAVEL_MIGRATION_TIMEOUT + + debug_log "Starting migrations (timeout: ${timeout}s, isolation: $AUTORUN_LARAVEL_MIGRATION_ISOLATION)" + + echo "🚀 Clearing Laravel cache before attempting migrations..." + php "$APP_BASE_DIR/artisan" config:clear + + # Do not exit on error for this loop + set +e + echo "⚡️ Attempting database connection..." + while [ $count -lt "$timeout" ]; do + if [ "$AUTORUN_DEBUG" = "true" ]; then + # Show output when debug is enabled + test_db_connection + else + # Otherwise suppress output + test_db_connection > /dev/null 2>&1 + fi + status=$? + if [ $status -eq 0 ]; then + echo "✅ Database connection successful." + break + else + # Only log every 5 attempts to reduce noise + if [ $((count % 5)) -eq 0 ]; then + debug_log "Connection attempt $((count + 1))/$timeout failed (status: $status)" + fi + echo "Waiting on database connection, retrying... $((timeout - count)) seconds left" + count=$((count + 1)) + sleep 1 + fi + done + + # Re-enable exit on error + set -e + + if [ $count -eq "$timeout" ]; then + echo "❌ $script_name: Database connection failed after multiple attempts." + debug_log "Database connection timed out after $timeout seconds" + return 1 + fi + + if [ "$AUTORUN_LARAVEL_MIGRATION_ISOLATION" = "true" ] && laravel_version_is_at_least "9.38.0"; then + debug_log "Running migrations with --isolated flag" + echo "🚀 Running migrations: \"php artisan migrate --force --isolated\"..." + php "$APP_BASE_DIR/artisan" migrate --force --isolated + else + debug_log "Running standard migrations" + echo "🚀 Running migrations: \"php artisan migrate --force\"..." + php "$APP_BASE_DIR/artisan" migrate --force + fi +} + +artisan_storage_link() { + if [ -d "$APP_BASE_DIR/public/storage" ]; then + echo "✅ Storage already linked..." + else + echo "🔐 Running storage link: \"php artisan storage:link\"..." + php "$APP_BASE_DIR/artisan" storage:link + fi +} + +artisan_optimize() { + # Case 1: All optimizations are enabled - use simple optimize command + if [ "$AUTORUN_LARAVEL_OPTIMIZE" = "true" ] && \ + [ "$AUTORUN_LARAVEL_CONFIG_CACHE" = "true" ] && \ + [ "$AUTORUN_LARAVEL_ROUTE_CACHE" = "true" ] && \ + [ "$AUTORUN_LARAVEL_VIEW_CACHE" = "true" ] && \ + [ "$AUTORUN_LARAVEL_EVENT_CACHE" = "true" ]; then + echo "🚀 Running optimize command: \"php artisan optimize\"..." + if ! php "$APP_BASE_DIR/artisan" optimize; then + echo "❌ Laravel optimize failed" + return 1 + fi + return 0 + fi + + # Case 2: AUTORUN_LARAVEL_OPTIMIZE is true but some optimizations disabled + if [ "$AUTORUN_LARAVEL_OPTIMIZE" = "true" ] && laravel_version_is_at_least "11.38.0"; then + echo "🛠️ Preparing optimizations..." + except="" + + # Build except string for disabled optimizations + [ "$AUTORUN_LARAVEL_CONFIG_CACHE" = "false" ] && except="${except:+${except},}config" + [ "$AUTORUN_LARAVEL_ROUTE_CACHE" = "false" ] && except="${except:+${except},}routes" + [ "$AUTORUN_LARAVEL_VIEW_CACHE" = "false" ] && except="${except:+${except},}views" + [ "$AUTORUN_LARAVEL_EVENT_CACHE" = "false" ] && except="${except:+${except},}events" + + echo "🛠️ Running optimizations: \"php artisan optimize ${except:+--except=${except}}\"..." + if ! php "$APP_BASE_DIR/artisan" optimize ${except:+--except=${except}}; then + echo "$script_name: ❌ Laravel optimize failed" + return 1 + fi + return 0 + fi + + if [ "$AUTORUN_LARAVEL_OPTIMIZE" = "true" ] && ! laravel_version_is_at_least "11.38.0"; then + echo "ℹ️ Granular optimizations require Laravel v11.38.0 or above, using individual optimizations instead..." + fi + + # Case 3: Individual optimizations when AUTORUN_LARAVEL_OPTIMIZE is false + has_error=0 + + if [ "$AUTORUN_LARAVEL_CONFIG_CACHE" = "true" ]; then + echo "🚀 Caching config: \"php artisan config:cache\"..." + php "$APP_BASE_DIR/artisan" config:cache || has_error=1 + fi + + if [ "$AUTORUN_LARAVEL_ROUTE_CACHE" = "true" ]; then + echo "🚀 Caching routes: \"php artisan route:cache\"..." + php "$APP_BASE_DIR/artisan" route:cache || has_error=1 + fi + + if [ "$AUTORUN_LARAVEL_VIEW_CACHE" = "true" ]; then + echo "🚀 Caching views: \"php artisan view:cache\"..." + php "$APP_BASE_DIR/artisan" view:cache || has_error=1 + fi + + if [ "$AUTORUN_LARAVEL_EVENT_CACHE" = "true" ]; then + echo "🚀 Caching events: \"php artisan event:cache\"..." + php "$APP_BASE_DIR/artisan" event:cache || has_error=1 + fi + + return $has_error +} + +get_laravel_version() { + # Return cached version if already set + if [ -n "$INSTALLED_LARAVEL_VERSION" ]; then + debug_log "Using cached Laravel version: $INSTALLED_LARAVEL_VERSION" + echo "$INSTALLED_LARAVEL_VERSION" + return 0 + fi + + debug_log "Detecting Laravel version..." + # Use 2>/dev/null to handle potential PHP warnings + artisan_version_output=$(php "$APP_BASE_DIR/artisan" --version 2>/dev/null) + + # Check if command was successful + if [ $? -ne 0 ]; then + echo "❌ $script_name: Failed to execute artisan command" >&2 + return 1 + fi + + # Extract version number using sed (POSIX compliant) + # Using a more strict pattern that matches "Laravel Framework X.Y.Z" + laravel_version=$(echo "$artisan_version_output" | sed -e 's/^Laravel Framework \([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*$/\1/') + + # Validate that we got a version number (POSIX compliant regex) + if echo "$laravel_version" | grep '^[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$' >/dev/null 2>&1; then + INSTALLED_LARAVEL_VERSION="$laravel_version" + debug_log "Detected Laravel version: $laravel_version" + echo "$laravel_version" + return 0 + else + echo "❌ $script_name: Failed to determine Laravel version" >&2 + return 1 + fi +} + +laravel_is_installed() { + if [ ! -f "$APP_BASE_DIR/artisan" ]; then + return 1 + fi + + if [ ! -d "$APP_BASE_DIR/vendor" ]; then + return 1 + fi + + return 0 +} + +laravel_version_is_at_least() { + required_version="$1" + + if [ -z "$required_version" ]; then + echo "❌ $script_name - Usage: laravel_version_is_at_least " >&2 + return 1 + fi + + # Validate required version format + if ! echo "$required_version" | grep -Eq '^[0-9]+\.[0-9]+(\.[0-9]+)?$'; then + echo "❌ $script_name - Invalid version requirement format: $required_version" >&2 + return 1 + fi + + current_version=$(get_laravel_version) + if [ $? -ne 0 ]; then + echo "❌ $script_name: Failed to get Laravel version" >&2 + return 1 + fi + + # normalize_version() takes a version string and ensures it has 3 parts + normalize_version() { + echo "$1" | awk -F. '{ print $1"."$2"."(NF>2?$3:0) }' + } + + normalized_current=$(normalize_version "$current_version") + normalized_required=$(normalize_version "$required_version") + + # Use sort -V to get the lower version, then compare it with required version + # This works in BusyBox because we only need to check the first line of output + lowest_version=$(printf '%s\n%s\n' "$normalized_required" "$normalized_current" | sort -V | head -n1) + if [ "$lowest_version" = "$normalized_required" ]; then + return 0 # Success: current version is >= required version + else + return 1 # Failure: current version is < required version + fi +} + test_db_connection() { php -r " require '$APP_BASE_DIR/vendor/autoload.php'; @@ -32,102 +289,38 @@ test_db_connection() { " } +############################################################################ +# Main +############################################################################ -# Set default values for Laravel automations -: "${AUTORUN_ENABLED:=false}" -: "${AUTORUN_LARAVEL_MIGRATION_TIMEOUT:=30}" - -if [ "$DISABLE_DEFAULT_CONFIG" = "false" ]; then - # Check to see if an Artisan file exists and assume it means Laravel is configured. - if [ -f "$APP_BASE_DIR/artisan" ] && [ "$AUTORUN_ENABLED" = "true" ]; then - echo "Checking for Laravel automations..." - ############################################################################ - # artisan migrate - ############################################################################ - if [ "${AUTORUN_LARAVEL_MIGRATION:=true}" = "true" ]; then - count=0 - timeout=$AUTORUN_LARAVEL_MIGRATION_TIMEOUT - - echo "🚀 Clearing Laravel cache before attempting migrations..." - php "$APP_BASE_DIR/artisan" config:clear - - # Do not exit on error for this loop - set +e - echo "⚡️ Attempting database connection..." - while [ $count -lt "$timeout" ]; do - test_db_connection > /dev/null 2>&1 - status=$? - if [ $status -eq 0 ]; then - echo "✅ Database connection successful." - break - else - echo "Waiting on database connection, retrying... $((timeout - count)) seconds left" - count=$((count + 1)) - sleep 1 - fi - done - - # Re-enable exit on error - set -e - - if [ $count -eq "$timeout" ]; then - echo "Database connection failed after multiple attempts." - return 1 - fi - - echo "🚀 Running migrations..." - if [ "${AUTORUN_LARAVEL_MIGRATION_ISOLATION:=false}" = "true" ]; then - php "$APP_BASE_DIR/artisan" migrate --force --isolated - else - php "$APP_BASE_DIR/artisan" migrate --force - fi - fi +if laravel_is_installed; then + debug_log "Laravel detected: v$(get_laravel_version)" + debug_log "Automation settings:" + debug_log "- Storage Link: $AUTORUN_LARAVEL_STORAGE_LINK" + debug_log "- Migrations: $AUTORUN_LARAVEL_MIGRATION" + debug_log "- Migrations Isolation: $AUTORUN_LARAVEL_MIGRATION_ISOLATION" + debug_log "- Optimize: $AUTORUN_LARAVEL_OPTIMIZE" + debug_log "- Config Cache: $AUTORUN_LARAVEL_CONFIG_CACHE" + debug_log "- Route Cache: $AUTORUN_LARAVEL_ROUTE_CACHE" + debug_log "- View Cache: $AUTORUN_LARAVEL_VIEW_CACHE" + debug_log "- Event Cache: $AUTORUN_LARAVEL_EVENT_CACHE" - ############################################################################ - # artisan storage:link - ############################################################################ - if [ "${AUTORUN_LARAVEL_STORAGE_LINK:=true}" = "true" ]; then - if [ -d "$APP_BASE_DIR/public/storage" ]; then - echo "✅ Storage already linked..." - else - echo "🔐 Linking the storage..." - php "$APP_BASE_DIR/artisan" storage:link - fi - fi - ############################################################################ - # artisan config:cache - ############################################################################ - if [ "${AUTORUN_LARAVEL_CONFIG_CACHE:=true}" = "true" ]; then - echo "🚀 Caching Laravel config..." - php "$APP_BASE_DIR/artisan" config:cache - fi - - ############################################################################ - # artisan route:cache - ############################################################################ - if [ "${AUTORUN_LARAVEL_ROUTE_CACHE:=true}" = "true" ]; then - echo "🚀 Caching Laravel routes..." - php "$APP_BASE_DIR/artisan" route:cache - fi - - ############################################################################ - # artisan view:cache - ############################################################################ - if [ "${AUTORUN_LARAVEL_VIEW_CACHE:=true}" = "true" ]; then - echo "🚀 Caching Laravel views..." - php "$APP_BASE_DIR/artisan" view:cache - fi + echo "🤔 Checking for Laravel automations..." + if [ "$AUTORUN_LARAVEL_STORAGE_LINK" = "true" ]; then + artisan_storage_link + fi + + if [ "$AUTORUN_LARAVEL_MIGRATION" = "true" ]; then + artisan_migrate + fi - ############################################################################ - # artisan event:cache - ############################################################################ - if [ "${AUTORUN_LARAVEL_EVENT_CACHE:=true}" = "true" ]; then - echo "🚀 Caching Laravel events..." - php "$APP_BASE_DIR/artisan" event:cache - fi + if [ "$AUTORUN_LARAVEL_OPTIMIZE" = "true" ] || \ + [ "$AUTORUN_LARAVEL_CONFIG_CACHE" = "true" ] || \ + [ "$AUTORUN_LARAVEL_ROUTE_CACHE" = "true" ] || \ + [ "$AUTORUN_LARAVEL_VIEW_CACHE" = "true" ] || \ + [ "$AUTORUN_LARAVEL_EVENT_CACHE" = "true" ]; then + artisan_optimize fi else - if [ "$LOG_OUTPUT_LEVEL" = "debug" ]; then - echo "👉 $script_name: DISABLE_DEFAULT_CONFIG does not equal 'false', so automations will NOT be performed." - fi -fi + echo "👉 $script_name: Skipping Laravel automations because Laravel is not installed." +fi \ No newline at end of file diff --git a/src/common/usr/local/bin/docker-php-serversideup-entrypoint b/src/common/usr/local/bin/docker-php-serversideup-entrypoint index 879c4e447..b678f29ee 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-entrypoint +++ b/src/common/usr/local/bin/docker-php-serversideup-entrypoint @@ -32,6 +32,9 @@ fi export SERVERSIDEUP_DEFAULT_COMMAND export S6_INITIALIZED +# Export the CMD for use in initialization scripts +export DOCKER_CMD="$*" + ############################################### # Usage: docker-php-serversideup-entrypoint ############################################### @@ -42,14 +45,24 @@ export S6_INITIALIZED find /etc/entrypoint.d/ -type f -name '*.sh' | sort -V | while IFS= read -r f; do if [ -e "$f" ]; then if [ "$LOG_OUTPUT_LEVEL" = "debug" ]; then - echo "Executing $f" + echo "🔄 Executing initialization script: $f" + fi + + # Source the script in a subshell to contain exits while preserving environment + (. "$f") + exit_code=$? + + if [ "$LOG_OUTPUT_LEVEL" = "debug" ]; then + echo "📝 Script $f completed with exit code: $exit_code" fi - if ! . "$f"; then - echo "Error executing $f" >&2 - exit 1 + + # Only stop on actual errors (non-zero exit codes) + if [ $exit_code -ne 0 ]; then + echo "❌ Error: Initialization script $f failed with exit code $exit_code" >&2 + exit $exit_code fi else - echo "Warning: $f not found" >&2 + echo "⚠️ Warning: Initialization script $f not found" >&2 fi done diff --git a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions index 63afb9cde..9d4bd6909 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions +++ b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions @@ -150,6 +150,7 @@ case "$OS" in apache) DIRS=" /composer + /run /var/www /etc/apache2 /etc/ssl/private @@ -160,6 +161,7 @@ case "$OS" in nginx) DIRS=" /composer + /run /var/www /etc/nginx /var/log/nginx diff --git a/src/s6/etc/entrypoint.d/10-init-webserver-config.sh b/src/s6/etc/entrypoint.d/10-init-webserver-config.sh index e501bc66e..3151f8ab3 100644 --- a/src/s6/etc/entrypoint.d/10-init-webserver-config.sh +++ b/src/s6/etc/entrypoint.d/10-init-webserver-config.sh @@ -9,8 +9,10 @@ script_name="init-webserver-config" # Check if S6 is initialized if [ "$S6_INITIALIZED" != "true" ]; then - echo "ℹ️ [NOTICE]: Running custom command instead of web server configuration: '$*'" - return 0 + if [ "$LOG_OUTPUT_LEVEL" = "debug" ]; then + echo "👉 $script_name: S6 is not initialized, so web server configuration will NOT be performed." + fi + exit 0 fi ########## diff --git a/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check b/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check index 45a273210..e718322f8 100644 --- a/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check +++ b/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check @@ -10,8 +10,10 @@ if is_online; then echo "✅ NGINX + PHP-FPM is running correctly." exit 0 else - echo "❌ There seems to be a failure in checking the NGINX + PHP-FPM." - status_code=$(curl $curl_options -w "%{http_code}" "$healthcheck_url") - echo "HTTP Status Code: $status_code" + echo "👉 NGINX + PHP-FPM is not online. Waiting for it to start..." + if [ "$LOG_OUTPUT_LEVEL" = "debug" ]; then + status_code=$(curl $curl_options -w "%{http_code}" "$healthcheck_url") + echo "HTTP Status Code: $status_code" + fi exit 1 fi