Merge pull request #3316 from coollabsio/next

v4.0.0-beta.330
This commit is contained in:
Andras Bacsai
2024-09-06 14:53:39 +02:00
committed by GitHub
53 changed files with 1386 additions and 488 deletions

View File

@@ -21,7 +21,10 @@ DB_PASSWORD=password
DB_HOST=host.docker.internal
DB_PORT=5432
#Set custom ray port
# Ray Configuration
# Set to true to enable Ray
RAY_ENABLED=false
# Set custom ray port
RAY_PORT=
# Special Keys for Andras

View File

@@ -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:

View File

@@ -4,6 +4,8 @@ on:
push:
branches: ["main"]
paths-ignore:
- .github/workflows/coolify-helper.yml
- docker/coolify-helper/Dockerfile
- templates/service-templates.json
env:

View File

@@ -39,27 +39,27 @@ Special thanks to our biggest sponsors!
![image](https://github.com/user-attachments/assets/c95a07df-7c5a-4e77-a35a-81f25fcbece1)
* [CCCareers](https://cccareers.org/) - A career development platform for coding bootcamp graduates.
* [Hetzner](http://htznr.li/CoolifyXHetzner) - A German web hosting company offering dedicated servers and cloud services.
* [Logto](https://logto.io/?ref=coolify) - An open-source authentication and authorization platform.
* [BC Direct](https://bc.direct/?ref=coolify.io) - A digital marketing agency specializing in e-commerce solutions.
* [QuantCDN](https://www.quantcdn.io/?ref=coolify.io) - A content delivery network (CDN) for fast content delivery.
* [Arcjet](https://arcjet.com/?ref=coolify.io) - A cloud-based platform for data analytics and visualization.
* [SupaGuide](https://supa.guide/?ref=coolify.io) - A platform offering guides and resources for web development and design.
* [Tigris](https://tigrisdata.com/?ref=coolify.io) - A data integration platform for connecting and managing data sources.
* [Fractal Networks](https://fractalnetworks.co/?ref=coolify.io) - A decentralized network infrastructure for secure data exchange.
* [Advin](https://coolify.ad.vin/?ref=coolify.io) - A digital advertising agency specializing in programmatic advertising.
* [Treive](https://trieve.ai/?ref=coolify.io) - An AI-powered data analytics platform for business insights.
* [Blacksmith](https://blacksmith.sh/?ref=coolify.io) - A cloud-based platform for automating DevOps and infrastructure management.
* [Latitude](https://latitude.sh/?ref=coolify.io) - A platform offering location-based services and geospatial data.
* [Brand Dev](https://brand.dev/?ref=coolify.io) - A web development agency specializing in brand identity and digital presence.
* [Jobscollider](https://jobscollider.com/remote-jobs?ref=coolify.io) - A job search platform specializing in remote and flexible work opportunities.
* [Hostinger](https://hostinger.com?ref=coolify.io) - A web hosting company offering shared, VPS, and cloud hosting services.
* [Glueops](https://www.glueops.dev/?ref=coolify.io) - A DevOps and cloud consulting company offering infrastructure automation services.
* [Ubicloud](https://ubicloud.com/?ref=coolify.io) - A cloud-based platform for IoT device management and data analytics.
* [Juxtdigital](https://juxtdigital.dev/?ref=coolify.io) - A digital agency offering web development, design, and marketing services.
* [Saasykit](https://saasykit.com/?ref=coolify.io) - SaaSykit is a Laravel-based boilerplate with everything you need to build an awesome SaaS.
* [Massivegrid](https://massivegrid.com/?ref=coolify.io) - A cloud-based platform for data storage and processing.
* [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+)

View File

@@ -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);
}
}

View File

@@ -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();

View File

@@ -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);

View File

@@ -145,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);
@@ -171,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.'],
],
),

View File

@@ -483,6 +483,516 @@ 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.',

View File

@@ -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;
@@ -1293,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);

View File

@@ -1,32 +0,0 @@
<?php
namespace App\Jobs;
use App\Traits\ExecuteRemoteCommand;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ApplicationRestartJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, ExecuteRemoteCommand, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 3600;
public $tries = 1;
public string $applicationDeploymentQueueId;
public function __construct(string $applicationDeploymentQueueId)
{
$this->applicationDeploymentQueueId = $applicationDeploymentQueueId;
}
public function handle()
{
ray('Restarting application');
}
}

View File

@@ -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]);
}

View File

@@ -1,93 +0,0 @@
<?php
namespace App\Jobs;
use App\Actions\Server\InstallLogDrain;
use App\Models\Server;
use App\Notifications\Container\ContainerRestarted;
use App\Notifications\Container\ContainerStopped;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Sleep;
class CheckLogDrainContainerJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public Server $server) {}
public function middleware(): array
{
return [(new WithoutOverlapping($this->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);
}
}
}

View File

@@ -1,28 +0,0 @@
<?php
namespace App\Jobs;
use App\Actions\Server\UpdateCoolify;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class InstanceAutoUpdateJob implements ShouldBeEncrypted, ShouldBeUnique, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 600;
public $tries = 1;
public function __construct() {}
public function handle(): void
{
UpdateCoolify::run();
}
}

View File

@@ -1,50 +0,0 @@
<?php
namespace App\Jobs;
use App\Models\InstanceSettings;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
class PullCoolifyImageJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function handle(): void
{
try {
if (isDev() || isCloud()) {
return;
}
$settings = InstanceSettings::get();
$server = Server::findOrFail(0);
$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));
}
$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;
}
}
}

View File

@@ -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());

View File

@@ -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();

View File

@@ -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]+)/',
'<a href="$1" target="_blank" rel="noopener noreferrer" class="underline text-neutral-400">$1</a>',
$logLine['line'],
);
return $logLine;
});
}
public function render()
{
return view('livewire.project.application.deployment.show');

View File

@@ -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);

