#!/usr/bin/env bash set -euo pipefail APP_DIR=${APP_DIR:-/var/www} cd "$APP_DIR" DB_HOST=${DB_HOST:-mysql} DB_PORT=${DB_PORT:-3306} DB_USERNAME=${DB_USERNAME:-winary_user} DB_PASSWORD=${DB_PASSWORD:-winary_password} DB_DATABASE=${DB_DATABASE:-laravel_winary} APP_PORT=${APP_PORT:-8741} NPM_INSTALL_ARGS=${NPM_INSTALL_ARGS:---no-fund --no-audit --no-progress} prepare_cache_dirs() { mkdir -p bootstrap/cache \ storage/logs \ storage/framework/cache \ storage/framework/views \ storage/framework/sessions \ storage/framework/testing } clear_cached_artifacts() { rm -f bootstrap/cache/*.php } ensure_php_dependencies() { if [ -f vendor/autoload.php ]; then return fi echo "Installing PHP dependencies..." composer install --no-dev --optimize-autoloader --no-interaction --no-progress } wait_for_database() { local attempt=0 while true; do if php -r " \$host = getenv('DB_HOST') ?: 'mysql'; \$port = getenv('DB_PORT') ?: '3306'; \$db = getenv('DB_DATABASE') ?: 'laravel_winary'; \$user = getenv('DB_USERNAME') ?: 'winary_user'; \$pass = getenv('DB_PASSWORD') ?: 'winary_password'; \$dsn = \"mysql:host=\$host;port=\$port;dbname=\$db\"; try { new PDO(\$dsn, \$user, \$pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); } catch (Exception \$e) { fwrite(STDERR, \$e->getMessage()); exit(1); } "; then echo "Database is ready." break else attempt=$((attempt + 1)) echo "Database not ready (attempt ${attempt}), retrying in 2s..." sleep 2 fi done } install_node_dependencies() { if [ -d node_modules ]; then echo "Node dependencies already installed, skipping npm install." else echo "Installing Node dependencies..." npm ci $NPM_INSTALL_ARGS fi } build_assets() { echo "Building frontend assets..." npm run build } ensure_storage_symlink() { local link_path="public/storage" local desired_target="../storage/app/public" mkdir -p storage/app/public if [ -L "$link_path" ]; then local current_target current_target=$(readlink "$link_path") if [ "$current_target" = "$desired_target" ] || [ "$current_target" = "/var/www/storage/app/public" ]; then return fi echo "Updating storage symlink (was pointing to $current_target)..." rm "$link_path" elif [ -e "$link_path" ]; then echo "Removing existing public/storage to recreate symlink..." rm -rf "$link_path" fi if php artisan storage:link --relative >/dev/null 2>&1; then return fi ln -s "$desired_target" "$link_path" } ensure_docs_symlink() { local source_path="doc.html" local link_path="public/doc.html" if [ ! -f "$source_path" ]; then echo "doc.html not found at $source_path, skipping docs symlink." return fi mkdir -p public if [ -L "$link_path" ]; then local current_target current_target=$(readlink "$link_path") if [ "$current_target" = "../doc.html" ] || [ "$current_target" = "/var/www/doc.html" ]; then return fi echo "Updating doc.html symlink (was pointing to $current_target)..." rm "$link_path" elif [ -e "$link_path" ]; then echo "Removing existing public/doc.html to recreate symlink..." rm -rf "$link_path" fi ln -s ../doc.html "$link_path" } fix_permissions() { # Ensure runtime-writable dirs are accessible even on bind mounts chown -R www-data:www-data storage bootstrap/cache public/storage || true chmod -R ug+rwX storage bootstrap/cache public/storage || true } prepare_cache_dirs clear_cached_artifacts wait_for_database ensure_php_dependencies php artisan migrate:fresh --seed --force install_node_dependencies build_assets ensure_storage_symlink ensure_docs_symlink fix_permissions # Normalize command to always serve on configured APP_PORT when using artisan serve if [ "$#" -eq 0 ]; then set -- php artisan serve --host=0.0.0.0 --port="${APP_PORT}" elif [ "$1" = "php" ] && [ "${2:-}" = "artisan" ] && [ "${3:-}" = "serve" ]; then shift 3 set -- php artisan serve --host=0.0.0.0 --port="${APP_PORT}" "$@" fi exec "$@"