Isha Labs Support

Installer

Updated Oct 19, 2026

Matterly ships a production web installer at /install. It runs only while the app is not locked.


Installation workflow

flowchart TD
  A[Visit any URL] --> B{storage/installed?}
  B -->|no| C[/install Welcome]
  B -->|yes| D[Normal app]
  C --> E[Requirements]
  E --> F[Environment]
  F --> G[Database]
  G --> H[Test DB / Migrate / Seed]
  H --> I[Super Admin]
  I --> J[Write lock file]
  J --> K[Finished → Login]
Step Route What happens
1 Welcome GET /install Overview
2 Requirements GET /install/requirements PHP, extensions, permissions
3 Environment GET/POST /install/environment App name, URL, env, debug → session
4 Database GET/POST /install/database MySQL + install actions
4b Test POST /install/database/test Connection only
5–7 Install inside storeDatabase Key, migrate, seed, link, clear; .env persisted after response
8 Admin GET/POST /install/admin Super Admin user
9 Lock Installer::lock() storage/installed
10 Done GET /install/finished Auto-redirect to /login

Requirements

Checked by InstallerService::requirements():

  • PHP ≥ 8.2
  • Extensions: BCMath, Ctype, JSON, Mbstring, OpenSSL, PDO, PDO MySQL, Tokenizer, cURL
  • Writable: storage/, bootstrap/cache/, .env (or project root)

Environment setup

Collected on step 3 and written into .env during install:

Field .env key
App name APP_NAME
App URL APP_URL
Environment APP_ENV (production / staging / local)
Debug APP_DEBUG

Also forced for simple hosting:

  • SESSION_DRIVER=file
  • CACHE_STORE=file
  • QUEUE_CONNECTION=sync

Pre-boot: bootstrap/ensure-env.php copies .env.example and ensures a non-empty APP_KEY so the wizard can load.


Database configuration

Field .env key
Host DB_HOST
Port DB_PORT
Database DB_DATABASE
Username DB_USERNAME
Password DB_PASSWORD
Connection DB_CONNECTION=mysql

Test connection uses a temporary install_test PDO connection (5s timeout).

Install database then:

  1. Applies runtime MySQL config
  2. Generates APP_KEY in memory if missing
  3. php artisan migrate --force
  4. db:seed --force (demo) or PlanSeeder (clean)
  5. storage:link (best-effort)
  6. optimize:clear (best-effort)
  7. Schedules atomic .env write on request termination (avoids php artisan serve mid-request restart / ERR_EMPTY_RESPONSE)

APP_KEY generation

  • Prefer existing config('app.key').
  • Otherwise generate base64: + 32 random bytes and set runtime config.
  • Persist via .env APP_KEY= with other installer values (not via key:generate mid-request).

Migrations & seeders

Mode Command
Demo db:seed --force (full DatabaseSeeder)
Clean db:seed --class=PlanSeeder --force

Migrations create the full SaaS and practice management schema (firms, matters, invoices, AI, knowledge, etc.).


Admin creation

InstallerService::createSuperAdmin():

  • Requires users table
  • Refuses if already installed
  • User with role = super_admin, firm_id = null
  • Optional ENVATO_PURCHASE_CODE in .env
  • Calls Installer::lock()

Installation lock

Item Detail
Path storage/installed
Created After Super Admin step
Effect RedirectIfInstalled blocks /install/*; EnsureInstalled allows normal app
Reset Delete the file (and typically reset DB) to re-run wizard

GET /install/finished remains reachable after lock to show success and redirect to login.


Security notes

  • Wizard blocked after lock.
  • Step middleware enforces order (environment before database, schema before admin).
  • Install routes skip DB probing in EnsureInstalled to avoid hangs on bad MySQL hosts.
  • Passwords validated (min 8, confirmed) via StoreAdminRequest.

Key classes

Class Role
InstallController HTTP steps
InstallerService Business logic
App\Support\Installer Lock + atomic writeEnv
EnsureInstalled / RedirectIfInstalled / EnsureInstallStep Gates
Store*Request Validation

Related docs