4904
CHANGELOG.md
4904
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -27,7 +27,7 @@ class CheckProxy
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$proxyType = $server->proxyType();
|
$proxyType = $server->proxyType();
|
||||||
if (is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop) {
|
if ((is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop) && ! $fromUI) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (! $server->isProxyShouldRun()) {
|
if (! $server->isProxyShouldRun()) {
|
||||||
@@ -37,8 +37,12 @@ class CheckProxy
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine proxy container name based on environment
|
||||||
|
$proxyContainerName = $server->isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy';
|
||||||
|
|
||||||
if ($server->isSwarm()) {
|
if ($server->isSwarm()) {
|
||||||
$status = getContainerStatus($server, 'coolify-proxy_traefik');
|
$status = getContainerStatus($server, $proxyContainerName);
|
||||||
$server->proxy->set('status', $status);
|
$server->proxy->set('status', $status);
|
||||||
$server->save();
|
$server->save();
|
||||||
if ($status === 'running') {
|
if ($status === 'running') {
|
||||||
@@ -47,7 +51,7 @@ class CheckProxy
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
$status = getContainerStatus($server, 'coolify-proxy');
|
$status = getContainerStatus($server, $proxyContainerName);
|
||||||
if ($status === 'running') {
|
if ($status === 'running') {
|
||||||
$server->proxy->set('status', 'running');
|
$server->proxy->set('status', 'running');
|
||||||
$server->save();
|
$server->save();
|
||||||
@@ -61,34 +65,11 @@ class CheckProxy
|
|||||||
if ($server->id === 0) {
|
if ($server->id === 0) {
|
||||||
$ip = 'host.docker.internal';
|
$ip = 'host.docker.internal';
|
||||||
}
|
}
|
||||||
|
|
||||||
$portsToCheck = ['80', '443'];
|
$portsToCheck = ['80', '443'];
|
||||||
|
|
||||||
foreach ($portsToCheck as $port) {
|
foreach ($portsToCheck as $port) {
|
||||||
// Try multiple methods to check port availability
|
// Use the smart port checker that handles dual-stack properly
|
||||||
$commands = [
|
if ($this->isPortConflict($server, $port, $proxyContainerName)) {
|
||||||
// Method 1: Check /proc/net/tcp directly (convert port to hex)
|
|
||||||
"cat /proc/net/tcp | grep -q '00000000:".str_pad(dechex($port), 4, '0', STR_PAD_LEFT)."'",
|
|
||||||
// Method 2: Use ss command (modern alternative to netstat)
|
|
||||||
"ss -tuln | grep -q ':$port '",
|
|
||||||
// Method 3: Use lsof if available
|
|
||||||
"lsof -i :$port >/dev/null 2>&1",
|
|
||||||
// Method 4: Use fuser if available
|
|
||||||
"fuser $port/tcp >/dev/null 2>&1",
|
|
||||||
];
|
|
||||||
|
|
||||||
$portInUse = false;
|
|
||||||
foreach ($commands as $command) {
|
|
||||||
try {
|
|
||||||
instant_remote_process([$command], $server);
|
|
||||||
$portInUse = true;
|
|
||||||
break;
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($portInUse) {
|
|
||||||
if ($fromUI) {
|
if ($fromUI) {
|
||||||
throw new \Exception("Port $port is in use.<br>You must stop the process using this port.<br><br>Docs: <a target='_blank' class='dark:text-white hover:underline' href='https://coolify.io/docs'>https://coolify.io/docs</a><br>Discord: <a target='_blank' class='dark:text-white hover:underline' href='https://coolify.io/discord'>https://coolify.io/discord</a>");
|
throw new \Exception("Port $port is in use.<br>You must stop the process using this port.<br><br>Docs: <a target='_blank' class='dark:text-white hover:underline' href='https://coolify.io/docs'>https://coolify.io/docs</a><br>Discord: <a target='_blank' class='dark:text-white hover:underline' href='https://coolify.io/discord'>https://coolify.io/discord</a>");
|
||||||
} else {
|
} else {
|
||||||
@@ -126,4 +107,144 @@ class CheckProxy
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Smart port checker that handles dual-stack configurations
|
||||||
|
* Returns true only if there's a real port conflict (not just dual-stack)
|
||||||
|
*/
|
||||||
|
private function isPortConflict(Server $server, string $port, string $proxyContainerName): bool
|
||||||
|
{
|
||||||
|
// First check if our own proxy is using this port (which is fine)
|
||||||
|
try {
|
||||||
|
$getProxyContainerId = "docker ps -a --filter name=$proxyContainerName --format '{{.ID}}'";
|
||||||
|
$containerId = trim(instant_remote_process([$getProxyContainerId], $server));
|
||||||
|
|
||||||
|
if (! empty($containerId)) {
|
||||||
|
$checkProxyPort = "docker inspect $containerId --format '{{json .NetworkSettings.Ports}}' | grep '\"$port/tcp\"'";
|
||||||
|
try {
|
||||||
|
instant_remote_process([$checkProxyPort], $server);
|
||||||
|
|
||||||
|
// Our proxy is using the port, which is fine
|
||||||
|
return false;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// Our container exists but not using this port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// Container not found or error checking, continue with regular checks
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command sets for different ways to check ports, ordered by preference
|
||||||
|
$commandSets = [
|
||||||
|
// Set 1: Use ss to check listener counts by protocol stack
|
||||||
|
[
|
||||||
|
'available' => 'command -v ss >/dev/null 2>&1',
|
||||||
|
'check' => [
|
||||||
|
// Get listening process details
|
||||||
|
"ss_output=\$(ss -Htuln state listening sport = :$port 2>/dev/null) && echo \"\$ss_output\"",
|
||||||
|
// Count IPv4 listeners
|
||||||
|
"echo \"\$ss_output\" | grep -c ':$port '",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
// Set 2: Use netstat as alternative to ss
|
||||||
|
[
|
||||||
|
'available' => 'command -v netstat >/dev/null 2>&1',
|
||||||
|
'check' => [
|
||||||
|
// Get listening process details
|
||||||
|
"netstat_output=\$(netstat -tuln 2>/dev/null) && echo \"\$netstat_output\" | grep ':$port '",
|
||||||
|
// Count listeners
|
||||||
|
"echo \"\$netstat_output\" | grep ':$port ' | grep -c 'LISTEN'",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
// Set 3: Use lsof as last resort
|
||||||
|
[
|
||||||
|
'available' => 'command -v lsof >/dev/null 2>&1',
|
||||||
|
'check' => [
|
||||||
|
// Get process using the port
|
||||||
|
"lsof -i :$port -P -n | grep 'LISTEN'",
|
||||||
|
// Count listeners
|
||||||
|
"lsof -i :$port -P -n | grep 'LISTEN' | wc -l",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
// Try each command set until we find one available
|
||||||
|
foreach ($commandSets as $set) {
|
||||||
|
try {
|
||||||
|
// Check if the command is available
|
||||||
|
instant_remote_process([$set['available']], $server);
|
||||||
|
|
||||||
|
// Run the actual check commands
|
||||||
|
$output = instant_remote_process($set['check'], $server, true);
|
||||||
|
|
||||||
|
// Parse the output lines
|
||||||
|
$lines = explode("\n", trim($output));
|
||||||
|
|
||||||
|
// Get the detailed output and listener count
|
||||||
|
$details = trim($lines[0] ?? '');
|
||||||
|
$count = intval(trim($lines[1] ?? '0'));
|
||||||
|
|
||||||
|
// If no listeners or empty result, port is free
|
||||||
|
if ($count == 0 || empty($details)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to detect if this is our coolify-proxy
|
||||||
|
if (strpos($details, 'docker') !== false || strpos($details, $proxyContainerName) !== false) {
|
||||||
|
// It's likely our docker or proxy, which is fine
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for dual-stack scenario - typically 1-2 listeners (IPv4+IPv6)
|
||||||
|
// If exactly 2 listeners and both have same port, likely dual-stack
|
||||||
|
if ($count <= 2) {
|
||||||
|
// Check if it looks like a standard dual-stack setup
|
||||||
|
$isDualStack = false;
|
||||||
|
|
||||||
|
// Look for IPv4 and IPv6 in the listing (ss output format)
|
||||||
|
if (preg_match('/LISTEN.*:'.$port.'\s/', $details) &&
|
||||||
|
(preg_match('/\*:'.$port.'\s/', $details) ||
|
||||||
|
preg_match('/:::'.$port.'\s/', $details))) {
|
||||||
|
$isDualStack = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For netstat format
|
||||||
|
if (strpos($details, '0.0.0.0:'.$port) !== false &&
|
||||||
|
strpos($details, ':::'.$port) !== false) {
|
||||||
|
$isDualStack = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For lsof format (IPv4 and IPv6)
|
||||||
|
if (strpos($details, '*:'.$port) !== false &&
|
||||||
|
preg_match('/\*:'.$port.'.*IPv4/', $details) &&
|
||||||
|
preg_match('/\*:'.$port.'.*IPv6/', $details)) {
|
||||||
|
$isDualStack = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($isDualStack) {
|
||||||
|
return false; // This is just a normal dual-stack setup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here, it's likely a real port conflict
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// This command set failed, try the next one
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to simpler check if all above methods fail
|
||||||
|
try {
|
||||||
|
// Just try to bind to the port directly to see if it's available
|
||||||
|
$checkCommand = "nc -z -w1 127.0.0.1 $port >/dev/null 2>&1 && echo 'in-use' || echo 'free'";
|
||||||
|
$result = instant_remote_process([$checkCommand], $server, true);
|
||||||
|
|
||||||
|
return trim($result) === 'in-use';
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// If everything fails, assume the port is free to avoid false positives
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3006,73 +3006,73 @@ class ApplicationsController extends Controller
|
|||||||
// ]);
|
// ]);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// private function validateDataApplications(Request $request, Server $server)
|
private function validateDataApplications(Request $request, Server $server)
|
||||||
// {
|
{
|
||||||
// $teamId = getTeamIdFromToken();
|
$teamId = getTeamIdFromToken();
|
||||||
|
|
||||||
// // Validate ports_mappings
|
// Validate ports_mappings
|
||||||
// if ($request->has('ports_mappings')) {
|
if ($request->has('ports_mappings')) {
|
||||||
// $ports = [];
|
$ports = [];
|
||||||
// foreach (explode(',', $request->ports_mappings) as $portMapping) {
|
foreach (explode(',', $request->ports_mappings) as $portMapping) {
|
||||||
// $port = explode(':', $portMapping);
|
$port = explode(':', $portMapping);
|
||||||
// if (in_array($port[0], $ports)) {
|
if (in_array($port[0], $ports)) {
|
||||||
// return response()->json([
|
return response()->json([
|
||||||
// 'message' => 'Validation failed.',
|
'message' => 'Validation failed.',
|
||||||
// 'errors' => [
|
'errors' => [
|
||||||
// 'ports_mappings' => 'The first number before : should be unique between mappings.',
|
'ports_mappings' => 'The first number before : should be unique between mappings.',
|
||||||
// ],
|
],
|
||||||
// ], 422);
|
], 422);
|
||||||
// }
|
}
|
||||||
// $ports[] = $port[0];
|
$ports[] = $port[0];
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// // Validate custom_labels
|
// Validate custom_labels
|
||||||
// if ($request->has('custom_labels')) {
|
if ($request->has('custom_labels')) {
|
||||||
// if (! isBase64Encoded($request->custom_labels)) {
|
if (! isBase64Encoded($request->custom_labels)) {
|
||||||
// return response()->json([
|
return response()->json([
|
||||||
// 'message' => 'Validation failed.',
|
'message' => 'Validation failed.',
|
||||||
// 'errors' => [
|
'errors' => [
|
||||||
// 'custom_labels' => 'The custom_labels should be base64 encoded.',
|
'custom_labels' => 'The custom_labels should be base64 encoded.',
|
||||||
// ],
|
],
|
||||||
// ], 422);
|
], 422);
|
||||||
// }
|
}
|
||||||
// $customLabels = base64_decode($request->custom_labels);
|
$customLabels = base64_decode($request->custom_labels);
|
||||||
// if (mb_detect_encoding($customLabels, 'ASCII', true) === false) {
|
if (mb_detect_encoding($customLabels, 'ASCII', true) === false) {
|
||||||
// return response()->json([
|
return response()->json([
|
||||||
// 'message' => 'Validation failed.',
|
'message' => 'Validation failed.',
|
||||||
// 'errors' => [
|
'errors' => [
|
||||||
// 'custom_labels' => 'The custom_labels should be base64 encoded.',
|
'custom_labels' => 'The custom_labels should be base64 encoded.',
|
||||||
// ],
|
],
|
||||||
// ], 422);
|
], 422);
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// if ($request->has('domains') && $server->isProxyShouldRun()) {
|
if ($request->has('domains') && $server->isProxyShouldRun()) {
|
||||||
// $uuid = $request->uuid;
|
$uuid = $request->uuid;
|
||||||
// $fqdn = $request->domains;
|
$fqdn = $request->domains;
|
||||||
// $fqdn = str($fqdn)->replaceEnd(',', '')->trim();
|
$fqdn = str($fqdn)->replaceEnd(',', '')->trim();
|
||||||
// $fqdn = str($fqdn)->replaceStart(',', '')->trim();
|
$fqdn = str($fqdn)->replaceStart(',', '')->trim();
|
||||||
// $errors = [];
|
$errors = [];
|
||||||
// $fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) {
|
$fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) {
|
||||||
// if (filter_var($domain, FILTER_VALIDATE_URL) === false) {
|
if (filter_var($domain, FILTER_VALIDATE_URL) === false) {
|
||||||
// $errors[] = 'Invalid domain: '.$domain;
|
$errors[] = 'Invalid domain: '.$domain;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// return str($domain)->trim()->lower();
|
return str($domain)->trim()->lower();
|
||||||
// });
|
});
|
||||||
// if (count($errors) > 0) {
|
if (count($errors) > 0) {
|
||||||
// return response()->json([
|
return response()->json([
|
||||||
// 'message' => 'Validation failed.',
|
'message' => 'Validation failed.',
|
||||||
// 'errors' => $errors,
|
'errors' => $errors,
|
||||||
// ], 422);
|
], 422);
|
||||||
// }
|
}
|
||||||
// if (checkIfDomainIsAlreadyUsed($fqdn, $teamId, $uuid)) {
|
if (checkIfDomainIsAlreadyUsed($fqdn, $teamId, $uuid)) {
|
||||||
// return response()->json([
|
return response()->json([
|
||||||
// 'message' => 'Validation failed.',
|
'message' => 'Validation failed.',
|
||||||
// 'errors' => [
|
'errors' => [
|
||||||
// 'domains' => 'One of the domain is already used.',
|
'domains' => 'One of the domain is already used.',
|
||||||
// ],
|
],
|
||||||
// ], 422);
|
], 422);
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
@@ -455,7 +455,6 @@ class General extends Component
|
|||||||
{
|
{
|
||||||
$config = GenerateConfig::run($this->application, true);
|
$config = GenerateConfig::run($this->application, true);
|
||||||
$fileName = str($this->application->name)->slug()->append('_config.json');
|
$fileName = str($this->application->name)->slug()->append('_config.json');
|
||||||
dd($config);
|
|
||||||
|
|
||||||
return response()->streamDownload(function () use ($config) {
|
return response()->streamDownload(function () use ($config) {
|
||||||
echo $config;
|
echo $config;
|
||||||
|
@@ -925,7 +925,7 @@ $schema://$host {
|
|||||||
|
|
||||||
public function isFunctional()
|
public function isFunctional()
|
||||||
{
|
{
|
||||||
$isFunctional = $this->settings->is_reachable && $this->settings->is_usable && $this->settings->force_disabled === false && $this->ip !== '1.2.3.4';
|
$isFunctional = data_get($this->settings, 'is_reachable') && data_get($this->settings, 'is_usable') && data_get($this->settings, 'force_disabled') === false && $this->ip !== '1.2.3.4';
|
||||||
|
|
||||||
if ($isFunctional === false) {
|
if ($isFunctional === false) {
|
||||||
Storage::disk('ssh-mux')->delete($this->muxFilename());
|
Storage::disk('ssh-mux')->delete($this->muxFilename());
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'coolify' => [
|
'coolify' => [
|
||||||
'version' => '4.0.0-beta.405',
|
'version' => '4.0.0-beta.406',
|
||||||
'helper_version' => '1.0.8',
|
'helper_version' => '1.0.8',
|
||||||
'realtime_version' => '1.0.6',
|
'realtime_version' => '1.0.6',
|
||||||
'self_hosted' => env('SELF_HOSTED', true),
|
'self_hosted' => env('SELF_HOSTED', true),
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"coolify": {
|
"coolify": {
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.405"
|
"version": "4.0.0-beta.406"
|
||||||
},
|
},
|
||||||
"nightly": {
|
"nightly": {
|
||||||
"version": "4.0.0-beta.406"
|
"version": "4.0.0-beta.407"
|
||||||
},
|
},
|
||||||
"helper": {
|
"helper": {
|
||||||
"version": "1.0.8"
|
"version": "1.0.8"
|
||||||
|
@@ -72,6 +72,7 @@
|
|||||||
@script
|
@script
|
||||||
<script>
|
<script>
|
||||||
$wire.$on('checkProxyEvent', () => {
|
$wire.$on('checkProxyEvent', () => {
|
||||||
|
$wire.$dispatch('info', 'Checking proxy.');
|
||||||
$wire.$call('checkProxy');
|
$wire.$call('checkProxy');
|
||||||
});
|
});
|
||||||
$wire.$on('restartEvent', () => {
|
$wire.$on('restartEvent', () => {
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
<div x-init="$wire.checkProxy()" class="flex gap-2">
|
<div @if (data_get($server, 'proxy.force_stop', false) === false) x-init="$wire.checkProxy()" @endif class="flex gap-2">
|
||||||
<x-forms.button wire:click='checkProxy(true)' :showLoadingIndicator="false">Refresh</x-forms.button>
|
@if (data_get($server, 'proxy.force_stop', false) === false)
|
||||||
|
<x-forms.button wire:click='checkProxy(true)' :showLoadingIndicator="false">Refresh</x-forms.button>
|
||||||
|
@endif
|
||||||
@if (data_get($server, 'proxy.status') === 'running')
|
@if (data_get($server, 'proxy.status') === 'running')
|
||||||
<x-status.running status="Proxy Running" />
|
<x-status.running status="Proxy Running" />
|
||||||
@elseif (data_get($server, 'proxy.status') === 'restarting')
|
@elseif (data_get($server, 'proxy.status') === 'restarting')
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"coolify": {
|
"coolify": {
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.405"
|
"version": "4.0.0-beta.406"
|
||||||
},
|
},
|
||||||
"nightly": {
|
"nightly": {
|
||||||
"version": "4.0.0-beta.406"
|
"version": "4.0.0-beta.407"
|
||||||
},
|
},
|
||||||
"helper": {
|
"helper": {
|
||||||
"version": "1.0.8"
|
"version": "1.0.8"
|
||||||
|
Reference in New Issue
Block a user