Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
925cd3a
Refactor Laravel migration script to improve isolation handling and d…
jaydrogers Dec 5, 2025
0645221
Remove debug log from Laravel migration script to streamline output d…
jaydrogers Dec 5, 2025
9d3f0a4
Update Laravel migration script to change error message from error to…
jaydrogers Jan 14, 2026
9c2d689
Update PHP base opertating systems. Add Alpine 3.23 support (#638)
alloylab Jan 14, 2026
0260542
Update PHP extension installer version to 2.9.27
jaydrogers Jan 14, 2026
7f2836a
Update NGINX version to 1.28.1
jaydrogers Jan 14, 2026
4c4a436
Merge branch 'release/bugfixes-and-dependency-updates' into fix/larav…
jaydrogers Jan 14, 2026
7a1b6b9
Refactor and improve security headers, file blocks, etc (#631)
marns93 Jan 15, 2026
2829cf2
Update GitHub Actions to use actions/checkout@v6 in multiple workflows
jaydrogers Jan 15, 2026
f0e5dc9
Update GitHub Actions to use actions/upload-artifact@v6 for improved …
jaydrogers Jan 15, 2026
b8259a1
Update GitHub Actions to use actions/download-artifact@v7 for improve…
jaydrogers Jan 15, 2026
245e23f
Upgrade FrankenPHP to v1.11.1
jaydrogers Jan 15, 2026
ca9136d
Increase size of GitHub Actions Runners because of memory segmentatio…
jaydrogers Jan 15, 2026
0c7b634
Merge branch 'main' into release/bugfixes-and-dependency-updates
jaydrogers Jan 15, 2026
919fd47
Add security measures to block PHP execution in storage directory (#641)
jaydrogers Jan 16, 2026
3a70f32
Merge branch 'release/bugfixes-and-dependency-updates' into fix/larav…
jaydrogers Jan 16, 2026
e6681ce
Refactor Laravel version check and isolation mode handling in automat…
jaydrogers Jan 16, 2026
375839a
Update container info script to include automation status
jaydrogers Jan 17, 2026
58b4b2c
Merge branch 'release/webserver-improvements-and-fixes' into fix/lara…
jaydrogers Jan 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/common/etc/entrypoint.d/0-container-info.sh
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ Brought to you by serversideup.net
• Upload Limit: '"$UPLOAD_LIMIT"'

🔄 Runtime
• Docker CMD: '"$DOCKER_CMD"'
• Automations: '"$AUTORUN_ENABLED"'
• Docker CMD: '"$DOCKER_CMD"'
'

if [ "$PHP_OPCACHE_STATUS" = "0" ]; then
Expand Down
145 changes: 96 additions & 49 deletions src/common/etc/entrypoint.d/50-laravel-automations.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,6 @@ fi
############################################################################

artisan_migrate() {
migrate_flags=""

debug_log "Starting migrations (isolation: $AUTORUN_LARAVEL_MIGRATION_ISOLATION)"

echo "🚀 Clearing Laravel cache before attempting migrations..."
php "$APP_BASE_DIR/artisan" config:clear

Expand All @@ -73,7 +69,8 @@ artisan_migrate() {
;;
esac

# Build migration flags (used for all databases)
# Determine if isolation is intended to be used
isolation_enabled="false"
if [ "$AUTORUN_LARAVEL_MIGRATION_ISOLATION" = "true" ]; then
# Isolation only works in default mode
if [ "$AUTORUN_LARAVEL_MIGRATION_MODE" != "default" ]; then
Expand All @@ -82,14 +79,18 @@ artisan_migrate() {
fi

# Isolation requires Laravel 9.38.0+
if ! laravel_version_is_at_least "9.38.0"; then
echo "❌ $script_name: Isolated migrations require Laravel v9.38.0 or above. Detected version: $(get_laravel_version)"
return 1
if laravel_version_is_at_least "9.38.0"; then
isolation_enabled="true"
debug_log "Isolation mode enabled (Laravel version check passed)"
else
echo "⚠️ $script_name: Isolated migrations require Laravel v9.38.0 or above. Detected version: $(get_laravel_version)"
echo " Continuing without isolation mode..."
fi

migrate_flags="$migrate_flags --isolated"
fi

# Start assembling migration flags
migrate_flags=""

if [ "$AUTORUN_LARAVEL_MIGRATION_FORCE" = "true" ]; then
migrate_flags="$migrate_flags --force"
fi
Expand All @@ -98,30 +99,55 @@ artisan_migrate() {
migrate_flags="$migrate_flags --seed"
fi

# Determine if multiple databases are specified
# Helper function to run migrations for a specific database
run_migration_for_db() {
db_name="${1:-}"

# Build display name and database flag for messages/commands
if [ -n "$db_name" ]; then
db_display_name="'$db_name'"
db_flag="--database=$db_name"
else
db_display_name="default database"
db_flag=""
fi

# Wait for database connection
if ! wait_for_database_connection $db_name; then
echo "❌ $script_name: Failed to connect to $db_display_name"
return 1
fi

# Determine if --isolated can be used for this database
db_migrate_flags="$migrate_flags"
if [ "$isolation_enabled" = "true" ]; then
if db_has_migrations_table $db_name; then
db_migrate_flags="$db_migrate_flags --isolated"
debug_log "Using --isolated flag for $db_display_name"
else
echo "ℹ️ Skipping --isolated flag for $db_display_name: migrations table not ready (normal for first deployment)"
echo " The --isolated flag will be used on subsequent deployments."
fi
fi

echo "🚀 Running migrations for $db_display_name"
php "$APP_BASE_DIR/artisan" $migration_command $db_flag $db_migrate_flags
}

# Run migrations for specified database(s)
if [ -n "$AUTORUN_LARAVEL_MIGRATION_DATABASE" ]; then
databases=$(convert_comma_delimited_to_space_separated "$AUTORUN_LARAVEL_MIGRATION_DATABASE")
database_list=$(echo "$databases" | tr ',' ' ')

for db in $database_list; do
# Wait for this specific database to be ready
if ! wait_for_database_connection "$db"; then
echo "❌ $script_name: Failed to connect to database: $db"
if ! run_migration_for_db "$db"; then
return 1
fi

echo "🚀 Running migrations for database: $db"
php "$APP_BASE_DIR/artisan" $migration_command --database=$db $migrate_flags
done
else
# Wait for default database connection
if ! wait_for_database_connection; then
echo "❌ $script_name: Failed to connect to default database"
if ! run_migration_for_db; then
return 1
fi

# Run migration with default database connection
php "$APP_BASE_DIR/artisan" $migration_command $migrate_flags
fi
}

Expand Down Expand Up @@ -241,17 +267,16 @@ get_laravel_version() {
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
# Capture artisan output
if ! artisan_version_output=$(php "$APP_BASE_DIR/artisan" --version 2>/dev/null); then
echo "❌ $script_name: Failed to execute artisan command" >&2
return 1
fi

debug_log "Raw artisan output: $artisan_version_output"

# 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)
Expand All @@ -261,7 +286,7 @@ get_laravel_version() {
echo "$laravel_version"
return 0
else
echo "❌ $script_name: Failed to determine Laravel version" >&2
echo "❌ $script_name: Failed to determine Laravel version from: $artisan_version_output" >&2
return 1
fi
}
Expand All @@ -286,33 +311,55 @@ laravel_version_is_at_least() {
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) }'
}
# Extract version components using cut (POSIX compliant)
cur_major=$(echo "$current_version" | cut -d. -f1)
cur_minor=$(echo "$current_version" | cut -d. -f2)
cur_patch=$(echo "$current_version" | cut -d. -f3)

req_major=$(echo "$required_version" | cut -d. -f1)
req_minor=$(echo "$required_version" | cut -d. -f2)
req_patch=$(echo "$required_version" | cut -d. -f3)

normalized_current=$(normalize_version "$current_version")
normalized_required=$(normalize_version "$required_version")
# Default patch to 0 if not specified
: "${cur_patch:=0}"
: "${req_patch:=0}"

# 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
# Numeric comparison (POSIX arithmetic expansion handles this correctly)
# Compare major version
if [ "$cur_major" -gt "$req_major" ]; then
return 0
elif [ "$cur_major" -lt "$req_major" ]; then
return 1
fi

# Major versions equal, compare minor
if [ "$cur_minor" -gt "$req_minor" ]; then
return 0
elif [ "$cur_minor" -lt "$req_minor" ]; then
return 1
fi

# Minor versions equal, compare patch
if [ "$cur_patch" -ge "$req_patch" ]; then
return 0
fi

return 1
}

db_has_migrations_table() {
database_arg="${1:-}"

if [ -n "$database_arg" ]; then
php "$APP_BASE_DIR/artisan" migrate:status --database="$database_arg" > /dev/null 2>&1
else
return 1 # Failure: current version is < required version
php "$APP_BASE_DIR/artisan" migrate:status > /dev/null 2>&1
fi
}

Expand All @@ -324,9 +371,9 @@ test_db_connection() {
# Pass database connection name only if specified (not empty)
database_arg="${1:-}"
if [ -n "$database_arg" ]; then
php "$AUTORUN_LIB_DIR/laravel/test-db-connection.php" "$APP_BASE_DIR" "$AUTORUN_LARAVEL_MIGRATION_MODE" "$AUTORUN_LARAVEL_MIGRATION_ISOLATION" "$database_arg"
php "$AUTORUN_LIB_DIR/laravel/test-db-connection.php" "$APP_BASE_DIR" "$AUTORUN_LARAVEL_MIGRATION_MODE" "$database_arg"
else
php "$AUTORUN_LIB_DIR/laravel/test-db-connection.php" "$APP_BASE_DIR" "$AUTORUN_LARAVEL_MIGRATION_MODE" "$AUTORUN_LARAVEL_MIGRATION_ISOLATION"
php "$AUTORUN_LIB_DIR/laravel/test-db-connection.php" "$APP_BASE_DIR" "$AUTORUN_LARAVEL_MIGRATION_MODE"
fi
}

Expand Down
33 changes: 7 additions & 26 deletions src/common/etc/entrypoint.d/lib/laravel/test-db-connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@
* This script tests if the Laravel application can connect to its configured database.
* It's designed to be called from shell scripts during container initialization.
*
* Usage: php test-db-connection.php /path/to/app/base/dir [migration_mode] [migration_isolation] [database_connection]
* Usage: php test-db-connection.php /path/to/app/base/dir [migration_mode] [database_connection]
*
* Arguments:
* app_base_dir - Path to Laravel application root
* migration_mode - Migration mode: 'default', 'fresh', or 'refresh' (optional, defaults to 'default')
* migration_isolation - Whether to run migrations in isolation (optional, defaults to 'false')
* app_base_dir - Path to Laravel application root
* migration_mode - Migration mode: 'default', 'fresh', or 'refresh' (optional, defaults to 'default')
* database_connection - Name of the database connection to test (optional, defaults to 'default')
*
* Exit codes:
Expand All @@ -21,15 +20,14 @@
*/

// Validate arguments
if ($argc < 2 || $argc > 5) {
fwrite(STDERR, "Usage: php test-db-connection.php /path/to/app/base/dir [migration_mode] [migration_isolation] [database_connection]\n");
if ($argc < 2 || $argc > 4) {
fwrite(STDERR, "Usage: php test-db-connection.php /path/to/app/base/dir [migration_mode] [database_connection]\n");
exit(1);
}

$appBaseDir = $argv[1];
$migrationMode = $argc >= 3 ? $argv[2] : 'default';
$migrationIsolation = $argc >= 4 ? $argv[3] : 'false';
$databaseConnection = $argc >= 5 ? $argv[4] : null;
$databaseConnection = $argc >= 4 ? $argv[3] : null;

// Validate migration mode
$validModes = ['default', 'fresh', 'refresh'];
Expand All @@ -38,13 +36,6 @@
exit(1);
}

// Validate migration isolation
$validIsolations = ['true', 'false'];
if (!in_array($migrationIsolation, $validIsolations)) {
fwrite(STDERR, "Error: Invalid migration isolation '{$migrationIsolation}'. Must be one of: " . implode(', ', $validIsolations) . "\n");
exit(1);
}

// Validate that the app base directory exists
if (!is_dir($appBaseDir)) {
fwrite(STDERR, "Error: App base directory does not exist: {$appBaseDir}\n");
Expand Down Expand Up @@ -126,17 +117,7 @@
exit(1);
}

// For isolated migrations, the database file must exist (even in default mode)
if ($migrationIsolation === 'true') {
fwrite(STDERR, "SQLite database file does not exist: {$dbPath}\n");
fwrite(STDERR, "Isolated migrations require the database file to exist before running.\n");
fwrite(STDERR, "Either:\n");
fwrite(STDERR, " 1. Create the database (ensure it has read and write permissions for your user): touch {$dbPath}\n");
fwrite(STDERR, " 2. Set AUTORUN_LARAVEL_MIGRATION_ISOLATION=false to let migrations create it\n");
exit(1);
}

// Directory exists and is writable - migrations can create the database file (default mode only)
// Directory exists and is writable - migrations can create the database file
fwrite(STDOUT, "SQLite database directory is ready - migrations will create database\n");
exit(0);
}
Expand Down
Loading