mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-04 10:42:23 +00:00
Merge remote-tracking branch 'origin/main' into next
This commit is contained in:
20 files changed
+201
-111
No files matched your search
@@ -71,7 +71,6 @@ Thank you so much!
|
||||
|
||||
### Huge Sponsors
|
||||
|
||||
* [Context.dev](https://www.context.dev/) - Web scraping API for AI agents
|
||||
* [Ginernet](https://ginernet.com/) - Hosting powerful servers in Spain
|
||||
* [SerpAPI](https://serpapi.com) - Google Search API — Scrape Google and other search engines from our fast, easy, and complete API.
|
||||
* [MVPS](https://www.mvps.net) - Cheap VPS servers at the highest possible quality
|
||||
@@ -164,6 +163,7 @@ Thank you so much!
|
||||
<a href="https://interviewpal.com"><img width="60px" alt="InterviewPal" src="https://cdn.coollabs.io/sponsors/interviewpal.svg"/></a>
|
||||
<a href="https://decidable.no?utm_source=coolify.io"><img width="60px" alt="Decidable" src="https://github.com/Decidable-AS.png"/></a>
|
||||
<a href="https://hosthavoc.com"><img width="60px" alt="Host Havoc" src="https://cdn.coollabs.io/sponsors/hosthavoc.png"/></a>
|
||||
<a href="https://martiinii.dev"><img width="60px" alt="martiinii" src="https://github.com/martiinii.png"/></a>
|
||||
|
||||
...and many more at [GitHub Sponsors](https://github.com/sponsors/coollabsio)
|
||||
|
||||
|
||||
@@ -231,7 +231,7 @@ class ServerDockerRegistriesController extends Controller
|
||||
|
||||
#[OA\Post(
|
||||
summary: 'Check a registry login on a server',
|
||||
description: 'Check that the saved login of the server for a registry still works. Runs docker login with the saved credentials on the server.',
|
||||
description: 'Check that the saved login of the server for a registry still works. Runs docker login with the saved credentials on the server. Needs a token with the write and deploy permissions.',
|
||||
path: '/servers/{uuid}/registries/{registry}/check',
|
||||
operationId: 'check-server-registry-login',
|
||||
security: [['bearerAuth' => []]],
|
||||
|
||||
@@ -55,11 +55,6 @@ class GithubRunners extends Component
|
||||
|
||||
public bool $allowPullRequests = false;
|
||||
|
||||
/** Null until checked over SSH. */
|
||||
public ?bool $isSysboxInstalled = null;
|
||||
|
||||
protected $listeners = ['sysboxInstalled' => 'checkSysbox'];
|
||||
|
||||
public function mount(string $server_uuid)
|
||||
{
|
||||
try {
|
||||
@@ -178,7 +173,6 @@ class GithubRunners extends Component
|
||||
$githubApp = $this->selectedApp;
|
||||
$this->authorize('update', $githubApp);
|
||||
if ($this->dockerMode === GithubRunnerDockerMode::Sysbox->value && ! InstallSysbox::isInstalled($this->server)) {
|
||||
$this->isSysboxInstalled = false;
|
||||
throw ValidationException::withMessages(['dockerMode' => 'Sysbox is not installed on this server. Install it, then save again.']);
|
||||
}
|
||||
|
||||
@@ -258,25 +252,6 @@ class GithubRunners extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function checkSysbox(): void
|
||||
{
|
||||
$this->authorize('view', $this->server);
|
||||
$this->isSysboxInstalled = InstallSysbox::isInstalled($this->server);
|
||||
}
|
||||
|
||||
public function installSysbox()
|
||||
{
|
||||
$this->authorize('update', $this->server);
|
||||
|
||||
try {
|
||||
$activity = InstallSysbox::run($this->server);
|
||||
$this->dispatch('sysbox-install-started');
|
||||
$this->dispatch('activityMonitor', $activity->id, 'sysboxInstalled');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the runner group when the App can run workflow jobs. An App that is not ready is saved anyway:
|
||||
* the page lists what it misses, and the runner group is created when the first job is provisioned.
|
||||
|
||||
@@ -850,8 +850,11 @@ class Show extends Component
|
||||
{
|
||||
try {
|
||||
$this->authorize('update', $this->server);
|
||||
$providerRefreshed = $this->refreshProviderStatus();
|
||||
if (! $this->server->isFunctional()) {
|
||||
$this->dispatch('error', 'Validate the server connection before fetching details.');
|
||||
$providerRefreshed
|
||||
? $this->dispatch('info', 'Provider status refreshed. Validate the server connection to fetch server details.')
|
||||
: $this->dispatch('error', 'Validate the server connection before fetching details.');
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -868,6 +871,30 @@ class Show extends Component
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the state of the linked cloud provider server, if there is one.
|
||||
*/
|
||||
private function refreshProviderStatus(): bool
|
||||
{
|
||||
if (! $this->server->cloudProviderToken || $this->server->isTransferredAway()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$check = match (true) {
|
||||
filled($this->server->hetzner_server_id) => $this->checkHetznerServerStatus(...),
|
||||
filled($this->server->digitalocean_droplet_id) => $this->checkDigitalOceanDropletStatus(...),
|
||||
filled($this->server->vultr_instance_id) => $this->checkVultrInstanceStatus(...),
|
||||
filled($this->server->hostinger_virtual_machine_id) => $this->checkHostingerVirtualMachineStatus(...),
|
||||
default => null,
|
||||
};
|
||||
if (! $check) {
|
||||
return false;
|
||||
}
|
||||
$check();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Server;
|
||||
|
||||
use App\Actions\Server\InstallSysbox;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Livewire\Attributes\Lazy;
|
||||
use Livewire\Component;
|
||||
|
||||
/**
|
||||
* A separate component, so that its requests do not send the unsaved fields of the runner form.
|
||||
* Otherwise the check would store a new Docker mode in the form snapshot and hide the unsaved bar.
|
||||
*/
|
||||
#[Lazy]
|
||||
class SysboxStatus extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
public Server $server;
|
||||
|
||||
public bool $isInstalled = false;
|
||||
|
||||
protected $listeners = ['sysboxInstalled' => 'checkSysbox'];
|
||||
|
||||
public function mount(Server $server): void
|
||||
{
|
||||
$this->authorize('view', $server);
|
||||
$this->server = $server;
|
||||
$this->checkSysbox();
|
||||
}
|
||||
|
||||
public function checkSysbox(): void
|
||||
{
|
||||
$this->authorize('view', $this->server);
|
||||
$this->isInstalled = InstallSysbox::isInstalled($this->server);
|
||||
}
|
||||
|
||||
public function installSysbox()
|
||||
{
|
||||
$this->authorize('update', $this->server);
|
||||
|
||||
try {
|
||||
$activity = InstallSysbox::run($this->server);
|
||||
$this->dispatch('sysbox-install-started');
|
||||
$this->dispatch('activityMonitor', $activity->id, 'sysboxInstalled');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function placeholder(): View
|
||||
{
|
||||
return view('livewire.server.sysbox-status-placeholder');
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('livewire.server.sysbox-status');
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -18391,7 +18391,7 @@
|
||||
"Servers"
|
||||
],
|
||||
"summary": "Check a registry login on a server",
|
||||
"description": "Check that the saved login of the server for a registry still works. Runs docker login with the saved credentials on the server.",
|
||||
"description": "Check that the saved login of the server for a registry still works. Runs docker login with the saved credentials on the server. Needs a token with the write and deploy permissions.",
|
||||
"operationId": "check-server-registry-login",
|
||||
"parameters": [
|
||||
{
|
||||
|
||||
+1
-1
@@ -11705,7 +11705,7 @@ paths:
|
||||
tags:
|
||||
- Servers
|
||||
summary: 'Check a registry login on a server'
|
||||
description: 'Check that the saved login of the server for a registry still works. Runs docker login with the saved credentials on the server.'
|
||||
description: 'Check that the saved login of the server for a registry still works. Runs docker login with the saved credentials on the server. Needs a token with the write and deploy permissions.'
|
||||
operationId: check-server-registry-login
|
||||
parameters:
|
||||
-
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
'dynamic' => false,
|
||||
'pulse' => false, // ping the dot for a state that is still in progress
|
||||
'size' => 'md', // sm fits inline next to 12px table text without growing the row
|
||||
'spinning' => false, // show the loading spinner instead of the dot, e.g. for a running deployment
|
||||
])
|
||||
|
||||
@php
|
||||
@@ -29,12 +30,19 @@
|
||||
@if ($dynamic)
|
||||
{{ $slot }}
|
||||
@else
|
||||
@if ($spinning)
|
||||
<svg class="loading-indicator size-3 shrink-0 animate-spin text-neutral-500 dark:text-fg-dim" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<circle class="opacity-20" cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2" />
|
||||
<path class="opacity-80" d="M21 12a9 9 0 0 0-9-9" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
|
||||
</svg>
|
||||
@else
|
||||
<span class="relative flex size-1.5 shrink-0">
|
||||
@if ($pulse)
|
||||
<span class="absolute inline-flex size-full rounded-full opacity-75 motion-safe:animate-ping {{ $dotClasses[$type] ?? $dotClasses['neutral'] }}"></span>
|
||||
@endif
|
||||
<span class="relative inline-flex size-1.5 rounded-full {{ $dotClasses[$type] ?? $dotClasses['neutral'] }}"></span>
|
||||
</span>
|
||||
@endif
|
||||
<span class="truncate">{{ collect([$label, $status])->filter()->join(' ') }}</span>
|
||||
@endif
|
||||
</button>
|
||||
@@ -43,12 +51,19 @@
|
||||
@if ($dynamic)
|
||||
{{ $slot }}
|
||||
@else
|
||||
@if ($spinning)
|
||||
<svg class="loading-indicator size-3 shrink-0 animate-spin text-neutral-500 dark:text-fg-dim" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<circle class="opacity-20" cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2" />
|
||||
<path class="opacity-80" d="M21 12a9 9 0 0 0-9-9" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
|
||||
</svg>
|
||||
@else
|
||||
<span class="relative flex size-1.5 shrink-0">
|
||||
@if ($pulse)
|
||||
<span class="absolute inline-flex size-full rounded-full opacity-75 motion-safe:animate-ping {{ $dotClasses[$type] ?? $dotClasses['neutral'] }}"></span>
|
||||
@endif
|
||||
<span class="relative inline-flex size-1.5 rounded-full {{ $dotClasses[$type] ?? $dotClasses['neutral'] }}"></span>
|
||||
</span>
|
||||
@endif
|
||||
<span class="truncate">{{ collect([$label, $status])->filter()->join(' ') }}</span>
|
||||
@endif
|
||||
</a>
|
||||
@@ -57,12 +72,19 @@
|
||||
@if ($dynamic)
|
||||
{{ $slot }}
|
||||
@else
|
||||
@if ($spinning)
|
||||
<svg class="loading-indicator size-3 shrink-0 animate-spin text-neutral-500 dark:text-fg-dim" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<circle class="opacity-20" cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2" />
|
||||
<path class="opacity-80" d="M21 12a9 9 0 0 0-9-9" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
|
||||
</svg>
|
||||
@else
|
||||
<span class="relative flex size-1.5 shrink-0">
|
||||
@if ($pulse)
|
||||
<span class="absolute inline-flex size-full rounded-full opacity-75 motion-safe:animate-ping {{ $dotClasses[$type] ?? $dotClasses['neutral'] }}"></span>
|
||||
@endif
|
||||
<span class="relative inline-flex size-1.5 rounded-full {{ $dotClasses[$type] ?? $dotClasses['neutral'] }}"></span>
|
||||
</span>
|
||||
@endif
|
||||
<span class="truncate">{{ collect([$label, $status])->filter()->join(' ') }}</span>
|
||||
@endif
|
||||
</span>
|
||||
|
||||
@@ -81,14 +81,14 @@
|
||||
{{ $deployment->server_name ?: '-' }}
|
||||
</p>
|
||||
<span class="hidden @2xl:block">
|
||||
<x-status-badge :status="$deploymentStatus" :type="$deploymentStatusType" />
|
||||
<x-status-badge :status="$deploymentStatus" :type="$deploymentStatusType" :spinning="$deployment->status === 'in_progress'" />
|
||||
</span>
|
||||
<p class="hidden truncate text-[12px] text-neutral-500 @2xl:block dark:text-fg-dim">
|
||||
{{ $deployment->created_at?->diffForHumans() ?? '-' }}
|
||||
</p>
|
||||
|
||||
<div class="mt-3 flex min-w-0 items-center gap-3 @2xl:hidden">
|
||||
<x-status-badge :status="$deploymentStatus" :type="$deploymentStatusType" />
|
||||
<x-status-badge :status="$deploymentStatus" :type="$deploymentStatusType" :spinning="$deployment->status === 'in_progress'" />
|
||||
<p class="min-w-0 truncate text-[11px] text-neutral-500 dark:text-fg-faint">
|
||||
{{ $environmentPath ?: '-' }}
|
||||
<span class="px-1 text-neutral-300 dark:text-white/15">·</span>
|
||||
@@ -141,14 +141,14 @@
|
||||
{{ $deployment->server_name ?: '-' }}
|
||||
</p>
|
||||
<span class="hidden @2xl:block">
|
||||
<x-status-badge :status="$deploymentStatus" :type="$deploymentStatusType" />
|
||||
<x-status-badge :status="$deploymentStatus" :type="$deploymentStatusType" :spinning="$deployment->status === 'in_progress'" />
|
||||
</span>
|
||||
<p class="hidden truncate text-[12px] text-neutral-500 @2xl:block dark:text-fg-dim">
|
||||
{{ $deployment->created_at?->diffForHumans() ?? '-' }}
|
||||
</p>
|
||||
|
||||
<div class="mt-3 flex min-w-0 items-center gap-3 @2xl:hidden">
|
||||
<x-status-badge :status="$deploymentStatus" :type="$deploymentStatusType" />
|
||||
<x-status-badge :status="$deploymentStatus" :type="$deploymentStatusType" :spinning="$deployment->status === 'in_progress'" />
|
||||
<p class="min-w-0 truncate text-[11px] text-neutral-500 dark:text-fg-faint">
|
||||
{{ $environmentPath ?: '-' }}
|
||||
<span class="px-1 text-neutral-300 dark:text-white/15">·</span>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<p class="truncate text-[13px] font-semibold text-black dark:text-fg">
|
||||
{{ $deployment->application_name }}
|
||||
</p>
|
||||
<x-status-badge :status="$deploymentStatus" :type="$deploymentStatusType"
|
||||
<x-status-badge :status="$deploymentStatus" :type="$deploymentStatusType" :spinning="$deployment->status === 'in_progress'"
|
||||
class="shrink-0" />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
@unless ($embedded)
|
||||
<span class="min-w-0">
|
||||
<x-status-badge as="button" :status="$statusLabel" :type="$statusType"
|
||||
:spinning="$deploymentStatus === 'in_progress'"
|
||||
class="table-filter-value table-filter-badge" title="Filter by {{ $statusLabel }}"
|
||||
wire:click="toggleDeploymentFilter('status:{{ $deploymentStatus }}')" />
|
||||
</span>
|
||||
@@ -336,6 +337,10 @@
|
||||
<span>{{ \Carbon\Carbon::parse(data_get($deployment, 'created_at'))->diffForHumans(syntax: \Carbon\CarbonInterface::DIFF_ABSOLUTE, short: true) }}</span>
|
||||
<x-loading compact x-cloak x-show="selecting === '{{ data_get($deployment, 'deployment_uuid') }}'"
|
||||
aria-label="Loading deployment logs..." />
|
||||
@if ($deploymentStatus === 'in_progress')
|
||||
<x-loading compact x-show="selecting !== '{{ data_get($deployment, 'deployment_uuid') }}'"
|
||||
aria-label="Deployment in progress" />
|
||||
@else
|
||||
<span x-show="selecting !== '{{ data_get($deployment, 'deployment_uuid') }}'" @class([
|
||||
'size-2 shrink-0 rounded-full',
|
||||
'bg-emerald-500' => $statusType === 'success',
|
||||
@@ -343,6 +348,7 @@
|
||||
'bg-red-500' => $statusType === 'error',
|
||||
'bg-neutral-400 dark:bg-neutral-500' => $statusType === 'neutral',
|
||||
])></span>
|
||||
@endif
|
||||
</a>
|
||||
@endif
|
||||
<a href="{{ $deploymentUrl }}" {!! $deploymentLinkAttributes !!} class="deployment-row-link absolute inset-0"
|
||||
|
||||
@@ -336,6 +336,9 @@
|
||||
</button>
|
||||
</div>
|
||||
<span class="flex shrink-0 items-center gap-1.5 text-xs font-medium text-neutral-600 dark:text-fg-dim">
|
||||
@if ($deploymentStatus === 'in_progress')
|
||||
<x-loading compact />
|
||||
@else
|
||||
<span @class([
|
||||
'size-2 shrink-0 rounded-full',
|
||||
'bg-emerald-500' => $deploymentStatusType === 'success',
|
||||
@@ -343,6 +346,7 @@
|
||||
'bg-red-500' => $deploymentStatusType === 'error',
|
||||
'bg-neutral-400 dark:bg-neutral-500' => $deploymentStatusType === 'neutral',
|
||||
])></span>
|
||||
@endif
|
||||
{{ $deploymentStatusLabel }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
:confirmWithText="false" :confirmWithPassword="false">
|
||||
<x-slot:trigger>
|
||||
<button type="button"
|
||||
class="button whitespace-nowrap text-error! hover:text-error! dark:text-error!">
|
||||
class="button button-error whitespace-nowrap">
|
||||
<span class="max-sm:hidden">Delete unused keys</span>
|
||||
<span class="sm:hidden">Delete unused</span>
|
||||
</button>
|
||||
|
||||
@@ -171,32 +171,8 @@
|
||||
:placeholder="config('constants.github_runner.image')"
|
||||
helper="Empty uses the latest official runner image, which Coolify pulls for every runner. Set a tag, for example ghcr.io/actions/actions-runner:2.337.0, to pin a version, or use a custom image based on the official one to add tools. GitHub stops sending jobs to runners that are more than 30 days old." />
|
||||
</div>
|
||||
<div x-cloak x-show="$wire.dockerMode === 'sysbox'" class="mt-4"
|
||||
x-effect="if ($wire.dockerMode === 'sysbox' && $wire.isSysboxInstalled === null) $wire.checkSysbox()">
|
||||
@if ($isSysboxInstalled === true)
|
||||
<x-callout type="success" title="Sysbox is installed">
|
||||
Jobs get a private Docker daemon in an unprivileged container.
|
||||
</x-callout>
|
||||
@elseif ($isSysboxInstalled === false)
|
||||
<x-callout type="warning" title="Sysbox is not installed">
|
||||
Isolated Docker needs the Sysbox runtime. Coolify installs Sysbox
|
||||
{{ config('constants.github_runner.sysbox.version') }} from the official package
|
||||
(Debian and Ubuntu, kernel 5.12 or newer). Docker reloads its configuration, and
|
||||
running containers are not restarted.
|
||||
@can('update', $server)
|
||||
<div class="mt-3">
|
||||
<x-forms.button type="button" wire:click="installSysbox"
|
||||
wire:loading.attr="disabled" wire:target="installSysbox">
|
||||
Install Sysbox
|
||||
</x-forms.button>
|
||||
</div>
|
||||
@endcan
|
||||
</x-callout>
|
||||
@else
|
||||
<x-callout type="info" title="Checking Sysbox">
|
||||
Coolify checks if Sysbox is installed on this server.
|
||||
</x-callout>
|
||||
@endif
|
||||
<div x-cloak x-show="$wire.dockerMode === 'sysbox'" class="mt-4">
|
||||
<livewire:server.sysbox-status :server="$server" />
|
||||
</div>
|
||||
</x-application.settings-section>
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
'Hostinger' => $hostingerVirtualMachineStatus,
|
||||
default => null,
|
||||
};
|
||||
$canRefreshProvider = $provider && $server->cloudProviderToken && ! $server->isTransferredAway();
|
||||
$providerStatusType = match (true) {
|
||||
in_array($providerStatus, ['running', 'active']) => 'success',
|
||||
in_array($providerStatus, ['starting', 'initializing', 'pending', 'new', 'initial', 'creating', 'unsuspending', 'recreating', 'restoring']) => 'warning',
|
||||
@@ -265,35 +266,14 @@
|
||||
|
||||
<x-application.settings-section id="server-overview-section" title="Server overview"
|
||||
helper="Provider state, operating system, and hardware details.">
|
||||
<x-slot:actions>
|
||||
@if ($provider)
|
||||
@if ($provider)
|
||||
<x-slot:badges>
|
||||
<x-status-badge :label="$provider . ($providerStatus ? ' · ' . ucfirst($providerStatus) : '')"
|
||||
:type="$providerStatusType" />
|
||||
@if ($provider === 'Hetzner')
|
||||
<x-forms.button type="button" class="size-8! px-0!"
|
||||
wire:click.prevent="checkHetznerServerStatus(true)"
|
||||
title="Refresh provider status">
|
||||
<x-reicon name="refresh" class="size-3.5" />
|
||||
</x-forms.button>
|
||||
@elseif ($provider === 'DigitalOcean')
|
||||
<x-forms.button type="button" class="size-8! px-0!"
|
||||
wire:click.prevent="checkDigitalOceanDropletStatus(true)"
|
||||
title="Refresh provider status">
|
||||
<x-reicon name="refresh" class="size-3.5" />
|
||||
</x-forms.button>
|
||||
@elseif ($provider === 'Vultr')
|
||||
<x-forms.button type="button" class="size-8! px-0!"
|
||||
wire:click.prevent="checkVultrInstanceStatus(true)"
|
||||
title="Refresh provider status">
|
||||
<x-reicon name="refresh" class="size-3.5" />
|
||||
</x-forms.button>
|
||||
@elseif ($provider === 'Hostinger')
|
||||
<x-forms.button type="button" class="size-8! px-0!"
|
||||
wire:click.prevent="checkHostingerVirtualMachineStatus(true)"
|
||||
title="Refresh provider status">
|
||||
<x-reicon name="refresh" class="size-3.5" />
|
||||
</x-forms.button>
|
||||
@endif
|
||||
</x-slot:badges>
|
||||
@endif
|
||||
<x-slot:actions>
|
||||
@if ($provider)
|
||||
@if ($server->cloudProviderToken && ! $server->isTransferredAway())
|
||||
@if ($provider === 'Hetzner' && !$server->isFunctional() && $hetznerServerStatus === 'off')
|
||||
<x-forms.button type="button" wire:click.prevent="startHetznerServer" isHighlighted
|
||||
@@ -314,8 +294,8 @@
|
||||
@endif
|
||||
@endif
|
||||
<x-forms.button type="button" wire:click="refreshServerMetadata"
|
||||
:disabled="! $server->isFunctional()"
|
||||
:tooltip="$server->isFunctional() ? null : 'Validate the connection first.'">
|
||||
:disabled="! $server->isFunctional() && ! $canRefreshProvider"
|
||||
:tooltip="$server->isFunctional() || $canRefreshProvider ? null : 'Validate the connection first.'">
|
||||
<x-reicon name="refresh" class="size-3.5" />
|
||||
{{ $server->server_metadata ? 'Refresh details' : 'Fetch server details' }}
|
||||
</x-forms.button>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<div>
|
||||
<x-callout type="info" title="Checking Sysbox">
|
||||
Coolify checks if Sysbox is installed on this server.
|
||||
</x-callout>
|
||||
</div>
|
||||
@@ -0,0 +1,22 @@
|
||||
<div>
|
||||
@if ($isInstalled)
|
||||
<x-callout type="success" title="Sysbox is installed">
|
||||
Jobs get a private Docker daemon in an unprivileged container.
|
||||
</x-callout>
|
||||
@else
|
||||
<x-callout type="warning" title="Sysbox is not installed">
|
||||
Isolated Docker needs the Sysbox runtime. Coolify installs Sysbox
|
||||
{{ config('constants.github_runner.sysbox.version') }} from the official package
|
||||
(Debian and Ubuntu, kernel 5.12 or newer). Docker reloads its configuration, and
|
||||
running containers are not restarted.
|
||||
@can('update', $server)
|
||||
<div class="mt-3">
|
||||
<x-forms.button type="button" wire:click="installSysbox"
|
||||
wire:loading.attr="disabled" wire:target="installSysbox">
|
||||
Install Sysbox
|
||||
</x-forms.button>
|
||||
</div>
|
||||
@endcan
|
||||
</x-callout>
|
||||
@endif
|
||||
</div>
|
||||
+1
-1
@@ -201,7 +201,7 @@ Route::group([
|
||||
|
||||
Route::get('/servers/{uuid}/registries', [ServerDockerRegistriesController::class, 'index'])->middleware(['api.ability:read:sensitive']);
|
||||
Route::post('/servers/{uuid}/registries', [ServerDockerRegistriesController::class, 'login'])->middleware(['api.ability:write', 'api.ability:deploy']);
|
||||
Route::post('/servers/{uuid}/registries/{registry}/check', [ServerDockerRegistriesController::class, 'check'])->middleware(['api.ability:write']);
|
||||
Route::post('/servers/{uuid}/registries/{registry}/check', [ServerDockerRegistriesController::class, 'check'])->middleware(['api.ability:write', 'api.ability:deploy']);
|
||||
Route::delete('/servers/{uuid}/registries/{registry}', [ServerDockerRegistriesController::class, 'logout'])->middleware(['api.ability:delete']);
|
||||
|
||||
Route::get('/servers/{uuid}/proxy', [ServerProxyController::class, 'show'])->middleware(['api.ability:read']);
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Jobs\ProvisionGithubRunnerJob;
|
||||
use App\Livewire\Server\GithubRunnerExecutions;
|
||||
use App\Livewire\Server\GithubRunners;
|
||||
use App\Livewire\Server\Show;
|
||||
use App\Livewire\Server\SysboxStatus;
|
||||
use App\Livewire\Source\Github\Change;
|
||||
use App\Livewire\Source\Github\Create;
|
||||
use App\Models\GithubApp;
|
||||
@@ -446,8 +447,7 @@ describe('isolated Docker with Sysbox', function () {
|
||||
->set('githubAppId', $this->githubApp->id)
|
||||
->set('dockerMode', 'sysbox')
|
||||
->call('submit')
|
||||
->assertHasErrors('dockerMode')
|
||||
->assertSet('isSysboxInstalled', false);
|
||||
->assertHasErrors('dockerMode');
|
||||
|
||||
expect(GithubRunnerConfig::count())->toBe(0);
|
||||
});
|
||||
@@ -469,29 +469,29 @@ describe('isolated Docker with Sysbox', function () {
|
||||
it('checks that Sysbox can mount its FUSE file system', function () {
|
||||
Process::fake(['*' => Process::result(output: '')]);
|
||||
|
||||
Livewire::actingAs($this->owner)
|
||||
->test(GithubRunners::class, ['server_uuid' => $this->server->uuid])
|
||||
->call('checkSysbox');
|
||||
Livewire::withoutLazyLoading()
|
||||
->actingAs($this->owner)
|
||||
->test(SysboxStatus::class, ['server' => $this->server]);
|
||||
|
||||
Process::assertRan(fn ($process) => str_contains(remoteProcessScript($process), 'test -x /usr/bin/fusermount3'));
|
||||
});
|
||||
|
||||
it('shows the install button when Sysbox is missing', function () {
|
||||
Process::fake(['*' => Process::result(output: '')]);
|
||||
GithubRunnerConfig::create(['server_id' => $this->server->id, 'github_app_id' => $this->githubApp->id, 'labels' => ['coolify']]);
|
||||
|
||||
Livewire::actingAs($this->owner)
|
||||
->test(GithubRunners::class, ['server_uuid' => $this->server->uuid])
|
||||
->call('checkSysbox')
|
||||
->assertSet('isSysboxInstalled', false)
|
||||
Livewire::withoutLazyLoading()
|
||||
->actingAs($this->owner)
|
||||
->test(SysboxStatus::class, ['server' => $this->server])
|
||||
->assertSet('isInstalled', false)
|
||||
->assertSee('Install Sysbox');
|
||||
});
|
||||
|
||||
it('stops before the installation on servers without apt', function () {
|
||||
Process::fake(['*' => Process::result(output: '')]);
|
||||
|
||||
Livewire::actingAs($this->owner)
|
||||
->test(GithubRunners::class, ['server_uuid' => $this->server->uuid])
|
||||
Livewire::withoutLazyLoading()
|
||||
->actingAs($this->owner)
|
||||
->test(SysboxStatus::class, ['server' => $this->server])
|
||||
->call('installSysbox')
|
||||
->assertNotDispatched('activityMonitor');
|
||||
|
||||
@@ -499,14 +499,15 @@ describe('isolated Docker with Sysbox', function () {
|
||||
});
|
||||
|
||||
it('does not let members install Sysbox', function () {
|
||||
Process::fake();
|
||||
Process::fake(['*' => Process::result(output: '')]);
|
||||
|
||||
Livewire::actingAs($this->member)
|
||||
->test(GithubRunners::class, ['server_uuid' => $this->server->uuid])
|
||||
Livewire::withoutLazyLoading()
|
||||
->actingAs($this->member)
|
||||
->test(SysboxStatus::class, ['server' => $this->server])
|
||||
->call('installSysbox')
|
||||
->assertForbidden();
|
||||
|
||||
Process::assertNothingRan();
|
||||
Process::assertDidntRun(fn ($process) => str_contains(remoteProcessScript($process), 'apt-get'));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -407,7 +407,7 @@ it('starts a stopped Hostinger VPS from the server page', function () {
|
||||
expect($server->fresh()->hostinger_virtual_machine_status)->toBe('starting');
|
||||
});
|
||||
|
||||
it('labels the Hostinger status refresh button on the server page', function () {
|
||||
it('refreshes the Hostinger status from the server details action before the server is validated', function () {
|
||||
$server = Server::factory()->create([
|
||||
'team_id' => $this->team->id,
|
||||
'private_key_id' => $this->privateKey->id,
|
||||
@@ -416,10 +416,20 @@ it('labels the Hostinger status refresh button on the server page', function ()
|
||||
'hostinger_virtual_machine_status' => 'running',
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'https://developers.hostinger.com/api/vps/v1/virtual-machines/17923' => Http::response([
|
||||
'id' => 17923,
|
||||
'state' => 'stopped',
|
||||
'ipv4' => [['address' => $server->ip]],
|
||||
]),
|
||||
]);
|
||||
|
||||
Livewire::test(Show::class, ['server_uuid' => $server->uuid])
|
||||
->assertSee('Hostinger · Running')
|
||||
->assertSee('Refresh provider status')
|
||||
->assertSeeHtml('wire:click.prevent="checkHostingerVirtualMachineStatus(true)"');
|
||||
->call('refreshServerMetadata')
|
||||
->assertSet('hostingerVirtualMachineStatus', 'stopped')
|
||||
->assertDispatched('info', 'Provider status refreshed. Validate the server connection to fetch server details.')
|
||||
->assertSee('Hostinger · Stopped');
|
||||
});
|
||||
|
||||
it('does not validate a stopped Hostinger VPS over SSH', function () {
|
||||
|
||||
Reference in new issue
Block a user