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:
- 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). - 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. - Detect installation status — Probe the
userstable, notsettings— many apps fragment settings across a dozen tables (global_settings,email_settings, …), which gives false negatives. - 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.
- 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.
- 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.
- 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.
- Capture credentials — immediately — Default credentials like
123456are the #1 security risk for CodeCanyon apps. Vendors can seed multiple default accounts, so enumerate them all before Phase 6.
Background
Section titled “Background”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.
1. Raise the PHP-FPM timeout
Section titled “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).
-
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 = 300memory_limit = 512Mpost_max_size = 100Mupload_max_filesize = 100MEOF# Expected: public/.user.ini exists with the four keys- ✅
public/.user.inicarries the four raised values.
- ✅
-
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_timeis0(unlimited).
- ✅ CLI
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.
2. Raise the nginx timeout too
Section titled “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. Raising only one leaves the other as the bottleneck.
-
Patch the Herd nginx config and restart.
Terminal window NGINX_CONF=~/Library/Application\ Support/Herd/config/valet/Nginx/[PROJECT_NAME].testgrep -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_timeoutis300and Herd has restarted.
- ✅ nginx
3. Detect installation status
Section titled “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. users is universal.
-
Check the marker and the
userstable.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 +
userstable.
- ✅ The current install state is clear from the marker +
-
Map the state to its action.
storage/installeduserstableAction Missing ❌ empty Run the installer — CLI app:install(§5) or web wizard (§6)Missing ✅ present Installer did not record a complete install — diagnose logs/timeouts and rerun the installer path; do not hand-create the marker Exists ✅ present Already installed — skip to page 5 Exists ❌ empty Corrupted — rm storage/installedand re-run the installer- ✅ The correct next action is chosen.
-
Confirm the installer route prefix — most apps use
/install; some use/installeror/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.
4. Pre-flight check
Section titled “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.
-
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.
-
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).
-
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:freshdrop is reversible).
- ✅ A pre-install DB dump exists (so the
-
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.
- ✅ The installer exits with 0 errors, seeds the admin, and writes
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.
-
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, Port3306, 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 screenN/A, cite the seeder grep or boundedusersquery that proves the default accounts, and flag every default for Phase 6 rotation. | | App settings | 🤖 | Fill app name and URLhttps://[PROJECT_NAME].testfromZaj-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:
7. Verify the install completed
Section titled “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. 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.
-
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.
- ✅ Marker present,
8. Capture credentials — immediately
Section titled “8. Capture credentials — immediately”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.
-
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 asdefault-change-mefor Phase 6.
- ✅ Every seeded default account candidate is listed from source. Vendors often seed 2+ defaults (
-
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 -10mysql -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.
-
Store each default credential in a vault — never echo or
cata 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), taggeddefault-change-me.
- ✅ Each seeded/default credential lives in 1Password (or the gitignored
Checklist
Section titled “Checklist”Do not mark this step done until every box below is checked.
- 🤖 Timeouts raised —
public/.user.iniand nginxfastcgi_read_timeoutboth = 300s. - 🤖 Pre-flight green — the check shows 4× ✅.
- 🤖 DB backed up — a
mariadb-dump/mysqldumptaken before anymigrate: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 realstorage/installedmarker (never faked onto an empty DB). - 🤖 Seam intact — the install touched only runtime state, no vendor/app files changed.
- 🤖 Install verified —
User::count()> 0 and no errors inlaravel.log. - 🤖 All seeded/default credentials stored — in 1Password /
credentials.md, matched to seeder/docs/users evidence, and flagged for Phase 6 rotation.