Skip to content
prod e051e98
Browse

4 · Run the installer

Objective — run the vendor’s web wizard without a 504 mid-migration: raise the PHP-FPM and nginx timeouts, pre-flight four preconditions, complete the installer in the browser, then capture the admin credentials safely.

The web installer creates the admin account, runs every migration, and seeds demo data. The single biggest failure mode is a 504 mid-migration that leaves a partial schema and no install marker — so raise the timeouts and pre-flight before you touch the browser.

The installer runs in the web SAPI, where PHP defaults to a 60-second limit. Set .user.ini (read by PHP-FPM on every request).

  1. Write the raised limits to public/.user.ini (PHP-FPM reads it from the document root, not the repo root).

    Terminal window
    cat > public/.user.ini <<'EOF'
    max_execution_time = 300
    memory_limit = 512M
    post_max_size = 100M
    upload_max_filesize = 100M
    EOF
    # Expected: public/.user.ini exists with the four keys
    • public/.user.ini carries the four raised values.
  2. Verify the CLI side is unlimited so terminal migrations never time out.

    Terminal window
    php -r "echo 'CLI max_execution_time=' . ini_get('max_execution_time') . PHP_EOL;"
    # Expected: CLI max_execution_time=0
    • ✅ CLI max_execution_time is 0 (unlimited).

The post_max_size / upload_max_filesize bumps are a bonus — CodeCanyon admin panels often accept logos, PDFs, and media larger than PHP’s 8 MB default.

nginx has its own 60s fastcgi_read_timeout. You need both — nginx times out waiting for FPM, FPM times out executing the script. Raising only one leaves the other as the bottleneck.

  1. Patch the Herd nginx config and restart.

    Terminal window
    NGINX_CONF=~/Library/Application\ Support/Herd/config/valet/Nginx/[PROJECT_NAME].test
    grep -q 'fastcgi_read_timeout' "$NGINX_CONF" 2>/dev/null || \
    sed -i '' 's/fastcgi_pass \$herd_sock;/fastcgi_pass $herd_sock;\n fastcgi_read_timeout 300;/' \
    "$NGINX_CONF"
    herd restart
    # Expected: fastcgi_read_timeout 300 now present; Herd reloads
    • ✅ nginx fastcgi_read_timeout is 300 and Herd has restarted.

Probe the users table, not settings — many apps fragment settings across a dozen tables (global_settings, email_settings, …), which gives false negatives. users is universal.

  1. Check the marker and the users table.

    Terminal window
    ls storage/installed 2>/dev/null && echo "marker: ✅" || echo "marker: ❌ missing"
    php artisan tinker --execute="echo Schema::hasTable('users') ? 'users: ✅' : 'users: ❌ empty DB';"
    # Expected: both lines print the marker + users-table state
    • ✅ The current install state is clear from the marker + users table.
  2. Map the state to its action.

    storage/installedusers tableAction
    Missing❌ emptyRun the installer (§5)
    Missing✅ presentInstaller finished but timed out before the marker — touch storage/installed, skip to page 5
    Exists✅ presentAlready installed — skip to page 5
    Exists❌ emptyCorruptedrm storage/installed and re-run the installer
    • ✅ The correct next action is chosen.
  3. Confirm the installer route prefix — most apps use /install; some use /installer or /setup.

    Terminal window
    grep -rn "prefix.*install" routes/ app/ vendor/ --include="*.php" 2>/dev/null | head -1 || echo "Check vendor docs"
    # Expected: a route prefix line, or the "Check vendor docs" fallback
    • ✅ The installer route prefix is known.

