mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-04 10:42:23 +00:00
Add a recommendations page with dismiss and restore controls, and default new applications to Docker Build Secrets. Warn when Dockerfile syntax or variable usage is incompatible with build secrets.
186 lines
16 KiB
PHP
186 lines
16 KiB
PHP
<?php
|
|
|
|
// Non-default values on development-qemu-debian-root of the source (A), for full-e2e.sh. Run after
|
|
// seed-extras.php and after full-e2e.sh created the resources through the API. Values that a transfer must keep
|
|
// are not the defaults, so a lost value shows up in the snapshot diff. Deployed resources keep working values.
|
|
|
|
use App\Models\Application;
|
|
use App\Models\ApplicationPreview;
|
|
use App\Models\EnvironmentVariable;
|
|
use App\Models\GitlabApp;
|
|
use App\Models\LocalFileVolume;
|
|
use App\Models\LocalPersistentVolume;
|
|
use App\Models\PrivateKey;
|
|
use App\Models\S3Storage;
|
|
use App\Models\ScheduledDatabaseBackup;
|
|
use App\Models\ScheduledTask;
|
|
use App\Models\ScheduledVolumeBackup;
|
|
use App\Models\Server;
|
|
use App\Models\Service;
|
|
use App\Models\SharedEnvironmentVariable;
|
|
use App\Models\StandalonePostgresql;
|
|
use App\Models\Tag;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Tests\Support\ServerTransferFuzzer as Fuzzer;
|
|
|
|
$server = Server::where('uuid', 'development-qemu-debian-root')->firstOrFail();
|
|
$teamId = $server->team_id;
|
|
$ip = $server->ip;
|
|
$app = Application::where('name', 'whoami-2')->firstOrFail();
|
|
$environment = $app->environment;
|
|
$project = $environment->project;
|
|
$s3 = S3Storage::where('team_id', $teamId)->firstOrFail();
|
|
|
|
// 1. Server, proxy, and server settings
|
|
$server->forceFill(['description' => 'full coverage server'])->saveQuietly();
|
|
$server->proxy->set('redirect_enabled', true);
|
|
$server->proxy->set('redirect_url', 'https://redirect.example.test');
|
|
$server->saveQuietly();
|
|
$server->settings->forceFill([
|
|
'wildcard_domain' => "http://{$ip}.sslip.io", 'concurrent_builds' => 3, 'dynamic_timeout' => 1800, 'deployment_queue_limit' => 5,
|
|
'connection_timeout' => 20, 'server_timezone' => 'Europe/Budapest', 'force_docker_cleanup' => true, 'docker_cleanup_frequency' => '0 4 * * *',
|
|
'docker_cleanup_threshold' => 70, 'delete_unused_volumes' => true, 'delete_unused_networks' => true,
|
|
'server_disk_usage_notification_threshold' => 70, 'server_disk_usage_check_frequency' => '*/30 * * * *', 'server_disk_usage_notification_interval_hours' => 12,
|
|
'patch_check_frequency' => 'daily', 'is_auto_security_updates_enabled' => true, 'auto_security_updates_time' => '04:30',
|
|
'is_terminal_enabled' => false, 'disable_application_image_retention' => true, 'generate_exact_labels' => true,
|
|
'traefik_certificate_resolver' => 'custom', 'backup_compression_cpu_percentage' => 50,
|
|
'is_metrics_enabled' => true, 'sentinel_metrics_refresh_rate_seconds' => 10, 'sentinel_metrics_history_days' => 14, 'sentinel_push_interval_seconds' => 30,
|
|
'is_traffic_analytics_enabled' => true, 'traffic_topn' => 20, 'traffic_sample_threshold' => 9, 'traffic_retention_1h_days' => 3, 'traffic_retention_1d_days' => 60,
|
|
'traffic_ip_mode' => 'anonymized', 'is_geoip_enabled' => true, 'geoip_maxmind_license_key' => 'fake-maxmind-key', 'geoip_refresh_days' => 14,
|
|
'logdrain_newrelic_license_key' => 'fake-newrelic', 'logdrain_newrelic_base_uri' => 'https://log-api.eu.newrelic.com/log/v1',
|
|
'logdrain_highlight_project_id' => 'fake-highlight', 'logdrain_axiom_dataset_name' => 'fake-dataset', 'logdrain_axiom_api_key' => 'fake-axiom',
|
|
'logdrain_custom_config' => "[OUTPUT]\n Name stdout\n", 'logdrain_custom_config_parser' => "[PARSER]\n Name fake\n",
|
|
'logdrain_cloudwatch_region' => 'eu-central-1', 'logdrain_cloudwatch_group' => 'fake-group', 'logdrain_cloudwatch_stream_prefix' => 'fake-prefix',
|
|
])->saveQuietly();
|
|
|
|
// 2. Shared variables: server (multiline, literal, comment, empty), project, environment
|
|
$shared = fn (array $attributes) => SharedEnvironmentVariable::create(['team_id' => $teamId, ...$attributes]);
|
|
$shared(['type' => 'server', 'server_id' => $server->id, 'key' => 'SRV_MULTI', 'value' => "line one\n line two\n", 'is_multiline' => true, 'comment' => 'multiline']);
|
|
$shared(['type' => 'server', 'server_id' => $server->id, 'key' => 'SRV_LITERAL', 'value' => '$NOT_EXPANDED', 'is_literal' => true]);
|
|
$shared(['type' => 'server', 'server_id' => $server->id, 'key' => 'SRV_EMPTY', 'value' => '']);
|
|
$shared(['type' => 'project', 'project_id' => $project->id, 'key' => 'PRJ_KEY', 'value' => 'project-value', 'is_shown_once' => true]);
|
|
$shared(['type' => 'environment', 'environment_id' => $environment->id, 'key' => 'ENV_KEY', 'value' => 'environment-value']);
|
|
|
|
// 3. whoami-2 (deployed): domains, health check, limits, advanced options, settings
|
|
$app->forceFill([
|
|
'fqdn' => "http://whoami2.{$ip}.sslip.io,http://whoami2b.{$ip}.sslip.io", 'noindex_domains' => ["http://whoami2b.{$ip}.sslip.io"],
|
|
'description' => 'full coverage app', 'ports_mappings' => '18080:80', 'preview_url_template' => 'pr-{{pr_id}}.{{domain}}', 'redirect' => 'non-www',
|
|
'health_check_enabled' => true, 'health_check_path' => '/health', 'health_check_port' => '80', 'health_check_interval' => 7,
|
|
'health_check_timeout' => 4, 'health_check_retries' => 3, 'health_check_start_period' => 9,
|
|
'limits_memory' => '128m', 'limits_memory_swap' => '256m', 'limits_memory_swappiness' => 30, 'limits_memory_reservation' => '64m',
|
|
'limits_cpus' => '0.5', 'limits_cpuset' => '0', 'limits_cpu_shares' => 512,
|
|
'custom_docker_run_options' => '--cap-add=NET_ADMIN --ulimit nofile=1024:2048', 'custom_network_aliases' => 'whoami-alias',
|
|
'watch_paths' => "src/**\ndocs/*.md",
|
|
'manual_webhook_secret_github' => 'fake-gh-secret', 'is_http_basic_auth_enabled' => true, 'http_basic_auth_username' => 'fuzz',
|
|
'http_basic_auth_password' => 'fuzz-password', 'max_restart_count' => 4,
|
|
])->save();
|
|
$app->settings->forceFill([
|
|
'is_gzip_enabled' => false, 'is_stripprefix_enabled' => false, 'is_include_timestamps' => true, 'is_container_label_escape_enabled' => false,
|
|
'is_env_sorting_enabled' => true, 'docker_images_to_keep' => 5, 'stop_grace_period' => 30, 'redact_all_env_values_in_logs' => true,
|
|
'include_coolify_default_variables' => false, 'traefik_certificate_resolver' => 'custom', 'connect_to_docker_network' => true,
|
|
'custom_internal_name' => 'whoami-two', 'is_permanent_redirect_enabled' => true, 'is_log_drain_enabled' => false,
|
|
'is_preview_deployments_enabled' => true, 'is_pr_deployments_public_enabled' => true, 'is_auto_deploy_enabled' => false,
|
|
'use_build_secrets' => false, 'dismissed_recommendations' => ['use-build-secrets'],
|
|
])->save();
|
|
|
|
// Variables of every kind, and references to every shared scope
|
|
$env = fn (array $attributes) => EnvironmentVariable::create(['resourceable_type' => $app->getMorphClass(), 'resourceable_id' => $app->id, ...$attributes]);
|
|
$env(['key' => 'MULTI_LINE', 'value' => "first\nsecond", 'is_multiline' => true, 'comment' => 'two lines']);
|
|
$env(['key' => 'LITERAL_VAR', 'value' => '$HOME/not-expanded', 'is_literal' => true]);
|
|
$env(['key' => 'SHOWN_ONCE', 'value' => 'only-once', 'is_shown_once' => true]);
|
|
$env(['key' => 'BUILD_ONLY', 'value' => 'build', 'is_runtime' => false, 'is_buildtime' => true]);
|
|
$env(['key' => 'RUNTIME_ONLY', 'value' => 'runtime', 'is_runtime' => true, 'is_buildtime' => false]);
|
|
$env(['key' => 'REQUIRED_VAR', 'value' => 'required', 'is_required' => true]);
|
|
$env(['key' => 'PREVIEW_ONLY', 'value' => 'preview', 'is_preview' => true]);
|
|
$env(['key' => 'USES_PROJECT', 'value' => '{{project.PRJ_KEY}}']);
|
|
$env(['key' => 'USES_ENVIRONMENT', 'value' => '{{environment.ENV_KEY}}']);
|
|
$env(['key' => 'USES_SERVER', 'value' => '{{server.SRV_LITERAL}}']);
|
|
|
|
// Storages: file mount with whitespace, directory mount, host bind mount, persistent volume with an S3 backup
|
|
$file = fn (array $attributes) => LocalFileVolume::create(['resource_type' => $app->getMorphClass(), 'resource_id' => $app->id, ...$attributes]);
|
|
$file(['fs_path' => "/data/coolify/applications/{$app->uuid}/etc/fuzz.conf", 'mount_path' => '/etc/fuzz.conf', 'content' => " indented: true\nlast: line\n", 'chown' => '1000:1000', 'chmod' => '0640']);
|
|
$file(['fs_path' => "/data/coolify/applications/{$app->uuid}/fuzz-dir", 'mount_path' => '/fuzz-dir', 'is_directory' => true]);
|
|
LocalPersistentVolume::create(['name' => 'host-bind', 'mount_path' => '/host', 'host_path' => '/srv/fuzz-host', 'resource_type' => $app->getMorphClass(), 'resource_id' => $app->id]);
|
|
$volume = LocalPersistentVolume::create(['name' => "{$app->uuid}-data", 'mount_path' => '/data', 'is_preview_suffix_enabled' => true, 'resource_type' => $app->getMorphClass(), 'resource_id' => $app->id]);
|
|
$volumeBackup = ScheduledVolumeBackup::create([
|
|
'backupable_type' => $volume->getMorphClass(), 'backupable_id' => $volume->id, 'team_id' => $teamId, 's3_storage_id' => $s3->id,
|
|
'frequency' => '0 5 * * *', 'enabled' => true, 'save_s3' => true, 'stop_during_backup' => true, 'retention_amount_locally' => 3,
|
|
'retention_days_locally' => 4, 'retention_max_storage_locally' => 5, 'retention_amount_s3' => 6, 'retention_days_s3' => 7,
|
|
'retention_max_storage_s3' => 8, 'timeout' => 900, 'missing_backup_notification_days' => 2,
|
|
]);
|
|
$volumeBackup->s3Storages()->sync([$s3->id]);
|
|
ScheduledTask::create(['name' => 'disabled-task', 'command' => 'echo disabled', 'frequency' => '0 1 * * *', 'enabled' => false, 'container' => 'whoami', 'timeout' => 120, 'application_id' => $app->id, 'team_id' => $teamId]);
|
|
|
|
// 4. PostgreSQL with SSL (started): init script, config, public port, limits, health check, backups to S3
|
|
$pg = StandalonePostgresql::where('name', 'pg-ssl')->firstOrFail();
|
|
$pg->forceFill([
|
|
'description' => 'full coverage postgres', 'image' => 'postgres:17-alpine', 'postgres_initdb_args' => '--data-checksums', 'postgres_host_auth_method' => 'scram-sha-256',
|
|
'init_scripts' => [['index' => 0, 'filename' => 'init.sql', 'content' => "CREATE TABLE IF NOT EXISTS fuzz (id int);\n"]],
|
|
'postgres_conf' => "max_connections = 50\n", 'enable_ssl' => true, 'ssl_mode' => 'verify-full', 'is_public' => true, 'public_port' => 25432,
|
|
'public_port_timeout' => 1800, 'limits_memory' => '256m', 'limits_cpus' => '0.5', 'custom_docker_run_options' => '--shm-size=128m',
|
|
'health_check_interval' => 11, 'health_check_timeout' => 6, 'health_check_retries' => 4, 'health_check_start_period' => 12,
|
|
'is_include_timestamps' => true, 'redact_all_env_values_in_logs' => true, 'include_coolify_default_variables' => false,
|
|
])->save();
|
|
EnvironmentVariable::create(['key' => 'PG_EXTRA', 'value' => 'pg-extra', 'resourceable_type' => $pg->getMorphClass(), 'resourceable_id' => $pg->id]);
|
|
$pg->tags()->attach(Tag::firstOrCreate(['name' => 'database', 'team_id' => $teamId])->id);
|
|
$backup = ScheduledDatabaseBackup::create([
|
|
'team_id' => $teamId, 'database_type' => $pg->getMorphClass(), 'database_id' => $pg->id, 's3_storage_id' => $s3->id, 'description' => 'full coverage backup',
|
|
'enabled' => true, 'save_s3' => true, 'frequency' => '0 6 * * *', 'databases_to_backup' => 'postgres', 'dump_all' => false,
|
|
'database_backup_retention_amount_locally' => 3, 'database_backup_retention_days_locally' => 4, 'database_backup_retention_max_storage_locally' => 5,
|
|
'database_backup_retention_amount_s3' => 6, 'database_backup_retention_days_s3' => 7, 'database_backup_retention_max_storage_s3' => 8,
|
|
'timeout' => 1200, 'disable_local_backup' => false, 'missing_backup_notification_days' => 2,
|
|
]);
|
|
$backup->s3Storages()->sync([$s3->id]);
|
|
|
|
// 5. Legacy service (parser version 3) with a ./ bind mount, service app and database settings
|
|
$service = Service::where('name', 'svc-legacy')->firstOrFail();
|
|
$service->forceFill([
|
|
'compose_parsing_version' => '3', 'description' => 'parser v3 service', 'connect_to_docker_network' => true, 'is_container_label_escape_enabled' => false,
|
|
'redact_all_env_values_in_logs' => true, 'include_coolify_default_variables' => false,
|
|
])->saveQuietly();
|
|
$serviceApp = $service->applications()->firstOrFail();
|
|
$serviceApp->forceFill([
|
|
'fqdn' => "http://svc-legacy.{$ip}.sslip.io", 'is_gzip_enabled' => false, 'is_stripprefix_enabled' => false, 'exclude_from_status' => true,
|
|
'redirect' => 'non-www', 'traefik_certificate_resolver' => 'custom', 'human_name' => 'Legacy Web', 'description' => 'service app',
|
|
])->save();
|
|
$serviceDb = $service->databases()->firstOrFail();
|
|
$serviceDb->forceFill(['public_port' => 26379, 'is_public' => false, 'custom_type' => 'redis', 'human_name' => 'Legacy Cache'])->save();
|
|
EnvironmentVariable::create(['key' => 'SERVICE_EXTRA', 'value' => 'service-extra', 'resourceable_type' => $service->getMorphClass(), 'resourceable_id' => $service->id]);
|
|
ScheduledTask::create(['name' => 'service-task', 'command' => 'echo service', 'frequency' => '0 2 * * *', 'container' => 'cache', 'service_id' => $service->id, 'team_id' => $teamId]);
|
|
$service->tags()->attach(Tag::firstOrCreate(['name' => 'legacy', 'team_id' => $teamId])->id);
|
|
|
|
// 6. Records that are not deployed: git sources, deploy key, preview. These and the databases that are not started get
|
|
// a non-default value in every column (Tests\Support\ServerTransferFuzzer), except the columns that must stay valid.
|
|
$deployKey = PrivateKey::create(['team_id' => $teamId, 'name' => 'fake deploy key', 'private_key' => generateSSHKey('ed25519')['private'], 'is_git_related' => true]);
|
|
$gitlab = new GitlabApp;
|
|
$gitlab->forceFill(['uuid' => new_public_id(), 'name' => 'fake-gitlab', 'api_url' => 'https://gitlab.example.test/api/v4', 'html_url' => 'https://gitlab.example.test',
|
|
'is_system_wide' => false, 'team_id' => $teamId, 'private_key_id' => $deployKey->id])->saveQuietly();
|
|
Fuzzer::fill($gitlab, ['is_system_wide', 'api_url', 'html_url']);
|
|
$keepApplication = ['name', 'fqdn', 'build_pack', 'git_repository', 'status', 'docker_compose_raw', 'docker_compose', 'docker_compose_domains', 'domain_port_overrides',
|
|
'noindex_domains', 'custom_labels', 'domain_dns_statuses', 'dockerfile', 'custom_nginx_configuration', 'watch_paths', 'custom_network_aliases'];
|
|
foreach ([['deploykey-app', 'nixpacks', null, $deployKey->id, 'git@github.com:acme/private.git'], ['gitlab-app', 'nixpacks', $gitlab, null, 'acme/gitlab-repo'], ['static-site', 'static', null, null, 'acme/static-site']] as [$name, $buildPack, $source, $keyId, $repo]) {
|
|
$git = Application::create(['name' => $name, 'build_pack' => $buildPack, 'git_repository' => $repo, 'git_branch' => 'main', 'ports_exposes' => '3000',
|
|
'environment_id' => $environment->id, 'destination_id' => $app->destination_id, 'destination_type' => $app->destination_type]);
|
|
$git->forceFill(['source_type' => $source ? $source::class : null, 'source_id' => $source?->id, 'private_key_id' => $keyId])->saveQuietly();
|
|
Fuzzer::fill($git, $keepApplication, ['compose_parsing_version' => '2', 'fqdn' => "http://{$name}.{$ip}.sslip.io", 'redirect' => 'www']);
|
|
Fuzzer::fill($git->settings);
|
|
}
|
|
// The is_static setter sets port 80; the user can set another port after that.
|
|
Application::where('name', 'static-site')->firstOrFail()->forceFill(['ports_exposes' => '8080'])->saveQuietly();
|
|
// Keys that only older Coolify versions accepted. Deployments skip them with a warning.
|
|
$legacy = EnvironmentVariable::create(['key' => 'LEGACY_KEY', 'value' => 'legacy', 'resourceable_type' => $app->getMorphClass(), 'resourceable_id' => Application::where('name', 'static-site')->value('id')]);
|
|
DB::table('environment_variables')->where('id', $legacy->id)->update(['key' => 'legacy-key']);
|
|
$legacyShared = $shared(['type' => 'server', 'server_id' => $server->id, 'key' => 'LEGACY_SHARED', 'value' => 'legacy']);
|
|
DB::table('shared_environment_variables')->where('id', $legacyShared->id)->update(['key' => 'legacy-shared']);
|
|
$preview = new ApplicationPreview;
|
|
$preview->forceFill(['application_id' => $app->id, 'pull_request_id' => 42, 'pull_request_html_url' => 'https://github.com/acme/shop/pull/42',
|
|
'fqdn' => "http://pr-42.whoami2.{$ip}.sslip.io", 'max_restart_count' => 3, 'docker_registry_image_tag' => 'pr-42'])->save();
|
|
foreach (['StandaloneMysql', 'StandaloneMongodb', 'StandaloneKeydb', 'StandaloneDragonfly', 'StandaloneClickhouse', 'StandaloneRedis', 'StandaloneSqlite'] as $type) {
|
|
foreach (("App\\Models\\{$type}")::where('name', 'like', 'idle-%')->get() as $database) {
|
|
// ssl_mode has a check constraint; pg-ssl covers it.
|
|
Fuzzer::fill($database, ['status', 'is_public', 'public_port', 'init_scripts', 'sqlite_databases', 'ssl_mode'], ['description' => "idle {$type}"]);
|
|
}
|
|
}
|
|
|
|
echo "seeded full coverage\n";
|