getecz/laravel-installer
A Laravel installer package that streamlines scaffolding new applications, with an interactive CLI to set up projects quickly and consistently. A handy starting point for developers who want a fast, repeatable Laravel setup.
The installer is a "post-deploy setup wizard": it gives a self-hosted install the same first-run experience as WordPress/Nextcloud — requirement checks → database config → migrations → admin account → finish — instead of asking users to edit .env by hand.
Minimal path to a running wizard:
composer require getecz/laravel-installer
php artisan vendor:publish --provider="Getecz\LaravelInstaller\InstallerServiceProvider"
php artisan migrate # if the package ships its own state table
Then look here first, in this order:
config/installer.php (published) — routes prefix, middleware, whether the wizard is gated, and the requirements list.routes/ inside the package (or php artisan route::list | grep install) — the actual step URLs, so you know what the wizard is really doing before you customize it.resources/views/vendor/installer/) — this is where 90% of your day-to-day customization happens.First use case to wire up: guard the app until installation is complete. Typically you add the package's middleware to your web group (or a custom group) so unauthenticated/uninstalled requests are redirected to /install:
// bootstrap/app.php (Laravel 11/12)
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
\Getecz\LaravelInstaller\Http\Middleware\EnsureInstalled::class,
]);
})
Then visit /install on a fresh clone and walk the wizard end-to-end once on a throwaway database. That single pass tells you more than the README — you'll see exactly which files it writes and which artisan commands it shells out to.
The "installed" flag is the contract. Most installers of this shape write a lock file (e.g. storage/installed or a settings row) plus a cached config value. Treat that flag as the single source of truth and read it in exactly one middleware — don't scatter if (installed()) checks through controllers.
Environment-aware gating. You almost never want the wizard live in local dev. Common pattern:
// config/installer.php
'enabled' => env('INSTALLER_ENABLED', !app()->environment('local')),
and short-circuit the middleware in local so php artisan serve never kicks you into a wizard you've already completed.
Docker / CI provisioning. For self-hosted apps shipped as images, split "wizard install" from "automated install":
/install.php artisan migrate --force, seed admin from env, touch the lock file). Keep the wizard's underlying actions in services so your entrypoint and the wizard share code — otherwise you maintain two installers.Customizing steps instead of forking. The maintainable pattern is: (a) publish views, (b) add your own extra step routes that live around the package rather than modifying it. E.g. add a "license key" or "SMTP test" step as a Laravel route that renders your own view and redirects back into the wizard's final step. Forking a 38-star package means you own every future Laravel 13 compatibility fix.
Post-install handoff. After the final step the wizard should: write/refresh .env → php artisan config:clear → php artisan key:generate --force (if not done) → migrate --force → seed admin → set the lock. Capture each as a discrete step with success/failure UI so a failed migration doesn't leave a half-installed app with a "done" flag.
Testing your install flow. In CI, add a job that boots a fresh container, POSTs through each wizard step, asserts you land on the dashboard, and asserts /install now 404s/redirects. This is the only reliable way to catch regressions when the package updates — you have zero dependents and 38 stars, so you are effectively the QA team.
.env writes fail silently. The wizard rewrites .env via file_put_contents; on read-only container filesystems or with wrong ownership it can appear to succeed while nothing changes. Check is_writable(base_path('.env')) in your requirements step and surface it explicitly.env() returns stale values after the wizard writes .env. Once config is cached, env() returns null outside config files, and a fresh .env isn't re-read in the same request. Do config:clear / config:cache (via Artisan::call) after saving, and re-resolve the DB connection rather than trusting the booted config.DB_* env vars and continue — the connection was already resolved at boot. Pattern: accept the form, write .env, call config:clear, then DB::purge() and reconfigure at runtime to test the credentials before advancing.abort_unless(!installed(), 404) — belt and suspenders, because middleware groups get edited.APP_KEY rotation. If the wizard runs key:generate after you've already encrypted data (or sessions), you'll invalidate everything. Generate the key in the first step, before any DB writes, or require it to be pre-set in the deployment environment.php artisan storage:link and chmod -R on storage//bootstrap/cache are the two requirements that fail most often on shared hosting. Make them part of the requirements checklist rather than a migration step./app behind nginx. Set ASSET_URL and TrustProxies before testing the wizard, or the "next" buttons will point at the wrong host.pdo_mysql, gd, intl, redis) via the package's callback/hook if it exposes one — otherwise prepend a check in your own view.composer.json ("getecz/laravel-installer": "^1.0") and read the diff on every bump; a small package like this can change route names or the lock-file location between minor releases, and your middleware will silently stop gating.How can I help you explore Laravel packages today?