diff --git a/.env.development.example b/.env.development.example index 920c32d92..ba0213f58 100644 --- a/.env.development.example +++ b/.env.development.example @@ -1,16 +1,34 @@ -APP_NAME=Coolify-localhost -APP_ID=development +# Coolify Configuration APP_ENV=local +APP_NAME="Coolify Development" +APP_ID=development APP_KEY= -APP_DEBUG=true APP_URL=http://localhost APP_PORT=8000 +APP_DEBUG=true MUX_ENABLED=false +# Enable Laravel Telescope for debugging +TELESCOPE_ENABLED=false + +# Selenium Driver URL for Dusk DUSK_DRIVER_URL=http://selenium:4444 -## For Andras only -# To purge cache +# PostgreSQL Database Configuration +DB_DATABASE=coolify +DB_USERNAME=coolify +DB_PASSWORD=password +DB_HOST=host.docker.internal +DB_PORT=5432 + +# Ray Configuration +# Set to true to enable Ray +RAY_ENABLED=false +# Set custom ray port +RAY_PORT= + +# Special Keys for Andras +# For cache purging BUNNY_API_KEY= -# To upload assets +# For asset uploads BUNNY_STORAGE_API_KEY= diff --git a/.env.production b/.env.production index f15a8b0e9..099ec7c25 100644 --- a/.env.production +++ b/.env.production @@ -1,10 +1,16 @@ +# Coolify Configuration APP_ID= APP_NAME=Coolify APP_KEY= +# PostgreSQL Database Configuration +DB_USERNAME=coolify DB_PASSWORD= + +# Redis Configuration REDIS_PASSWORD= +# Pusher Configuration PUSHER_APP_ID= PUSHER_APP_KEY= PUSHER_APP_SECRET= diff --git a/.github/workflows/coolify-helper-next.yml b/.github/workflows/coolify-helper-next.yml index d9921b363..3823e0707 100644 --- a/.github/workflows/coolify-helper-next.yml +++ b/.github/workflows/coolify-helper-next.yml @@ -25,6 +25,10 @@ jobs: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Get Version + id: version + run: | + echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT - name: Build image and push to registry uses: docker/build-push-action@v5 with: @@ -33,7 +37,7 @@ jobs: file: docker/coolify-helper/Dockerfile platforms: linux/amd64 push: true - tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next aarch64: runs-on: [ self-hosted, arm64 ] permissions: @@ -47,6 +51,10 @@ jobs: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Get Version + id: version + run: | + echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT - name: Build image and push to registry uses: docker/build-push-action@v5 with: @@ -55,7 +63,7 @@ jobs: file: docker/coolify-helper/Dockerfile platforms: linux/aarch64 push: true - tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64 + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 merge-manifest: runs-on: ubuntu-latest permissions: @@ -75,9 +83,13 @@ jobs: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Get Version + id: version + run: | + echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT - name: Create & publish manifest run: | - docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next + docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next - uses: sarisia/actions-status-discord@v1 if: always() with: diff --git a/.github/workflows/coolify-helper.yml b/.github/workflows/coolify-helper.yml index 7e8132ec6..37199919a 100644 --- a/.github/workflows/coolify-helper.yml +++ b/.github/workflows/coolify-helper.yml @@ -25,6 +25,10 @@ jobs: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Get Version + id: version + run: | + echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT - name: Build image and push to registry uses: docker/build-push-action@v5 with: @@ -33,7 +37,7 @@ jobs: file: docker/coolify-helper/Dockerfile platforms: linux/amd64 push: true - tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} aarch64: runs-on: [ self-hosted, arm64 ] permissions: @@ -47,6 +51,10 @@ jobs: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Get Version + id: version + run: | + echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT - name: Build image and push to registry uses: docker/build-push-action@v5 with: @@ -55,7 +63,7 @@ jobs: file: docker/coolify-helper/Dockerfile platforms: linux/aarch64 push: true - tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 merge-manifest: runs-on: ubuntu-latest permissions: @@ -75,9 +83,13 @@ jobs: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Get Version + id: version + run: | + echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT - name: Create & publish manifest run: | - docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest - uses: sarisia/actions-status-discord@v1 if: always() with: diff --git a/.github/workflows/production-build.yml b/.github/workflows/production-build.yml index e4bad6a65..c78c865bf 100644 --- a/.github/workflows/production-build.yml +++ b/.github/workflows/production-build.yml @@ -4,6 +4,8 @@ on: push: branches: ["main"] paths-ignore: + - .github/workflows/coolify-helper.yml + - docker/coolify-helper/Dockerfile - templates/service-templates.json env: diff --git a/README.md b/README.md index d840739d7..c3412be14 100644 --- a/README.md +++ b/README.md @@ -35,22 +35,32 @@ Thank you so much! Special thanks to our biggest sponsors! -cccareers logo -hetzner logo -logto logo -bc direct logo -quantcdn logo -arcjet logo -supaguide logo -tigris logo -fractal logo -advin logo -trieve logo -blacksmith logo -latitude logo -branddev logo -jobscollider logo -hostinger logo +### Special Sponsors + +![image](https://github.com/user-attachments/assets/c95a07df-7c5a-4e77-a35a-81f25fcbece1) + +* [CCCareers](https://cccareers.org/) - A career development platform connecting coding bootcamp graduates with job opportunities in the tech industry. +* [Hetzner](http://htznr.li/CoolifyXHetzner) - A German web hosting company offering affordable dedicated servers, cloud services, and web hosting solutions. +* [Logto](https://logto.io/?ref=coolify) - An open-source authentication and authorization solution for building secure login systems and managing user identities. +* [BC Direct](https://bc.direct/?ref=coolify.io) - A digital marketing agency specializing in e-commerce solutions and online business growth strategies. +* [QuantCDN](https://www.quantcdn.io/?ref=coolify.io) - A content delivery network (CDN) optimizing website performance through global content distribution. +* [Arcjet](https://arcjet.com/?ref=coolify.io) - A cloud-based platform providing real-time protection against API abuse and bot attacks. +* [SupaGuide](https://supa.guide/?ref=coolify.io) - A comprehensive resource hub offering guides and tutorials for web development using Supabase. +* [Tigris](https://tigrisdata.com/?ref=coolify.io) - A fully managed serverless object storage service compatible with Amazon S3 API. Offers high performance, scalability, and built-in search capabilities for efficient data management. +* [Fractal Networks](https://fractalnetworks.co/?ref=coolify.io) - A decentralized network infrastructure company focusing on secure and private communication solutions. +* [Advin](https://coolify.ad.vin/?ref=coolify.io) - A digital advertising agency specializing in programmatic advertising and data-driven marketing strategies. +* [Treive](https://trieve.ai/?ref=coolify.io) - An AI-powered search and discovery platform for enhancing information retrieval in large datasets. +* [Blacksmith](https://blacksmith.sh/?ref=coolify.io) - A cloud-native platform for automating infrastructure provisioning and management across multiple cloud providers. +* [Latitude](https://latitude.sh/?ref=coolify.io) - A cloud computing platform offering bare metal servers and cloud instances for developers and businesses. +* [Brand Dev](https://brand.dev/?ref=coolify.io) - A web development agency specializing in creating custom digital experiences and brand identities. +* [Jobscollider](https://jobscollider.com/remote-jobs?ref=coolify.io) - A job search platform connecting professionals with remote work opportunities across various industries. +* [Hostinger](https://hostinger.com?ref=coolify.io) - A web hosting provider offering affordable hosting solutions, domain registration, and website building tools. +* [Glueops](https://www.glueops.dev/?ref=coolify.io) - A DevOps consulting company providing infrastructure automation and cloud optimization services. +* [Ubicloud](https://ubicloud.com/?ref=coolify.io) - An open-source alternative to hyperscale cloud providers, offering high-performance cloud computing services. +* [Juxtdigital](https://juxtdigital.dev/?ref=coolify.io) - A digital agency offering web development, design, and digital marketing services for businesses. +* [Saasykit](https://saasykit.com/?ref=coolify.io) - A Laravel-based boilerplate providing essential components and features for building SaaS applications quickly. +* [Massivegrid](https://massivegrid.com/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes. + ## Github Sponsors ($40+) SerpAPI @@ -77,6 +87,7 @@ Special thanks to our biggest sponsors! Paweł Pierścionek Michael Mazurczak Formbricks +Adith Suhas ## Organizations diff --git a/app/Actions/Server/UpdateCoolify.php b/app/Actions/Server/UpdateCoolify.php index 09d6471e8..c4af6bb21 100644 --- a/app/Actions/Server/UpdateCoolify.php +++ b/app/Actions/Server/UpdateCoolify.php @@ -4,8 +4,6 @@ namespace App\Actions\Server; use App\Models\InstanceSettings; use App\Models\Server; -use Illuminate\Support\Facades\File; -use Illuminate\Support\Facades\Http; use Lorisleiva\Actions\Concerns\AsAction; class UpdateCoolify @@ -27,11 +25,6 @@ class UpdateCoolify return; } CleanupDocker::dispatch($this->server)->onQueue('high'); - $response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json'); - if ($response->successful()) { - $versions = $response->json(); - File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT)); - } $this->latestVersion = get_latest_version_of_coolify(); $this->currentVersion = config('version'); if (! $manual_update) { @@ -62,10 +55,11 @@ class UpdateCoolify return; } + instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$this->latestVersion}"], $this->server, false); + remote_process([ 'curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh', "bash /data/coolify/source/upgrade.sh $this->latestVersion", ], $this->server); - } } diff --git a/app/Console/Commands/CleanupStuckedResources.php b/app/Console/Commands/CleanupStuckedResources.php index 0a544d2ff..68beb448a 100644 --- a/app/Console/Commands/CleanupStuckedResources.php +++ b/app/Console/Commands/CleanupStuckedResources.php @@ -4,6 +4,7 @@ namespace App\Console\Commands; use App\Models\Application; use App\Models\ApplicationPreview; +use App\Models\ScheduledDatabaseBackup; use App\Models\ScheduledTask; use App\Models\Service; use App\Models\ServiceApplication; @@ -165,6 +166,18 @@ class CleanupStuckedResources extends Command echo "Error in cleaning stuck scheduledtasks: {$e->getMessage()}\n"; } + try { + $scheduled_backups = ScheduledDatabaseBackup::all(); + foreach ($scheduled_backups as $scheduled_backup) { + if (! $scheduled_backup->server()) { + echo "Deleting stuck scheduledbackup: {$scheduled_backup->name}\n"; + $scheduled_backup->delete(); + } + } + } catch (\Throwable $e) { + echo "Error in cleaning stuck scheduledbackups: {$e->getMessage()}\n"; + } + // Cleanup any resources that are not attached to any environment or destination or server try { $applications = Application::all(); diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index c2c787be4..96740ab24 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -6,7 +6,6 @@ use App\Jobs\CheckForUpdatesJob; use App\Jobs\CleanupInstanceStuffsJob; use App\Jobs\DatabaseBackupJob; use App\Jobs\DockerCleanupJob; -use App\Jobs\PullCoolifyImageJob; use App\Jobs\PullHelperImageJob; use App\Jobs\PullSentinelImageJob; use App\Jobs\PullTemplatesFromCDN; @@ -44,7 +43,6 @@ class Kernel extends ConsoleKernel // Instance Jobs $schedule->command('horizon:snapshot')->everyFiveMinutes(); $schedule->command('cleanup:unreachable-servers')->daily()->onOneServer(); - $schedule->job(new PullCoolifyImageJob)->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer(); $schedule->job(new PullTemplatesFromCDN)->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer(); $schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer(); $this->schedule_updates($schedule); @@ -139,6 +137,10 @@ class Kernel extends ConsoleKernel } $server = $scheduled_backup->server(); + + if (! $server) { + continue; + } $serverTimezone = $server->settings->server_timezone; if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) { @@ -181,6 +183,9 @@ class Kernel extends ConsoleKernel } $server = $scheduled_task->server(); + if (! $server) { + continue; + } $serverTimezone = $server->settings->server_timezone ?: config('app.timezone'); if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) { diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 413555194..81b173011 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -53,6 +53,7 @@ class ApplicationsController extends Controller summary: 'List', description: 'List all applications.', path: '/applications', + operationId: 'list-applications', security: [ ['bearerAuth' => []], ], @@ -101,6 +102,7 @@ class ApplicationsController extends Controller summary: 'Create (Public)', description: 'Create new application based on a public git repository.', path: '/applications/public', + operationId: 'create-public-application', security: [ ['bearerAuth' => []], ], @@ -202,6 +204,7 @@ class ApplicationsController extends Controller summary: 'Create (Private - GH App)', description: 'Create new application based on a private repository through a Github App.', path: '/applications/private-github-app', + operationId: 'create-private-github-app-application', security: [ ['bearerAuth' => []], ], @@ -303,6 +306,7 @@ class ApplicationsController extends Controller summary: 'Create (Private - Deploy Key)', description: 'Create new application based on a private repository through a Deploy Key.', path: '/applications/private-deploy-key', + operationId: 'create-private-deploy-key-application', security: [ ['bearerAuth' => []], ], @@ -404,6 +408,7 @@ class ApplicationsController extends Controller summary: 'Create (Dockerfile)', description: 'Create new application based on a simple Dockerfile.', path: '/applications/dockerfile', + operationId: 'create-dockerfile-application', security: [ ['bearerAuth' => []], ], @@ -490,6 +495,7 @@ class ApplicationsController extends Controller summary: 'Create (Docker Image)', description: 'Create new application based on a prebuilt docker image', path: '/applications/dockerimage', + operationId: 'create-dockerimage-application', security: [ ['bearerAuth' => []], ], @@ -573,6 +579,7 @@ class ApplicationsController extends Controller summary: 'Create (Docker Compose)', description: 'Create new application based on a docker-compose file.', path: '/applications/dockercompose', + operationId: 'create-dockercompose-application', security: [ ['bearerAuth' => []], ], @@ -1171,6 +1178,7 @@ class ApplicationsController extends Controller summary: 'Get', description: 'Get application by UUID.', path: '/applications/{uuid}', + operationId: 'get-application-by-uuid', security: [ ['bearerAuth' => []], ], @@ -1235,6 +1243,7 @@ class ApplicationsController extends Controller summary: 'Delete', description: 'Delete application by UUID.', path: '/applications/{uuid}', + operationId: 'delete-application-by-uuid', security: [ ['bearerAuth' => []], ], @@ -1321,6 +1330,7 @@ class ApplicationsController extends Controller summary: 'Update', description: 'Update application by UUID.', path: '/applications/{uuid}', + operationId: 'update-application-by-uuid', security: [ ['bearerAuth' => []], ], @@ -1557,6 +1567,7 @@ class ApplicationsController extends Controller summary: 'List Envs', description: 'List all envs by application UUID.', path: '/applications/{uuid}/envs', + operationId: 'list-envs-by-application-uuid', security: [ ['bearerAuth' => []], ], @@ -1639,6 +1650,7 @@ class ApplicationsController extends Controller summary: 'Update Env', description: 'Update env by application UUID.', path: '/applications/{uuid}/envs', + operationId: 'update-env-by-application-uuid', security: [ ['bearerAuth' => []], ], @@ -1821,6 +1833,7 @@ class ApplicationsController extends Controller summary: 'Update Envs (Bulk)', description: 'Update multiple envs by application UUID.', path: '/applications/{uuid}/envs/bulk', + operationId: 'update-envs-by-application-uuid', security: [ ['bearerAuth' => []], ], @@ -2012,6 +2025,7 @@ class ApplicationsController extends Controller summary: 'Create Env', description: 'Create env by application UUID.', path: '/applications/{uuid}/envs', + operationId: 'create-env-by-application-uuid', security: [ ['bearerAuth' => []], ], @@ -2171,6 +2185,7 @@ class ApplicationsController extends Controller summary: 'Delete Env', description: 'Delete env by UUID.', path: '/applications/{uuid}/envs/{env_uuid}', + operationId: 'delete-env-by-application-uuid', security: [ ['bearerAuth' => []], ], @@ -2256,6 +2271,7 @@ class ApplicationsController extends Controller summary: 'Start', description: 'Start application. `Post` request is also accepted.', path: '/applications/{uuid}/start', + operationId: 'start-application-by-uuid', security: [ ['bearerAuth' => []], ], @@ -2359,6 +2375,7 @@ class ApplicationsController extends Controller summary: 'Stop', description: 'Stop application. `Post` request is also accepted.', path: '/applications/{uuid}/stop', + operationId: 'stop-application-by-uuid', security: [ ['bearerAuth' => []], ], @@ -2431,6 +2448,7 @@ class ApplicationsController extends Controller summary: 'Restart', description: 'Restart application. `Post` request is also accepted.', path: '/applications/{uuid}/restart', + operationId: 'restart-application-by-uuid', security: [ ['bearerAuth' => []], ], diff --git a/app/Http/Controllers/Api/DatabasesController.php b/app/Http/Controllers/Api/DatabasesController.php index 0f1ee00d8..a205704cc 100644 --- a/app/Http/Controllers/Api/DatabasesController.php +++ b/app/Http/Controllers/Api/DatabasesController.php @@ -46,6 +46,7 @@ class DatabasesController extends Controller summary: 'List', description: 'List all databases.', path: '/databases', + operationId: 'list-databases', security: [ ['bearerAuth' => []], ], @@ -91,6 +92,7 @@ class DatabasesController extends Controller summary: 'Get', description: 'Get database by UUID.', path: '/databases/{uuid}', + operationId: 'get-database-by-uuid', security: [ ['bearerAuth' => []], ], @@ -151,6 +153,7 @@ class DatabasesController extends Controller summary: 'Update', description: 'Update database by UUID.', path: '/databases/{uuid}', + operationId: 'update-database-by-uuid', security: [ ['bearerAuth' => []], ], @@ -510,6 +513,7 @@ class DatabasesController extends Controller summary: 'Create (PostgreSQL)', description: 'Create a new PostgreSQL database.', path: '/databases/postgresql', + operationId: 'create-database-postgresql', security: [ ['bearerAuth' => []], ], @@ -575,6 +579,7 @@ class DatabasesController extends Controller summary: 'Create (Clickhouse)', description: 'Create a new Clickhouse database.', path: '/databases/clickhouse', + operationId: 'create-database-clickhouse', security: [ ['bearerAuth' => []], ], @@ -636,6 +641,7 @@ class DatabasesController extends Controller summary: 'Create (DragonFly)', description: 'Create a new DragonFly database.', path: '/databases/dragonfly', + operationId: 'create-database-dragonfly', security: [ ['bearerAuth' => []], ], @@ -696,6 +702,7 @@ class DatabasesController extends Controller summary: 'Create (Redis)', description: 'Create a new Redis database.', path: '/databases/redis', + operationId: 'create-database-redis', security: [ ['bearerAuth' => []], ], @@ -757,6 +764,7 @@ class DatabasesController extends Controller summary: 'Create (KeyDB)', description: 'Create a new KeyDB database.', path: '/databases/keydb', + operationId: 'create-database-keydb', security: [ ['bearerAuth' => []], ], @@ -818,6 +826,7 @@ class DatabasesController extends Controller summary: 'Create (MariaDB)', description: 'Create a new MariaDB database.', path: '/databases/mariadb', + operationId: 'create-database-mariadb', security: [ ['bearerAuth' => []], ], @@ -882,6 +891,7 @@ class DatabasesController extends Controller summary: 'Create (MySQL)', description: 'Create a new MySQL database.', path: '/databases/mysql', + operationId: 'create-database-mysql', security: [ ['bearerAuth' => []], ], @@ -945,6 +955,7 @@ class DatabasesController extends Controller summary: 'Create (MongoDB)', description: 'Create a new MongoDB database.', path: '/databases/mongodb', + operationId: 'create-database-mongodb', security: [ ['bearerAuth' => []], ], @@ -1514,6 +1525,7 @@ class DatabasesController extends Controller summary: 'Delete', description: 'Delete database by UUID.', path: '/databases/{uuid}', + operationId: 'delete-database-by-uuid', security: [ ['bearerAuth' => []], ], @@ -1597,6 +1609,7 @@ class DatabasesController extends Controller summary: 'Start', description: 'Start database. `Post` request is also accepted.', path: '/databases/{uuid}/start', + operationId: 'start-database-by-uuid', security: [ ['bearerAuth' => []], ], @@ -1672,6 +1685,7 @@ class DatabasesController extends Controller summary: 'Stop', description: 'Stop database. `Post` request is also accepted.', path: '/databases/{uuid}/stop', + operationId: 'stop-database-by-uuid', security: [ ['bearerAuth' => []], ], @@ -1747,6 +1761,7 @@ class DatabasesController extends Controller summary: 'Restart', description: 'Restart database. `Post` request is also accepted.', path: '/databases/{uuid}/restart', + operationId: 'restart-database-by-uuid', security: [ ['bearerAuth' => []], ], diff --git a/app/Http/Controllers/Api/DeployController.php b/app/Http/Controllers/Api/DeployController.php index 437162058..96f98d844 100644 --- a/app/Http/Controllers/Api/DeployController.php +++ b/app/Http/Controllers/Api/DeployController.php @@ -32,6 +32,7 @@ class DeployController extends Controller summary: 'List', description: 'List currently running deployments', path: '/deployments', + operationId: 'list-deployments', security: [ ['bearerAuth' => []], ], @@ -79,6 +80,7 @@ class DeployController extends Controller summary: 'Get', description: 'Get deployment by UUID.', path: '/deployments/{uuid}', + operationId: 'get-deployment-by-uuid', security: [ ['bearerAuth' => []], ], @@ -134,6 +136,7 @@ class DeployController extends Controller summary: 'Deploy', description: 'Deploy by tag or uuid. `Post` request also accepted.', path: '/deploy', + operationId: 'deploy-by-tag-or-uuid', security: [ ['bearerAuth' => []], ], diff --git a/app/Http/Controllers/Api/OtherController.php b/app/Http/Controllers/Api/OtherController.php index 1e48ffdbe..c085b88a5 100644 --- a/app/Http/Controllers/Api/OtherController.php +++ b/app/Http/Controllers/Api/OtherController.php @@ -13,6 +13,7 @@ class OtherController extends Controller summary: 'Version', description: 'Get Coolify version.', path: '/version', + operationId: 'version', security: [ ['bearerAuth' => []], ], @@ -43,6 +44,7 @@ class OtherController extends Controller summary: 'Enable API', description: 'Enable API (only with root permissions).', path: '/enable', + operationId: 'enable-api', security: [ ['bearerAuth' => []], ], @@ -94,6 +96,7 @@ class OtherController extends Controller summary: 'Disable API', description: 'Disable API (only with root permissions).', path: '/disable', + operationId: 'disable-api', security: [ ['bearerAuth' => []], ], @@ -158,6 +161,7 @@ class OtherController extends Controller summary: 'Healthcheck', description: 'Healthcheck endpoint.', path: '/healthcheck', + operationId: 'healthcheck', responses: [ new OA\Response( response: 200, diff --git a/app/Http/Controllers/Api/ProjectController.php b/app/Http/Controllers/Api/ProjectController.php index 6aec31e9b..75721ff54 100644 --- a/app/Http/Controllers/Api/ProjectController.php +++ b/app/Http/Controllers/Api/ProjectController.php @@ -13,6 +13,7 @@ class ProjectController extends Controller summary: 'List', description: 'list projects.', path: '/projects', + operationId: 'list-projects', security: [ ['bearerAuth' => []], ], @@ -56,6 +57,7 @@ class ProjectController extends Controller summary: 'Get', description: 'Get project by Uuid.', path: '/projects/{uuid}', + operationId: 'get-project-by-uuid', security: [ ['bearerAuth' => []], ], @@ -102,6 +104,7 @@ class ProjectController extends Controller summary: 'Environment', description: 'Get environment by name.', path: '/projects/{uuid}/{environment_name}', + operationId: 'get-environment-by-name', security: [ ['bearerAuth' => []], ], @@ -142,6 +145,9 @@ class ProjectController extends Controller return response()->json(['message' => 'Environment name is required.'], 422); } $project = Project::whereTeamId($teamId)->whereUuid($request->uuid)->first(); + if (! $project) { + return response()->json(['message' => 'Project not found.'], 404); + } $environment = $project->environments()->whereName($request->environment_name)->first(); if (! $environment) { return response()->json(['message' => 'Environment not found.'], 404); @@ -155,6 +161,7 @@ class ProjectController extends Controller summary: 'Create', description: 'Create Project.', path: '/projects', + operationId: 'create-project', security: [ ['bearerAuth' => []], ], @@ -167,7 +174,7 @@ class ProjectController extends Controller schema: new OA\Schema( type: 'object', properties: [ - 'uuid' => ['type' => 'string', 'description' => 'The name of the project.'], + 'name' => ['type' => 'string', 'description' => 'The name of the project.'], 'description' => ['type' => 'string', 'description' => 'The description of the project.'], ], ), @@ -250,6 +257,7 @@ class ProjectController extends Controller summary: 'Update', description: 'Update Project.', path: '/projects/{uuid}', + operationId: 'update-project-by-uuid', security: [ ['bearerAuth' => []], ], @@ -355,6 +363,7 @@ class ProjectController extends Controller summary: 'Delete', description: 'Delete project by UUID.', path: '/projects/{uuid}', + operationId: 'delete-project-by-uuid', security: [ ['bearerAuth' => []], ], diff --git a/app/Http/Controllers/Api/ResourcesController.php b/app/Http/Controllers/Api/ResourcesController.php index ae076bb71..1fd5792e0 100644 --- a/app/Http/Controllers/Api/ResourcesController.php +++ b/app/Http/Controllers/Api/ResourcesController.php @@ -13,6 +13,7 @@ class ResourcesController extends Controller summary: 'List', description: 'Get all resources.', path: '/resources', + operationId: 'list-resources', security: [ ['bearerAuth' => []], ], diff --git a/app/Http/Controllers/Api/SecurityController.php b/app/Http/Controllers/Api/SecurityController.php index 67128234e..3a489f647 100644 --- a/app/Http/Controllers/Api/SecurityController.php +++ b/app/Http/Controllers/Api/SecurityController.php @@ -26,6 +26,7 @@ class SecurityController extends Controller summary: 'List', description: 'List all private keys.', path: '/security/keys', + operationId: 'list-private-keys', security: [ ['bearerAuth' => []], ], @@ -68,6 +69,7 @@ class SecurityController extends Controller summary: 'Get', description: 'Get key by UUID.', path: '/security/keys/{uuid}', + operationId: 'get-private-key-by-uuid', security: [ ['bearerAuth' => []], ], @@ -124,6 +126,7 @@ class SecurityController extends Controller summary: 'Create', description: 'Create a new private key.', path: '/security/keys', + operationId: 'create-private-key', security: [ ['bearerAuth' => []], ], @@ -217,6 +220,7 @@ class SecurityController extends Controller summary: 'Update', description: 'Update a private key.', path: '/security/keys', + operationId: 'update-private-key', security: [ ['bearerAuth' => []], ], @@ -313,6 +317,7 @@ class SecurityController extends Controller summary: 'Delete', description: 'Delete a private key.', path: '/security/keys/{uuid}', + operationId: 'delete-private-key-by-uuid', security: [ ['bearerAuth' => []], ], diff --git a/app/Http/Controllers/Api/ServersController.php b/app/Http/Controllers/Api/ServersController.php index 9044c4a35..e2bde52f7 100644 --- a/app/Http/Controllers/Api/ServersController.php +++ b/app/Http/Controllers/Api/ServersController.php @@ -46,6 +46,7 @@ class ServersController extends Controller summary: 'List', description: 'List all servers.', path: '/servers', + operationId: 'list-servers', security: [ ['bearerAuth' => []], ], @@ -100,6 +101,7 @@ class ServersController extends Controller summary: 'Get', description: 'Get server by UUID.', path: '/servers/{uuid}', + operationId: 'get-server-by-uuid', security: [ ['bearerAuth' => []], ], @@ -177,6 +179,7 @@ class ServersController extends Controller summary: 'Resources', description: 'Get resources by server.', path: '/servers/{uuid}/resources', + operationId: 'get-resources-by-server-uuid', security: [ ['bearerAuth' => []], ], @@ -254,6 +257,7 @@ class ServersController extends Controller summary: 'Domains', description: 'Get domains by server.', path: '/servers/{uuid}/domains', + operationId: 'get-domains-by-server-uuid', security: [ ['bearerAuth' => []], ], @@ -401,6 +405,7 @@ class ServersController extends Controller summary: 'Create', description: 'Create Server.', path: '/servers', + operationId: 'create-server', security: [ ['bearerAuth' => []], ], @@ -545,6 +550,7 @@ class ServersController extends Controller summary: 'Update', description: 'Update Server.', path: '/servers/{uuid}', + operationId: 'update-server-by-uuid', security: [ ['bearerAuth' => []], ], @@ -655,6 +661,7 @@ class ServersController extends Controller summary: 'Delete', description: 'Delete server by UUID.', path: '/servers/{uuid}', + operationId: 'delete-server-by-uuid', security: [ ['bearerAuth' => []], ], @@ -727,6 +734,7 @@ class ServersController extends Controller summary: 'Validate', description: 'Validate server by UUID.', path: '/servers/{uuid}/validate', + operationId: 'validate-server-by-uuid', security: [ ['bearerAuth' => []], ], diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index 3a5b59f55..37377a6bd 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -38,6 +38,7 @@ class ServicesController extends Controller summary: 'List', description: 'List all services.', path: '/services', + operationId: 'list-services', security: [ ['bearerAuth' => []], ], @@ -88,6 +89,7 @@ class ServicesController extends Controller summary: 'Create', description: 'Create a one-click service', path: '/services', + operationId: 'create-service', security: [ ['bearerAuth' => []], ], @@ -365,6 +367,7 @@ class ServicesController extends Controller summary: 'Get', description: 'Get service by UUID.', path: '/services/{uuid}', + operationId: 'get-service-by-uuid', security: [ ['bearerAuth' => []], ], @@ -422,6 +425,7 @@ class ServicesController extends Controller summary: 'Delete', description: 'Delete service by UUID.', path: '/services/{uuid}', + operationId: 'delete-service-by-uuid', security: [ ['bearerAuth' => []], ], @@ -479,10 +483,521 @@ class ServicesController extends Controller ]); } + #[OA\Get( + summary: 'List Envs', + description: 'List all envs by service UUID.', + path: '/services/{uuid}/envs', + operationId: 'list-envs-by-service-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Services'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the service.', + required: true, + schema: new OA\Schema( + type: 'string', + format: 'uuid', + ) + ), + ], + responses: [ + new OA\Response( + response: 200, + description: 'All environment variables by service UUID.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'array', + items: new OA\Items(ref: '#/components/schemas/EnvironmentVariable') + ) + ), + ]), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 400, + ref: '#/components/responses/400', + ), + new OA\Response( + response: 404, + ref: '#/components/responses/404', + ), + ] + )] + public function envs(Request $request) + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first(); + if (! $service) { + return response()->json(['message' => 'Service not found.'], 404); + } + + $envs = $service->environment_variables->map(function ($env) { + $env->makeHidden([ + 'application_id', + 'standalone_clickhouse_id', + 'standalone_dragonfly_id', + 'standalone_keydb_id', + 'standalone_mariadb_id', + 'standalone_mongodb_id', + 'standalone_mysql_id', + 'standalone_postgresql_id', + 'standalone_redis_id', + ]); + $env = $this->removeSensitiveData($env); + + return $env; + }); + + return response()->json($envs); + } + + #[OA\Patch( + summary: 'Update Env', + description: 'Update env by service UUID.', + path: '/services/{uuid}/envs', + operationId: 'update-env-by-service-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Services'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the service.', + required: true, + schema: new OA\Schema( + type: 'string', + format: 'uuid', + ) + ), + ], + requestBody: new OA\RequestBody( + description: 'Env updated.', + required: true, + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + required: ['key', 'value'], + properties: [ + 'key' => ['type' => 'string', 'description' => 'The key of the environment variable.'], + 'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'], + 'is_preview' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in preview deployments.'], + 'is_build_time' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in build time.'], + 'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'], + 'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'], + 'is_shown_once' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable\'s value is shown on the UI.'], + ], + ), + ), + ], + ), + responses: [ + new OA\Response( + response: 201, + description: 'Environment variable updated.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'message' => ['type' => 'string', 'example' => 'Environment variable updated.'], + ] + ) + ), + ]), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 400, + ref: '#/components/responses/400', + ), + new OA\Response( + response: 404, + ref: '#/components/responses/404', + ), + ] + )] + public function update_env_by_uuid(Request $request) + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + + $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first(); + if (! $service) { + return response()->json(['message' => 'Service not found.'], 404); + } + + $validator = customApiValidator($request->all(), [ + 'key' => 'string|required', + 'value' => 'string|nullable', + 'is_build_time' => 'boolean', + 'is_literal' => 'boolean', + 'is_multiline' => 'boolean', + 'is_shown_once' => 'boolean', + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $env = $service->environment_variables()->where('key', $request->key)->first(); + if (! $env) { + return response()->json(['message' => 'Environment variable not found.'], 404); + } + + $env->fill($request->all()); + $env->save(); + + return response()->json($this->removeSensitiveData($env))->setStatusCode(201); + } + + #[OA\Patch( + summary: 'Update Envs (Bulk)', + description: 'Update multiple envs by service UUID.', + path: '/services/{uuid}/envs/bulk', + operationId: 'update-envs-by-service-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Services'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the service.', + required: true, + schema: new OA\Schema( + type: 'string', + format: 'uuid', + ) + ), + ], + requestBody: new OA\RequestBody( + description: 'Bulk envs updated.', + required: true, + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + required: ['data'], + properties: [ + 'data' => [ + 'type' => 'array', + 'items' => new OA\Schema( + type: 'object', + properties: [ + 'key' => ['type' => 'string', 'description' => 'The key of the environment variable.'], + 'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'], + 'is_preview' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in preview deployments.'], + 'is_build_time' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in build time.'], + 'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'], + 'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'], + 'is_shown_once' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable\'s value is shown on the UI.'], + ], + ), + ], + ], + ), + ), + ], + ), + responses: [ + new OA\Response( + response: 201, + description: 'Environment variables updated.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'message' => ['type' => 'string', 'example' => 'Environment variables updated.'], + ] + ) + ), + ]), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 400, + ref: '#/components/responses/400', + ), + new OA\Response( + response: 404, + ref: '#/components/responses/404', + ), + ] + )] + public function create_bulk_envs(Request $request) + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + + $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first(); + if (! $service) { + return response()->json(['message' => 'Service not found.'], 404); + } + + $bulk_data = $request->get('data'); + if (! $bulk_data) { + return response()->json(['message' => 'Bulk data is required.'], 400); + } + + $updatedEnvs = collect(); + foreach ($bulk_data as $item) { + $validator = customApiValidator($item, [ + 'key' => 'string|required', + 'value' => 'string|nullable', + 'is_build_time' => 'boolean', + 'is_literal' => 'boolean', + 'is_multiline' => 'boolean', + 'is_shown_once' => 'boolean', + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $env = $service->environment_variables()->updateOrCreate( + ['key' => $item['key']], + $item + ); + + $updatedEnvs->push($this->removeSensitiveData($env)); + } + + return response()->json($updatedEnvs)->setStatusCode(201); + } + + #[OA\Post( + summary: 'Create Env', + description: 'Create env by service UUID.', + path: '/services/{uuid}/envs', + operationId: 'create-env-by-service-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Services'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the service.', + required: true, + schema: new OA\Schema( + type: 'string', + format: 'uuid', + ) + ), + ], + requestBody: new OA\RequestBody( + required: true, + description: 'Env created.', + content: new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'key' => ['type' => 'string', 'description' => 'The key of the environment variable.'], + 'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'], + 'is_preview' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in preview deployments.'], + 'is_build_time' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in build time.'], + 'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'], + 'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'], + 'is_shown_once' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable\'s value is shown on the UI.'], + ], + ), + ), + ), + responses: [ + new OA\Response( + response: 201, + description: 'Environment variable created.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'uuid' => ['type' => 'string', 'example' => 'nc0k04gk8g0cgsk440g0koko'], + ] + ) + ), + ]), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 400, + ref: '#/components/responses/400', + ), + new OA\Response( + response: 404, + ref: '#/components/responses/404', + ), + ] + )] + public function create_env(Request $request) + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + + $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first(); + if (! $service) { + return response()->json(['message' => 'Service not found.'], 404); + } + + $validator = customApiValidator($request->all(), [ + 'key' => 'string|required', + 'value' => 'string|nullable', + 'is_build_time' => 'boolean', + 'is_literal' => 'boolean', + 'is_multiline' => 'boolean', + 'is_shown_once' => 'boolean', + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $existingEnv = $service->environment_variables()->where('key', $request->key)->first(); + if ($existingEnv) { + return response()->json([ + 'message' => 'Environment variable already exists. Use PATCH request to update it.', + ], 409); + } + + $env = $service->environment_variables()->create($request->all()); + + return response()->json($this->removeSensitiveData($env))->setStatusCode(201); + } + + #[OA\Delete( + summary: 'Delete Env', + description: 'Delete env by UUID.', + path: '/services/{uuid}/envs/{env_uuid}', + operationId: 'delete-env-by-service-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Services'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the service.', + required: true, + schema: new OA\Schema( + type: 'string', + format: 'uuid', + ) + ), + new OA\Parameter( + name: 'env_uuid', + in: 'path', + description: 'UUID of the environment variable.', + required: true, + schema: new OA\Schema( + type: 'string', + format: 'uuid', + ) + ), + ], + responses: [ + new OA\Response( + response: 200, + description: 'Environment variable deleted.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'message' => ['type' => 'string', 'example' => 'Environment variable deleted.'], + ] + ) + ), + ]), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 400, + ref: '#/components/responses/400', + ), + new OA\Response( + response: 404, + ref: '#/components/responses/404', + ), + ] + )] + public function delete_env_by_uuid(Request $request) + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + + $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first(); + if (! $service) { + return response()->json(['message' => 'Service not found.'], 404); + } + + $env = EnvironmentVariable::where('uuid', $request->env_uuid) + ->where('service_id', $service->id) + ->first(); + + if (! $env) { + return response()->json(['message' => 'Environment variable not found.'], 404); + } + + $env->forceDelete(); + + return response()->json(['message' => 'Environment variable deleted.']); + } + #[OA\Get( summary: 'Start', description: 'Start service. `Post` request is also accepted.', path: '/services/{uuid}/start', + operationId: 'start-service-by-uuid', security: [ ['bearerAuth' => []], ], @@ -558,6 +1073,7 @@ class ServicesController extends Controller summary: 'Stop', description: 'Stop service. `Post` request is also accepted.', path: '/services/{uuid}/stop', + operationId: 'stop-service-by-uuid', security: [ ['bearerAuth' => []], ], @@ -633,6 +1149,7 @@ class ServicesController extends Controller summary: 'Restart', description: 'Restart service. `Post` request is also accepted.', path: '/services/{uuid}/restart', + operationId: 'restart-service-by-uuid', security: [ ['bearerAuth' => []], ], diff --git a/app/Http/Controllers/Api/TeamController.php b/app/Http/Controllers/Api/TeamController.php index 1a481e5ec..3f951c6f7 100644 --- a/app/Http/Controllers/Api/TeamController.php +++ b/app/Http/Controllers/Api/TeamController.php @@ -32,6 +32,7 @@ class TeamController extends Controller summary: 'List', description: 'Get all teams.', path: '/teams', + operationId: 'list-teams', security: [ ['bearerAuth' => []], ], @@ -79,6 +80,7 @@ class TeamController extends Controller summary: 'Get', description: 'Get team by TeamId.', path: '/teams/{id}', + operationId: 'get-team-by-id', security: [ ['bearerAuth' => []], ], @@ -129,6 +131,7 @@ class TeamController extends Controller summary: 'Members', description: 'Get members by TeamId.', path: '/teams/{id}/members', + operationId: 'get-members-by-team-id', security: [ ['bearerAuth' => []], ], @@ -189,6 +192,7 @@ class TeamController extends Controller summary: 'Authenticated Team', description: 'Get currently authenticated team.', path: '/teams/current', + operationId: 'get-current-team', security: [ ['bearerAuth' => []], ], @@ -225,6 +229,7 @@ class TeamController extends Controller summary: 'Authenticated Team Members', description: 'Get currently authenticated team members.', path: '/teams/current/members', + operationId: 'get-current-team-members', security: [ ['bearerAuth' => []], ], diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 1ab6e5543..a0195d1b9 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -12,6 +12,7 @@ use App\Models\ApplicationPreview; use App\Models\EnvironmentVariable; use App\Models\GithubApp; use App\Models\GitlabApp; +use App\Models\InstanceSettings; use App\Models\Server; use App\Models\StandaloneDocker; use App\Models\SwarmDocker; @@ -109,7 +110,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue private bool $is_debug_enabled; - private $build_args; + private Collection|string $build_args; private $env_args; @@ -168,6 +169,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $this->application_deployment_queue = ApplicationDeploymentQueue::find($application_deployment_queue_id); $this->application = Application::find($this->application_deployment_queue->application_id); $this->build_pack = data_get($this->application, 'build_pack'); + $this->build_args = collect([]); $this->application_deployment_queue_id = $application_deployment_queue_id; $this->deployment_uuid = $this->application_deployment_queue->deployment_uuid; @@ -1292,7 +1294,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue private function prepare_builder_image() { + $settings = InstanceSettings::get(); $helperImage = config('coolify.helper_image'); + $helperImage = "{$helperImage}:{$settings->helper_version}"; // Get user home directory $this->serverUserHomeDir = instant_remote_process(['echo $HOME'], $this->server); $this->dockerConfigFileExists = instant_remote_process(["test -f {$this->serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $this->server); diff --git a/app/Jobs/ApplicationRestartJob.php b/app/Jobs/ApplicationRestartJob.php deleted file mode 100644 index 54c062197..000000000 --- a/app/Jobs/ApplicationRestartJob.php +++ /dev/null @@ -1,32 +0,0 @@ -applicationDeploymentQueueId = $applicationDeploymentQueueId; - } - - public function handle() - { - ray('Restarting application'); - } -} diff --git a/app/Jobs/CheckForUpdatesJob.php b/app/Jobs/CheckForUpdatesJob.php index 86b66fbfb..ddc264839 100644 --- a/app/Jobs/CheckForUpdatesJob.php +++ b/app/Jobs/CheckForUpdatesJob.php @@ -10,6 +10,7 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Http; +use Illuminate\Support\Facades\File; class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue { @@ -25,12 +26,14 @@ class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue $response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json'); if ($response->successful()) { $versions = $response->json(); + $latest_version = data_get($versions, 'coolify.v4.version'); $current_version = config('version'); if (version_compare($latest_version, $current_version, '>')) { // New version available $settings->update(['new_version_available' => true]); + File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT)); } else { $settings->update(['new_version_available' => false]); } diff --git a/app/Jobs/CheckLogDrainContainerJob.php b/app/Jobs/CheckLogDrainContainerJob.php deleted file mode 100644 index 16ef85192..000000000 --- a/app/Jobs/CheckLogDrainContainerJob.php +++ /dev/null @@ -1,93 +0,0 @@ -server->id))->dontRelease()]; - } - - public function uniqueId(): int - { - return $this->server->id; - } - - public function healthcheck() - { - $status = instant_remote_process(["docker inspect --format='{{json .State.Status}}' coolify-log-drain"], $this->server, false); - if (str($status)->contains('running')) { - return true; - } else { - return false; - } - } - - public function handle() - { - // ray("checking log drain statuses for {$this->server->id}"); - try { - if (! $this->server->isFunctional()) { - return; - } - $containers = instant_remote_process(['docker container ls -q'], $this->server, false); - if (! $containers) { - return; - } - $containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server); - $containers = format_docker_command_output_to_json($containers); - - $foundLogDrainContainer = $containers->filter(function ($value, $key) { - return data_get($value, 'Name') === '/coolify-log-drain'; - })->first(); - if (! $foundLogDrainContainer || ! $this->healthcheck()) { - ray('Log drain container not found or unhealthy. Restarting...'); - InstallLogDrain::run($this->server); - Sleep::for(10)->seconds(); - if ($this->healthcheck()) { - if ($this->server->log_drain_notification_sent) { - $this->server->team?->notify(new ContainerRestarted('Coolify Log Drainer', $this->server)); - $this->server->update(['log_drain_notification_sent' => false]); - } - - return; - } - if (! $this->server->log_drain_notification_sent) { - ray('Log drain container still unhealthy. Sending notification...'); - // $this->server->team?->notify(new ContainerStopped('Coolify Log Drainer', $this->server, null)); - $this->server->update(['log_drain_notification_sent' => true]); - } - } else { - if ($this->server->log_drain_notification_sent) { - $this->server->team?->notify(new ContainerRestarted('Coolify Log Drainer', $this->server)); - $this->server->update(['log_drain_notification_sent' => false]); - } - } - } catch (\Throwable $e) { - if (! isCloud()) { - send_internal_notification("CheckLogDrainContainerJob failed on ({$this->server->id}) with: ".$e->getMessage()); - } - ray($e->getMessage()); - - return handleError($e); - } - } -} diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php index 0666f78c3..5d481199b 100644 --- a/app/Jobs/DatabaseBackupJob.php +++ b/app/Jobs/DatabaseBackupJob.php @@ -25,6 +25,7 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Str; +use App\Models\InstanceSettings; class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue { @@ -493,12 +494,15 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue } else { $network = $this->database->destination->network; } - $commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro ghcr.io/coollabsio/coolify-helper"; + + $this->ensureHelperImageAvailable(); + + $fullImageName = $this->getFullImageName(); + $commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}"; $commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret"; $commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/"; instant_remote_process($commands, $this->server); $this->add_to_backup_output('Uploaded to S3.'); - ray('Uploaded to S3. '.$this->backup_location.' to s3://'.$bucket.$this->backup_dir); } catch (\Throwable $e) { $this->add_to_backup_output($e->getMessage()); throw $e; @@ -507,4 +511,40 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue instant_remote_process([$command], $this->server); } } + + private function ensureHelperImageAvailable(): void + { + $fullImageName = $this->getFullImageName(); + + $imageExists = $this->checkImageExists($fullImageName); + + if (!$imageExists) { + $this->pullHelperImage($fullImageName); + } + } + + private function checkImageExists(string $fullImageName): bool + { + $result = instant_remote_process(["docker image inspect {$fullImageName} >/dev/null 2>&1 && echo 'exists' || echo 'not exists'"], $this->server, false); + return trim($result) === 'exists'; + } + + private function pullHelperImage(string $fullImageName): void + { + try { + instant_remote_process(["docker pull {$fullImageName}"], $this->server); + } catch (\Exception $e) { + $errorMessage = "Failed to pull helper image: " . $e->getMessage(); + $this->add_to_backup_output($errorMessage); + throw new \RuntimeException($errorMessage); + } + } + + private function getFullImageName(): string + { + $settings = InstanceSettings::get(); + $helperImage = config('coolify.helper_image'); + $latestVersion = $settings->helper_version; + return "{$helperImage}:{$latestVersion}"; + } } diff --git a/app/Jobs/InstanceAutoUpdateJob.php b/app/Jobs/InstanceAutoUpdateJob.php deleted file mode 100644 index 1bbfcf8cb..000000000 --- a/app/Jobs/InstanceAutoUpdateJob.php +++ /dev/null @@ -1,28 +0,0 @@ -get('https://cdn.coollabs.io/coolify/versions.json'); - if ($response->successful()) { - $versions = $response->json(); - File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT)); - } - $latest_version = get_latest_version_of_coolify(); - instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$latest_version}"], $server, false); - - $current_version = config('version'); - if (! $settings->is_auto_update_enabled) { - return; - } - if ($latest_version === $current_version) { - return; - } - if (version_compare($latest_version, $current_version, '<')) { - return; - } - } catch (\Throwable $e) { - throw $e; - } - } -} diff --git a/app/Jobs/PullHelperImageJob.php b/app/Jobs/PullHelperImageJob.php index 30a1b8026..420119069 100644 --- a/app/Jobs/PullHelperImageJob.php +++ b/app/Jobs/PullHelperImageJob.php @@ -2,6 +2,7 @@ namespace App\Jobs; +use App\Models\InstanceSettings; use App\Models\Server; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeEncrypted; @@ -10,6 +11,7 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\Http; class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue { @@ -32,10 +34,20 @@ class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue public function handle(): void { try { - $helperImage = config('coolify.helper_image'); - ray("Pulling {$helperImage}"); - instant_remote_process(["docker pull -q {$helperImage}"], $this->server, false); - ray('PullHelperImageJob done'); + $response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json'); + if ($response->successful()) { + $versions = $response->json(); + $settings = InstanceSettings::get(); + $latest_version = data_get($versions, 'coolify.helper.version'); + $current_version = $settings->helper_version; + if (version_compare($latest_version, $current_version, '>')) { + // New version available + $helperImage = config('coolify.helper_image'); + instant_remote_process(["docker pull -q {$helperImage}:{$latest_version}"], $this->server); + $settings->update(['helper_version' => $latest_version]); + } + } + } catch (\Throwable $e) { send_internal_notification('PullHelperImageJob failed with: '.$e->getMessage()); ray($e->getMessage()); diff --git a/app/Jobs/ServerCheckJob.php b/app/Jobs/ServerCheckJob.php index 703b199c5..3dbd9d3a7 100644 --- a/app/Jobs/ServerCheckJob.php +++ b/app/Jobs/ServerCheckJob.php @@ -124,6 +124,9 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue private function checkLogDrainContainer() { + if(! $this->server->isLogDrainEnabled()) { + return; + } $foundLogDrainContainer = $this->containers->filter(function ($value, $key) { return data_get($value, 'Name') === '/coolify-log-drain'; })->first(); diff --git a/app/Livewire/Project/Application/Deployment/Show.php b/app/Livewire/Project/Application/Deployment/Show.php index 84a24255c..f2968f6d9 100644 --- a/app/Livewire/Project/Application/Deployment/Show.php +++ b/app/Livewire/Project/Application/Deployment/Show.php @@ -4,6 +4,7 @@ namespace App\Livewire\Project\Application\Deployment; use App\Models\Application; use App\Models\ApplicationDeploymentQueue; +use Illuminate\Support\Collection; use Livewire\Component; class Show extends Component @@ -69,6 +70,20 @@ class Show extends Component } } + public function getLogLinesProperty() + { + return decode_remote_command_output($this->application_deployment_queue)->map(function ($logLine) { + $logLine['line'] = e($logLine['line']); + $logLine['line'] = preg_replace( + '/(https?:\/\/[^\s]+)/', + '$1', + $logLine['line'], + ); + + return $logLine; + }); + } + public function render() { return view('livewire.project.application.deployment.show'); diff --git a/app/Livewire/Project/New/PublicGitRepository.php b/app/Livewire/Project/New/PublicGitRepository.php index b29fe8cab..b5c5cb1db 100644 --- a/app/Livewire/Project/New/PublicGitRepository.php +++ b/app/Livewire/Project/New/PublicGitRepository.php @@ -99,6 +99,16 @@ class PublicGitRepository extends Component } } + public function updatedDockerComposeLocation() + { + if ($this->docker_compose_location) { + $this->docker_compose_location = rtrim($this->docker_compose_location, '/'); + if (! str($this->docker_compose_location)->startsWith('/')) { + $this->docker_compose_location = '/'.$this->docker_compose_location; + } + } + } + public function updatedBuildPack() { if ($this->build_pack === 'nixpacks') { diff --git a/app/Livewire/Project/Service/EditCompose.php b/app/Livewire/Project/Service/EditCompose.php index 3d0ad4e0b..dc043e65a 100644 --- a/app/Livewire/Project/Service/EditCompose.php +++ b/app/Livewire/Project/Service/EditCompose.php @@ -43,6 +43,7 @@ class EditCompose extends Component { $this->dispatch('info', 'Saving new docker compose...'); $this->dispatch('saveCompose', $this->service->docker_compose_raw); + $this->dispatch('refreshStorages'); } public function instantSave() diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Livewire/Project/Shared/EnvironmentVariable/All.php index 4c79eb3a2..055788b57 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/All.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -24,6 +24,7 @@ class All extends Component protected $listeners = [ 'saveKey' => 'submit', + 'refreshEnvs', 'environmentVariableDeleted' => 'refreshEnvs', ]; @@ -61,7 +62,7 @@ class All extends Component $sortBy = data_get($this->resource, 'settings.is_env_sorting_enabled') ? 'key' : 'order'; $sortFunction = function ($variables) use ($sortBy) { - if (!$variables) { + if (! $variables) { return $variables; } if ($sortBy === 'key') { diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php index cbc4314f0..463ceecad 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php @@ -136,4 +136,4 @@ class Show extends Component return handleError($e); } } -} \ No newline at end of file +} diff --git a/app/Livewire/Upgrade.php b/app/Livewire/Upgrade.php index da7b5860d..dfbd945f5 100644 --- a/app/Livewire/Upgrade.php +++ b/app/Livewire/Upgrade.php @@ -4,7 +4,6 @@ namespace App\Livewire; use App\Actions\Server\UpdateCoolify; use App\Models\InstanceSettings; -use Illuminate\Support\Facades\Http; use Livewire\Component; class Upgrade extends Component @@ -22,13 +21,8 @@ class Upgrade extends Component public function checkUpdate() { try { - $settings = InstanceSettings::get(); - $response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json'); - if ($response->successful()) { - $versions = $response->json(); - $this->latestVersion = data_get($versions, 'coolify.v4.version'); - } - $this->isUpgradeAvailable = $settings->new_version_available; + $this->latestVersion = get_latest_version_of_coolify(); + $this->isUpgradeAvailable = data_get(InstanceSettings::get(), 'new_version_available', false); } catch (\Throwable $e) { return handleError($e, $this); diff --git a/app/Models/ScheduledDatabaseBackup.php b/app/Models/ScheduledDatabaseBackup.php index 2d0e200da..50a0c8173 100644 --- a/app/Models/ScheduledDatabaseBackup.php +++ b/app/Models/ScheduledDatabaseBackup.php @@ -22,7 +22,8 @@ class ScheduledDatabaseBackup extends BaseModel public function executions(): HasMany { - return $this->hasMany(ScheduledDatabaseBackupExecution::class); + // Last execution first + return $this->hasMany(ScheduledDatabaseBackupExecution::class)->orderBy('created_at', 'desc'); } public function s3() diff --git a/app/Models/ScheduledTask.php b/app/Models/ScheduledTask.php index 7b1c0d275..82f0036a5 100644 --- a/app/Models/ScheduledTask.php +++ b/app/Models/ScheduledTask.php @@ -28,6 +28,7 @@ class ScheduledTask extends BaseModel public function executions(): HasMany { + // Last execution first return $this->hasMany(ScheduledTaskExecution::class)->orderBy('created_at', 'desc'); } diff --git a/app/Models/Server.php b/app/Models/Server.php index 3a1c0eabe..c72c7cc95 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -880,7 +880,7 @@ $schema://$host { public function muxFilename() { - return "{$this->ip}_{$this->port}_{$this->user}"; + return $this->uuid; } public function team() diff --git a/app/Models/Service.php b/app/Models/Service.php index e55c0bea5..a16220604 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -7,9 +7,10 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Storage; use OpenApi\Attributes as OA; use Spatie\Url\Url; -use Symfony\Component\Yaml\Yaml; +use Visus\Cuid2\Cuid2; #[OA\Schema( description: 'Service model', @@ -999,14 +1000,19 @@ class Service extends BaseModel public function saveComposeConfigs() { $workdir = $this->workdir(); - $commands[] = "mkdir -p $workdir"; + + instant_remote_process([ + "mkdir -p $workdir", + "cd $workdir", + ], $this->server); + + $filename = new Cuid2.'-docker-compose.yml'; + Storage::disk('local')->put("tmp/{$filename}", $this->docker_compose); + $path = Storage::path("tmp/{$filename}"); + instant_scp($path, "{$workdir}/docker-compose.yml", $this->server); + Storage::disk('local')->delete("tmp/{$filename}"); + $commands[] = "cd $workdir"; - - $json = Yaml::parse($this->docker_compose); - $this->docker_compose = Yaml::dump($json, 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK); - $docker_compose_base64 = base64_encode($this->docker_compose); - - $commands[] = "echo $docker_compose_base64 | base64 -d | tee docker-compose.yml > /dev/null"; $commands[] = 'rm -f .env || true'; $envs_from_coolify = $this->environment_variables()->get(); diff --git a/bootstrap/helpers/remoteProcess.php b/bootstrap/helpers/remoteProcess.php index 6ba7caeef..3f5cdfae2 100644 --- a/bootstrap/helpers/remoteProcess.php +++ b/bootstrap/helpers/remoteProcess.php @@ -146,7 +146,7 @@ function generateSshCommand(Server $server, string $command) $ssh_command = "timeout $timeout ssh "; if (config('coolify.mux_enabled') && config('coolify.is_windows_docker_desktop') == false) { - $ssh_command .= "-o ControlMaster=auto -o ControlPersist={$muxPersistTime} -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r "; + $ssh_command .= "-o ControlMaster=auto -o ControlPersist={$muxPersistTime} -o ControlPath=/var/www/html/storage/app/ssh/mux/{$server->muxFilename()} "; } if (data_get($server, 'settings.is_cloudflare_tunnel')) { $ssh_command .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" '; @@ -167,7 +167,6 @@ function generateSshCommand(Server $server, string $command) .$command.PHP_EOL .$delimiter; - // ray($ssh_command); return $ssh_command; } function instant_remote_process(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false): ?string @@ -234,6 +233,7 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d return collect([]); } // ray($decoded ); + $seenCommands = collect(); $formatted = collect($decoded); if (! $is_debug_enabled) { $formatted = $formatted->filter(fn ($i) => $i['hidden'] === false ?? false); @@ -244,7 +244,42 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d data_set($i, 'timestamp', Carbon::parse(data_get($i, 'timestamp'))->format('Y-M-d H:i:s.u')); return $i; - }); + }) + ->reduce(function ($deploymentLogLines, $logItem) use ($seenCommands) { + $command = $logItem['command']; + $isStderr = $logItem['type'] === 'stderr'; + $isNewCommand = ! is_null($command) && ! $seenCommands->first(function ($seenCommand) use ($logItem) { + return $seenCommand['command'] === $logItem['command'] && $seenCommand['batch'] === $logItem['batch']; + }); + + if ($isNewCommand) { + $deploymentLogLines->push([ + 'line' => $command, + 'timestamp' => $logItem['timestamp'], + 'stderr' => $isStderr, + 'hidden' => $logItem['hidden'], + 'command' => true, + ]); + + $seenCommands->push([ + 'command' => $command, + 'batch' => $logItem['batch'], + ]); + } + + $lines = explode(PHP_EOL, $logItem['output']); + + foreach ($lines as $line) { + $deploymentLogLines->push([ + 'line' => $line, + 'timestamp' => $logItem['timestamp'], + 'stderr' => $isStderr, + 'hidden' => $logItem['hidden'], + ]); + } + + return $deploymentLogLines; + }, collect()); return $formatted; } diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index ee1ceb049..5f93ce36f 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -2819,8 +2819,10 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $defaultLabels = defaultLabels($resource->id, $containerName, $pull_request_id, type: 'application'); $serviceLabels = $serviceLabels->merge($defaultLabels); - if ($server->isLogDrainEnabled() && $resource->isLogDrainEnabled()) { - data_set($service, 'logging', generate_fluentd_configuration()); + if ($server->isLogDrainEnabled()) { + if ($resource instanceof Application && $resource->isLogDrainEnabled()) { + data_set($service, 'logging', generate_fluentd_configuration()); + } } if ($serviceLabels->count() > 0) { if ($resource->settings->is_container_label_escape_enabled) { @@ -2923,8 +2925,10 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $restart = data_get_str($service, 'restart', RESTART_MODE); $logging = data_get($service, 'logging'); - if ($server->isLogDrainEnabled() && $resource->isLogDrainEnabled()) { - $logging = generate_fluentd_configuration(); + if ($server->isLogDrainEnabled()) { + if ($resource instanceof Application && $resource->isLogDrainEnabled()) { + $logging = generate_fluentd_configuration(); + } } $volumes = collect(data_get($service, 'volumes', [])); $networks = collect(data_get($service, 'networks', [])); @@ -3088,10 +3092,9 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $topLevel->get('volumes')->put($name, [ 'name' => $name, ]); - LocalPersistentVolume::updateOrCreate( [ - 'mount_path' => $target, + 'name' => $name, 'resource_id' => $originalResource->id, 'resource_type' => get_class($originalResource), ], @@ -3216,10 +3219,31 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int // filter magic environments $magicEnvironments = $environment->filter(function ($value, $key) { + $regex = '/\$\{(.*?)\}/'; + preg_match_all($regex, $value, $matches); + if (count($matches[1]) > 0) { + foreach ($matches[1] as $match) { + if (str($match)->startsWith('SERVICE_') || str($match)->startsWith('SERVICE_')) { + return $match; + } + } + } $value = str(replaceVariables(str($value))); return str($key)->startsWith('SERVICE_') || str($value)->startsWith('SERVICE_'); }); + foreach ($environment as $key => $value) { + $regex = '/\$\{(.*?)\}/'; + preg_match_all($regex, $value, $matches); + if (count($matches[1]) > 0) { + foreach ($matches[1] as $match) { + if (str($match)->startsWith('SERVICE_') || str($match)->startsWith('SERVICE_')) { + $magicEnvironments->put($match, '$'.$match); + } + } + $magicEnvironments->forget($key); + } + } $normalEnvironments = $environment->diffKeys($magicEnvironments); if ($magicEnvironments->count() > 0) { foreach ($magicEnvironments as $key => $value) { @@ -3262,15 +3286,17 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $value = $fqdn; } if (! $isDatabase) { - if ($isApplication && is_null($resource->fqdn)) { - data_forget($resource, 'environment_variables'); - data_forget($resource, 'environment_variables_preview'); - $resource->fqdn = $value; - $resource->save(); - } elseif ($isService && is_null($savedService->fqdn)) { - if ($key->startsWith('SERVICE_FQDN_')) { - $savedService->fqdn = $value; - $savedService->save(); + if ($key->startsWith('SERVICE_FQDN_') && ($originalValue->value() === '' || $originalValue->startsWith('/'))) { + if ($isApplication && is_null($resource->fqdn)) { + data_forget($resource, 'environment_variables'); + data_forget($resource, 'environment_variables_preview'); + $resource->fqdn = $value; + $resource->save(); + } elseif ($isService && is_null($savedService->fqdn)) { + if ($key->startsWith('SERVICE_FQDN_')) { + $savedService->fqdn = $value; + $savedService->save(); + } } } } @@ -3328,7 +3354,10 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int foreach ($normalEnvironments as $key => $value) { $key = str($key); $value = str($value); - if ($value->startsWith('$')) { + if ($value->startsWith('$') || $value->contains('${')) { + if ($value->contains('${')) { + $value = $value->after('${')->before('}'); + } $value = str(replaceVariables(str($value))); if ($value->contains(':-')) { $key = $value->before(':'); @@ -3421,7 +3450,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $defaultLabels = defaultLabels($resource->id, $containerName, type: 'service', subType: $isDatabase ? 'database' : 'application', subId: $savedService->id); } // Add COOLIFY_FQDN & COOLIFY_URL to environment - if (! $isDatabase && $fqdns?->count() > 0) { + if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) { $environment->put('COOLIFY_URL', $fqdns->implode(',')); $urls = $fqdns->map(function ($fqdn) { @@ -3432,7 +3461,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int add_coolify_default_environment_variables($resource, $environment, $resource->environment_variables); $serviceLabels = $labels->merge($defaultLabels); - if (! $isDatabase && $fqdns?->count() > 0) { + if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) { if ($isApplication) { $shouldGenerateLabelsExactly = $resource->destination->server->settings->generate_exact_labels; $uuid = $resource->uuid; @@ -3540,7 +3569,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $parsedServices->put($serviceName, $payload); } - ray($parsedServices); $topLevel->put('services', $parsedServices); $customOrder = ['services', 'volumes', 'networks', 'configs', 'secrets']; @@ -3570,6 +3598,23 @@ function generate_fluentd_configuration(): array ]; } +function isAssociativeArray($array) +{ + if ($array instanceof Collection) { + $array = $array->toArray(); + } + + if (! is_array($array)) { + throw new \InvalidArgumentException('Input must be an array or a Collection.'); + } + + if ($array === []) { + return false; + } + + return array_keys($array) !== range(0, count($array) - 1); +} + /** * This method adds the default environment variables to the resource. * - COOLIFY_APP_NAME @@ -3581,37 +3626,39 @@ function generate_fluentd_configuration(): array */ function add_coolify_default_environment_variables(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|Application|Service $resource, Collection &$where_to_add, ?Collection $where_to_check = null) { + if ($resource instanceof Service) { + $ip = $resource->server->ip; + } else { + $ip = $resource->destination->server->ip; + } + if (isAssociativeArray($where_to_add)) { + $isAssociativeArray = true; + } else { + $isAssociativeArray = false; + } if ($where_to_check != null && $where_to_check->where('key', 'COOLIFY_APP_NAME')->isEmpty()) { - if ($resource instanceof Application && $resource->build_pack === 'dockercompose') { - $where_to_add->put('COOLIFY_APP_NAME', $resource->name); - } elseif ($resource instanceof Service) { + if ($isAssociativeArray) { $where_to_add->put('COOLIFY_APP_NAME', $resource->name); } else { $where_to_add->push("COOLIFY_APP_NAME={$resource->name}"); } } if ($where_to_check != null && $where_to_check->where('key', 'COOLIFY_SERVER_IP')->isEmpty()) { - if ($resource instanceof Application && $resource->build_pack === 'dockercompose') { - $where_to_add->put('COOLIFY_SERVER_IP', $resource->destination->server->ip); - } elseif ($resource instanceof Service) { - $where_to_add->put('COOLIFY_SERVER_IP', $resource->server->ip); + if ($isAssociativeArray) { + $where_to_add->put('COOLIFY_SERVER_IP', $ip); } else { - $where_to_add->push("COOLIFY_SERVER_IP={$resource->destination->server->ip}"); + $where_to_add->push("COOLIFY_SERVER_IP={$ip}"); } } if ($where_to_check != null && $where_to_check->where('key', 'COOLIFY_ENVIRONMENT_NAME')->isEmpty()) { - if ($resource instanceof Application && $resource->build_pack === 'dockercompose') { - $where_to_add->put('COOLIFY_ENVIRONMENT_NAME', $resource->environment->name); - } elseif ($resource instanceof Service) { + if ($isAssociativeArray) { $where_to_add->put('COOLIFY_ENVIRONMENT_NAME', $resource->environment->name); } else { $where_to_add->push("COOLIFY_ENVIRONMENT_NAME={$resource->environment->name}"); } } if ($where_to_check != null && $where_to_check->where('key', 'COOLIFY_PROJECT_NAME')->isEmpty()) { - if ($resource instanceof Application && $resource->build_pack === 'dockercompose') { - $where_to_add->put('COOLIFY_PROJECT_NAME', $resource->project()->name); - } elseif ($resource instanceof Service) { + if ($isAssociativeArray) { $where_to_add->put('COOLIFY_PROJECT_NAME', $resource->project()->name); } else { $where_to_add->push("COOLIFY_PROJECT_NAME={$resource->project()->name}"); @@ -3622,29 +3669,17 @@ function add_coolify_default_environment_variables(StandaloneRedis|StandalonePos function convertComposeEnvironmentToArray($environment) { $convertedServiceVariables = collect([]); - foreach ($environment as $variableName => $variableValue) { - if (is_array($variableValue)) { - $key = str(collect($variableValue)->keys()->first()); - $value = str(collect($variableValue)->values()->first()); - } elseif (is_string($variableValue)) { - if (str($variableValue)->contains('=')) { - $key = str($variableValue)->before('='); - $value = str($variableValue)->after('='); - } else { - if (is_numeric($variableName)) { - $key = str($variableValue); - $value = null; - } else { - $key = str($variableName); - if ($variableValue) { - $value = str($variableValue); - } else { - $value = null; - } - } + if (isAssociativeArray($environment)) { + $convertedServiceVariables = $environment; + } else { + foreach ($environment as $value) { + $parts = explode('=', $value, 2); + $key = $parts[0]; + $realValue = $parts[1] ?? ''; + if ($key) { + $convertedServiceVariables->put($key, $realValue); } } - $convertedServiceVariables->put($key->value(), $value?->value() ?? null); } return $convertedServiceVariables; diff --git a/config/coolify.php b/config/coolify.php index a6d6d8581..6e284fe9e 100644 --- a/config/coolify.php +++ b/config/coolify.php @@ -11,7 +11,7 @@ return [ 'dev_webhook' => env('SERVEO_URL'), 'is_windows_docker_desktop' => env('IS_WINDOWS_DOCKER_DESKTOP', false), 'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'), - 'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper:latest'), + 'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper'), 'is_horizon_enabled' => env('HORIZON_ENABLED', true), 'is_scheduler_enabled' => env('SCHEDULER_ENABLED', true), ]; diff --git a/config/horizon.php b/config/horizon.php index 4d25e40bc..939d74883 100644 --- a/config/horizon.php +++ b/config/horizon.php @@ -186,7 +186,7 @@ return [ 'balance' => env('HORIZON_BALANCE', 'auto'), 'maxTime' => 0, 'maxJobs' => 0, - 'memory' => 64, + 'memory' => 128, 'tries' => 1, 'timeout' => 3560, 'nice' => 0, diff --git a/config/sentry.php b/config/sentry.php index 657377edf..f48995f01 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ return [ // The release version of your application // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) - 'release' => '4.0.0-beta.324', + 'release' => '4.0.0-beta.331', // When left empty or `null` the Laravel environment will be used 'environment' => config('app.env'), diff --git a/config/version.php b/config/version.php index 7af787ece..12f68e4e0 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ string('helper_version')->default('1.0.0'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('instance_settings', function (Blueprint $table) { + $table->dropColumn('helper_version'); + }); + } +}; diff --git a/database/migrations/2024_09_06_062534_change_server_cleanup_to_forced.php b/database/migrations/2024_09_06_062534_change_server_cleanup_to_forced.php new file mode 100644 index 000000000..ad6e5bd9e --- /dev/null +++ b/database/migrations/2024_09_06_062534_change_server_cleanup_to_forced.php @@ -0,0 +1,37 @@ +boolean('force_docker_cleanup')->default(true)->change(); + }); + $serverSettings = ServerSetting::all(); + foreach ($serverSettings as $serverSetting) { + if ($serverSetting->force_docker_cleanup === false) { + $serverSetting->force_docker_cleanup = true; + $serverSetting->docker_cleanup_frequency = '*/10 * * * *'; + $serverSetting->save(); + } + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('server_settings', function (Blueprint $table) { + $table->boolean('force_docker_cleanup')->default(false)->change(); + }); + } +}; diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index b8156cab5..b26cd5746 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -13,19 +13,18 @@ services: - /data/coolify/backups:/var/www/html/storage/app/backups - /data/coolify/webhooks-during-maintenance:/var/www/html/storage/app/webhooks-during-maintenance environment: - - PHP_MEMORY_LIMIT - - APP_ID - APP_ENV=production - - APP_DEBUG - APP_NAME + - APP_ID - APP_KEY - APP_URL - - DB_CONNECTION - - DB_HOST - - DB_PORT + - APP_DEBUG - DB_DATABASE - DB_USERNAME - DB_PASSWORD + - DB_HOST + - DB_PORT + - DB_CONNECTION - QUEUE_CONNECTION - REDIS_HOST - REDIS_PASSWORD @@ -34,6 +33,7 @@ services: - HORIZON_BALANCE_MAX_SHIFT - HORIZON_BALANCE_COOLDOWN - SSL_MODE=off + - PHP_MEMORY_LIMIT - PHP_PM_CONTROL=dynamic - PHP_PM_START_SERVERS=1 - PHP_PM_MIN_SPARE_SERVERS=1 @@ -83,21 +83,17 @@ services: condition: service_healthy redis: condition: service_healthy + soketi: + condition: service_healthy postgres: volumes: - coolify-db:/var/lib/postgresql/data environment: - POSTGRES_USER: "${DB_USERNAME:-coolify}" + POSTGRES_USER: "${DB_USERNAME}" POSTGRES_PASSWORD: "${DB_PASSWORD}" POSTGRES_DB: "${DB_DATABASE:-coolify}" healthcheck: - test: - [ - "CMD-SHELL", - "pg_isready -U ${DB_USERNAME:-coolify}", - "-d", - "${DB_DATABASE:-coolify}" - ] + test: [ "CMD-SHELL", "pg_isready -U ${DB_USERNAME}", "-d", "${DB_DATABASE:-coolify}" ] interval: 5s retries: 10 timeout: 2s @@ -130,3 +126,7 @@ volumes: name: coolify-db coolify-redis: name: coolify-redis + +networks: + coolify: + external: true diff --git a/docker-compose.windows.yml b/docker-compose.windows.yml index af5ecc0f7..a1ee1aeea 100644 --- a/docker-compose.windows.yml +++ b/docker-compose.windows.yml @@ -71,14 +71,14 @@ services: volumes: - coolify-db:/var/lib/postgresql/data environment: - POSTGRES_USER: "${DB_USERNAME:-coolify}" + POSTGRES_USER: "${DB_USERNAME}" POSTGRES_PASSWORD: "${DB_PASSWORD}" POSTGRES_DB: "${DB_DATABASE:-coolify}" healthcheck: test: [ "CMD-SHELL", - "pg_isready -U ${DB_USERNAME:-coolify}", + "pg_isready -U ${DB_USERNAME}", "-d", "${DB_DATABASE:-coolify}" ] diff --git a/docker-compose.yml b/docker-compose.yml index 8eed44f8c..930c0a6b9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,7 @@ services: depends_on: - postgres - redis + - soketi postgres: image: postgres:15-alpine container_name: coolify-db @@ -32,4 +33,4 @@ networks: coolify: name: coolify driver: bridge - external: true + external: false diff --git a/docker/coolify-helper/Dockerfile b/docker/coolify-helper/Dockerfile index 472f46c5d..09ca18825 100644 --- a/docker/coolify-helper/Dockerfile +++ b/docker/coolify-helper/Dockerfile @@ -8,9 +8,9 @@ ARG DOCKER_COMPOSE_VERSION=2.27.1 # https://github.com/docker/buildx/releases ARG DOCKER_BUILDX_VERSION=0.14.1 # https://github.com/buildpacks/pack/releases -ARG PACK_VERSION=0.34.1 +ARG PACK_VERSION=0.35.1 # https://github.com/railwayapp/nixpacks/releases -ARG NIXPACKS_VERSION=1.24.1 +ARG NIXPACKS_VERSION=1.28.0 USER root WORKDIR /artifacts diff --git a/openapi.yaml b/openapi.yaml index 482ff6f05..cbe41368a 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -13,7 +13,7 @@ paths: - Applications summary: List description: 'List all applications.' - operationId: 02978e79fc0b54d573b2359f2a1f7d86 + operationId: list-applications responses: '200': description: 'Get all applications.' @@ -36,7 +36,7 @@ paths: - Applications summary: 'Create (Public)' description: 'Create new application based on a public git repository.' - operationId: cb56324ad19693469b4461d3f6065a5b + operationId: create-public-application requestBody: description: 'Application object that needs to be created.' required: true @@ -253,7 +253,7 @@ paths: - Applications summary: 'Create (Private - GH App)' description: 'Create new application based on a private repository through a Github App.' - operationId: 8b7af9c9a509385963bf3e72eeeea786 + operationId: create-private-github-app-application requestBody: description: 'Application object that needs to be created.' required: true @@ -474,7 +474,7 @@ paths: - Applications summary: 'Create (Private - Deploy Key)' description: 'Create new application based on a private repository through a Deploy Key.' - operationId: e3eaa989ffb05366247a00cdfd551efa + operationId: create-private-deploy-key-application requestBody: description: 'Application object that needs to be created.' required: true @@ -695,7 +695,7 @@ paths: - Applications summary: 'Create (Dockerfile)' description: 'Create new application based on a simple Dockerfile.' - operationId: 2b433ad6f5d259eb7f4f3b5af9913708 + operationId: create-dockerfile-application requestBody: description: 'Application object that needs to be created.' required: true @@ -867,7 +867,7 @@ paths: - Applications summary: 'Create (Docker Image)' description: 'Create new application based on a prebuilt docker image' - operationId: e9a2d6dd9404acf880dc3053f09477fc + operationId: create-dockerimage-application requestBody: description: 'Application object that needs to be created.' required: true @@ -1030,7 +1030,7 @@ paths: - Applications summary: 'Create (Docker Compose)' description: 'Create new application based on a docker-compose file.' - operationId: 3731add8226c2d664455978cac46c242 + operationId: create-dockercompose-application requestBody: description: 'Application object that needs to be created.' required: true @@ -1084,7 +1084,7 @@ paths: - Applications summary: Get description: 'Get application by UUID.' - operationId: 3630b62c28e7358e7f0087c1d8fe1845 + operationId: get-application-by-uuid parameters: - name: uuid @@ -1115,7 +1115,7 @@ paths: - Applications summary: Delete description: 'Delete application by UUID.' - operationId: 1e110b190a1045d34f3e1c61608a8702 + operationId: delete-application-by-uuid parameters: - name: uuid @@ -1156,7 +1156,7 @@ paths: - Applications summary: Update description: 'Update application by UUID.' - operationId: 62a3b1775e8cba5d39a236ebb69830b7 + operationId: update-application-by-uuid requestBody: description: 'Application updated.' required: true @@ -1376,7 +1376,7 @@ paths: - Applications summary: 'List Envs' description: 'List all envs by application UUID.' - operationId: 7c8e0c286870e23294a075cc0584df2f + operationId: list-envs-by-application-uuid parameters: - name: uuid @@ -1409,7 +1409,7 @@ paths: - Applications summary: 'Create Env' description: 'Create env by application UUID.' - operationId: 4699ffbb7d6e58581fd0b0a14f36ffc2 + operationId: create-env-by-application-uuid parameters: - name: uuid @@ -1471,7 +1471,7 @@ paths: - Applications summary: 'Update Env' description: 'Update env by application UUID.' - operationId: 3d70a2d569f395be220b3f09ad36674b + operationId: update-env-by-application-uuid parameters: - name: uuid @@ -1537,7 +1537,7 @@ paths: - Applications summary: 'Update Envs (Bulk)' description: 'Update multiple envs by application UUID.' - operationId: ae96f0f585ed158b2abd2d9ba40f3cf9 + operationId: update-envs-by-application-uuid parameters: - name: uuid @@ -1584,7 +1584,7 @@ paths: - Applications summary: 'Delete Env' description: 'Delete env by UUID.' - operationId: 96097c5cfc7dc0e7a3de229645f630c7 + operationId: delete-env-by-application-uuid parameters: - name: uuid @@ -1626,7 +1626,7 @@ paths: - Applications summary: Start description: 'Start application. `Post` request is also accepted.' - operationId: dc87c2061ab303757a0e061f87900c4c + operationId: start-application-by-uuid parameters: - name: uuid @@ -1675,7 +1675,7 @@ paths: - Applications summary: Stop description: 'Stop application. `Post` request is also accepted.' - operationId: 133ef3c7bd5043901f24bb5002a536eb + operationId: stop-application-by-uuid parameters: - name: uuid @@ -1709,7 +1709,7 @@ paths: - Applications summary: Restart description: 'Restart application. `Post` request is also accepted.' - operationId: b231ae7baab9ef47f0627be820e735bc + operationId: restart-application-by-uuid parameters: - name: uuid @@ -1744,7 +1744,7 @@ paths: - Databases summary: List description: 'List all databases.' - operationId: ecd0ee1e46e4c854c18e6c9daa3d37f3 + operationId: list-databases responses: '200': description: 'Get all databases' @@ -1766,7 +1766,7 @@ paths: - Databases summary: Get description: 'Get database by UUID.' - operationId: b49cb2d3e8f34c4e80cdffd8a201031d + operationId: get-database-by-uuid parameters: - name: uuid @@ -1798,7 +1798,7 @@ paths: - Databases summary: Delete description: 'Delete database by UUID.' - operationId: 20610931b2bae8aba34eee68624ab673 + operationId: delete-database-by-uuid parameters: - name: uuid @@ -1839,7 +1839,7 @@ paths: - Databases summary: Update description: 'Update database by UUID.' - operationId: 5ba459ed390a721711a1708760e9de3b + operationId: update-database-by-uuid parameters: - name: uuid @@ -1989,7 +1989,7 @@ paths: - Databases summary: 'Create (PostgreSQL)' description: 'Create a new PostgreSQL database.' - operationId: 8f7f491ddc46a9fa065b4424512231cd + operationId: create-database-postgresql requestBody: description: 'Database data' required: true @@ -2087,7 +2087,7 @@ paths: - Databases summary: 'Create (Clickhouse)' description: 'Create a new Clickhouse database.' - operationId: a1189fa7f956f238f0e95c9150ff57f6 + operationId: create-database-clickhouse requestBody: description: 'Database data' required: true @@ -2173,7 +2173,7 @@ paths: - Databases summary: 'Create (DragonFly)' description: 'Create a new DragonFly database.' - operationId: e73f7de1c8eee4219e5ec98c4b9b7efe + operationId: create-database-dragonfly requestBody: description: 'Database data' required: true @@ -2256,7 +2256,7 @@ paths: - Databases summary: 'Create (Redis)' description: 'Create a new Redis database.' - operationId: 4d352d13544ee2953fd48ad7b0651098 + operationId: create-database-redis requestBody: description: 'Database data' required: true @@ -2342,7 +2342,7 @@ paths: - Databases summary: 'Create (KeyDB)' description: 'Create a new KeyDB database.' - operationId: b908f3929c371c217d489638e0a21ff6 + operationId: create-database-keydb requestBody: description: 'Database data' required: true @@ -2428,7 +2428,7 @@ paths: - Databases summary: 'Create (MariaDB)' description: 'Create a new MariaDB database.' - operationId: 6bea521ddcd738dcbb5f3783a7308acf + operationId: create-database-mariadb requestBody: description: 'Database data' required: true @@ -2523,7 +2523,7 @@ paths: - Databases summary: 'Create (MySQL)' description: 'Create a new MySQL database.' - operationId: 0a1158cf759c4493cbb1e30024c60623 + operationId: create-database-mysql requestBody: description: 'Database data' required: true @@ -2615,7 +2615,7 @@ paths: - Databases summary: 'Create (MongoDB)' description: 'Create a new MongoDB database.' - operationId: fdba3de84d02519bb37599fea34b115d + operationId: create-database-mongodb requestBody: description: 'Database data' required: true @@ -2701,7 +2701,7 @@ paths: - Databases summary: Start description: 'Start database. `Post` request is also accepted.' - operationId: 4c6eb21e734d411e2b3388578761123d + operationId: start-database-by-uuid parameters: - name: uuid @@ -2735,7 +2735,7 @@ paths: - Databases summary: Stop description: 'Stop database. `Post` request is also accepted.' - operationId: cb6d983c2679aff841c7501ce612a372 + operationId: stop-database-by-uuid parameters: - name: uuid @@ -2769,7 +2769,7 @@ paths: - Databases summary: Restart description: 'Restart database. `Post` request is also accepted.' - operationId: 04c7a5e4752b4a00036addb433f3f218 + operationId: restart-database-by-uuid parameters: - name: uuid @@ -2803,7 +2803,7 @@ paths: - Deployments summary: List description: 'List currently running deployments' - operationId: a2c05736269191ad0d99cadfd4708986 + operationId: list-deployments responses: '200': description: 'Get all currently running deployments.' @@ -2826,7 +2826,7 @@ paths: - Deployments summary: Get description: 'Get deployment by UUID.' - operationId: ccf9856174c115a1430d952ccbd36aea + operationId: get-deployment-by-uuid parameters: - name: uuid @@ -2857,7 +2857,7 @@ paths: - Deployments summary: Deploy description: 'Deploy by tag or uuid. `Post` request also accepted.' - operationId: 700eb6e51f4c9e86d722f600c65ed1d4 + operationId: deploy-by-tag-or-uuid parameters: - name: tag @@ -2897,7 +2897,7 @@ paths: get: summary: Version description: 'Get Coolify version.' - operationId: 187b37139844731110757711ee71c215 + operationId: version responses: '200': description: 'Returns the version of the application' @@ -2917,7 +2917,7 @@ paths: get: summary: 'Enable API' description: 'Enable API (only with root permissions).' - operationId: 595019bae03d08277def667609779ff3 + operationId: enable-api responses: '200': description: 'Enable API.' @@ -2946,7 +2946,7 @@ paths: get: summary: 'Disable API' description: 'Disable API (only with root permissions).' - operationId: 50e2486a2d196a996b24a284a283bcdb + operationId: disable-api responses: '200': description: 'Disable API.' @@ -2975,7 +2975,7 @@ paths: get: summary: Healthcheck description: 'Healthcheck endpoint.' - operationId: 64db893135e686704bb88c3c238022c1 + operationId: healthcheck responses: '200': description: 'Healthcheck endpoint.' @@ -2994,7 +2994,7 @@ paths: - Projects summary: List description: 'list projects.' - operationId: 762788f00f2dabb981df9adbc948d3f6 + operationId: list-projects responses: '200': description: 'Get all projects.' @@ -3016,7 +3016,7 @@ paths: - Projects summary: Create description: 'Create Project.' - operationId: cf067eb7cf18216cda3239329a2eeadb + operationId: create-project requestBody: description: 'Project created.' required: true @@ -3024,7 +3024,7 @@ paths: application/json: schema: properties: - uuid: + name: type: string description: 'The name of the project.' description: @@ -3055,7 +3055,7 @@ paths: - Projects summary: Get description: 'Get project by Uuid.' - operationId: 63bf8b6a68fbb757f09ab519331f6298 + operationId: get-project-by-uuid parameters: - name: uuid @@ -3085,7 +3085,7 @@ paths: - Projects summary: Delete description: 'Delete project by UUID.' - operationId: f668a936f505b4401948c74b6a663029 + operationId: delete-project-by-uuid parameters: - name: uuid @@ -3118,7 +3118,7 @@ paths: - Projects summary: Update description: 'Update Project.' - operationId: 2db343bd6fc14c658cb51a2b73b2f842 + operationId: update-project-by-uuid requestBody: description: 'Project updated.' required: true @@ -3159,7 +3159,7 @@ paths: - Projects summary: Environment description: 'Get environment by name.' - operationId: 7e44845dce5aa47ed7b0daf5595ad2e1 + operationId: get-environment-by-name parameters: - name: uuid @@ -3197,7 +3197,7 @@ paths: - Resources summary: List description: 'Get all resources.' - operationId: c399903694eb1314596832e49f7c66d7 + operationId: list-resources responses: '200': description: 'Get all resources' @@ -3219,7 +3219,7 @@ paths: - 'Private Keys' summary: List description: 'List all private keys.' - operationId: 8a5d8d3ccbbcef54ed0e913a27faea9d + operationId: list-private-keys responses: '200': description: 'Get all private keys.' @@ -3241,7 +3241,7 @@ paths: - 'Private Keys' summary: Create description: 'Create a new private key.' - operationId: eb4780acaa990c594cdbe8ffa80b4fb0 + operationId: create-private-key requestBody: required: true content: @@ -3279,7 +3279,7 @@ paths: - 'Private Keys' summary: Update description: 'Update a private key.' - operationId: 371fd26b8949a070c26a264231fe233f + operationId: update-private-key requestBody: required: true content: @@ -3318,7 +3318,7 @@ paths: - 'Private Keys' summary: Get description: 'Get key by UUID.' - operationId: 2f743a85eb65d5ddb8cd5b362bb3d26a + operationId: get-private-key-by-uuid parameters: - name: uuid @@ -3350,7 +3350,7 @@ paths: - 'Private Keys' summary: Delete description: 'Delete a private key.' - operationId: 8faa0bb399142f0084dfc3e003c42cf6 + operationId: delete-private-key-by-uuid parameters: - name: uuid @@ -3383,7 +3383,7 @@ paths: - Servers summary: List description: 'List all servers.' - operationId: 787448df856cefd2d9a313566be30d34 + operationId: list-servers responses: '200': description: 'Get all servers.' @@ -3405,7 +3405,7 @@ paths: - Servers summary: Create description: 'Create Server.' - operationId: fa44b42490379e428ba5b8747716a8d9 + operationId: create-server requestBody: description: 'Server created.' required: true @@ -3470,7 +3470,7 @@ paths: - Servers summary: Get description: 'Get server by UUID.' - operationId: 5baf04bddb8302c7e07f5b4c41aad10c + operationId: get-server-by-uuid parameters: - name: uuid @@ -3500,7 +3500,7 @@ paths: - Servers summary: Delete description: 'Delete server by UUID.' - operationId: 0231fe0134f0306b21f006ce51b0a3dc + operationId: delete-server-by-uuid parameters: - name: uuid @@ -3533,7 +3533,7 @@ paths: - Servers summary: Update description: 'Update Server.' - operationId: 41bbdaf79eb1938592494fc5494442a0 + operationId: update-server-by-uuid requestBody: description: 'Server updated.' required: true @@ -3590,7 +3590,7 @@ paths: - Servers summary: Resources description: 'Get resources by server.' - operationId: cef26c059941b44fbd8de3a7a58c10a5 + operationId: get-resources-by-server-uuid parameters: - name: uuid @@ -3622,7 +3622,7 @@ paths: - Servers summary: Domains description: 'Get domains by server.' - operationId: 1ee227755be848d572f412272f53dd93 + operationId: get-domains-by-server-uuid parameters: - name: uuid @@ -3654,7 +3654,7 @@ paths: - Servers summary: Validate description: 'Validate server by UUID.' - operationId: a543a12ef2cbc7a3dd22c3dbe6cbee89 + operationId: validate-server-by-uuid parameters: - name: uuid @@ -3687,7 +3687,7 @@ paths: - Services summary: List description: 'List all services.' - operationId: 5d014ac25d33391b8f4c2316060ba452 + operationId: list-services responses: '200': description: 'Get all services' @@ -3709,7 +3709,7 @@ paths: - Services summary: Create description: 'Create a one-click service' - operationId: 3d6cbfb54d919b53ba3984a113e837d7 + operationId: create-service requestBody: required: true content: @@ -3773,7 +3773,7 @@ paths: - Services summary: Get description: 'Get service by UUID.' - operationId: 895d39ee2cb3994285de57256c2d428d + operationId: get-service-by-uuid parameters: - name: uuid @@ -3803,7 +3803,7 @@ paths: - Services summary: Delete description: 'Delete service by UUID.' - operationId: 6e1a61e4fddaa9d95bb9fc66dfaf0442 + operationId: delete-service-by-uuid parameters: - name: uuid @@ -3836,7 +3836,7 @@ paths: - Services summary: Start description: 'Start service. `Post` request is also accepted.' - operationId: d2ddd9c028d123fbdec830dc4b25b4cb + operationId: start-service-by-uuid parameters: - name: uuid @@ -3870,7 +3870,7 @@ paths: - Services summary: Stop description: 'Stop service. `Post` request is also accepted.' - operationId: 87399d34758ce16830740c68626614db + operationId: stop-service-by-uuid parameters: - name: uuid @@ -3904,7 +3904,7 @@ paths: - Services summary: Restart description: 'Restart service. `Post` request is also accepted.' - operationId: 836645faa615b75052759dae78639469 + operationId: restart-service-by-uuid parameters: - name: uuid @@ -3932,13 +3932,263 @@ paths: security: - bearerAuth: [] + '/services/{uuid}/envs': + get: + tags: + - Services + summary: 'List Envs' + description: 'List all envs by service UUID.' + operationId: list-envs-by-service-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the service.' + required: true + schema: + type: string + format: uuid + responses: + '200': + description: 'All environment variables by service UUID.' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/EnvironmentVariable' + '401': + $ref: '#/components/responses/401' + '400': + $ref: '#/components/responses/400' + '404': + $ref: '#/components/responses/404' + security: + - + bearerAuth: [] + post: + tags: + - Services + summary: 'Create Env' + description: 'Create env by service UUID.' + operationId: create-env-by-service-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the service.' + required: true + schema: + type: string + format: uuid + requestBody: + description: 'Env created.' + required: true + content: + application/json: + schema: + properties: + key: + type: string + description: 'The key of the environment variable.' + value: + type: string + description: 'The value of the environment variable.' + is_preview: + type: boolean + description: 'The flag to indicate if the environment variable is used in preview deployments.' + is_build_time: + type: boolean + description: 'The flag to indicate if the environment variable is used in build time.' + is_literal: + type: boolean + description: 'The flag to indicate if the environment variable is a literal, nothing espaced.' + is_multiline: + type: boolean + description: 'The flag to indicate if the environment variable is multiline.' + is_shown_once: + type: boolean + description: "The flag to indicate if the environment variable's value is shown on the UI." + type: object + responses: + '201': + description: 'Environment variable created.' + content: + application/json: + schema: + properties: + uuid: { type: string, example: nc0k04gk8g0cgsk440g0koko } + type: object + '401': + $ref: '#/components/responses/401' + '400': + $ref: '#/components/responses/400' + '404': + $ref: '#/components/responses/404' + security: + - + bearerAuth: [] + patch: + tags: + - Services + summary: 'Update Env' + description: 'Update env by service UUID.' + operationId: update-env-by-service-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the service.' + required: true + schema: + type: string + format: uuid + requestBody: + description: 'Env updated.' + required: true + content: + application/json: + schema: + required: + - key + - value + properties: + key: + type: string + description: 'The key of the environment variable.' + value: + type: string + description: 'The value of the environment variable.' + is_preview: + type: boolean + description: 'The flag to indicate if the environment variable is used in preview deployments.' + is_build_time: + type: boolean + description: 'The flag to indicate if the environment variable is used in build time.' + is_literal: + type: boolean + description: 'The flag to indicate if the environment variable is a literal, nothing espaced.' + is_multiline: + type: boolean + description: 'The flag to indicate if the environment variable is multiline.' + is_shown_once: + type: boolean + description: "The flag to indicate if the environment variable's value is shown on the UI." + type: object + responses: + '201': + description: 'Environment variable updated.' + content: + application/json: + schema: + properties: + message: { type: string, example: 'Environment variable updated.' } + type: object + '401': + $ref: '#/components/responses/401' + '400': + $ref: '#/components/responses/400' + '404': + $ref: '#/components/responses/404' + security: + - + bearerAuth: [] + '/services/{uuid}/envs/bulk': + patch: + tags: + - Services + summary: 'Update Envs (Bulk)' + description: 'Update multiple envs by service UUID.' + operationId: update-envs-by-service-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the service.' + required: true + schema: + type: string + format: uuid + requestBody: + description: 'Bulk envs updated.' + required: true + content: + application/json: + schema: + required: + - data + properties: + data: + type: array + items: { properties: { key: { type: string, description: 'The key of the environment variable.' }, value: { type: string, description: 'The value of the environment variable.' }, is_preview: { type: boolean, description: 'The flag to indicate if the environment variable is used in preview deployments.' }, is_build_time: { type: boolean, description: 'The flag to indicate if the environment variable is used in build time.' }, is_literal: { type: boolean, description: 'The flag to indicate if the environment variable is a literal, nothing espaced.' }, is_multiline: { type: boolean, description: 'The flag to indicate if the environment variable is multiline.' }, is_shown_once: { type: boolean, description: "The flag to indicate if the environment variable's value is shown on the UI." } }, type: object } + type: object + responses: + '201': + description: 'Environment variables updated.' + content: + application/json: + schema: + properties: + message: { type: string, example: 'Environment variables updated.' } + type: object + '401': + $ref: '#/components/responses/401' + '400': + $ref: '#/components/responses/400' + '404': + $ref: '#/components/responses/404' + security: + - + bearerAuth: [] + '/services/{uuid}/envs/{env_uuid}': + delete: + tags: + - Services + summary: 'Delete Env' + description: 'Delete env by UUID.' + operationId: delete-env-by-service-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the service.' + required: true + schema: + type: string + format: uuid + - + name: env_uuid + in: path + description: 'UUID of the environment variable.' + required: true + schema: + type: string + format: uuid + responses: + '200': + description: 'Environment variable deleted.' + content: + application/json: + schema: + properties: + message: { type: string, example: 'Environment variable deleted.' } + type: object + '401': + $ref: '#/components/responses/401' + '400': + $ref: '#/components/responses/400' + '404': + $ref: '#/components/responses/404' + security: + - + bearerAuth: [] /teams: get: tags: - Teams summary: List description: 'Get all teams.' - operationId: f9c530b5b25df9601cb87d6a58646f0a + operationId: list-teams responses: '200': description: 'List of teams.' @@ -3961,7 +4211,7 @@ paths: - Teams summary: Get description: 'Get team by TeamId.' - operationId: ac57ff546c002032cef44602c46a4e76 + operationId: get-team-by-id parameters: - name: id @@ -3992,7 +4242,7 @@ paths: - Teams summary: Members description: 'Get members by TeamId.' - operationId: 7858f5a45d9ea55184c182852a7f0f6c + operationId: get-members-by-team-id parameters: - name: id @@ -4025,7 +4275,7 @@ paths: - Teams summary: 'Authenticated Team' description: 'Get currently authenticated team.' - operationId: 6a4ec9fed1aad7b0b38356c47d7ac509 + operationId: get-current-team responses: '200': description: 'Current Team.' @@ -4046,7 +4296,7 @@ paths: - Teams summary: 'Authenticated Team Members' description: 'Get currently authenticated team members.' - operationId: 97e636a5796dbe71afb0bbcf1eec6e41 + operationId: get-current-team-members responses: '200': description: 'Currently authenticated team members.' diff --git a/other/logos/glueops.webp b/other/logos/glueops.webp new file mode 100644 index 000000000..d5acda999 Binary files /dev/null and b/other/logos/glueops.webp differ diff --git a/other/logos/juxtdigital.png b/other/logos/juxtdigital.png new file mode 100644 index 000000000..92257bd35 Binary files /dev/null and b/other/logos/juxtdigital.png differ diff --git a/other/logos/massivegrid.svg b/other/logos/massivegrid.svg new file mode 100644 index 000000000..09f7ba00f --- /dev/null +++ b/other/logos/massivegrid.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + diff --git a/other/logos/saasykit.png b/other/logos/saasykit.png new file mode 100644 index 000000000..27013eadd Binary files /dev/null and b/other/logos/saasykit.png differ diff --git a/other/logos/saasykit.webp b/other/logos/saasykit.webp new file mode 100644 index 000000000..0d1085e6c Binary files /dev/null and b/other/logos/saasykit.webp differ diff --git a/other/logos/ubicloud.svg b/other/logos/ubicloud.svg new file mode 100644 index 000000000..3613858ca --- /dev/null +++ b/other/logos/ubicloud.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/other/nightly/install.sh b/other/nightly/install.sh index 01fdcbc41..d87101141 100755 --- a/other/nightly/install.sh +++ b/other/nightly/install.sh @@ -6,7 +6,7 @@ set -e # Exit immediately if a command exits with a non-zero status #set -u # Treat unset variables as an error and exit set -o pipefail # Cause a pipeline to return the status of the last command that exited with a non-zero status -VERSION="1.3.4" +VERSION="1.4" DOCKER_VERSION="26.0" CDN="https://cdn.coollabs.io/coolify-nightly" @@ -45,6 +45,12 @@ if [ "$OS_TYPE" = 'amzn' ]; then fi LATEST_VERSION=$(curl --silent $CDN/versions.json | grep -i version | xargs | awk '{print $2}' | tr -d ',') +LATEST_HELPER_VERSION=$(curl --silent $CDN/versions.json | grep -i version | xargs | awk '{print $6}' | tr -d ',') + +if [ -z "$LATEST_HELPER_VERSION" ]; then + LATEST_HELPER_VERSION=latest +fi + DATE=$(date +"%Y%m%d-%H%M%S") if [ $EUID != 0 ]; then @@ -75,6 +81,7 @@ echo -e "-------------" echo "OS: $OS_TYPE $OS_VERSION" echo "Coolify version: $LATEST_VERSION" +echo "Helper version: $LATEST_HELPER_VERSION" echo -e "-------------" echo "Installing required packages..." @@ -342,7 +349,7 @@ if ! grep -qw "root@coolify" ~/.ssh/authorized_keys; then addSshKey fi -bash /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}" +bash /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}" "${LATEST_HELPER_VERSION:-latest}" echo "Waiting for 20 seconds for Coolify to be ready..." diff --git a/other/nightly/upgrade.sh b/other/nightly/upgrade.sh index 775cd3f81..bce82aaa5 100644 --- a/other/nightly/upgrade.sh +++ b/other/nightly/upgrade.sh @@ -1,8 +1,10 @@ #!/bin/bash ## Do not modify this file. You will lose the ability to autoupdate! -VERSION="1.0.6" +VERSION="1.1" CDN="https://cdn.coollabs.io/coolify-nightly" +LATEST_IMAGE=${1:-latest} +LATEST_HELPER_VERSION=${2:-latest} curl -fsSL $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml curl -fsSL $CDN/docker-compose.prod.yml -o /data/coolify/source/docker-compose.prod.yml @@ -31,7 +33,7 @@ docker network create --attachable coolify 2>/dev/null if [ -f /data/coolify/source/docker-compose.custom.yml ]; then echo "docker-compose.custom.yml detected." - docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper bash -c "LATEST_IMAGE=${1:-} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml -f /data/coolify/source/docker-compose.custom.yml up -d --remove-orphans --force-recreate --wait --wait-timeout 60" + docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper:${LATEST_HELPER_VERSION:-latest} bash -c "LATEST_IMAGE=${1:-} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml -f /data/coolify/source/docker-compose.custom.yml up -d --remove-orphans --force-recreate --wait --wait-timeout 60" else - docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper bash -c "LATEST_IMAGE=${1:-} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml up -d --remove-orphans --force-recreate --wait --wait-timeout 60" + docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper:${LATEST_HELPER_VERSION:-latest} bash -c "LATEST_IMAGE=${1:-} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml up -d --remove-orphans --force-recreate --wait --wait-timeout 60" fi diff --git a/other/nightly/versions.json b/other/nightly/versions.json index 9ad886308..7bb400bfd 100644 --- a/other/nightly/versions.json +++ b/other/nightly/versions.json @@ -1,10 +1,13 @@ { "coolify": { "v4": { - "version": "4.0.0-beta.324" + "version": "4.0.0-beta.330" }, "nightly": { - "version": "4.0.0-beta.324" + "version": "4.0.0-beta.331" + }, + "helper": { + "version": "1.0.0" } } -} \ No newline at end of file +} diff --git a/public/svgs/browserless.svg b/public/svgs/browserless.svg new file mode 100644 index 000000000..1d2d09a23 --- /dev/null +++ b/public/svgs/browserless.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svgs/plunk.svg b/public/svgs/plunk.svg new file mode 100644 index 000000000..3f6ed4792 --- /dev/null +++ b/public/svgs/plunk.svg @@ -0,0 +1 @@ + diff --git a/resources/views/livewire/project/application/deployment/index.blade.php b/resources/views/livewire/project/application/deployment/index.blade.php index 001499b71..f6fdb64ab 100644 --- a/resources/views/livewire/project/application/deployment/index.blade.php +++ b/resources/views/livewire/project/application/deployment/index.blade.php @@ -31,11 +31,12 @@ @endif @forelse ($deployments as $deployment)
+ 'dark:bg-coolgray-100 p-2 border-l-2 transition-colors hover:no-underline box-without-bg-without-border bg-white flex-col cursor-pointer dark:hover:text-neutral-400 dark:hover:bg-coolgray-200', + 'border-warning border-dashed ' => data_get($deployment, 'status') === 'in_progress' || data_get($deployment, 'status') === 'cancelled-by-user', - 'border-error' => data_get($deployment, 'status') === 'failed', + 'border-error border-dashed ' => + data_get($deployment, 'status') === 'failed', 'border-success' => data_get($deployment, 'status') === 'finished', ]) x-on:click.stop="goto('{{ $current_url . '/' . data_get($deployment, 'deployment_uuid') }}')"> @@ -106,7 +107,7 @@
@if ($deployment->status !== 'in_progress') - Finished 0s in + Finished 0s ago in 0s @else Running for 0s @@ -157,7 +158,7 @@ } }, measure_since_started() { - return dayjs.utc(created_at).fromNow(); + return dayjs.utc(created_at).fromNow(true); // "true" prevents the "ago" suffix }, })) diff --git a/resources/views/livewire/project/application/deployment/show.blade.php b/resources/views/livewire/project/application/deployment/show.blade.php index f97914ec2..9d9301d5c 100644 --- a/resources/views/livewire/project/application/deployment/show.blade.php +++ b/resources/views/livewire/project/application/deployment/show.blade.php @@ -9,6 +9,7 @@ fullscreen: false, alwaysScroll: false, intervalId: null, + showTimestamps: true, makeFullscreen() { this.fullscreen = !this.fullscreen; if (this.fullscreen === false) { @@ -53,63 +54,69 @@ class="dark:text-warning">{{ Str::headline(data_get($application_deployment_queue, 'status')) }}.
@endif -
+
- - - + class="flex flex-col-reverse w-full p-2 px-4 mt-4 overflow-y-auto bg-white dark:text-white dark:bg-coolgray-100 scrollbar dark:border-coolgray-300" + :class="fullscreen ? '' : 'min-h-14 max-h-[40rem] border border-dotted rounded'"> +
+
+ + + + + +
+
-
- @if (decode_remote_command_output($application_deployment_queue)->count() > 0) - @foreach (decode_remote_command_output($application_deployment_queue) as $line) + @forelse ($this->logLines as $line) +
$line['command'] ?? false, + 'flex gap-2 dark:hover:bg-coolgray-500 hover:bg-gray-100', + ])> + {{ $line['timestamp'] }} $line['hidden'], - 'text-red-500 font-bold whitespace-pre-line' => $line['type'] == 'stderr', - ])>[{{ $line['timestamp'] }}] @if ($line['hidden']) -

[COMMAND] {{ $line['command'] }}
[OUTPUT] - @endif @if (str($line['output'])->contains('http://') || str($line['output'])->contains('https://')) - @php - $line['output'] = preg_replace( - '/(https?:\/\/[^\s]+)/', - '$1', - $line['output'], - ); - @endphp {!! $line['output'] !!} - @else - {{ $line['output'] }} - - @endif -
- @endforeach - @else - No logs yet. - @endif + 'text-coollabs dark:text-warning' => $line['hidden'], + 'text-red-500' => $line['stderr'], + 'font-bold' => $line['command'] ?? false, + 'whitespace-pre-wrap', + ])>{!! $line['line'] !!} +
+ @empty + No logs yet. + @endforelse
diff --git a/resources/views/livewire/project/database/backup-executions.blade.php b/resources/views/livewire/project/database/backup-executions.blade.php index 6d177505c..54c3e2ede 100644 --- a/resources/views/livewire/project/database/backup-executions.blade.php +++ b/resources/views/livewire/project/database/backup-executions.blade.php @@ -4,7 +4,7 @@

Executions

Cleanup Failed Backups
-
+
@forelse($executions as $execution)
@empty -
No executions found.
+
No executions found.
@endforelse