Skip to content

Commit 84e6db4

Browse files
authored
Merge pull request #4874 from coollabsio/next
v4.0.0-beta.383
2 parents 6062fa7 + f0c10b5 commit 84e6db4

File tree

15 files changed

+249
-29
lines changed

15 files changed

+249
-29
lines changed

app/Http/Controllers/Api/SecurityController.php

+25
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,31 @@ public function create_key(Request $request)
195195
if (! $request->description) {
196196
$request->offsetSet('description', 'Created by Coolify via API');
197197
}
198+
199+
$isPrivateKeyString = str_starts_with($request->private_key, '-----BEGIN');
200+
if (! $isPrivateKeyString) {
201+
try {
202+
$base64PrivateKey = base64_decode($request->private_key);
203+
$request->offsetSet('private_key', $base64PrivateKey);
204+
} catch (\Exception $e) {
205+
return response()->json([
206+
'message' => 'Invalid private key.',
207+
], 422);
208+
}
209+
}
210+
$isPrivateKeyValid = PrivateKey::validatePrivateKey($request->private_key);
211+
if (! $isPrivateKeyValid) {
212+
return response()->json([
213+
'message' => 'Invalid private key.',
214+
], 422);
215+
}
216+
$fingerPrint = PrivateKey::generateFingerprint($request->private_key);
217+
$isFingerPrintExists = PrivateKey::fingerprintExists($fingerPrint);
218+
if ($isFingerPrintExists) {
219+
return response()->json([
220+
'message' => 'Private key already exists.',
221+
], 422);
222+
}
198223
$key = PrivateKey::create([
199224
'team_id' => $teamId,
200225
'name' => $request->name,

app/Http/Controllers/Api/ServersController.php

+7-4
Original file line numberDiff line numberDiff line change
@@ -530,11 +530,11 @@ public function create_server(Request $request)
530530
'user' => $request->user,
531531
'private_key_id' => $privateKey->id,
532532
'team_id' => $teamId,
533-
'proxy' => [
534-
'type' => $proxyType,
535-
'status' => ProxyStatus::EXITED->value,
536-
],
537533
]);
534+
$server->proxy->set('type', $proxyType);
535+
$server->proxy->set('status', ProxyStatus::EXITED->value);
536+
$server->save();
537+
538538
$server->settings()->update([
539539
'is_build_server' => $request->is_build_server,
540540
]);
@@ -742,6 +742,9 @@ public function delete_server(Request $request)
742742
if ($server->definedResources()->count() > 0) {
743743
return response()->json(['message' => 'Server has resources, so you need to delete them before.'], 400);
744744
}
745+
if ($server->isLocalhost()) {
746+
return response()->json(['message' => 'Local server cannot be deleted.'], 400);
747+
}
745748
$server->delete();
746749
DeleteServer::dispatch($server);
747750

app/Livewire/Project/New/DockerImage.php

+7-8
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use App\Models\Project;
77
use App\Models\StandaloneDocker;
88
use App\Models\SwarmDocker;
9+
use App\Services\DockerImageParser;
910
use Livewire\Component;
1011
use Visus\Cuid2\Cuid2;
1112

@@ -28,12 +29,10 @@ public function submit()
2829
$this->validate([
2930
'dockerImage' => 'required',
3031
]);
31-
$image = str($this->dockerImage)->before(':');
32-
if (str($this->dockerImage)->contains(':')) {
33-
$tag = str($this->dockerImage)->after(':');
34-
} else {
35-
$tag = 'latest';
36-
}
32+
33+
$parser = new DockerImageParser;
34+
$parser->parse($this->dockerImage);
35+
3736
$destination_uuid = $this->query['destination'];
3837
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
3938
if (! $destination) {
@@ -53,8 +52,8 @@ public function submit()
5352
'git_branch' => 'main',
5453
'build_pack' => 'dockerimage',
5554
'ports_exposes' => 80,
56-
'docker_registry_image_name' => $image,
57-
'docker_registry_image_tag' => $tag,
55+
'docker_registry_image_name' => $parser->getFullImageNameWithoutTag(),
56+
'docker_registry_image_tag' => $parser->getTag(),
5857
'environment_id' => $environment->id,
5958
'destination_id' => $destination->id,
6059
'destination_type' => $destination_class,

app/Models/PrivateKey.php

+9-3
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ class PrivateKey extends BaseModel
4040
'private_key' => 'encrypted',
4141
];
4242

