Compare commits

...

67 Commits

Author SHA1 Message Date
Ron
723deff398 Merge pull request #401 from fleetbase/dev-v0.7.5
Some checks failed
Fleetbase CI / Build and Start Docker Services (push) Has been cancelled
v0.7.5 ~ Added route optimization and routing control registry and se…
2025-05-30 17:07:44 +08:00
Ronald A. Richardson
fd9adc3961 update composer.json 2025-05-30 17:07:03 +08:00
Ronald A. Richardson
4244a04052 upgraded fleetops 2025-05-30 16:57:31 +08:00
Ronald A. Richardson
e3c60a2232 fix release md typo 2025-05-30 16:15:44 +08:00
Ronald A. Richardson
1eaeb2c46e updated release file 2025-05-30 16:14:26 +08:00
Ronald A. Richardson
1d64d18b8b v0.7.5 ~ Added route optimization and routing control registry and settings & optimized environment/settings mapper 2025-05-30 16:10:56 +08:00
Ron
1124ecb56c Merge pull request #400 from fleetbase/dev-v0.7.4
Some checks failed
Fleetbase CI / Build and Start Docker Services (push) Has been cancelled
v0.7.4 ~ new docker install script, added logic condition property sh…
2025-05-26 15:54:09 +08:00
Ronald A. Richardson
672f3d51ca docker installer: added 12 sec delay before deploy script run 2025-05-26 15:46:05 +08:00
Ronald A. Richardson
cd5af8dfc8 added feature to wait database running in docker install script 2025-05-26 15:32:49 +08:00
Ronald A. Richardson
1a0073eae0 few tweaks to readme and install script 2025-05-26 15:20:48 +08:00
Ronald A. Richardson
d24b1d6fbe update release and readme 2025-05-26 14:59:20 +08:00
Ronald A. Richardson
ebbc4b2bf8 v0.7.4 ~ new docker install script, added logic condition property shortcut keys 2025-05-26 14:52:32 +08:00
Ron
b531c18d65 Merge pull request #399 from fleetbase/hotfix/ci-macos-binary-build
Some checks failed
Fleetbase CI / Build and Start Docker Services (push) Has been cancelled
attempt to patch macos binary build ci
2025-05-24 14:03:54 +08:00
Ronald A. Richardson
fded8b24df attempt to patch macos binary build ci 2025-05-24 13:59:57 +08:00
Ron
98d082c780 Merge pull request #398 from fleetbase/dev-v0.7.3
v0.7.3 ~ hotfix: route optimization w/ no driver, seeder command
2025-05-24 13:26:23 +08:00
Ronald A. Richardson
d905943511 bump fleetops api version 2025-05-24 13:19:52 +08:00
Ronald A. Richardson
5c73b6e76d v0.7.3 ~ hotfix: route optimization w/ no driver, seeder command 2025-05-24 13:16:47 +08:00
Ron
cedf96fc97 Merge pull request #397 from fleetbase/dev-v0.7.2
Some checks are pending
Fleetbase CI / Build and Start Docker Services (push) Waiting to run
v0.7.2 ~ route optimization patch, telemetry patch, network store management patch
2025-05-23 20:07:15 +08:00
Ronald A. Richardson
854fa2e680 fixed release.md date 2025-05-23 20:00:58 +08:00
Ronald A. Richardson
91b01c8a17 updated release info 2025-05-23 19:59:24 +08:00
Ronald A. Richardson
a4033db36c fixed fleetops route optimization, fixed network store management 2025-05-23 19:57:38 +08:00
Ronald A. Richardson
c54ef7fb30 v0.7.2 ~ Telemetry tweak patch 2025-05-22 15:19:01 +08:00
Ronald A. Richardson
b5ec15f0bb fix discord announcement workflow, attempt 4
Some checks failed
Fleetbase CI / Build and Start Docker Services (push) Has been cancelled
2025-05-22 12:47:27 +08:00
Ronald A. Richardson
1f609dd882 fix discord announcement workflow, attempt 3 2025-05-22 12:35:12 +08:00
Ronald A. Richardson
01883da5a2 attempt #2 to fix the discord announcement 2025-05-22 12:29:27 +08:00
Ronald A. Richardson
d2ab5b8a94 fix discord announcement workflow 2025-05-22 12:23:54 +08:00
Ronald A. Richardson
dca23f7e3f fix gh action workflows 2025-05-22 12:18:48 +08:00
Ron
d94dff7fbb Merge pull request #394 from fleetbase/dev-v0.7.1
v0.7.1 ~ Fleetbase console can now read in a runtime config
2025-05-22 12:09:47 +08:00
Ronald A. Richardson
e1ab6a3b11 use release.md for discord announcement 2025-05-22 12:06:58 +08:00
Ronald A. Richardson
c79fe67e44 ready to release, if macos doesnt build fix later 2025-05-22 11:59:09 +08:00
Ronald A. Richardson
d8adf42b24 revert back to previous curl patch 2025-05-22 11:51:05 +08:00
Ronald A. Richardson
80da5fe013 add SPC_OPT_DOWNLOAD_ARGS to attempt to fix osx build on gh runner 2025-05-22 11:25:45 +08:00
Ronald A. Richardson
06fd5e20e8 make osx build script gh runner friendly 2025-05-22 11:21:21 +08:00
Ronald A. Richardson
f04807de1e fixed upload to only run on release workflow, debug macos build on runer 2025-05-22 11:14:53 +08:00
Ronald A. Richardson
b7666eeb3e fix github workflows and setup to debug macos build 2025-05-22 10:45:37 +08:00
Ronald A. Richardson
dd895a0fd8 remove CURRENT_HASH file 2025-05-22 10:31:05 +08:00
Ronald A. Richardson
8c74c0fb99 release is almost ready 2025-05-22 10:30:10 +08:00
Ronald A. Richardson
92170c965e updated docker-compose to use latest images, patched osx binary build script, experimenting with artifact upload for binaries 2025-05-21 22:18:49 +08:00
Ronald A. Richardson
fcb3694874 added curl patch for gh runners 2025-05-21 21:33:45 +08:00
Ronald A. Richardson
aa46059bff minor tweak on php build of osx build script 2025-05-21 21:11:41 +08:00
Ronald A. Richardson
a5175bb11b fix php 8.4 detection in osx build script 2025-05-21 21:08:52 +08:00
Ronald A. Richardson
01816a1fe0 update osx build script to skip asdf install if php 8.4 is already installed 2025-05-21 21:02:04 +08:00
Ronald A. Richardson
15d500cd58 just install php via homebrew 2025-05-21 20:49:37 +08:00
Ronald A. Richardson
95d77a6ddd in osx build workflow use correct asdf commands 2025-05-21 20:45:46 +08:00
Ronald A. Richardson
eefc93e130 debug osx binary build workflow 2025-05-21 20:42:09 +08:00
Ronald A. Richardson
0f18ae85f1 debug osx binary build workflow 2025-05-21 20:35:22 +08:00
Ronald A. Richardson
a4812192da debug osx binary build workflow 2025-05-21 20:26:24 +08:00
Ronald A. Richardson
15d3c957b8 debug osx binary build workflow 2025-05-21 20:20:33 +08:00
Ronald A. Richardson
c2bd098d14 debug osx binary build workflow 2025-05-21 20:16:16 +08:00
Ronald A. Richardson
98511fd418 patch console route && debug osx action binary build 2025-05-21 20:13:14 +08:00
Ronald A. Richardson
225110c8dc attempt to patch binary build workflow 2025-05-21 19:39:04 +08:00
Ronald A. Richardson
1aa2a99763 added workflow to build fleetbase api binaries 2025-05-21 19:35:08 +08:00
Ron
6e888af772 Merge pull request #376 from fleetbase/feature/fleetbase-binary
Working static build script for a fleetbase binary (unix/linux/osx)
2025-05-21 19:24:49 +08:00
Ronald A. Richardson
d61205d898 added fleetbase config file, added ability to set tz for user and organization 2025-05-21 19:21:22 +08:00
Ronald A. Richardson
72078553cc remove hash tracking from docker build 2025-05-20 10:19:42 +08:00
Ronald A. Richardson
bfae04a645 attempt to fix current hash check in build 2025-05-20 10:16:43 +08:00
Ronald A. Richardson
c59f028755 fix docker build 2025-05-20 10:06:26 +08:00
Ronald A. Richardson
2b959db773 remove the current hash file 2025-05-19 19:36:05 +08:00
Ronald A. Richardson
a9354ccbfd removed EXPECTED_HASH checkin 2025-05-19 19:06:21 +08:00
Ronald A. Richardson
23e6d1e6b9 removed EXPECTED_HASH 2025-05-19 17:33:13 +08:00
Ronald A. Richardson
86da1bd095 Improvements to docker setups 2025-05-19 17:30:18 +08:00
Ronald A. Richardson
ae89600ae6 updated dockerhub publish workflow 2025-05-19 13:54:28 +08:00
Ronald A. Richardson
6697b79185 secured runtime config to only allow select config values to be set 2025-05-19 13:50:10 +08:00
Ronald A. Richardson
4dc9764853 v0.7.1 ~ Fleetbase console can now read in a runtime config 2025-05-19 13:12:57 +08:00
Ronald A. Richardson
e372bc6396 minor update to linux build script 2025-05-07 14:32:17 +08:00
Ronald A. Richardson
2f432d148a Remove build artifacts; add dist & downloads to .gitignore 2025-05-07 14:26:57 +08:00
Ronald A. Richardson
8f66bc12e4 Working static build script for a fleetbase binary (unix/linux) 2025-04-16 14:22:07 +08:00
58 changed files with 3105 additions and 1910 deletions

64
.github/workflows/build-binaries.yml vendored Normal file
View File

@@ -0,0 +1,64 @@
name: Build Fleetbase Binaries
on:
workflow_dispatch:
workflow_run:
workflows: ["Create Release"]
types: [completed]
permissions:
contents: write
env:
DIST_DIR: builds/dist
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
build-linux:
name: Linux Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build Linux binary
run: |
chmod +x ./builds/linux/build-linux.sh
./builds/linux/build-linux.sh
- name: Upload Linux binary
if: github.event_name == 'workflow_run'
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.event.workflow_run.head_branch }}
files: |
${{ env.DIST_DIR }}/fleetbase-linux-x86_64
draft: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build-macos:
name: macOS (ARM64) Build
needs: build-linux
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- name: Install build dependencies
run: |
brew update
brew install autoconf automake coreutils asdf php@8.4
source "$(brew --prefix asdf)/libexec/asdf.sh"
asdf plugin add php https://github.com/asdf-community/asdf-php.git
- name: Build macOS binary
run: |
chmod +x ./builds/osx/build-osx.sh
./builds/osx/build-osx.sh
- name: Upload Linux binary
if: github.event_name == 'workflow_run'
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.event.workflow_run.head_branch }}
files: |
${{ env.DIST_DIR }}/fleetbase-darwin-arm64
draft: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