View File

@@ -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()

View File

@@ -28,6 +28,7 @@ class ScheduledTask extends BaseModel
public function executions(): HasMany
{
// Last execution first
return $this->hasMany(ScheduledTaskExecution::class)->orderBy('created_at', 'desc');
}

View File

@@ -880,7 +880,7 @@ $schema://$host {
public function muxFilename()
{
return "{$this->ip}_{$this->port}_{$this->user}";
return $this->uuid;
}
public function team()

View File

@@ -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;
}

View File

@@ -3219,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) {
@@ -3265,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();
}
}
}
}

View File

@@ -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),
];

View File

@@ -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.329',
'release' => '4.0.0-beta.330',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),

View File

@@ -1,3 +1,3 @@
<?php
return '4.0.0-beta.329';
return '4.0.0-beta.330';

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('instance_settings', function (Blueprint $table) {
$table->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');
});
}
};

View File

@@ -0,0 +1,37 @@
<?php
use App\Models\ServerSetting;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('server_settings', function (Blueprint $table) {
$table->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();
});
}
};

View File

@@ -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

View File

@@ -3024,7 +3024,7 @@ paths:
application/json:
schema:
properties:
uuid:
name:
type: string
description: 'The name of the project.'
description:
@@ -3932,6 +3932,256 @@ 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:

View File

@@ -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..."

View File

@@ -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

View File

@@ -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"
}
}
}
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 83.14 159.66"><defs><style>.cls-1{fill:url(#linear-gradient);}</style><linearGradient id="linear-gradient" x1="-19.19" y1="72.36" x2="89.91" y2="101.6" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#faed24"/><stop offset="0.5" stop-color="#f79421"/><stop offset="1" stop-color="#d41c5c"/></linearGradient></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="53.43 71.61 53.43 31.67 35.58 21.11 35.58 109.91 65.24 96 65.24 115.47 17.96 138.97 18 123.48 18 10.44 0.2 0 0 148.13 17.16 159.66 83.14 126.89 83.08 89.09 53.43 71.61"/></g></g></svg>

After

Width:  |  Height:  |  Size: 711 B

1
public/svgs/plunk.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 1080 1080"><path fill="#fff" d="M0 0h1080v1080H0z"></path><path fill="#000" d="M976 284.628q0 95.414-49.38 175.112-49.379 79.699-143.65 130.212-94.27 50.512-224.453 59.493L510.26 919.971Q482.204 1076 357.632 1076q-68.459 0-126.816-40.41-57.235-40.41-92.026-123.477Q104 829.048 104 707.816q0-227.871 72.947-386.145 74.07-159.396 197.519-237.973Q499.037 4 648.299 4q105.492 0 178.44 37.043 74.069 37.042 111.104 101.026Q976 204.929 976 284.628M578.718 533.826q230.064-29.185 230.065-239.095 0-74.086-49.38-120.109-48.258-47.145-150.384-47.146-115.593 0-202.007 72.964-85.293 72.963-132.428 203.175-46.013 129.088-46.013 295.221 0 69.596 13.468 123.476 14.59 53.88 35.912 84.189 22.446 29.185 42.646 29.185 28.057 0 42.646-77.454l37.035-212.154q-43.769-6.736-38.157-5.613-33.668-5.613-43.768-20.205-10.101-15.715-10.101-39.288 0-24.696 13.467-39.288 14.59-14.593 39.28-14.593 11.223 0 16.834 1.123 26.934 4.49 41.524 5.612 14.589-87.556 41.523-234.605 6.734-38.166 30.302-53.881 24.689-16.837 57.235-16.837 37.035 0 52.746 14.592 16.834 13.47 16.834 43.778 0 17.96-2.244 29.186z"></path><path fill="#fff" d="m304.835 467.541 121.264 22.411-14.248 90.985-20.76 118.879-125.003-23.173z"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -31,11 +31,12 @@
@endif
@forelse ($deployments as $deployment)
<div @class([
'dark:bg-coolgray-100 p-2 border-l border-dashed 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' =>
'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 @@
<div class="flex flex-col" x-data="elapsedTime('{{ $deployment->deployment_uuid }}', '{{ $deployment->status }}', '{{ $deployment->created_at }}', '{{ $deployment->updated_at }}')">
<div>
@if ($deployment->status !== 'in_progress')
Finished <span x-text="measure_since_started()">0s</span> in
Finished <span x-text="measure_since_started()">0s</span> ago in
<span class="font-bold" x-text="measure_finished_time()">0s</span>
@else
Running for <span class="font-bold" x-text="measure_since_started()">0s</span>
@@ -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
},
}))
</script>

View File

