Compare commits

...

5 Commits

Author SHA1 Message Date
Manus AI
6819dbc26b feat: complete install wizard with db, mail, storage, security & third-party config
Expand docker-install.sh from a minimal 4-variable setup into a full
production-ready interactive installation wizard, mirroring the equivalent
changes made to the fleetbase-cli install-fleetbase command.

Changes:
- Pre-flight checks: verify Docker, Docker Compose v2, Git, and openssl are
  present; warn (non-blocking) on port conflicts for 8000, 4200, 3306, 38000
- Step 1 (core): add APP_NAME prompt alongside existing host/environment
- Step 3 (database): choose bundled Docker MySQL (auto-generated secure
  credentials via openssl) or external MySQL/RDS; sets DATABASE_URL,
  MYSQL_ROOT_PASSWORD, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE,
  MYSQL_ALLOW_EMPTY_PASSWORD=no
- Step 4 (mail): select driver (SMTP, Mailgun, Postmark, SendGrid, Resend,
  SES, or log); collect driver-specific credentials; sets MAIL_MAILER,
  MAIL_HOST, MAIL_PORT, MAIL_USERNAME, MAIL_PASSWORD, MAIL_FROM_ADDRESS,
  MAIL_FROM_NAME and driver-specific keys
- Step 5 (storage): choose local disk, AWS S3, or Google Cloud Storage; sets
  FILESYSTEM_DRIVER, AWS_* / GOOGLE_CLOUD_* variables
- Step 6 (security/CORS): auto-derive SESSION_DOMAIN and
  SOCKETCLUSTER_OPTIONS origins from the configured host; prompt for
  additional FRONTEND_HOSTS
- Step 7 (third-party): optional IPINFO_API_KEY, GOOGLE_MAPS_API_KEY,
  GOOGLE_MAPS_LOCALE, TWILIO_SID, TWILIO_TOKEN, TWILIO_FROM
- Write a complete docker-compose.override.yml covering application, socket,
  and database services using atomic temp-file writes; back up any existing
  override file with a timestamp suffix
- Improved database readiness check: prefer Docker HEALTHCHECK status, fall
  back to mysqladmin ping with a 90-second timeout
- Rich post-install summary showing configured/skipped items and next steps
- Add --non-interactive flag to skip all optional prompts with safe defaults
  (useful for CI/CD pipelines)
- Add colour helpers (info/success/warn/error/section) for readable output
- Add env_line() helper to emit only non-empty YAML environment entries
- Add gen_secret() helper for generating random hex secrets via openssl
2026-03-12 03:05:56 -04:00
Ron
29c23e373f Merge pull request #508 from fleetbase/dev-v0.7.30
Some checks failed
Fleetbase CI / Build and Start Docker Services (push) Has been cancelled
v0.7.30 <> Patches extensions endpoint + fleetops #507
2026-02-28 10:24:05 +08:00
Ronald A. Richardson
e09ed5e85c docs: correct v0.7.30 release notes and README CLI section
- Scope RELEASE.md to only the three in-scope changes:
  registry-bridge public endpoint fix, fleetops driver vehicle
  validation, and fleetbase-cli flb search command
- Remove internals/account deletion content (not open source scope)
- Fix README: remove 'without authentication' from flb search description
- Condense Browsing Extensions section to minimal code block
2026-02-27 21:03:27 -05:00
Ronald A. Richardson
17a6ad34d8 docs: add v0.7.30 release notes and flb search to README
- Add RELEASE.md for v0.7.30 documenting:
  - FleetOps ResolvableVehicle fix (driver creation TypeError)
  - Registry Bridge public extensions endpoint sanitization
  - Fleetbase CLI flb search/list-extensions command
  - Internals 14-day account deletion grace period
  - PurgeSuspendedOrganizations canceled-only fix
  - WarnSuspendedOrganizations auto-clear on reactivation
- Add flb search [query] to README CLI commands table
- Add Browsing Extensions section with usage examples
2026-02-27 20:57:27 -05:00
Ronald A. Richardson
269175ff51 v0.7.30 <> Patches extensions endpoint + fleetops #507 2026-02-28 09:53:46 +08:00
10 changed files with 557 additions and 244 deletions

View File

@@ -191,6 +191,7 @@ npm install -g @fleetbase/cli
|---------|-------------|
| `flb install-fleetbase` | Install Fleetbase using Docker with interactive setup |
| `flb set-auth <token>` | Set your registry authentication token for installing extensions |
| `flb search [query]` | Search and browse available extensions |
| `flb install <extension>` | Install an extension to your Fleetbase instance |
| `flb uninstall <extension>` | Uninstall an extension from your instance |
| `flb register` | Register a Registry Developer Account |
@@ -204,6 +205,16 @@ npm install -g @fleetbase/cli
Extensions are modular components that enhance the functionality of your Fleetbase instance. They allow you to add new features, customize existing behavior, or integrate with external systems.
### Browsing Extensions
```bash
flb search # list all extensions
flb search fleet # search by keyword
flb search --category logistics
flb search --free
flb search --json # machine-readable output
```
### Installing Extensions
To install extensions on a self-hosted instance:

View File