43+
protected $appends = ['public_key'];
44+
4345
protected static function booted()
4446
{
4547
static::saving(function ($key) {
@@ -64,6 +66,11 @@ protected static function booted()
6466
});
6567
}
6668

69+
public function getPublicKeyAttribute()
70+
{
71+
return self::extractPublicKeyFromPrivate($this->private_key) ?? 'Error loading private key';
72+
}
73+
6774
public function getPublicKey()
6875
{
6976
return self::extractPublicKeyFromPrivate($this->private_key) ?? 'Error loading private key';
@@ -208,15 +215,14 @@ public static function generateFingerprint($privateKey)
208215
{
209216
try {
210217
$key = PublicKeyLoader::load($privateKey);
211-
$publicKey = $key->getPublicKey();
212218

213-
return $publicKey->getFingerprint('sha256');
219+
return $key->getPublicKey()->getFingerprint('sha256');
214220
} catch (\Throwable $e) {
215221
return null;
216222
}
217223
}
218224

219-
private static function fingerprintExists($fingerprint, $excludeId = null)
225+
public static function fingerprintExists($fingerprint, $excludeId = null)
220226
{
221227
$query = self::query()
222228
->where('fingerprint', $fingerprint)

app/Services/DockerImageParser.php

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
namespace App\Services;
4+
5+
class DockerImageParser
6+
{
7+
private string $registryUrl = '';
8+
9+
private string $imageName = '';
10+
11+
private string $tag = 'latest';
12+
13+
public function parse(string $imageString): self
14+
{
15+
// First split by : to handle the tag, but be careful with registry ports
16+
$lastColon = strrpos($imageString, ':');
17+
$hasSlash = str_contains($imageString, '/');
18+
19+
// If the last colon appears after the last slash, it's a tag
20+
// Otherwise it might be a port in the registry URL
21+
if ($lastColon !== false && (! $hasSlash || $lastColon > strrpos($imageString, '/'))) {
22+
$mainPart = substr($imageString, 0, $lastColon);
23+
$this->tag = substr($imageString, $lastColon + 1);
24+
} else {
25+
$mainPart = $imageString;
26+
$this->tag = 'latest';
27+
}
28+
29+
// Split the main part by / to handle registry and image name
30+
$pathParts = explode('/', $mainPart);
31+
32+
// If we have more than one part and the first part contains a dot or colon
33+
// it's likely a registry URL
34+
if (count($pathParts) > 1 && (str_contains($pathParts[0], '.') || str_contains($pathParts[0], ':'))) {
35+
$this->registryUrl = array_shift($pathParts);
36+
$this->imageName = implode('/', $pathParts);
37+
} else {
38+
$this->imageName = $mainPart;
39+
}
40+
41+
return $this;
42+
}
43+
44+
public function getFullImageNameWithoutTag(): string
45+
{
46+
return $this->registryUrl.'/'.$this->imageName;
47+
}
48+
49+
public function getRegistryUrl(): string
50+
{
51+
return $this->registryUrl;
52+
}
53+
54+
public function getImageName(): string
55+
{
56+
return $this->imageName;
57+
}
58+
59+
public function getTag(): string
60+
{
61+
return $this->tag;
62+
}
63+
64+
public function toString(): string
65+
{
66+
$parts = [];
67+
if ($this->registryUrl) {
68+
$parts[] = $this->registryUrl;
69+
}
70+
$parts[] = $this->imageName;
71+
72+
return implode('/', $parts).':'.$this->tag;
73+
}
74+
}

bootstrap/helpers/shared.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -2005,7 +2005,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
20052005
projectName: $resource->project()->name,
20062006
resourceName: $resource->name,
20072007
type: 'service',
2008-
subType: $isDatabase ? 'database' : 'application',
2008+
subType: $isDatabase ? 'database' : 'application',
20092009
subId: $savedService->id,
20102010
subName: $savedService->name,
20112011
environment: $resource->environment->name,
@@ -2872,7 +2872,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
28722872
data_forget($service, 'volumes.*.is_directory');
28732873
data_forget($service, 'exclude_from_hc');
28742874
data_set($service, 'environment', $serviceVariables->toArray());
2875-
updateCompose($savedService);
2875+
updateCompose($service);
28762876

28772877
return $service;
28782878
});