21
.github/workflows/create-release.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Create Release
on:
push:
tags:
- 'v*'
jobs:
create:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Publish GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: ${{ github.ref_name }}
body_path: RELEASE.md
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -0,0 +1,61 @@
name: Discord Announcement
on:
workflow_run:
workflows: ["Create Release"]
types: [completed]
workflow_dispatch:
inputs:
tag:
description: "Release tag to announce (e.g. v0.7.1)"
required: true
jobs:
discord_announcement:
runs-on: ubuntu-latest
steps:
# 1⃣ Figure out which tag were talking about
- id: vars
shell: bash
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
TAG="${{ github.event.inputs.tag }}"
else
TAG="${{ github.event.workflow_run.head_branch }}"
fi
echo "TAG=$TAG" >> "$GITHUB_ENV"
# 2⃣ Check out the exact commit for that tag
- uses: actions/checkout@v3
with:
ref: ${{ env.TAG }}
fetch-depth: 1
# 3⃣ Stash RELEASE.md in an env var (one atomic write → no EOF error)
- id: prep-body
shell: bash
run: |
body=$(<RELEASE.md)
max=4000
[[ ${#body} -gt $max ]] && body="${body:0:$max}…" # add ellipsis if trimmed
{
echo "body<<EOF"
echo "$body"
echo "EOF"
} >> "$GITHUB_OUTPUT"
# 4⃣ Fire the webhook
- uses: tsickert/discord-webhook@v5.3.0
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
username: Fleetbase
content: |
@everyone
📦 **Fleetbase ${{ env.TAG }} released!**
<https://github.com/${{ github.repository }}/releases/tag/${{ env.TAG }}>
embed-title: "Fleetbase ${{ env.TAG }} — release notes"
embed-url: "https://github.com/fleetbase/fleetbase/releases/tag/${{ env.TAG }}"
embed-description: ${{ steps.prep-body.outputs.body }}
embed-color: 4362730 # 0x4291EA (Fleetbase Blue)

View File

@@ -1,48 +0,0 @@
name: Discord Announcement
on:
push:
tags:
- "v*"
jobs:
discord_announcement:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Get tag message
id: tag
run: |
echo "::set-output name=version::$(git describe --tags --abbrev=0)"
if [[ "${ACT}" == "true" ]]; then
# If running with act, use an environment variable for the tag message
echo "::set-output name=message::$(echo -e "${TAG_MESSAGE}" | base64)"
else
# If running on GitHub, use git to get the tag message
echo "::set-output name=message::$(git tag -l --format='%(contents)' $(git describe --tags --abbrev=0) | base64)"
fi
- name: Print tag message
run: echo "${{ steps.tag.outputs.message }}"
- name: Get tag name
id: get_tag
run: echo "::set-output name=tag::${GITHUB_REF/refs\/tags\//}"
- name: Decode message
id: decode
run: |
echo "Decoding message..."
echo "::set-output name=message::$(echo '${{ steps.tag.outputs.message }}' | base64 --decode)"
- name: Send message to Discord
uses: tsickert/discord-webhook@v5.3.0
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
content: "@everyone \n📦 New Fleetbase Version ${{ steps.get_tag.outputs.tag }} Released!\n${{ steps.decode.outputs.message }} \nVersion: ${{ steps.get_tag.outputs.tag }} \n[Release Notes for ${{ steps.get_tag.outputs.tag }}](https://github.com/fleetbase/fleetbase/releases/tag/${{ steps.get_tag.outputs.tag }})"
username: Fleetbase

View File

@@ -1,4 +1,4 @@
name: Fleetbase CI/CD
name: Fleetbase GCP CI/CD
on:
push:

View File

@@ -0,0 +1,50 @@
name: Fleetbase Docker Images
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
branch:
description: 'Branch to build from'
required: false
default: 'main'
version:
description: 'Image version tag (e.g., v0.7.1-beta)'
required: false
jobs:
docker-release:
name: Build and Push Docker Images
runs-on: ubuntu-latest
env:
REGISTRY: fleetbase
VERSION: ${{ github.event.inputs.version || (github.ref_type == 'tag' && startsWith(github.ref_name, 'v') && github.ref_name) || 'manual' }}
steps:
- name: Checkout Repo
uses: actions/checkout@v3
with:
ref: ${{ github.event.inputs.branch || github.ref_name }}
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Build and Push Console & API Images
uses: docker/bake-action@v2
with:
push: true
targets: |
fleetbase-console
fleetbase-api
files: |
./docker-bake.hcl

13
.gitignore vendored
View File

@@ -34,4 +34,15 @@ packages/customer-portal
# wip
packages/solid
solid
verdaccio
verdaccio
# asdf
.tools-versions
# binary build resources
builds/osx/frankenphp
# build artifacts
/builds/dist/
/builds/linux/spc/downloads/*
*.exe
*.dll
*.so
*.dylib

View File

@@ -32,10 +32,7 @@ Fleetbase is a modular logistics and supply chain operating system designed to s
```bash
git clone git@github.com:fleetbase/fleetbase.git
cd fleetbase
docker-compose up -d
docker exec -ti fleetbase-application-1 bash
sh deploy.sh
cd fleetbase && ./scripts/docker-install.sh
```
## 📖 Table of contents
@@ -75,10 +72,7 @@ Make sure you have both the latest versions of docker and docker-compose install
```bash
git clone git@github.com:fleetbase/fleetbase.git
cd fleetbase
docker-compose up -d
docker exec -ti fleetbase-application-1 bash
sh deploy.sh
cd fleetbase && ./scripts/docker-install.sh
```
### Accessing Fleetbase
@@ -89,7 +83,17 @@ Fleetbase API: http://localhost:8000
### Additional Configurations
**CORS:** If youre installing directly on a server you may need to add your IP address or domain to the `api/config/cors.php` file in the `allowed_hosts` array.
**CORS:** If youre installing directly on a server you will need to configure the environment variables to the application container:
```
CONSOLE_HOST=http://{yourhost}:4200
```
If you have additional applications or frontends you can use the environment variable `FRONTEND_HOSTS` to add a comma delimited list of additioal frontend hosts.
**Application Key** If you get an issue about a missing application key just run:
```bash
docker compose exec application bash -c "php artisan key:generate --show"
```
Next copy this value to the `APP_KEY` environment variable in the application container and restart.
**Routing:** Fleetbase ships with a default OSRM server hosted by `[router.project-osrm.org](https://router.project-osrm.org)` but youre able to use your own or any other OSRM compatible server. You can modify this in the `console/environments` directory by modifying the .env file of the environment youre deploying and setting the `OSRM_HOST` to the OSRM server for Fleetbase to use.
@@ -100,6 +104,7 @@ version: “3.8”
services:
application:
environment:
CONSOLE_HOST: http://localhost:4200
MAIL_MAILER: (ses, smtp, mailgun, postmark, sendgrid)
OSRM_HOST: https://router.project-osrm.org
IPINFO_API_KEY:
@@ -108,7 +113,6 @@ services:
TWILIO_SID:
TWILIO_TOKEN:
TWILIO_FROM:
CONSOLE_HOST: http://localhost:4200
```
You can learn more about full installation, and configuration in the [official documentation](https://docs.fleetbase.io/getting-started/install).
@@ -145,9 +149,8 @@ Fleetbase offers a few open sourced apps which are built on Fleetbase which can
## 🛣️ Roadmap
1. **Inventory and Warehouse Management** ~ Pallet will be Fleetbases first official extension for WMS & Inventory.
2. **Accounting and Invoicing** ~ Ledger will be Fleetbases first official extension accounting and invoicing.
3. **Binary Builds** ~ Run Fleetbase from a single binary.
4. **Fleetbase for Desktop** ~ Desktop builds for OSX and Windows.
5. **Custom Maps and Routing Engines** ~ Feature to enable easy integrations with custom maps and routing engines like Google Maps or Mapbox etc…
3. **Fleetbase for Desktop** ~ Desktop builds for OSX and Windows.
4. **Custom Maps and Routing Engines** ~ Feature to enable easy integrations with custom maps and routing engines like Google Maps or Mapbox etc…
## 🪲 Bugs and 💡 Feature Requests

34
RELEASE.md Normal file
View File

@@ -0,0 +1,34 @@
# 🚀 Fleetbase v0.7.5 — 2025-05-30
> “Route optimization and routing control advancements”
---
## ✨ Highlights
- Added route optimization and routing control services for registering additional routing engines and route optimization services.
- Added settings for Routing (Next release will be able to set unit "Miles" or "Kilometers")
- Improved and optimized environment and settings mapper.
- Added entity activity events
- Patched multiple waypoint order creation via API
---
## ⚠️ Breaking Changes
- None 🙂
---
## 🔧 Upgrade Steps
```bash
# Pull latest version
git pull origin main --no-rebase
# Update docker
docker compose down && docker compose up -d
# Run deploy script
docker compose exec application bash -c "./deploy.sh"
```
## Need help?
Join the discussion on [GitHub Discussions](https://github.com/fleetbase/fleetbase/discussions) or drop by [#fleetbase on Discord](https://discord.com/invite/HnTqQ6zAVn)

View File

@@ -10,10 +10,10 @@
"require": {
"php": "^8.0",
"appstract/laravel-opcache": "^4.0",
"fleetbase/core-api": "^1.6.5",
"fleetbase/fleetops-api": "^0.6.8",
"fleetbase/core-api": "^1.6.11",
"fleetbase/fleetops-api": "^0.6.14",
"fleetbase/registry-bridge": "^0.0.19",
"fleetbase/storefront-api": "^0.3.31",
"fleetbase/storefront-api": "^0.4.0",
"guzzlehttp/guzzle": "^7.0.1",
"laravel/framework": "^10.0",
"laravel/octane": "^2.3",

443
api/composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -26,7 +26,7 @@ return [
|
*/
'env' => env('APP_ENV', 'production'),
'env' => env('APP_ENV', env('ENVIRONMENT', 'production')),
/*
|--------------------------------------------------------------------------

View File

@@ -1,5 +1,6 @@
<?php
use Fleetbase\Support\Utils;
use Laravel\Octane\Contracts\OperationTerminated;
use Laravel\Octane\Events\RequestHandled;
use Laravel\Octane\Events\RequestReceived;
@@ -192,6 +193,7 @@ return [
'routes',
'composer.lock',
'.env',
...Utils::arrayFrom(env('OCTANE_WATCH_DIRS'))
],
/*

View File

@@ -0,0 +1,47 @@
#!/bin/bash
set -e
# Resolve the directory the script is located in
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
APP_NAME="Fleetbase"
IMAGE_NAME="fleetbase-linux-static"
CONTAINER_NAME="fleetbase-linux-build"
DIST_DIR="$ROOT_DIR/builds/dist"
BINARY_NAME="fleetbase-linux-x86_64"
DOCKERFILE="$ROOT_DIR/builds/linux/static-build.Dockerfile"
# Ensure pkg-config archive is available
SPC_DOWNLOADS_DIR="$SCRIPT_DIR/spc/downloads"
PKG_TAR="pkg-config-0.29.2.tar.gz"
PKG_URL="https://static-php-cli.fra1.digitaloceanspaces.com/static-php-cli/deps/pkg-config/${PKG_TAR}"
if [[ ! -f "${SPC_DOWNLOADS_DIR}/${PKG_TAR}" ]]; then
echo "📥 pkg-config archive missing downloading..."
mkdir -p "${SPC_DOWNLOADS_DIR}"
curl -L --retry 3 -o "${SPC_DOWNLOADS_DIR}/${PKG_TAR}" "${PKG_URL}"
else
echo "✅ pkg-config archive already present."
fi
# Build the image
echo "📦 Building static Linux binary for ${APP_NAME}..."
docker build -f "$DOCKERFILE" -t "$IMAGE_NAME" .
# Create a container from the built image
echo "📦 Creating container to extract binary..."
docker create --name "$CONTAINER_NAME" "$IMAGE_NAME"
# Make sure dist folder exist
mkdir -p "$DIST_DIR"
# Copy binary from container to local dist folder
echo "📂 Extracting binary..."
docker cp "$CONTAINER_NAME:/go/src/app/dist/frankenphp-linux-x86_64" "$DIST_DIR/$BINARY_NAME"
# Cleanup the temp container
docker rm "$CONTAINER_NAME"
echo "✅ Build complete! Binary is located at: $DIST_DIR/$BINARY_NAME"

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace SPC\builder\linux\library;
class libgeos extends LinuxLibraryBase
{
use \SPC\builder\unix\library\libgeos;
public const NAME = 'libgeos';
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace SPC\builder\unix\library;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\store\FileSystem;
trait libgeos
{
/**
* @throws FileSystemException
* @throws RuntimeException
*/
protected function build(): void
{
FileSystem::resetDir($this->source_dir . '/build');
shell()->cd($this->source_dir . '/build')
->setEnv([
'CFLAGS' => $this->getLibExtraCFlags(),
'LDFLAGS' => $this->getLibExtraLdFlags(),
'LIBS' => $this->getLibExtraLibs(),
])
->execWithEnv("cmake {$this->builder->makeCmakeArgs()} -DBUILD_SHARED_LIBS=OFF ..")
->execWithEnv("make -j{$this->builder->concurrency}")
->execWithEnv('make install');
$this->patchPkgconfPrefix(['geos.pc']);
}
}

View File

@@ -0,0 +1,92 @@
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
WORKDIR /go/src/app
# Copy Fleetbase app
COPY ../../api ./dist/app
# Set working directory to the embedded Fleetbase app
WORKDIR /go/src/app/dist/app
# Setup for production environment
ENV APP_ENV=production
ENV APP_DEBUG=false
ENV BROADCAST_DRIVER=socketcluster
ENV OSRM_HOST="https://router.project-osrm.org"
ENV REGISTRY_PREINSTALLED_EXTENSIONS=true
# Optional: Ensure writable storage
RUN chmod -R 775 bootstrap/cache storage
# Set permissions for deploy script
RUN chmod +x ./deploy.sh
# Move back to main app directory before running build-static.sh
WORKDIR /go/src/app
# Install geos lib
RUN apk add --no-cache geos geos-dev
# Inject the libgeos library handlers
COPY ./builds/linux/spc/libgeos-linux.php ./dist/static-php-cli/src/SPC/builder/linux/library/libgeos.php
COPY ./builds/linux/spc/libgeos-unix.php ./dist/static-php-cli/src/SPC/builder/unix/library/libgeos.php
# Patch source.json to add geos extension source
RUN jq '. + {"php-geos": {"type": "url", "url": "https://github.com/libgeos/php-geos/archive/dfe1ab17b0f155cc315bc13c75689371676e02e1.zip", "license": [{"type": "file", "path": "php-geos-dfe1ab17b0f155cc315bc13c75689371676e02e1/MIT-LICENSE"}, {"type": "file", "path": "php-geos-dfe1ab17b0f155cc315bc13c75689371676e02e1/LGPL-2"}]}}' \
./dist/static-php-cli/config/source.json > ./dist/static-php-cli/config/source.tmp.json && \
mv ./dist/static-php-cli/config/source.tmp.json ./dist/static-php-cli/config/source.json
# Pathc source.json to add libgeos library
RUN jq '. + {"libgeos": {"type": "url", "url": "https://download.osgeo.org/geos/geos-3.12.1.tar.bz2", "filename": "geos-3.12.1.tar.bz2", "extract": "geos-3.12.1", "build-dir": "build", "license": [{"type": "file", "path": "COPYING"}]}}' \
./dist/static-php-cli/config/source.json > ./dist/static-php-cli/config/source.tmp.json && \
mv ./dist/static-php-cli/config/source.tmp.json ./dist/static-php-cli/config/source.json
# Patch ext.json to add geos extension dynamically
RUN jq '. + {"geos": {"type": "external", "arg-type": "enable", "source": "php-geos", "lib-depends": ["libgeos"]}}' \
./dist/static-php-cli/config/ext.json > ./dist/static-php-cli/config/ext.tmp.json && \
mv ./dist/static-php-cli/config/ext.tmp.json ./dist/static-php-cli/config/ext.json
# Patch lib.json to add libgeos
RUN jq '. + {"libgeos": {"source": "libgeos", "static-libs-unix": ["libgeos.a", "libgeos_c.a"]}}' \
./dist/static-php-cli/config/lib.json > ./dist/static-php-cli/config/lib.tmp.json && \
mv ./dist/static-php-cli/config/lib.tmp.json ./dist/static-php-cli/config/lib.json
# Install dependencies for SPC CLI
WORKDIR /go/src/app/dist/static-php-cli
RUN composer install --no-dev -a
# Set PHP extensions to be built (including geos!)
ENV PHP_EXTENSIONS="pdo_mysql,gd,bcmath,redis,intl,zip,gmp,apcu,opcache,imagick,sockets,pcntl,geos,iconv,mbstring,fileinfo,ctype,tokenizer,simplexml,dom,filter,session"
ENV PHP_EXTENSION_LIBS="libgeos,libzip,bzip2,libxml2,openssl,zlib"
# Force SPC to use the local source version (not download binary)
ENV SPC_REL_TYPE=source
# Debug build
ENV SPC_LOG_LEVEL=debug
# Skip compression
ENV NO_COMPRESS=1
# set PHP version
ENV PHP_VERSION=8.2
# Move to the app directory
WORKDIR /go/src/app
# Make sure pkg-config is available within the static build container
COPY ./builds/linux/spc/downloads/pkg-config-0.29.2.tar.gz ./dist/static-php-cli/downloads/pkg-config-0.29.2.tar.gz
# Pre-build pkg-config using the existing tarball
RUN apk add --no-cache build-base && \
tar -xzf ./dist/static-php-cli/downloads/pkg-config-0.29.2.tar.gz -C /tmp && \
cd /tmp/pkg-config-0.29.2 && \
./configure --with-internal-glib --prefix=/go/src/app/dist/static-php-cli/build/bin && \
make && make install && \
rm -rf /tmp/pkg-config-0.29.2
# Do not run git pull
RUN sed -i 's/^[ \t]*git pull/# git pull/' ./build-static.sh
# Build the FrankenPHP static binary
RUN EMBED=dist/app ./build-static.sh

188
builds/osx/build-osx.sh Executable file
View File

@@ -0,0 +1,188 @@
#!/bin/bash
set -e
log() {
echo -e "\033[1;34m[🔧 $1]\033[0m"
}
log_success() {
echo -e "\033[1;32m[✅ $1]\033[0m"
}
log_warn() {
echo -e "\033[1;33m[⚠️ $1]\033[0m"
}
log_error() {
echo -e "\033[1;31m[❌ $1]\033[0m"
}
# Define base paths
log "Resolving directories..."
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
OSX_DIR="$ROOT_DIR/builds/osx"
DIST_DIR="$ROOT_DIR/builds/dist"
APP_DIR="$ROOT_DIR/api"
BREW_PREFIX="/opt/homebrew"
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
ARCH="$(uname -m)"
BINARY_NAME="fleetbase-$OS-$ARCH"
log "Binary will be: $BINARY_NAME"
# Setup PHP 8.4
log "Detecting current PHP version..."
ORIGINAL_PHP_PATH="$(which php)"
ORIGINAL_PHP_VERSION="$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION.".".PHP_RELEASE_VERSION;' 2>/dev/null)"
IS_ASDF_MANAGED=false
if [[ "$ORIGINAL_PHP_PATH" == *".asdf"* ]]; then
IS_ASDF_MANAGED=true
fi
# 🔁 Trap to restore PHP when script exits
trap 'if [ "$IS_ASDF_MANAGED" = true ]; then
log "Restoring asdf-managed PHP version: $ORIGINAL_PHP_VERSION"
asdf set php "$ORIGINAL_PHP_VERSION" || true
log "Reverted to PHP $(php -v | head -n 1)"
else
log "Unsetting asdf set to restore system PHP"
asdf set php system || true
log "Reverted to PHP $(php -v | head -n 1)"
fi' EXIT
log "Detected PHP version: $ORIGINAL_PHP_VERSION"
log "Detected PHP binary: $ORIGINAL_PHP_PATH"
# ───────────────────────────────────────────────────────────────────────────────
# If the *current* php is already 8.4.x, we skip the entire asdf install step
# ───────────────────────────────────────────────────────────────────────────────
if [[ "$ORIGINAL_PHP_PATH" == "$BREW_PREFIX/bin/php" && "$ORIGINAL_PHP_VERSION" =~ ^8\.4\. ]]; then
log "Homebrew PHP $ORIGINAL_PHP_VERSION detected at $ORIGINAL_PHP_PATH — skipping asdf build/install."
else
# Only install under asdf if we dont already have 8.4.0 installed
log "No Homebrew PHP 8.4 detected (found $ORIGINAL_PHP_PATH $ORIGINAL_PHP_VERSION), using asdf to build/install."
if ! asdf list php | grep -q "8.4.0"; then
# Use brew to install required dependencies for asdf php management
log "Checking and installing Homebrew packages required for PHP 8.4 build..."
for pkg in autoconf automake bison freetype gd gettext icu4c krb5 libedit libiconv libjpeg libpng libxml2 libzip pkg-config re2c zlib sqlite3 libsodium oniguruma openssl@3 nasm; do
if ! brew list "$pkg" &>/dev/null; then
log_warn "$pkg not found. Installing..."
arch -arm64 brew install "$pkg"
else
log "$pkg already installed. Skipping."
fi
done
# Set necessary env flags/paths for PHP build on OSX ARM64
export CPPFLAGS="-I$BREW_PREFIX/opt/oniguruma/include -I$BREW_PREFIX/opt/libsodium/include -I$BREW_PREFIX/opt/bzip2/include -I$BREW_PREFIX/opt/zlib/include -I$BREW_PREFIX/opt/openssl@3/include -I$BREW_PREFIX/opt/libxml2/include -I$BREW_PREFIX/opt/libedit/include -I$BREW_PREFIX/opt/curl/include -I$BREW_PREFIX/opt/sqlite3/include -I$BREW_PREFIX/opt/freetype/include -I$BREW_PREFIX/opt/jpeg/include -I$BREW_PREFIX/opt/libpng/include -I$BREW_PREFIX/opt/libzip/include"
export LDFLAGS="-L$BREW_PREFIX/opt/openssl@3/lib -lssl -lcrypto -lz -L$BREW_PREFIX/opt/oniguruma/lib -lonig -L$BREW_PREFIX/opt/libsodium/lib -lsodium -L$BREW_PREFIX/opt/bzip2/lib -Wl,-rpath,$BREW_PREFIX/opt/bzip2/lib -lbz2 -L$BREW_PREFIX/opt/zlib/lib -L$BREW_PREFIX/opt/openssl@3/lib -L$BREW_PREFIX/opt/libxml2/lib -L$BREW_PREFIX/opt/libedit/lib -L$BREW_PREFIX/opt/sqlite3/lib -lsqlite3 -L$BREW_PREFIX/opt/curl/lib -lcurl -L$BREW_PREFIX/opt/freetype/lib -L$BREW_PREFIX/opt/jpeg/lib -L$BREW_PREFIX/opt/libpng/lib -L$BREW_PREFIX/opt/libzip/lib -lzip -lz"
export PKG_CONFIG_PATH="$BREW_PREFIX/opt/openssl/lib/pkgconfig:$BREW_PREFIX/opt/oniguruma/lib/pkgconfig:$BREW_PREFIX/opt/libsodium/lib/pkgconfig:$BREW_PREFIX/opt/libzip/lib/pkgconfig:$BREW_PREFIX/opt/gd/lib/pkgconfig:$BREW_PREFIX/opt/zlib/lib/pkgconfig:$BREW_PREFIX/opt/openssl@3/lib/pkgconfig:$BREW_PREFIX/opt/libxml2/lib/pkgconfig:$BREW_PREFIX/opt/curl/lib/pkgconfig:$BREW_PREFIX/opt/sqlite3/lib/pkgconfig:$BREW_PREFIX/opt/freetype/lib/pkgconfig:$BREW_PREFIX/opt/jpeg/lib/pkgconfig:$BREW_PREFIX/opt/libpng/lib/pkgconfig"
export PHP_CONFIGURE_OPTIONS="--with-openssl=$(brew --prefix openssl) --with-iconv=$(brew --prefix libiconv)"
log "Installing PHP 8.4.0 with asdf..."
asdf install php 8.4.0 --verbose
else
log "asdf already has PHP 8.4.0 installed, skipping"
fi
log "Switching to PHP 8.4.0 with asdf set..."
asdf set php 8.4.0 --home
fi
# Clone FrankenPHP
if [ ! -d "$OSX_DIR/frankenphp" ]; then
log "Cloning FrankenPHP..."
git clone https://github.com/dunglas/frankenphp "$OSX_DIR/frankenphp"
else
log_warn "FrankenPHP already cloned. Skipping."
fi
cd "$OSX_DIR/frankenphp"
# Patch build script
log "Patching build-static.sh to skip git pull..."
sed -i '' 's/^[ \t]*git pull/# git pull/' ./build-static.sh
# Set environment variables
log "Exporting build environment variables..."
export PHP_VERSION=8.2
export PHP_EXTENSIONS="pdo_mysql,gd,bcmath,redis,intl,zip,gmp,apcu,opcache,imagick,sockets,pcntl,geos,iconv,mbstring,fileinfo,ctype,tokenizer,simplexml,dom,filter,session"
export PHP_EXTENSION_LIBS="libgeos,libzip,bzip2,libxml2,openssl,zlib"
export SPC_REL_TYPE=source
export NO_COMPRESS=1
export SPC_OPT_BUILD_ARGS="--debug"
export CMAKE_OSX_ARCHITECTURES=arm64
# Clone and prepare static-php-cli in dist/
STATIC_PHP_CLI_DIR="$OSX_DIR/frankenphp/dist/static-php-cli"
if [ ! -d "$STATIC_PHP_CLI_DIR" ]; then
log "Cloning static-php-cli into dist/..."
git clone https://github.com/crazywhalecc/static-php-cli.git "$STATIC_PHP_CLI_DIR"
else
log_warn "static-php-cli already exists in dist/. Skipping clone."
fi
# Inject libgeos support
log "Injecting libgeos patch files..."
cp "$ROOT_DIR/builds/osx/spc/libgeos-unix.php" "$STATIC_PHP_CLI_DIR/src/SPC/builder/unix/library/libgeos.php"
cp "$ROOT_DIR/builds/osx/spc/libgeos-macos.php" "$STATIC_PHP_CLI_DIR/src/SPC/builder/macos/library/libgeos.php"
cp "$ROOT_DIR/builds/osx/spc/UnixBuilderBase-macos.php" "$STATIC_PHP_CLI_DIR/src/SPC/builder/unix/UnixBuilderBase.php"
# Patch SPC config
log "Patching SPC config files (source.json, ext.json, lib.json)..."
jq '. + {"php-geos": {"type": "url", "url": "https://github.com/libgeos/php-geos/archive/dfe1ab17b0f155cc315bc13c75689371676e02e1.zip", "license": [{"type": "file", "path": "php-geos-dfe1ab17b0f155cc315bc13c75689371676e02e1/MIT-LICENSE"}, {"type": "file", "path": "php-geos-dfe1ab17b0f155cc315bc13c75689371676e02e1/LGPL-2"}]}}' \
"$STATIC_PHP_CLI_DIR/config/source.json" > "$STATIC_PHP_CLI_DIR/config/source.tmp.json" && \
mv "$STATIC_PHP_CLI_DIR/config/source.tmp.json" "$STATIC_PHP_CLI_DIR/config/source.json"
jq '. + {"libgeos": {"type": "url", "url": "https://download.osgeo.org/geos/geos-3.12.1.tar.bz2", "filename": "geos-3.12.1.tar.bz2", "extract": "geos-3.12.1", "build-dir": "build", "license": [{"type": "file", "path": "COPYING"}]}}' \
"$STATIC_PHP_CLI_DIR/config/source.json" > "$STATIC_PHP_CLI_DIR/config/source.tmp.json" && \
mv "$STATIC_PHP_CLI_DIR/config/source.tmp.json" "$STATIC_PHP_CLI_DIR/config/source.json"
jq '. + {"libgeos": {"source": "libgeos", "static-libs-unix": ["libgeos.a", "libgeos_c.a"]}}' \
"$STATIC_PHP_CLI_DIR/config/lib.json" > "$STATIC_PHP_CLI_DIR/config/lib.tmp.json" && \
mv "$STATIC_PHP_CLI_DIR/config/lib.tmp.json" "$STATIC_PHP_CLI_DIR/config/lib.json"
jq '. + {"geos": {"type": "external", "arg-type": "enable", "source": "php-geos", "lib-depends": ["libgeos"]}}' \
"$STATIC_PHP_CLI_DIR/config/ext.json" > "$STATIC_PHP_CLI_DIR/config/ext.tmp.json" && \
mv "$STATIC_PHP_CLI_DIR/config/ext.tmp.json" "$STATIC_PHP_CLI_DIR/config/ext.json"
# Prepare app embed folder
log "📦 Preparing embedded app directory..."
rm -rf ./dist/app
mkdir -p ./dist/app
cp -R "$APP_DIR"/* ./dist/app/
log "Patching build-static.sh to skip git pull and composer install..."
# Skip `git pull`
sed -i '' 's/^[[:space:]]*git pull/# git pull/' "$OSX_DIR/frankenphp/build-static.sh"
# Patch add CoreServices framework for Caddy build on OSX
sed -i '' 's/-framework CoreFoundation -framework SystemConfiguration/& -framework CoreServices/' "$OSX_DIR/frankenphp/build-static.sh"
# ── work around 403 on GH macOS runners ────────────────────────────────────────
log "Patching curl to use a browser-like User-Agent (to avoid 403s)…"
curl() {
command curl -sSL -A "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6 Safari/605.1.15" "$@"
}
export -f curl
# Build the binary
log "⚙️ Running FrankenPHP build-static.sh..."
EMBED=dist/app ./build-static.sh
# Move built binary to dist
log "Moving built binary to output folder..."
mkdir -p "$DIST_DIR"
mv dist/frankenphp-mac-$ARCH "$DIST_DIR/$BINARY_NAME"
log_success "✅ macOS binary built at: $DIST_DIR/$BINARY_NAME"
# Clean up frankenphp build and app embed folder
log "🧹 Cleaning temporary app directory..."
rm -rf "$OSX_DIR/frankenphp"

View File

@@ -0,0 +1,271 @@
<?php
declare(strict_types=1);
namespace SPC\builder\unix;
use SPC\builder\BuilderBase;
use SPC\builder\linux\LinuxBuilder;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\Config;
use SPC\store\FileSystem;
use SPC\util\DependencyUtil;
use SPC\util\SPCConfigUtil;
abstract class UnixBuilderBase extends BuilderBase
{
/** @var string cflags */
public string $arch_c_flags;
/** @var string C++ flags */
public string $arch_cxx_flags;
/** @var string cmake toolchain file */
public string $cmake_toolchain_file;
/**
* @throws WrongUsageException
* @throws FileSystemException
*/
public function getAllStaticLibFiles(): array
{
$libs = [];
// reorder libs
foreach ($this->libs as $lib) {
foreach ($lib->getDependencies() as $dep) {
$libs[] = $dep;
}
$libs[] = $lib;
}
$libFiles = [];
$libNames = [];
// merge libs
foreach ($libs as $lib) {
if (!in_array($lib::NAME, $libNames, true)) {
$libNames[] = $lib::NAME;
array_unshift($libFiles, ...$lib->getStaticLibs());
}
}
return array_map(fn ($x) => realpath(BUILD_LIB_PATH . "/{$x}"), $libFiles);
}
/**
* Return generic cmake options when configuring cmake projects
*/
public function makeCmakeArgs(): string
{
$extra = $this instanceof LinuxBuilder ? '-DCMAKE_C_COMPILER=' . getenv('CC') . ' ' : '';
// NEW: allow env-variable override
$arch = getenv('CMAKE_OSX_ARCHITECTURES') ?: 'arm64';
return $extra .
'-DCMAKE_BUILD_TYPE=Release ' .
'-DCMAKE_INSTALL_PREFIX=' . BUILD_ROOT_PATH . ' ' .
'-DCMAKE_INSTALL_BINDIR=bin ' .
'-DCMAKE_INSTALL_LIBDIR=lib ' .
'-DCMAKE_INSTALL_INCLUDEDIR=include ' .
"-DCMAKE_OSX_ARCHITECTURES={$arch} " .
"-DCMAKE_TOOLCHAIN_FILE={$this->cmake_toolchain_file}";
}
/**
* Generate configure flags
*/
public function makeAutoconfFlags(int $flag = AUTOCONF_ALL): string
{
$extra = '';
// TODO: add auto pkg-config support
if (($flag & AUTOCONF_LIBS) === AUTOCONF_LIBS) {
$extra .= 'LIBS="' . BUILD_LIB_PATH . '" ';
}
if (($flag & AUTOCONF_CFLAGS) === AUTOCONF_CFLAGS) {
$extra .= 'CFLAGS="-I' . BUILD_INCLUDE_PATH . '" ';
}
if (($flag & AUTOCONF_CPPFLAGS) === AUTOCONF_CPPFLAGS) {
$extra .= 'CPPFLAGS="-I' . BUILD_INCLUDE_PATH . '" ';
}
if (($flag & AUTOCONF_LDFLAGS) === AUTOCONF_LDFLAGS) {
$extra .= 'LDFLAGS="-L' . BUILD_LIB_PATH . '" ';
}
return $extra;
}
public function proveLibs(array $sorted_libraries): void
{
// search all supported libs
$support_lib_list = [];
$classes = FileSystem::getClassesPsr4(
ROOT_DIR . '/src/SPC/builder/' . osfamily2dir() . '/library',
'SPC\builder\\' . osfamily2dir() . '\library'
);
foreach ($classes as $class) {
if (defined($class . '::NAME') && $class::NAME !== 'unknown' && Config::getLib($class::NAME) !== null) {
$support_lib_list[$class::NAME] = $class;
}
}
// if no libs specified, compile all supported libs
if ($sorted_libraries === [] && $this->isLibsOnly()) {
$libraries = array_keys($support_lib_list);
$sorted_libraries = DependencyUtil::getLibs($libraries);
}
// add lib object for builder
foreach ($sorted_libraries as $library) {
if (!in_array(Config::getLib($library, 'type', 'lib'), ['lib', 'package'])) {
continue;
}
// if some libs are not supported (but in config "lib.json", throw exception)
if (!isset($support_lib_list[$library])) {
throw new WrongUsageException('library [' . $library . '] is in the lib.json list but not supported to compile, but in the future I will support it!');
}
$lib = new ($support_lib_list[$library])($this);
$this->addLib($lib);
}
// calculate and check dependencies
foreach ($this->libs as $lib) {
$lib->calcDependency();
}
$this->lib_list = $sorted_libraries;
}
/**
* Sanity check after build complete
*
* @throws RuntimeException
*/
protected function sanityCheck(int $build_target): void
{
// sanity check for php-cli
if (($build_target & BUILD_TARGET_CLI) === BUILD_TARGET_CLI) {
logger()->info('running cli sanity check');
[$ret, $output] = shell()->execWithResult(BUILD_ROOT_PATH . '/bin/php -n -r "echo \"hello\";"');
$raw_output = implode('', $output);
if ($ret !== 0 || trim($raw_output) !== 'hello') {
throw new RuntimeException("cli failed sanity check: ret[{$ret}]. out[{$raw_output}]");
}
foreach ($this->getExts(false) as $ext) {
logger()->debug('testing ext: ' . $ext->getName());
$ext->runCliCheckUnix();
}
}
// sanity check for phpmicro
if (($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO) {
$test_task = $this->getMicroTestTasks();
foreach ($test_task as $task_name => $task) {
$test_file = SOURCE_PATH . '/' . $task_name . '.exe';
if (file_exists($test_file)) {
@unlink($test_file);
}
file_put_contents($test_file, file_get_contents(SOURCE_PATH . '/php-src/sapi/micro/micro.sfx') . $task['content']);
chmod($test_file, 0755);
[$ret, $out] = shell()->execWithResult($test_file);
foreach ($task['conditions'] as $condition => $closure) {
if (!$closure($ret, $out)) {
$raw_out = trim(implode('', $out));
throw new RuntimeException("micro failed sanity check: {$task_name}, condition [{$condition}], ret[{$ret}], out[{$raw_out}]");
}
}
}
}
// sanity check for embed
if (($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED) {
logger()->info('running embed sanity check');
$sample_file_path = SOURCE_PATH . '/embed-test';
if (!is_dir($sample_file_path)) {
@mkdir($sample_file_path);
}
// copy embed test files
copy(ROOT_DIR . '/src/globals/common-tests/embed.c', $sample_file_path . '/embed.c');
copy(ROOT_DIR . '/src/globals/common-tests/embed.php', $sample_file_path . '/embed.php');
$util = new SPCConfigUtil($this);
$config = $util->config($this->ext_list, $this->lib_list, $this->getOption('with-suggested-exts'), $this->getOption('with-suggested-libs'));
$lens = "{$config['cflags']} {$config['ldflags']} {$config['libs']}";
if (PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') === 'musl') {
$lens .= ' -static';
}
[$ret, $out] = shell()->cd($sample_file_path)->execWithResult(getenv('CC') . ' -o embed embed.c ' . $lens);
if ($ret !== 0) {
throw new RuntimeException('embed failed sanity check: build failed. Error message: ' . implode("\n", $out));
}
// if someone changed to --enable-embed=shared, we need to add LD_LIBRARY_PATH
if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'shared') {
$ext_path = 'LD_LIBRARY_PATH=' . BUILD_ROOT_PATH . '/lib:$LD_LIBRARY_PATH ';
FileSystem::removeFileIfExists(BUILD_ROOT_PATH . '/lib/libphp.a');
} else {
$ext_path = '';
FileSystem::removeFileIfExists(BUILD_ROOT_PATH . '/lib/libphp.so');
}
[$ret, $output] = shell()->cd($sample_file_path)->execWithResult($ext_path . './embed');
if ($ret !== 0 || trim(implode('', $output)) !== 'hello') {
throw new RuntimeException('embed failed sanity check: run failed. Error message: ' . implode("\n", $output));
}
}
}
/**
* 将编译好的二进制文件发布到 buildroot
*
* @param int $type 发布类型
* @throws RuntimeException
* @throws FileSystemException
*/
protected function deployBinary(int $type): bool
{
$src = match ($type) {
BUILD_TARGET_CLI => SOURCE_PATH . '/php-src/sapi/cli/php',
BUILD_TARGET_MICRO => SOURCE_PATH . '/php-src/sapi/micro/micro.sfx',
BUILD_TARGET_FPM => SOURCE_PATH . '/php-src/sapi/fpm/php-fpm',
default => throw new RuntimeException('Deployment does not accept type ' . $type),
};
logger()->info('Deploying ' . $this->getBuildTypeName($type) . ' file');
FileSystem::createDir(BUILD_ROOT_PATH . '/bin');
shell()->exec('cp ' . escapeshellarg($src) . ' ' . escapeshellarg(BUILD_ROOT_PATH . '/bin/'));
return true;
}
/**
* Run php clean
*
* @throws RuntimeException
*/
protected function cleanMake(): void
{
logger()->info('cleaning up');
shell()->cd(SOURCE_PATH . '/php-src')->exec('make clean');
}
/**
* Patch phpize and php-config if needed
* @throws FileSystemException
*/
protected function patchPhpScripts(): void
{
// patch phpize
if (file_exists(BUILD_BIN_PATH . '/phpize')) {
logger()->debug('Patching phpize prefix');
FileSystem::replaceFileStr(BUILD_BIN_PATH . '/phpize', "prefix=''", "prefix='" . BUILD_ROOT_PATH . "'");
FileSystem::replaceFileStr(BUILD_BIN_PATH . '/phpize', 's##', 's#/usr/local#');
}
// patch php-config
if (file_exists(BUILD_BIN_PATH . '/php-config')) {
logger()->debug('Patching php-config prefix and libs order');
$php_config_str = FileSystem::readFile(BUILD_BIN_PATH . '/php-config');
$php_config_str = str_replace('prefix=""', 'prefix="' . BUILD_ROOT_PATH . '"', $php_config_str);
// move mimalloc to the beginning of libs
$php_config_str = preg_replace('/(libs=")(.*?)\s*(' . preg_quote(BUILD_LIB_PATH, '/') . '\/mimalloc\.o)\s*(.*?)"/', '$1$3 $2 $4"', $php_config_str);
// move lstdc++ to the end of libs
$php_config_str = preg_replace('/(libs=")(.*?)\s*(-lstdc\+\+)\s*(.*?)"/', '$1$2 $4 $3"', $php_config_str);
FileSystem::writeFile(BUILD_BIN_PATH . '/php-config', $php_config_str);
}
}
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace SPC\builder\macos\library;
class libgeos extends MacOSLibraryBase
{
use \SPC\builder\unix\library\libgeos;
public const NAME = 'libgeos';
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace SPC\builder\unix\library;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\store\FileSystem;
trait libgeos
{
/**
* @throws FileSystemException
* @throws RuntimeException
*/
protected function build(): void
{
FileSystem::resetDir($this->source_dir . '/build');
shell()->cd($this->source_dir . '/build')
->setEnv([
'CFLAGS' => $this->getLibExtraCFlags(),
'LDFLAGS' => $this->getLibExtraLdFlags(),
'LIBS' => $this->getLibExtraLibs(),
])
->execWithEnv("cmake {$this->builder->makeCmakeArgs()} -DBUILD_SHARED_LIBS=OFF ..")
->execWithEnv("make -j{$this->builder->concurrency}")
->execWithEnv('make install');
$this->patchPkgconfPrefix(['geos.pc']);
}
}

View File

@@ -6,19 +6,19 @@ WORKDIR /console
# Create the pnpm directory and set the PNPM_HOME environment variable
RUN mkdir -p ~/.pnpm
ENV PNPM_HOME /root/.pnpm
ENV PNPM_HOME=/root/.pnpm
# Set environment
ARG ENVIRONMENT=production
# Add the pnpm global bin to the PATH
ENV PATH /root/.pnpm/bin:$PATH
ENV PATH=/root/.pnpm/bin:$PATH
# Copy pnpm-lock.yaml (or package.json) into the directory /console in the container
COPY console/package.json console/pnpm-lock.yaml ./
COPY package.json pnpm-lock.yaml ./
# Copy over .npmrc if applicable
COPY console/.npmr[c] ./
COPY .npmr[c] ./
# Install global dependencies
RUN npm install -g ember-cli pnpm
@@ -33,7 +33,7 @@ RUN mkdir -p -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts
RUN pnpm install
# Copy the console directory contents into the container at /console
COPY console .
COPY . .
# Build the application
RUN pnpm build --environment $ENVIRONMENT
@@ -48,7 +48,7 @@ COPY --from=builder /console/dist /usr/share/nginx/html
EXPOSE 4200
# Use custom nginx.conf
COPY console/nginx.conf /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Start Nginx server
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -1,57 +0,0 @@
# ---- Build Stage ----
FROM node:18.15.0-alpine
# Set the working directory in the container to /console
WORKDIR /console
# Create the pnpm directory and set the PNPM_HOME environment variable
RUN mkdir -p ~/.pnpm
ENV PNPM_HOME /root/.pnpm
# Set environment
ARG ENVIRONMENT=production
# Add the pnpm global bin to the PATH
ENV PATH /root/.pnpm/bin:$PATH
# Copy pnpm-lock.yaml (or package.json) into the directory /console in the container
COPY console/package.json console/pnpm-lock.yaml ./
# Copy over .npmrc if applicable
COPY console/.npmr[c] ./
# Install global dependencies
RUN npm install -g ember-cli pnpm
# Install git
RUN apk update && apk add git openssh-client
# Trust GitHub's RSA host key
RUN mkdir -p -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts
# Install app dependencies
RUN pnpm install
# Copy the console directory contents into the container at /console
COPY console .
# Build the application
RUN pnpm build --environment $ENVIRONMENT
# # Make sure the build output is available in /console/dist
# RUN ls -la /console/dist
# # ---- Serve Stage ----
# FROM nginx:alpine
# # Copy the built app to our served directory
# COPY --from=builder /console/dist /usr/share/nginx/html
# # Expose the port nginx is bound to
# EXPOSE 4201
# # Use custom nginx.conf
# COPY console/nginx.conf /etc/nginx/conf.d/default.conf
# # Start Nginx server
# CMD ["nginx", "-g", "daemon off;"]

View File

@@ -4,6 +4,7 @@ import loadInitializers from 'ember-load-initializers';
import config from '@fleetbase/console/config/environment';
import loadExtensions from '@fleetbase/ember-core/utils/load-extensions';
import mapEngines from '@fleetbase/ember-core/utils/map-engines';
import loadRuntimeConfig from '@fleetbase/console/utils/runtime-config';
export default class App extends Application {
modulePrefix = config.modulePrefix;
@@ -20,4 +21,11 @@ export default class App extends Application {
}
}
loadInitializers(App, config.modulePrefix);
document.addEventListener('DOMContentLoaded', async () => {
await loadRuntimeConfig();
loadInitializers(App, config.modulePrefix);
let fleetbase = App.create();
fleetbase.deferReadiness();
fleetbase.boot();
});

View File

@@ -3,7 +3,7 @@ import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import { isArray } from '@ember/array';
import { task } from 'ember-concurrency-decorators';
import { task } from 'ember-concurrency';
export default class MetricComponent extends Component {
@service fetch;

View File

@@ -1,6 +1,6 @@
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { task } from 'ember-concurrency-decorators';
import { task } from 'ember-concurrency';
import { inject as service } from '@ember/service';
import { tracked } from '@glimmer/tracking';

View File

@@ -2,7 +2,7 @@ import Controller from '@ember/controller';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import { task } from 'ember-concurrency-decorators';
import { task } from 'ember-concurrency';
import getTwoFaMethods from '@fleetbase/console/utils/get-two-fa-methods';
/**

View File

@@ -1,7 +1,9 @@
import Controller from '@ember/controller';
import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import { alias } from '@ember/object/computed';
import { debug } from '@ember/debug';
import { task } from 'ember-concurrency';
export default class ConsoleAccountIndexController extends Controller {
@@ -40,6 +42,18 @@ export default class ConsoleAccountIndexController extends Controller {
*/
@alias('currentUser.user') user;
/**
* Available timezones for selection.
*
* @memberof ConsoleAccountIndexController
*/
@tracked timezones = [];
constructor() {
super(...arguments);
this.loadTimezones.perform();
}
/**
* Handle upload of new photo
*
@@ -116,6 +130,19 @@ export default class ConsoleAccountIndexController extends Controller {
return isPasswordValid;
}
/**
* Load all available timezones from lookup.
*
* @memberof ConsoleAccountIndexController
*/
@task *loadTimezones() {
try {
this.timezones = yield this.fetch.get('lookup/timezones');
} catch (error) {
debug(`Unable to load timezones : ${error.message}`);
}
}
/**
* Checks if any user attribute has been changed
*

View File

@@ -2,7 +2,7 @@ import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
import { task } from 'ember-concurrency-decorators';
import { task } from 'ember-concurrency';
import getTwoFaMethods from '@fleetbase/console/utils/get-two-fa-methods';
/**

View File

@@ -2,6 +2,8 @@ import Controller from '@ember/controller';
import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import { debug } from '@ember/debug';
import { task } from 'ember-concurrency';
export default class ConsoleSettingsIndexController extends Controller {
/**
@@ -25,13 +27,6 @@ export default class ConsoleSettingsIndexController extends Controller {
*/
@service fetch;
/**
* The request loading state.
*
* @memberof ConsoleSettingsIndexController
*/
@tracked isLoading = false;
/**
* the upload queue.
*
@@ -46,23 +41,32 @@ export default class ConsoleSettingsIndexController extends Controller {
*/
@tracked uploadedFiles = [];
/**
* Available timezones for selection.
*
* @memberof ConsoleAccountIndexController
*/
@tracked timezones = [];
constructor() {
super(...arguments);
this.loadTimezones.perform();
}
/**
* Save the organization settings.
*
* @memberof ConsoleSettingsIndexController
*/
@action saveSettings(event) {
event.preventDefault();
this.isLoading = true;
@task *saveSettings(event) {
event?.preventDefault();
this.model
.save()
.then(() => {
this.notifications.success('Organization changes successfully saved.');
})
.finally(() => {
this.isLoading = false;
});
try {
yield this.model.save();
this.notifications.success('Organization changes successfully saved.');
} catch (error) {
debug(`Unable to save organization settings : ${error.message}`);
}
}
/**
@@ -91,4 +95,17 @@ export default class ConsoleSettingsIndexController extends Controller {
}
);
}
/**
* Load all available timezones from lookup.
*
* @memberof ConsoleAccountIndexController
*/
@task *loadTimezones() {
try {
this.timezones = yield this.fetch.get('lookup/timezones');
} catch (error) {
debug(`Unable to load timezones : ${error.message}`);
}
}
}

View File

@@ -2,7 +2,7 @@ import Controller from '@ember/controller';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import { task } from 'ember-concurrency-decorators';
import { task } from 'ember-concurrency';
import getTwoFaMethods from '@fleetbase/console/utils/get-two-fa-methods';
export default class ConsoleSettingsTwoFaController extends Controller {

View File

@@ -2,7 +2,7 @@ import Controller from '@ember/controller';
import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import { task } from 'ember-concurrency-decorators';
import { task } from 'ember-concurrency';
export default class InstallController extends Controller {
@service fetch;

View File

@@ -14,5 +14,6 @@ export function initialize(application) {
}
export default {
name: 'load-leaflet',
initialize,
};

View File

@@ -39,9 +39,6 @@ export default class ConsoleRoute extends Route {
async afterModel(model, transition) {
this.universe.callHooks('console:after-model', this.session, this.router, model, transition);
removeBootLoader();
// Reboot extensions
this.universe.bootEngines();
}
/**

View File

@@ -67,3 +67,11 @@ body.console-admin-organizations-index-index .next-table-wrapper > table {
body[data-theme='dark'] #boot-loader > .loader-container > .loading-message {
color: #fff;
}
/** hotfix: ember-power-select-trigger broken padding after upgrade - @todo move to ember-ui */
body.fleetbase-console .ember-power-select-trigger {
padding-top: 0.5rem;
padding-right: 2.5rem;
padding-bottom: 0.5rem;
padding-left: 0.75rem;
}

View File

@@ -1,5 +1,6 @@
{{page-title (t "app.name")}}
<ModalsContainer />
<NotificationContainer @position="top" @zindex="99999" />
<BasicDropdownWormhole />
<div id="application-root-wormhole"></div>
{{outlet}}

View File

@@ -33,6 +33,9 @@
<PhoneInput @value={{this.user.phone}} @onInput={{fn (mut this.user.phone)}} class="form-input input-lg w-full" />
</InputGroup>
<InputGroup @name={{t "common.date-of-birth"}} @type="date" @value={{this.user.date_of_birth}} />
<InputGroup @name={{t "common.timezone"}} @helpText={{t "console.account.index.timezone"}}>
<Select @value={{this.user.timezone}} @options={{this.timezones}} @onSelect={{fn (mut this.user.timezone)}} @placeholder={{t "console.account.index.timezone"}} />
</InputGroup>
</div>
<div class="mt-3 flex items-center justify-end">
<Button @buttonType="submit" @type="primary" @size="lg" @icon="save" @text={{t "common.save-button-text"}} @onClick={{perform this.saveProfile}} @isLoading={{not this.saveProfile.isIdle}} />

View File

@@ -4,7 +4,7 @@
<div class="container mx-auto h-screen">
<div class="max-w-3xl my-10 mx-auto space-y-6">
<ContentPanel @title={{t "console.settings.index.title"}} @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
<form {{on "submit" this.saveSettings}}>
<form {{on "submit" (perform this.saveSettings)}}>
<InputGroup @name={{t "console.settings.index.organization-name"}} @value={{@model.name}} />
<InputGroup @name={{t "console.settings.index.organization-description"}} @value={{@model.description}} />
<InputGroup @name={{t "console.settings.index.organization-phone"}}>
@@ -13,9 +13,12 @@
<InputGroup @name={{t "console.settings.index.organization-currency"}}>
<CurrencySelect @currency={{@model.currency}} @onCurrencyChange={{fn (mut @model.currency)}} @triggerClass="w-full form-select" />
</InputGroup>
<InputGroup @name={{t "common.timezone"}} @helpText={{t "console.settings.index.organization-timezone"}}>
<Select @value={{@model.timezone}} @options={{this.timezones}} @onSelect={{fn (mut @model.timezone)}} @placeholder={{t "console.settings.index.select-timezone"}} class="w-full form-select" />
</InputGroup>
<InputGroup @name={{t "console.settings.index.organization-id"}} @value={{@model.public_id}} @disabled={{true}} />
<div class="mt-3 flex items-center justify-end">
<Button @buttonType="submit" @type="primary" @size="lg" @icon="save" @text="{{t "common.save-button-text"}}" @isLoading={{this.isLoading}} />
<Button @buttonType="submit" @type="primary" @size="lg" @icon="save" @text="{{t "common.save-button-text"}}" @isLoading={{this.saveSettings.isRunning}} />
</div>
</form>
</ContentPanel>

View File

@@ -0,0 +1,84 @@
import config from '@fleetbase/console/config/environment';
import toBoolean from '@fleetbase/ember-core/utils/to-boolean';
import { set } from '@ember/object';
import { debug } from '@ember/debug';
/**
* Maps allowed runtime keys to internal config paths.
*/
const RUNTIME_CONFIG_MAP = {
API_HOST: 'API.host',
API_NAMESPACE: 'API.namespace',
SOCKETCLUSTER_PATH: 'socket.path',
SOCKETCLUSTER_HOST: 'socket.hostname',
SOCKETCLUSTER_SECURE: 'socket.secure',
SOCKETCLUSTER_PORT: 'socket.port',
OSRM_HOST: 'osrm.host',
EXTENSIONS: 'APP.extensions',
};
/**
* Coerce and sanitize runtime config values based on key.
*
* @param {String} key
* @param {*} value
* @return {*}
*/
function coerceValue(key, value) {
switch (key) {
case 'SOCKETCLUSTER_PORT':
return parseInt(value, 10);
case 'SOCKETCLUSTER_SECURE':
return toBoolean(value);
case 'EXTENSIONS':
return typeof value === 'string' ? value.split(',') : Array.from(value);
default:
return value;
}
}
/**
* Apply runtime config overrides based on strict allowlist mapping.
*
* @param {Object} rawConfig
*/
export function applyRuntimeConfig(rawConfig = {}) {
Object.entries(rawConfig).forEach(([key, value]) => {
const configPath = RUNTIME_CONFIG_MAP[key];
if (configPath) {
const coercedValue = coerceValue(key, value);
set(config, configPath, coercedValue);
} else {
debug(`[runtime-config] Ignored unknown key: ${key}`);
}
});
}
/**
* Load and apply runtime config.
*
* @export
* @return {void}
*/
export default async function loadRuntimeConfig() {
if (config.APP.disableRuntimeConfig) {
return;
}
try {
const response = await fetch(`/fleetbase.config.json?_t=${Date.now()}`, { cache: 'no-cache' });
if (!response.ok) {
debug('No fleetbase.config.json found, using built-in config defaults');
return;
}
const runtimeConfig = await response.json();
applyRuntimeConfig(runtimeConfig);
} catch (e) {
debug(`Failed to load runtime config : ${e.message}`);
}
}

View File

@@ -21,7 +21,9 @@ module.exports = function (environment) {
},
APP: {
autoboot: false,
extensions: asArray(getenv('EXTENSIONS')),
disableRuntimeConfig: toBoolean(getenv('DISABLE_RUNTIME_CONFIG'))
},
API: {

View File

@@ -3,6 +3,8 @@
/** eslint-disable node/no-unpublished-require */
const EmberApp = require('ember-cli/lib/broccoli/ember-app');
const FleetbaseExtensionsIndexer = require('fleetbase-extensions-indexer');
const Funnel = require('broccoli-funnel');
const writeFile = require('broccoli-file-creator');
const postcssImport = require('postcss-import');
const postcssPresetEnv = require('postcss-preset-env');
const postcssEach = require('postcss-each');
@@ -11,6 +13,8 @@ const postcssConditionals = require('postcss-conditionals-renewed');
const postcssAtRulesVariables = require('postcss-at-rules-variables');
const autoprefixer = require('autoprefixer');
const tailwind = require('tailwindcss');
const toBoolean = require('./config/utils/to-boolean');
const environment = process.env.EMBER_ENV;
module.exports = function (defaults) {
const app = new EmberApp(defaults, {
@@ -59,19 +63,15 @@ module.exports = function (defaults) {
});
let extensions = new FleetbaseExtensionsIndexer();
let runtimeConfigTree;
if (toBoolean(process.env.DISABLE_RUNTIME_CONFIG)) {
runtimeConfigTree = writeFile('fleetbase.config.json', '{}');
} else {
runtimeConfigTree = new Funnel('.', {
files: ['fleetbase.config.json'],
destDir: '/',
});
}
// Use `app.import` to add additional libraries to the generated
// output files.
//
// If you need to use different assets in different
// environments, specify an object as the first parameter. That
// object's keys should be the environment name and the values
// should be the asset to use in that environment.
//
// If the library that you are including contains AMD or ES6
// modules that you would like to import into your application
// please specify an object with the list of modules as keys
// along with the exports of each module as its value.
return app.toTree([extensions]);
return app.toTree([extensions, runtimeConfigTree].filter(Boolean));
};

View File

@@ -0,0 +1,3 @@
{
"API_HOST": "http://localhost:8000"
}

View File

@@ -6,4 +6,18 @@ server {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html =404;
}
# Serve runtime config with no cache
location = /fleetbase.config.json {
root /usr/share/nginx/html;
default_type application/json;
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires 0;
try_files /fleetbase.config.json @config_fallback;
}
location @config_fallback {
return 200 '{}';
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@fleetbase/console",
"version": "0.7.0",
"version": "0.7.5",
"private": true,
"description": "Modular logistics and supply chain operating system (LSOS)",
"repository": "https://github.com/fleetbase/fleetbase",
@@ -29,24 +29,23 @@
},
"dependencies": {
"@ember/legacy-built-in-components": "^0.4.2",
"@fleetbase/dev-engine": "^0.2.9",
"@fleetbase/ember-core": "latest",
"@fleetbase/ember-ui": "latest",
"@fleetbase/fleetops-data": "latest",
"@fleetbase/fleetops-engine": "^0.6.8",
"@fleetbase/fleetops-engine": "^0.6.14",
"@fleetbase/iam-engine": "^0.1.3",
"@fleetbase/dev-engine": "^0.2.9",
"@fleetbase/leaflet-routing-machine": "^3.2.17",
"@fleetbase/registry-bridge-engine": "^0.0.19",
"@fleetbase/storefront-engine": "^0.3.31",
"@fleetbase/leaflet-routing-machine": "^3.2.16",
"@fleetbase/storefront-engine": "^0.4.0",
"@fortawesome/ember-fontawesome": "^2.0.0",
"ember-changeset": "4.1.2",
"ember-changeset-validations": "4.1.2",
"ember-composable-helpers": "^5.0.0",
"ember-concurrency": "^3.1.1",
"ember-concurrency": "^4.0.4",
"ember-concurrency-decorators": "^2.0.3",
"ember-intl": "6.3.2",
"ember-math-helpers": "^2.18.2",
"ember-power-select": "^7.2.0",
"ember-prism": "^0.13.0",
"ember-radio-button": "3.0.0-beta.1",
"ember-tag-input": "^3.1.0",
@@ -56,13 +55,13 @@
"postcss-nth-list": "^1.0.2"
},
"devDependencies": {
"@embroider/macros": "1.16.12",
"@babel/core": "^7.25.2",
"@babel/eslint-parser": "^7.25.1",
"@babel/plugin-proposal-decorators": "^7.24.7",
"@ember/optional-features": "^2.1.0",
"@ember/string": "^3.1.1",
"@ember/test-helpers": "^3.3.1",
"@embroider/macros": "1.16.12",
"@fleetbase/intl-lint": "^0.0.1",
"@fortawesome/fontawesome-svg-core": "6.4.0",
"@fortawesome/free-brands-svg-icons": "6.4.0",
@@ -72,6 +71,7 @@
"@tailwindcss/forms": "^0.5.7",
"autoprefixer": "^10.4.20",
"broccoli-asset-rev": "^3.0.0",
"broccoli-file-creator": "^2.1.1",
"broccoli-funnel": "^3.0.8",
"concurrently": "^8.2.2",
"date-fns": "^2.30.0",

2847
console/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
import runtimeConfig from '@fleetbase/console/utils/runtime-config';
import { module, test } from 'qunit';
module('Unit | Utility | runtime-config', function () {
// TODO: Replace this with your real tests.
test('it works', function (assert) {
let result = runtimeConfig();
assert.ok(result);
});
});

View File

@@ -75,6 +75,7 @@ common:
permissions-required-for-changes: You do not have the required permissions to make changes.
push-notifications: Push Notifications
role: Role
timezone: Timezone
component:
file:
dropdown-label: File actions
@@ -229,6 +230,7 @@ console:
upload-new: Upload new
phone: Your phone number.
photos: photos
timezone: Select your timezone.
admin:
schedule-monitor:
schedule-monitor: Schedule Monitor
@@ -301,6 +303,8 @@ console:
backdrop: Backdrop
backdrop-help-text: Optional banner or background image for your organization.
upload-new-backdrop: Upload new backdrop
organization-timezone: Select the default timezone for your organization.
select-timezone: Select timezone.
extensions:
title: Extensions are coming soon!
message: Please check back in the upcoming versions as we prepare to launch the Extensions repository and marketplace.

View File

@@ -9,6 +9,10 @@ group "default" {
targets = ["app", "app-httpd"]
}
group "release" {
targets = ["fleetbase-console", "fleetbase-api"]
}
target "app" {
name = "app-${tgt}"
@@ -48,4 +52,27 @@ target "app-httpd" {
GCP ? "${REGISTRY}/app-httpd:%s" : "${REGISTRY}:app-httpd-%s",
compact(["latest", VERSION])
) : []
}
target "fleetbase-console" {
context = "./console"
dockerfile = "Dockerfile"
platforms = ["linux/amd64"]
tags = notequal("", REGISTRY) ? formatlist(
"${REGISTRY}/fleetbase-console:%s",
compact(["latest", VERSION])
) : []
}
target "fleetbase-api" {
context = "./"
dockerfile = "docker/Dockerfile"
target = "app-release"
platforms = ["linux/amd64"]
tags = notequal("", REGISTRY) ? formatlist(
"${REGISTRY}/fleetbase-api:%s",
compact(["latest", VERSION])
) : []
}

View File

@@ -31,13 +31,20 @@ services:
SOCKETCLUSTER_WORKERS: 10
SOCKETCLUSTER_BROKERS: 10
scheduler:
image: fleetbase/fleetbase-api:latest
command: ["go-crond", "--verbose", "root:./crontab"]
environment:
DATABASE_URL: "mysql://root@database/fleetbase"
QUEUE_CONNECTION: redis
CACHE_DRIVER: redis
CACHE_PATH: /fleetbase/api/storage/framework/cache
CACHE_URL: tcp://cache
REDIS_URL: tcp://cache
queue:
build:
context: .
dockerfile: docker/Dockerfile
target: events-dev
args:
ENVIRONMENT: development
image: fleetbase/fleetbase-api:latest
command: ["php", "artisan", "queue:work"]
healthcheck:
test: ["CMD", "php", "artisan", "queue:status"]
interval: 30s
@@ -52,21 +59,16 @@ services:
REDIS_URL: tcp://cache
console:
build:
context: .
dockerfile: console/Dockerfile.server-build
args:
ENVIRONMENT: development
image: fleetbase/fleetbase-console:latest
ports:
- "4200:4200"
volumes:
- ./console/fleetbase.config.json:/usr/share/nginx/html/fleetbase.config.json
application:
build:
context: .
dockerfile: docker/Dockerfile
target: app-dev
args:
ENVIRONMENT: development
image: fleetbase/fleetbase-api:latest
volumes:
- ./api/.env:/fleetbase/api/.env
environment:
ENVIRONMENT: development
DATABASE_URL: "mysql://root@database/fleetbase"

View File

@@ -3,7 +3,7 @@
FROM dunglas/frankenphp:1.5.0-php8.2-bookworm AS base
# Install packages
RUN apt-get update && apt-get install -y git bind9-utils mycli nodejs npm nano \
RUN apt-get update && apt-get install -y git bind9-utils mycli nodejs npm nano uuid-runtime \
&& mkdir -p /root/.ssh \
&& ssh-keyscan github.com >> /root/.ssh/known_hosts
@@ -75,20 +75,34 @@ ENV QUEUE_CONNECTION=redis
ENV CADDYFILE_PATH=/fleetbase/Caddyfile
ENV CONSOLE_PATH=/fleetbase/console
ENV OCTANE_SERVER=frankenphp
ENV FLEETBASE_VERSION=0.7.5
# Set environment
ARG ENVIRONMENT=production
ENV APP_ENV=$ENVIRONMENT
# Setup github auth
ARG GITHUB_AUTH_KEY
# Copy Caddyfile
COPY --chown=www-data:www-data ./Caddyfile $CADDYFILE_PATH
# Create /fleetbase directory and set correct permissions
RUN mkdir -p /fleetbase/api && mkdir -p /fleetbase/console && chown -R www-data:www-data /fleetbase
# Generate and store a UUID in .fleetbase-id
RUN uuidgen > /fleetbase/api/.fleetbase-id
# Export the same ID as an environment variable for use in the container
RUN export FLEETBASE_INSTANCE_ID=$(cat /fleetbase/api/.fleetbase-id) && \
echo "FLEETBASE_INSTANCE_ID=$FLEETBASE_INSTANCE_ID" >> /etc/environment
# Set working directory
WORKDIR /fleetbase/api
# If GITHUB_AUTH_KEY is provided, create auth.json with it
RUN if [ -n "$GITHUB_AUTH_KEY" ]; then echo "{\"github-oauth\": {\"github.com\": \"$GITHUB_AUTH_KEY\"}}" > auth.json; fi
# Prepare composer cache directory
RUN mkdir -p /var/www/.cache/composer && chown -R www-data:www-data /var/www/.cache/composer
@@ -116,21 +130,18 @@ RUN chmod -R 755 /fleetbase/api/storage
# Set permissions for deploy script
RUN chmod +x /fleetbase/api/deploy.sh
# Scheduler base stage
FROM base AS scheduler-base
# Install go-crond
RUN curl -L https://github.com/webdevops/go-crond/releases/download/23.12.0/go-crond.linux.amd64 > /usr/local/bin/go-crond && chmod +x /usr/local/bin/go-crond
COPY docker/crontab ./crontab
RUN chmod 0600 ./crontab
# Scheduler dev stage
FROM scheduler-base AS scheduler-dev
FROM base AS scheduler-dev
ENTRYPOINT []
CMD ["go-crond", "--verbose", "root:./crontab"]
# Scheduler stage
FROM scheduler-base AS scheduler
FROM base AS scheduler
ENTRYPOINT ["/sbin/ssm-parent", "-c", ".ssm-parent.yaml", "run", "--"]
CMD ["go-crond", "--verbose", "root:./crontab"]
@@ -147,7 +158,12 @@ CMD ["php", "artisan", "queue:work"]
# Application dev stage
FROM base AS app-dev
ENTRYPOINT ["docker-php-entrypoint"]
CMD ["sh", "-c", "php artisan octane:frankenphp --max-requests=250 --port=8000 --host=0.0.0.0 --watch"]
CMD ["sh", "-c", "php artisan octane:frankenphp --max-requests=250 --port=8000 --host=0.0.0.0 --watch"]
# Application release stage
FROM base AS app-release
ENTRYPOINT ["docker-php-entrypoint"]
CMD ["sh", "-c", "php artisan octane:frankenphp --max-requests=250 --port=8000 --host=0.0.0.0"]
# Application stage
FROM base AS app

170
scripts/docker-install.sh Executable file
View File

@@ -0,0 +1,170 @@
#!/usr/bin/env bash
# scripts/docker-install.sh
# Fleetbase Docker installer (dev / prod aware)
# --------------------------------------------
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"
###############################################################################
# 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
done
echo "➜ Environment: $ENVIRONMENT"
USE_HTTPS=false
APP_DEBUG=true
SC_SECURE=false
if [[ "$ENVIRONMENT" == "production" ]]; then
USE_HTTPS=true
APP_DEBUG=false
SC_SECURE=true
fi
###############################################################################
# 3. Determine project root no matter where script is called from
###############################################################################
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
PROJECT_ROOT="$( cd "$SCRIPT_DIR/.." && pwd )"
cd "$PROJECT_ROOT"
###############################################################################
# 4. Generate a fresh Laravel APP_KEY
###############################################################################
if ! command -v openssl >/dev/null 2>&1; then
echo "✖ openssl is required but not found. Install it and retry." >&2
exit 1
fi
APP_KEY="base64:$(openssl rand -base64 32 | tr -d '\n')"
echo "✔ Generated APP_KEY"
###############################################################################
# 5. Ensure dockercompose.override.yml is present & updated
###############################################################################
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")
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)"
}
create_override() {
cat > "$OVERRIDE_FILE" <<YML
services:
application:
environment:
APP_KEY: "$APP_KEY"
CONSOLE_HOST: "$SCHEME_CONSOLE://$HOST:4200"
ENVIRONMENT: "$ENVIRONMENT"
APP_DEBUG: "$APP_DEBUG"
YML
echo "$OVERRIDE_FILE written"
}
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
fi
else
create_override
fi
###############################################################################
# 6. Write console/fleetbase.config.json atomically
###############################################################################
CONFIG_DIR="console"
CONFIG_PATH="$CONFIG_DIR/fleetbase.config.json"
mkdir -p "$CONFIG_DIR"
cat > "${CONFIG_PATH}.tmp" <<JSON
{
"API_HOST": "$SCHEME_API://$HOST:8000",
"SOCKETCLUSTER_HOST": "$HOST",
"SOCKETCLUSTER_PORT": "38000",
"SOCKETCLUSTER_SECURE": "$SC_SECURE"
}
JSON
mv -f "${CONFIG_PATH}.tmp" "$CONFIG_PATH"
echo "$CONFIG_PATH updated"
###############################################################################
# 7. Start stack, wait for DB, then run deploy
###############################################################################
echo "⏳ Starting Fleetbase containers..."
docker compose up -d
###############################################################################
# 7a. Wait for the database container to be ready
###############################################################################
DB_SERVICE="database" # ← change if your dockercompose uses a different name
DB_WAIT_TIMEOUT=60 # seconds
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."
exit 1
fi
# If the service defines a HEALTHCHECK we can rely on it…
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."
exit 1
fi
sleep 2
done
sleep 12
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."
exit 1
fi
sleep 2
done
fi
echo "✔ Database is ready."
###############################################################################
# 7b. Run the deploy script inside the application container
###############################################################################
echo "⏳ Running deploy script inside the application container..."
docker compose exec application bash -c "./deploy.sh"
docker compose up -d
echo
echo "🏁 Fleetbase is up!"
printf " API → %s://%s:8000\n" "$SCHEME_API" "$HOST"
printf " Console → %s://%s:4200\n\n" "$SCHEME_CONSOLE" "$HOST"

View File

@@ -1,55 +0,0 @@
#!/bin/bash
# Find the root directory of your repository
root_dir=$(git rev-parse --show-toplevel)
# Check if the packages directory exists
packages_dir="$root_dir/packages"
if [ ! -d "$packages_dir" ]; then
echo "Packages directory not found."
exit 1
fi
# Initialize flags
remove_lock=false
remove_modules=false
for arg in "$@"
do
case $arg in
--remove-lock)
remove_lock=true
;;
--remove-modules)
remove_modules=true
;;
esac
done
# Navigate to the packages directory
cd "$packages_dir"
# Find all child directories and run pnpm install if package.json exists
for dir in */; do
if [[ -f "${dir}package.json" ]]; then
echo "Running pnpm install in $dir"
# Remove pnpm-lock.yaml if the option is set
if [ "$remove_lock" = true ] && [ -f "${dir}pnpm-lock.yaml" ]; then
echo "Removing pnpm-lock.yaml in $dir"
rm "${dir}pnpm-lock.yaml"
fi
# Remove ./node_modules if the option is set
if [ "$remove_modules" = true ] && [ -d "${dir}node_modules" ]; then
echo "Removing /node_modules in $dir"
rm -rf "${dir}node_modules"
fi
cd "$dir"
pnpm install
cd "$packages_dir" # Go back to the packages directory
else
echo "No package.json found in $dir, skipping..."
fi
done

View File

@@ -1,22 +0,0 @@
#!/bin/bash
# Exit the script as soon as a command fails
set -e
echo "Switching to the main branch..."
# git checkout main
echo "Updating submodules..."
git submodule update --init --recursive
echo "Updating console..."
cd console
git checkout main
git pull
cd ..
echo "Building Docker images..."
docker-compose build console
docker-compose build application
echo "Update completed. Run \`docker-compose up -d\` to launch!"