@@ -1,114 +1,58 @@
# 🚀 Fleetbase v0.7.29 — 2026-02-27
# 🚀 Fleetbase v0.7.30 — 2026-02-28
> "Major security enhancements, analytics tracking, developer tools, and UX improvements"
> "Extension discovery, driver vehicle validation, and CLI search"
---
## ✨ Highlights
This release brings **critical security patches**, comprehensive **analytics event tracking** across the platform, enhanced **developer account management** for the extension marketplace, and several **user experience improvements** including accurate geolocation detection.
This release includes two bug fixes and one new feature: a corrected public extension discovery endpoint in the Registry Bridge, a driver vehicle validation patch in FleetOps, and a new `flb search` command in the Fleetbase CLI.
### 🔒 Security Enhancements
### 🔌 Registry Bridge — Public Extension Discovery
Fleetbase v0.7.29 includes critical security fixes that strengthen tenant isolation and prevent unauthorized access. The **core-api** has been patched to address a systemic tenant isolation vulnerability (GHSA-3wj9-hh56-7fw7) with the introduction of a `CompanyScope` global scope that enforces proper tenant boundaries. Additional security improvements include removal of hardcoded authentication bypasses, enforcement of strong password policies across all validators, and prevention of user enumeration in login flows. Cross-tenant IDOR vulnerabilities have been resolved with company-scoped authorization checks throughout the API.
The public extensions listing endpoint (`~registry/v1/extensions`) has been corrected and hardened. A dedicated `PublicRegistryExtension` API resource now sanitizes the response, stripping all sensitive fields before they leave the server. The `install_count` aggregation has been fixed to use `withCount('installs')` and the incorrect `author` relationship has been replaced with the proper `company` relationship. The endpoint returns a clean, flat array.
### 📊 Analytics & Event Tracking
### 🚛 FleetOps — Driver Vehicle Validation
A comprehensive **events service** has been added to **ember-core**, providing centralized analytics tracking across all core services. The system emits both generic events (e.g., `resource.created`) and specific events (e.g., `order.created`) using a standardized dot notation naming convention. Event tracking has been integrated into CRUD operations (create, update, delete, bulk actions, import, export) and resource actions across the platform. In **FleetOps**, 30 controllers now emit analytics events, and import operations return accurate counts of imported records. The dual event system fires on both the events service and universe service, enabling cross-engine communication for analytics integrations like PostHog.
A `TypeError` that occurred when creating a driver with a vehicle object sent from the frontend has been resolved. A new `ResolvableVehicle` validation rule accepts a `public_id` string (e.g., `vehicle_abc123`), a UUID string, or an object/array containing an `id`, `public_id`, or `uuid` key. Vehicle normalization has been added to both `createRecord()` and `updateRecord()` in `DriverController` so the correct vehicle UUID is always resolved before persistence.
### 🛠️ Developer Tools & Marketplace
### 🔍 Fleetbase CLI — Extension Search Command
The **registry-bridge** now supports **Registry Developer Accounts** for self-hosted instances, enabling developers to publish and monetize extensions through a centralized marketplace. The Universal Extension Marketplace backend provides a public extension listing endpoint with 15-minute caching for performance. Stripe Connect account management has been added, allowing developers to update bank account details after initial onboarding. The **fleetbase-cli** has been significantly enhanced with new commands including `flb register` for developer account registration, `flb verify` for email verification, `flb resend-verification` for expired codes, and `flb install-fleetbase` for Docker-based installation with automatic repository cloning.
### 🌍 Geolocation & UX Improvements
A critical bug affecting user onboarding has been fixed where the system was displaying the **server's location** instead of the **user's actual location**. The **ember-core** now implements frontend IP lookup using multiple geolocation APIs (geoiplookup.io and ipapi.co) with automatic fallback support and localStorage caching. The **phone-input component** in **ember-ui** has been updated to use this frontend IP lookup, ensuring accurate country code detection for phone number formatting. The **IAM engine** now features tabbed user type sections in the users management interface for better organization.
### 📈 Reporting & Data Access
**FleetOps** now exposes the `meta` column and `Transaction` relationships in the Orders report schema, enabling users to query and report on order metadata, custom fields, and financial data including transaction amounts, line items, and aggregates. This resolves a significant limitation where critical financial data was previously inaccessible in reports.
### 🌐 Internationalization
Support for **KZT (Kazakhstani Tenge)** currency has been added across both **core-api** and **ember-ui**, expanding Fleetbase's international capabilities.
---
## 🔐 Security Fixes
- **[core-api]** Patched critical tenant isolation vulnerability (GHSA-3wj9-hh56-7fw7) with CompanyScope global scope
- **[core-api]** Removed hardcoded SMS auth bypass code, replaced with environment-driven bypass for non-production
- **[core-api]** Fixed cross-tenant IDOR vulnerabilities with company-scoped authorization
- **[core-api]** Enforced strong password policy across all validators
- **[core-api]** Prevented user enumeration in login flow
- **[core-api]** Restored authToken re-authentication with identity verification
A new `flb search [query]` command (alias: `flb list-extensions`) lets developers and administrators browse all available extensions directly from the terminal. Results are displayed in a formatted, colour-coded table showing the extension name, category, publisher, version, price, and supported install formats. Filtering options include `--category`, `--free`, `--json`, `--simple`, and `--host`.
---
## ✨ New Features
### Analytics & Tracking
- **[ember-core]** Added centralized events service for analytics tracking across all core services
- **[ember-core]** Event tracking in CRUD service (create, update, delete, bulk actions, import, export)
- **[ember-core]** Dual event system (fires on both events service and universe service)
- **[fleetops]** Added event tracking to 30 FleetOps controllers for event tracking
- **[fleetops]** Import operations now return count of imported records in response
### Developer Tools
- **[registry-bridge]** Registry Developer Account support for self-hosted instances
- **[registry-bridge]** Universal Extension Marketplace backend with public extension listing endpoint
- **[registry-bridge]** Stripe Connect account management for bank account updates
- **[registry-bridge]** Email verification for developer accounts using VerificationCode model
- **[registry-bridge]** Automatic registry token generation upon email verification
- **[fleetbase-cli]** Added `flb register` command for Registry Developer Account registration
- **[fleetbase-cli]** Added `flb verify` command for email verification
- **[fleetbase-cli]** Added `flb resend-verification` command to request new verification codes
- **[fleetbase-cli]** Added `flb install-fleetbase` command for Docker-based installation
- **[fleetbase-cli]** Auto-clone Fleetbase repository if not present during installation
- **[fleetbase-cli]** Support for `--host` parameter to work with self-hosted instances
### Reporting & Data
- **[fleetops]** Exposed meta column and Transaction relationships in Orders report schema for financial reporting
- **[core-api]** User cache now includes updated_at timestamp for automatic cache busting
### UI/UX
- **[iam-engine]** Added tabbed user type sections to users management interface
- **[iam-engine]** Enhanced edit user interface with better validation and error handling
### Internationalization
- **[core-api]** Added KZT (Kazakhstani Tenge) currency support
- **[ember-ui]** Added support for KZT currency
- **[fleetbase-cli]** Added `flb search [query]` command (alias: `flb list-extensions`) for browsing available extensions
- **[fleetbase-cli]** `--category` filter to narrow results by extension category
- **[fleetbase-cli]** `--free` flag to list only free extensions
- **[fleetbase-cli]** `--json` flag for machine-readable JSON output
- **[fleetbase-cli]** `--simple` flag for plain-text terminal output
- **[fleetbase-cli]** `--host` option to target self-hosted registry instances
---
## 🐛 Bug Fixes
### Geolocation
- **[ember-core]** Implemented frontend IP lookup to get accurate user location (fixes onboarding showing server location)
- **[ember-core]** Added lookup-user-ip utility with multi-API fallback support (geoiplookup.io and ipapi.co)
- **[ember-core]** localStorage caching for IP lookup results (1 hour TTL)
- **[ember-core]** Graceful fallback to browser timezone when geolocation APIs fail
- **[ember-ui]** Updated phone-input component to use frontend IP lookup (fixes incorrect country code detection)
- **[ember-ui]** Phone input now always initializes with US fallback if geolocation fails
### FleetOps
- **[fleetops]** Fixed `TypeError` when creating a driver with a vehicle object sent from the frontend
- **[fleetops]** Added `ResolvableVehicle` validation rule accepting `public_id`, UUID, or object with `id`/`public_id`/`uuid`
- **[fleetops]** Added vehicle normalization in `DriverController::createRecord()` and `updateRecord()`
### Core Fixes
- **[core-api]** Verification codes now default to 'pending' status
- **[core-api]** Fixed verification email HTML rendering (button component)
- **[core-api]** Prevented empty email/phone on user update
- **[core-api]** Resolved camelCase expansion methods from snake_case query params in Filter
- **[fleetops]** Prevented duplicate driver creation when user_uuid already has a driver profile
- **[registry-bridge]** Made developer account registration routes public (no auth required)
- **[registry-bridge]** Polymorphic purchaser relationship for extension purchases (supports both Company and RegistryDeveloperAccount)
### Registry Bridge
- **[registry-bridge]** Fixed `install_count` column error by switching to `withCount('installs')` eager load
- **[registry-bridge]** Removed incorrect `author` relationship; replaced with correct `company` relationship
- **[registry-bridge]** Removed sensitive data (internal UUIDs, Stripe IDs, private relationships) from public endpoint response
- **[registry-bridge]** Public extensions endpoint now returns a plain array without a wrapping key
---
## 🔧 Improvements
- **[fleetops]** Moved avatar management to FleetOps settings
- **[ember-ui]** Faster phone input lookup (1 network hop vs 2, no backend dependency)
- **[fleetbase-cli]** Better error handling and debugging for all commands
- **[fleetbase-cli]** Skip interactive prompts when command-line options are provided
- **[ember-core]** Standardized event naming with dot notation (e.g., resource.created, order.created)
- **[fleetbase-cli]** Price display correctly converts cents to dollars in search results
- **[fleetbase-cli]** Search results show both install formats: `flb install fleetbase/<slug>` and `flb install <extension_id>`
- **[registry-bridge]** Extension listing response is a clean, flat array for easier consumption by CLI and third-party tools
---
@@ -136,13 +80,9 @@ docker compose exec application bash -c "./deploy.sh"
## 📦 Component Versions
- **core-api**: v1.6.36
- **fleetops**: v0.6.36
- **registry-bridge**: v0.1.6
- **iam-engine**: v0.1.7
- **ember-core**: v0.3.11, v0.3.12
- **ember-ui**: v0.3.20, v0.3.21
- **fleetbase-cli**: v0.0.4
- **fleetops**: v0.6.37
- **registry-bridge**: v0.1.7
- **fleetbase-cli**: v0.0.5
---

View File

