Skip to content
prod 352bb92
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.

Steps at a glance:

  1. Raise the PHP-FPM timeout — 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).
  2. Raise the nginx timeout too — nginx has its own 60s fastcgi_read_timeout. You need both — nginx times out waiting for FPM, FPM times out executing the script.
  3. Detect installation status — Probe the users table, not settings — many apps fragment settings across a dozen tables (global_settings, email_settings, …), which gives false negatives.
  4. Pre-flight check — 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.
  5. Back up the DB, then run the installer — CLI path (preferred when the vendor ships one) — Many vendors ship a one-shot CLI installer that does everything the web wizard does, deterministically.
  6. Complete the wizard (web path — if there’s no CLI installer) — Browser automation drives every non-secret installer screen when available; the human pauses only for password/admin-account fields.
  7. Verify the install completed — With the installer finished (CLI or web), prove the marker, users, and log are all healthy from the terminal — but do not fabricate the marker if it’s missing.
  8. Capture credentials — immediately — Default credentials like 123456 are the #1 security risk for CodeCanyon apps. Vendors can seed multiple default accounts, so enumerate them all before Phase 6.

The installer creates the admin account, runs every migration, and seeds demo data — and reaching a real installed state is the whole point of this page. Some vendors ship a CLI installer (Some vendors: php artisan app:install) alongside (or instead of) the web wizard; prefer it when present — it’s scriptable and deterministic. Either way, 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 — CLI app:install (§5) or web wizard (§6)
    Missing✅ presentInstaller did not record a complete install — diagnose logs/timeouts and rerun the installer path; do not hand-create the marker
    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.

5. Back up the DB, then run the installer — CLI path (preferred when the vendor ships one)

Section titled “5. Back up the DB, then run the installer — CLI path (preferred when the vendor ships one)”

Many vendors ship a one-shot CLI installer that does everything the web wizard does, deterministically. One common shape is php artisan app:install, which runs key:generate + migrate:fresh + db:seed + module seeders and writes storage/installed. Prefer it when present — but migrate:fresh DROPS every table first, so back up the DB before you run it.

  1. Detect whether a CLI installer exists.

    Terminal window
    php artisan list 2>/dev/null | grep -iE "app:install|install" || echo "No CLI installer — use the web wizard (§6)"
    # Expected: a line like "app:install ...", or the fallback to the web wizard
    • ✅ You know whether to run the CLI installer (§5) or the web wizard (§6).
  2. Back up the local DB before the destructive install.

    Terminal window
    DB_NAME=$(grep DB_DATABASE .env | cut -d= -f2)
    DUMP=mariadb-dump; command -v "$DUMP" >/dev/null 2>&1 || DUMP=mysqldump
    "$DUMP" -h 127.0.0.1 -u root "$DB_NAME" > "/tmp/${DB_NAME}-pre-install-$(date +%Y%m%d-%H%M%S).sql" \
    && echo "✅ DB dumped (restore-on-regret point)" || echo "⚠️ dump failed — fix before app:install"
    # Expected: a timestamped .sql dump exists before any migrate:fresh runs
    • ✅ A pre-install DB dump exists (so the migrate:fresh drop is reversible).
  3. Run the CLI installer.

    Terminal window
    php artisan app:install # Example CLI installer: key:generate + migrate:fresh + db:seed + module package:seed + writes storage/installed
    # Expected: 0 errors; a seeded admin (often superadmin@example.com); storage/installed written
    • ✅ The installer exits with 0 errors, seeds the admin, and writes storage/installed — skip the web wizard (§6) and go to §7 to verify.

6. Complete the wizard (web path — if there’s no CLI installer)

Section titled “6. Complete the wizard (web path — if there’s no CLI installer)”

The web installer is a clicked-through flow that creates the admin account and runs every migration. When browser automation is available, the agent should drive every non-secret screen; the human owns only password, admin-account, login, and credential-entry fields.

  1. Work through each installer step with the narrow secret boundary.

    | Step | Actor | What to do | |---|---| | Requirements | 🤖 | Snapshot the screen, confirm all green, and record the web-SAPI PHP version if shown. Red = install the missing PHP extension before continuing. | | Permissions | 🤖 | Snapshot the screen and confirm all writable checks are green. Red = fix folder permissions before continuing. | | Database | 🔀 | 🤖 fills Host 127.0.0.1, Port 3306, DB [PROJECT_NAME]_local, and DB username from .env; 👤 enters the DB password only if one is required. | | Admin account | 👤 / 🤖 evidence | Human enters the admin password/account credential if this vendor creates an admin account in the wizard. If the vendor seeds defaults instead, mark the wizard screen N/A, cite the seeder grep or bounded users query that proves the default accounts, and flag every default for Phase 6 rotation. | | App settings | 🤖 | Fill app name and URL https://[PROJECT_NAME].test from Zaj-PROJECT.md / .env. | | Finish | 🤖 | Click finish and wait — expect 30–120s for 100+ migrations under the 300s timeout; if it errors, stop and inspect logs. |

    • ✅ 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 installer finished (CLI or web), prove the marker, users, and log are all healthy from the terminal — but do not fabricate the marker if it’s missing. A missing marker means the installer did not record a complete install; diagnose logs/timeouts and rerun the installer path instead of creating the marker by hand.

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

    Terminal window
    ls -la storage/installed 2>/dev/null && echo "marker: ✅" || echo "marker: ❌ — installer did not complete marker write"
    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. If the marker is missing, resolve it through the installer path — never fake it onto the filesystem.

Default credentials like 123456 are the #1 security risk for CodeCanyon apps. If the finish screen didn’t show them, find them. The vendor seeder is the definitive source — a users-table query only proves what landed after install.

  1. Enumerate all seeded default accounts from the vendor seeder.

    Terminal window
    grep -rniE "password|bcrypt|Hash::make|'1234'|admin@|superadmin" database/seeders/ | head -30
    # Expected: every seeded default credential path is visible, including multi-admin vendors
    • ✅ Every seeded default account candidate is listed from source. Vendors often seed 2+ defaults (superadmin + company/demo). Capture and rotate every one; tag each as default-change-me for Phase 6.
  2. Confirm which seeded accounts landed in the local database.

    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: vendor docs and/or the users table confirm which seeded accounts landed
    • ✅ Every default/admin email that exists after install is matched back to either vendor docs or the seeder scan.
  3. Store each default credential 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)
    • ✅ Each seeded/default credential lives 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× ✅.
  • 🤖 DB backed up — a mariadb-dump/mysqldump taken before any migrate:fresh/app:install.
  • 🔀 Installer run for real — CLI app:install (🤖) or web wizard (🤖 agent-driven when browser automation is available, 👤 secret/password fields only); a real storage/installed marker (never faked onto an empty DB).
  • 🤖 Seam intact — the install touched only runtime state, no vendor/app files changed.
  • 🤖 Install verifiedUser::count() > 0 and no errors in laravel.log.
  • 🤖 All seeded/default credentials stored — in 1Password / credentials.md, matched to seeder/docs/users evidence, and flagged for Phase 6 rotation.