Run this before opening the wizard — it confirms all four preconditions at once. If any check fails, fix it before proceeding — this is what prevents the 504-mid-migration disaster.

  1. Run the four-in-one pre-flight.

    Terminal window
    DB_NAME=$(grep DB_DATABASE .env | cut -d= -f2)
    TABLES=$(mysql -h 127.0.0.1 -u root -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='${DB_NAME}';" 2>/dev/null)
    [ -n "$TABLES" ] && echo "✅ DB ${DB_NAME} reachable (${TABLES} tables)" || echo "❌ DB NOT reachable"
    [ ! -f storage/installed ] && echo "✅ marker absent" || echo "❌ marker EXISTS — rm storage/installed"
    INSTALL_STATUS=$(curl -sI -o /dev/null -w "%{http_code}" https://[PROJECT_NAME].test/install 2>/dev/null)
    [ "$INSTALL_STATUS" = "200" ] && echo "✅ /install HTTP 200" || echo "❌ /install HTTP ${INSTALL_STATUS} — fix page 3"
    MAX_EXEC=$(grep -oE 'max_execution_time\s*=\s*[0-9]+' public/.user.ini 2>/dev/null | grep -oE '[0-9]+$')
    [ -n "$MAX_EXEC" ] && [ "$MAX_EXEC" -ge 300 ] && echo "✅ max_execution_time=${MAX_EXEC}" || echo "❌ re-run §1"
    # Expected: 4× ✅
    • ✅ All four checks print ✅ before the wizard opens.

The web installer is a clicked-through flow that creates the admin account and runs every migration — a person drives it in the browser.

  1. Work through each installer step.

    StepWhat to enter
    RequirementsAll green (red = install the missing PHP extension)
    PermissionsAll green (red = fix folder permissions)
    DatabaseHost 127.0.0.1, Port 3306, DB [PROJECT_NAME]_local, User root, Pass (empty or yours)
    Admin accountSee the credential-choice note below
    App settingsApp name, URL https://[PROJECT_NAME].test
    FinishWait — expect 30–120s for 100+ migrations under the 300s timeout
    • ✅ The wizard reaches its finish screen without an error.

Whichever credentials you choose, the one thing never to do mid-wizard is panic-retry:

With the wizard finished, prove the marker, users, and log are all healthy from the terminal.

  1. Confirm the marker, the user count, and a clean log.

    Terminal window
    ls -la storage/installed 2>/dev/null && echo "Installed" || touch storage/installed
    php artisan tinker --execute="echo 'Users: ' . App\Models\User::count();"
    tail -20 storage/logs/laravel.log 2>/dev/null | grep -i "error\|exception" || echo "No errors"
    # Expected: marker present, Users > 0, "No errors"
    • ✅ Marker present, User::count() > 0, and no errors in the log tail.

Default credentials like 123456 are the #1 security risk for CodeCanyon apps. If the finish screen didn’t show them, find them.

  1. Locate the admin credentials.

    Terminal window
    grep -riE "admin|password|default.*user" Admin-Local/2-Docs/1-VendorDocs/ 2>/dev/null | head -10
    mysql -h 127.0.0.1 -u root -e "USE $(grep DB_DATABASE .env | cut -d= -f2); SELECT id, name, email, type FROM users LIMIT 10;"
    # Expected: the admin email surfaces from vendor docs or the users table
    • ✅ The admin email (and the default password, if applicable) is in hand.
  2. Store them in a vault — never echo or cat a stored credential.

    Terminal window
    op item create --category login --title "superadmin" --vault "[PROJECT_NAME]-Local" \
    --url "https://[PROJECT_NAME].test/admin" --tags "admin,default-change-me" \
    "username=[ADMIN_EMAIL]" "password=[ADMIN_PASSWORD]"
    # Expected: the item is created in 1Password (no value printed to the terminal)
    • ✅ Credentials live in 1Password (or the gitignored credentials.md), tagged default-change-me.

Do not mark this step done until every box below is checked.

  • 🤖 Timeouts raisedpublic/.user.ini and nginx fastcgi_read_timeout both = 300s.
  • 🤖 Pre-flight green — the check shows 4× ✅.
  • 👤 Wizard completestorage/installed marker present after the browser flow.
  • 🤖 Install verifiedUser::count() > 0 and no errors in laravel.log.
  • 🤖 Credentials stored — in 1Password / credentials.md and flagged for Phase 6 rotation.