@@ -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')) }}</span>.
</div>
@endif
<div id="screen" :class="fullscreen ? 'fullscreen' : ''">
<div id="screen" :class="fullscreen ? 'fullscreen' : 'relative'">
<div @if ($isKeepAliveOn) wire:poll.2000ms="polling" @endif
class="relative 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 ? '' : 'max-h-[40rem] border border-dotted rounded'">
<button title="Minimize" x-show="fullscreen" class="fixed top-4 right-4"
x-on:click="makeFullscreen"><svg class="icon" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M6 14h4m0 0v4m0-4l-6 6m14-10h-4m0 0V6m0 4l6-6" />
</svg></button>
<button title="Go Top" x-show="fullscreen" class="fixed top-4 right-28" x-on:click="goTop"> <svg
class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M12 5v14m4-10l-4-4M8 9l4-4" />
</svg></button>
<button title="Follow Logs" x-show="fullscreen" :class="alwaysScroll ? 'dark:text-warning' : ''"
class="fixed top-4 right-16" x-on:click="toggleScroll"><svg class="icon" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M12 5v14m4-4l-4 4m-4-4l4 4" />
</svg></button>
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'">
<div :class="fullscreen ? 'fixed' : 'absolute'" class="top-4 right-6">
<div class="flex justify-end gap-4 fixed -translate-x-full">
<button title="Toggle timestamps" x-on:click="showTimestamps = !showTimestamps">
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none"
stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>
</button>
<button title="Go Top" x-show="fullscreen" x-on:click="goTop">
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="2" d="M12 5v14m4-10l-4-4M8 9l4-4" />
</svg>
</button>
<button title="Follow Logs" x-show="fullscreen" :class="alwaysScroll ? 'dark:text-warning' : ''"
x-on:click="toggleScroll">
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="2" d="M12 5v14m4-4l-4 4m-4-4l4 4" />
</svg>
</button>
<button title="Fullscreen" x-show="!fullscreen" x-on:click="makeFullscreen">
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g fill="none">
<path
d="M24 0v24H0V0h24ZM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018Zm.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022Zm-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01l-.184-.092Z" />
<path fill="currentColor"
d="M9.793 12.793a1 1 0 0 1 1.497 1.32l-.083.094L6.414 19H9a1 1 0 0 1 .117 1.993L9 21H4a1 1 0 0 1-.993-.883L3 20v-5a1 1 0 0 1 1.993-.117L5 15v2.586l4.793-4.793ZM20 3a1 1 0 0 1 .993.883L21 4v5a1 1 0 0 1-1.993.117L19 9V6.414l-4.793 4.793a1 1 0 0 1-1.497-1.32l.083-.094L17.586 5H15a1 1 0 0 1-.117-1.993L15 3h5Z" />
</g>
</svg>
</button>
<button title="Minimize" x-show="fullscreen" x-on:click="makeFullscreen">
<svg class="icon" viewBox="0 0 24 24"xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="2"
d="M6 14h4m0 0v4m0-4l-6 6m14-10h-4m0 0V6m0 4l6-6" />
</svg>
</button>
</div>
</div>
<button title="Fullscreen" x-show="!fullscreen" class="absolute top-2 right-8"
x-on:click="makeFullscreen"><svg class="fixed icon" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<g fill="none">
<path
d="M24 0v24H0V0h24ZM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018Zm.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022Zm-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01l-.184-.092Z" />
<path fill="currentColor"
d="M9.793 12.793a1 1 0 0 1 1.497 1.32l-.083.094L6.414 19H9a1 1 0 0 1 .117 1.993L9 21H4a1 1 0 0 1-.993-.883L3 20v-5a1 1 0 0 1 1.993-.117L5 15v2.586l4.793-4.793ZM20 3a1 1 0 0 1 .993.883L21 4v5a1 1 0 0 1-1.993.117L19 9V6.414l-4.793 4.793a1 1 0 0 1-1.497-1.32l.083-.094L17.586 5H15a1 1 0 0 1-.117-1.993L15 3h5Z" />
</g>
</svg></button>
<div id="logs" class="flex flex-col font-mono">
@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)
<div @class([
'mt-2' => $line['command'] ?? false,
'flex gap-2 dark:hover:bg-coolgray-500 hover:bg-gray-100',
])>
<span x-show="showTimestamps" class="shrink-0 text-gray-500">{{ $line['timestamp'] }}</span>
<span @class([
'text-coollabs dark:text-warning whitespace-pre-line' => $line['hidden'],
'text-red-500 font-bold whitespace-pre-line' => $line['type'] == 'stderr',
])>[{{ $line['timestamp'] }}] @if ($line['hidden'])
<br><br>[COMMAND] {{ $line['command'] }}<br>[OUTPUT]
@endif @if (str($line['output'])->contains('http://') || str($line['output'])->contains('https://'))
@php
$line['output'] = preg_replace(
'/(https?:\/\/[^\s]+)/',
'<a href="$1" target="_blank" class="underline text-neutral-400">$1</a>',
$line['output'],
);
@endphp {!! $line['output'] !!}
@else
{{ $line['output'] }}
@endif
</span>
@endforeach
@else
<span class="font-mono text-neutral-400">No logs yet.</span>
@endif
'text-coollabs dark:text-warning' => $line['hidden'],
'text-red-500' => $line['stderr'],
'font-bold' => $line['command'] ?? false,
'whitespace-pre-wrap',
])>{!! $line['line'] !!}</span>
</div>
@empty
<span class="font-mono text-neutral-400 mb-2">No logs yet.</span>
@endforelse
</div>
</div>
</div>

