@@ -47,7 +47,6 @@ class StartDatabaseProxy
|
|||||||
if ($isSSLEnabled) {
|
if ($isSSLEnabled) {
|
||||||
$internalPort = match ($databaseType) {
|
$internalPort = match ($databaseType) {
|
||||||
'standalone-redis', 'standalone-keydb', 'standalone-dragonfly' => 6380,
|
'standalone-redis', 'standalone-keydb', 'standalone-dragonfly' => 6380,
|
||||||
default => throw new \Exception("Unsupported database type: $databaseType"),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,26 +7,270 @@ use Illuminate\Support\Facades\Redis;
|
|||||||
|
|
||||||
class CleanupRedis extends Command
|
class CleanupRedis extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'cleanup:redis';
|
protected $signature = 'cleanup:redis {--dry-run : Show what would be deleted without actually deleting} {--skip-overlapping : Skip overlapping queue cleanup}';
|
||||||
|
|
||||||
protected $description = 'Cleanup Redis';
|
protected $description = 'Cleanup Redis (Horizon jobs, metrics, overlapping queues, and related data)';
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$redis = Redis::connection('horizon');
|
$redis = Redis::connection('horizon');
|
||||||
$keys = $redis->keys('*');
|
|
||||||
$prefix = config('horizon.prefix');
|
$prefix = config('horizon.prefix');
|
||||||
|
$dryRun = $this->option('dry-run');
|
||||||
|
$skipOverlapping = $this->option('skip-overlapping');
|
||||||
|
|
||||||
|
if ($dryRun) {
|
||||||
|
$this->info('DRY RUN MODE - No data will be deleted');
|
||||||
|
}
|
||||||
|
|
||||||
|
$deletedCount = 0;
|
||||||
|
$totalKeys = 0;
|
||||||
|
|
||||||
|
// Get all keys with the horizon prefix
|
||||||
|
$keys = $redis->keys('*');
|
||||||
|
$totalKeys = count($keys);
|
||||||
|
|
||||||
|
$this->info("Scanning {$totalKeys} keys for cleanup...");
|
||||||
|
|
||||||
foreach ($keys as $key) {
|
foreach ($keys as $key) {
|
||||||
$keyWithoutPrefix = str_replace($prefix, '', $key);
|
$keyWithoutPrefix = str_replace($prefix, '', $key);
|
||||||
$type = $redis->command('type', [$keyWithoutPrefix]);
|
$type = $redis->command('type', [$keyWithoutPrefix]);
|
||||||
|
|
||||||
|
// Handle hash-type keys (individual jobs)
|
||||||
if ($type === 5) {
|
if ($type === 5) {
|
||||||
$data = $redis->command('hgetall', [$keyWithoutPrefix]);
|
if ($this->shouldDeleteHashKey($redis, $keyWithoutPrefix, $dryRun)) {
|
||||||
$status = data_get($data, 'status');
|
$deletedCount++;
|
||||||
if ($status === 'completed') {
|
}
|
||||||
$redis->command('del', [$keyWithoutPrefix]);
|
}
|
||||||
|
// Handle other key types (metrics, lists, etc.)
|
||||||
|
else {
|
||||||
|
if ($this->shouldDeleteOtherKey($redis, $keyWithoutPrefix, $key, $dryRun)) {
|
||||||
|
$deletedCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean up overlapping queues if not skipped
|
||||||
|
if (! $skipOverlapping) {
|
||||||
|
$this->info('Cleaning up overlapping queues...');
|
||||||
|
$overlappingCleaned = $this->cleanupOverlappingQueues($redis, $prefix, $dryRun);
|
||||||
|
$deletedCount += $overlappingCleaned;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dryRun) {
|
||||||
|
$this->info("DRY RUN: Would delete {$deletedCount} out of {$totalKeys} keys");
|
||||||
|
} else {
|
||||||
|
$this->info("Deleted {$deletedCount} out of {$totalKeys} keys");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function shouldDeleteHashKey($redis, $keyWithoutPrefix, $dryRun)
|
||||||
|
{
|
||||||
|
$data = $redis->command('hgetall', [$keyWithoutPrefix]);
|
||||||
|
$status = data_get($data, 'status');
|
||||||
|
|
||||||
|
// Delete completed and failed jobs
|
||||||
|
if (in_array($status, ['completed', 'failed'])) {
|
||||||
|
if ($dryRun) {
|
||||||
|
$this->line("Would delete job: {$keyWithoutPrefix} (status: {$status})");
|
||||||
|
} else {
|
||||||
|
$redis->command('del', [$keyWithoutPrefix]);
|
||||||
|
$this->line("Deleted job: {$keyWithoutPrefix} (status: {$status})");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function shouldDeleteOtherKey($redis, $keyWithoutPrefix, $fullKey, $dryRun)
|
||||||
|
{
|
||||||
|
// Clean up various Horizon data structures
|
||||||
|
$patterns = [
|
||||||
|
'recent_jobs' => 'Recent jobs list',
|
||||||
|
'failed_jobs' => 'Failed jobs list',
|
||||||
|
'completed_jobs' => 'Completed jobs list',
|
||||||
|
'job_classes' => 'Job classes metrics',
|
||||||
|
'queues' => 'Queue metrics',
|
||||||
|
'processes' => 'Process metrics',
|
||||||
|
'supervisors' => 'Supervisor data',
|
||||||
|
'metrics' => 'General metrics',
|
||||||
|
'workload' => 'Workload data',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($patterns as $pattern => $description) {
|
||||||
|
if (str_contains($keyWithoutPrefix, $pattern)) {
|
||||||
|
if ($dryRun) {
|
||||||
|
$this->line("Would delete {$description}: {$keyWithoutPrefix}");
|
||||||
|
} else {
|
||||||
|
$redis->command('del', [$keyWithoutPrefix]);
|
||||||
|
$this->line("Deleted {$description}: {$keyWithoutPrefix}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up old timestamped data (older than 7 days)
|
||||||
|
if (preg_match('/(\d{10})/', $keyWithoutPrefix, $matches)) {
|
||||||
|
$timestamp = (int) $matches[1];
|
||||||
|
$weekAgo = now()->subDays(7)->timestamp;
|
||||||
|
|
||||||
|
if ($timestamp < $weekAgo) {
|
||||||
|
if ($dryRun) {
|
||||||
|
$this->line("Would delete old timestamped data: {$keyWithoutPrefix}");
|
||||||
|
} else {
|
||||||
|
$redis->command('del', [$keyWithoutPrefix]);
|
||||||
|
$this->line("Deleted old timestamped data: {$keyWithoutPrefix}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cleanupOverlappingQueues($redis, $prefix, $dryRun)
|
||||||
|
{
|
||||||
|
$cleanedCount = 0;
|
||||||
|
$queueKeys = [];
|
||||||
|
|
||||||
|
// Find all queue-related keys
|
||||||
|
$allKeys = $redis->keys('*');
|
||||||
|
foreach ($allKeys as $key) {
|
||||||
|
$keyWithoutPrefix = str_replace($prefix, '', $key);
|
||||||
|
if (str_contains($keyWithoutPrefix, 'queue:') || preg_match('/queues?[:\-]/', $keyWithoutPrefix)) {
|
||||||
|
$queueKeys[] = $keyWithoutPrefix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('Found '.count($queueKeys).' queue-related keys');
|
||||||
|
|
||||||
|
// Group queues by name pattern to find duplicates
|
||||||
|
$queueGroups = [];
|
||||||
|
foreach ($queueKeys as $queueKey) {
|
||||||
|
// Extract queue name (remove timestamps, suffixes)
|
||||||
|
$baseName = preg_replace('/[:\-]\d+$/', '', $queueKey);
|
||||||
|
$baseName = preg_replace('/[:\-](pending|reserved|delayed|processing)$/', '', $baseName);
|
||||||
|
|
||||||
|
if (! isset($queueGroups[$baseName])) {
|
||||||
|
$queueGroups[$baseName] = [];
|
||||||
|
}
|
||||||
|
$queueGroups[$baseName][] = $queueKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process each group for overlaps
|
||||||
|
foreach ($queueGroups as $baseName => $keys) {
|
||||||
|
if (count($keys) > 1) {
|
||||||
|
$cleanedCount += $this->deduplicateQueueGroup($redis, $baseName, $keys, $dryRun);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check for duplicate jobs within individual queues
|
||||||
|
foreach ($keys as $queueKey) {
|
||||||
|
$cleanedCount += $this->deduplicateQueueContents($redis, $queueKey, $dryRun);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $cleanedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deduplicateQueueGroup($redis, $baseName, $keys, $dryRun)
|
||||||
|
{
|
||||||
|
$cleanedCount = 0;
|
||||||
|
$this->line("Processing queue group: {$baseName} (".count($keys).' keys)');
|
||||||
|
|
||||||
|
// Sort keys to keep the most recent one
|
||||||
|
usort($keys, function ($a, $b) {
|
||||||
|
// Prefer keys without timestamps (they're usually the main queue)
|
||||||
|
$aHasTimestamp = preg_match('/\d{10}/', $a);
|
||||||
|
$bHasTimestamp = preg_match('/\d{10}/', $b);
|
||||||
|
|
||||||
|
if ($aHasTimestamp && ! $bHasTimestamp) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (! $aHasTimestamp && $bHasTimestamp) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If both have timestamps, prefer the newer one
|
||||||
|
if ($aHasTimestamp && $bHasTimestamp) {
|
||||||
|
preg_match('/(\d{10})/', $a, $aMatches);
|
||||||
|
preg_match('/(\d{10})/', $b, $bMatches);
|
||||||
|
|
||||||
|
return ($bMatches[1] ?? 0) <=> ($aMatches[1] ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return strcmp($a, $b);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Keep the first (preferred) key, remove others that are empty or redundant
|
||||||
|
$keepKey = array_shift($keys);
|
||||||
|
|
||||||
|
foreach ($keys as $redundantKey) {
|
||||||
|
$type = $redis->command('type', [$redundantKey]);
|
||||||
|
$shouldDelete = false;
|
||||||
|
|
||||||
|
if ($type === 1) { // LIST type
|
||||||
|
$length = $redis->command('llen', [$redundantKey]);
|
||||||
|
if ($length == 0) {
|
||||||
|
$shouldDelete = true;
|
||||||
|
}
|
||||||
|
} elseif ($type === 3) { // SET type
|
||||||
|
$count = $redis->command('scard', [$redundantKey]);
|
||||||
|
if ($count == 0) {
|
||||||
|
$shouldDelete = true;
|
||||||
|
}
|
||||||
|
} elseif ($type === 4) { // ZSET type
|
||||||
|
$count = $redis->command('zcard', [$redundantKey]);
|
||||||
|
if ($count == 0) {
|
||||||
|
$shouldDelete = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($shouldDelete) {
|
||||||
|
if ($dryRun) {
|
||||||
|
$this->line(" Would delete empty queue: {$redundantKey}");
|
||||||
|
} else {
|
||||||
|
$redis->command('del', [$redundantKey]);
|
||||||
|
$this->line(" Deleted empty queue: {$redundantKey}");
|
||||||
|
}
|
||||||
|
$cleanedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $cleanedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deduplicateQueueContents($redis, $queueKey, $dryRun)
|
||||||
|
{
|
||||||
|
$cleanedCount = 0;
|
||||||
|
$type = $redis->command('type', [$queueKey]);
|
||||||
|
|
||||||
|
if ($type === 1) { // LIST type - common for job queues
|
||||||
|
$length = $redis->command('llen', [$queueKey]);
|
||||||
|
if ($length > 1) {
|
||||||
|
$items = $redis->command('lrange', [$queueKey, 0, -1]);
|
||||||
|
$uniqueItems = array_unique($items);
|
||||||
|
|
||||||
|
if (count($uniqueItems) < count($items)) {
|
||||||
|
$duplicates = count($items) - count($uniqueItems);
|
||||||
|
|
||||||
|
if ($dryRun) {
|
||||||
|
$this->line(" Would remove {$duplicates} duplicate jobs from queue: {$queueKey}");
|
||||||
|
} else {
|
||||||
|
// Rebuild the list with unique items
|
||||||
|
$redis->command('del', [$queueKey]);
|
||||||
|
foreach (array_reverse($uniqueItems) as $item) {
|
||||||
|
$redis->command('lpush', [$queueKey, $item]);
|
||||||
|
}
|
||||||
|
$this->line(" Removed {$duplicates} duplicate jobs from queue: {$queueKey}");
|
||||||
|
}
|
||||||
|
$cleanedCount += $duplicates;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $cleanedCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -36,24 +36,20 @@ class Init extends Command
|
|||||||
|
|
||||||
$this->servers = Server::all();
|
$this->servers = Server::all();
|
||||||
if (! isCloud()) {
|
if (! isCloud()) {
|
||||||
$this->send_alive_signal();
|
$this->sendAliveSignal();
|
||||||
get_public_ips();
|
get_public_ips();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backward compatibility
|
// Backward compatibility
|
||||||
$this->replace_slash_in_environment_name();
|
$this->replaceSlashInEnvironmentName();
|
||||||
$this->restore_coolify_db_backup();
|
$this->restoreCoolifyDbBackup();
|
||||||
$this->update_user_emails();
|
$this->updateUserEmails();
|
||||||
//
|
//
|
||||||
$this->update_traefik_labels();
|
$this->updateTraefikLabels();
|
||||||
if (! isCloud() || $this->option('force-cloud')) {
|
if (! isCloud() || $this->option('force-cloud')) {
|
||||||
$this->cleanup_unused_network_from_coolify_proxy();
|
$this->cleanupUnusedNetworkFromCoolifyProxy();
|
||||||
}
|
|
||||||
if (isCloud()) {
|
|
||||||
$this->cleanup_unnecessary_dynamic_proxy_configuration();
|
|
||||||
} else {
|
|
||||||
$this->cleanup_in_progress_application_deployments();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->call('cleanup:redis');
|
$this->call('cleanup:redis');
|
||||||
|
|
||||||
$this->call('cleanup:stucked-resources');
|
$this->call('cleanup:stucked-resources');
|
||||||
@@ -66,33 +62,35 @@ class Init extends Command
|
|||||||
|
|
||||||
if (isCloud()) {
|
if (isCloud()) {
|
||||||
try {
|
try {
|
||||||
|
$this->cleanupUnnecessaryDynamicProxyConfiguration();
|
||||||
$this->pullTemplatesFromCDN();
|
$this->pullTemplatesFromCDN();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
echo "Could not pull templates from CDN: {$e->getMessage()}\n";
|
echo "Could not pull templates from CDN: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! isCloud()) {
|
try {
|
||||||
try {
|
$this->cleanupInProgressApplicationDeployments();
|
||||||
$this->pullTemplatesFromCDN();
|
$this->pullTemplatesFromCDN();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
echo "Could not pull templates from CDN: {$e->getMessage()}\n";
|
echo "Could not pull templates from CDN: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
$localhost = $this->servers->where('id', 0)->first();
|
$localhost = $this->servers->where('id', 0)->first();
|
||||||
$localhost->setupDynamicProxyConfiguration();
|
$localhost->setupDynamicProxyConfiguration();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
|
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
$settings = instanceSettings();
|
$settings = instanceSettings();
|
||||||
if (! is_null(config('constants.coolify.autoupdate', null))) {
|
if (! is_null(config('constants.coolify.autoupdate', null))) {
|
||||||
if (config('constants.coolify.autoupdate') == true) {
|
if (config('constants.coolify.autoupdate') == true) {
|
||||||
echo "Enabling auto-update\n";
|
echo "Enabling auto-update\n";
|
||||||
$settings->update(['is_auto_update_enabled' => true]);
|
$settings->update(['is_auto_update_enabled' => true]);
|
||||||
} else {
|
} else {
|
||||||
echo "Disabling auto-update\n";
|
echo "Disabling auto-update\n";
|
||||||
$settings->update(['is_auto_update_enabled' => false]);
|
$settings->update(['is_auto_update_enabled' => false]);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,7 +115,7 @@ class Init extends Command
|
|||||||
Artisan::call('optimize');
|
Artisan::call('optimize');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function update_user_emails()
|
private function updateUserEmails()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
User::whereRaw('email ~ \'[A-Z]\'')->get()->each(function (User $user) {
|
User::whereRaw('email ~ \'[A-Z]\'')->get()->each(function (User $user) {
|
||||||
@@ -128,7 +126,7 @@ class Init extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function update_traefik_labels()
|
private function updateTraefikLabels()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
Server::where('proxy->type', 'TRAEFIK_V2')->update(['proxy->type' => 'TRAEFIK']);
|
Server::where('proxy->type', 'TRAEFIK_V2')->update(['proxy->type' => 'TRAEFIK']);
|
||||||
@@ -137,7 +135,7 @@ class Init extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function cleanup_unnecessary_dynamic_proxy_configuration()
|
private function cleanupUnnecessaryDynamicProxyConfiguration()
|
||||||
{
|
{
|
||||||
foreach ($this->servers as $server) {
|
foreach ($this->servers as $server) {
|
||||||
try {
|
try {
|
||||||
@@ -158,7 +156,7 @@ class Init extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function cleanup_unused_network_from_coolify_proxy()
|
private function cleanupUnusedNetworkFromCoolifyProxy()
|
||||||
{
|
{
|
||||||
foreach ($this->servers as $server) {
|
foreach ($this->servers as $server) {
|
||||||
if (! $server->isFunctional()) {
|
if (! $server->isFunctional()) {
|
||||||
@@ -197,7 +195,7 @@ class Init extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function restore_coolify_db_backup()
|
private function restoreCoolifyDbBackup()
|
||||||
{
|
{
|
||||||
if (version_compare('4.0.0-beta.179', config('constants.coolify.version'), '<=')) {
|
if (version_compare('4.0.0-beta.179', config('constants.coolify.version'), '<=')) {
|
||||||
try {
|
try {
|
||||||
@@ -223,7 +221,7 @@ class Init extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function send_alive_signal()
|
private function sendAliveSignal()
|
||||||
{
|
{
|
||||||
$id = config('app.id');
|
$id = config('app.id');
|
||||||
$version = config('constants.coolify.version');
|
$version = config('constants.coolify.version');
|
||||||
@@ -241,7 +239,7 @@ class Init extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function cleanup_in_progress_application_deployments()
|
private function cleanupInProgressApplicationDeployments()
|
||||||
{
|
{
|
||||||
// Cleanup any failed deployments
|
// Cleanup any failed deployments
|
||||||
try {
|
try {
|
||||||
@@ -258,7 +256,7 @@ class Init extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function replace_slash_in_environment_name()
|
private function replaceSlashInEnvironmentName()
|
||||||
{
|
{
|
||||||
if (version_compare('4.0.0-beta.298', config('constants.coolify.version'), '<=')) {
|
if (version_compare('4.0.0-beta.298', config('constants.coolify.version'), '<=')) {
|
||||||
$environments = Environment::all();
|
$environments = Environment::all();
|
||||||
|
@@ -30,6 +30,7 @@ use Illuminate\Support\Collection;
|
|||||||
use Illuminate\Support\Sleep;
|
use Illuminate\Support\Sleep;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
use Spatie\Url\Url;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
@@ -471,7 +472,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
} else {
|
} else {
|
||||||
$composeFile = $this->application->parse(pull_request_id: $this->pull_request_id, preview_id: data_get($this->preview, 'id'));
|
$composeFile = $this->application->parse(pull_request_id: $this->pull_request_id, preview_id: data_get($this->preview, 'id'));
|
||||||
$this->save_environment_variables();
|
$this->save_environment_variables();
|
||||||
if (! is_null($this->env_filename)) {
|
if (filled($this->env_filename)) {
|
||||||
$services = collect(data_get($composeFile, 'services', []));
|
$services = collect(data_get($composeFile, 'services', []));
|
||||||
$services = $services->map(function ($service, $name) {
|
$services = $services->map(function ($service, $name) {
|
||||||
$service['env_file'] = [$this->env_filename];
|
$service['env_file'] = [$this->env_filename];
|
||||||
@@ -480,7 +481,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
});
|
});
|
||||||
$composeFile['services'] = $services->toArray();
|
$composeFile['services'] = $services->toArray();
|
||||||
}
|
}
|
||||||
if (is_null($composeFile)) {
|
if (empty($composeFile)) {
|
||||||
$this->application_deployment_queue->addLogEntry('Failed to parse docker-compose file.');
|
$this->application_deployment_queue->addLogEntry('Failed to parse docker-compose file.');
|
||||||
$this->fail('Failed to parse docker-compose file.');
|
$this->fail('Failed to parse docker-compose file.');
|
||||||
|
|
||||||
@@ -887,10 +888,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
private function save_environment_variables()
|
private function save_environment_variables()
|
||||||
{
|
{
|
||||||
$envs = collect([]);
|
$envs = collect([]);
|
||||||
$local_branch = $this->branch;
|
|
||||||
if ($this->pull_request_id !== 0) {
|
|
||||||
$local_branch = "pull/{$this->pull_request_id}/head";
|
|
||||||
}
|
|
||||||
$sort = $this->application->settings->is_env_sorting_enabled;
|
$sort = $this->application->settings->is_env_sorting_enabled;
|
||||||
if ($sort) {
|
if ($sort) {
|
||||||
$sorted_environment_variables = $this->application->environment_variables->sortBy('key');
|
$sorted_environment_variables = $this->application->environment_variables->sortBy('key');
|
||||||
@@ -899,6 +896,14 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$sorted_environment_variables = $this->application->environment_variables->sortBy('id');
|
$sorted_environment_variables = $this->application->environment_variables->sortBy('id');
|
||||||
$sorted_environment_variables_preview = $this->application->environment_variables_preview->sortBy('id');
|
$sorted_environment_variables_preview = $this->application->environment_variables_preview->sortBy('id');
|
||||||
}
|
}
|
||||||
|
if ($this->build_pack === 'dockercompose') {
|
||||||
|
$sorted_environment_variables = $sorted_environment_variables->filter(function ($env) {
|
||||||
|
return ! str($env->key)->startsWith('SERVICE_FQDN_') && ! str($env->key)->startsWith('SERVICE_URL_');
|
||||||
|
});
|
||||||
|
$sorted_environment_variables_preview = $sorted_environment_variables_preview->filter(function ($env) {
|
||||||
|
return ! str($env->key)->startsWith('SERVICE_FQDN_') && ! str($env->key)->startsWith('SERVICE_URL_');
|
||||||
|
});
|
||||||
|
}
|
||||||
$ports = $this->application->main_port();
|
$ports = $this->application->main_port();
|
||||||
$coolify_envs = $this->generate_coolify_env_variables();
|
$coolify_envs = $this->generate_coolify_env_variables();
|
||||||
$coolify_envs->each(function ($item, $key) use ($envs) {
|
$coolify_envs->each(function ($item, $key) use ($envs) {
|
||||||
@@ -908,17 +913,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->env_filename = '.env';
|
$this->env_filename = '.env';
|
||||||
|
|
||||||
foreach ($sorted_environment_variables as $env) {
|
foreach ($sorted_environment_variables as $env) {
|
||||||
$real_value = $env->real_value;
|
$envs->push($env->key.'='.$env->real_value);
|
||||||
if ($env->version === '4.0.0-beta.239') {
|
|
||||||
$real_value = $env->real_value;
|
|
||||||
} else {
|
|
||||||
if ($env->is_literal || $env->is_multiline) {
|
|
||||||
$real_value = '\''.$real_value.'\'';
|
|
||||||
} else {
|
|
||||||
$real_value = escapeEnvVariables($env->real_value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$envs->push($env->key.'='.$real_value);
|
|
||||||
}
|
}
|
||||||
// Add PORT if not exists, use the first port as default
|
// Add PORT if not exists, use the first port as default
|
||||||
if ($this->build_pack !== 'dockercompose') {
|
if ($this->build_pack !== 'dockercompose') {
|
||||||
@@ -930,20 +925,28 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if ($this->application->environment_variables->where('key', 'HOST')->isEmpty()) {
|
if ($this->application->environment_variables->where('key', 'HOST')->isEmpty()) {
|
||||||
$envs->push('HOST=0.0.0.0');
|
$envs->push('HOST=0.0.0.0');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->build_pack === 'dockercompose') {
|
||||||
|
$domains = collect(json_decode($this->application->docker_compose_domains)) ?? collect([]);
|
||||||
|
|
||||||
|
// Generate SERVICE_FQDN & SERVICE_URL for dockercompose
|
||||||
|
foreach ($domains as $forServiceName => $domain) {
|
||||||
|
$parsedDomain = data_get($domain, 'domain');
|
||||||
|
if (filled($parsedDomain)) {
|
||||||
|
$parsedDomain = str($parsedDomain)->explode(',')->first();
|
||||||
|
$coolifyUrl = Url::fromString($parsedDomain);
|
||||||
|
$coolifyScheme = $coolifyUrl->getScheme();
|
||||||
|
$coolifyFqdn = $coolifyUrl->getHost();
|
||||||
|
$coolifyUrl = $coolifyUrl->withScheme($coolifyScheme)->withHost($coolifyFqdn)->withPort(null);
|
||||||
|
$envs->push('SERVICE_URL_'.str($forServiceName)->upper().'='.$coolifyUrl->__toString());
|
||||||
|
$envs->push('SERVICE_FQDN_'.str($forServiceName)->upper().'='.$coolifyFqdn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$this->env_filename = ".env-pr-$this->pull_request_id";
|
$this->env_filename = ".env-pr-$this->pull_request_id";
|
||||||
foreach ($sorted_environment_variables_preview as $env) {
|
foreach ($sorted_environment_variables_preview as $env) {
|
||||||
$real_value = $env->real_value;
|
$envs->push($env->key.'='.$env->real_value);
|
||||||
if ($env->version === '4.0.0-beta.239') {
|
|
||||||
$real_value = $env->real_value;
|
|
||||||
} else {
|
|
||||||
if ($env->is_literal || $env->is_multiline) {
|
|
||||||
$real_value = '\''.$real_value.'\'';
|
|
||||||
} else {
|
|
||||||
$real_value = escapeEnvVariables($env->real_value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$envs->push($env->key.'='.$real_value);
|
|
||||||
}
|
}
|
||||||
// Add PORT if not exists, use the first port as default
|
// Add PORT if not exists, use the first port as default
|
||||||
if ($this->build_pack !== 'dockercompose') {
|
if ($this->build_pack !== 'dockercompose') {
|
||||||
@@ -956,6 +959,23 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$envs->push('HOST=0.0.0.0');
|
$envs->push('HOST=0.0.0.0');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->build_pack === 'dockercompose') {
|
||||||
|
$domains = collect(json_decode(data_get($this->preview, 'docker_compose_domains'))) ?? collect([]);
|
||||||
|
|
||||||
|
// Generate SERVICE_FQDN & SERVICE_URL for dockercompose
|
||||||
|
foreach ($domains as $forServiceName => $domain) {
|
||||||
|
$parsedDomain = data_get($domain, 'domain');
|
||||||
|
if (filled($parsedDomain)) {
|
||||||
|
$parsedDomain = str($parsedDomain)->explode(',')->first();
|
||||||
|
$coolifyUrl = Url::fromString($parsedDomain);
|
||||||
|
$coolifyScheme = $coolifyUrl->getScheme();
|
||||||
|
$coolifyFqdn = $coolifyUrl->getHost();
|
||||||
|
$coolifyUrl = $coolifyUrl->withScheme($coolifyScheme)->withHost($coolifyFqdn)->withPort(null);
|
||||||
|
$envs->push('SERVICE_URL_'.str($forServiceName)->upper().'='.$coolifyUrl->__toString());
|
||||||
|
$envs->push('SERVICE_FQDN_'.str($forServiceName)->upper().'='.$coolifyFqdn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ($envs->isEmpty()) {
|
if ($envs->isEmpty()) {
|
||||||
$this->env_filename = null;
|
$this->env_filename = null;
|
||||||
@@ -1367,9 +1387,16 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$fqdn = $this->preview->fqdn;
|
$fqdn = $this->preview->fqdn;
|
||||||
}
|
}
|
||||||
if (isset($fqdn)) {
|
if (isset($fqdn)) {
|
||||||
$this->coolify_variables .= "COOLIFY_FQDN={$fqdn} ";
|
$url = Url::fromString($fqdn);
|
||||||
$url = str($fqdn)->replace('http://', '')->replace('https://', '');
|
$fqdn = $url->getHost();
|
||||||
$this->coolify_variables .= "COOLIFY_URL={$url} ";
|
$url = $url->withHost($fqdn)->withPort(null)->__toString();
|
||||||
|
if ((int) $this->application->compose_parsing_version >= 3) {
|
||||||
|
$this->coolify_variables .= "COOLIFY_URL={$url} ";
|
||||||
|
$this->coolify_variables .= "COOLIFY_FQDN={$fqdn} ";
|
||||||
|
} else {
|
||||||
|
$this->coolify_variables .= "COOLIFY_URL={$fqdn} ";
|
||||||
|
$this->coolify_variables .= "COOLIFY_FQDN={$url} ";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (isset($this->application->git_branch)) {
|
if (isset($this->application->git_branch)) {
|
||||||
$this->coolify_variables .= "COOLIFY_BRANCH={$this->application->git_branch} ";
|
$this->coolify_variables .= "COOLIFY_BRANCH={$this->application->git_branch} ";
|
||||||
@@ -1715,10 +1742,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
{
|
{
|
||||||
$this->create_workdir();
|
$this->create_workdir();
|
||||||
$ports = $this->application->main_port();
|
$ports = $this->application->main_port();
|
||||||
$onlyPort = null;
|
|
||||||
if (count($ports) > 0) {
|
|
||||||
$onlyPort = $ports[0];
|
|
||||||
}
|
|
||||||
$persistent_storages = $this->generate_local_persistent_volumes();
|
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||||
$persistent_file_volumes = $this->application->fileStorages()->get();
|
$persistent_file_volumes = $this->application->fileStorages()->get();
|
||||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||||
@@ -2253,9 +2276,10 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
$this->application_deployment_queue->addLogEntry('Building docker image completed.');
|
$this->application_deployment_queue->addLogEntry('Building docker image completed.');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function graceful_shutdown_container(string $containerName, int $timeout = 30)
|
private function graceful_shutdown_container(string $containerName)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
$timeout = isDev() ? 1 : 30;
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
["docker stop --time=$timeout $containerName", 'hidden' => true, 'ignore_errors' => true],
|
["docker stop --time=$timeout $containerName", 'hidden' => true, 'ignore_errors' => true],
|
||||||
["docker rm -f $containerName", 'hidden' => true, 'ignore_errors' => true]
|
["docker rm -f $containerName", 'hidden' => true, 'ignore_errors' => true]
|
||||||
|
@@ -7,7 +7,6 @@ use App\Models\Application;
|
|||||||
use App\Models\ApplicationPreview;
|
use App\Models\ApplicationPreview;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Spatie\Url\Url;
|
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
class Previews extends Component
|
class Previews extends Component
|
||||||
@@ -87,18 +86,9 @@ class Previews extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$fqdn = generateFqdn($this->application->destination->server, $this->application->uuid);
|
$this->application->generate_preview_fqdn($preview->pull_request_id);
|
||||||
$url = Url::fromString($fqdn);
|
$this->application->refresh();
|
||||||
$template = $this->application->preview_url_template;
|
$this->dispatch('update_links');
|
||||||
$host = $url->getHost();
|
|
||||||
$schema = $url->getScheme();
|
|
||||||
$random = new Cuid2;
|
|
||||||
$preview_fqdn = str_replace('{{random}}', $random, $template);
|
|
||||||
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
|
|
||||||
$preview_fqdn = str_replace('{{pr_id}}', $preview->pull_request_id, $preview_fqdn);
|
|
||||||
$preview_fqdn = "$schema://$preview_fqdn";
|
|
||||||
$preview->fqdn = $preview_fqdn;
|
|
||||||
$preview->save();
|
|
||||||
$this->dispatch('success', 'Domain generated.');
|
$this->dispatch('success', 'Domain generated.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -121,8 +121,8 @@ class BackupExecutions extends Component
|
|||||||
{
|
{
|
||||||
return view('livewire.project.database.backup-executions', [
|
return view('livewire.project.database.backup-executions', [
|
||||||
'checkboxes' => [
|
'checkboxes' => [
|
||||||
['id' => 'delete_backup_s3', 'label' => 'Delete the selected backup permanently form S3 Storage'],
|
['id' => 'delete_backup_s3', 'label' => 'Delete the selected backup permanently from S3 Storage'],
|
||||||
// ['id' => 'delete_backup_sftp', 'label' => 'Delete the selected backup permanently form SFTP Storage'],
|
// ['id' => 'delete_backup_sftp', 'label' => 'Delete the selected backup permanently from SFTP Storage'],
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@@ -52,24 +52,38 @@ class ApplicationPreview extends BaseModel
|
|||||||
|
|
||||||
public function generate_preview_fqdn_compose()
|
public function generate_preview_fqdn_compose()
|
||||||
{
|
{
|
||||||
$domains = collect(json_decode($this->application->docker_compose_domains)) ?? collect();
|
$services = collect(json_decode($this->application->docker_compose_domains)) ?? collect();
|
||||||
foreach ($domains as $service_name => $domain) {
|
$docker_compose_domains = data_get($this, 'docker_compose_domains');
|
||||||
$domain = data_get($domain, 'domain');
|
$docker_compose_domains = json_decode($docker_compose_domains, true) ?? [];
|
||||||
$url = Url::fromString($domain);
|
|
||||||
$template = $this->application->preview_url_template;
|
foreach ($services as $service_name => $service_config) {
|
||||||
$host = $url->getHost();
|
$domain_string = data_get($service_config, 'domain');
|
||||||
$schema = $url->getScheme();
|
$service_domains = str($domain_string)->explode(',')->map(fn ($d) => trim($d));
|
||||||
$random = new Cuid2;
|
|
||||||
$preview_fqdn = str_replace('{{random}}', $random, $template);
|
$preview_domains = [];
|
||||||
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
|
foreach ($service_domains as $domain) {
|
||||||
$preview_fqdn = str_replace('{{pr_id}}', $this->pull_request_id, $preview_fqdn);
|
if (empty($domain)) {
|
||||||
$preview_fqdn = "$schema://$preview_fqdn";
|
continue;
|
||||||
$docker_compose_domains = data_get($this, 'docker_compose_domains');
|
}
|
||||||
$docker_compose_domains = json_decode($docker_compose_domains, true);
|
|
||||||
$docker_compose_domains[$service_name]['domain'] = $preview_fqdn;
|
$url = Url::fromString($domain);
|
||||||
$docker_compose_domains = json_encode($docker_compose_domains);
|
$template = $this->application->preview_url_template;
|
||||||
$this->docker_compose_domains = $docker_compose_domains;
|
$host = $url->getHost();
|
||||||
$this->save();
|
$schema = $url->getScheme();
|
||||||
|
$random = new Cuid2;
|
||||||
|
$preview_fqdn = str_replace('{{random}}', $random, $template);
|
||||||
|
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
|
||||||
|
$preview_fqdn = str_replace('{{pr_id}}', $this->pull_request_id, $preview_fqdn);
|
||||||
|
$preview_fqdn = "$schema://$preview_fqdn";
|
||||||
|
$preview_domains[] = $preview_fqdn;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! empty($preview_domains)) {
|
||||||
|
$docker_compose_domains[$service_name]['domain'] = implode(',', $preview_domains);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->docker_compose_domains = json_encode($docker_compose_domains);
|
||||||
|
$this->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -118,7 +118,14 @@ class EnvironmentVariable extends BaseModel
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->get_real_environment_variables($this->value, $resource);
|
$real_value = $this->get_real_environment_variables($this->value, $resource);
|
||||||
|
if ($this->is_literal || $this->is_multiline) {
|
||||||
|
$real_value = '\''.$real_value.'\'';
|
||||||
|
} else {
|
||||||
|
$real_value = escapeEnvVariables($real_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $real_value;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1260,26 +1260,17 @@ class Service extends BaseModel
|
|||||||
|
|
||||||
return 3;
|
return 3;
|
||||||
});
|
});
|
||||||
|
$envs = collect([]);
|
||||||
foreach ($sorted as $env) {
|
foreach ($sorted as $env) {
|
||||||
if (version_compare($env->version, '4.0.0-beta.347', '<=')) {
|
$envs->push("{$env->key}={$env->real_value}");
|
||||||
$commands[] = "echo '{$env->key}={$env->real_value}' >> .env";
|
|
||||||
} else {
|
|
||||||
$real_value = $env->real_value;
|
|
||||||
if ($env->version === '4.0.0-beta.239') {
|
|
||||||
$real_value = $env->real_value;
|
|
||||||
} else {
|
|
||||||
if ($env->is_literal || $env->is_multiline) {
|
|
||||||
$real_value = '\''.$real_value.'\'';
|
|
||||||
} else {
|
|
||||||
$real_value = escapeEnvVariables($env->real_value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$commands[] = "echo \"{$env->key}={$real_value}\" >> .env";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if ($sorted->count() === 0) {
|
if ($envs->count() === 0) {
|
||||||
$commands[] = 'touch .env';
|
$commands[] = 'touch .env';
|
||||||
|
} else {
|
||||||
|
$envs_base64 = base64_encode($envs->implode("\n"));
|
||||||
|
$commands[] = "echo '$envs_base64' | base64 -d | tee .env > /dev/null";
|
||||||
}
|
}
|
||||||
|
|
||||||
instant_remote_process($commands, $this->server);
|
instant_remote_process($commands, $this->server);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -599,7 +599,15 @@ function getTopLevelNetworks(Service|Application $resource)
|
|||||||
try {
|
try {
|
||||||
$yaml = Yaml::parse($resource->docker_compose_raw);
|
$yaml = Yaml::parse($resource->docker_compose_raw);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
throw new \RuntimeException($e->getMessage());
|
// If the docker-compose.yml file is not valid, we will return the network name as the key
|
||||||
|
$topLevelNetworks = collect([
|
||||||
|
$resource->uuid => [
|
||||||
|
'name' => $resource->uuid,
|
||||||
|
'external' => true,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $topLevelNetworks->keys();
|
||||||
}
|
}
|
||||||
$services = data_get($yaml, 'services');
|
$services = data_get($yaml, 'services');
|
||||||
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
|
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
|
||||||
@@ -653,9 +661,16 @@ function getTopLevelNetworks(Service|Application $resource)
|
|||||||
try {
|
try {
|
||||||
$yaml = Yaml::parse($resource->docker_compose_raw);
|
$yaml = Yaml::parse($resource->docker_compose_raw);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
throw new \RuntimeException($e->getMessage());
|
// If the docker-compose.yml file is not valid, we will return the network name as the key
|
||||||
|
$topLevelNetworks = collect([
|
||||||
|
$resource->uuid => [
|
||||||
|
'name' => $resource->uuid,
|
||||||
|
'external' => true,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $topLevelNetworks->keys();
|
||||||
}
|
}
|
||||||
$server = $resource->destination->server;
|
|
||||||
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
|
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
|
||||||
$services = data_get($yaml, 'services');
|
$services = data_get($yaml, 'services');
|
||||||
$definedNetwork = collect([$resource->uuid]);
|
$definedNetwork = collect([$resource->uuid]);
|
||||||
@@ -2931,7 +2946,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
} catch (\Exception) {
|
} catch (\Exception) {
|
||||||
return collect([]);
|
return collect([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$services = data_get($yaml, 'services', collect([]));
|
$services = data_get($yaml, 'services', collect([]));
|
||||||
$topLevel = collect([
|
$topLevel = collect([
|
||||||
'volumes' => collect(data_get($yaml, 'volumes', [])),
|
'volumes' => collect(data_get($yaml, 'volumes', [])),
|
||||||
@@ -3049,7 +3063,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get magic environments where we need to preset the FQDN
|
// Get magic environments where we need to preset the FQDN
|
||||||
if ($key->startsWith('SERVICE_FQDN_')) {
|
if ($key->startsWith('SERVICE_FQDN_')) {
|
||||||
// SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000
|
// SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000
|
||||||
@@ -3134,6 +3147,9 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ($command->value() === 'FQDN') {
|
if ($command->value() === 'FQDN') {
|
||||||
|
if ($isApplication && $resource->build_pack === 'dockercompose') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value();
|
$fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value();
|
||||||
if (str($fqdnFor)->contains('_')) {
|
if (str($fqdnFor)->contains('_')) {
|
||||||
$fqdnFor = str($fqdnFor)->before('_');
|
$fqdnFor = str($fqdnFor)->before('_');
|
||||||
@@ -3149,6 +3165,9 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
'is_preview' => false,
|
'is_preview' => false,
|
||||||
]);
|
]);
|
||||||
} elseif ($command->value() === 'URL') {
|
} elseif ($command->value() === 'URL') {
|
||||||
|
if ($isApplication && $resource->build_pack === 'dockercompose') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$fqdnFor = $key->after('SERVICE_URL_')->lower()->value();
|
$fqdnFor = $key->after('SERVICE_URL_')->lower()->value();
|
||||||
if (str($fqdnFor)->contains('_')) {
|
if (str($fqdnFor)->contains('_')) {
|
||||||
$fqdnFor = str($fqdnFor)->before('_');
|
$fqdnFor = str($fqdnFor)->before('_');
|
||||||
@@ -3599,7 +3618,8 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
'is_required' => $isRequired,
|
'is_required' => $isRequired,
|
||||||
]);
|
]);
|
||||||
// Add the variable to the environment so it will be shown in the deployable compose file
|
// Add the variable to the environment so it will be shown in the deployable compose file
|
||||||
$environment[$parsedKeyValue->value()] = $resource->environment_variables()->where('key', $parsedKeyValue)->where('resourceable_type', get_class($resource))->where('resourceable_id', $resource->id)->first()->value;
|
// $environment[$parsedKeyValue->value()] = $resource->environment_variables()->where('key', $parsedKeyValue)->where('resourceable_type', get_class($resource))->where('resourceable_id', $resource->id)->first()->real_value;
|
||||||
|
$environment[$parsedKeyValue->value()] = $value;
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -3637,9 +3657,30 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($isApplication) {
|
if ($isApplication) {
|
||||||
$domains = collect(json_decode($resource->docker_compose_domains)) ?? collect([]);
|
if ($isPullRequest) {
|
||||||
|
$preview = $resource->previews()->find($preview_id);
|
||||||
|
$domains = collect(json_decode(data_get($preview, 'docker_compose_domains'))) ?? collect([]);
|
||||||
|
} else {
|
||||||
|
$domains = collect(json_decode($resource->docker_compose_domains)) ?? collect([]);
|
||||||
|
}
|
||||||
$fqdns = data_get($domains, "$serviceName.domain");
|
$fqdns = data_get($domains, "$serviceName.domain");
|
||||||
if ($fqdns) {
|
// Generate SERVICE_FQDN & SERVICE_URL for dockercompose
|
||||||
|
if ($resource->build_pack === 'dockercompose') {
|
||||||
|
foreach ($domains as $forServiceName => $domain) {
|
||||||
|
$parsedDomain = data_get($domain, 'domain');
|
||||||
|
if (filled($parsedDomain)) {
|
||||||
|
$parsedDomain = str($parsedDomain)->explode(',')->first();
|
||||||
|
$coolifyUrl = Url::fromString($parsedDomain);
|
||||||
|
$coolifyScheme = $coolifyUrl->getScheme();
|
||||||
|
$coolifyFqdn = $coolifyUrl->getHost();
|
||||||
|
$coolifyUrl = $coolifyUrl->withScheme($coolifyScheme)->withHost($coolifyFqdn)->withPort(null);
|
||||||
|
$coolifyEnvironments->put('SERVICE_URL_'.str($forServiceName)->upper(), $coolifyUrl->__toString());
|
||||||
|
$coolifyEnvironments->put('SERVICE_FQDN_'.str($forServiceName)->upper(), $coolifyFqdn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the domain is set, we need to generate the FQDNs for the preview
|
||||||
|
if (filled($fqdns)) {
|
||||||
$fqdns = str($fqdns)->explode(',');
|
$fqdns = str($fqdns)->explode(',');
|
||||||
if ($isPullRequest) {
|
if ($isPullRequest) {
|
||||||
$preview = $resource->previews()->find($preview_id);
|
$preview = $resource->previews()->find($preview_id);
|
||||||
@@ -3671,7 +3712,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$defaultLabels = defaultLabels(
|
$defaultLabels = defaultLabels(
|
||||||
id: $resource->id,
|
id: $resource->id,
|
||||||
name: $containerName,
|
name: $containerName,
|
||||||
@@ -3681,6 +3721,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
type: 'application',
|
type: 'application',
|
||||||
environment: $resource->environment->name,
|
environment: $resource->environment->name,
|
||||||
);
|
);
|
||||||
|
|
||||||
} elseif ($isService) {
|
} elseif ($isService) {
|
||||||
if ($savedService->serviceType()) {
|
if ($savedService->serviceType()) {
|
||||||
$fqdns = generateServiceSpecificFqdns($savedService);
|
$fqdns = generateServiceSpecificFqdns($savedService);
|
||||||
@@ -3702,10 +3743,13 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
}
|
}
|
||||||
// Add COOLIFY_FQDN & COOLIFY_URL to environment
|
// Add COOLIFY_FQDN & COOLIFY_URL to environment
|
||||||
if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) {
|
if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) {
|
||||||
$coolifyEnvironments->put('COOLIFY_URL', $fqdns->implode(','));
|
$fqdnsWithoutPort = $fqdns->map(function ($fqdn) {
|
||||||
|
return str($fqdn)->after('://')->before(':')->prepend(str($fqdn)->before('://')->append('://'));
|
||||||
|
});
|
||||||
|
$coolifyEnvironments->put('COOLIFY_URL', $fqdnsWithoutPort->implode(','));
|
||||||
|
|
||||||
$urls = $fqdns->map(function ($fqdn) {
|
$urls = $fqdns->map(function ($fqdn) {
|
||||||
return str($fqdn)->replace('http://', '')->replace('https://', '');
|
return str($fqdn)->replace('http://', '')->replace('https://', '')->before(':');
|
||||||
});
|
});
|
||||||
$coolifyEnvironments->put('COOLIFY_FQDN', $urls->implode(','));
|
$coolifyEnvironments->put('COOLIFY_FQDN', $urls->implode(','));
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'coolify' => [
|
'coolify' => [
|
||||||
'version' => '4.0.0-beta.420.3',
|
'version' => '4.0.0-beta.420.4',
|
||||||
'helper_version' => '1.0.8',
|
'helper_version' => '1.0.8',
|
||||||
'realtime_version' => '1.0.9',
|
'realtime_version' => '1.0.9',
|
||||||
'self_hosted' => env('SELF_HOSTED', true),
|
'self_hosted' => env('SELF_HOSTED', true),
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
<h1 class="py-0">Deployment</h1>
|
<h1 class="py-0">Deployment</h1>
|
||||||
<livewire:project.shared.configuration-checker :resource="$application" />
|
<livewire:project.shared.configuration-checker :resource="$application" />
|
||||||
<livewire:project.application.heading :application="$application" />
|
<livewire:project.application.heading :application="$application" />
|
||||||
<div class="pt-4" x-data="{
|
<div x-data="{
|
||||||
fullscreen: false,
|
fullscreen: false,
|
||||||
alwaysScroll: false,
|
alwaysScroll: false,
|
||||||
intervalId: null,
|
intervalId: null,
|
||||||
|
@@ -69,7 +69,7 @@
|
|||||||
<x-forms.button wire:click="generateNginxConfiguration">Generate Default Nginx
|
<x-forms.button wire:click="generateNginxConfiguration">Generate Default Nginx
|
||||||
Configuration</x-forms.button>
|
Configuration</x-forms.button>
|
||||||
@endif
|
@endif
|
||||||
<div class="w-96 pb-8">
|
<div class="w-96 pb-6">
|
||||||
@if ($application->could_set_build_commands())
|
@if ($application->could_set_build_commands())
|
||||||
<x-forms.checkbox instantSave id="application.settings.is_static" label="Is it a static site?"
|
<x-forms.checkbox instantSave id="application.settings.is_static" label="Is it a static site?"
|
||||||
helper="If your application is a static site or the final build assets should be served as a static site, enable this." />
|
helper="If your application is a static site or the final build assets should be served as a static site, enable this." />
|
||||||
@@ -176,7 +176,7 @@
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
<div class="py-4 border-b dark:border-coolgray-200">
|
<div>
|
||||||
<h3>Build</h3>
|
<h3>Build</h3>
|
||||||
@if ($application->build_pack === 'dockerimage')
|
@if ($application->build_pack === 'dockerimage')
|
||||||
<x-forms.input
|
<x-forms.input
|
||||||
@@ -290,8 +290,11 @@
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@if ($application->build_pack === 'dockercompose')
|
@if ($application->build_pack === 'dockercompose')
|
||||||
<x-forms.button wire:target='initLoadingCompose'
|
<div class="flex items-center gap-2 pb-4">
|
||||||
x-on:click="$wire.dispatch('loadCompose', false)">Reload Compose File</x-forms.button>
|
<h3>Docker Compose</h3>
|
||||||
|
<x-forms.button wire:target='initLoadingCompose'
|
||||||
|
x-on:click="$wire.dispatch('loadCompose', false)">Reload Compose File</x-forms.button>
|
||||||
|
</div>
|
||||||
@if ($application->settings->is_raw_compose_deployment_enabled)
|
@if ($application->settings->is_raw_compose_deployment_enabled)
|
||||||
<x-forms.textarea rows="10" readonly id="application.docker_compose_raw"
|
<x-forms.textarea rows="10" readonly id="application.docker_compose_raw"
|
||||||
label="Docker Compose Content (applicationId: {{ $application->id }})"
|
label="Docker Compose Content (applicationId: {{ $application->id }})"
|
||||||
|
@@ -253,6 +253,11 @@ if [ "$OS_TYPE" = "endeavouros" ]; then
|
|||||||
OS_TYPE="arch"
|
OS_TYPE="arch"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Check if the OS is Cachy OS, if so, change it to arch
|
||||||
|
if [ "$OS_TYPE" = "cachyos" ]; then
|
||||||
|
OS_TYPE="arch"
|
||||||
|
fi
|
||||||
|
|
||||||
# Check if the OS is Asahi Linux, if so, change it to fedora
|
# Check if the OS is Asahi Linux, if so, change it to fedora
|
||||||
if [ "$OS_TYPE" = "fedora-asahi-remix" ]; then
|
if [ "$OS_TYPE" = "fedora-asahi-remix" ]; then
|
||||||
OS_TYPE="fedora"
|
OS_TYPE="fedora"
|
||||||
@@ -844,7 +849,7 @@ IPV6_PUBLIC_IP=$(curl -6s https://ifconfig.io || true)
|
|||||||
|
|
||||||
echo -e "\nYour instance is ready to use!\n"
|
echo -e "\nYour instance is ready to use!\n"
|
||||||
if [ -n "$IPV4_PUBLIC_IP" ]; then
|
if [ -n "$IPV4_PUBLIC_IP" ]; then
|
||||||
echo -e "You can access Coolify through your Public IPV4: http://$(curl -4s https://ifconfig.io):8000"
|
echo -e "You can access Coolify through your Public IPV4: http://[$IPV4_PUBLIC_IP]:8000"
|
||||||
fi
|
fi
|
||||||
if [ -n "$IPV6_PUBLIC_IP" ]; then
|
if [ -n "$IPV6_PUBLIC_IP" ]; then
|
||||||
echo -e "You can access Coolify through your Public IPv6: http://[$IPV6_PUBLIC_IP]:8000"
|
echo -e "You can access Coolify through your Public IPv6: http://[$IPV6_PUBLIC_IP]:8000"
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
postiz:
|
postiz:
|
||||||
image: ghcr.io/gitroomhq/postiz-app:latest
|
image: ghcr.io/gitroomhq/postiz-app:v1.60.1
|
||||||
environment:
|
environment:
|
||||||
- SERVICE_FQDN_POSTIZ_5000
|
- SERVICE_FQDN_POSTIZ_5000
|
||||||
- MAIN_URL=${SERVICE_FQDN_POSTIZ}
|
- MAIN_URL=${SERVICE_FQDN_POSTIZ}
|
||||||
|
File diff suppressed because one or more lines are too long
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"coolify": {
|
"coolify": {
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.420.3"
|
"version": "4.0.0-beta.420.4"
|
||||||
},
|
},
|
||||||
"nightly": {
|
"nightly": {
|
||||||
"version": "4.0.0-beta.420.4"
|
"version": "4.0.0-beta.420.5"
|
||||||
},
|
},
|
||||||
"helper": {
|
"helper": {
|
||||||
"version": "1.0.8"
|
"version": "1.0.8"
|
||||||
|
Reference in New Issue
Block a user