diff --git a/.github/workflows/chore-manage-stale-issues-and-prs.yml b/.github/workflows/chore-manage-stale-issues-and-prs.yml index 2afc996cb..58a2b7d7e 100644 --- a/.github/workflows/chore-manage-stale-issues-and-prs.yml +++ b/.github/workflows/chore-manage-stale-issues-and-prs.yml @@ -13,16 +13,16 @@ jobs: id: stale with: stale-issue-message: 'This issue will be automatically closed in a few days if no response is received. Please provide an update with the requested information.' - stale-pr-message: 'This pull request will be automatically closed in a few days if no response is received. Please update your PR or comment if you would like to continue working on it.' + stale-pr-message: 'This pull request requires attention. If no changes or response is received within the next few days, it will be automatically closed. Please update your PR or leave a comment with the requested information.' close-issue-message: 'This issue has been automatically closed due to inactivity.' - close-pr-message: 'This pull request has been automatically closed due to inactivity.' + close-pr-message: 'Thank you for your contribution. Due to inactivity, this PR was automatically closed. If you would like to continue working on this change in the future, feel free to reopen this PR or submit a new one.' days-before-stale: 14 days-before-close: 7 stale-issue-label: '⏱︎ Stale' stale-pr-label: '⏱︎ Stale' - only-labels: '💤 Waiting for feedback' + only-labels: '💤 Waiting for feedback, 💤 Waiting for changes' remove-stale-when-updated: true operations-per-run: 100 - labels-to-remove-when-unstale: '⏱︎ Stale, 💤 Waiting for feedback' + labels-to-remove-when-unstale: '⏱︎ Stale, 💤 Waiting for feedback, 💤 Waiting for changes' close-issue-reason: 'not_planned' exempt-all-milestones: false diff --git a/.github/workflows/chore-remove-labels-and-assignees-on-close.yml b/.github/workflows/chore-remove-labels-and-assignees-on-close.yml index ea097e328..a3c299b5e 100644 --- a/.github/workflows/chore-remove-labels-and-assignees-on-close.yml +++ b/.github/workflows/chore-remove-labels-and-assignees-on-close.yml @@ -19,8 +19,12 @@ jobs: script: | const { owner, repo } = context.repo; - async function processIssue(issueNumber) { + async function processIssue(issueNumber, isFromPR = false, prBaseBranch = null) { try { + if (isFromPR && prBaseBranch !== 'main') { + return; + } + const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({ owner, repo, @@ -59,19 +63,19 @@ jobs: } } - if (context.eventName === 'issues' || context.eventName === 'pull_request' || context.eventName === 'pull_request_target') { - const issue = context.payload.issue || context.payload.pull_request; - await processIssue(issue.number); + if (context.eventName === 'issues') { + await processIssue(context.payload.issue.number); } if (context.eventName === 'pull_request' || context.eventName === 'pull_request_target') { const pr = context.payload.pull_request; - if (pr.body) { + await processIssue(pr.number); + if (pr.merged && pr.base.ref === 'main' && pr.body) { const issueReferences = pr.body.match(/#(\d+)/g); if (issueReferences) { for (const reference of issueReferences) { const issueNumber = parseInt(reference.substring(1)); - await processIssue(issueNumber); + await processIssue(issueNumber, true, pr.base.ref); } } } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 80ec0614e..dba3676cf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,6 +4,8 @@ You can ask for guidance anytime on our [Discord server](https://coollabs.io/discord) in the `#contribute` channel. +To understand the tech stack, please refer to the [Tech Stack](TECH_STACK.md) document. + ## Table of Contents 1. [Setup Development Environment](#1-setup-development-environment) diff --git a/TECH_STACK.md b/TECH_STACK.md new file mode 100644 index 000000000..6a779eb29 --- /dev/null +++ b/TECH_STACK.md @@ -0,0 +1,29 @@ +# Coolify Technology Stack + +## Frontend + +- Livewire and Alpine.js +- Blade (PHP templating engine) +- Tailwind CSS +- Monaco Editor (Code editor component) +- XTerm.js (Terminal component) + +## Backend + +- Laravel 11 (PHP Framework) +- PostgreSQL 15 (Database) +- Redis 7 (Caching & Real-time features) +- Soketi (WebSocket Server) + +## DevOps & Infrastructure + +- Docker & Docker Compose +- Nginx (Web Server) +- S6 Overlay (Process Supervisor) +- GitHub Actions (CI/CD) + +## Languages + +- PHP 8.4 +- JavaScript +- Shell/Bash scripts diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index f8e28d216..ca57a8e95 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -294,7 +294,7 @@ class General extends Component public function resetDefaultLabels($manualReset = false) { try { - if ($this->application->settings->is_container_label_readonly_enabled && ! $manualReset) { + if (! $this->application->settings->is_container_label_readonly_enabled && ! $manualReset) { return; } $this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n"); @@ -324,6 +324,7 @@ class General extends Component } check_domain_usage(resource: $this->application); $this->application->fqdn = $domains->implode(','); + $this->resetDefaultLabels(false); } } diff --git a/app/Livewire/Project/Shared/ExecuteContainerCommand.php b/app/Livewire/Project/Shared/ExecuteContainerCommand.php index f993480c7..98289c536 100644 --- a/app/Livewire/Project/Shared/ExecuteContainerCommand.php +++ b/app/Livewire/Project/Shared/ExecuteContainerCommand.php @@ -146,11 +146,16 @@ class ExecuteContainerCommand extends Component private function checkShellAvailability(Server $server, string $container): bool { $escapedContainer = escapeshellarg($container); - $result = instant_remote_process([ - "docker exec {$escapedContainer} which bash || docker exec {$escapedContainer} which sh", - ], $server, false); + try { + instant_remote_process([ + "docker exec {$escapedContainer} bash -c 'exit 0' 2>/dev/null || ". + "docker exec {$escapedContainer} sh -c 'exit 0' 2>/dev/null", + ], $server); - return ! empty($result); + return true; + } catch (\Throwable) { + return false; + } } #[On('connectToServer')] diff --git a/app/Livewire/Project/Shared/Terminal.php b/app/Livewire/Project/Shared/Terminal.php index d8f101277..a3d1aa10f 100644 --- a/app/Livewire/Project/Shared/Terminal.php +++ b/app/Livewire/Project/Shared/Terminal.php @@ -9,6 +9,8 @@ use Livewire\Component; class Terminal extends Component { + public bool $hasShell = true; + public function getListeners() { $teamId = auth()->user()->currentTeam()->id; @@ -23,6 +25,21 @@ class Terminal extends Component $this->dispatch('reloadWindow'); } + private function checkShellAvailability(Server $server, string $container): bool + { + $escapedContainer = escapeshellarg($container); + try { + instant_remote_process([ + "docker exec {$escapedContainer} bash -c 'exit 0' 2>/dev/null || ". + "docker exec {$escapedContainer} sh -c 'exit 0' 2>/dev/null", + ], $server); + + return true; + } catch (\Throwable) { + return false; + } + } + #[On('send-terminal-command')] public function sendTerminalCommand($isContainer, $identifier, $serverUuid) { @@ -40,6 +57,12 @@ class Terminal extends Component return; } + // Check shell availability + $this->hasShell = $this->checkShellAvailability($server, $identifier); + if (! $this->hasShell) { + return; + } + // Escape the identifier for shell usage $escapedIdentifier = escapeshellarg($identifier); $command = SshMultiplexingHelper::generateSshCommand($server, "docker exec -it {$escapedIdentifier} sh -c 'PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'"); diff --git a/app/Models/EnvironmentVariable.php b/app/Models/EnvironmentVariable.php index 507ff0d7e..5f686de60 100644 --- a/app/Models/EnvironmentVariable.php +++ b/app/Models/EnvironmentVariable.php @@ -4,9 +4,7 @@ namespace App\Models; use App\Models\EnvironmentVariable as ModelsEnvironmentVariable; use Illuminate\Database\Eloquent\Casts\Attribute; -use Illuminate\Database\Eloquent\Model; use OpenApi\Attributes as OA; -use Visus\Cuid2\Cuid2; #[OA\Schema( description: 'Environment Variable model', @@ -30,7 +28,7 @@ use Visus\Cuid2\Cuid2; 'updated_at' => ['type' => 'string'], ] )] -class EnvironmentVariable extends Model +class EnvironmentVariable extends BaseModel { protected $guarded = []; @@ -49,12 +47,6 @@ class EnvironmentVariable extends Model protected static function booted() { - static::creating(function (Model $model) { - if (! $model->uuid) { - $model->uuid = (string) new Cuid2; - } - }); - static::created(function (EnvironmentVariable $environment_variable) { if ($environment_variable->resourceable_type === Application::class && ! $environment_variable->is_preview) { $found = ModelsEnvironmentVariable::where('key', $environment_variable->key) diff --git a/config/constants.php b/config/constants.php index c463824d0..da7bdc65e 100644 --- a/config/constants.php +++ b/config/constants.php @@ -2,8 +2,8 @@ return [ 'coolify' => [ - 'version' => '4.0.0-beta.388', - 'helper_version' => '1.0.5', + 'version' => '4.0.0-beta.389', + 'helper_version' => '1.0.6', 'realtime_version' => '1.0.5', 'self_hosted' => env('SELF_HOSTED', true), 'autoupdate' => env('AUTOUPDATE'), diff --git a/docker/coolify-helper/Dockerfile b/docker/coolify-helper/Dockerfile index bda538bca..7486acdcd 100644 --- a/docker/coolify-helper/Dockerfile +++ b/docker/coolify-helper/Dockerfile @@ -12,7 +12,7 @@ ARG DOCKER_BUILDX_VERSION=0.19.3 # https://github.com/buildpacks/pack/releases ARG PACK_VERSION=0.36.2 # https://github.com/railwayapp/nixpacks/releases -ARG NIXPACKS_VERSION=1.32.0 +ARG NIXPACKS_VERSION=1.29.0 # https://github.com/minio/mc/releases ARG MINIO_VERSION=RELEASE.2024-11-21T17-21-54Z diff --git a/public/svgs/flipt.svg b/public/svgs/flipt.svg new file mode 100644 index 000000000..8c8164f8f --- /dev/null +++ b/public/svgs/flipt.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/views/components/server/sidebar.blade.php b/resources/views/components/server/sidebar.blade.php index 092e306a5..ecc5785aa 100644 --- a/resources/views/components/server/sidebar.blade.php +++ b/resources/views/components/server/sidebar.blade.php @@ -22,7 +22,7 @@ Log Drains - Metrics @endif @if (!$server->isLocalhost()) diff --git a/resources/views/livewire/project/application/configuration.blade.php b/resources/views/livewire/project/application/configuration.blade.php index c6f27d3f3..7fcbbd691 100644 --- a/resources/views/livewire/project/application/configuration.blade.php +++ b/resources/views/livewire/project/application/configuration.blade.php @@ -74,8 +74,7 @@ href="{{ route('project.application.resource-operations', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}" wire:navigate>Resource Operations Metrics + href="{{ route('project.application.metrics', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}" >Metrics Tags diff --git a/resources/views/livewire/project/application/deployment/index.blade.php b/resources/views/livewire/project/application/deployment/index.blade.php index ec5cf0f98..b29b335c3 100644 --- a/resources/views/livewire/project/application/deployment/index.blade.php +++ b/resources/views/livewire/project/application/deployment/index.blade.php @@ -60,9 +60,10 @@ @if(data_get($deployment, 'status') !== 'queued')
Started: {{ formatDateInServerTimezone(data_get($deployment, 'created_at'), data_get($application, 'destination.server')) }} - @if($deployment->status !== 'in_progress' && $deployment->status !== 'cancelled-by-user' && $deployment->status !== 'failed') + @if($deployment->status !== 'in_progress' && $deployment->status !== 'cancelled-by-user')
Ended: {{ formatDateInServerTimezone(data_get($deployment, 'finished_at'), data_get($application, 'destination.server')) }}
Duration: {{ calculateDuration(data_get($deployment, 'created_at'), data_get($deployment, 'finished_at')) }} +
Finished {{ \Carbon\Carbon::parse(data_get($deployment, 'finished_at'))->diffForHumans() }} @elseif($deployment->status === 'in_progress')
Running for: {{ calculateDuration(data_get($deployment, 'created_at'), now()) }} @endif diff --git a/resources/views/livewire/project/database/backup-executions.blade.php b/resources/views/livewire/project/database/backup-executions.blade.php index 2f46a4657..d13f88e37 100644 --- a/resources/views/livewire/project/database/backup-executions.blade.php +++ b/resources/views/livewire/project/database/backup-executions.blade.php @@ -4,7 +4,7 @@

Executions

Cleanup Failed Backups
-
+
@forelse($executions as $execution)
Ended: {{ formatDateInServerTimezone(data_get($execution, 'finished_at'), $this->server()) }}
Duration: {{ calculateDuration(data_get($execution, 'created_at'), data_get($execution, 'finished_at')) }} +
Finished {{ \Carbon\Carbon::parse(data_get($execution, 'finished_at'))->diffForHumans() }} @endif
diff --git a/resources/views/livewire/project/database/configuration.blade.php b/resources/views/livewire/project/database/configuration.blade.php index d3649f94a..cce8a5a20 100644 --- a/resources/views/livewire/project/database/configuration.blade.php +++ b/resources/views/livewire/project/database/configuration.blade.php @@ -32,8 +32,7 @@ href="{{ route('project.database.resource-operations', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}" wire:navigate>Resource Operations Metrics + href="{{ route('project.database.metrics', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Metrics Tags diff --git a/resources/views/livewire/project/shared/metrics.blade.php b/resources/views/livewire/project/shared/metrics.blade.php index cfe83ded6..645de8331 100644 --- a/resources/views/livewire/project/shared/metrics.blade.php +++ b/resources/views/livewire/project/shared/metrics.blade.php @@ -8,7 +8,7 @@ @elseif(!$resource->destination->server->isMetricsEnabled())
Metrics are only available for servers with Sentinel & Metrics enabled!
Go to Server settings to + wire:navigate href="{{ route('server.show', $resource->destination->server->uuid) }}">Server settings to enable it.
@else diff --git a/resources/views/livewire/project/shared/scheduled-task/executions.blade.php b/resources/views/livewire/project/shared/scheduled-task/executions.blade.php index 63be05a2d..13e8000f1 100644 --- a/resources/views/livewire/project/shared/scheduled-task/executions.blade.php +++ b/resources/views/livewire/project/shared/scheduled-task/executions.blade.php @@ -1,4 +1,4 @@ -
-
-
- - -
+ @if(!$hasShell) +
+
+
+ + + +
+

Terminal Not Available

+

No shell (bash/sh) is available in this container. Please ensure either bash or sh is installed to use the terminal.

+
+
+
+
+ @else +
+
+ + +
+ @endif @script