View File

@@ -4,7 +4,7 @@
<h3 class="py-4">Executions</h3>
<x-forms.button wire:click='cleanupFailed'>Cleanup Failed Backups</x-forms.button>
</div>
<div class="flex flex-col-reverse gap-4">
<div class="flex flex-col gap-4">
@forelse($executions as $execution)
<div wire:key="{{ data_get($execution, 'id') }}" @class([
'flex flex-col border-l-2 transition-colors p-4 ',
@@ -54,7 +54,7 @@
</div>
</div>
@empty
<div class="p-4 bg-gray-100 dark:bg-coolgray-200 rounded">No executions found.</div>
<div class="p-4 bg-gray-100 dark:bg-coolgray-100 rounded">No executions found.</div>
@endforelse
</div>
<script>

View File

@@ -32,18 +32,18 @@
@if (data_get($this->parameters, 'application_uuid'))
@foreach ($containers as $container)
<option value="{{ data_get($container, 'container.Names') }}">
{{ data_get($container, 'container.Names') }}
{{ data_get($container, 'container.Names') }} ({{ data_get($container, 'server.name') }})
</option>
@endforeach
@elseif(data_get($this->parameters, 'service_uuid'))
@foreach ($containers as $container)
<option value="{{ $container }}">
{{ $container }}
{{ $container }} ({{ data_get($servers, '0.name') }})
</option>
@endforeach
@else
<option value="{{ $container }}">
{{ $container }}
{{ $container }} ({{ data_get($servers, '0.name') }})
</option>
@endif
</x-forms.select>

View File

@@ -13,7 +13,7 @@
</x-forms.select>
@else
<x-forms.input placeholder="php" id="container"
helper="You can leave it empty if your resource only have one container." label="Container name" />
helper="You can leave this empty if your resource only has one container." label="Container name" />
@endif
@elseif ($type === 'service')
<x-forms.select id="container" label="Container name">

View File

@@ -1,36 +1,38 @@
<div class="flex flex-col-reverse gap-4">
<div class="flex flex-col gap-4">
@forelse($executions as $execution)
@if (data_get($execution, 'id') == $selectedKey)
<div class="p-4 mb-2 bg-gray-100 dark:bg-coolgray-200 rounded">
@if (data_get($execution, 'message'))
<div>
<pre class="whitespace-pre-wrap">{{ data_get($execution, 'message') }}</pre>
</div>
@else
<div>No output was recorded for this execution.</div>
<a wire:click="selectTask({{ data_get($execution, 'id') }})" @class([
'flex flex-col border-l-2 transition-colors p-4 cursor-pointer',
'bg-white hover:bg-gray-100 dark:bg-coolgray-100 dark:hover:bg-coolgray-200',
'text-black dark:text-white',
'bg-gray-200 dark:bg-coolgray-200' =>
data_get($execution, 'id') == $selectedKey,
'border-green-500' => data_get($execution, 'status') === 'success',
'border-red-500' => data_get($execution, 'status') === 'failed',
'border-yellow-500' => data_get($execution, 'status') === 'running',
])>
@if (data_get($execution, 'status') === 'running')
<div class="absolute top-2 right-2">
<x-loading />
</div>
@endif
<div class="text-gray-700 dark:text-gray-300 font-semibold mb-1">Status: {{ data_get($execution, 'status') }}
</div>
<div class="text-gray-600 dark:text-gray-400 text-sm">
Started At: {{ $this->formatDateInServerTimezone(data_get($execution, 'created_at', now())) }}
</div>
</a>
@if (data_get($execution, 'id') == $selectedKey)
<div class="p-4 mb-2 bg-gray-100 dark:bg-coolgray-200 rounded">
@if (data_get($execution, 'message'))
<div>
<pre class="whitespace-pre-wrap">{{ data_get($execution, 'message') }}</pre>
</div>
@else
<div>No output was recorded for this execution.</div>
@endif
</div>
@endif
</div>
@endif
<a wire:click="selectTask({{ data_get($execution, 'id') }})" @class([
'flex flex-col border-l-4 transition-colors cursor-pointer p-4 rounded',
'bg-white hover:bg-gray-100 dark:bg-coolgray-100 dark:hover:bg-coolgray-200',
'text-black dark:text-white',
'bg-gray-200 dark:bg-coolgray-200' => data_get($execution, 'id') == $selectedKey,
'border-green-500' => data_get($execution, 'status') === 'success',
'border-red-500' => data_get($execution, 'status') === 'failed',
'border-yellow-500' => data_get($execution, 'status') === 'running',
])>
@if (data_get($execution, 'status') === 'running')
<div class="absolute top-2 right-2">
<x-loading />
</div>
@endif
<div class="text-gray-700 dark:text-gray-300 font-semibold mb-1">Status: {{ data_get($execution, 'status') }}</div>
<div class="text-gray-600 dark:text-gray-400 text-sm">
Started At: {{ $this->formatDateInServerTimezone(data_get($execution, 'created_at', now())) }}
</div>
</a>
@empty
<div class="p-4 bg-gray-100 dark:bg-coolgray-200 rounded">No executions found.</div>
<div class="p-4 bg-gray-100 dark:bg-coolgray-100 rounded">No executions found.</div>
@endforelse
</div>
</div>

View File

@@ -30,11 +30,11 @@
<x-forms.input placeholder="0 0 * * * or daily" id="task.frequency" label="Frequency" required />
@if ($type === 'application')
<x-forms.input placeholder="php"
helper="You can leave it empty if your resource only have one container." id="task.container"
helper="You can leave this empty if your resource only has one container." id="task.container"
label="Container name" />
@elseif ($type === 'service')
<x-forms.input placeholder="php"
helper="You can leave it empty if your resource only have one service in your stack. Otherwise use the stack name, without the random generated id. So if you have a mysql service in your stack, use mysql."
helper="You can leave this empty if your resource only has one service in your stack. Otherwise use the stack name, without the random generated ID. So if you have a mysql service in your stack, use mysql."
id="task.container" label="Service name" />
@endif
</div>
@@ -42,6 +42,6 @@
<div class="pt-4">
<h3 class="py-4">Recent executions <span class="text-xs text-neutral-500">(click to check output)</span></h3>
<livewire:project.shared.scheduled-task.executions :task="$task" key="{{ $task->id }}" selectedKey="" :executions="$task->executions->take(-20)" />
<livewire:project.shared.scheduled-task.executions :task="$task" key="{{ $task->id }}" selectedKey="" :executions="$task->executions->take(20)" />
</div>
</div>

View File

@@ -178,64 +178,58 @@
</x-modal-input>
@endif
@endif
</div>
</div>
@if ($server->isFunctional())
<h3 class="pt-4">Settings</h3>
<div class="flex flex-col gap-2">
<div class="flex flex-col flex-wrap gap-2 sm:flex-nowrap">
<div class="w-64">
<x-forms.checkbox
helper="Enable force Docker Cleanup. This will cleanup build caches / unused images / etc."
instantSave id="server.settings.force_docker_cleanup" label="Force Docker Cleanup" />
<div class="flex flex-col gap-1">
<div class="flex flex-col gap-2">
<div class="flex flex-col flex-wrap gap-2 sm:flex-nowrap">
<div class="w-64">
<x-forms.checkbox
helper="Enable force Docker Cleanup. This will cleanup build caches / unused images / etc."
instantSave id="server.settings.force_docker_cleanup" label="Force Docker Cleanup" />
</div>
@if ($server->settings->force_docker_cleanup)
<x-forms.input placeholder="*/10 * * * *" id="server.settings.docker_cleanup_frequency"
label="Docker cleanup frequency" required
helper="Cron expression for Docker Cleanup.<br>You can use every_minute, hourly, daily, weekly, monthly, yearly.<br><br>Default is every 10 minutes." />
@else
<x-forms.input id="server.settings.docker_cleanup_threshold"
label="Docker cleanup threshold (%)" required
helper="The Docker cleanup tasks will run when the disk usage exceeds this threshold." />
@endif
</div>
@if ($server->settings->force_docker_cleanup)
<x-forms.input placeholder="*/10 * * * *" id="server.settings.docker_cleanup_frequency"
label="Docker cleanup frequency" required
helper="Cron expression for Docker Cleanup.<br>You can use every_minute, hourly, daily, weekly, monthly, yearly.<br><br>Default is every 10 minutes." />
@else
<x-forms.input id="server.settings.docker_cleanup_threshold"
label="Docker cleanup threshold (%)" required
helper="The Docker cleanup tasks will run when the disk usage exceeds this threshold." />
@endif
</div>
@else
<x-forms.input id="cleanup_after_percentage" label="Disk cleanup threshold (%)" required
helper="The disk cleanup task will run when the disk usage exceeds this threshold." />
<div class="w-64">
<x-forms.checkbox helper="This will cleanup build caches / unused images / etc every 10 minutes."
instantSave id="server.settings.is_force_cleanup_enabled"
label="Force Cleanup Docker Engine" />
<div class="flex flex-wrap gap-2 sm:flex-nowrap">
<x-forms.input id="server.settings.concurrent_builds" label="Number of concurrent builds" required
helper="You can specify the number of simultaneous build processes/deployments that should run concurrently." />
<x-forms.input id="server.settings.dynamic_timeout" label="Deployment timeout (seconds)" required
helper="You can define the maximum duration for a deployment to run before timing it out." />
</div>
@endif
<div class="flex flex-wrap gap-2 sm:flex-nowrap">
<x-forms.input id="server.settings.concurrent_builds" label="Number of concurrent builds" required
helper="You can specify the number of simultaneous build processes/deployments that should run concurrently." />
<x-forms.input id="server.settings.dynamic_timeout" label="Deployment timeout (seconds)" required
helper="You can define the maximum duration for a deployment to run before timing it out." />
</div>
<div class="flex items-center gap-2 pt-4 pb-2">
<h3>Sentinel</h3>
{{-- @if ($server->isSentinelEnabled()) --}}
{{-- <x-forms.button wire:click='restartSentinel'>Restart</x-forms.button> --}}
{{-- @endif --}}
</div>
<div>Metrics are disabled until a few bugs are fixed.</div>
{{-- <div class="w-64">
</div>
<div class="flex items-center gap-2 pt-4 pb-2">
<h3>Sentinel</h3>
{{-- @if ($server->isSentinelEnabled()) --}}
{{-- <x-forms.button wire:click='restartSentinel'>Restart</x-forms.button> --}}
{{-- @endif --}}
</div>
<div>Metrics are disabled until a few bugs are fixed.</div>
{{-- <div class="w-64">
<x-forms.checkbox instantSave id="server.settings.is_metrics_enabled" label="Enable Metrics" />
</div>
<div class="pt-4">
<div class="flex flex-wrap gap-2 sm:flex-nowrap">
<x-forms.input type="password" id="server.settings.metrics_token" label="Metrics token" required
helper="Token for collector (Sentinel)." />
helper="Token for collector (Sentinel)." />
<x-forms.input id="server.settings.metrics_refresh_rate_seconds" label="Metrics rate (seconds)"
required
helper="The interval for gathering metrics. Lower means more disk space will be used." />
required
helper="The interval for gathering metrics. Lower means more disk space will be used." />
<x-forms.input id="server.settings.metrics_history_days" label="Metrics history (days)" required
helper="How many days should the metrics data should be reserved." />
helper="How many days should the metrics data should be reserved." />
</div>
</div> --}}
@endif
</form>
</div>

View File

@@ -84,7 +84,7 @@ Route::group([
Route::get('/applications/{uuid}/envs', [ApplicationsController::class, 'envs']);
Route::post('/applications/{uuid}/envs', [ApplicationsController::class, 'create_env'])->middleware([IgnoreReadOnlyApiToken::class]);
Route::patch('/applications/{uuid}/envs/bulk', [ApplicationsController::class, 'create_bulk_envs'])->middleware([IgnoreReadOnlyApiToken::class]);
Route::patch('/applications/{uuid}/envs', [ApplicationsController::class, 'update_env_by_uuid']);
Route::patch('/applications/{uuid}/envs', [ApplicationsController::class, 'update_env_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]);
Route::delete('/applications/{uuid}/envs/{env_uuid}', [ApplicationsController::class, 'delete_env_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]);
Route::match(['get', 'post'], '/applications/{uuid}/start', [ApplicationsController::class, 'action_deploy'])->middleware([IgnoreReadOnlyApiToken::class]);
@@ -116,6 +116,12 @@ Route::group([
// Route::patch('/services/{uuid}', [ServicesController::class, 'update_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]);
Route::delete('/services/{uuid}', [ServicesController::class, 'delete_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]);
Route::get('/services/{uuid}/envs', [ServicesController::class, 'envs']);
Route::post('/services/{uuid}/envs', [ServicesController::class, 'create_env'])->middleware([IgnoreReadOnlyApiToken::class]);
Route::patch('/services/{uuid}/envs/bulk', [ServicesController::class, 'create_bulk_envs'])->middleware([IgnoreReadOnlyApiToken::class]);
Route::patch('/services/{uuid}/envs', [ServicesController::class, 'update_env_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]);
Route::delete('/services/{uuid}/envs/{env_uuid}', [ServicesController::class, 'delete_env_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]);
Route::match(['get', 'post'], '/services/{uuid}/start', [ServicesController::class, 'action_deploy'])->middleware([IgnoreReadOnlyApiToken::class]);
Route::match(['get', 'post'], '/services/{uuid}/restart', [ServicesController::class, 'action_restart'])->middleware([IgnoreReadOnlyApiToken::class]);
Route::match(['get', 'post'], '/services/{uuid}/stop', [ServicesController::class, 'action_stop'])->middleware([IgnoreReadOnlyApiToken::class]);

View File

@@ -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"
@@ -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..."

View File

@@ -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"
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

View File

@@ -0,0 +1,19 @@
# documentation: https://docs.browserless.io/
# slogan: A headless Chrome browser as a service .
# tags: chrome,headless,browser,service
# logo: svgs/browserless.svg
# port: 3000
services:
browserless:
image: ghcr.io/browserless/chromium
environment:
- TOKEN=$SERVICE_BASE64_BROWSERLESS_TOKEN
- SERVICE_FQDN_BROWSERLESS_3000
expose:
- 3000
healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:3000/docs"]
interval: 2s
timeout: 10s
retries: 15

View File

@@ -5,7 +5,7 @@
# port: 3000
services:
rails:
chatwoot:
image: chatwoot/chatwoot:latest
depends_on:
- postgres

View File

@@ -0,0 +1,59 @@
# documentation: https://docs.useplunk.com/getting-started/introduction
# slogan: Plunk, The Open-Source Email Platform for AWS
# tags: plunk,email,automation,aws
# logo: svgs/plunk.svg
# port: 3000
version: '3'
services:
plunk:
image: driaug/plunk
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_started
environment:
- SERVICE_FQDN_PLUNK_3000
- REDIS_URL=redis://redis:6379
- DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgresql/docmost?schema=public
- JWT_SECRET=${SERVICE_PASSWORD_JWT_SECRET}
- AWS_REGION=${AWS_REGION}
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
- AWS_SES_CONFIGURATION_SET=${AWS_SES_CONFIGURATION_SET}
- NEXT_PUBLIC_API_URI=${SERVICE_FQDN_PLUNK}/api
- APP_URI=${SERVICE_FQDN_PLUNK}
- API_URI=${SERVICE_FQDN_PLUNK}/api
- DISABLE_SIGNUPS=False
entrypoint: [ "/app/entry.sh" ]
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:3000"]
interval: 2s
timeout: 10s
retries: 15
postgresql:
image: postgres:16-alpine
environment:
- POSTGRES_USER=$SERVICE_USER_POSTGRES
- POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
- POSTGRES_DB=${POSTGRES_DB:-plunk}
volumes:
- postgresql-data:/var/lib/postgresql/data
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U postgres -d postgres" ]
interval: 5s
timeout: 10s
retries: 20
redis:
image: "redis:7.4-alpine"
volumes:
- "redis-data:/data"
healthcheck:
test:
- CMD
- redis-cli
- PING
interval: 5s
timeout: 10s
retries: 20

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,6 @@
<?php
use App\Actions\Service\DeleteService;
use App\Models\Application;
use App\Models\ApplicationPreview;
use App\Models\GithubApp;
@@ -8,7 +9,6 @@ use App\Models\Service;
use App\Models\StandaloneDocker;
use Illuminate\Support\Collection;
use Symfony\Component\Yaml\Yaml;
use Visus\Cuid2\Cuid2;
beforeEach(function () {
$this->applicationYaml = '
@@ -21,6 +21,7 @@ services:
APP_KEY: base64
APP_DEBUG: "${APP_DEBUG:-false}"
APP_URL: $SERVICE_FQDN_APP
DB_URL: postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@db:5432/postgres?schema=public
volumes:
- "./nginx:/etc/nginx"
- "data:/var/www/html"
@@ -29,8 +30,8 @@ services:
db:
image: postgres
environment:
POSTGRES_USER: "${POSTGRES_USER:-postgres}"
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-postgres}"
POSTGRES_USER: "${SERVICE_USER_POSTGRES}"
POSTGRES_PASSWORD: "${SERVICE_PASSWORD_POSTGRES}"
volumes:
- "dbdata:/var/lib/postgresql/data"
healthcheck:
@@ -86,75 +87,123 @@ networks:
'pull_request_html_url' => 'https://github.com/coollabsio/coolify-examples/pull/1',
]);
$this->serviceYaml = '
version: "3.8"
services:
activepieces:
image: ghcr.io/activepieces/activepieces:latest
environment:
- SERVICE_FQDN_ACTIVEPIECES
- AP_ENCRYPTION_KEY=$SERVICE_PASSWORD_ENCRYPTIONKEY
- AP_EXECUTION_MODE=UNSANDBOXED
- AP_FRONTEND_URL=$SERVICE_FQDN_ACTIVEPIECES
- AP_TEST=${AP_TEST:-test}
volumes:
- "dbdata:/var/lib/postgresql/data"
chatwoot:
image: chatwoot/chatwoot:latest
depends_on:
- postgres
- redis
activepieces2:
image: ghcr.io/activepieces/activepieces:latest
environment:
TEST: $SERVICE_FQDN_ACTIVEPIECES
- SERVICE_FQDN_CHATWOOT_3000
- SECRET_KEY_BASE=$SERVICE_PASSWORD_CHATWOOT
- FRONTEND_URL=${SERVICE_FQDN_CHATWOOT}
- DEFAULT_LOCALE=${CHATWOOT_DEFAULT_LOCALE}
- FORCE_SSL=false
- ENABLE_ACCOUNT_SIGNUP=false
- REDIS_URL=redis://default@redis:6379
- REDIS_PASSWORD=$SERVICE_PASSWORD_REDIS
- REDIS_OPENSSL_VERIFY_MODE=none
- POSTGRES_DATABASE=chatwoot
- POSTGRES_HOST=postgres
- POSTGRES_USERNAME=$SERVICE_USER_POSTGRES_USER
- POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
- RAILS_MAX_THREADS=5
- NODE_ENV=production
- RAILS_ENV=production
- INSTALLATION_ENV=docker
- MAILER_SENDER_EMAIL=${CHATWOOT_MAILER_SENDER_EMAIL}
- SMTP_ADDRESS=${CHATWOOT_SMTP_ADDRESS}
- SMTP_AUTHENTICATION=${CHATWOOT_SMTP_AUTHENTICATION}
- SMTP_DOMAIN=${CHATWOOT_SMTP_DOMAIN}
- SMTP_ENABLE_STARTTLS_AUTO=${CHATWOOT_SMTP_ENABLE_STARTTLS_AUTO}
- SMTP_PORT=${CHATWOOT_SMTP_PORT}
- SMTP_USERNAME=${CHATWOOT_SMTP_USERNAME}
- SMTP_PASSWORD=${CHATWOOT_SMTP_PASSWORD}
- ACTIVE_STORAGE_SERVICE=local
entrypoint: docker/entrypoints/rails.sh
command: sh -c "bundle exec rails db:chatwoot_prepare && bundle exec rails s -p 3000 -b 0.0.0.0"
volumes:
- "dbdata:/var/lib/postgresql/data"
- rails-data:/app/storage
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:3000"]
interval: 5s
timeout: 20s
retries: 10
sidekiq:
image: chatwoot/chatwoot:latest
depends_on:
- postgres
- redis
environment:
- SECRET_KEY_BASE=$SERVICE_PASSWORD_CHATWOOT
- FRONTEND_URL=${SERVICE_FQDN_CHATWOOT}
- DEFAULT_LOCALE=${CHATWOOT_DEFAULT_LOCALE}
- FORCE_SSL=false
- ENABLE_ACCOUNT_SIGNUP=false
- REDIS_URL=redis://default@redis:6379
- REDIS_PASSWORD=$SERVICE_PASSWORD_REDIS
- REDIS_OPENSSL_VERIFY_MODE=none
- POSTGRES_DATABASE=chatwoot
- POSTGRES_HOST=postgres
- POSTGRES_USERNAME=$SERVICE_USER_POSTGRES_USER
- POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
- RAILS_MAX_THREADS=5
- NODE_ENV=production
- RAILS_ENV=production
- INSTALLATION_ENV=docker
- MAILER_SENDER_EMAIL=${CHATWOOT_MAILER_SENDER_EMAIL}
- SMTP_ADDRESS=${CHATWOOT_SMTP_ADDRESS}
- SMTP_AUTHENTICATION=${CHATWOOT_SMTP_AUTHENTICATION}
- SMTP_DOMAIN=${CHATWOOT_SMTP_DOMAIN}
- SMTP_ENABLE_STARTTLS_AUTO=${CHATWOOT_SMTP_ENABLE_STARTTLS_AUTO}
- SMTP_PORT=${CHATWOOT_SMTP_PORT}
- SMTP_USERNAME=${CHATWOOT_SMTP_USERNAME}
- SMTP_PASSWORD=${CHATWOOT_SMTP_PASSWORD}
- ACTIVE_STORAGE_SERVICE=local
command: ["bundle", "exec", "sidekiq", "-C", "config/sidekiq.yml"]
volumes:
- sidekiq-data:/app/storage
healthcheck:
test: ["CMD-SHELL", "bundle exec rails runner \'puts Sidekiq.redis(&:info)\' > /dev/null 2>&1"]
interval: 30s
timeout: 10s
retries: 3
postgres:
image: postgres:latest
image: postgres:12
restart: always
volumes:
- postgres-data:/var/lib/postgresql/data
environment:
POSTGRES_DB: activepieces
POSTGRES_USER: $SERVICE_USER_POSTGRES
POSTGRES_PASSWORD: $SERVICE_PASSWORD_POSTGRES
volumes:
- "dbdata:/var/lib/postgresql/data"
- POSTGRES_DB=chatwoot
- POSTGRES_USER=$SERVICE_USER_POSTGRES_USER
- POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
healthcheck:
test:
- CMD
- pg_isready
- "-U"
- "postgres"
interval: 2s
test: ["CMD-SHELL", "pg_isready -U $SERVICE_USER_POSTGRES_USER -d chatwoot -h 127.0.0.1"]
interval: 30s
timeout: 10s
retries: 10
retries: 5
redis:
image: redis:latest
image: redis:alpine
restart: always
command: ["sh", "-c", "redis-server --requirepass \"$SERVICE_PASSWORD_REDIS\""]
volumes:
- "redis_data:/data"
- redis-data:/data
healthcheck:
test:
- CMD
- redis-cli
- ping
interval: 2s
test: ["CMD", "redis-cli", "-a", "$SERVICE_PASSWORD_REDIS", "PING"]
interval: 30s
timeout: 10s
retries: 10
volumes:
dbdata:
redis_data:
networks:
default:
name: something
external: true
noinet:
driver: bridge
internal: true';
retries: 5
';
$this->serviceComposeFileString = Yaml::parse($this->serviceYaml);
$this->service = Service::create([
'name' => 'Service for tests',
'uuid' => (string) new Cuid2(),
'uuid' => 'tgwcg8w4s844wkog8kskw44g',
'docker_compose_raw' => $this->serviceYaml,
'environment_id' => 1,
'server_id' => 0,
@@ -166,9 +215,18 @@ networks:
afterEach(function () {
// $this->applicationPreview->forceDelete();
$this->application->forceDelete();
DeleteService::run($this->service);
$this->service->forceDelete();
});
test('ServiceComposeParseNew', function () {
$output = newParser($this->service);
// ray('New parser');
// ray($output->toArray());
ray($this->service->environment_variables->pluck('value', 'key')->toArray());
expect($output)->toBeInstanceOf(Collection::class);
});
// test('ApplicationComposeParse', function () {
// expect($this->jsonapplicationComposeFile)->toBeJson()->ray();
@@ -332,14 +390,6 @@ afterEach(function () {
// });
// test('ServiceComposeParseNew', function () {
// $output = newParser($this->application, pull_request_id: 1, preview_id: $this->applicationPreview->id);
// // ray('New parser');
// // ray($output->toArray());
// ray($this->service->environment_variables_preview->pluck('value', 'key')->toArray());
// expect($output)->toBeInstanceOf(Collection::class);
// });
// test('ServiceComposeParseOld', function () {
// $output = parseDockerComposeFile($this->service);
// ray('Old parser');

View File

@@ -1,10 +1,10 @@
{
"coolify": {
"v4": {
"version": "4.0.0-beta.329"
"version": "4.0.0-beta.330"
},
"nightly": {
"version": "4.0.0-beta.330"
"version": "4.0.0-beta.331"
},
"helper": {
"version": "1.0.0"