diff --git a/.gitignore b/.gitignore index e184db0..4b93666 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ composer.lock # This ensures users get working assets on composer install without needing Node.js. # The compile-assets workflow auto-commits changes on every push to keep them in sync. .env +.claude/ diff --git a/README.md b/README.md index 7bc2d04..8994151 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Then provision your infrastructure: php artisan coolify:provision ``` -Creates app + PostgreSQL + Dragonfly on Coolify and deploys. One command. +This creates app + PostgreSQL + Dragonfly on Coolify and deploys. The provision command automatically adds `COOLIFY_PROJECT_UUID` to your `.env` file, enabling all other commands to work without manual configuration. ## Documentation @@ -146,17 +146,27 @@ Coolify::services()->all(); ## Configuration +After running `coolify:provision`, your `.env` will contain: + +```env +COOLIFY_URL=https://your-coolify.com +COOLIFY_TOKEN=your-api-token +COOLIFY_PROJECT_UUID=your-project-uuid # Added automatically by provision +``` + +The package automatically finds your application by matching your local git repository with applications in Coolify. No need to manually configure application UUIDs. + ```php // config/coolify.php return [ 'url' => env('COOLIFY_URL'), 'token' => env('COOLIFY_TOKEN'), - 'path' => env('COOLIFY_PATH', 'coolify'), // Dashboard URL path + 'project_uuid' => env('COOLIFY_PROJECT_UUID'), // Set by coolify:provision + 'path' => env('COOLIFY_PATH', 'coolify'), 'docker' => [ - 'php_version' => '8.3', - 'node_version' => '20', - 'extensions' => ['pdo_pgsql', 'redis', 'pcntl', 'bcmath'], + 'php_version' => '8.4', + 'use_base_image' => true, // Fast builds with pre-built images ], ]; ``` diff --git a/config/coolify.php b/config/coolify.php index 9ebba33..e836984 100644 --- a/config/coolify.php +++ b/config/coolify.php @@ -58,6 +58,20 @@ 'github_app_uuid' => env('COOLIFY_GITHUB_APP_UUID'), + /* + |-------------------------------------------------------------------------- + | Project UUID + |-------------------------------------------------------------------------- + | + | The UUID of the Coolify project this application belongs to. This is + | set automatically by `coolify:provision`. All other resource UUIDs + | (applications, databases, etc.) are fetched from Coolify using this + | project as the scope. + | + */ + + 'project_uuid' => env('COOLIFY_PROJECT_UUID'), + /* |-------------------------------------------------------------------------- | Dashboard Path diff --git a/database/migrations/2025_01_01_000000_create_coolify_resources_table.php b/database/migrations/2025_01_01_000000_create_coolify_resources_table.php deleted file mode 100644 index b94be60..0000000 --- a/database/migrations/2025_01_01_000000_create_coolify_resources_table.php +++ /dev/null @@ -1,37 +0,0 @@ -id(); - $table->string('name')->unique(); - $table->string('server_uuid'); - $table->string('project_uuid'); - $table->string('environment')->default('production'); - $table->string('deploy_key_uuid')->nullable(); - $table->string('repository')->nullable(); - $table->string('branch')->nullable(); - $table->string('application_uuid')->nullable(); - $table->string('database_uuid')->nullable(); - $table->string('redis_uuid')->nullable(); - $table->boolean('is_default')->default(false)->index(); - $table->json('metadata')->nullable(); - $table->timestamps(); - }); - } - - public function down(): void - { - Schema::dropIfExists('coolify_resources'); - } -}; diff --git a/database/migrations/2025_01_16_000000_add_webhook_secret_to_coolify_resources_table.php b/database/migrations/2025_01_16_000000_add_webhook_secret_to_coolify_resources_table.php deleted file mode 100644 index 8d0efd6..0000000 --- a/database/migrations/2025_01_16_000000_add_webhook_secret_to_coolify_resources_table.php +++ /dev/null @@ -1,24 +0,0 @@ -string('webhook_secret')->nullable()->after('redis_uuid'); - }); - } - } - - public function down(): void - { - Schema::table('coolify_resources', function (Blueprint $table) { - $table->dropColumn('webhook_secret'); - }); - } -}; diff --git a/docs/src/content/docs/commands/provision.md b/docs/src/content/docs/commands/provision.md index babf71a..398867c 100644 --- a/docs/src/content/docs/commands/provision.md +++ b/docs/src/content/docs/commands/provision.md @@ -51,25 +51,15 @@ php artisan coolify:provision \ 5. Dragonfly cache (with `--with-dragonfly` or `--all`) 6. Environment variables linking all services -## Database Storage - -Resource UUIDs are stored in the `coolify_resources` table: - -| Column | Value | -|--------|-------| -| `name` | Application name | -| `server_uuid` | Selected server | -| `project_uuid` | Project UUID | -| `environment` | Environment name | -| `application_uuid` | Created application | -| `database_uuid` | PostgreSQL UUID | -| `redis_uuid` | Dragonfly/Redis UUID | -| `deploy_key_uuid` | SSH key UUID | -| `repository` | GitHub repository | -| `branch` | Git branch | -| `is_default` | true | - -This record is marked as the default, so subsequent commands (`coolify:deploy`, `coolify:status`, etc.) use it automatically. +## Configuration Storage + +After provisioning, `COOLIFY_PROJECT_UUID` is automatically added to your local `.env` file: + +```bash +COOLIFY_PROJECT_UUID=abc123-def456-... +``` + +All other commands (`coolify:deploy`, `coolify:status`, etc.) automatically find your application by matching your local git repository with applications in Coolify. No manual UUID configuration is required. ## Generated Files diff --git a/docs/src/content/docs/getting-started/configuration.md b/docs/src/content/docs/getting-started/configuration.md index 9bc6cb3..ddfe955 100644 --- a/docs/src/content/docs/getting-started/configuration.md +++ b/docs/src/content/docs/getting-started/configuration.md @@ -12,6 +12,12 @@ COOLIFY_URL=https://your-coolify.com # Coolify instance URL COOLIFY_TOKEN=your-api-token # API token ``` +After running `coolify:provision`, this is added automatically: + +```bash +COOLIFY_PROJECT_UUID=your-project-uuid # Set by coolify:provision +``` + Optional: ```bash @@ -31,9 +37,16 @@ COOLIFY_NGINX_MAX_BODY_SIZE=35M COOLIFY_PHP_MEMORY_LIMIT=256M ``` -## Database Storage +## How Application Lookup Works + +The package automatically finds your application by matching your local git repository URL with applications in Coolify. When you run commands like `coolify:deploy` or `coolify:status`: + +1. It reads `COOLIFY_PROJECT_UUID` from your `.env` +2. Fetches all applications from Coolify +3. Matches your local `git remote get-url origin` with application git repositories +4. Uses the matching application for all operations -Resource configuration (UUIDs for applications, databases, etc.) is stored in the `coolify_resources` database table. This is created automatically when you run `php artisan coolify:install`. +This means no manual UUID configuration is needed after provisioning. ## Dashboard Authentication diff --git a/docs/src/content/docs/reference/config.md b/docs/src/content/docs/reference/config.md index dbc6767..c46f800 100644 --- a/docs/src/content/docs/reference/config.md +++ b/docs/src/content/docs/reference/config.md @@ -47,9 +47,15 @@ return [ // Log channel for Coolify events 'log_channel' => env('COOLIFY_LOG_CHANNEL', 'stack'), + // Project UUID (set by coolify:provision) + 'project_uuid' => env('COOLIFY_PROJECT_UUID'), + // Docker configuration 'docker' => [ 'php_version' => env('COOLIFY_PHP_VERSION', '8.4'), + 'use_base_image' => env('COOLIFY_USE_BASE_IMAGE', true), + 'auto_migrate' => env('COOLIFY_AUTO_MIGRATE', true), + 'db_wait_timeout' => env('COOLIFY_DB_WAIT_TIMEOUT', 30), 'health_check_path' => env('COOLIFY_HEALTH_CHECK_PATH', '/up'), 'nginx' => [ 'client_max_body_size' => env('COOLIFY_NGINX_MAX_BODY_SIZE', '35M'), @@ -64,31 +70,15 @@ return [ ]; ``` -## Resource Configuration +## How Application Lookup Works -Resource UUIDs (application, database, redis, etc.) are stored in the `coolify_resources` database table, not in config. Run the migration after installing: +Only `COOLIFY_PROJECT_UUID` is stored in your `.env` file. All other resource UUIDs are fetched from the Coolify API automatically. -```bash -php artisan migrate -``` +When you run commands like `coolify:deploy` or `coolify:status`: + +1. The package reads `COOLIFY_PROJECT_UUID` from your config +2. Fetches all applications from Coolify +3. Matches your local `git remote get-url origin` with application git repositories +4. Uses the matching application for operations -See [Database Schema](#database-schema) for the table structure. - -## Database Schema - -The `coolify_resources` table stores provisioned resource information: - -| Column | Type | Description | -|--------|------|-------------| -| `name` | string | Unique resource name | -| `server_uuid` | string | Coolify server | -| `project_uuid` | string | Coolify project | -| `environment` | string | Environment (production, staging) | -| `deploy_key_uuid` | string | SSH key for git access | -| `repository` | string | GitHub repository (owner/repo) | -| `branch` | string | Git branch | -| `application_uuid` | string | Coolify application | -| `database_uuid` | string | PostgreSQL instance | -| `redis_uuid` | string | Dragonfly/Redis instance | -| `is_default` | boolean | Default resource for commands | -| `metadata` | json | Additional data | +This means no manual UUID configuration is needed after provisioning. diff --git a/docs/src/content/docs/reference/env-vars.md b/docs/src/content/docs/reference/env-vars.md index a49caa8..b6a7ba5 100644 --- a/docs/src/content/docs/reference/env-vars.md +++ b/docs/src/content/docs/reference/env-vars.md @@ -10,6 +10,12 @@ description: All supported environment variables | `COOLIFY_URL` | Coolify instance URL | | `COOLIFY_TOKEN` | API authentication token | +## Auto-Generated + +| Variable | Description | +|----------|-------------| +| `COOLIFY_PROJECT_UUID` | Set automatically by `coolify:provision` | + ## Optional | Variable | Default | Description | @@ -51,8 +57,8 @@ When `COOLIFY_USE_BASE_IMAGE=false`: - Build time: ~12 minutes - Use this if you need custom PHP extensions -## Resource Configuration +## How Application Lookup Works -Resource UUIDs (application, database, server, etc.) are stored in the database, not environment variables. Run `coolify:provision` to create resources and store their UUIDs automatically. +Only `COOLIFY_PROJECT_UUID` is stored in your `.env`. All other resource UUIDs (applications, databases, etc.) are fetched from the Coolify API. -See [Configuration Reference](/laravel-coolify/reference/config/#database-schema) for the database schema. +When you run commands like `coolify:deploy`, the package automatically finds your application by matching your local git repository URL with applications in Coolify. diff --git a/src/Console/DeployCommand.php b/src/Console/DeployCommand.php index a40c589..b6526eb 100644 --- a/src/Console/DeployCommand.php +++ b/src/Console/DeployCommand.php @@ -6,9 +6,9 @@ use Stumason\Coolify\Console\Concerns\StreamsDeploymentLogs; use Stumason\Coolify\Contracts\ApplicationRepository; use Stumason\Coolify\Contracts\DeploymentRepository; +use Stumason\Coolify\Coolify; use Stumason\Coolify\CoolifyClient; use Stumason\Coolify\Exceptions\CoolifyApiException; -use Stumason\Coolify\Models\CoolifyResource; use Symfony\Component\Console\Attribute\AsCommand; use function Laravel\Prompts\confirm; @@ -52,7 +52,7 @@ public function handle( return self::FAILURE; } - $uuid = $this->option('uuid') ?? CoolifyResource::getDefault()?->application_uuid; + $uuid = $this->option('uuid') ?? Coolify::getApplicationUuid(); if (! $uuid) { $this->components->error('No application configured. Run coolify:provision first or use --uuid option.'); diff --git a/src/Console/LogsCommand.php b/src/Console/LogsCommand.php index c1f3c21..8faca40 100644 --- a/src/Console/LogsCommand.php +++ b/src/Console/LogsCommand.php @@ -5,9 +5,9 @@ use Illuminate\Console\Command; use Stumason\Coolify\Contracts\ApplicationRepository; use Stumason\Coolify\Contracts\DeploymentRepository; +use Stumason\Coolify\Coolify; use Stumason\Coolify\CoolifyClient; use Stumason\Coolify\Exceptions\CoolifyApiException; -use Stumason\Coolify\Models\CoolifyResource; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'coolify:logs')] @@ -60,7 +60,7 @@ public function handle( */ protected function showApplicationLogs(ApplicationRepository $applications): int { - $uuid = $this->option('uuid') ?? CoolifyResource::getDefault()?->application_uuid; + $uuid = $this->option('uuid') ?? Coolify::getApplicationUuid(); if (! $uuid) { $this->components->error('No application configured. Run coolify:provision first or use --uuid option.'); diff --git a/src/Console/ProvisionCommand.php b/src/Console/ProvisionCommand.php index d2ebb84..1f301ed 100644 --- a/src/Console/ProvisionCommand.php +++ b/src/Console/ProvisionCommand.php @@ -16,7 +16,6 @@ use Stumason\Coolify\Contracts\ServerRepository; use Stumason\Coolify\CoolifyClient; use Stumason\Coolify\Exceptions\CoolifyApiException; -use Stumason\Coolify\Models\CoolifyResource; use Symfony\Component\Console\Attribute\AsCommand; use function Laravel\Prompts\confirm; @@ -357,26 +356,11 @@ public function handle( $this->components->twoColumnDetail(' '.$cacheType, $redisUuid); } - // Save resource configuration to database - $resource = CoolifyResource::updateOrCreate( - ['name' => $appName], - [ - 'server_uuid' => $serverUuid, - 'project_uuid' => $projectUuid, - 'environment' => $environment, - 'deploy_key_uuid' => $deployKey['uuid'], - 'repository' => $repoInfo['full_name'], - 'branch' => $branch, - 'application_uuid' => $appUuid, - 'database_uuid' => $dbUuid, - 'redis_uuid' => $redisUuid, - 'webhook_secret' => $this->webhookSecret, - ] - ); - $resource->setAsDefault(); + // Save project UUID to local .env + $this->updateEnvFile('COOLIFY_PROJECT_UUID', $projectUuid); $this->newLine(); - $this->line(' Resource configuration saved to database'); + $this->line(' COOLIFY_PROJECT_UUID saved to .env'); $this->line(' Database credentials set on Coolify application'); // ───────────────────────────────────────────────────────────────── @@ -1571,4 +1555,37 @@ protected function checkDockerfile(): bool return true; } + + /** + * Update or add an environment variable in the .env file. + */ + protected function updateEnvFile(string $key, string $value): void + { + $envPath = base_path('.env'); + + if (! File::exists($envPath)) { + File::put($envPath, "{$key}={$value}\n"); + + return; + } + + $content = File::get($envPath); + + // Escape key for regex pattern + $escapedKey = preg_quote($key, '/'); + + // Check if the key already exists (only uncommented lines) + if (preg_match("/^{$escapedKey}=.*/m", $content)) { + // Update existing key + $content = preg_replace("/^{$escapedKey}=.*/m", "{$key}={$value}", $content); + } else { + // Add new key at the end + $content = rtrim($content, "\n")."\n{$key}={$value}\n"; + } + + // Atomic write using temp file + $tempPath = $envPath.'.tmp'; + File::put($tempPath, $content); + File::move($tempPath, $envPath); + } } diff --git a/src/Console/RestartCommand.php b/src/Console/RestartCommand.php index ebc59a0..ea8a59a 100644 --- a/src/Console/RestartCommand.php +++ b/src/Console/RestartCommand.php @@ -4,9 +4,9 @@ use Illuminate\Console\Command; use Stumason\Coolify\Contracts\ApplicationRepository; +use Stumason\Coolify\Coolify; use Stumason\Coolify\CoolifyClient; use Stumason\Coolify\Exceptions\CoolifyApiException; -use Stumason\Coolify\Models\CoolifyResource; use Symfony\Component\Console\Attribute\AsCommand; use function Laravel\Prompts\confirm; @@ -42,7 +42,7 @@ public function handle(CoolifyClient $client, ApplicationRepository $application return self::FAILURE; } - $uuid = $this->option('uuid') ?? CoolifyResource::getDefault()?->application_uuid; + $uuid = $this->option('uuid') ?? Coolify::getApplicationUuid(); if (! $uuid) { $this->components->error('No application configured. Run coolify:provision first or use --uuid option.'); diff --git a/src/Console/RollbackCommand.php b/src/Console/RollbackCommand.php index e0bc16e..709555e 100644 --- a/src/Console/RollbackCommand.php +++ b/src/Console/RollbackCommand.php @@ -4,9 +4,9 @@ use Illuminate\Console\Command; use Stumason\Coolify\Contracts\DeploymentRepository; +use Stumason\Coolify\Coolify; use Stumason\Coolify\CoolifyClient; use Stumason\Coolify\Exceptions\CoolifyApiException; -use Stumason\Coolify\Models\CoolifyResource; use Symfony\Component\Console\Attribute\AsCommand; use function Laravel\Prompts\confirm; @@ -44,7 +44,7 @@ public function handle(CoolifyClient $client, DeploymentRepository $deployments) return self::FAILURE; } - $applicationUuid = $this->option('uuid') ?? CoolifyResource::getDefault()?->application_uuid; + $applicationUuid = $this->option('uuid') ?? Coolify::getApplicationUuid(); if (! $applicationUuid) { $this->components->error('No application configured. Run coolify:provision first or use --uuid option.'); diff --git a/src/Console/StatusCommand.php b/src/Console/StatusCommand.php index baf723b..4377564 100644 --- a/src/Console/StatusCommand.php +++ b/src/Console/StatusCommand.php @@ -5,9 +5,9 @@ use Illuminate\Console\Command; use Stumason\Coolify\Contracts\ApplicationRepository; use Stumason\Coolify\Contracts\DatabaseRepository; +use Stumason\Coolify\Coolify; use Stumason\Coolify\CoolifyClient; use Stumason\Coolify\Exceptions\CoolifyApiException; -use Stumason\Coolify\Models\CoolifyResource; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'coolify:status')] @@ -118,7 +118,7 @@ protected function showAllResources( */ protected function showApplicationStatus(ApplicationRepository $applications): int { - $uuid = $this->option('uuid') ?? CoolifyResource::getDefault()?->application_uuid; + $uuid = $this->option('uuid') ?? Coolify::getApplicationUuid(); if (! $uuid) { $this->components->error('No application configured. Run coolify:provision first or use --uuid option.'); diff --git a/src/Coolify.php b/src/Coolify.php index fb062fa..2792464 100644 --- a/src/Coolify.php +++ b/src/Coolify.php @@ -3,13 +3,14 @@ namespace Stumason\Coolify; use Closure; +use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Process; use Stumason\Coolify\Contracts\ApplicationRepository; use Stumason\Coolify\Contracts\DatabaseRepository; use Stumason\Coolify\Contracts\DeploymentRepository; use Stumason\Coolify\Contracts\ProjectRepository; use Stumason\Coolify\Contracts\ServerRepository; use Stumason\Coolify\Contracts\ServiceRepository; -use Stumason\Coolify\Models\CoolifyResource; class Coolify { @@ -96,32 +97,119 @@ public static function projects(): ProjectRepository return app(ProjectRepository::class); } + /** + * Get the application UUID for this project by matching git repository. + * Fetches from Coolify and caches the result. + */ + public static function getApplicationUuid(): ?string + { + $projectUuid = config('coolify.project_uuid'); + if (! $projectUuid) { + return null; + } + + $gitRepo = static::getCurrentGitRepository(); + if (! $gitRepo) { + return null; + } + + // Include git repo in cache key to prevent stale cache when switching repos + $cacheKey = "coolify.app_uuid.{$projectUuid}.".md5($gitRepo); + $ttl = config('coolify.cache_ttl', 30); + + return Cache::remember($cacheKey, $ttl, function () use ($gitRepo) { + // Fetch all applications and find the one matching our repository + $applications = static::applications()->all(); + + foreach ($applications as $app) { + $appRepo = $app['git_repository'] ?? ''; + // Normalize: strip .git suffix and compare + $normalizedAppRepo = preg_replace('/\.git$/', '', $appRepo); + $normalizedAppRepo = preg_replace('#^git@github\.com:#', '', $normalizedAppRepo); + $normalizedAppRepo = preg_replace('#^https?://github\.com/#', '', $normalizedAppRepo); + + if (strcasecmp($normalizedAppRepo, $gitRepo) === 0) { + return $app['uuid'] ?? null; + } + } + + return null; + }); + } + + /** + * Get the current git repository name (owner/repo format). + * + * Note: Currently only supports GitHub repositories. GitLab, Bitbucket, + * and other providers will return null and require using --uuid flag. + */ + public static function getCurrentGitRepository(): ?string + { + // Validate we're in a git repository first + if (! is_dir(base_path('.git'))) { + return null; + } + + $result = Process::run('git remote get-url origin 2>/dev/null'); + + if (! $result->successful() || empty(trim($result->output()))) { + return null; + } + + $remoteUrl = trim($result->output()); + + // Extract owner/repo from various formats + if (preg_match('#github\.com[:/]([^/]+/[^/]+?)(?:\.git)?$#', $remoteUrl, $matches)) { + return $matches[1]; + } + + return null; + } + /** * Deploy the current application. + * + * @throws \InvalidArgumentException */ public static function deploy(?string $uuid = null): array { - $uuid = $uuid ?? CoolifyResource::getDefault()?->application_uuid; + $uuid = $uuid ?? static::getApplicationUuid(); + + if ($uuid === null) { + throw new \InvalidArgumentException('No application UUID configured. Run coolify:provision first or provide a UUID.'); + } return static::applications()->deploy($uuid); } /** * Get the status of the current application. + * + * @throws \InvalidArgumentException */ public static function status(?string $uuid = null): array { - $uuid = $uuid ?? CoolifyResource::getDefault()?->application_uuid; + $uuid = $uuid ?? static::getApplicationUuid(); + + if ($uuid === null) { + throw new \InvalidArgumentException('No application UUID configured. Run coolify:provision first or provide a UUID.'); + } return static::applications()->get($uuid); } /** * Get the logs for the current application. + * + * @throws \InvalidArgumentException */ public static function logs(?string $uuid = null): array { - $uuid = $uuid ?? CoolifyResource::getDefault()?->application_uuid; + $uuid = $uuid ?? static::getApplicationUuid(); + + if ($uuid === null) { + throw new \InvalidArgumentException('No application UUID configured. Run coolify:provision first or provide a UUID.'); + } return static::applications()->logs($uuid); } diff --git a/src/Http/Controllers/DashboardStatsController.php b/src/Http/Controllers/DashboardStatsController.php index dd76931..1a338bc 100644 --- a/src/Http/Controllers/DashboardStatsController.php +++ b/src/Http/Controllers/DashboardStatsController.php @@ -7,10 +7,9 @@ use Stumason\Coolify\Contracts\DatabaseRepository; use Stumason\Coolify\Contracts\DeploymentRepository; use Stumason\Coolify\Contracts\ProjectRepository; -use Stumason\Coolify\Contracts\SecurityKeyRepository; +use Stumason\Coolify\Coolify; use Stumason\Coolify\CoolifyClient; use Stumason\Coolify\Exceptions\CoolifyApiException; -use Stumason\Coolify\Models\CoolifyResource; class DashboardStatsController extends Controller { @@ -22,7 +21,6 @@ public function index( ApplicationRepository $applications, DatabaseRepository $databases, DeploymentRepository $deployments, - SecurityKeyRepository $securityKeys, ProjectRepository $projects ): JsonResponse { $stats = [ @@ -30,7 +28,6 @@ public function index( 'application' => null, 'databases' => [], 'recentDeployments' => [], - 'deployKey' => null, 'project' => null, 'environment' => null, 'server' => null, @@ -45,26 +42,9 @@ public function index( return response()->json($stats); } - // Get resource configuration from database - $resource = CoolifyResource::getDefault(); - - // Get deploy key info if configured - if ($deployKeyUuid = $resource?->deploy_key_uuid) { - try { - $key = $securityKeys->get($deployKeyUuid); - $stats['deployKey'] = [ - 'uuid' => $key['uuid'] ?? null, - 'name' => $key['name'] ?? 'Unknown', - 'public_key' => $key['public_key'] ?? null, - ]; - } catch (CoolifyApiException) { - // Key not found - } - } - - // Fetch project and environment info from resource config - $projectUuid = $resource?->project_uuid; - $environmentName = $resource?->environment; + // Get project from config + $projectUuid = config('coolify.project_uuid'); + $environmentName = 'production'; // Default environment $environmentUuid = null; if ($projectUuid) { @@ -73,7 +53,7 @@ public function index( $stats['project'] = $project; // Find environment UUID by name - if ($environmentName && ! empty($project['environments'])) { + if (! empty($project['environments'])) { foreach ($project['environments'] as $env) { if (($env['name'] ?? null) === $environmentName) { $environmentUuid = $env['uuid'] ?? null; @@ -87,10 +67,12 @@ public function index( } } - // Get application status - if ($uuid = $resource?->application_uuid) { + // Get application by matching git repository + $appUuid = Coolify::getApplicationUuid(); + + if ($appUuid) { try { - $app = $applications->get($uuid); + $app = $applications->get($appUuid); // Determine if using deploy key (no GitHub App) $usesDeployKey = empty($app['source_id']) || $app['source_type'] === null; @@ -100,19 +82,17 @@ public function index( $webhookUrl = null; $coolifyUrl = rtrim(config('coolify.url'), '/'); if ($usesDeployKey && $webhookSecret) { - $webhookUrl = "{$coolifyUrl}/webhooks/source/github/events/manual?source={$uuid}&webhook_secret={$webhookSecret}"; + $webhookUrl = "{$coolifyUrl}/webhooks/source/github/events/manual?source={$appUuid}&webhook_secret={$webhookSecret}"; } // Build proper Coolify dashboard URL - // Format: /project/{project_uuid}/environment/{environment_uuid}/application/{app_uuid} $coolifyDashboardUrl = null; if ($projectUuid && $environmentUuid) { - $coolifyDashboardUrl = "{$coolifyUrl}/project/{$projectUuid}/environment/{$environmentUuid}/application/{$uuid}"; + $coolifyDashboardUrl = "{$coolifyUrl}/project/{$projectUuid}/environment/{$environmentUuid}/application/{$appUuid}"; } // Return ALL application data from Coolify, plus computed fields $stats['application'] = array_merge($app, [ - // Computed convenience fields 'repository' => $app['git_repository'] ?? null, 'branch' => $app['git_branch'] ?? null, 'commit' => isset($app['git_commit_sha']) ? substr($app['git_commit_sha'], 0, 7) : null, @@ -135,11 +115,10 @@ public function index( // Get recent deployments try { - $recentDeployments = $deployments->forApplication($uuid); + $recentDeployments = $deployments->forApplication($appUuid); $stats['recentDeployments'] = collect($recentDeployments) ->take(10) ->map(function ($d) { - // Calculate duration if both timestamps exist $duration = null; if (! empty($d['created_at']) && ! empty($d['finished_at'])) { $start = strtotime($d['created_at']); @@ -165,28 +144,22 @@ public function index( // Ignore deployment fetch errors } } catch (CoolifyApiException) { - // Application not found or error - } - } - - // Get database status - if ($dbUuid = $resource?->database_uuid) { - try { - $db = $databases->get($dbUuid); - $stats['databases']['primary'] = $this->formatDatabaseInfo($db, 'database'); - } catch (CoolifyApiException) { - // Database not found or error + // Application not found } } - // Get redis status - if ($redisUuid = $resource?->redis_uuid) { - try { - $redis = $databases->get($redisUuid); - $stats['databases']['redis'] = $this->formatDatabaseInfo($redis, 'redis'); - } catch (CoolifyApiException) { - // Redis not found or error + // Fetch all databases and show them + try { + $allDatabases = $databases->all(); + foreach ($allDatabases as $db) { + $category = $this->getDatabaseCategory($db); + if (! isset($stats['databases'][$category])) { + $stats['databases'][$category] = []; + } + $stats['databases'][$category][] = $this->formatDatabaseInfo($db, $category); } + } catch (CoolifyApiException) { + // Ignore database fetch errors } } catch (CoolifyApiException) { @@ -196,12 +169,25 @@ public function index( return response()->json($stats); } + /** + * Determine database category (primary, redis, etc). + */ + protected function getDatabaseCategory(array $db): string + { + $dbType = $db['database_type'] ?? ''; + + if (str_contains($dbType, 'redis') || str_contains($dbType, 'dragonfly') || str_contains($dbType, 'keydb')) { + return 'redis'; + } + + return 'primary'; + } + /** * Format database info for the dashboard. */ protected function formatDatabaseInfo(array $db, string $category): array { - // Determine display type from database_type field $dbType = $db['database_type'] ?? 'unknown'; $displayType = match (true) { str_contains($dbType, 'postgresql') => 'PostgreSQL', @@ -214,7 +200,6 @@ protected function formatDatabaseInfo(array $db, string $category): array default => $dbType, }; - // Return ALL database data from Coolify, plus computed fields return array_merge($db, [ 'type' => $displayType, 'category' => $category, diff --git a/src/Http/Controllers/EnvironmentController.php b/src/Http/Controllers/EnvironmentController.php index fd0fa44..cab36bc 100644 --- a/src/Http/Controllers/EnvironmentController.php +++ b/src/Http/Controllers/EnvironmentController.php @@ -3,51 +3,30 @@ namespace Stumason\Coolify\Http\Controllers; use Illuminate\Http\JsonResponse; -use Stumason\Coolify\Models\CoolifyResource; class EnvironmentController extends Controller { /** * List all configured environments. + * + * Note: Multi-environment support has been removed. + * Use COOLIFY_PROJECT_UUID in .env to configure your project. */ public function index(): JsonResponse { - $environments = CoolifyResource::query() - ->orderByDesc('is_default') - ->orderBy('name') - ->get() - ->map(fn ($env) => [ - 'id' => $env->id, - 'name' => $env->name, - 'environment' => $env->environment, - 'is_default' => $env->is_default, - 'application_uuid' => $env->application_uuid, - 'database_uuid' => $env->database_uuid, - 'redis_uuid' => $env->redis_uuid, - 'repository' => $env->repository, - 'branch' => $env->branch, - ]); - - return response()->json($environments); + return response()->json([]); } /** * Switch to a different environment. + * + * Note: Multi-environment support has been removed. */ public function switch(int $id): JsonResponse { - $resource = CoolifyResource::query()->findOrFail($id); - $resource->setAsDefault(); - return response()->json([ - 'success' => true, - 'message' => "Switched to {$resource->name}", - 'environment' => [ - 'id' => $resource->id, - 'name' => $resource->name, - 'environment' => $resource->environment, - 'is_default' => true, - ], - ]); + 'success' => false, + 'message' => 'Multi-environment support has been removed. Configure COOLIFY_PROJECT_UUID in .env instead.', + ], 400); } } diff --git a/src/Models/CoolifyResource.php b/src/Models/CoolifyResource.php deleted file mode 100644 index 322944c..0000000 --- a/src/Models/CoolifyResource.php +++ /dev/null @@ -1,73 +0,0 @@ -|null $metadata - * @property \Illuminate\Support\Carbon|null $created_at - * @property \Illuminate\Support\Carbon|null $updated_at - * - * @method static \Illuminate\Database\Eloquent\Builder where($column, $operator = null, $value = null, $boolean = 'and') - * @method static \Illuminate\Database\Eloquent\Builder query() - * @method static static updateOrCreate(array $attributes, array $values = []) - */ -class CoolifyResource extends Model -{ - protected $fillable = [ - 'name', - 'server_uuid', - 'project_uuid', - 'environment', - 'deploy_key_uuid', - 'repository', - 'branch', - 'application_uuid', - 'database_uuid', - 'redis_uuid', - 'webhook_secret', - 'is_default', - 'metadata', - ]; - - protected $casts = [ - 'is_default' => 'boolean', - 'metadata' => 'array', - ]; - - /** - * Get the default resource configuration. - */ - public static function getDefault(): ?self - { - return static::where('is_default', true)->first(); - } - - /** - * Set this resource as the default, unsetting any other default. - */ - public function setAsDefault(): self - { - return DB::transaction(function () { - static::where('is_default', true)->update(['is_default' => false]); - $this->update(['is_default' => true]); - - return $this; - }); - } -} diff --git a/tests/Feature/Console/DeployCommandTest.php b/tests/Feature/Console/DeployCommandTest.php index 240a983..8e06e6f 100644 --- a/tests/Feature/Console/DeployCommandTest.php +++ b/tests/Feature/Console/DeployCommandTest.php @@ -1,7 +1,6 @@ delete(); + // Clear the project UUID so no application can be found + config(['coolify.project_uuid' => null]); $this->artisan('coolify:deploy', ['--force' => true]) ->assertFailed() @@ -36,7 +35,8 @@ ], 200), ]); - $this->artisan('coolify:deploy', ['--force' => true]) + // Use --uuid to bypass the git repo lookup + $this->artisan('coolify:deploy', ['--force' => true, '--uuid' => 'test-app-uuid']) ->assertSuccessful() ->expectsOutputToContain('Deployment triggered successfully') ->expectsOutputToContain('new-deployment-123'); @@ -53,7 +53,7 @@ ], 200), ]); - $this->artisan('coolify:deploy', ['--force' => true, '--tag' => 'v1.0.0']) + $this->artisan('coolify:deploy', ['--force' => true, '--tag' => 'v1.0.0', '--uuid' => 'test-app-uuid']) ->assertSuccessful() ->expectsOutputToContain('Deployment triggered'); diff --git a/tests/Feature/Console/LogsCommandTest.php b/tests/Feature/Console/LogsCommandTest.php index 904535e..91f7a22 100644 --- a/tests/Feature/Console/LogsCommandTest.php +++ b/tests/Feature/Console/LogsCommandTest.php @@ -14,7 +14,8 @@ ]), ]); - $this->artisan('coolify:logs') + // Use --uuid to bypass git repository lookup + $this->artisan('coolify:logs', ['--uuid' => 'test-app-uuid']) ->assertSuccessful(); }); @@ -25,7 +26,8 @@ ]), ]); - $this->artisan('coolify:logs') + // Use --uuid to bypass git repository lookup + $this->artisan('coolify:logs', ['--uuid' => 'test-app-uuid']) ->assertSuccessful(); }); diff --git a/tests/Feature/Console/RestartCommandTest.php b/tests/Feature/Console/RestartCommandTest.php index ac95ee4..74ccf04 100644 --- a/tests/Feature/Console/RestartCommandTest.php +++ b/tests/Feature/Console/RestartCommandTest.php @@ -14,7 +14,8 @@ ]), ]); - $this->artisan('coolify:restart', ['--force' => true]) + // Use --uuid to bypass git repository lookup + $this->artisan('coolify:restart', ['--uuid' => 'test-app-uuid', '--force' => true]) ->assertSuccessful(); }); @@ -25,7 +26,8 @@ ]), ]); - $this->artisan('coolify:restart', ['--force' => true]) + // Use --uuid to bypass git repository lookup + $this->artisan('coolify:restart', ['--uuid' => 'test-app-uuid', '--force' => true]) ->expectsOutputToContain('restart triggered successfully') ->assertSuccessful(); }); @@ -38,7 +40,8 @@ ), ]); - $this->artisan('coolify:restart', ['--force' => true]) + // Use --uuid to bypass git repository lookup + $this->artisan('coolify:restart', ['--uuid' => 'test-app-uuid', '--force' => true]) ->assertFailed(); }); }); diff --git a/tests/Feature/Console/RollbackCommandTest.php b/tests/Feature/Console/RollbackCommandTest.php index 6055cbd..1c14bfa 100644 --- a/tests/Feature/Console/RollbackCommandTest.php +++ b/tests/Feature/Console/RollbackCommandTest.php @@ -29,7 +29,8 @@ ]), ]); - $this->artisan('coolify:rollback', ['--deployment' => 'deployment-2', '--force' => true]) + // Use --uuid to bypass git repository lookup + $this->artisan('coolify:rollback', ['--uuid' => 'test-app-uuid', '--deployment' => 'deployment-2', '--force' => true]) ->assertSuccessful(); }); @@ -58,7 +59,8 @@ ]); // In non-interactive mode without specifying a deployment, it defaults to the previous one - $this->artisan('coolify:rollback', ['--force' => true, '--no-interaction' => true]) + // Use --uuid to bypass git repository lookup + $this->artisan('coolify:rollback', ['--uuid' => 'test-app-uuid', '--force' => true, '--no-interaction' => true]) ->assertSuccessful(); }); }); diff --git a/tests/Feature/Console/StatusCommandTest.php b/tests/Feature/Console/StatusCommandTest.php index 8a4d073..1766261 100644 --- a/tests/Feature/Console/StatusCommandTest.php +++ b/tests/Feature/Console/StatusCommandTest.php @@ -1,7 +1,6 @@ delete(); + // Clear the project UUID so no application UUID can be resolved + config(['coolify.project_uuid' => null]); Http::fake(['*' => Http::response(['version' => '4.0'], 200)]); @@ -40,7 +39,8 @@ ], 200), ]); - $this->artisan('coolify:status') + // Use --uuid to bypass git repository lookup + $this->artisan('coolify:status', ['--uuid' => 'test-app-uuid']) ->assertSuccessful() ->expectsOutputToContain('My Laravel App') ->expectsOutputToContain('running'); diff --git a/tests/Feature/Http/DashboardTest.php b/tests/Feature/Http/DashboardTest.php index 354852c..717c2e3 100644 --- a/tests/Feature/Http/DashboardTest.php +++ b/tests/Feature/Http/DashboardTest.php @@ -1,6 +1,7 @@ Process::result('git@github.com:StuMason/laravel-coolify.git'), + ]); + Http::fake([ '*/version' => Http::response(['version' => '4.0'], 200), + // Fake the applications list endpoint for git repository lookup + '*/applications' => Http::response([ + [ + 'uuid' => 'test-app-uuid', + 'name' => 'My App', + 'git_repository' => 'https://github.com/StuMason/laravel-coolify', + ], + ], 200), '*/security/keys/test-deploy-key-uuid' => Http::response([ 'uuid' => 'test-deploy-key-uuid', 'name' => 'test-key', @@ -38,20 +57,22 @@ 'status' => 'running', 'fqdn' => 'https://myapp.com', ], 200), - '*/applications/test-app-uuid/deployments' => Http::response([ + '*/deployments/applications/test-app-uuid' => Http::response([ ['uuid' => 'deploy-1', 'status' => 'finished'], ], 200), - '*/databases/test-db-uuid' => Http::response([ - 'uuid' => 'test-db-uuid', - 'name' => 'test-db', - 'database_type' => 'postgresql', - 'status' => 'running', - ], 200), - '*/databases/test-redis-uuid' => Http::response([ - 'uuid' => 'test-redis-uuid', - 'name' => 'test-redis', - 'database_type' => 'dragonfly', - 'status' => 'running', + '*/databases' => Http::response([ + [ + 'uuid' => 'test-db-uuid', + 'name' => 'test-db', + 'database_type' => 'postgresql', + 'status' => 'running', + ], + [ + 'uuid' => 'test-redis-uuid', + 'name' => 'test-redis', + 'database_type' => 'dragonfly', + 'status' => 'running', + ], ], 200), ]); @@ -65,6 +86,11 @@ 'status' => 'running', ], ]); + + // Cleanup + if (is_dir($gitDir)) { + rmdir($gitDir); + } }); it('returns disconnected status on API failure', function () { diff --git a/tests/Feature/Http/EnvironmentControllerTest.php b/tests/Feature/Http/EnvironmentControllerTest.php index 44f06a4..141979b 100644 --- a/tests/Feature/Http/EnvironmentControllerTest.php +++ b/tests/Feature/Http/EnvironmentControllerTest.php @@ -2,86 +2,41 @@ use Illuminate\Support\Facades\Http; use Stumason\Coolify\Coolify; -use Stumason\Coolify\Models\CoolifyResource; beforeEach(function () { Http::preventStrayRequests(); Coolify::auth(fn () => true); - - // Clean up any existing resources - CoolifyResource::query()->delete(); }); describe('EnvironmentController', function () { - it('lists all configured environments', function () { - CoolifyResource::create([ - 'name' => 'Production', - 'environment' => 'production', - 'server_uuid' => 'server-1', - 'project_uuid' => 'project-1', - 'application_uuid' => 'app-prod', - 'is_default' => true, - ]); - - CoolifyResource::create([ - 'name' => 'Staging', - 'environment' => 'staging', - 'server_uuid' => 'server-1', - 'project_uuid' => 'project-1', - 'application_uuid' => 'app-staging', - 'is_default' => false, - ]); - + it('returns empty array for environments list', function () { + // Multi-environment support has been removed + // The endpoint now always returns an empty array $response = $this->getJson(route('coolify.environments.index')); $response->assertOk() - ->assertJsonCount(2) - ->assertJsonFragment(['name' => 'Production', 'is_default' => true]) - ->assertJsonFragment(['name' => 'Staging', 'is_default' => false]); + ->assertJsonCount(0); }); - it('switches to a different environment', function () { - $prod = CoolifyResource::create([ - 'name' => 'Production', - 'environment' => 'production', - 'server_uuid' => 'server-1', - 'project_uuid' => 'project-1', - 'application_uuid' => 'app-prod', - 'is_default' => true, - ]); + it('returns error when trying to switch environments', function () { + // Multi-environment support has been removed + // The switch endpoint now returns a 400 error + $response = $this->postJson(route('coolify.environments.switch', 1)); - $staging = CoolifyResource::create([ - 'name' => 'Staging', - 'environment' => 'staging', - 'server_uuid' => 'server-1', - 'project_uuid' => 'project-1', - 'application_uuid' => 'app-staging', - 'is_default' => false, - ]); - - $response = $this->postJson(route('coolify.environments.switch', $staging->id)); - - $response->assertOk() + $response->assertStatus(400) ->assertJson([ - 'success' => true, - 'message' => 'Switched to Staging', + 'success' => false, + 'message' => 'Multi-environment support has been removed. Configure COOLIFY_PROJECT_UUID in .env instead.', ]); - - // Verify the switch happened - expect(CoolifyResource::find($staging->id)->is_default)->toBeTrue(); - expect(CoolifyResource::find($prod->id)->is_default)->toBeFalse(); }); - it('returns 404 for non-existent environment', function () { + it('returns same error for any environment id', function () { + // Any id should return the same error since multi-environment is removed $response = $this->postJson(route('coolify.environments.switch', 999)); - $response->assertNotFound(); - }); - - it('returns empty array when no environments configured', function () { - $response = $this->getJson(route('coolify.environments.index')); - - $response->assertOk() - ->assertJsonCount(0); + $response->assertStatus(400) + ->assertJson([ + 'success' => false, + ]); }); }); diff --git a/tests/TestCase.php b/tests/TestCase.php index 4aad2b4..ff718cd 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,10 +2,8 @@ namespace Stumason\Coolify\Tests; -use Illuminate\Support\Facades\Schema; use Orchestra\Testbench\TestCase as BaseTestCase; use Stumason\Coolify\CoolifyServiceProvider; -use Stumason\Coolify\Models\CoolifyResource; abstract class TestCase extends BaseTestCase { @@ -56,59 +54,7 @@ protected function defineEnvironment($app): void // Coolify configuration $app['config']->set('coolify.url', 'https://coolify.test'); $app['config']->set('coolify.token', 'test-token'); + $app['config']->set('coolify.project_uuid', 'test-project-uuid'); $app['config']->set('coolify.cache_ttl', 0); } - - /** - * Set up the test case. - */ - protected function setUp(): void - { - parent::setUp(); - - // Create table if not exists - if (! Schema::hasTable('coolify_resources')) { - Schema::create('coolify_resources', function ($table) { - $table->id(); - $table->string('name')->unique(); - $table->string('server_uuid'); - $table->string('project_uuid'); - $table->string('environment')->default('production'); - $table->string('deploy_key_uuid')->nullable(); - $table->string('repository')->nullable(); - $table->string('branch')->nullable(); - $table->string('application_uuid')->nullable(); - $table->string('database_uuid')->nullable(); - $table->string('redis_uuid')->nullable(); - $table->boolean('is_default')->default(false)->index(); - $table->json('metadata')->nullable(); - $table->timestamps(); - }); - } - - // Create default test resource - $this->createDefaultResource(); - } - - /** - * Create a default CoolifyResource for testing. - */ - protected function createDefaultResource(): CoolifyResource - { - return CoolifyResource::updateOrCreate( - ['name' => 'test-app'], - [ - 'server_uuid' => 'test-server-uuid', - 'project_uuid' => 'test-project-uuid', - 'environment' => 'production', - 'deploy_key_uuid' => 'test-deploy-key-uuid', - 'repository' => 'test/repo', - 'branch' => 'main', - 'application_uuid' => 'test-app-uuid', - 'database_uuid' => 'test-db-uuid', - 'redis_uuid' => 'test-redis-uuid', - 'is_default' => true, - ] - ); - } } diff --git a/tests/Unit/Console/StreamsDeploymentLogsTest.php b/tests/Unit/Console/StreamsDeploymentLogsTest.php index 3822e65..d054e53 100644 --- a/tests/Unit/Console/StreamsDeploymentLogsTest.php +++ b/tests/Unit/Console/StreamsDeploymentLogsTest.php @@ -3,6 +3,8 @@ use Illuminate\Support\Facades\Http; beforeEach(function () { + Http::preventStrayRequests(); + config([ 'coolify.url' => 'https://coolify.example.com', 'coolify.token' => 'test-token', @@ -21,10 +23,8 @@ ]), ]); - config(['coolify.application_uuid' => 'app-123']); - - // Just check that the --wait flag is accepted (without actually waiting) - $this->artisan('coolify:deploy', ['--force' => true]) + // Use --uuid to bypass git repository lookup + $this->artisan('coolify:deploy', ['--uuid' => 'app-123', '--force' => true]) ->expectsOutputToContain('Deployment triggered successfully') ->assertSuccessful(); }); @@ -40,9 +40,8 @@ ]), ]); - config(['coolify.application_uuid' => 'app-123']); - - $this->artisan('coolify:deploy', ['--force' => true, '--debug' => true]) + // Use --uuid to bypass git repository lookup + $this->artisan('coolify:deploy', ['--uuid' => 'app-123', '--force' => true, '--debug' => true]) ->expectsOutputToContain('Deployment triggered successfully') ->assertSuccessful(); }); diff --git a/tests/Unit/CoolifyTest.php b/tests/Unit/CoolifyTest.php index b65b862..6f34b1a 100644 --- a/tests/Unit/CoolifyTest.php +++ b/tests/Unit/CoolifyTest.php @@ -22,7 +22,7 @@ ->and(Coolify::services())->toBeInstanceOf(ServiceRepository::class); }); - it('deploys using configured UUID', function () { + it('deploys using explicit UUID', function () { Http::fake([ '*/deploy*' => Http::response([ 'deployments' => [[ @@ -33,7 +33,8 @@ ], 200), ]); - $result = Coolify::deploy(); + // Use explicit UUID to avoid git repository lookup + $result = Coolify::deploy('test-app-uuid'); expect($result['deployment_uuid'])->toBe('deploy-123'); }); @@ -54,7 +55,7 @@ expect($result['deployment_uuid'])->toBe('deploy-456'); }); - it('gets status using configured UUID', function () { + it('gets status using explicit UUID', function () { Http::fake([ '*/applications/test-app-uuid' => Http::response([ 'uuid' => 'test-app-uuid', @@ -62,19 +63,21 @@ ], 200), ]); - $result = Coolify::status(); + // Use explicit UUID to avoid git repository lookup + $result = Coolify::status('test-app-uuid'); expect($result['status'])->toBe('running'); }); - it('gets logs using configured UUID', function () { + it('gets logs using explicit UUID', function () { Http::fake([ '*/applications/test-app-uuid/logs*' => Http::response([ 'logs' => 'App logs...', ], 200), ]); - $result = Coolify::logs(); + // Use explicit UUID to avoid git repository lookup + $result = Coolify::logs('test-app-uuid'); expect($result['logs'])->toBe('App logs...'); });