mirror of
https://github.com/coollabsio/coolify.git
synced 2026-09-26 17:30:46 -04:00
fix(server-transfer): allow local peer targets in development
Route localhost targets through host.docker.internal in containers while continuing to reject other private targets.
This commit is contained in:
@@ -100,6 +100,18 @@ class ServerTransferMigrator
|
||||
throw new RuntimeException('A valid target instance URL is required (e.g. https://coolify.example.com).');
|
||||
}
|
||||
|
||||
if ($this->isLocalDevelopmentTarget($targetUrl)) {
|
||||
if (file_exists('/.dockerenv') || is_file('/run/.containerenv')) {
|
||||
return (string) preg_replace(
|
||||
'#^(https?://)(localhost|127\.0\.0\.1)(?=[:/]|$)#i',
|
||||
'$1host.docker.internal',
|
||||
$targetUrl,
|
||||
);
|
||||
}
|
||||
|
||||
return $targetUrl;
|
||||
}
|
||||
|
||||
Validator::make(['target_url' => $targetUrl], [
|
||||
'target_url' => ['required', new SafeWebhookUrl],
|
||||
])->validate();
|
||||
@@ -107,6 +119,13 @@ class ServerTransferMigrator
|
||||
return $targetUrl;
|
||||
}
|
||||
|
||||
private function isLocalDevelopmentTarget(string $url): bool
|
||||
{
|
||||
return isDev()
|
||||
&& in_array(strtolower((string) parse_url($url, PHP_URL_SCHEME)), ['http', 'https'], true)
|
||||
&& in_array(strtolower((string) parse_url($url, PHP_URL_HOST)), ['localhost', '127.0.0.1', 'host.docker.internal'], true);
|
||||
}
|
||||
|
||||
private function normalizeToken(string $targetToken): string
|
||||
{
|
||||
$token = trim($targetToken);
|
||||
@@ -137,7 +156,9 @@ class ServerTransferMigrator
|
||||
|
||||
try {
|
||||
$response = Http::timeout(120)
|
||||
->withOptions(SafeWebhookUrl::httpClientOptions($importUrl))
|
||||
->withOptions($this->isLocalDevelopmentTarget($importUrl)
|
||||
? ['allow_redirects' => false]
|
||||
: SafeWebhookUrl::httpClientOptions($importUrl))
|
||||
->acceptJson()
|
||||
->withToken($token)
|
||||
->asJson()
|
||||
|
||||
@@ -78,6 +78,7 @@ test('migrate exports imports via http and completes locally', function () {
|
||||
});
|
||||
|
||||
test('migrate rejects a private target before sending the bundle', function () {
|
||||
config()->set('app.env', 'production');
|
||||
Http::fake();
|
||||
|
||||
expect(fn () => app(ServerTransferMigrator::class)->migrate(
|
||||
@@ -89,6 +90,40 @@ test('migrate rejects a private target before sending the bundle', function () {
|
||||
Http::assertNothingSent();
|
||||
});
|
||||
|
||||
test('migrate reaches a local peer instance in development', function () {
|
||||
config()->set('app.env', 'local');
|
||||
Http::fake(fn () => Http::response([
|
||||
'dry_run' => false,
|
||||
'server_uuid' => $this->server->uuid,
|
||||
'claimed' => true,
|
||||
'warnings' => [],
|
||||
], 201));
|
||||
|
||||
app(ServerTransferMigrator::class)->migrate(
|
||||
$this->server,
|
||||
'http://localhost:8001',
|
||||
'target-token',
|
||||
);
|
||||
|
||||
$targetHost = file_exists('/.dockerenv') || is_file('/run/.containerenv')
|
||||
? 'host.docker.internal'
|
||||
: 'localhost';
|
||||
Http::assertSent(fn ($request) => $request->url() === "http://{$targetHost}:8001/api/v1/servers/import");
|
||||
});
|
||||
|
||||
test('migrate still rejects other private targets in development', function () {
|
||||
config()->set('app.env', 'local');
|
||||
Http::fake();
|
||||
|
||||
expect(fn () => app(ServerTransferMigrator::class)->migrate(
|
||||
$this->server,
|
||||
'http://10.0.0.5:8001',
|
||||
'token',
|
||||
))->toThrow(ValidationException::class);
|
||||
|
||||
Http::assertNothingSent();
|
||||
});
|
||||
|
||||
test('migrate fails clearly when target is unreachable', function () {
|
||||
Http::fake([
|
||||
'http://8.8.4.4/*' => Http::failedConnection(),
|
||||
|
||||
Reference in New Issue
Block a user