diff --git a/app/Models/Server.php b/app/Models/Server.php index 36d6d154a..79e3d0bc6 100755 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -385,6 +385,21 @@ public function installedNodejsVersions(): array return $versions; } + /** + * @return array + */ + public function installedBunVersions(): array + { + $versions = []; + $buns = $this->services()->where('type', 'bun')->get(['version']); + /** @var Service $bun */ + foreach ($buns as $bun) { + $versions[] = $bun->version; + } + + return $versions; + } + public function provider(): \App\ServerProviders\ServerProvider { $providerClass = config('server-provider.providers.'.$this->provider.'.handler'); @@ -449,6 +464,15 @@ public function nodejs(?string $version = null): ?Service return $this->service('nodejs', $version); } + public function bun(?string $version = null): ?Service + { + if ($version === null || $version === '' || $version === '0') { + return $this->defaultService('bun'); + } + + return $this->service('bun', $version); + } + public function memoryDatabase(?string $version = null): ?Service { if ($version === null || $version === '' || $version === '0') { diff --git a/app/Providers/ServiceTypeServiceProvider.php b/app/Providers/ServiceTypeServiceProvider.php index aaa4fd110..5e004fcd7 100644 --- a/app/Providers/ServiceTypeServiceProvider.php +++ b/app/Providers/ServiceTypeServiceProvider.php @@ -3,6 +3,7 @@ namespace App\Providers; use App\Plugins\RegisterServiceType; +use App\Services\Bun\Bun; use App\Services\Database\Mariadb; use App\Services\Database\Mysql; use App\Services\Database\Postgresql; @@ -31,6 +32,7 @@ public function boot(): void $this->monitoring(); $this->php(); $this->node(); + $this->bun(); } private function webservers(): void @@ -219,4 +221,19 @@ private function node(): void ]) ->register(); } + + private function bun(): void + { + RegisterServiceType::make(Bun::id()) + ->type(Bun::type()) + ->label('Bun') + ->handler(Bun::class) + ->versions([ + '1.3.4', + '1.2.23', + '1.1.45', + '1.0.36', + ]) + ->register(); + } } diff --git a/app/Providers/SiteTypeServiceProvider.php b/app/Providers/SiteTypeServiceProvider.php index 695472a0d..9c7ddfe7d 100644 --- a/app/Providers/SiteTypeServiceProvider.php +++ b/app/Providers/SiteTypeServiceProvider.php @@ -8,6 +8,7 @@ use App\Plugins\RegisterSiteFeature; use App\Plugins\RegisterSiteFeatureAction; use App\Plugins\RegisterSiteType; +use App\SiteTypes\Bun; use App\SiteTypes\Laravel; use App\SiteTypes\LoadBalancer; use App\SiteTypes\NodeJS; @@ -27,6 +28,7 @@ public function boot(): void $this->phpBlank(); $this->laravel(); $this->nodeJS(); + $this->bun(); $this->loadBalancer(); $this->phpMyAdmin(); $this->wordpress(); @@ -159,6 +161,33 @@ private function nodeJS(): void ->register(); } + private function bun(): void + { + RegisterSiteType::make(Bun::id()) + ->label('Bun') + ->handler(Bun::class) + ->form(DynamicForm::make([ + DynamicField::make('source_control') + ->component() + ->label('Source Control'), + DynamicField::make('port') + ->text() + ->label('Port') + ->placeholder('3000') + ->description('On which port your app will be running'), + DynamicField::make('repository') + ->text() + ->label('Repository') + ->placeholder('organization/repository') + ->description('Your package.json must have start and build scripts'), + DynamicField::make('branch') + ->text() + ->label('Branch') + ->default('main'), + ])) + ->register(); + } + public function loadBalancer(): void { RegisterSiteType::make(LoadBalancer::id()) diff --git a/app/Services/Bun/Bun.php b/app/Services/Bun/Bun.php new file mode 100644 index 000000000..8c829c569 --- /dev/null +++ b/app/Services/Bun/Bun.php @@ -0,0 +1,101 @@ + [ + function (string $attribute, mixed $value, Closure $fail): void { + $exists = $this->service->server->bun(); + if ($exists) { + $fail('You already have Bun installed on the server.'); + } + }, + ], + 'version' => [ + 'required', + Rule::in(config('service.services.bun.versions')), + Rule::unique('services', 'version') + ->where('type', 'bun') + ->where('server_id', $this->service->server_id), + ], + ]; + } + + public function deletionRules(): array + { + return [ + 'service' => [ + function (string $attribute, mixed $value, Closure $fail): void { + $hasSite = $this->service->server->sites() + ->where('type', 'bun') + ->exists(); + if ($hasSite) { + $fail('Some sites are using Bun.'); + } + }, + ], + ]; + } + + /** + * @throws SSHError + */ + public function install(): void + { + $server = $this->service->server; + $server->ssh()->exec( + view('ssh.services.bun.install-bun', [ + 'version' => $this->service->version, + ]), + 'install-bun-'.$this->service->version + ); + event('service.installed', $this->service); + $this->service->server->os()->cleanup(); + } + + /** + * @throws SSHError + */ + public function uninstall(): void + { + $this->service->server->ssh()->exec( + view('ssh.services.bun.uninstall-bun'), + 'uninstall-bun' + ); + event('service.uninstalled', $this->service); + $this->service->server->os()->cleanup(); + } + + public function version(): string + { + $version = $this->service->server->ssh()->exec( + 'bun --version' + ); + + return trim($version); + } +} diff --git a/app/SiteTypes/Bun.php b/app/SiteTypes/Bun.php new file mode 100755 index 000000000..63b5a0f28 --- /dev/null +++ b/app/SiteTypes/Bun.php @@ -0,0 +1,172 @@ + self::id()])); + } + + public function createRules(array $input): array + { + return [ + 'source_control' => [ + 'required', + Rule::exists('source_controls', 'id'), + ], + 'repository' => [ + 'required', + ], + 'branch' => [ + 'required', + ], + 'port' => [ + 'required', + 'numeric', + 'between:1,65535', + ], + ]; + } + + public function createFields(array $input): array + { + return [ + 'source_control_id' => $input['source_control'] ?? '', + 'repository' => $input['repository'] ?? '', + 'branch' => $input['branch'] ?? '', + 'port' => $input['port'] ?? '', + ]; + } + + public function data(array $input): array + { + return []; + } + + /** + * @throws FailedToDeployGitKey + * @throws SSHError + */ + public function install(): void + { + $this->isolate(); + $this->site->webserver()->createVHost($this->site); + $this->progress(15); + $this->deployKey(); + $this->progress(30); + app(Git::class)->clone($this->site); + $this->site->server->ssh($this->site->user)->exec( + __('bun install --cwd :path', [ + 'path' => $this->site->path, + ]), + 'install-bun-dependencies', + $this->site->id + ); + $this->site->server->ssh($this->site->user)->exec( + __('bun --bun run --cwd :path build', [ + 'path' => $this->site->path, + ]), + 'bun-build', + $this->site->id + ); + $this->progress(65); + $command = __('bun --bun run --cwd :path start', [ + 'path' => $this->site->path, + ]); + $this->progress(80); + /** @var ?Worker $worker */ + $worker = $this->site->workers()->where('name', 'app')->first(); + if ($worker) { + app(ManageWorker::class)->restart($worker); + } else { + app(CreateWorker::class)->create( + $this->site->server, + [ + 'name' => 'app', + 'command' => $command, + 'user' => $this->site->user ?? $this->site->server->getSshUser(), + 'auto_start' => true, + 'auto_restart' => true, + 'numprocs' => 1, + ], + $this->site, + ); + } + } + + public function baseCommands(): array + { + return [ + [ + 'name' => 'bun:install', + 'command' => 'bun install', + ], + [ + 'name' => 'bun:build', + 'command' => 'bun --bun run build', + ], + ]; + } + + public function vhost(string $webserver): string|View + { + if ($webserver === 'nginx') { + return view('ssh.services.webserver.nginx.vhost', [ + 'header' => [ + view('ssh.services.webserver.nginx.vhost-blocks.force-ssl', ['site' => $this->site]), + ], + 'main' => [ + view('ssh.services.webserver.nginx.vhost-blocks.port', ['site' => $this->site]), + view('ssh.services.webserver.nginx.vhost-blocks.core', ['site' => $this->site]), + view('ssh.services.webserver.nginx.vhost-blocks.reverse-proxy', ['site' => $this->site]), + view('ssh.services.webserver.nginx.vhost-blocks.redirects', ['site' => $this->site]), + ], + ]); + } + + if ($webserver === 'caddy') { + return view('ssh.services.webserver.caddy.vhost', [ + 'main' => [ + view('ssh.services.webserver.caddy.vhost-blocks.force-ssl', ['site' => $this->site]), + view('ssh.services.webserver.caddy.vhost-blocks.port', ['site' => $this->site]), + view('ssh.services.webserver.caddy.vhost-blocks.core', ['site' => $this->site]), + view('ssh.services.webserver.caddy.vhost-blocks.reverse-proxy', ['site' => $this->site]), + view('ssh.services.webserver.caddy.vhost-blocks.redirects', ['site' => $this->site]), + ], + ]); + } + + return ''; + } +} diff --git a/app/WorkflowActions/Site/CreateBunSite.php b/app/WorkflowActions/Site/CreateBunSite.php new file mode 100644 index 000000000..e4fe68056 --- /dev/null +++ b/app/WorkflowActions/Site/CreateBunSite.php @@ -0,0 +1,17 @@ + 'bun', + 'port' => 'Port to run the Bun.js application on, example: 3000', + 'source_control' => 'Source control ID', + 'repository' => 'organization/repository', + 'branch' => 'Branch to deploy, example: main', + ]); + } +} diff --git a/resources/deployment-scripts/bun.sh b/resources/deployment-scripts/bun.sh new file mode 100644 index 000000000..98b780747 --- /dev/null +++ b/resources/deployment-scripts/bun.sh @@ -0,0 +1,11 @@ +cd $SITE_PATH + +git pull origin $BRANCH + +bun install + +bun --bun run build + +sudo supervisorctl restart all + +echo "✅ Deployment completed successfully!" diff --git a/resources/views/ssh/services/bun/install-bun.blade.php b/resources/views/ssh/services/bun/install-bun.blade.php new file mode 100644 index 000000000..41c3054b1 --- /dev/null +++ b/resources/views/ssh/services/bun/install-bun.blade.php @@ -0,0 +1,18 @@ +# Define the Bun version to install (e.g., "1.1", "1.0") +BUN_VERSION={{ $version }} + +# Install Bun using the official installer +curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}" + +# Move Bun and Bunx binaries to /usr/local/bin for global access +sudo mv ~/.bun/bin/bun /usr/local/bin/ +sudo chmod a+x /usr/local/bin/bun +sudo mv ~/.bun/bin/bunx /usr/local/bin/ +sudo chmod a+x /usr/local/bin/bunx + +# The installation script adds "~/.bun/bin" to $PATH in "~/.bashrc" automatically, we should remove it +sed -i '/\.bun\/bin/d' "$HOME/.bashrc" 2>/dev/null || true +sed -i '/\.bun\/bin/d' "$HOME/.zshrc" 2>/dev/null || true + +# Verify installation +bun --version diff --git a/resources/views/ssh/services/bun/uninstall-bun.blade.php b/resources/views/ssh/services/bun/uninstall-bun.blade.php new file mode 100644 index 000000000..643677fec --- /dev/null +++ b/resources/views/ssh/services/bun/uninstall-bun.blade.php @@ -0,0 +1,12 @@ +echo "Uninstalling Bun..." + +# Remove Bun installation directory +rm -rf "$HOME/.bun" + +# Remove Bun init lines from shell configs +sed -i '/\.bun/d' "$HOME/.bashrc" 2>/dev/null || true +sed -i '/\.bun/d' "$HOME/.zshrc" 2>/dev/null || true +sed -i '/BUN_INSTALL/d' "$HOME/.bashrc" 2>/dev/null || true +sed -i '/BUN_INSTALL/d' "$HOME/.zshrc" 2>/dev/null || true + +echo "Bun uninstalled successfully."