Skip to content

Installer Overview

The Installer package is the temporary browser setup layer for a new Capell application. It collects install input, runs preflight checks, delegates install work to Core Actions, gives the user a progress/report surface, and can remove itself once the site is ready.

Core owns the install primitives. Installer owns the web routes, views, Filament setup pages, dashboard warning, install guide patches, installer session state, and post-install cleanup.

StepOwnerWhat happens
Show /installInstallController::show()Regenerates the CSRF token, builds page data with BuildInstallerPageDataAction, and shows active install status if the current session may see it.
Build form stateBuildInstallerPageDataActionReads package metadata from CapellCore::getPackages(), fetches downloadable package metadata when available, builds theme options, language options, existing user options, default package selections, and a preflight report.
Submit installInstallController::store()Validates the form and converts it to InstallInputData through InstallInputFactory. The browser flow usually uses step-based JSON requests; non-AJAX and queued installs are still supported.
Run preflightInstallerPreflightChecks PHP extensions, process support, PHP/Composer/Git binaries, writable paths, database readiness, cache readiness, queue readiness, and Composer dry-runs when optional packages are selected.
Run install stepsRunInstallStepAction in CoreInstaller stores the input and plan in cache, then calls one step at a time so the browser can keep showing progress and retry around CSRF refreshes.
Show progress/reportInstallerSessionRepository, FileLogProgressReporter, CacheProgressReporterProgress lines and status are kept under capell.install.{installId}.* cache keys for two hours. Reports can be downloaded from the progress route by the same install session.
Finish cleanupRemoveSetupPackageActionThe success page enables installer removal for the current session. The delete route removes capell-app/installer from the host app.

EnsureNotInstalled guards the main install routes. It treats the app as installed when the Admin package is installed, the sites table exists, and at least one site exists. Reinstall is allowed only when CAPELL_SETUP_ALLOW_REINSTALL is true, which defaults to APP_DEBUG.

Install guide patches are the installer-owned extension point. A patch implements Capell\Installer\Support\InstallGuide\Patch:

MethodPurpose
id()Stable machine key used by the install guide UI and tests.
group()Groups related checks in the guide.
label() / description()Human copy shown to the installer user.
docUrl()Optional link to the longer setup guide.
defaultEnabled()Whether the patch should be selected by default when it can run.
probe()Returns a PatchStatus describing whether the host already matches the expected state.
reason()Explains why a patch is needed or why it cannot run.
apply()Makes the smallest idempotent host-file change.

The service provider registers first-party patches in registerPatches() through PatchRegistry. Add new first-party patches there, keep them idempotent, and cover each patch with a focused test under packages/installer/tests/Feature/InstallGuide/Patches.

Patch code should use the installer patching helpers (EnvFileEditor, PhpFileEditor, ConfigArrayEditor) instead of raw string edits when possible. A patch that only documents manual work should stay explicit, as the DocOnly*Patch classes do.

The installer always has safe theme choices from ThemePackageCandidates, including none, foundation, corporate, and saas when available. Installed and downloadable package metadata can add more choices with a theme key, package name, description, requirements, and preview image.

Default selected packages come from two places:

  • trusted core package defaults from Core;
  • CAPELL_SETUP_DEFAULT_PACKAGES, filtered to packages that are available to the installer.

Optional Composer packages are shown only when the Composer package can be resolved by the web PHP process. If a package is missing from the installer, check Composer repository access before changing UI code.

Fresh apps may configure database-backed sessions or cache before the required tables exist. InstallerServiceProvider::fallbackDatabaseDriversIfNeeded() switches the current web request to file-backed session/cache when those database tables are missing. This is request-scoped and only runs for web requests.

The installer still refuses to start when the active cache store cannot store progress. If the progress request says the cache store is unavailable, either create the cache table, temporarily use CACHE_STORE=file, or run the CLI installer.

SymptomLikely causeCheckFix
/install shows an already-installed notice on a fresh appThe Admin package, sites table, and a site row existphp artisan tinker then Capell\Installer\Support\InstallerInstallationState::capellIsInstalled()Use the installed admin, or set CAPELL_SETUP_ALLOW_REINSTALL=true only for a controlled local reinstall.
/install 404sPackage provider or route file is not loadedphp artisan route:list --name=capell-installerConfirm capell-app/installer is installed/discovered and run php artisan optimize:clear.
Preflight blocks on proc_openWeb PHP cannot run subprocessesCheck PHP disabled functions for the web SAPIEnable proc_open or run the CLI installer from an environment that can run Composer and Artisan.
Preflight warns about PHP binaryCAPELL_SETUP_PHP_BINARY points at FPM or a missing binaryphp artisan config:show capell-installer.php_binarySet CAPELL_SETUP_PHP_BINARY to the CLI PHP executable and clear config cache.
Composer dry-run failsWeb PHP cannot see Composer, Git, credentials, or path repositoriescomposer config repositories and the preflight reportSet CAPELL_SETUP_COMPOSER_BINARY, fix Composer auth/repositories, then restart the installer.
Progress page says the install session expiredcapell.install.{installId}.* cache keys expired or were clearedCheck cache driver and capell.install.lockRestart the installer. For long installs, use a persistent cache store and keep queue workers alive.
Another install appears lockedcapell.install.lock still points at a queued/running installInspect the cache key or revisit /install from the same browserCancel from the UI, wait for the lock TTL, or clear only the installer lock in a local/dev environment.
Guide patch is unavailableprobe() found an unsafe or already-satisfied host stateRead the patch reason() in the UI/reportFix the host file manually or update the patch probe/apply pair with a regression test.
Success page still exposes installer removal/report URLsInstaller package remains installed after setupcomposer show capell-app/installerRemove the package or restrict access. Treat progress/report URLs as bootstrap-only secrets.

Run the package test suite after changing installer routes, validation, preflight checks, patching, session state, or cleanup:

Terminal window
vendor/bin/pest packages/installer/tests --configuration=phpunit.xml

Run the browser installer spec after changing the web flow or CSRF retry behavior:

Terminal window
npm run test:installer-browser

Run screenshot checks when installer pages or visual states change:

Terminal window
npm run screenshots
npm run screenshots:check