Skip to content
prod 352bb92
Browse

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:

  1. Temporarily unblock the /install route — Phase 3 added .htaccess rules (and possibly a Cloudflare WAF rule) blocking /install, /update, and /upgrade-script.
  2. 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.
  3. Verify the installation — The human signs in once; browser automation resumes after the session exists, snapshots the dashboard, and tails logs on errors.
  4. Re-block /install and /update — The writable, installable surface must close as soon as the wizard finishes.
  5. Re-harden permissions — Walk back the temporary 777 from page 3 to least-privilege.

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.

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.

  1. 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 lock
    ssh <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 installed lock is removed.
  2. 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
    • /install returns HTTP/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.

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.

  1. Decide the browser path.

    ConditionAction
    Browser automation availableAgent opens an isolated browser instance, navigates to /install, and drives every non-secret screen
    Browser automation unavailableUser opens /install and runs the wizard manually
    Secret field or account-creation screen appearsHuman 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.
  2. 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.md or the phase notes.
  3. 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.
  4. Environment configuration — agent fills non-secret values from Zaj-PROJECT.md and the selected environment’s .env.<env-key> (.env.staging is 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 .env are left blank; the agent never types or prints passwords.
  5. 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 defaults is proven and every default is flagged for Phase 6 rotation.
  6. Settings — agent-driven when browser automation exists. Confirm app name (e.g. “Your App — QA”) and URL from the selected Zaj-PROJECT.md environment row.

    • ✅ App name and URL are set for the selected non-production environment.
  7. 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 resultAction
Wizard loadsContinue
403 Forbidden.htaccess still blocking — re-run section 1
404Installer route missing — confirm the app ships one
500tail -50 …/deploy/current/storage/logs/laravel.log
504 Gateway TimeoutPHP timeout too low — confirm shared/.user.ini has max_execution_time = 300 (page 1, step 7), then retry
”Access Denied” mid-wizardStale 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)

Sign in and confirm the app is healthy after the wizard.

  1. Sign in at /login and 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.

    CheckPass
    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.

The writable, installable surface must close as soon as the wizard finishes.

  1. Re-comment the blocking rules and confirm /install is 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 /install returns HTTP/2 403.

Re-enable the Cloudflare WAF rule if you toggled it off (👤).

Walk back the temporary 777 from page 3 to least-privilege.

  1. Restore least-privilege on storage and .env.

    Terminal window
    # storage: dirs 775, files 664
    ssh <non-prod-alias> "cd ~/domains/nonprod.example.com/deploy/shared/storage && \
    find . -type d -exec chmod 775 {} \; && find . -type f -exec chmod 664 {} \;"
    # .env: 640
    ssh <non-prod-alias> "chmod 640 ~/domains/nonprod.example.com/deploy/shared/.env"
    # Expected: storage dirs 775 / files 664; .env 640
    • ✅ Storage dirs are 775, files 664, and .env is 640.

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

  • 🔀 Wizard completed/install reachable (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/install re-blocked (403); Cloudflare WAF rule restored.
  • 🤖 Permissions re-hardened — storage dirs 775 / files 664; .env 640.