@@ -21,8 +21,8 @@
"php": ">=8.0 <=8.2.30",
"appstract/laravel-opcache": "^4.0",
"fleetbase/core-api": "^1.6.37",
"fleetbase/fleetops-api": "^0.6.36",
"fleetbase/registry-bridge": "^0.1.6",
"fleetbase/fleetops-api": "^0.6.37",
"fleetbase/registry-bridge": "^0.1.7",
"fleetbase/storefront-api": "^0.4.13",
"guzzlehttp/guzzle": "^7.0.1",
"laravel/framework": "^10.0",

42
api/composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "6327bc3172d3e92c060c576df8b57919",
"content-hash": "784bd052687a49a240afcf2a8b3a651e",
"packages": [
{
"name": "appstract/laravel-opcache",
@@ -124,16 +124,16 @@
},
{
"name": "aws/aws-sdk-php",
"version": "3.371.2",
"version": "3.371.3",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "32090a8ac3ec8859cb83bdde800b8f0ecf92d8ec"
"reference": "d300ec1c861e52dc8f17ca3d75dc754da949f065"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/32090a8ac3ec8859cb83bdde800b8f0ecf92d8ec",
"reference": "32090a8ac3ec8859cb83bdde800b8f0ecf92d8ec",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/d300ec1c861e52dc8f17ca3d75dc754da949f065",
"reference": "d300ec1c861e52dc8f17ca3d75dc754da949f065",
"shasum": ""
},
"require": {
@@ -197,11 +197,11 @@
"authors": [
{
"name": "Amazon Web Services",
"homepage": "http://aws.amazon.com"
"homepage": "https://aws.amazon.com"
}
],
"description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project",
"homepage": "http://aws.amazon.com/sdkforphp",
"homepage": "https://aws.amazon.com/sdk-for-php",
"keywords": [
"amazon",
"aws",
@@ -215,9 +215,9 @@
"support": {
"forum": "https://github.com/aws/aws-sdk-php/discussions",
"issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.371.2"
"source": "https://github.com/aws/aws-sdk-php/tree/3.371.3"
},
"time": "2026-02-26T19:06:10+00:00"
"time": "2026-02-27T19:05:40+00:00"
},
{
"name": "aws/aws-sdk-php-laravel",
@@ -2323,16 +2323,16 @@
},
{
"name": "fleetbase/fleetops-api",
"version": "0.6.36",
"version": "0.6.37",
"source": {
"type": "git",
"url": "https://github.com/fleetbase/fleetops.git",
"reference": "2a6178e011ed2aad1fe3e5cb67308455c48e1cca"
"reference": "071a0f06fbe60b7a98fb84686112cc0cd2c3018c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fleetbase/fleetops/zipball/2a6178e011ed2aad1fe3e5cb67308455c48e1cca",
"reference": "2a6178e011ed2aad1fe3e5cb67308455c48e1cca",
"url": "https://api.github.com/repos/fleetbase/fleetops/zipball/071a0f06fbe60b7a98fb84686112cc0cd2c3018c",
"reference": "071a0f06fbe60b7a98fb84686112cc0cd2c3018c",
"shasum": ""
},
"require": {
@@ -2407,9 +2407,9 @@
],
"support": {
"issues": "https://github.com/fleetbase/fleetops/issues",
"source": "https://github.com/fleetbase/fleetops/tree/v0.6.36"
"source": "https://github.com/fleetbase/fleetops/tree/v0.6.37"
},
"time": "2026-02-27T07:56:16+00:00"
"time": "2026-02-28T01:43:39+00:00"
},
{
"name": "fleetbase/laravel-mysql-spatial",
@@ -2479,16 +2479,16 @@
},
{
"name": "fleetbase/registry-bridge",
"version": "0.1.6",
"version": "0.1.7",
"source": {
"type": "git",
"url": "https://github.com/fleetbase/registry-bridge.git",
"reference": "3e6ba698f5015616ec88c336fe66e98701442ad2"
"reference": "52441e2b2c56d74afb02a216f962cd712c356efd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fleetbase/registry-bridge/zipball/3e6ba698f5015616ec88c336fe66e98701442ad2",
"reference": "3e6ba698f5015616ec88c336fe66e98701442ad2",
"url": "https://api.github.com/repos/fleetbase/registry-bridge/zipball/52441e2b2c56d74afb02a216f962cd712c356efd",
"reference": "52441e2b2c56d74afb02a216f962cd712c356efd",
"shasum": ""
},
"require": {
@@ -2555,9 +2555,9 @@
],
"support": {
"issues": "https://github.com/fleetbase/registry-bridge/issues",
"source": "https://github.com/fleetbase/registry-bridge/tree/v0.1.6"
"source": "https://github.com/fleetbase/registry-bridge/tree/v0.1.7"
},
"time": "2026-02-27T07:58:45+00:00"
"time": "2026-02-28T01:41:56+00:00"
},
{
"name": "fleetbase/storefront-api",

View File

@@ -1,6 +1,6 @@
{
"name": "@fleetbase/console",
"version": "0.7.29",
"version": "0.7.30",
"private": true,
"description": "Modular logistics and supply chain operating system (LSOS)",
"repository": "https://github.com/fleetbase/fleetbase",
@@ -37,10 +37,10 @@
"@fleetbase/ember-core": "^0.3.12",
"@fleetbase/ember-ui": "^0.3.21",
"@fleetbase/fleetops-data": "^0.1.25",
"@fleetbase/fleetops-engine": "^0.6.36",
"@fleetbase/fleetops-engine": "^0.6.37",
"@fleetbase/iam-engine": "^0.1.7",
"@fleetbase/leaflet-routing-machine": "^3.2.17",
"@fleetbase/registry-bridge-engine": "^0.1.6",
"@fleetbase/registry-bridge-engine": "^0.1.7",
"@fleetbase/storefront-engine": "^0.4.13",
"@formatjs/intl-datetimeformat": "^6.18.2",
"@formatjs/intl-numberformat": "^8.15.6",

20
console/pnpm-lock.yaml generated
View File

@@ -29,8 +29,8 @@ importers:
specifier: ^0.1.25
version: 0.1.25(@ember/string@3.1.1)(@ember/test-helpers@3.3.1(@babel/core@7.29.0)(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(webpack@5.105.0))(ember-resolver@11.0.1(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(eslint@8.57.1)(webpack@5.105.0)
'@fleetbase/fleetops-engine':
specifier: ^0.6.36
version: 0.6.36(@ember/string@3.1.1)(@ember/test-helpers@3.3.1(@babel/core@7.29.0)(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(webpack@5.105.0))(@glimmer/component@1.1.2(@babel/core@7.29.0))(@glimmer/tracking@1.1.2)(ember-engines@0.9.0(@ember/legacy-built-in-components@0.4.2(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-resolver@11.0.1(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(eslint@8.57.1)(postcss@8.5.6)(rollup@2.79.2)(tracked-built-ins@3.4.0(@babel/core@7.29.0))(webpack@5.105.0)
specifier: ^0.6.37
version: 0.6.37(@ember/string@3.1.1)(@ember/test-helpers@3.3.1(@babel/core@7.29.0)(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(webpack@5.105.0))(@glimmer/component@1.1.2(@babel/core@7.29.0))(@glimmer/tracking@1.1.2)(ember-engines@0.9.0(@ember/legacy-built-in-components@0.4.2(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-resolver@11.0.1(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(eslint@8.57.1)(postcss@8.5.6)(rollup@2.79.2)(tracked-built-ins@3.4.0(@babel/core@7.29.0))(webpack@5.105.0)
'@fleetbase/iam-engine':
specifier: ^0.1.7
version: 0.1.7(@ember/string@3.1.1)(@ember/test-helpers@3.3.1(@babel/core@7.29.0)(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(webpack@5.105.0))(@glimmer/component@1.1.2(@babel/core@7.29.0))(@glimmer/tracking@1.1.2)(ember-engines@0.9.0(@ember/legacy-built-in-components@0.4.2(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-resolver@11.0.1(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(eslint@8.57.1)(postcss@8.5.6)(rollup@2.79.2)(tracked-built-ins@3.4.0(@babel/core@7.29.0))(webpack@5.105.0)
@@ -38,8 +38,8 @@ importers:
specifier: ^3.2.17
version: 3.2.17
'@fleetbase/registry-bridge-engine':
specifier: ^0.1.6
version: 0.1.6(@ember/string@3.1.1)(@ember/test-helpers@3.3.1(@babel/core@7.29.0)(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(webpack@5.105.0))(@glimmer/component@1.1.2(@babel/core@7.29.0))(@glimmer/tracking@1.1.2)(ember-engines@0.9.0(@ember/legacy-built-in-components@0.4.2(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-resolver@11.0.1(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(eslint@8.57.1)(postcss@8.5.6)(rollup@2.79.2)(tracked-built-ins@3.4.0(@babel/core@7.29.0))(webpack@5.105.0)
specifier: ^0.1.7
version: 0.1.7(@ember/string@3.1.1)(@ember/test-helpers@3.3.1(@babel/core@7.29.0)(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(webpack@5.105.0))(@glimmer/component@1.1.2(@babel/core@7.29.0))(@glimmer/tracking@1.1.2)(ember-engines@0.9.0(@ember/legacy-built-in-components@0.4.2(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-resolver@11.0.1(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(eslint@8.57.1)(postcss@8.5.6)(rollup@2.79.2)(tracked-built-ins@3.4.0(@babel/core@7.29.0))(webpack@5.105.0)
'@fleetbase/storefront-engine':
specifier: ^0.4.13
version: 0.4.13(@ember/string@3.1.1)(@ember/test-helpers@3.3.1(@babel/core@7.29.0)(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(webpack@5.105.0))(@glimmer/component@1.1.2(@babel/core@7.29.0))(@glimmer/tracking@1.1.2)(ember-engines@0.9.0(@ember/legacy-built-in-components@0.4.2(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-resolver@11.0.1(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(eslint@8.57.1)(postcss@8.5.6)(rollup@2.79.2)(tracked-built-ins@3.4.0(@babel/core@7.29.0))(webpack@5.105.0)
@@ -1544,8 +1544,8 @@ packages:
resolution: {integrity: sha512-uCX/qB4ANDGNN+EM1vdsVc4inprGEwj1dT0G5OTYKsFaHL3CWOeXsOg8qSa5EDClqxIodadx6stB+dSwrhYowg==}
engines: {node: '>= 18'}
'@fleetbase/fleetops-engine@0.6.36':
resolution: {integrity: sha512-Segq0+W5VHDcjxJ2DL5mbWSsY0Zp6f+jrlE2+Ub6EUJ5BFiqg1vR6NsypiC+uZCSqI8PjHzekIaZSQaJzv/N1g==}
'@fleetbase/fleetops-engine@0.6.37':
resolution: {integrity: sha512-bmhT26lloVFUJNq8hSzHNL/3C7bONdsCT9qNrD1sFKpKQLA5c7Jsti4pWkb+95sqIuqiYZ7ccfyoOXzGUr7MIA==}
engines: {node: '>= 18'}
peerDependencies:
ember-engines: ^0.9.0
@@ -1563,8 +1563,8 @@ packages:
'@fleetbase/leaflet-routing-machine@3.2.17':
resolution: {integrity: sha512-2S/XLPzf25ZKV7cFJwfeu4voYQboF9JiDfpRUTrif4XCfgdrQ2Zim7O5iTpoNv2l8Ne8D+Ed7BGJsKWjJFLcsw==}
'@fleetbase/registry-bridge-engine@0.1.6':
resolution: {integrity: sha512-FmmVQfMMGy1xzmLfVDpLaOg/mf0hfwfL3k+Ae9+QOl+s0GLGzaKX6K/lxviHP/DotzEJEtsuNGZ5jpA+mjzAow==}
'@fleetbase/registry-bridge-engine@0.1.7':
resolution: {integrity: sha512-z6bA8/DeJ/lTVyjO24W2ltt0C6/GwM9lHO17DwDlyxBh6o6t8htd/PvQap8qcys7tOZXVXu7i4ufkdvb6DwbyQ==}
engines: {node: '>= 18'}
peerDependencies:
ember-engines: ^0.9.0
@@ -10774,7 +10774,7 @@ snapshots:
- utf-8-validate
- webpack
'@fleetbase/fleetops-engine@0.6.36(@ember/string@3.1.1)(@ember/test-helpers@3.3.1(@babel/core@7.29.0)(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(webpack@5.105.0))(@glimmer/component@1.1.2(@babel/core@7.29.0))(@glimmer/tracking@1.1.2)(ember-engines@0.9.0(@ember/legacy-built-in-components@0.4.2(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-resolver@11.0.1(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(eslint@8.57.1)(postcss@8.5.6)(rollup@2.79.2)(tracked-built-ins@3.4.0(@babel/core@7.29.0))(webpack@5.105.0)':
'@fleetbase/fleetops-engine@0.6.37(@ember/string@3.1.1)(@ember/test-helpers@3.3.1(@babel/core@7.29.0)(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(webpack@5.105.0))(@glimmer/component@1.1.2(@babel/core@7.29.0))(@glimmer/tracking@1.1.2)(ember-engines@0.9.0(@ember/legacy-built-in-components@0.4.2(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-resolver@11.0.1(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(eslint@8.57.1)(postcss@8.5.6)(rollup@2.79.2)(tracked-built-ins@3.4.0(@babel/core@7.29.0))(webpack@5.105.0)':
dependencies:
'@babel/core': 7.29.0
'@fleetbase/ember-core': 0.3.12(@ember/string@3.1.1)(@ember/test-helpers@3.3.1(@babel/core@7.29.0)(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(webpack@5.105.0))(ember-resolver@11.0.1(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(eslint@8.57.1)(webpack@5.105.0)
@@ -10889,7 +10889,7 @@ snapshots:
'@mapbox/polyline': 0.2.0
osrm-text-instructions: 0.13.4
'@fleetbase/registry-bridge-engine@0.1.6(@ember/string@3.1.1)(@ember/test-helpers@3.3.1(@babel/core@7.29.0)(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(webpack@5.105.0))(@glimmer/component@1.1.2(@babel/core@7.29.0))(@glimmer/tracking@1.1.2)(ember-engines@0.9.0(@ember/legacy-built-in-components@0.4.2(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-resolver@11.0.1(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(eslint@8.57.1)(postcss@8.5.6)(rollup@2.79.2)(tracked-built-ins@3.4.0(@babel/core@7.29.0))(webpack@5.105.0)':
'@fleetbase/registry-bridge-engine@0.1.7(@ember/string@3.1.1)(@ember/test-helpers@3.3.1(@babel/core@7.29.0)(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(webpack@5.105.0))(@glimmer/component@1.1.2(@babel/core@7.29.0))(@glimmer/tracking@1.1.2)(ember-engines@0.9.0(@ember/legacy-built-in-components@0.4.2(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-resolver@11.0.1(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(eslint@8.57.1)(postcss@8.5.6)(rollup@2.79.2)(tracked-built-ins@3.4.0(@babel/core@7.29.0))(webpack@5.105.0)':
dependencies:
'@babel/core': 7.29.0
'@fleetbase/ember-core': 0.3.12(@ember/string@3.1.1)(@ember/test-helpers@3.3.1(@babel/core@7.29.0)(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(webpack@5.105.0))(ember-resolver@11.0.1(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(eslint@8.57.1)(webpack@5.105.0)

View File

@@ -75,7 +75,7 @@ ENV QUEUE_CONNECTION=redis
ENV CADDYFILE_PATH=/fleetbase/Caddyfile
ENV CONSOLE_PATH=/fleetbase/console
ENV OCTANE_SERVER=frankenphp
ENV FLEETBASE_VERSION=0.7.29
ENV FLEETBASE_VERSION=0.7.30
# Set environment
ARG ENVIRONMENT=production

View File

@@ -1,200 +1,562 @@
#!/usr/bin/env bash
# scripts/docker-install.sh
# Fleetbase Docker installer (dev / prod aware)
# --------------------------------------------
# Fleetbase Docker installer — interactive setup wizard
# -------------------------------------------------------
# Usage:
# bash scripts/docker-install.sh # interactive (default)
# bash scripts/docker-install.sh --non-interactive # CI/CD, all defaults
# -------------------------------------------------------
set -euo pipefail
###############################################################################
# 1. Ask for host (default: localhost)
###############################################################################
read -rp "Enter host or IP address to bind to [localhost]: " HOST_INPUT
HOST=${HOST_INPUT:-localhost}
echo "➜ Using host: $HOST"
# ─── Colour helpers ──────────────────────────────────────────────────────────
RED='\033[0;31m'; YELLOW='\033[1;33m'; GREEN='\033[0;32m'
CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m'
info() { echo -e "${CYAN} ${RESET}$*"; }
success() { echo -e "${GREEN}${RESET}$*"; }
warn() { echo -e "${YELLOW}${RESET}$*"; }
error() { echo -e "${RED}${RESET}$*" >&2; }
section() { echo -e "\n${BOLD}── $* $(printf '─%.0s' {1..40})${RESET}"; }
###############################################################################
# 2. Ask for environment (development | production)
###############################################################################
while true; do
read -rp "Choose environment (development / production) [development]: " ENV_INPUT
ENV_INPUT=$(echo "$ENV_INPUT" | tr '[:upper:]' '[:lower:]')
case "$ENV_INPUT" in
""|d|dev|development) ENVIRONMENT=development; break ;;
p|prod|production) ENVIRONMENT=production; break ;;
*) echo "Please type either 'development' or 'production'." ;;
esac
# ─── Non-interactive flag ────────────────────────────────────────────────────
NON_INTERACTIVE=false
for arg in "$@"; do
[[ "$arg" == "--non-interactive" ]] && NON_INTERACTIVE=true
done
echo "➜ Environment: $ENVIRONMENT"
$NON_INTERACTIVE && info "Non-interactive mode: all optional steps will use safe defaults."
USE_HTTPS=false
APP_DEBUG=true
SC_SECURE=false
if [[ "$ENVIRONMENT" == "production" ]]; then
USE_HTTPS=true
APP_DEBUG=false
SC_SECURE=true
# ─── Helper: generate a random hex secret ────────────────────────────────────
gen_secret() { openssl rand -hex "${1:-20}"; }
# ─── Helper: append a non-empty env var line to the override builder ─────────
# Usage: env_line VAR_NAME "value" → echoes ' VAR_NAME: "value"' if non-empty
env_line() {
local key="$1" val="$2"
[[ -z "$val" ]] && return
printf ' %s: "%s"\n' "$key" "$val"
}
echo
echo -e "${BOLD}🚀 Fleetbase Installation Wizard${RESET}"
echo
###############################################################################
# STEP 0 — Pre-flight checks
###############################################################################
section "Pre-flight Checks"
# Required tools
for tool in docker git openssl; do
if ! command -v "$tool" >/dev/null 2>&1; then
error "$tool is required but not found. Install it and retry."
exit 1
fi
success "$tool found"
done
# Docker Compose v2
if ! docker compose version >/dev/null 2>&1; then
error "'docker compose' (v2) is required. Please upgrade Docker Desktop or install the Compose plugin."
exit 1
fi
success "Docker Compose v2 found"
# Port availability (warn only — do not block)
for port_label in "8000:API" "4200:Console" "3306:MySQL" "38000:SocketCluster"; do
port="${port_label%%:*}"
label="${port_label##*:}"
if ss -tlnp 2>/dev/null | grep -q ":${port} " || \
netstat -tlnp 2>/dev/null | grep -q ":${port} "; then
warn "Port ${port} (${label}) is already in use — this may cause a conflict."
else
success "Port ${port} (${label}) is free"
fi
done
success "Pre-flight checks complete"
###############################################################################
# STEP 1 — Core parameters
###############################################################################
section "Core Configuration"
if $NON_INTERACTIVE; then
HOST="localhost"
ENVIRONMENT="development"
APP_NAME="Fleetbase"
else
read -rp "Host or IP address to bind to [localhost]: " HOST_INPUT
HOST="${HOST_INPUT:-localhost}"
while true; do
read -rp "Environment (development / production) [development]: " ENV_INPUT
ENV_INPUT=$(echo "$ENV_INPUT" | tr '[:upper:]' '[:lower:]')
case "$ENV_INPUT" in
""|d|dev|development) ENVIRONMENT=development; break ;;
p|prod|production) ENVIRONMENT=production; break ;;
*) warn "Please type either 'development' or 'production'." ;;
esac
done
read -rp "Application name [Fleetbase]: " APP_NAME_INPUT
APP_NAME="${APP_NAME_INPUT:-Fleetbase}"
fi
# Derive scheme flags
USE_HTTPS=false; APP_DEBUG=true; SC_SECURE=false
[[ "$ENVIRONMENT" == "production" ]] && { USE_HTTPS=true; APP_DEBUG=false; SC_SECURE=true; }
SCHEME_API=$([[ "$USE_HTTPS" == true ]] && echo "https" || echo "http")
SCHEME_CONSOLE=$([[ "$USE_HTTPS" == true ]] && echo "https" || echo "http")
# Detect localhost
IS_LOCALHOST=false
[[ "$HOST" == "localhost" || "$HOST" == "0.0.0.0" || "$HOST" == "127.0.0.1" ]] && IS_LOCALHOST=true
info "Host: $HOST | Environment: $ENVIRONMENT | App name: $APP_NAME"
###############################################################################
# 3. Determine project root no matter where script is called from
# STEP 2 — Locate project root
###############################################################################
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
PROJECT_ROOT="$( cd "$SCRIPT_DIR/.." && pwd )"
cd "$PROJECT_ROOT"
###############################################################################
# 4. Generate a fresh Laravel APP_KEY
# STEP 3 — Database configuration
###############################################################################
if ! command -v openssl >/dev/null 2>&1; then
echo "✖ openssl is required but not found. Install it and retry." >&2
exit 1
section "Database Configuration"
DB_MODE="internal" # default
if ! $NON_INTERACTIVE; then
echo " 1) Bundled Docker MySQL (recommended for development)"
echo " 2) External MySQL server (e.g. AWS RDS, PlanetScale)"
read -rp "Choose [1]: " DB_CHOICE_INPUT
[[ "${DB_CHOICE_INPUT:-1}" == "2" ]] && DB_MODE="external"
fi
if [[ "$DB_MODE" == "external" ]]; then
read -rp " Database host [127.0.0.1]: " DB_HOST_INPUT; DB_HOST="${DB_HOST_INPUT:-127.0.0.1}"
read -rp " Database port [3306]: " DB_PORT_INPUT; DB_PORT="${DB_PORT_INPUT:-3306}"
read -rp " Database name [fleetbase]: " DB_NAME_INPUT; DB_NAME="${DB_NAME_INPUT:-fleetbase}"
read -rp " Database username: " DB_USER
read -srp " Database password: " DB_PASS; echo
# URL-encode the password (basic: replace @ and / which are most problematic)
DB_PASS_ENC=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1],safe=''))" "$DB_PASS" 2>/dev/null || echo "$DB_PASS")
DATABASE_URL="mysql://${DB_USER}:${DB_PASS_ENC}@${DB_HOST}:${DB_PORT}/${DB_NAME}"
DB_ROOT_PASSWORD=""
DB_USERNAME="$DB_USER"
DB_PASSWORD="$DB_PASS"
DB_DATABASE="$DB_NAME"
success "External database configured"
else
DB_ROOT_PASSWORD="$(gen_secret 20)"
DB_PASSWORD="$(gen_secret 20)"
DB_USERNAME="fleetbase"
DB_DATABASE="fleetbase"
DATABASE_URL="mysql://${DB_USERNAME}:${DB_PASSWORD}@database/${DB_DATABASE}"
success "Secure database credentials auto-generated"
fi
APP_KEY="base64:$(openssl rand -base64 32 | tr -d '\n')"
echo "✔ Generated APP_KEY"
###############################################################################
# 5. Ensure dockercompose.override.yml is present & updated
# STEP 4 — Mail configuration
###############################################################################
section "Mail Configuration"
MAIL_MAILER="log"
MAIL_HOST=""; MAIL_PORT=""; MAIL_USERNAME=""; MAIL_PASSWORD=""
MAIL_FROM_ADDRESS=""; MAIL_FROM_NAME="$APP_NAME"
MAILGUN_DOMAIN=""; MAILGUN_SECRET=""
POSTMARK_TOKEN=""; SENDGRID_API_KEY=""; RESEND_KEY=""
CONFIG_MAIL=false
if ! $NON_INTERACTIVE; then
read -rp "Configure a mail server? Required for password resets & notifications (y/N): " MAIL_YN
[[ "${MAIL_YN,,}" == "y" || "${MAIL_YN,,}" == "yes" ]] && CONFIG_MAIL=true
fi
if $CONFIG_MAIL; then
echo " Mail drivers: 1) SMTP 2) Mailgun 3) Postmark 4) SendGrid 5) Resend 6) AWS SES 7) Log only"
read -rp " Choose driver [1]: " MAIL_DRIVER_INPUT
case "${MAIL_DRIVER_INPUT:-1}" in
2) MAIL_MAILER="mailgun" ;;
3) MAIL_MAILER="postmark" ;;
4) MAIL_MAILER="sendgrid" ;;
5) MAIL_MAILER="resend" ;;
6) MAIL_MAILER="ses" ;;
7) MAIL_MAILER="log" ;;
*) MAIL_MAILER="smtp" ;;
esac
DEFAULT_FROM="hello@$( $IS_LOCALHOST && echo 'example.com' || echo "$HOST" )"
read -rp " From address [$DEFAULT_FROM]: " MAIL_FROM_INPUT
MAIL_FROM_ADDRESS="${MAIL_FROM_INPUT:-$DEFAULT_FROM}"
read -rp " From name [$APP_NAME]: " MAIL_FROM_NAME_INPUT
MAIL_FROM_NAME="${MAIL_FROM_NAME_INPUT:-$APP_NAME}"
case "$MAIL_MAILER" in
smtp)
read -rp " SMTP host [smtp.mailgun.org]: " MAIL_HOST_INPUT; MAIL_HOST="${MAIL_HOST_INPUT:-smtp.mailgun.org}"
read -rp " SMTP port [587]: " MAIL_PORT_INPUT; MAIL_PORT="${MAIL_PORT_INPUT:-587}"
read -rp " SMTP username: " MAIL_USERNAME
read -srp " SMTP password: " MAIL_PASSWORD; echo
;;
mailgun)
read -rp " Mailgun domain: " MAILGUN_DOMAIN
read -srp " Mailgun API secret: " MAILGUN_SECRET; echo
;;
postmark)
read -srp " Postmark server token: " POSTMARK_TOKEN; echo
;;
sendgrid)
read -srp " SendGrid API key: " SENDGRID_API_KEY; echo
;;
resend)
read -srp " Resend API key: " RESEND_KEY; echo
;;
ses)
info "AWS SES will use the AWS credentials configured in the Storage step."
;;
esac
success "Mail driver set to: $MAIL_MAILER"
else
info "Skipped — emails will be written to the application log."
fi
###############################################################################
# STEP 5 — File storage
###############################################################################
section "File Storage Configuration"
FILESYSTEM_DRIVER="public"
AWS_ACCESS_KEY_ID=""; AWS_SECRET_ACCESS_KEY=""; AWS_DEFAULT_REGION=""
AWS_BUCKET=""; AWS_URL=""; AWS_USE_PATH_STYLE_ENDPOINT=""
GOOGLE_CLOUD_PROJECT_ID=""; GOOGLE_CLOUD_STORAGE_BUCKET=""; GOOGLE_CLOUD_KEY_FILE=""
if ! $NON_INTERACTIVE; then
echo " Storage drivers: 1) Local disk (dev only) 2) AWS S3 3) Google Cloud Storage"
read -rp " Choose driver [1]: " STORAGE_INPUT
case "${STORAGE_INPUT:-1}" in
2) FILESYSTEM_DRIVER="s3" ;;
3) FILESYSTEM_DRIVER="gcs" ;;
*) FILESYSTEM_DRIVER="public" ;;
esac
fi
if [[ "$FILESYSTEM_DRIVER" == "s3" ]]; then
read -rp " AWS Access Key ID: " AWS_ACCESS_KEY_ID
read -srp " AWS Secret Access Key: " AWS_SECRET_ACCESS_KEY; echo
read -rp " AWS Region [us-east-1]: " AWS_REGION_INPUT; AWS_DEFAULT_REGION="${AWS_REGION_INPUT:-us-east-1}"
read -rp " S3 Bucket name: " AWS_BUCKET
read -rp " S3 Public URL (leave blank for default): " AWS_URL
read -rp " Use path-style endpoint? (for MinIO/non-AWS S3) (y/N): " PATH_STYLE_INPUT
[[ "${PATH_STYLE_INPUT,,}" == "y" ]] && AWS_USE_PATH_STYLE_ENDPOINT="true"
success "S3 storage configured"
elif [[ "$FILESYSTEM_DRIVER" == "gcs" ]]; then
read -rp " GCS Project ID: " GOOGLE_CLOUD_PROJECT_ID
read -rp " GCS Bucket name: " GOOGLE_CLOUD_STORAGE_BUCKET
read -rp " Path to GCS key file (JSON): " GOOGLE_CLOUD_KEY_FILE
success "Google Cloud Storage configured"
else
info "Local disk selected — suitable for development only."
fi
###############################################################################
# STEP 6 — Security & CORS
###############################################################################
section "Security & CORS Configuration"
# Derive SESSION_DOMAIN
SESSION_DOMAIN="$( $IS_LOCALHOST && echo 'localhost' || echo "$HOST" )"
# Derive SOCKETCLUSTER_OPTIONS origins
if $IS_LOCALHOST; then
SOCKET_ORIGINS="http://localhost:*,https://localhost:*,ws://localhost:*,wss://localhost:*"
else
SOCKET_ORIGINS="${SCHEME_CONSOLE}://${HOST}:*,wss://${HOST}:*"
fi
SOCKETCLUSTER_OPTIONS="{\"origins\":\"${SOCKET_ORIGINS}\"}"
success "SESSION_DOMAIN set to: $SESSION_DOMAIN"
success "WebSocket origins restricted to: $SOCKET_ORIGINS"
FRONTEND_HOSTS=""
if ! $NON_INTERACTIVE; then
read -rp "Additional frontend hosts for CORS (comma-separated, leave blank for none): " FRONTEND_HOSTS
fi
###############################################################################
# STEP 7 — Optional third-party API keys
###############################################################################
section "Optional Third-Party Services"
IPINFO_API_KEY=""; GOOGLE_MAPS_API_KEY=""; GOOGLE_MAPS_LOCALE="us"
TWILIO_SID=""; TWILIO_TOKEN=""; TWILIO_FROM=""
CONFIG_3P=false
if ! $NON_INTERACTIVE; then
read -rp "Configure optional third-party API keys now? (Maps, Geolocation, SMS) (y/N): " TP_YN
[[ "${TP_YN,,}" == "y" || "${TP_YN,,}" == "yes" ]] && CONFIG_3P=true
fi
if $CONFIG_3P; then
read -rp " IPInfo API key (geolocation, leave blank to skip): " IPINFO_API_KEY
read -rp " Google Maps API key (leave blank to skip): " GOOGLE_MAPS_API_KEY
read -rp " Google Maps locale [us]: " GM_LOCALE_INPUT; GOOGLE_MAPS_LOCALE="${GM_LOCALE_INPUT:-us}"
read -rp " Twilio Account SID (SMS, leave blank to skip): " TWILIO_SID
read -srp " Twilio Auth Token: " TWILIO_TOKEN; echo
read -rp " Twilio From phone number: " TWILIO_FROM
success "Third-party services configured"
else
info "Skipped — these can be added later via docker-compose.override.yml"
fi
###############################################################################
# STEP 8 — Generate APP_KEY
###############################################################################
section "Generating Application Key"
APP_KEY="base64:$(openssl rand -base64 32 | tr -d '\n')"
success "APP_KEY generated"
###############################################################################
# STEP 9 — Write docker-compose.override.yml
###############################################################################
section "Writing docker-compose.override.yml"
OVERRIDE_FILE="docker-compose.override.yml"
# url helpers
SCHEME_API=$([[ "$USE_HTTPS" == true ]] && echo "https" || echo "http")
SCHEME_CONSOLE=$([[ "$USE_HTTPS" == true ]] && echo "https" || echo "http")
# Back up any existing override
if [[ -f "$OVERRIDE_FILE" ]]; then
BACKUP="${OVERRIDE_FILE}.bak.$(date +%Y%m%d%H%M%S)"
cp "$OVERRIDE_FILE" "$BACKUP"
info "Existing override backed up to $BACKUP"
fi
update_override_with_yq() {
yq -i "
.services.application.environment.APP_KEY = \"$APP_KEY\" |
.services.application.environment.CONSOLE_HOST = \"$SCHEME_CONSOLE://$HOST:4200\" |
.services.application.environment.ENVIRONMENT = \"$ENVIRONMENT\" |
.services.application.environment.APP_DEBUG = \"$APP_DEBUG\"
" "$OVERRIDE_FILE"
echo "$OVERRIDE_FILE updated (yq)"
}
# Build the file using a temp file for atomicity
OVERRIDE_TMP="${OVERRIDE_FILE}.tmp.$$"
create_override() {
cat > "$OVERRIDE_FILE" <<YML
{
cat <<YAML_HEADER
services:
application:
environment:
APP_KEY: "$APP_KEY"
CONSOLE_HOST: "$SCHEME_CONSOLE://$HOST:4200"
ENVIRONMENT: "$ENVIRONMENT"
APP_DEBUG: "$APP_DEBUG"
YML
echo "$OVERRIDE_FILE written"
}
YAML_HEADER
if [[ -f "$OVERRIDE_FILE" ]]; then
if command -v yq >/dev/null 2>&1; then
update_override_with_yq
else
cp "$OVERRIDE_FILE" "${OVERRIDE_FILE}.bak.$(date +%Y%m%d%H%M%S)"
echo " Existing $OVERRIDE_FILE backed up (no yq found — recreating)"
create_override
env_line "APP_KEY" "$APP_KEY"
env_line "APP_NAME" "$APP_NAME"
env_line "APP_URL" "${SCHEME_API}://${HOST}:8000"
env_line "CONSOLE_HOST" "${SCHEME_CONSOLE}://${HOST}:4200"
env_line "ENVIRONMENT" "$ENVIRONMENT"
env_line "APP_DEBUG" "$APP_DEBUG"
env_line "DATABASE_URL" "$DATABASE_URL"
env_line "SESSION_DOMAIN" "$SESSION_DOMAIN"
env_line "FRONTEND_HOSTS" "$FRONTEND_HOSTS"
# Mail
env_line "MAIL_MAILER" "$MAIL_MAILER"
env_line "MAIL_HOST" "$MAIL_HOST"
env_line "MAIL_PORT" "$MAIL_PORT"
env_line "MAIL_USERNAME" "$MAIL_USERNAME"
env_line "MAIL_PASSWORD" "$MAIL_PASSWORD"
env_line "MAIL_FROM_ADDRESS" "$MAIL_FROM_ADDRESS"
env_line "MAIL_FROM_NAME" "$MAIL_FROM_NAME"
env_line "MAILGUN_DOMAIN" "$MAILGUN_DOMAIN"
env_line "MAILGUN_SECRET" "$MAILGUN_SECRET"
env_line "POSTMARK_TOKEN" "$POSTMARK_TOKEN"
env_line "SENDGRID_API_KEY" "$SENDGRID_API_KEY"
env_line "RESEND_KEY" "$RESEND_KEY"
# Storage
[[ "$FILESYSTEM_DRIVER" != "public" ]] && env_line "FILESYSTEM_DRIVER" "$FILESYSTEM_DRIVER"
env_line "AWS_ACCESS_KEY_ID" "$AWS_ACCESS_KEY_ID"
env_line "AWS_SECRET_ACCESS_KEY" "$AWS_SECRET_ACCESS_KEY"
env_line "AWS_DEFAULT_REGION" "$AWS_DEFAULT_REGION"
env_line "AWS_BUCKET" "$AWS_BUCKET"
env_line "AWS_URL" "$AWS_URL"
env_line "AWS_USE_PATH_STYLE_ENDPOINT" "$AWS_USE_PATH_STYLE_ENDPOINT"
env_line "GOOGLE_CLOUD_PROJECT_ID" "$GOOGLE_CLOUD_PROJECT_ID"
env_line "GOOGLE_CLOUD_STORAGE_BUCKET" "$GOOGLE_CLOUD_STORAGE_BUCKET"
env_line "GOOGLE_CLOUD_KEY_FILE" "$GOOGLE_CLOUD_KEY_FILE"
# Third-party
env_line "IPINFO_API_KEY" "$IPINFO_API_KEY"
env_line "GOOGLE_MAPS_API_KEY" "$GOOGLE_MAPS_API_KEY"
env_line "GOOGLE_MAPS_LOCALE" "$GOOGLE_MAPS_LOCALE"
env_line "TWILIO_SID" "$TWILIO_SID"
env_line "TWILIO_TOKEN" "$TWILIO_TOKEN"
env_line "TWILIO_FROM" "$TWILIO_FROM"
cat <<YAML_SOCKET
socket:
environment:
SOCKETCLUSTER_OPTIONS: '${SOCKETCLUSTER_OPTIONS}'
YAML_SOCKET
# Add database service block only when using the bundled container
if [[ "$DB_MODE" == "internal" ]]; then
cat <<YAML_DB
database:
environment:
MYSQL_ROOT_PASSWORD: "${DB_ROOT_PASSWORD}"
MYSQL_DATABASE: "${DB_DATABASE}"
MYSQL_USER: "${DB_USERNAME}"
MYSQL_PASSWORD: "${DB_PASSWORD}"
MYSQL_ALLOW_EMPTY_PASSWORD: "no"
YAML_DB
fi
else
create_override
fi
} > "$OVERRIDE_TMP"
mv -f "$OVERRIDE_TMP" "$OVERRIDE_FILE"
success "$OVERRIDE_FILE written"
###############################################################################
# 6. Write console/fleetbase.config.json atomically (for development runtime)
# STEP 10 — Write console configuration files
###############################################################################
section "Updating Console Configuration"
CONFIG_DIR="console"
CONFIG_PATH="$CONFIG_DIR/fleetbase.config.json"
mkdir -p "$CONFIG_DIR"
cat > "${CONFIG_PATH}.tmp" <<JSON
OSRM_HOST="https://router.project-osrm.org"
cat > "${CONFIG_DIR}/fleetbase.config.json.tmp" <<JSON
{
"API_HOST": "$SCHEME_API://$HOST:8000",
"SOCKETCLUSTER_HOST": "$HOST",
"API_HOST": "${SCHEME_API}://${HOST}:8000",
"SOCKETCLUSTER_HOST": "${HOST}",
"SOCKETCLUSTER_PORT": "38000",
"SOCKETCLUSTER_SECURE": "$SC_SECURE"
"SOCKETCLUSTER_SECURE": "${SC_SECURE}"
}
JSON
mv -f "${CONFIG_PATH}.tmp" "$CONFIG_PATH"
echo "$CONFIG_PATH updated"
mv -f "${CONFIG_DIR}/fleetbase.config.json.tmp" "${CONFIG_DIR}/fleetbase.config.json"
###############################################################################
# 6b. Update console environment files (.env.development and .env.production)
###############################################################################
ENV_DIR="$CONFIG_DIR/environments"
ENV_DIR="${CONFIG_DIR}/environments"
mkdir -p "$ENV_DIR"
# Update .env.development
cat > "$ENV_DIR/.env.development" <<ENV_DEV
API_HOST=http://$HOST:8000
cat > "${ENV_DIR}/.env.development" <<ENV_DEV
API_HOST=http://${HOST}:8000
API_NAMESPACE=int/v1
SOCKETCLUSTER_PATH=/socketcluster/
SOCKETCLUSTER_HOST=$HOST
SOCKETCLUSTER_HOST=${HOST}
SOCKETCLUSTER_SECURE=false
SOCKETCLUSTER_PORT=38000
OSRM_HOST=https://router.project-osrm.org
OSRM_HOST=${OSRM_HOST}
ENV_DEV
# Update .env.production
cat > "$ENV_DIR/.env.production" <<ENV_PROD
API_HOST=https://$HOST:8000
cat > "${ENV_DIR}/.env.production" <<ENV_PROD
API_HOST=https://${HOST}:8000
API_NAMESPACE=int/v1
API_SECURE=true
SOCKETCLUSTER_PATH=/socketcluster/
SOCKETCLUSTER_HOST=$HOST
SOCKETCLUSTER_HOST=${HOST}
SOCKETCLUSTER_SECURE=true
SOCKETCLUSTER_PORT=38000
OSRM_HOST=https://router.project-osrm.org
OSRM_HOST=${OSRM_HOST}
ENV_PROD
echo "✔ Console environment files updated"
success "Console configuration files updated"
###############################################################################
# 7. Start stack, wait for DB, then run deploy
# STEP 11 — Start containers
###############################################################################
echo "⏳ Starting Fleetbase containers..."
section "Starting Fleetbase Containers"
echo " This may take a few minutes on first run..."
docker compose up -d
###############################################################################
# 7a. Wait for the database container to be ready
# STEP 12 — Wait for database
###############################################################################
DB_SERVICE="database" # ← change if your dockercompose uses a different name
DB_WAIT_TIMEOUT=60 # seconds
section "Waiting for Database"
DB_SERVICE="database"
DB_WAIT_TIMEOUT=90
echo "⏳ Waiting for $DB_SERVICE to become ready (timeout: ${DB_WAIT_TIMEOUT}s)..."
DB_CONTAINER=$(docker compose ps -q "$DB_SERVICE")
if [ -z "$DB_CONTAINER" ]; then
echo "✖ Cannot find a running container for service \"$DB_SERVICE\". Check dockercompose.yml."
DB_CONTAINER=$(docker compose ps -q "$DB_SERVICE" 2>/dev/null || true)
if [[ -z "$DB_CONTAINER" ]]; then
error "Cannot find a running container for service \"$DB_SERVICE\". Check docker-compose.yml."
exit 1
fi
# If the service defines a HEALTHCHECK we can rely on it…
# Prefer Docker HEALTHCHECK if defined, fall back to mysqladmin ping
if docker inspect -f '{{.State.Health.Status}}' "$DB_CONTAINER" &>/dev/null; then
SECONDS=0
until [ "$(docker inspect -f '{{.State.Health.Status}}' "$DB_CONTAINER")" = "healthy" ]; do
if [ "$SECONDS" -ge "$DB_WAIT_TIMEOUT" ]; then
echo "✖ Timed out waiting for the database to become healthy."
until [[ "$(docker inspect -f '{{.State.Health.Status}}' "$DB_CONTAINER")" == "healthy" ]]; do
if (( SECONDS >= DB_WAIT_TIMEOUT )); then
error "Timed out waiting for the database to become healthy."
exit 1
fi
sleep 2
done
sleep 12
sleep 5 # brief grace period after healthy
else
# Fallback: use mysqladmin ping (works for MySQL / MariaDB)
SECONDS=0
until docker compose exec "$DB_SERVICE" sh -c "mysqladmin --silent --wait=1 -uroot -h127.0.0.1 ping" &>/dev/null; do
if [ "$SECONDS" -ge "$DB_WAIT_TIMEOUT" ]; then
echo "✖ Timed out waiting for the database to accept connections."
until docker compose exec -T "$DB_SERVICE" sh -c "mysqladmin --silent --wait=1 -uroot -h127.0.0.1 ping" &>/dev/null; do
if (( SECONDS >= DB_WAIT_TIMEOUT )); then
error "Timed out waiting for the database to accept connections."
exit 1
fi
sleep 2
done
fi
echo "✔ Database is ready."
success "Database is ready"
###############################################################################
# 7b. Run the deploy script inside the application container
# STEP 13 — Run deploy script
###############################################################################
echo "⏳ Running deploy script inside the application container..."
docker compose exec application bash -c "./deploy.sh"
section "Running Deployment Script"
docker compose exec -T application bash -c "./deploy.sh"
docker compose up -d
success "Deployment complete"
###############################################################################
# STEP 14 — Post-install summary
###############################################################################
CONFIGURED_ITEMS=()
SKIPPED_ITEMS=()
[[ "$DB_MODE" == "external" ]] \
&& CONFIGURED_ITEMS+=("External Database") \
|| CONFIGURED_ITEMS+=("Bundled MySQL (secure credentials auto-generated)")
$CONFIG_MAIL \
&& CONFIGURED_ITEMS+=("Mail (${MAIL_MAILER})") \
|| SKIPPED_ITEMS+=("Mail (using log driver — configure later)")
[[ "$FILESYSTEM_DRIVER" != "public" ]] \
&& CONFIGURED_ITEMS+=("File Storage (${FILESYSTEM_DRIVER^^})") \
|| SKIPPED_ITEMS+=("File storage (local disk — not suitable for production)")
CONFIGURED_ITEMS+=("WebSocket security (origins restricted to ${HOST})")
$CONFIG_3P \
&& CONFIGURED_ITEMS+=("Third-party APIs (Maps, Geolocation, SMS)") \
|| SKIPPED_ITEMS+=("Third-party APIs (Maps, Geolocation, SMS)")
echo
echo "🏁 Fleetbase is up!"
printf " API → %s://%s:8000\n" "$SCHEME_API" "$HOST"
printf " Console → %s://%s:4200\n\n" "$SCHEME_CONSOLE" "$HOST"
printf '%0.s═' {1..60}; echo
echo -e " ${BOLD}🏁 Fleetbase Installation Complete${RESET}"
printf '%0.s═' {1..60}; echo
echo
echo " 📍 Endpoints"
printf " API → %s://%s:8000\n" "$SCHEME_API" "$HOST"
printf " Console → %s://%s:4200\n" "$SCHEME_CONSOLE" "$HOST"
if [[ ${#CONFIGURED_ITEMS[@]} -gt 0 ]]; then
echo
echo " ✔ Configured:"
for item in "${CONFIGURED_ITEMS[@]}"; do echo "$item"; done
fi
if [[ ${#SKIPPED_ITEMS[@]} -gt 0 ]]; then
echo
echo " ⚠ Skipped (defaults applied):"
for item in "${SKIPPED_ITEMS[@]}"; do echo "$item"; done
fi
echo
echo " 🔐 Next Steps"
echo " 1. Open the Console URL in your browser."
echo " 2. Complete the onboarding wizard to create your"
echo " initial organization and administrator account."
if [[ ${#SKIPPED_ITEMS[@]} -gt 0 ]]; then
echo " 3. To configure skipped options, edit"
echo " docker-compose.override.yml and run:"
echo " docker compose up -d"
fi
echo
echo " 📄 Config saved to: docker-compose.override.yml"
printf '%0.s═' {1..60}; echo
echo