Installer Documentation
LawyerPara 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.4
- 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:
- Applies runtime MySQL config
- Generates
APP_KEY in memory if missing
php artisan migrate --force
db:seed --force (demo) or PlanSeeder (clean)
storage:link (best-effort)
optimize:clear (best-effort)
- 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 + PMS 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 |