config/constants.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
return [
44
'coolify' => [
5-
'version' => '4.0.0-beta.382',
5+
'version' => '4.0.0-beta.383',
66
'self_hosted' => env('SELF_HOSTED', true),
77
'autoupdate' => env('AUTOUPDATE'),
88
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),

other/nightly/install.sh

+5-5
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ set -e # Exit immediately if a command exits with a non-zero status
55
## $1 could be empty, so we need to disable this check
66
#set -u # Treat unset variables as an error and exit
77
set -o pipefail # Cause a pipeline to return the status of the last command that exited with a non-zero status
8-
CDN="https://cdn.coollabs.io/coolify"
8+
CDN="https://cdn.coollabs.io/coolify-nightly"
99
DATE=$(date +"%Y%m%d-%H%M%S")
1010

11-
VERSION="1.6"
11+
VERSION="1.7"
1212
DOCKER_VERSION="27.0"
1313
# TODO: Ask for a user
1414
CURRENT_USER=$USER
@@ -488,13 +488,13 @@ fi
488488

489489
# Add default root user credentials from environment variables
490490
if [ -n "$ROOT_USERNAME" ] && [ -n "$ROOT_USER_EMAIL" ] && [ -n "$ROOT_USER_PASSWORD" ]; then
491-
if ! grep -q "^ROOT_USERNAME=" "$ENV_FILE-$DATE"; then
491+
if grep -q "^ROOT_USERNAME=" "$ENV_FILE-$DATE"; then
492492
sed -i "s|^ROOT_USERNAME=.*|ROOT_USERNAME=$ROOT_USERNAME|" "$ENV_FILE-$DATE"
493493
fi
494-
if ! grep -q "^ROOT_USER_EMAIL=" "$ENV_FILE-$DATE"; then
494+
if grep -q "^ROOT_USER_EMAIL=" "$ENV_FILE-$DATE"; then
495495
sed -i "s|^ROOT_USER_EMAIL=.*|ROOT_USER_EMAIL=$ROOT_USER_EMAIL|" "$ENV_FILE-$DATE"
496496
fi
497-
if ! grep -q "^ROOT_USER_PASSWORD=" "$ENV_FILE-$DATE"; then
497+
if grep -q "^ROOT_USER_PASSWORD=" "$ENV_FILE-$DATE"; then
498498
sed -i "s|^ROOT_USER_PASSWORD=.*|ROOT_USER_PASSWORD=$ROOT_USER_PASSWORD|" "$ENV_FILE-$DATE"
499499
fi
500500
fi

resources/views/livewire/server/new/by-ip.blade.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class="font-bold underline" target="_blank"
3131
</div>
3232
<div class="">
3333
<h3 class="pt-6">Swarm <span class="text-xs text-neutral-500">(experimental)</span></h3>
34-
<div class="pb-4">Read the docs <a class='dark:text-white'
34+
<div class="pb-4">Read the docs <a class='underline dark:text-white'
3535
href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>.</div>
3636
@if ($is_swarm_worker || $is_build_server)
3737
<x-forms.checkbox disabled instantSave type="checkbox" id="is_swarm_manager"

scripts/install.sh

+19-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ set -o pipefail # Cause a pipeline to return the status of the last command that
88
CDN="https://cdn.coollabs.io/coolify"
99
DATE=$(date +"%Y%m%d-%H%M%S")
1010

11-
VERSION="1.6"
11+
VERSION="1.7"
1212
DOCKER_VERSION="27.0"
1313
# TODO: Ask for a user
1414
CURRENT_USER=$USER
@@ -22,6 +22,11 @@ echo -e "Welcome to Coolify Installer!"
2222
echo -e "This script will install everything for you. Sit back and relax."
2323
echo -e "Source code: https://github.com/coollabsio/coolify/blob/main/scripts/install.sh\n"
2424

25+
# Predefined root user
26+
ROOT_USERNAME=${ROOT_USERNAME:-}
27+
ROOT_USER_EMAIL=${ROOT_USER_EMAIL:-}
28+
ROOT_USER_PASSWORD=${ROOT_USER_PASSWORD:-}
29+
2530
TOTAL_SPACE=$(df -BG / | awk 'NR==2 {print $2}' | sed 's/G//')
2631
AVAILABLE_SPACE=$(df -BG / | awk 'NR==2 {print $4}' | sed 's/G//')
2732
REQUIRED_TOTAL_SPACE=30
@@ -481,6 +486,19 @@ else
481486
sed -i "s|^PUSHER_APP_SECRET=.*|PUSHER_APP_SECRET=$(openssl rand -hex 32)|" "$ENV_FILE-$DATE"
482487
fi
483488

489+
# Add default root user credentials from environment variables
490+
if [ -n "$ROOT_USERNAME" ] && [ -n "$ROOT_USER_EMAIL" ] && [ -n "$ROOT_USER_PASSWORD" ]; then
491+
if grep -q "^ROOT_USERNAME=" "$ENV_FILE-$DATE"; then
492+
sed -i "s|^ROOT_USERNAME=.*|ROOT_USERNAME=$ROOT_USERNAME|" "$ENV_FILE-$DATE"
493+
fi
494+
if grep -q "^ROOT_USER_EMAIL=" "$ENV_FILE-$DATE"; then
495+
sed -i "s|^ROOT_USER_EMAIL=.*|ROOT_USER_EMAIL=$ROOT_USER_EMAIL|" "$ENV_FILE-$DATE"
496+
fi
497+
if grep -q "^ROOT_USER_PASSWORD=" "$ENV_FILE-$DATE"; then
498+
sed -i "s|^ROOT_USER_PASSWORD=.*|ROOT_USER_PASSWORD=$ROOT_USER_PASSWORD|" "$ENV_FILE-$DATE"
499+
fi
500+
fi
501+
484502
# Merge .env and .env.production. New values will be added to .env
485503
echo -e "7. Propagating .env with new values - if necessary."
486504
awk -F '=' '!seen[$1]++' "$ENV_FILE-$DATE" /data/coolify/source/.env.production > $ENV_FILE

scripts/upgrade.sh

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ fi
3333
docker network create --attachable coolify 2>/dev/null
3434
# docker network create --attachable --driver=overlay coolify-overlay 2>/dev/null
3535

36+
echo "If you encounter any issues, please check the log file: $LOGFILE"
3637
if [ -f /data/coolify/source/docker-compose.custom.yml ]; then
3738
echo "docker-compose.custom.yml detected." >> $LOGFILE
3839
docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper:${LATEST_HELPER_VERSION} bash -c "LATEST_IMAGE=${LATEST_IMAGE} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml -f /data/coolify/source/docker-compose.custom.yml up -d --remove-orphans --force-recreate --wait --wait-timeout 60" >> $LOGFILE 2>&1

templates/compose/chatwoot.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ services:
8888
retries: 3
8989

9090
postgres:
91-
image: postgres:12
91+
image: pgvector/pgvector:pg12
9292
restart: always
9393
volumes:
9494
- postgres-data:/var/lib/postgresql/data

0 commit comments

Comments
 (0)