4 · Installer + harden
Objective — install the app the CodeCanyon way (a web wizard, not artisan migrations): temporarily unblock /install, run the wizard against the selected non-production DB from Zaj-PROJECT.md, verify the app loads, then immediately re-block /install + /update and re-harden permissions — because you opened a writable, installable surface to the public web.
Steps at a glance:
- Temporarily unblock the
/installroute — Phase 3 added.htaccessrules (and possibly a Cloudflare WAF rule) blocking/install,/update, and/upgrade-script. - Run the installer wizard — With browser automation, the agent drives non-secret wizard screens in an isolated browser; the human handles every secret field and any admin-account creation transaction.
- Verify the installation — The human signs in once; browser automation resumes after the session exists, snapshots the dashboard, and tails logs on errors.
- Re-block
/installand/update— The writable, installable surface must close as soon as the wizard finishes. - Re-harden permissions — Walk back the temporary 777 from page 3 to least-privilege.
Background
Section titled “Background”CodeCanyon apps install through a web wizard, not artisan migrations. Phase 3 blocked /install and /update for safety, so this page temporarily unblocks them, runs the wizard, then re-blocks and re-hardens. The harden step is not optional — you opened a writable, installable surface to the public web.
1. Temporarily unblock the /install route
Section titled “1. Temporarily unblock the /install route”Phase 3 added .htaccess rules (and possibly a Cloudflare WAF rule) blocking /install, /update, and /upgrade-script. Comment them out so the wizard can load.
-
Unblock the install routes and remove any stale lock.
Terminal window # Is /install currently blocked?ssh <non-prod-alias> "grep 'RewriteRule ^install' ~/domains/nonprod.example.com/deploy/current/public/.htaccess 2>/dev/null"# Comment out the blocking rules (idempotent — skip lines already commented)ssh <non-prod-alias> 'cd ~/domains/nonprod.example.com/deploy/current/public && \grep -q "^RewriteRule ^install" .htaccess && \sed -i "s/^RewriteRule ^install/# RewriteRule ^install/" .htaccess; \grep -q "^RewriteRule ^update" .htaccess && \sed -i "s/^RewriteRule ^update/# RewriteRule ^update/" .htaccess; \grep -q "^RewriteRule ^upgrade-script" .htaccess && \sed -i "s/^RewriteRule ^upgrade-script/# RewriteRule ^upgrade-script/" .htaccess; \echo "install/update unblocked"'# Remove any stale install lockssh <non-prod-alias> "rm -f ~/domains/nonprod.example.com/deploy/shared/storage/installed 2>/dev/null && echo 'lock removed'"# Expected: "install/update unblocked" and "lock removed"- ✅ The blocking rules are commented out and any stale
installedlock is removed.
- ✅ The blocking rules are commented out and any stale
-
Confirm the wizard is reachable.
Terminal window curl -sI https://nonprod.example.com/install 2>&1 | head -1 # expect HTTP/2 200# Expected: HTTP/2 200- ✅
/installreturnsHTTP/2 200.
- ✅
If you created a Cloudflare WAF rule in Phase 3, toggle it OFF now (👤): Cloudflare → Security → WAF → “Block install and update routes” → OFF.
2. Run the installer wizard
Section titled “2. Run the installer wizard”Run https://nonprod.example.com/install with the safest actor split: browser automation drives non-secret wizard screens when available; the human fills secret fields and completes account-creation/authentication transactions.
-
Decide the browser path.
Condition Action Browser automation available Agent opens an isolated browser instance, navigates to /install, and drives every non-secret screenBrowser automation unavailable User opens /installand runs the wizard manuallySecret field or account-creation screen appears Human completes the credential/account transaction; agent waits, then resumes after the non-secret state exists - ✅ The run mode is chosen before any installer field is touched.
-
Requirements — agent-driven when browser automation exists. Confirm all checks are green, then record the web-SAPI PHP version displayed by the wizard. That screen is the authoritative web PHP cross-check for this environment; it can differ from SSH
php -v.- ✅ Every requirement check is green.
- ✅ The web-SAPI PHP version is recorded in
Zaj-PROJECT.mdor the phase notes.
-
File permissions — agent-driven when browser automation exists. Confirm every writable check is green. Reds usually mean the page-3 permission unblock missed a path.
- ✅ Every permissions check is green.
-
Environment configuration — agent fills non-secret values from
Zaj-PROJECT.mdand the selected environment’s.env.<env-key>(.env.stagingis the simple example): app name, app URL, DB host, DB port, DB username, and DB name. If Database Password is required, it is a human-only field from the project vault. If the vendor labels it optional and falls back to the already-rendered server.env, the agent leaves it blank and proceeds. After the human handles any required secret field, the agent clicks Test Connection and continues.- ✅ Test Connection succeeds against the selected non-production DB.
- ✅ Required secret fields are human-filled; optional password fields that fall back to
.envare left blank; the agent never types or prints passwords.
-
Admin account — conditional. Some vendors show an admin-create screen; some seed default admin rows instead. If a screen appears, the human completes the account-creation transaction, including password entry and submit. The agent may prepare non-secret context (name and non-production email) for the human to copy, then confirms the new admin login is saved to the project vault without seeing the password. If no screen appears, prove the vendor seeds defaults by citing the seeder grep from Phase 3/installer notes or a bounded post-install user query, then flag every default for Phase 6 rotation.
- ✅ Admin account handled: either created with a human-entered password, or
N/A — vendor seeds defaultsis proven and every default is flagged for Phase 6 rotation.
- ✅ Admin account handled: either created with a human-entered password, or
-
Settings — agent-driven when browser automation exists. Confirm app name (e.g. “Your App — QA”) and URL from the selected
Zaj-PROJECT.mdenvironment row.- ✅ App name and URL are set for the selected non-production environment.
-
Install / Finish — agent-driven when browser automation exists. Click through the install/finish screen and confirm “Installation Complete”.
- ✅ The wizard reports “Installation Complete”.
Use the table to recover from a wizard that won’t load or stalls:
| Installer result | Action |
|---|---|
| Wizard loads | Continue |
| 403 Forbidden | .htaccess still blocking — re-run section 1 |
| 404 | Installer route missing — confirm the app ships one |
| 500 | tail -50 …/deploy/current/storage/logs/laravel.log |
| 504 Gateway Timeout | PHP timeout too low — confirm shared/.user.ini has max_execution_time = 300 (page 1, step 7), then retry |
| ”Access Denied” mid-wizard | Stale cached config with old DB values — optimize:clear with the versioned PHP binary (page 3, step 2), then retry |
| ”Already installed” | Remove storage/installed (section 1) |
3. Verify the installation
Section titled “3. Verify the installation”Sign in and confirm the app is healthy after the wizard.
-
Sign in at
/loginand walk the post-install checks.Visit
https://nonprod.example.com/login; the human signs in with the admin account, then hands the authenticated browser session back to the agent for checks.Check Pass Dashboard loads [ ] Navigation works [ ] No PHP errors [ ] CSS/JS loading [ ] SSL padlock shows [ ] If the browser shows a 500, a blank dashboard, or broken assets, tail the Laravel log before continuing:
Terminal window ssh <non-prod-alias> "tail -80 ~/domains/nonprod.example.com/deploy/current/storage/logs/laravel.log"# Expected: no new fatal error for the login/dashboard request- ✅ Human admin sign-in works, all five post-install checks pass in the post-session browser, and no new fatal log entry appears.
4. Re-block /install and /update
Section titled “4. Re-block /install and /update”The writable, installable surface must close as soon as the wizard finishes.
-
Re-comment the blocking rules and confirm
/installis forbidden.Terminal window ssh <non-prod-alias> 'cd ~/domains/nonprod.example.com/deploy/current/public && \grep -q "^# RewriteRule ^install" .htaccess && \sed -i "s/^# RewriteRule ^install/RewriteRule ^install/" .htaccess; \grep -q "^# RewriteRule ^update" .htaccess && \sed -i "s/^# RewriteRule ^update/RewriteRule ^update/" .htaccess; \grep -q "^# RewriteRule ^upgrade-script" .htaccess && \sed -i "s/^# RewriteRule ^upgrade-script/RewriteRule ^upgrade-script/" .htaccess; \echo "install/update re-blocked"'curl -sI https://nonprod.example.com/install 2>&1 | head -1 # expect HTTP/2 403# Expected: "install/update re-blocked" then HTTP/2 403- ✅ The rules are restored and
/installreturnsHTTP/2 403.
- ✅ The rules are restored and
Re-enable the Cloudflare WAF rule if you toggled it off (👤).
5. Re-harden permissions
Section titled “5. Re-harden permissions”Walk back the temporary 777 from page 3 to least-privilege.
-
Restore least-privilege on storage and
.env.Terminal window # storage: dirs 775, files 664ssh <non-prod-alias> "cd ~/domains/nonprod.example.com/deploy/shared/storage && \find . -type d -exec chmod 775 {} \; && find . -type f -exec chmod 664 {} \;"# .env: 640ssh <non-prod-alias> "chmod 640 ~/domains/nonprod.example.com/deploy/shared/.env"# Expected: storage dirs 775 / files 664; .env 640- ✅ Storage dirs are
775, files664, and.envis640.
- ✅ Storage dirs are
Checklist
Section titled “Checklist”Do not mark this step done until every box below is checked.
- 🔀 Wizard completed —
/installreachable (200), browser automation drove all non-secret screens when available, human filled secret fields, and “Installation Complete” shown. - 🔀 App verified — browser automation verified login/dashboard when available; all five post-install checks pass; admin login works.
- 🔀 Routes re-blocked —
/installre-blocked (403); Cloudflare WAF rule restored. - 🤖 Permissions re-hardened — storage dirs
775/ files664;.env640.