mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-04 10:42:23 +00:00
Development mode rewrote /data/coolify paths to the dev Docker volume for every server. That is only right for the testing-host backend, which uses the host Docker daemon. The KVM localhost and other dev VMs have their own /data/coolify, so bind mounts, Compose content files, the Traefik and Caddy traffic mounts, Sentinel, and database proxies pointed to wrong paths there. - Server::sharesDevHostDocker() and devHostDockerPath() decide per server; only the testing-host server (ip coolify-testing-host) gets the volume path. Production is unchanged. - The testing-host volume names come from the Compose project (DEV_COOLIFY_DATA_VOLUME, DEV_COOLIFY_BACKUPS_VOLUME), with the legacy names as fallback. - In development, saved proxy configurations with the old path are fixed when they load and ask for a proxy restart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
157 lines
7.1 KiB
PHP
157 lines
7.1 KiB
PHP
<?php
|
|
|
|
use App\Actions\Server\StartSentinel;
|
|
use App\Models\PrivateKey;
|
|
use App\Models\Server;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Process;
|
|
use Symfony\Component\Yaml\Yaml;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
beforeEach(function () {
|
|
// generateDefaultProxyConfiguration() synchronously persists the config to the
|
|
// server over SSH (SaveProxyConfiguration); fake the process layer so tests
|
|
// don't attempt a real SSH connection.
|
|
Process::fake();
|
|
|
|
$user = User::factory()->create();
|
|
$this->team = $user->teams()->first();
|
|
|
|
$this->privateKey = PrivateKey::factory()->create(['team_id' => $this->team->id]);
|
|
});
|
|
|
|
it('does not mount the traffic volume for caddy when traffic analytics is disabled', function () {
|
|
$server = Server::factory()->create(['team_id' => $this->team->id, 'private_key_id' => $this->privateKey->id]);
|
|
$server->proxy->set('type', 'CADDY');
|
|
$server->save();
|
|
$server->settings->is_traffic_analytics_enabled = false;
|
|
$server->settings->save();
|
|
|
|
$config = Yaml::parse(generateDefaultProxyConfiguration($server->fresh()));
|
|
|
|
expect($config['services']['caddy']['volumes'])
|
|
->not->toContain($server->proxyPath().':/traffic');
|
|
});
|
|
|
|
it('mounts the traffic volume for caddy when traffic analytics is enabled', function () {
|
|
$server = Server::factory()->create(['team_id' => $this->team->id, 'private_key_id' => $this->privateKey->id]);
|
|
$server->proxy->set('type', 'CADDY');
|
|
$server->save();
|
|
$server->settings->is_traffic_analytics_enabled = true;
|
|
$server->settings->save();
|
|
|
|
$config = Yaml::parse(generateDefaultProxyConfiguration($server->fresh()));
|
|
|
|
expect($config['services']['caddy']['volumes'])
|
|
->toContain($server->proxyPath().':/traffic');
|
|
});
|
|
|
|
it('uses a default caddy image that supports per-app traffic attribution', function () {
|
|
$server = Server::factory()->create(['team_id' => $this->team->id, 'private_key_id' => $this->privateKey->id]);
|
|
$server->proxy->set('type', 'CADDY');
|
|
$server->save();
|
|
|
|
$config = Yaml::parse(generateDefaultProxyConfiguration($server->fresh()));
|
|
|
|
// caddy-docker-proxy 2.13 ships Caddy 2.11, which knows log_append; the old 2.8 default shipped Caddy 2.7.6.
|
|
expect($config['services']['caddy']['image'])->toBe('lucaslorentz/caddy-docker-proxy:2.13-alpine')
|
|
->and($server->fresh()->caddySupportsLogAppend())->toBeTrue();
|
|
});
|
|
|
|
function caddyTrafficServer(object $test, bool $analyticsEnabled = true, ?string $ip = null): Server
|
|
{
|
|
$server = Server::factory()->create(array_filter([
|
|
'team_id' => $test->team->id,
|
|
'private_key_id' => $test->privateKey->id,
|
|
'ip' => $ip,
|
|
]));
|
|
$server->proxy->set('type', 'CADDY');
|
|
$server->save();
|
|
$server->settings->is_traffic_analytics_enabled = $analyticsEnabled;
|
|
$server->settings->save();
|
|
|
|
return $server->fresh();
|
|
}
|
|
|
|
it('mounts the Sentinel traffic log directory into Caddy in production', function () {
|
|
$server = caddyTrafficServer($this);
|
|
|
|
$config = Yaml::parse(generateDefaultProxyConfiguration($server));
|
|
|
|
expect(StartSentinel::trafficLogDirectory($server))->toBe('/data/coolify/proxy/caddy')
|
|
->and($config['services']['caddy']['volumes'])->toContain('/data/coolify/proxy/caddy:/traffic');
|
|
});
|
|
|
|
it('mounts the Sentinel traffic log directory into Caddy in development', function (?string $ip, string $directory) {
|
|
config()->set('app.env', 'local');
|
|
$server = caddyTrafficServer($this, ip: $ip);
|
|
|
|
$volumes = Yaml::parse(generateDefaultProxyConfiguration($server))['services']['caddy']['volumes'];
|
|
$trafficVolumes = array_values(array_filter($volumes, fn (string $volume): bool => str_ends_with($volume, ':/traffic')));
|
|
|
|
// Caddy writes /traffic/access.log, Sentinel reads <trafficLogDirectory>/access.log.
|
|
expect($trafficVolumes)->toBe([StartSentinel::trafficLogDirectory($server).':/traffic'])
|
|
->and($trafficVolumes[0])->toBe("{$directory}:/traffic");
|
|
})->with([
|
|
// A dev KVM server has its own Docker daemon and its own /data/coolify, like production.
|
|
'dev KVM server' => ['10.221.1.10', '/data/coolify/proxy/caddy'],
|
|
// The testing-host server uses the host Docker daemon, so it mounts the dev data volume.
|
|
'dev testing-host server' => [Server::DEV_TESTING_HOST_IP, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/proxy/caddy'],
|
|
]);
|
|
|
|
it('uses the configured dev data volume for Caddy, Traefik, and Sentinel on the testing-host server', function () {
|
|
config()->set('app.env', 'local');
|
|
config()->set('constants.coolify.dev_data_volume', 'coolify-dev-feature_coolify_data');
|
|
$caddy = caddyTrafficServer($this, ip: Server::DEV_TESTING_HOST_IP);
|
|
|
|
$caddyVolumes = Yaml::parse(generateDefaultProxyConfiguration($caddy))['services']['caddy']['volumes'];
|
|
|
|
$traefik = Server::factory()->create(['team_id' => $this->team->id, 'private_key_id' => $this->privateKey->id, 'ip' => Server::DEV_TESTING_HOST_IP]);
|
|
$traefik->proxy->set('type', 'TRAEFIK');
|
|
$traefik->save();
|
|
$traefikVolumes = Yaml::parse(generateDefaultProxyConfiguration($traefik->fresh()))['services']['traefik']['volumes'];
|
|
|
|
expect(StartSentinel::trafficLogDirectory($caddy))->toBe('/var/lib/docker/volumes/coolify-dev-feature_coolify_data/_data/proxy/caddy')
|
|
->and($caddyVolumes)->toContain('/var/lib/docker/volumes/coolify-dev-feature_coolify_data/_data/proxy/caddy:/traffic')
|
|
->and($traefikVolumes)->toContain('/var/lib/docker/volumes/coolify-dev-feature_coolify_data/_data/proxy/:/traefik');
|
|
});
|
|
|
|
it('falls back to the legacy dev volumes for an invalid volume name', function (?string $volume) {
|
|
config()->set('app.env', 'local');
|
|
config()->set('constants.coolify.dev_data_volume', $volume);
|
|
config()->set('constants.coolify.dev_backups_volume', $volume);
|
|
$server = caddyTrafficServer($this, ip: Server::DEV_TESTING_HOST_IP);
|
|
|
|
expect(devHostDockerPath($server, '/data/coolify'))->toBe('/var/lib/docker/volumes/coolify_dev_coolify_data/_data')
|
|
->and(devHostDockerPath($server, '/data/coolify/backups/x'))->toBe('/var/lib/docker/volumes/coolify_dev_backups_data/_data/x');
|
|
})->with([
|
|
'empty' => [''],
|
|
'null' => [null],
|
|
'path traversal' => ['../../etc'],
|
|
'shell characters' => ['vol;rm -rf /'],
|
|
]);
|
|
|
|
it('replaces a stale Caddy traffic mount and removes it when analytics is disabled', function () {
|
|
config()->set('app.env', 'local');
|
|
$server = caddyTrafficServer($this);
|
|
$config = ['services' => ['caddy' => ['volumes' => [
|
|
'/var/run/docker.sock:/var/run/docker.sock:ro',
|
|
'/data/coolify/proxy/caddy:/traffic',
|
|
]]]];
|
|
|
|
$enabled = applyTrafficAnalyticsToProxyConfigArray($server, $config);
|
|
|
|
$server->settings->is_traffic_analytics_enabled = false;
|
|
$server->settings->save();
|
|
$disabled = applyTrafficAnalyticsToProxyConfigArray($server->fresh(), $enabled);
|
|
|
|
expect($enabled['services']['caddy']['volumes'])->toBe([
|
|
'/var/run/docker.sock:/var/run/docker.sock:ro',
|
|
StartSentinel::trafficLogDirectory($server).':/traffic',
|
|
])->and($disabled['services']['caddy']['volumes'])->toBe([
|
|
'/var/run/docker.sock:/var/run/docker.sock:ro',
|
|
]);
|
|
});
|