commit
5ce449aa08
2
.github/workflows/development-build.yml
vendored
2
.github/workflows/development-build.yml
vendored
@ -13,7 +13,7 @@ env:
|
||||
|
||||
jobs:
|
||||
amd64:
|
||||
runs-on: [self-hosted, x64]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Login to ghcr.io
|
||||
|
2
.github/workflows/production-build.yml
vendored
2
.github/workflows/production-build.yml
vendored
@ -10,7 +10,7 @@ env:
|
||||
|
||||
jobs:
|
||||
amd64:
|
||||
runs-on: [self-hosted, x64]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Login to ghcr.io
|
||||
|
28
.tinkerwell/snippets/SendEmail.php
Normal file
28
.tinkerwell/snippets/SendEmail.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* @label Send Email
|
||||
* @description Send email to all users
|
||||
*/
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
set_transanctional_email_settings();
|
||||
|
||||
$users = User::whereEmail('andras.bacsai@gmail.com');
|
||||
foreach ($users as $user) {
|
||||
Mail::send([], [], function ($message) use ($user) {
|
||||
$message
|
||||
->to($user->email)
|
||||
->subject("Testing")
|
||||
->text(
|
||||
<<<EOF
|
||||
Hello,
|
||||
|
||||
Welcome to Coolify Cloud.
|
||||
Here is your user id: $user->id
|
||||
|
||||
EOF
|
||||
);
|
||||
});
|
||||
}
|
@ -4,26 +4,26 @@ namespace App\Actions\License;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
|
||||
class CheckResaleLicense
|
||||
{
|
||||
public function __invoke()
|
||||
use AsAction;
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
$settings = InstanceSettings::get();
|
||||
$settings->update([
|
||||
'is_resale_license_active' => false,
|
||||
]);
|
||||
if (isDev()) {
|
||||
$settings->update([
|
||||
'is_resale_license_active' => true,
|
||||
]);
|
||||
return;
|
||||
}
|
||||
if (!$settings->resale_license) {
|
||||
return;
|
||||
}
|
||||
// if (!$settings->resale_license) {
|
||||
// return;
|
||||
// }
|
||||
$base_url = config('coolify.license_url');
|
||||
if (isDev()) {
|
||||
$base_url = 'http://host.docker.internal:8787';
|
||||
}
|
||||
$instance_id = config('app.id');
|
||||
|
||||
ray("Checking license key against $base_url/lemon/validate");
|
||||
|
@ -4,7 +4,6 @@ namespace App\Actions\Service;
|
||||
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use App\Models\Service;
|
||||
use App\Notifications\Application\StatusChanged;
|
||||
|
||||
class StopService
|
||||
{
|
||||
|
@ -61,12 +61,14 @@ class ResourcesDelete extends Command
|
||||
|
||||
foreach ($serversToDelete as $server) {
|
||||
$toDelete = $servers->where('id', $server)->first();
|
||||
$this->info($toDelete);
|
||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||
if (!$confirmed) {
|
||||
break;
|
||||
if ($toDelete) {
|
||||
$this->info($toDelete);
|
||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||
if (!$confirmed) {
|
||||
break;
|
||||
}
|
||||
$toDelete->delete();
|
||||
}
|
||||
$toDelete->delete();
|
||||
}
|
||||
}
|
||||
private function deleteApplication()
|
||||
@ -82,14 +84,15 @@ class ResourcesDelete extends Command
|
||||
);
|
||||
|
||||
foreach ($applicationsToDelete as $application) {
|
||||
ray($application);
|
||||
$toDelete = $applications->where('id', $application)->first();
|
||||
$this->info($toDelete);
|
||||
$confirmed = confirm("Are you sure you want to delete all selected resources? ");
|
||||
if (!$confirmed) {
|
||||
break;
|
||||
if ($toDelete) {
|
||||
$this->info($toDelete);
|
||||
$confirmed = confirm("Are you sure you want to delete all selected resources? ");
|
||||
if (!$confirmed) {
|
||||
break;
|
||||
}
|
||||
$toDelete->delete();
|
||||
}
|
||||
$toDelete->delete();
|
||||
}
|
||||
}
|
||||
private function deleteDatabase()
|
||||
@ -106,12 +109,14 @@ class ResourcesDelete extends Command
|
||||
|
||||
foreach ($databasesToDelete as $database) {
|
||||
$toDelete = $databases->where('id', $database)->first();
|
||||
$this->info($toDelete);
|
||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||
if (!$confirmed) {
|
||||
return;
|
||||
if ($toDelete) {
|
||||
$this->info($toDelete);
|
||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||
if (!$confirmed) {
|
||||
return;
|
||||
}
|
||||
$toDelete->delete();
|
||||
}
|
||||
$toDelete->delete();
|
||||
}
|
||||
}
|
||||
private function deleteService()
|
||||
@ -128,12 +133,14 @@ class ResourcesDelete extends Command
|
||||
|
||||
foreach ($servicesToDelete as $service) {
|
||||
$toDelete = $services->where('id', $service)->first();
|
||||
$this->info($toDelete);
|
||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||
if (!$confirmed) {
|
||||
return;
|
||||
if ($toDelete) {
|
||||
$this->info($toDelete);
|
||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||
if (!$confirmed) {
|
||||
return;
|
||||
}
|
||||
$toDelete->delete();
|
||||
}
|
||||
$toDelete->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ class Kernel extends ConsoleKernel
|
||||
// Instance Jobs
|
||||
$schedule->command('horizon:snapshot')->everyMinute();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
||||
|
||||
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
||||
// Server Jobs
|
||||
$this->check_scheduled_backups($schedule);
|
||||
$this->check_resources($schedule);
|
||||
@ -34,7 +34,7 @@ class Kernel extends ConsoleKernel
|
||||
// Instance Jobs
|
||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||
$schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
||||
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
||||
|
||||
// Server Jobs
|
||||
$this->instance_auto_update($schedule);
|
||||
|
@ -41,7 +41,7 @@ class ApplicationController extends Controller
|
||||
if (!$application) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
['deployments' => $deployments, 'count' => $count] = $application->deployments(0, 8);
|
||||
['deployments' => $deployments, 'count' => $count] = $application->deployments(0, 40);
|
||||
return view('project.application.deployments', ['application' => $application, 'deployments' => $deployments, 'deployments_count' => $count]);
|
||||
}
|
||||
|
||||
|
@ -32,8 +32,14 @@ class MagicController extends Controller
|
||||
|
||||
public function environments()
|
||||
{
|
||||
$project = Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->first();
|
||||
if (!$project) {
|
||||
return response()->json([
|
||||
'environments' => []
|
||||
]);
|
||||
}
|
||||
return response()->json([
|
||||
'environments' => Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->first()->environments
|
||||
'environments' => $project->environments
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -164,7 +164,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
{
|
||||
$this->validate([
|
||||
'remoteServerName' => 'required',
|
||||
'remoteServerHost' => 'required|ip',
|
||||
'remoteServerHost' => 'required',
|
||||
'remoteServerPort' => 'required|integer',
|
||||
'remoteServerUser' => 'required',
|
||||
]);
|
||||
|
@ -32,7 +32,7 @@ class CheckLicense extends Component
|
||||
$this->settings->save();
|
||||
if ($this->settings->resale_license) {
|
||||
try {
|
||||
resolve(CheckResaleLicense::class)();
|
||||
CheckResaleLicense::run();
|
||||
$this->emit('reloadWindow');
|
||||
} catch (\Throwable $e) {
|
||||
session()->flash('error', 'Something went wrong. Please contact support. <br>Error: ' . $e->getMessage());
|
||||
|
@ -16,7 +16,7 @@ class Form extends Component
|
||||
protected $validationAttributes = [
|
||||
'destination.name' => 'name',
|
||||
'destination.network' => 'network',
|
||||
'destination.server.ip' => 'IP Address',
|
||||
'destination.server.ip' => 'IP Address/Domain',
|
||||
];
|
||||
|
||||
public function submit()
|
||||
|
@ -3,24 +3,31 @@
|
||||
namespace App\Http\Livewire\Project\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
|
||||
class Deployments extends Component
|
||||
{
|
||||
public Application $application;
|
||||
public $deployments = [];
|
||||
public Array|Collection $deployments = [];
|
||||
public int $deployments_count = 0;
|
||||
public string $current_url;
|
||||
public int $skip = 0;
|
||||
public int $default_take = 8;
|
||||
public int $default_take = 40;
|
||||
public bool $show_next = false;
|
||||
|
||||
public ?string $pull_request_id = null;
|
||||
protected $queryString = ['pull_request_id'];
|
||||
public function mount()
|
||||
{
|
||||
$this->current_url = url()->current();
|
||||
$this->show_pull_request_only();
|
||||
$this->show_more();
|
||||
}
|
||||
|
||||
private function show_pull_request_only() {
|
||||
if ($this->pull_request_id) {
|
||||
$this->deployments = $this->deployments->where('pull_request_id', $this->pull_request_id);
|
||||
}
|
||||
}
|
||||
private function show_more()
|
||||
{
|
||||
if (count($this->deployments) !== 0) {
|
||||
@ -47,6 +54,7 @@ class Deployments extends Component
|
||||
['deployments' => $deployments, 'count' => $count] = $this->application->deployments($this->skip, $take);
|
||||
$this->deployments = $deployments;
|
||||
$this->deployments_count = $count;
|
||||
$this->show_pull_request_only();
|
||||
$this->show_more();
|
||||
}
|
||||
}
|
||||
|
@ -152,7 +152,7 @@ class General extends Component
|
||||
$fqdn = generateFqdn($server, $this->application->uuid);
|
||||
$this->application->fqdn = $fqdn;
|
||||
$this->application->save();
|
||||
$this->emit('success', 'Application settings updated!');
|
||||
$this->updatedApplicationFqdn();
|
||||
}
|
||||
}
|
||||
public function resetDefaultLabels($showToaster = true)
|
||||
|
@ -65,4 +65,18 @@ class Heading extends Component
|
||||
$this->application->save();
|
||||
$this->application->refresh();
|
||||
}
|
||||
public function restart() {
|
||||
$this->setDeploymentUuid();
|
||||
queue_application_deployment(
|
||||
application_id: $this->application->id,
|
||||
deployment_uuid: $this->deploymentUuid,
|
||||
restart_only: true,
|
||||
);
|
||||
return redirect()->route('project.application.deployment', [
|
||||
'project_uuid' => $this->parameters['project_uuid'],
|
||||
'application_uuid' => $this->parameters['application_uuid'],
|
||||
'deployment_uuid' => $this->deploymentUuid,
|
||||
'environment_name' => $this->parameters['environment_name'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ use App\Traits\SaveFromRedirect;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Livewire\Component;
|
||||
use Spatie\Url\Url;
|
||||
|
||||
class GithubPrivateRepository extends Component
|
||||
{
|
||||
|
@ -13,7 +13,7 @@ class Index extends Component
|
||||
public $databases;
|
||||
public array $parameters;
|
||||
public array $query;
|
||||
protected $listeners = ["refreshStacks","checkStatus"];
|
||||
protected $listeners = ["refreshStacks", "checkStatus"];
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.index');
|
||||
|
@ -4,7 +4,6 @@ namespace App\Http\Livewire\Project\Service;
|
||||
|
||||
use App\Actions\Service\StartService;
|
||||
use App\Actions\Service\StopService;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Models\Service;
|
||||
use Livewire\Component;
|
||||
|
||||
@ -13,15 +12,14 @@ class Navbar extends Component
|
||||
public Service $service;
|
||||
public array $parameters;
|
||||
public array $query;
|
||||
protected $listeners = ["checkStatus"];
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.navbar');
|
||||
}
|
||||
|
||||
public function checkStatus()
|
||||
{
|
||||
$this->emit('checkStatus');
|
||||
public function checkStatus() {
|
||||
$this->service->refresh();
|
||||
}
|
||||
public function deploy()
|
||||
{
|
||||
@ -34,6 +32,6 @@ class Navbar extends Component
|
||||
StopService::run($this->service);
|
||||
$this->service->refresh();
|
||||
$this->emit('success', 'Service stopped successfully.');
|
||||
$this->checkStatus();
|
||||
$this->emit('checkStatus');
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Http\Livewire\Project\Shared;
|
||||
|
||||
use App\Jobs\StopResourceJob;
|
||||
use App\Jobs\DeleteResourceJob;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
@ -21,7 +21,7 @@ class Danger extends Component
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
StopResourceJob::dispatchSync($this->resource);
|
||||
DeleteResourceJob::dispatchSync($this->resource);
|
||||
return redirect()->route('project.resources', [
|
||||
'project_uuid' => $this->parameters['project_uuid'],
|
||||
'environment_name' => $this->parameters['environment_name']
|
||||
|
@ -33,9 +33,11 @@ class GetLogs extends Component
|
||||
if ($refresh) {
|
||||
$this->outputs = '';
|
||||
}
|
||||
Process::run($sshCommand, function (string $type, string $output) {
|
||||
$this->doSomethingWithThisChunkOfOutput($output);
|
||||
});
|
||||
$command = Process::run($sshCommand);
|
||||
$output = $command->output();
|
||||
$error = $command->errorOutput();
|
||||
$this->doSomethingWithThisChunkOfOutput($output);
|
||||
$this->doSomethingWithThisChunkOfOutput($error);
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
|
31
app/Http/Livewire/Server/Delete.php
Normal file
31
app/Http/Livewire/Server/Delete.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Server;
|
||||
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Livewire\Component;
|
||||
|
||||
class Delete extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
public $server;
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
$this->authorize('delete', $this->server);
|
||||
if (!$this->server->isEmpty()) {
|
||||
$this->emit('error', 'Server has defined resources. Please delete them first.');
|
||||
return;
|
||||
}
|
||||
$this->server->delete();
|
||||
return redirect()->route('server.all');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.server.delete');
|
||||
}
|
||||
}
|
@ -4,12 +4,10 @@ namespace App\Http\Livewire\Server;
|
||||
|
||||
use App\Actions\Server\InstallDocker;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Livewire\Component;
|
||||
|
||||
class Form extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
public Server $server;
|
||||
public bool $isValidConnection = false;
|
||||
public bool $isValidDocker = false;
|
||||
@ -32,7 +30,7 @@ class Form extends Component
|
||||
protected $validationAttributes = [
|
||||
'server.name' => 'Name',
|
||||
'server.description' => 'Description',
|
||||
'server.ip' => 'IP address',
|
||||
'server.ip' => 'IP address/Domain',
|
||||
'server.user' => 'User',
|
||||
'server.port' => 'Port',
|
||||
'server.settings.is_cloudflare_tunnel' => 'Cloudflare Tunnel',
|
||||
@ -106,26 +104,12 @@ class Form extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
$this->authorize('delete', $this->server);
|
||||
if (!$this->server->isEmpty()) {
|
||||
$this->emit('error', 'Server has defined resources. Please delete them first.');
|
||||
return;
|
||||
}
|
||||
$this->server->delete();
|
||||
return redirect()->route('server.all');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
if(isCloud() && !isDev()) {
|
||||
if (isCloud() && !isDev()) {
|
||||
$this->validate();
|
||||
$this->validate([
|
||||
'server.ip' => 'required|ip',
|
||||
'server.ip' => 'required',
|
||||
]);
|
||||
} else {
|
||||
$this->validate();
|
||||
|
@ -26,14 +26,14 @@ class ByIp extends Component
|
||||
protected $rules = [
|
||||
'name' => 'required|string',
|
||||
'description' => 'nullable|string',
|
||||
'ip' => 'required|ip',
|
||||
'ip' => 'required',
|
||||
'user' => 'required|string',
|
||||
'port' => 'required|integer',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'name' => 'Name',
|
||||
'description' => 'Description',
|
||||
'ip' => 'IP Address',
|
||||
'ip' => 'IP Address/Domain',
|
||||
'user' => 'User',
|
||||
'port' => 'Port',
|
||||
];
|
||||
|
@ -44,6 +44,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
private int $pull_request_id;
|
||||
private string $commit;
|
||||
private bool $force_rebuild;
|
||||
private bool $restart_only;
|
||||
|
||||
private ?string $dockerImage = null;
|
||||
private ?string $dockerImageTag = null;
|
||||
@ -94,6 +95,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->pull_request_id = $this->application_deployment_queue->pull_request_id;
|
||||
$this->commit = $this->application_deployment_queue->commit;
|
||||
$this->force_rebuild = $this->application_deployment_queue->force_rebuild;
|
||||
$this->restart_only = $this->application_deployment_queue->restart_only;
|
||||
|
||||
$source = data_get($this->application, 'source');
|
||||
if ($source) {
|
||||
@ -136,9 +138,16 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
public function handle(): void
|
||||
{
|
||||
// ray()->measure();
|
||||
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id);
|
||||
if ($containers->count() > 0) {
|
||||
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
|
||||
if ($containers->count() === 1) {
|
||||
$this->currently_running_container_name = data_get($containers[0], 'Names');
|
||||
} else {
|
||||
$foundContainer = $containers->filter(function ($container) {
|
||||
return !str(data_get($container, 'Names'))->startsWith("{$this->application->uuid}-pr-");
|
||||
})->first();
|
||||
if ($foundContainer) {
|
||||
$this->currently_running_container_name = data_get($foundContainer, 'Names');
|
||||
}
|
||||
}
|
||||
if ($this->pull_request_id !== 0 && $this->pull_request_id !== null) {
|
||||
$this->currently_running_container_name = $this->container_name;
|
||||
@ -182,7 +191,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->application->git_repository = "$gitHost:$gitRepo";
|
||||
}
|
||||
try {
|
||||
if ($this->application->dockerfile) {
|
||||
if ($this->restart_only) {
|
||||
$this->just_restart();
|
||||
} else if ($this->application->dockerfile) {
|
||||
$this->deploy_simple_dockerfile();
|
||||
} else if ($this->application->build_pack === 'dockerimage') {
|
||||
$this->deploy_dockerimage_buildpack();
|
||||
@ -264,6 +275,49 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
// [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
|
||||
// );
|
||||
// }
|
||||
private function generate_image_names()
|
||||
{
|
||||
if ($this->application->dockerfile) {
|
||||
$this->build_image_name = Str::lower("{$this->application->git_repository}:build");
|
||||
$this->production_image_name = Str::lower("{$this->application->uuid}:latest");
|
||||
} else if ($this->application->build_pack === 'dockerimage') {
|
||||
$this->production_image_name = Str::lower("{$this->dockerImage}:{$this->dockerImageTag}");
|
||||
} else if ($this->pull_request_id !== 0) {
|
||||
$this->build_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}-build");
|
||||
$this->production_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}");
|
||||
} else {
|
||||
$tag = Str::of("{$this->commit}-{$this->application->id}-{$this->pull_request_id}");
|
||||
if (strlen($tag) > 128) {
|
||||
$tag = $tag->substr(0, 128);
|
||||
}
|
||||
$this->build_image_name = Str::lower("{$this->application->git_repository}:{$tag}-build");
|
||||
$this->production_image_name = Str::lower("{$this->application->uuid}:{$tag}");
|
||||
}
|
||||
ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
|
||||
}
|
||||
private function just_restart()
|
||||
{
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo 'Starting deployment of {$this->application->git_repository}:{$this->application->git_branch}.'"
|
||||
],
|
||||
);
|
||||
$this->prepare_builder_image();
|
||||
$this->check_git_if_build_needed();
|
||||
$this->set_base_dir();
|
||||
$this->generate_image_names();
|
||||
$this->execute_remote_command([
|
||||
"docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found"
|
||||
]);
|
||||
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
|
||||
$this->generate_compose_file();
|
||||
$this->rolling_update();
|
||||
return;
|
||||
}
|
||||
$this->execute_remote_command([
|
||||
"echo 'Cannot find image {$this->production_image_name} locally. Please redeploy the application.'",
|
||||
]);
|
||||
}
|
||||
private function save_environment_variables()
|
||||
{
|
||||
$envs = collect([]);
|
||||
@ -291,9 +345,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d > $this->workdir/Dockerfile")
|
||||
],
|
||||
);
|
||||
$this->build_image_name = Str::lower("{$this->application->git_repository}:build");
|
||||
$this->production_image_name = Str::lower("{$this->application->uuid}:latest");
|
||||
// ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
|
||||
$this->generate_image_names();
|
||||
$this->generate_compose_file();
|
||||
$this->generate_build_env_variables();
|
||||
$this->add_build_env_variables_to_dockerfile();
|
||||
@ -311,7 +363,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
"echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.'"
|
||||
],
|
||||
);
|
||||
$this->production_image_name = Str::lower("{$this->dockerImage}:{$this->dockerImageTag}");
|
||||
$this->generate_image_names();
|
||||
$this->prepare_builder_image();
|
||||
$this->generate_compose_file();
|
||||
$this->rolling_update();
|
||||
@ -328,16 +380,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
],
|
||||
);
|
||||
$this->prepare_builder_image();
|
||||
$this->check_git_if_build_needed();
|
||||
$this->clone_repository();
|
||||
$this->set_base_dir();
|
||||
$tag = Str::of("{$this->commit}-{$this->application->id}-{$this->pull_request_id}");
|
||||
if (strlen($tag) > 128) {
|
||||
$tag = $tag->substr(0, 128);
|
||||
}
|
||||
|
||||
$this->build_image_name = Str::lower("{$this->application->git_repository}:{$tag}-build");
|
||||
$this->production_image_name = Str::lower("{$this->application->uuid}:{$tag}");
|
||||
// ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
|
||||
$this->generate_image_names();
|
||||
$this->cleanup_git();
|
||||
$this->generate_compose_file();
|
||||
$this->generate_build_env_variables();
|
||||
@ -355,15 +401,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->prepare_builder_image();
|
||||
$this->check_git_if_build_needed();
|
||||
$this->set_base_dir();
|
||||
$tag = Str::of("{$this->commit}-{$this->application->id}-{$this->pull_request_id}");
|
||||
if (strlen($tag) > 128) {
|
||||
$tag = $tag->substr(0, 128);
|
||||
}
|
||||
|
||||
$this->build_image_name = Str::lower("{$this->application->git_repository}:{$tag}-build");
|
||||
$this->production_image_name = Str::lower("{$this->application->uuid}:{$tag}");
|
||||
// ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
|
||||
|
||||
$this->generate_image_names();
|
||||
if (!$this->force_rebuild) {
|
||||
$this->execute_remote_command([
|
||||
"docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found"
|
||||
@ -396,7 +434,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
if (count($this->application->ports_mappings_array) > 0) {
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Application has ports mapped to the host system, rolling update is not supported. Stopping current container.'"],
|
||||
["echo -n 'Application has ports mapped to the host system, rolling update is not supported.'"],
|
||||
);
|
||||
$this->stop_running_container(force: true);
|
||||
$this->start_by_compose_file();
|
||||
@ -457,9 +495,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
private function deploy_pull_request()
|
||||
{
|
||||
$this->build_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}-build");
|
||||
$this->production_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}");
|
||||
// ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
|
||||
$this->generate_image_names();
|
||||
$this->execute_remote_command([
|
||||
"echo 'Starting pull request (#{$this->pull_request_id}) deployment of {$this->application->git_repository}:{$this->application->git_branch}.'",
|
||||
]);
|
||||
@ -475,7 +511,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
// $this->generate_build_env_variables();
|
||||
// $this->add_build_env_variables_to_dockerfile();
|
||||
$this->build_image();
|
||||
$this->stop_running_container();
|
||||
if ($this->currently_running_container_name) {
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Removing old version of your application.'"],
|
||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->currently_running_container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
|
||||
);
|
||||
}
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Starting preview deployment.'"],
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
|
||||
@ -545,7 +586,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
);
|
||||
}
|
||||
|
||||
$this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t");
|
||||
if ($this->saved_outputs->get('git_commit_sha')) {
|
||||
$this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t");
|
||||
}
|
||||
}
|
||||
private function clone_repository()
|
||||
{
|
||||
@ -596,7 +639,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
if ($this->application->deploymentType() === 'deploy_key') {
|
||||
$this->fullRepoUrl = $this->application->git_repository;
|
||||
$private_key = base64_encode($this->application->private_key->private_key);
|
||||
$private_key = data_get($this->application, 'private_key.private_key');
|
||||
if (is_null($private_key)) {
|
||||
throw new Exception('Private key not found. Please add a private key to the application and try again.');
|
||||
}
|
||||
$private_key = base64_encode($private_key);
|
||||
$git_clone_command = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_repository} {$this->basedir}";
|
||||
$git_clone_command = $this->set_git_import_settings($git_clone_command);
|
||||
$commands = collect([
|
||||
@ -659,7 +706,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
private function nixpacks_build_cmd()
|
||||
{
|
||||
$this->generate_env_variables();
|
||||
$nixpacks_command = "nixpacks build --no-cache -o {$this->workdir} {$this->env_args} --no-error-without-start";
|
||||
$nixpacks_command = "nixpacks build --cache-key '{$this->application->uuid}' -o {$this->workdir} {$this->env_args} --no-error-without-start";
|
||||
if ($this->application->build_command) {
|
||||
$nixpacks_command .= " --build-cmd \"{$this->application->build_command}\"";
|
||||
}
|
||||
@ -707,6 +754,23 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
} else {
|
||||
$labels = collect(generateLabelsApplication($this->application, $this->preview));
|
||||
}
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$newLabels = collect(generateLabelsApplication($this->application, $this->preview));
|
||||
$newHostLabel = $newLabels->filter(function ($label) {
|
||||
return str($label)->contains('Host');
|
||||
});
|
||||
$labels = $labels->reject(function ($label) {
|
||||
return str($label)->contains('Host');
|
||||
});
|
||||
|
||||
$labels = $labels->map(function ($label) {
|
||||
$pattern = '/([a-zA-Z0-9]+)-(\d+)-(http|https)/';
|
||||
$replacement = "$1-pr-{$this->pull_request_id}-$2-$3";
|
||||
$newLabel = preg_replace($pattern, $replacement, $label);
|
||||
return $newLabel;
|
||||
});
|
||||
$labels = $labels->merge($newHostLabel);
|
||||
}
|
||||
$labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray();
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
@ -907,12 +971,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
if ($this->newVersionIsHealthy || $force) {
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Removing old version of your application.'"],
|
||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->currently_running_container_name >/dev/null 2>&1"), "hidden" => true],
|
||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->currently_running_container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
|
||||
);
|
||||
} else {
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'New version is not healthy, rolling back to the old version.'"],
|
||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true],
|
||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ class CheckResaleLicenseJob implements ShouldQueue, ShouldBeEncrypted
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
resolve(CheckResaleLicense::class)();
|
||||
CheckResaleLicense::run();
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('CheckResaleLicenseJob failed with: ' . $e->getMessage());
|
||||
ray($e);
|
||||
|
@ -18,7 +18,6 @@ use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
@ -26,6 +25,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
$this->handle();
|
||||
}
|
||||
public function middleware(): array
|
||||
{
|
||||
@ -58,6 +58,23 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->server->update([
|
||||
'unreachable_count' => 0,
|
||||
]);
|
||||
// Update all applications, databases and services to exited
|
||||
foreach ($this->server->applications() as $application) {
|
||||
$application->update(['status' => 'exited']);
|
||||
}
|
||||
foreach ($this->server->databases() as $database) {
|
||||
$database->update(['status' => 'exited']);
|
||||
}
|
||||
foreach ($this->server->services() as $service) {
|
||||
$apps = $service->applications()->get();
|
||||
$dbs = $service->databases()->get();
|
||||
foreach ($apps as $app) {
|
||||
$app->update(['status' => 'exited']);
|
||||
}
|
||||
foreach ($dbs as $db) {
|
||||
$db->update(['status' => 'exited']);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
$result = $this->server->validateConnection();
|
||||
@ -138,11 +155,10 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$containerStatus = "$containerStatus ($containerHealth)";
|
||||
$labels = data_get($container, 'Config.Labels');
|
||||
$labels = Arr::undot(format_docker_labels_to_json($labels));
|
||||
$labelId = data_get($labels, 'coolify.applicationId');
|
||||
if ($labelId) {
|
||||
if (str_contains($labelId, '-pr-')) {
|
||||
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
||||
$applicationId = (int) Str::before($labelId, '-pr-');
|
||||
$applicationId = data_get($labels, 'coolify.applicationId');
|
||||
if ($applicationId) {
|
||||
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
||||
if ($pullRequestId) {
|
||||
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
||||
if ($preview) {
|
||||
$foundApplicationPreviews[] = $preview->id;
|
||||
@ -154,7 +170,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
//Notify user that this container should not be there.
|
||||
}
|
||||
} else {
|
||||
$application = $applications->where('id', $labelId)->first();
|
||||
$application = $applications->where('id', $applicationId)->first();
|
||||
if ($application) {
|
||||
$foundApplications[] = $application->id;
|
||||
$statusFromDb = $application->status;
|
||||
@ -230,10 +246,13 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$name = data_get($exitedService, 'name');
|
||||
$fqdn = data_get($exitedService, 'fqdn');
|
||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||
$project = data_get($service, 'environment.project');
|
||||
$environment = data_get($service, 'environment');
|
||||
$projectUuid = data_get($service, 'environment.project.uuid');
|
||||
$serviceUuid = data_get($service, 'uuid');
|
||||
$environmentName = data_get($service, 'environment.name');
|
||||
|
||||
$url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/service/" . $service->uuid;
|
||||
if ($projectUuid && $serviceUuid && $environmentName) {
|
||||
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/service/" . $serviceUuid;
|
||||
}
|
||||
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
$exitedService->update(['status' => 'exited']);
|
||||
}
|
||||
@ -251,10 +270,13 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
|
||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||
|
||||
$project = data_get($application, 'environment.project');
|
||||
$environment = data_get($application, 'environment');
|
||||
$projectUuid = data_get($application, 'environment.project.uuid');
|
||||
$applicationUuid = data_get($application, 'uuid');
|
||||
$environment = data_get($application, 'environment.name');
|
||||
|
||||
$url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/application/" . $application->uuid;
|
||||
if ($projectUuid && $applicationUuid && $environment) {
|
||||
$url = base_url() . '/project/' . $projectUuid . "/" . $environment . "/application/" . $applicationUuid;
|
||||
}
|
||||
|
||||
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
}
|
||||
@ -271,10 +293,14 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
|
||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||
|
||||
$project = data_get($preview, 'application.environment.project');
|
||||
$environment = data_get($preview, 'application.environment');
|
||||
$projectUuid = data_get($preview, 'application.environment.project.uuid');
|
||||
$environmentName = data_get($preview, 'application.environment.name');
|
||||
$applicationUuid = data_get($preview, 'application.uuid');
|
||||
|
||||
if ($projectUuid && $applicationUuid && $environmentName) {
|
||||
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/application/" . $applicationUuid;
|
||||
}
|
||||
|
||||
$url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/application/" . $preview->application->uuid;
|
||||
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
}
|
||||
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
||||
@ -290,10 +316,13 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
|
||||
$containerName = $name;
|
||||
|
||||
$project = data_get($database, 'environment.project');
|
||||
$environment = data_get($database, 'environment');
|
||||
$projectUuid = data_get($database, 'environment.project.uuid');
|
||||
$environmentName = data_get($database, 'environment.name');
|
||||
$databaseUuid = data_get($database, 'uuid');
|
||||
|
||||
$url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/database/" . $database->uuid;
|
||||
if ($projectUuid && $databaseUuid && $environmentName) {
|
||||
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/database/" . $databaseUuid;
|
||||
}
|
||||
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Actions\Database\StopDatabase;
|
||||
use App\Models\S3Storage;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\ScheduledDatabaseBackupExecution;
|
||||
@ -22,6 +23,7 @@ use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Str;
|
||||
use Throwable;
|
||||
|
||||
class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
@ -64,6 +66,14 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
// Check if team is exists
|
||||
if (is_null($this->team)) {
|
||||
$this->backup->update(['status' => 'failed']);
|
||||
StopDatabase::run($this->database);
|
||||
$this->database->delete();
|
||||
return;
|
||||
}
|
||||
|
||||
$status = Str::of(data_get($this->database, 'status'));
|
||||
if (!$status->startsWith('running') && $this->database->id !== 0) {
|
||||
ray('database not running');
|
||||
|
@ -19,7 +19,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class StopResourceJob implements ShouldQueue, ShouldBeEncrypted
|
||||
class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
@ -32,6 +32,7 @@ class StopResourceJob implements ShouldQueue, ShouldBeEncrypted
|
||||
try {
|
||||
$server = $this->resource->destination->server;
|
||||
if (!$server->isFunctional()) {
|
||||
$this->resource->delete();
|
||||
return 'Server is not functional';
|
||||
}
|
||||
switch ($this->resource->type()) {
|
||||
@ -57,11 +58,10 @@ class StopResourceJob implements ShouldQueue, ShouldBeEncrypted
|
||||
StopService::run($this->resource);
|
||||
break;
|
||||
}
|
||||
$this->resource->delete();
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage());
|
||||
throw $e;
|
||||
} finally {
|
||||
$this->resource->delete();
|
||||
}
|
||||
}
|
||||
}
|
@ -46,11 +46,12 @@ class SendMessageToTelegramJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if (!empty($this->buttons)) {
|
||||
foreach ($this->buttons as $button) {
|
||||
$buttonUrl = data_get($button, 'url');
|
||||
$text = data_get($button, 'text', 'Click here');
|
||||
if ($buttonUrl && Str::contains($buttonUrl, 'http://localhost')) {
|
||||
$buttonUrl = str_replace('http://localhost', config('app.url'), $buttonUrl);
|
||||
}
|
||||
$inlineButtons[] = [
|
||||
'text' => $button['text'],
|
||||
'text' => $text,
|
||||
'url' => $buttonUrl,
|
||||
];
|
||||
}
|
||||
|
@ -40,7 +40,6 @@ class Service extends BaseModel
|
||||
instant_remote_process(["docker volume rm -f $storage->name"], $service->server, false);
|
||||
});
|
||||
}
|
||||
instant_remote_process(["docker network rm {$service->uuid}"], $service->server, false);
|
||||
});
|
||||
}
|
||||
public function type()
|
||||
@ -90,6 +89,10 @@ class Service extends BaseModel
|
||||
{
|
||||
return $this->hasMany(EnvironmentVariable::class)->orderBy('key', 'asc');
|
||||
}
|
||||
public function environment_variables_preview(): HasMany
|
||||
{
|
||||
return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->orderBy('key', 'asc');
|
||||
}
|
||||
public function workdir()
|
||||
{
|
||||
return service_configuration_dir() . "/{$this->uuid}";
|
||||
@ -257,7 +260,7 @@ class Service extends BaseModel
|
||||
$networks = $serviceNetworks->toArray();
|
||||
foreach ($definedNetwork as $key => $network) {
|
||||
$networks = array_merge($networks, [
|
||||
$network
|
||||
$network => null
|
||||
]);
|
||||
}
|
||||
data_set($service, 'networks', $networks);
|
||||
|
@ -55,6 +55,6 @@ class StandaloneDocker extends BaseModel
|
||||
|
||||
public function attachedTo()
|
||||
{
|
||||
return $this->applications?->count() > 0 || $this->databases?->count() > 0;
|
||||
return $this->applications?->count() > 0 || $this->databases()->count() > 0;
|
||||
}
|
||||
}
|
||||
|
@ -32,11 +32,11 @@ class StandaloneMongodb extends BaseModel
|
||||
]);
|
||||
});
|
||||
static::deleting(function ($database) {
|
||||
$database->scheduledBackups()->delete();
|
||||
$storages = $database->persistentStorages()->get();
|
||||
foreach ($storages as $storage) {
|
||||
instant_remote_process(["docker volume rm -f $storage->name"], $database->destination->server, false);
|
||||
}
|
||||
$database->scheduledBackups()->delete();
|
||||
$database->persistentStorages()->delete();
|
||||
$database->environment_variables()->delete();
|
||||
});
|
||||
|
@ -34,7 +34,9 @@ class EmailChannel
|
||||
if (isset($recepients)) {
|
||||
$message .= implode(', ', $recepients);
|
||||
}
|
||||
$message .= " with subject: {$mailMessage->subject}";
|
||||
if (isset($mailMessage)) {
|
||||
$message .= " with subject: {$mailMessage->subject}";
|
||||
}
|
||||
send_internal_notification($message);
|
||||
throw $e;
|
||||
}
|
||||
@ -49,8 +51,8 @@ class EmailChannel
|
||||
}
|
||||
return;
|
||||
}
|
||||
config()->set('mail.from.address', data_get($notifiable, 'smtp_from_address'));
|
||||
config()->set('mail.from.name', data_get($notifiable, 'smtp_from_name'));
|
||||
config()->set('mail.from.address', data_get($notifiable, 'smtp_from_address', 'test@example.com'));
|
||||
config()->set('mail.from.name', data_get($notifiable, 'smtp_from_name', 'Test'));
|
||||
if (data_get($notifiable, 'resend_enabled')) {
|
||||
config()->set('mail.default', 'resend');
|
||||
config()->set('resend.api_key', data_get($notifiable, 'resend_api_key'));
|
||||
|
@ -4,7 +4,7 @@ use App\Jobs\ApplicationDeploymentJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
|
||||
function queue_application_deployment(int $application_id, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false)
|
||||
function queue_application_deployment(int $application_id, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false)
|
||||
{
|
||||
$deployment = ApplicationDeploymentQueue::create([
|
||||
'application_id' => $application_id,
|
||||
@ -12,6 +12,7 @@ function queue_application_deployment(int $application_id, string $deployment_uu
|
||||
'pull_request_id' => $pull_request_id,
|
||||
'force_rebuild' => $force_rebuild,
|
||||
'is_webhook' => $is_webhook,
|
||||
'restart_only' => $restart_only,
|
||||
'commit' => $commit,
|
||||
]);
|
||||
$queued_deployments = ApplicationDeploymentQueue::where('application_id', $application_id)->where('status', 'queued')->get()->sortByDesc('created_at');
|
||||
|
@ -6,11 +6,14 @@ use App\Models\Server;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\Url\Url;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
function getCurrentApplicationContainerStatus(Server $server, int $id): Collection
|
||||
function getCurrentApplicationContainerStatus(Server $server, int $id, ?int $pullRequestId = null): Collection
|
||||
{
|
||||
$containers = instant_remote_process(["docker ps -a --filter='label=coolify.applicationId={$id}' --format '{{json .}}' "], $server);
|
||||
if ($pullRequestId) {
|
||||
$containers = instant_remote_process(["docker ps -a --filter='label=coolify.applicationId={$id}' --filter='label=coolify.pullRequestId={$pullRequestId}' --format '{{json .}}' "], $server);
|
||||
} else {
|
||||
$containers = instant_remote_process(["docker ps -a --filter='label=coolify.applicationId={$id}' --format '{{json .}}'"], $server);
|
||||
}
|
||||
if (!$containers) {
|
||||
return collect([]);
|
||||
}
|
||||
@ -77,20 +80,6 @@ function executeInDocker(string $containerId, string $command)
|
||||
// return "docker exec {$this->deployment_uuid} bash -c '{$command} |& tee -a /proc/1/fd/1; [ \$PIPESTATUS -eq 0 ] || exit \$PIPESTATUS'";
|
||||
}
|
||||
|
||||
function getApplicationContainerStatus(Application $application)
|
||||
{
|
||||
$server = data_get($application, 'destination.server');
|
||||
$id = $application->id;
|
||||
if (!$server) {
|
||||
return 'exited';
|
||||
}
|
||||
$containers = getCurrentApplicationContainerStatus($server, $id);
|
||||
if ($containers->count() > 0) {
|
||||
$status = data_get($containers[0], 'State', 'exited');
|
||||
return $status;
|
||||
}
|
||||
return 'exited';
|
||||
}
|
||||
function getContainerStatus(Server $server, string $container_id, bool $all_data = false, bool $throwError = false)
|
||||
{
|
||||
$container = instant_remote_process(["docker inspect --format '{{json .}}' {$container_id}"], $server, $throwError);
|
||||
@ -212,9 +201,9 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
||||
$onlyPort = $ports[0];
|
||||
}
|
||||
$pull_request_id = data_get($preview, 'pull_request_id', 0);
|
||||
$appId = $application->id;
|
||||
if ($pull_request_id !== 0 && $pull_request_id !== null) {
|
||||
$appId = $appId . '-pr-' . $pull_request_id;
|
||||
$appUuid = $application->uuid;
|
||||
if ($pull_request_id !== 0) {
|
||||
$appUuid = $appUuid . '-pr-' . $pull_request_id;
|
||||
}
|
||||
$labels = collect([]);
|
||||
if ($application->fqdn) {
|
||||
@ -224,7 +213,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
||||
$domains = Str::of(data_get($application, 'fqdn'))->explode(',');
|
||||
}
|
||||
// Add Traefik labels no matter which proxy is selected
|
||||
$labels = $labels->merge(fqdnLabelsForTraefik($application->uuid, $domains, $application->settings->is_force_https_enabled, $onlyPort));
|
||||
$labels = $labels->merge(fqdnLabelsForTraefik($appUuid, $domains, $application->settings->is_force_https_enabled, $onlyPort));
|
||||
}
|
||||
return $labels->all();
|
||||
}
|
||||
|
@ -174,8 +174,11 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d
|
||||
return $formatted;
|
||||
}
|
||||
|
||||
function refresh_server_connection(PrivateKey $private_key)
|
||||
function refresh_server_connection(?PrivateKey $private_key = null)
|
||||
{
|
||||
if (is_null($private_key)) {
|
||||
return;
|
||||
}
|
||||
foreach ($private_key->servers as $server) {
|
||||
Storage::disk('ssh-mux')->delete($server->muxFilename());
|
||||
}
|
||||
|
@ -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.108',
|
||||
'release' => '4.0.0-beta.109',
|
||||
// When left empty or `null` the Laravel environment will be used
|
||||
'environment' => config('app.env'),
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
<?php
|
||||
|
||||
return '4.0.0-beta.108';
|
||||
return '4.0.0-beta.109';
|
||||
|
@ -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('application_deployment_queues', function (Blueprint $table) {
|
||||
$table->boolean('restart_only')->default(false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('application_deployment_queues', function (Blueprint $table) {
|
||||
$table->dropColumn('restart_only');
|
||||
});
|
||||
}
|
||||
};
|
@ -26,7 +26,7 @@ class ApplicationSeeder extends Seeder
|
||||
'environment_id' => 1,
|
||||
'destination_id' => 0,
|
||||
'destination_type' => StandaloneDocker::class,
|
||||
'source_id' => 0,
|
||||
'source_id' => 1,
|
||||
'source_type' => GithubApp::class
|
||||
]);
|
||||
Application::create([
|
||||
|
@ -45,7 +45,7 @@ services:
|
||||
- /data/coolify/_volumes/redis/:/data
|
||||
# - coolify-redis-data-dev:/data
|
||||
vite:
|
||||
image: node:19
|
||||
image: node:20
|
||||
working_dir: /var/www/html
|
||||
ports:
|
||||
- "${VITE_PORT:-5173}:${VITE_PORT:-5173}"
|
||||
|
@ -10,7 +10,7 @@ ARG DOCKER_BUILDX_VERSION=0.11.2
|
||||
# https://github.com/buildpacks/pack/releases
|
||||
ARG PACK_VERSION=0.31.0
|
||||
# https://github.com/railwayapp/nixpacks/releases
|
||||
ARG NIXPACKS_VERSION=1.17.0
|
||||
ARG NIXPACKS_VERSION=1.18.0
|
||||
|
||||
USER root
|
||||
WORKDIR /artifacts
|
||||
|
@ -121,3 +121,6 @@ tr td:first-child {
|
||||
.subtitle {
|
||||
@apply pt-2 pb-10;
|
||||
}
|
||||
.fullscreen {
|
||||
@apply fixed top-0 left-0 w-full h-full z-[9999] bg-coolgray-100 overflow-y-auto scrollbar pb-4 ;
|
||||
}
|
||||
|
@ -17,7 +17,7 @@
|
||||
|
||||
@if ($application->status !== 'exited')
|
||||
<button title="With rolling update if possible" wire:click='deploy' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-warning" viewBox="0 0 24 24" stroke-width="2"
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-orange-400" viewBox="0 0 24 24" stroke-width="2"
|
||||
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path
|
||||
@ -27,6 +27,15 @@
|
||||
</svg>
|
||||
Redeploy
|
||||
</button>
|
||||
<button title="Restart without rebuilding" wire:click='restart' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||
<svg class="w-5 h-5 text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747"/>
|
||||
<path d="M20 4v5h-5"/>
|
||||
</g>
|
||||
</svg>
|
||||
Restart
|
||||
</button>
|
||||
<button wire:click='stop' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" stroke-width="2"
|
||||
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
|
@ -3,7 +3,7 @@
|
||||
<li class="inline-flex items-center">
|
||||
<a class="text-xs truncate lg:text-sm"
|
||||
href="{{ route('project.show', ['project_uuid' => $this->parameters['project_uuid']]) }}">
|
||||
{{ $resource->environment->project->name }}</a>
|
||||
{{ data_get($resource, 'environment.project.name', 'Undefined Name') }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<div class="flex items-center">
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div class="pt-4">
|
||||
<div class="pt-4" x-data="{ fullscreen: false, alwaysScroll: false, intervalId: null }">
|
||||
<livewire:project.application.deployment-navbar :application_deployment_queue="$application_deployment_queue" />
|
||||
@if (data_get($application_deployment_queue, 'status') === 'in_progress')
|
||||
<div class="flex items-center gap-1 pt-2 ">Deployment is
|
||||
@ -6,31 +6,94 @@
|
||||
</div>
|
||||
<x-loading class="loading-ring" />
|
||||
</div>
|
||||
<div class="">Logs will be updated automatically.</div>
|
||||
{{-- <div class="">Logs will be updated automatically.</div> --}}
|
||||
@else
|
||||
<div class="pt-2 ">Deployment is <span
|
||||
class="text-warning">{{ Str::headline(data_get($application_deployment_queue, 'status')) }}</span>.
|
||||
</div>
|
||||
@endif
|
||||
<div @if ($isKeepAliveOn) wire:poll.2000ms="polling" @endif
|
||||
class="scrollbar flex flex-col-reverse w-full overflow-y-auto border border-dotted rounded border-coolgray-400 max-h-[32rem] p-2 px-4 mt-4 text-xs">
|
||||
<span class="flex flex-col">
|
||||
@if (decode_remote_command_output($application_deployment_queue)->count() > 0)
|
||||
@foreach (decode_remote_command_output($application_deployment_queue) as $line)
|
||||
<div @class([
|
||||
'font-mono whitespace-pre-line',
|
||||
'text-neutral-400' => $line['type'] == 'stdout',
|
||||
'text-error' => $line['type'] == 'stderr',
|
||||
'text-warning' => $line['hidden'],
|
||||
])>[{{ $line['timestamp'] }}] @if ($line['hidden'])
|
||||
<br>COMMAND: <br>{{ $line['command'] }} <br><br>OUTPUT:
|
||||
@endif{{ $line['output'] }}@if ($line['hidden'])
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
@else
|
||||
<span class="font-mono text-neutral-400">No logs yet.</span>
|
||||
@endif
|
||||
</span>
|
||||
<div id="screen" :class="fullscreen ? 'fullscreen' : ''">
|
||||
<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 scrollbar border-coolgray-400"
|
||||
: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 ? '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>
|
||||
|
||||
<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">
|
||||
@if (decode_remote_command_output($application_deployment_queue)->count() > 0)
|
||||
@foreach (decode_remote_command_output($application_deployment_queue) as $line)
|
||||
<div @class([
|
||||
'font-mono whitespace-pre-line',
|
||||
'text-neutral-400' => $line['type'] == 'stdout',
|
||||
'text-error' => $line['type'] == 'stderr',
|
||||
'text-warning' => $line['hidden'],
|
||||
])>[{{ $line['timestamp'] }}] @if ($line['hidden'])
|
||||
<br>COMMAND: <br>{{ $line['command'] }} <br><br>OUTPUT:
|
||||
@endif{{ $line['output'] }}@if ($line['hidden'])
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
@else
|
||||
<span class="font-mono text-neutral-400">No logs yet.</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function makeFullscreen() {
|
||||
this.fullscreen = !this.fullscreen;
|
||||
if (this.fullscreen === false) {
|
||||
this.alwaysScroll = false;
|
||||
clearInterval(this.intervalId);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleScroll() {
|
||||
this.alwaysScroll = !this.alwaysScroll;
|
||||
|
||||
if (this.alwaysScroll) {
|
||||
this.intervalId = setInterval(() => {
|
||||
const screen = document.getElementById('screen');
|
||||
const logs = document.getElementById('logs');
|
||||
if (screen.scrollTop !== logs.scrollHeight) {
|
||||
screen.scrollTop = logs.scrollHeight;
|
||||
}
|
||||
}, 100);
|
||||
} else {
|
||||
clearInterval(this.intervalId);
|
||||
this.intervalId = null;
|
||||
}
|
||||
}
|
||||
|
||||
function goTop() {
|
||||
this.alwaysScroll = false;
|
||||
clearInterval(this.intervalId);
|
||||
const screen = document.getElementById('screen');
|
||||
screen.scrollTop = 0;
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
|
@ -1,10 +1,16 @@
|
||||
<div class="flex flex-col gap-2" @if ($skip == 0) wire:poll.5000ms='reload_deployments' @endif>
|
||||
<h2 class="pt-4">Deployments <span class="text-xs">({{ $deployments_count }})</span></h2>
|
||||
@if ($show_next)
|
||||
<x-forms.button wire:click="load_deployments({{ $default_take }})">Show More
|
||||
</x-forms.button>
|
||||
@endif
|
||||
@foreach ($deployments as $deployment)
|
||||
<div class="flex flex-col gap-2 pb-10" @if ($skip == 0) wire:poll.5000ms='reload_deployments' @endif>
|
||||
<div class="flex items-end gap-2 pt-4">
|
||||
<h2>Deployments <span class="text-xs">({{ $deployments_count }})</span></h2>
|
||||
@if ($show_next)
|
||||
<x-forms.button wire:click="load_deployments({{ $default_take }})">Next Page
|
||||
</x-forms.button>
|
||||
@endif
|
||||
</div>
|
||||
<form class="flex items-end gap-2">
|
||||
<x-forms.input id="pull_request_id" label="Pull Request"></x-forms.input>
|
||||
<x-forms.button type="submit">Filter</x-forms.button>
|
||||
</form>
|
||||
@forelse ($deployments as $deployment)
|
||||
<a @class([
|
||||
'bg-coolgray-200 p-2 border-l border-dashed transition-colors hover:no-underline',
|
||||
'cursor-not-allowed hover:bg-coolgray-200' =>
|
||||
@ -16,45 +22,45 @@
|
||||
data_get($deployment, 'status') === 'error',
|
||||
'border-success hover:bg-success' =>
|
||||
data_get($deployment, 'status') === 'finished',
|
||||
]) @if (data_get($deployment, 'status') !== 'cancelled by system' && data_get($deployment, 'status') !== 'queued')
|
||||
href="{{ $current_url . '/' . data_get($deployment, 'deployment_uuid') }}"
|
||||
@endif
|
||||
class="hover:no-underline">
|
||||
<div class="flex flex-col justify-start">
|
||||
<div>
|
||||
{{ $deployment->id }} <span class=" text-warning">></span> {{ $deployment->deployment_uuid }}
|
||||
<span class=" text-warning">></span>
|
||||
{{ $deployment->status }}
|
||||
</div>
|
||||
@if (data_get($deployment, 'pull_request_id'))
|
||||
<div>
|
||||
Pull Request #{{ data_get($deployment, 'pull_request_id') }}
|
||||
@if (data_get($deployment, 'is_webhook'))
|
||||
(Webhook)
|
||||
]) href="{{ $current_url . '/' . data_get($deployment, 'deployment_uuid') }}"
|
||||
class="hover:no-underline">
|
||||
<div class="flex flex-col justify-start">
|
||||
<div class="flex gap-1">
|
||||
{{ $deployment->created_at }} UTC
|
||||
<span class=" text-warning">></span>
|
||||
{{ $deployment->status }}
|
||||
</div>
|
||||
@if (data_get($deployment, 'pull_request_id'))
|
||||
<div>
|
||||
<span class=" text-warning">></span>
|
||||
Pull Request #{{ data_get($deployment, 'pull_request_id') }}
|
||||
@if (data_get($deployment, 'is_webhook'))
|
||||
(Webhook)
|
||||
@endif
|
||||
Webhook (SHA
|
||||
@if (data_get($deployment, 'commit'))
|
||||
{{ data_get($deployment, 'commit') }})
|
||||
@else
|
||||
HEAD)
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@elseif (data_get($deployment, 'is_webhook'))
|
||||
<div>Webhook (sha
|
||||
@if (data_get($deployment, 'commit'))
|
||||
{{ data_get($deployment, 'commit') }})
|
||||
@else
|
||||
HEAD)
|
||||
@endif
|
||||
|
||||
<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
|
||||
@else
|
||||
Running for
|
||||
@endif
|
||||
<span class="font-bold" x-text="measure_finished_time()">0s</span>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
<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
|
||||
@else
|
||||
Running for
|
||||
@endif
|
||||
<span class="font-bold" x-text="measure_finished_time()">0s</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
</a>
|
||||
@empty
|
||||
<div class="">No deployments found</div>
|
||||
@endforelse
|
||||
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/utc.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/relativeTime.js"></script>
|
||||
|
@ -49,14 +49,21 @@
|
||||
@if ($application->build_pack !== 'dockerimage')
|
||||
<h3>Build</h3>
|
||||
@if ($application->could_set_build_commands())
|
||||
<div class="flex flex-col gap-2 xl:flex-row">
|
||||
<x-forms.input placeholder="pnpm install" id="application.install_command"
|
||||
label="Install Command" />
|
||||
<x-forms.input placeholder="pnpm build" id="application.build_command" label="Build Command" />
|
||||
<x-forms.input placeholder="pnpm start" id="application.start_command" label="Start Command" />
|
||||
</div>
|
||||
@if ($application->build_pack === 'nixpacks')
|
||||
<div>Nixpacks will detect your package manager/configurations: <a class="underline" href="https://nixpacks.com/docs/providers">Nixpacks documentation</a></div>
|
||||
<div class="text-warning">You probably do not need to modify the commands below.</div>
|
||||
<div class="flex flex-col gap-2 xl:flex-row">
|
||||
<x-forms.input placeholder="If you modify this, you probably need to have a nixpacks.toml" id="application.install_command"
|
||||
label="Install Command" />
|
||||
<x-forms.input placeholder="If you modify this, you probably need to have a nixpacks.toml" id="application.build_command"
|
||||
label="Build Command" />
|
||||
<x-forms.input placeholder="If you modify this, you probably need to have a nixpacks.toml" id="application.start_command"
|
||||
label="Start Command" />
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
|
||||
<div class="flex flex-col gap-2 xl:flex-row">
|
||||
<x-forms.input placeholder="/" id="application.base_directory" label="Base Directory"
|
||||
helper="Directory to use as root. Useful for monorepos." />
|
||||
|
@ -78,11 +78,15 @@
|
||||
Redeploy
|
||||
@endif
|
||||
</x-forms.button>
|
||||
@if (data_get($preview, 'status') !== 'exited')
|
||||
<x-forms.button wire:click="stop({{ data_get($preview, 'pull_request_id') }})">Remove
|
||||
Preview
|
||||
<x-forms.button wire:click="stop({{ data_get($preview, 'pull_request_id') }})">Remove
|
||||
Preview
|
||||
</x-forms.button>
|
||||
<a
|
||||
href="{{ route('project.application.deployments', [...$parameters, 'pull_request_id' => data_get($preview, 'pull_request_id')]) }}">
|
||||
<x-forms.button>
|
||||
Get Deployment Logs
|
||||
</x-forms.button>
|
||||
@endif
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
@ -1,6 +1,6 @@
|
||||
<div x-data="{ raw: true, activeTab: window.location.hash ? window.location.hash.substring(1) : 'service-stack' }" wire:poll.15000ms="checkStatus">
|
||||
<div x-data="{ raw: true, activeTab: window.location.hash ? window.location.hash.substring(1) : 'service-stack' }" x-init="$wire.checkStatus" wire:poll.10000ms="checkStatus">
|
||||
<livewire:project.service.navbar :service="$service" :parameters="$parameters" :query="$query" />
|
||||
<livewire:project.service.compose-modal :raw="$service->docker_compose_raw" :actual="$service->docker_compose" />
|
||||
<livewire:project.service.compose-modal key={{ now() }} :raw="$service->docker_compose_raw" :actual="$service->docker_compose" />
|
||||
<div class="flex h-full pt-6">
|
||||
<div class="flex flex-col items-start gap-4 min-w-fit">
|
||||
<a target="_blank" href="{{ $service->documentation() }}">Documentation <x-external-link /></a>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div x-init="$wire.checkStatus" wire:poll.2500ms='checkStatus'>
|
||||
<div>
|
||||
<livewire:project.service.modal />
|
||||
<h1>Configuration</h1>
|
||||
<x-resources.breadcrumbs :resource="$service" :parameters="$parameters" />
|
||||
|
@ -13,11 +13,25 @@
|
||||
<x-forms.input label="Only Show Number of Lines" placeholder="1000" required id="numberOfLines"></x-forms.input>
|
||||
<x-forms.button type="submit">Refresh</x-forms.button>
|
||||
</form>
|
||||
|
||||
<div class="container w-full pt-4 mx-auto">
|
||||
<div
|
||||
class="scrollbar flex flex-col-reverse w-full overflow-y-auto border border-solid rounded border-coolgray-300 max-h-[32rem] p-4 pt-6 text-xs text-white">
|
||||
|
||||
<div x-data="{ fullscreen: false }" :class="fullscreen ? 'fullscreen' : 'container w-full pt-4 mx-auto'">
|
||||
<div class="relative flex flex-col-reverse w-full p-4 pt-6 overflow-y-auto text-white scrollbar border-coolgray-300"
|
||||
:class="fullscreen ? '' : 'max-h-[40rem] border border-solid rounded'">
|
||||
<button title="Minimize" x-show="fullscreen" class="fixed top-4 right-4"
|
||||
x-on:click="fullscreen = !fullscreen"><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="Fullscreen" x-show="!fullscreen" class="absolute top-2 right-8"
|
||||
x-on:click="fullscreen = !fullscreen"><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>
|
||||
<pre class="font-mono whitespace-pre-wrap">{{ $outputs }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
18
resources/views/livewire/server/delete.blade.php
Normal file
18
resources/views/livewire/server/delete.blade.php
Normal file
@ -0,0 +1,18 @@
|
||||
<div>
|
||||
<x-modal yesOrNo modalId="deleteServer" modalTitle="Delete Server">
|
||||
<x-slot:modalBody>
|
||||
<p>This server will be deleted. It is not reversible. <br>Please think again..</p>
|
||||
</x-slot:modalBody>
|
||||
</x-modal>
|
||||
@if ($server->id !== 0)
|
||||
<h2 class="pt-4">Danger Zone</h2>
|
||||
<div class="">Woah. I hope you know what are you doing.</div>
|
||||
<h4 class="pt-4">Delete Server</h4>
|
||||
<div class="pb-4">This will remove this server from Coolify. Beware! There is no coming
|
||||
back!
|
||||
</div>
|
||||
<x-forms.button isError isModal modalId="deleteServer">
|
||||
Delete
|
||||
</x-forms.button>
|
||||
@endif
|
||||
</div>
|
@ -1,9 +1,4 @@
|
||||
<div x-init="$wire.validateServer(false)">
|
||||
<x-modal yesOrNo modalId="deleteServer" modalTitle="Delete Server">
|
||||
<x-slot:modalBody>
|
||||
<p>This server will be deleted. It is not reversible. <br>Please think again..</p>
|
||||
</x-slot:modalBody>
|
||||
</x-modal>
|
||||
<x-modal yesOrNo modalId="changeLocalhost" modalTitle="Change Localhost" action="submit">
|
||||
<x-slot:modalBody>
|
||||
<p>You could lost a lot of functionalities if you change the server details of the server where Coolify is
|
||||
@ -47,7 +42,8 @@
|
||||
label="Is it part of a Swarm cluster?" /> --}}
|
||||
</div>
|
||||
<div class="flex flex-col w-full gap-2 lg:flex-row">
|
||||
<x-forms.input id="server.ip" label="IP Address" required />
|
||||
<x-forms.input id="server.ip" label="IP Address/Domain"
|
||||
helper="An IP Address (127.0.0.1) or domain (example.com)." required />
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input id="server.user" label="User" required />
|
||||
<x-forms.input type="number" id="server.port" label="Port" required />
|
||||
@ -66,21 +62,9 @@
|
||||
helper="Disk cleanup job will be executed if disk usage is more than this number." />
|
||||
@endif
|
||||
</form>
|
||||
@if ($server->id !== 0)
|
||||
<h2 class="pt-4">Danger Zone</h2>
|
||||
<div class="">Woah. I hope you know what are you doing.</div>
|
||||
<h4 class="pt-4">Delete Server</h4>
|
||||
<div class="pb-4">This will remove this server from Coolify. Beware! There is no coming
|
||||
back!
|
||||
</div>
|
||||
<x-forms.button isError isModal modalId="deleteServer">
|
||||
Delete
|
||||
</x-forms.button>
|
||||
@endif
|
||||
|
||||
<livewire:server.delete :server="$server" />
|
||||
<script>
|
||||
Livewire.on('installDocker', () => {
|
||||
console.log('asd');
|
||||
installDocker.showModal();
|
||||
})
|
||||
</script>
|
||||
|
@ -10,8 +10,8 @@
|
||||
<x-forms.input id="description" label="Description" />
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input id="ip" label="IP Address" required
|
||||
helper="An IP Address (127.0.0.1). No domain names." />
|
||||
<x-forms.input id="ip" label="IP Address/Domain" required
|
||||
helper="An IP Address (127.0.0.1) or domain (example.com)." />
|
||||
<x-forms.input id="user" label ="User" required />
|
||||
<x-forms.input type="number" id="port" label="Port" required />
|
||||
</div>
|
||||
|
@ -12,7 +12,7 @@
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input id="custom_user" label="Custom Git User" required />
|
||||
<x-forms.input id="custom_port" label="Custom Git Port" required />
|
||||
<x-forms.input id="custom_port" type="number" label="Custom Git Port" required />
|
||||
</div>
|
||||
@if (!isCloud())
|
||||
<x-forms.checkbox class="pt-2" id="is_system_wide" label="System Wide" />
|
||||
|
@ -11,8 +11,7 @@
|
||||
<div>This is the default team. You can't delete it.</div>
|
||||
@elseif(auth()->user()->teams()->get()->count() === 1)
|
||||
<div>You can't delete your last team.</div>
|
||||
@elseif(currentTeam()->subscription &&
|
||||
currentTeam()->subscription?->lemon_status !== 'cancelled')
|
||||
@elseif(currentTeam()->subscription && currentTeam()->subscription?->lemon_status !== 'cancelled')
|
||||
<div>Please cancel your subscription before delete this team (Manage My Subscription).</div>
|
||||
@else
|
||||
@if (currentTeam()->isEmpty())
|
||||
@ -23,30 +22,38 @@
|
||||
@else
|
||||
<div>
|
||||
<div class="pb-4">You need to delete the following resources to be able to delete the team:</div>
|
||||
<h4 class="pb-4">Projects:</h4>
|
||||
<ul class="pl-8 list-disc">
|
||||
@foreach (currentTeam()->projects as $resource)
|
||||
<li>{{ $resource->name }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
<h4 class="py-4">Servers:</h4>
|
||||
<ul class="pl-8 list-disc">
|
||||
@foreach (currentTeam()->servers as $resource)
|
||||
<li>{{ $resource->name }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
<h4 class="py-4">Private Keys:</h4>
|
||||
<ul class="pl-8 list-disc">
|
||||
@foreach (currentTeam()->privateKeys as $resource)
|
||||
<li>{{ $resource->name }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
<h4 class="py-4">Sources:</h4>
|
||||
<ul class="pl-8 list-disc">
|
||||
@foreach (currentTeam()->sources() as $resource)
|
||||
<li>{{ $resource->name }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
@if (currentTeam()->projects()->count() > 0)
|
||||
<h4 class="pb-4">Projects:</h4>
|
||||
<ul class="pl-8 list-disc">
|
||||
@foreach (currentTeam()->projects as $resource)
|
||||
<li>{{ $resource->name }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
@endif
|
||||
@if (currentTeam()->servers()->count() > 0)
|
||||
<h4 class="py-4">Servers:</h4>
|
||||
<ul class="pl-8 list-disc">
|
||||
@foreach (currentTeam()->servers as $resource)
|
||||
<li>{{ $resource->name }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
@endif
|
||||
@if (currentTeam()->privateKeys()->count() > 0)
|
||||
<h4 class="py-4">Private Keys:</h4>
|
||||
<ul class="pl-8 list-disc">
|
||||
@foreach (currentTeam()->privateKeys as $resource)
|
||||
<li>{{ $resource->name }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
@endif
|
||||
@if (currentTeam()->sources()->count() > 0)
|
||||
<h4 class="py-4">Sources:</h4>
|
||||
<ul class="pl-8 list-disc">
|
||||
@foreach (currentTeam()->sources() as $resource)
|
||||
<li>{{ $resource->name }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
@endif
|
||||
@endif
|
||||
@endif
|
||||
|
||||
|
@ -12,6 +12,7 @@ use App\Models\Waitlist;
|
||||
use App\Models\Webhook;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\Sleep;
|
||||
use Illuminate\Support\Str;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
@ -64,6 +65,7 @@ Route::get('/source/github/install', function () {
|
||||
});
|
||||
Route::post('/source/github/events', function () {
|
||||
try {
|
||||
$id = null;
|
||||
$x_github_delivery = request()->header('X-GitHub-Delivery');
|
||||
$x_github_event = Str::lower(request()->header('X-GitHub-Event'));
|
||||
$x_github_hook_installation_target_id = request()->header('X-GitHub-Hook-Installation-Target-Id');
|
||||
@ -73,7 +75,7 @@ Route::post('/source/github/events', function () {
|
||||
// Just pong
|
||||
return response('pong');
|
||||
}
|
||||
if ($x_github_event === 'installation') {
|
||||
if ($x_github_event === 'installation' || $x_github_event === 'installation_repositories') {
|
||||
// Installation handled by setup redirect url. Repositories queried on-demand.
|
||||
return response('cool');
|
||||
}
|
||||
@ -87,7 +89,6 @@ Route::post('/source/github/events', function () {
|
||||
return response('not cool');
|
||||
}
|
||||
}
|
||||
|
||||
if ($x_github_event === 'push') {
|
||||
$id = data_get($payload, 'repository.id');
|
||||
$branch = data_get($payload, 'ref');
|
||||
@ -281,7 +282,11 @@ Route::post('/payments/stripe/events', function () {
|
||||
break;
|
||||
case 'invoice.paid':
|
||||
$customerId = data_get($data, 'customer');
|
||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||
if (!$subscription) {
|
||||
Sleep::for(5);
|
||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
||||
}
|
||||
$planId = data_get($data, 'lines.data.0.plan.id');
|
||||
$subscription->update([
|
||||
'stripe_plan_id' => $planId,
|
||||
|
19
templates/compose/budge.yaml
Normal file
19
templates/compose/budge.yaml
Normal file
@ -0,0 +1,19 @@
|
||||
# documentation: https://github.com/linuxserver/budge
|
||||
# slogan: BudgE is an open-source 'budgeting with envelopes' personal finance app, helping you manage your finances effectively.
|
||||
# tags: personal finance, budgeting, expense tracking
|
||||
|
||||
services:
|
||||
budge:
|
||||
image: lscr.io/linuxserver/budge:latest
|
||||
environment:
|
||||
- SERVICE_FQDN_BUDGE
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=Europe/Madrid
|
||||
volumes:
|
||||
- budge-config:/config
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:80"]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 15
|
20
templates/compose/duplicati.yaml
Normal file
20
templates/compose/duplicati.yaml
Normal file
@ -0,0 +1,20 @@
|
||||
# documentation: https://duplicati.readthedocs.io/en/latest/02-installation/
|
||||
# slogan: Duplicati is an open-source backup solution, allowing you to safeguard your data with ease through scheduled backups and encryption.
|
||||
# tags: backup, encryption
|
||||
|
||||
services:
|
||||
duplicati:
|
||||
image: lscr.io/linuxserver/duplicati:latest
|
||||
environment:
|
||||
- SERVICE_FQDN_DUPLICATI
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=Europe/Madrid
|
||||
volumes:
|
||||
- duplicati-config:/config
|
||||
- duplicati-backups:/backups
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8200"]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 15
|
20
templates/compose/filebrowser.yaml
Normal file
20
templates/compose/filebrowser.yaml
Normal file
@ -0,0 +1,20 @@
|
||||
# documentation: https://filebrowser.org/configuration
|
||||
# slogan: FileBrowser is a self-hosted, web-based file manager and file explorer with a user-friendly interface. It allows you to manage and organize your files and directories directly from your web browser.
|
||||
# tags: file-management, storage-access, data-organization, file-utilization, administration-tool
|
||||
|
||||
services:
|
||||
filebrowser:
|
||||
image: filebrowser/filebrowser:latest
|
||||
environment:
|
||||
- SERVICE_FQDN_FILEBROWSER
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
volumes:
|
||||
- filebrowser-srv:/srv
|
||||
- filebrowser-database:/database/filebrowser.db
|
||||
- filebrowser-config:/config/settings.json
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:80"]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 15
|
22
templates/compose/jellyfin.yaml
Normal file
22
templates/compose/jellyfin.yaml
Normal file
@ -0,0 +1,22 @@
|
||||
# documentation: https://jellyfin.org/docs/
|
||||
# slogan: Jellyfin is an open-source media server for hosting and streaming your media collection, providing an alternative to proprietary media platforms.
|
||||
# tags: media, server, movies, tv, music
|
||||
|
||||
services:
|
||||
jellyfin:
|
||||
image: lscr.io/linuxserver/jellyfin:latest
|
||||
environment:
|
||||
- SERVICE_FQDN_JELLYFIN
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=Europe/Madrid
|
||||
- JELLYFIN_PublishedServerUrl=$SERVICE_FQDN_JELLYFIN
|
||||
volumes:
|
||||
- jellyfin-config:/config
|
||||
- jellyfin-tvshows:/data/tvshows
|
||||
- jellyfin-movies:/data/movies
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8096"]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 15
|
60
templates/compose/kuzzle.yaml
Normal file
60
templates/compose/kuzzle.yaml
Normal file
@ -0,0 +1,60 @@
|
||||
# documentation: https://docs.kuzzle.io/
|
||||
# slogan: Kuzzle is a generic backend offering the basic building blocks common to every application.
|
||||
# tags: backend, api, realtime, websocket, mqtt, rest, sdk, iot, geofencing, low-code
|
||||
|
||||
services:
|
||||
|
||||
redis:
|
||||
image: redis:6.2.4
|
||||
command: redis-server --appendonly yes
|
||||
healthcheck:
|
||||
test: [ "CMD", "redis-cli", "ping" ]
|
||||
interval: 1s
|
||||
timeout: 3s
|
||||
retries: 30
|
||||
|
||||
elasticsearch:
|
||||
image: kuzzleio/elasticsearch:7
|
||||
healthcheck:
|
||||
test: [ "CMD", "curl", "-f", "http://localhost:9200" ]
|
||||
interval: 2s
|
||||
timeout: 2s
|
||||
retries: 10
|
||||
ulimits:
|
||||
nofile: 65536
|
||||
|
||||
kuzzle:
|
||||
image: kuzzleio/kuzzle:latest
|
||||
environment:
|
||||
- SERVICE_FQDN_KUZZLE_7512
|
||||
- kuzzle_services__storageEngine__client__node=http://elasticsearch:9200
|
||||
- kuzzle_services__storageEngine__commonMapping__dynamic=true
|
||||
- kuzzle_services__internalCache__node__host=redis
|
||||
- kuzzle_services__memoryStorage__node__host=redis
|
||||
- kuzzle_server__protocols__mqtt__enabled=true
|
||||
- kuzzle_server__protocols__mqtt__developmentMode=false
|
||||
- kuzzle_limits__loginsPerSecond=50
|
||||
- NODE_ENV=production
|
||||
# - DEBUG=${DEBUG:-kuzzle:*,-kuzzle:network:protocols:websocket,-kuzzle:events}
|
||||
- DEBUG=${DEBUG:-kuzzle:cluster:sync}
|
||||
- DEBUG_DEPTH=${DEBUG_DEPTH:-0}
|
||||
- DEBUG_MAX_ARRAY_LENGTH=${DEBUG_MAX_ARRAY:-100}
|
||||
- DEBUG_EXPAND=${DEBUG_EXPAND:-off}
|
||||
- DEBUG_SHOW_HIDDEN={$DEBUG_SHOW_HIDDEN:-on}
|
||||
- DEBUG_COLORS=${DEBUG_COLORS:-on}
|
||||
cap_add:
|
||||
- SYS_PTRACE
|
||||
ulimits:
|
||||
nofile: 65536
|
||||
sysctls:
|
||||
- net.core.somaxconn=8192
|
||||
healthcheck:
|
||||
test: [ "CMD", "curl", "-f", "http://localhost:7512/_healthcheck" ]
|
||||
timeout: 1s
|
||||
interval: 2s
|
||||
retries: 30
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
elasticsearch:
|
||||
condition: service_healthy
|
37
templates/compose/moodle.yaml
Normal file
37
templates/compose/moodle.yaml
Normal file
@ -0,0 +1,37 @@
|
||||
# documentation: https://moodle.org
|
||||
# slogan: Moodle is the world’s most customisable and trusted eLearning solution that empowers educators to improve our world.
|
||||
# tags: moodle, elearning, education, lms, cms, open, source, low, code
|
||||
|
||||
services:
|
||||
mariadb:
|
||||
image: mariadb:11.1
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=no
|
||||
- MYSQL_ROOT_PASSWORD=$SERVICE_PASSWORD_ROOT
|
||||
- MYSQL_DATABASE=bitnami_moodle
|
||||
- MYSQL_USER=$SERVICE_USER_MARIADB
|
||||
- MYSQL_PASSWORD=$SERVICE_PASSWORD_MARIADB
|
||||
- MARIADB_CHARACTER_SET=utf8mb4
|
||||
- MARIADB_COLLATE=utf8mb4_unicode_ci
|
||||
volumes:
|
||||
- mariadb-data:/var/lib/mysql
|
||||
|
||||
moodle:
|
||||
image: docker.io/bitnami/moodle:4.3
|
||||
environment:
|
||||
- SERVICE_FQDN_MOODLE
|
||||
- MOODLE_DATABASE_HOST=mariadb
|
||||
- MOODLE_DATABASE_PORT_NUMBER=3306
|
||||
- MOODLE_DATABASE_USER=$SERVICE_USER_MARIADB
|
||||
- MOODLE_DATABASE_NAME=bitnami_moodle
|
||||
- MOODLE_DATABASE_PASSWORD=$SERVICE_PASSWORD_MARIADB
|
||||
- ALLOW_EMPTY_PASSWORD=no
|
||||
- MOODLE_USERNAME=${MOODLE_USERNAME:-user}
|
||||
- MOODLE_PASSWORD=$SERVICE_PASSWORD_MOODLE
|
||||
- MOODLE_EMAIL=user@example.com
|
||||
- MOODLE_SITE_NAME=${MOODLE_SITE_NAME:-New Site}
|
||||
volumes:
|
||||
- moodle-data:/bitnami/moodle
|
||||
- moodledata-data:/bitnami/moodledata
|
||||
depends_on:
|
||||
- mariadb
|
21
templates/compose/phpmyadmin.yaml
Normal file
21
templates/compose/phpmyadmin.yaml
Normal file
@ -0,0 +1,21 @@
|
||||
# documentation: https://docs.phpmyadmin.net/en/latest/
|
||||
# slogan: phpMyAdmin is a web-based database management tool for administering your MySQL and MariaDB databases through a user-friendly interface.
|
||||
# tags: database management
|
||||
|
||||
services:
|
||||
phpmyadmin:
|
||||
image: lscr.io/linuxserver/phpmyadmin:latest
|
||||
environment:
|
||||
- SERVICE_FQDN_PHPMYADMIN
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=Europe/Madrid
|
||||
- PMA_ARBITRARY=1
|
||||
- PMA_ABSOLUTE_URI=$SERVICE_FQDN_PHPMYADMIN
|
||||
volumes:
|
||||
- phpmyadmin-config:/config
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:80"]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 15
|
17
templates/compose/rabbitmq.yaml
Normal file
17
templates/compose/rabbitmq.yaml
Normal file
@ -0,0 +1,17 @@
|
||||
# ignore: true
|
||||
# documentation: https://www.rabbitmq.com/documentation.html
|
||||
# slogan: With tens of thousands of users, RabbitMQ is one of the most popular open source message brokers.
|
||||
# tags: message broker, message queue, message-oriented middleware, MOM, AMQP, MQTT, STOMP, messaging
|
||||
|
||||
services:
|
||||
rabbitmq:
|
||||
image: rabbitmq:3
|
||||
environment:
|
||||
- SERVICE_FQDN_RABBITMQ_5672
|
||||
- RABBITMQ_DEFAULT_USER=$SERVICE_USER_RABBITMQ
|
||||
- RABBITMQ_DEFAULT_PASS=$SERVICE_PASSWORD_RABBITMQ
|
||||
healthcheck:
|
||||
test: rabbitmq-diagnostics -q ping
|
||||
interval: 30s
|
||||
timeout: 30s
|
||||
retries: 3
|
36
templates/compose/sonarqube.yaml
Normal file
36
templates/compose/sonarqube.yaml
Normal file
@ -0,0 +1,36 @@
|
||||
# ignore: true
|
||||
# documentation: https://hub.docker.com/_/sonarqube/
|
||||
# slogan: SonarQube is a self-managed, automatic code review tool that systematically helps you deliver Clean Code
|
||||
# tags: sonarqube, code-review, clean-code, quality, code-quality, code-analysis, code-smells, code-coverage, code-security
|
||||
|
||||
services:
|
||||
sonarqube:
|
||||
image: sonarqube:community
|
||||
environment:
|
||||
- SERVICE_FQDN_SONARQUBE_9000
|
||||
- SONAR_JDBC_URL=jdbc:postgresql://postgresql:5432/${POSTGRES_DB:-sonar}
|
||||
- SONAR_JDBC_USERNAME=$SERVICE_USER_POSTGRES
|
||||
- SONAR_JDBC_PASSWORD=$SERVICE_PASSWORD_POSTGRES
|
||||
volumes:
|
||||
- sonarqube-data:/opt/sonarqube
|
||||
- sonarqube-conf:/opt/sonarqube/conf
|
||||
- sonarqube-extensions:/opt/sonarqube/extensions
|
||||
- sonarqube-logs:/opt/sonarqube/logs
|
||||
- sonarqube-bundled-plugins:/opt/sonarqube/lib/bundled-plugins
|
||||
depends_on:
|
||||
postgresql:
|
||||
condition: service_healthy
|
||||
postgresql:
|
||||
image: postgres:15-alpine
|
||||
volumes:
|
||||
- postgresql-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
- POSTGRES_USER=$SERVICE_USER_POSTGRES
|
||||
- POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
|
||||
- POSTGRES_DB=${POSTGRES_DB:-sonar}
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
||||
interval: 5s
|
||||
timeout: 20s
|
||||
retries: 10
|
||||
|
16
templates/compose/vaultwarden.yaml
Normal file
16
templates/compose/vaultwarden.yaml
Normal file
@ -0,0 +1,16 @@
|
||||
# documentation: https://github.com/dani-garcia/vaultwarden/wiki/FAQs
|
||||
# slogan: Vaultwarden is an open-source password manager that allows you to securely store and manage your passwords, helping you stay organized and protected.
|
||||
# tags: password manager, security
|
||||
|
||||
services:
|
||||
vaultwarden:
|
||||
image: vaultwarden/server:latest
|
||||
environment:
|
||||
- SERVICE_FQDN_VAULTWARDEN
|
||||
volumes:
|
||||
- vaultwarden-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:80"]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 15
|
14
templates/compose/whoogle.yaml
Normal file
14
templates/compose/whoogle.yaml
Normal file
@ -0,0 +1,14 @@
|
||||
# documentation: https://github.com/benbusby/whoogle-search#install
|
||||
# slogan: Whoogle is a self-hosted, privacy-focused search engine front-end for accessing Google search results without tracking and data collection.
|
||||
# tags: privacy, search engine
|
||||
|
||||
services:
|
||||
whoogle:
|
||||
image: benbusby/whoogle-search:latest
|
||||
environment:
|
||||
- SERVICE_FQDN_WHOOGLE
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:5000"]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 15
|
@ -33,6 +33,16 @@
|
||||
"activities"
|
||||
]
|
||||
},
|
||||
"budge": {
|
||||
"documentation": "https:\/\/github.com\/linuxserver\/budge",
|
||||
"slogan": "BudgE is an open-source 'budgeting with envelopes' personal finance app, helping you manage your finances effectively.",
|
||||
"compose": "c2VydmljZXM6CiAgYnVkZ2U6CiAgICBpbWFnZTogJ2xzY3IuaW8vbGludXhzZXJ2ZXIvYnVkZ2U6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX0JVREdFCiAgICAgIC0gUFVJRD0xMDAwCiAgICAgIC0gUEdJRD0xMDAwCiAgICAgIC0gVFo9RXVyb3BlL01hZHJpZAogICAgdm9sdW1lczoKICAgICAgLSAnYnVkZ2UtY29uZmlnOi9jb25maWcnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly9sb2NhbGhvc3Q6ODAnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK",
|
||||
"tags": [
|
||||
"personal finance",
|
||||
"budgeting",
|
||||
"expense tracking"
|
||||
]
|
||||
},
|
||||
"code-server": {
|
||||
"documentation": "https:\/\/coder.com\/docs\/code-server\/latest\/guide",
|
||||
"slogan": "Code-Server is a self-hosted, web-based code editor that enables remote coding and collaboration from any device, anywhere.",
|
||||
@ -66,6 +76,15 @@
|
||||
"base"
|
||||
]
|
||||
},
|
||||
"duplicati": {
|
||||
"documentation": "https:\/\/duplicati.readthedocs.io\/en\/latest\/02-installation\/",
|
||||
"slogan": "Duplicati is an open-source backup solution, allowing you to safeguard your data with ease through scheduled backups and encryption.",
|
||||
"compose": "c2VydmljZXM6CiAgZHVwbGljYXRpOgogICAgaW1hZ2U6ICdsc2NyLmlvL2xpbnV4c2VydmVyL2R1cGxpY2F0aTpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fRFVQTElDQVRJCiAgICAgIC0gUFVJRD0xMDAwCiAgICAgIC0gUEdJRD0xMDAwCiAgICAgIC0gVFo9RXVyb3BlL01hZHJpZAogICAgdm9sdW1lczoKICAgICAgLSAnZHVwbGljYXRpLWNvbmZpZzovY29uZmlnJwogICAgICAtICdkdXBsaWNhdGktYmFja3VwczovYmFja3VwcycKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDo4MjAwJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1Cg==",
|
||||
"tags": [
|
||||
"backup",
|
||||
"encryption"
|
||||
]
|
||||
},
|
||||
"emby": {
|
||||
"documentation": "https:\/\/emby.media\/support\/articles\/Home.html",
|
||||
"slogan": "A media server software that allows you to organize, stream, and access your multimedia content effortlessly, making it easy to enjoy your favorite movies, TV shows, music, and more.",
|
||||
@ -99,6 +118,18 @@
|
||||
"user-feedback"
|
||||
]
|
||||
},
|
||||
"filebrowser": {
|
||||
"documentation": "https:\/\/filebrowser.org\/configuration",
|
||||
"slogan": "FileBrowser is a self-hosted, web-based file manager and file explorer with a user-friendly interface. It allows you to manage and organize your files and directories directly from your web browser.",
|
||||
"compose": "c2VydmljZXM6CiAgZmlsZWJyb3dzZXI6CiAgICBpbWFnZTogJ2ZpbGVicm93c2VyL2ZpbGVicm93c2VyOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9GSUxFQlJPV1NFUgogICAgICAtIFBVSUQ9MTAwMAogICAgICAtIFBHSUQ9MTAwMAogICAgdm9sdW1lczoKICAgICAgLSAnZmlsZWJyb3dzZXItc3J2Oi9zcnYnCiAgICAgIC0gJ2ZpbGVicm93c2VyLWRhdGFiYXNlOi9kYXRhYmFzZS9maWxlYnJvd3Nlci5kYicKICAgICAgLSAnZmlsZWJyb3dzZXItY29uZmlnOi9jb25maWcvc2V0dGluZ3MuanNvbicKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDo4MCcKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQo=",
|
||||
"tags": [
|
||||
"file-management",
|
||||
"storage-access",
|
||||
"data-organization",
|
||||
"file-utilization",
|
||||
"administration-tool"
|
||||
]
|
||||
},
|
||||
"ghost": {
|
||||
"documentation": "https:\/\/ghost.org\/docs",
|
||||
"slogan": "Ghost is a popular open-source content management system (CMS) and blogging platform, known for its simplicity and focus on content creation.",
|
||||
@ -156,6 +187,35 @@
|
||||
"interface"
|
||||
]
|
||||
},
|
||||
"jellyfin": {
|
||||
"documentation": "https:\/\/jellyfin.org\/docs\/",
|
||||
"slogan": "Jellyfin is an open-source media server for hosting and streaming your media collection, providing an alternative to proprietary media platforms.",
|
||||
"compose": "c2VydmljZXM6CiAgamVsbHlmaW46CiAgICBpbWFnZTogJ2xzY3IuaW8vbGludXhzZXJ2ZXIvamVsbHlmaW46bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX0pFTExZRklOCiAgICAgIC0gUFVJRD0xMDAwCiAgICAgIC0gUEdJRD0xMDAwCiAgICAgIC0gVFo9RXVyb3BlL01hZHJpZAogICAgICAtIEpFTExZRklOX1B1Ymxpc2hlZFNlcnZlclVybD0kU0VSVklDRV9GUUROX0pFTExZRklOCiAgICB2b2x1bWVzOgogICAgICAtICdqZWxseWZpbi1jb25maWc6L2NvbmZpZycKICAgICAgLSAnamVsbHlmaW4tdHZzaG93czovZGF0YS90dnNob3dzJwogICAgICAtICdqZWxseWZpbi1tb3ZpZXM6L2RhdGEvbW92aWVzJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjgwOTYnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK",
|
||||
"tags": [
|
||||
"media",
|
||||
"server",
|
||||
"movies",
|
||||
"tv",
|
||||
"music"
|
||||
]
|
||||
},
|
||||
"kuzzle": {
|
||||
"documentation": "https:\/\/docs.kuzzle.io\/",
|
||||
"slogan": "Kuzzle is a generic backend offering the basic building blocks common to every application.",
|
||||
"compose": "c2VydmljZXM6CiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOjYuMi40JwogICAgY29tbWFuZDogJ3JlZGlzLXNlcnZlciAtLWFwcGVuZG9ubHkgeWVzJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIHJlZGlzLWNsaQogICAgICAgIC0gcGluZwogICAgICBpbnRlcnZhbDogMXMKICAgICAgdGltZW91dDogM3MKICAgICAgcmV0cmllczogMzAKICBlbGFzdGljc2VhcmNoOgogICAgaW1hZ2U6ICdrdXp6bGVpby9lbGFzdGljc2VhcmNoOjcnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly9sb2NhbGhvc3Q6OTIwMCcKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDJzCiAgICAgIHJldHJpZXM6IDEwCiAgICB1bGltaXRzOgogICAgICBub2ZpbGU6IDY1NTM2CiAga3V6emxlOgogICAgaW1hZ2U6ICdrdXp6bGVpby9rdXp6bGU6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX0tVWlpMRV83NTEyCiAgICAgIC0gJ2t1enpsZV9zZXJ2aWNlc19fc3RvcmFnZUVuZ2luZV9fY2xpZW50X19ub2RlPWh0dHA6Ly9lbGFzdGljc2VhcmNoOjkyMDAnCiAgICAgIC0ga3V6emxlX3NlcnZpY2VzX19zdG9yYWdlRW5naW5lX19jb21tb25NYXBwaW5nX19keW5hbWljPXRydWUKICAgICAgLSBrdXp6bGVfc2VydmljZXNfX2ludGVybmFsQ2FjaGVfX25vZGVfX2hvc3Q9cmVkaXMKICAgICAgLSBrdXp6bGVfc2VydmljZXNfX21lbW9yeVN0b3JhZ2VfX25vZGVfX2hvc3Q9cmVkaXMKICAgICAgLSBrdXp6bGVfc2VydmVyX19wcm90b2NvbHNfX21xdHRfX2VuYWJsZWQ9dHJ1ZQogICAgICAtIGt1enpsZV9zZXJ2ZXJfX3Byb3RvY29sc19fbXF0dF9fZGV2ZWxvcG1lbnRNb2RlPWZhbHNlCiAgICAgIC0ga3V6emxlX2xpbWl0c19fbG9naW5zUGVyU2Vjb25kPTUwCiAgICAgIC0gTk9ERV9FTlY9cHJvZHVjdGlvbgogICAgICAtICdERUJVRz0ke0RFQlVHOi1rdXp6bGU6Y2x1c3RlcjpzeW5jfScKICAgICAgLSAnREVCVUdfREVQVEg9JHtERUJVR19ERVBUSDotMH0nCiAgICAgIC0gJ0RFQlVHX01BWF9BUlJBWV9MRU5HVEg9JHtERUJVR19NQVhfQVJSQVk6LTEwMH0nCiAgICAgIC0gJ0RFQlVHX0VYUEFORD0ke0RFQlVHX0VYUEFORDotb2ZmfScKICAgICAgLSAnREVCVUdfU0hPV19ISURERU49eyRERUJVR19TSE9XX0hJRERFTjotb259JwogICAgICAtICdERUJVR19DT0xPUlM9JHtERUJVR19DT0xPUlM6LW9ufScKICAgIGNhcF9hZGQ6CiAgICAgIC0gU1lTX1BUUkFDRQogICAgdWxpbWl0czoKICAgICAgbm9maWxlOiA2NTUzNgogICAgc3lzY3RsczoKICAgICAgLSBuZXQuY29yZS5zb21heGNvbm49ODE5MgogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0Ojc1MTIvX2hlYWx0aGNoZWNrJwogICAgICB0aW1lb3V0OiAxcwogICAgICBpbnRlcnZhbDogMnMKICAgICAgcmV0cmllczogMzAKICAgIGRlcGVuZHNfb246CiAgICAgIHJlZGlzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIGVsYXN0aWNzZWFyY2g6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkK",
|
||||
"tags": [
|
||||
"backend",
|
||||
"api",
|
||||
"realtime",
|
||||
"websocket",
|
||||
"mqtt",
|
||||
"rest",
|
||||
"sdk",
|
||||
"iot",
|
||||
"geofencing",
|
||||
"low-code"
|
||||
]
|
||||
},
|
||||
"metube": {
|
||||
"documentation": "https:\/\/github.com\/alexta69\/metube",
|
||||
"slogan": "A web GUI for youtube-dl with playlist support. It enables you to effortlessly download videos from YouTube and dozens of other sites.",
|
||||
@ -179,6 +239,22 @@
|
||||
"api"
|
||||
]
|
||||
},
|
||||
"moodle": {
|
||||
"documentation": "https:\/\/moodle.org",
|
||||
"slogan": "Moodle is the world\u2019s most customisable and trusted eLearning solution that empowers educators to improve our world.",
|
||||
"compose": "c2VydmljZXM6CiAgbWFyaWFkYjoKICAgIGltYWdlOiAnbWFyaWFkYjoxMS4xJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gQUxMT1dfRU1QVFlfUEFTU1dPUkQ9bm8KICAgICAgLSBNWVNRTF9ST09UX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX1JPT1QKICAgICAgLSBNWVNRTF9EQVRBQkFTRT1iaXRuYW1pX21vb2RsZQogICAgICAtIE1ZU1FMX1VTRVI9JFNFUlZJQ0VfVVNFUl9NQVJJQURCCiAgICAgIC0gTVlTUUxfUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfTUFSSUFEQgogICAgICAtIE1BUklBREJfQ0hBUkFDVEVSX1NFVD11dGY4bWI0CiAgICAgIC0gTUFSSUFEQl9DT0xMQVRFPXV0ZjhtYjRfdW5pY29kZV9jaQogICAgdm9sdW1lczoKICAgICAgLSAnbWFyaWFkYi1kYXRhOi92YXIvbGliL215c3FsJwogIG1vb2RsZToKICAgIGltYWdlOiAnZG9ja2VyLmlvL2JpdG5hbWkvbW9vZGxlOjQuMycKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9NT09ETEUKICAgICAgLSBNT09ETEVfREFUQUJBU0VfSE9TVD1tYXJpYWRiCiAgICAgIC0gTU9PRExFX0RBVEFCQVNFX1BPUlRfTlVNQkVSPTMzMDYKICAgICAgLSBNT09ETEVfREFUQUJBU0VfVVNFUj0kU0VSVklDRV9VU0VSX01BUklBREIKICAgICAgLSBNT09ETEVfREFUQUJBU0VfTkFNRT1iaXRuYW1pX21vb2RsZQogICAgICAtIE1PT0RMRV9EQVRBQkFTRV9QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9NQVJJQURCCiAgICAgIC0gQUxMT1dfRU1QVFlfUEFTU1dPUkQ9bm8KICAgICAgLSAnTU9PRExFX1VTRVJOQU1FPSR7TU9PRExFX1VTRVJOQU1FOi11c2VyfScKICAgICAgLSBNT09ETEVfUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfTU9PRExFCiAgICAgIC0gTU9PRExFX0VNQUlMPXVzZXJAZXhhbXBsZS5jb20KICAgICAgLSAnTU9PRExFX1NJVEVfTkFNRT0ke01PT0RMRV9TSVRFX05BTUU6LU5ldyBTaXRlfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ21vb2RsZS1kYXRhOi9iaXRuYW1pL21vb2RsZScKICAgICAgLSAnbW9vZGxlZGF0YS1kYXRhOi9iaXRuYW1pL21vb2RsZWRhdGEnCiAgICBkZXBlbmRzX29uOgogICAgICAtIG1hcmlhZGIK",
|
||||
"tags": [
|
||||
"moodle",
|
||||
"elearning",
|
||||
"education",
|
||||
"lms",
|
||||
"cms",
|
||||
"open",
|
||||
"source",
|
||||
"low",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
"n8n-with-postgresql": {
|
||||
"documentation": "https:\/\/docs.n8n.io\/hosting\/",
|
||||
"slogan": "n8n is an extendable workflow automation tool which enables you to connect anything to everything via its open, fair-code model.",
|
||||
@ -247,6 +323,14 @@
|
||||
"teamwork"
|
||||
]
|
||||
},
|
||||
"phpmyadmin": {
|
||||
"documentation": "https:\/\/docs.phpmyadmin.net\/en\/latest\/",
|
||||
"slogan": "phpMyAdmin is a web-based database management tool for administering your MySQL and MariaDB databases through a user-friendly interface.",
|
||||
"compose": "c2VydmljZXM6CiAgcGhwbXlhZG1pbjoKICAgIGltYWdlOiAnbHNjci5pby9saW51eHNlcnZlci9waHBteWFkbWluOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9QSFBNWUFETUlOCiAgICAgIC0gUFVJRD0xMDAwCiAgICAgIC0gUEdJRD0xMDAwCiAgICAgIC0gVFo9RXVyb3BlL01hZHJpZAogICAgICAtIFBNQV9BUkJJVFJBUlk9MQogICAgICAtIFBNQV9BQlNPTFVURV9VUkk9JFNFUlZJQ0VfRlFETl9QSFBNWUFETUlOCiAgICB2b2x1bWVzOgogICAgICAtICdwaHBteWFkbWluLWNvbmZpZzovY29uZmlnJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjgwJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1Cg==",
|
||||
"tags": [
|
||||
"database management"
|
||||
]
|
||||
},
|
||||
"pocketbase": {
|
||||
"documentation": "https:\/\/pocketbase.io\/docs\/",
|
||||
"slogan": "Open Source backend for your next SaaS and Mobile app in 1 file",
|
||||
@ -296,6 +380,24 @@
|
||||
"real-time"
|
||||
]
|
||||
},
|
||||
"vaultwarden": {
|
||||
"documentation": "https:\/\/github.com\/dani-garcia\/vaultwarden\/wiki\/FAQs",
|
||||
"slogan": "Vaultwarden is an open-source password manager that allows you to securely store and manage your passwords, helping you stay organized and protected.",
|
||||
"compose": "c2VydmljZXM6CiAgdmF1bHR3YXJkZW46CiAgICBpbWFnZTogJ3ZhdWx0d2FyZGVuL3NlcnZlcjpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fVkFVTFRXQVJERU4KICAgIHZvbHVtZXM6CiAgICAgIC0gJ3ZhdWx0d2FyZGVuLWRhdGE6L2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly9sb2NhbGhvc3Q6ODAnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK",
|
||||
"tags": [
|
||||
"password manager",
|
||||
"security"
|
||||
]
|
||||
},
|
||||
"whoogle": {
|
||||
"documentation": "https:\/\/github.com\/benbusby\/whoogle-search#install",
|
||||
"slogan": "Whoogle is a self-hosted, privacy-focused search engine front-end for accessing Google search results without tracking and data collection.",
|
||||
"compose": "c2VydmljZXM6CiAgd2hvb2dsZToKICAgIGltYWdlOiAnYmVuYnVzYnkvd2hvb2dsZS1zZWFyY2g6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX1dIT09HTEUKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDo1MDAwJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1Cg==",
|
||||
"tags": [
|
||||
"privacy",
|
||||
"search engine"
|
||||
]
|
||||
},
|
||||
"wordpress-with-mariadb": {
|
||||
"documentation": "https:\/\/wordpress.org\/documentation\/",
|
||||
"slogan": "WordPress with MariaDB. Wordpress is open source software you can use to create a beautiful website, blog, or app.",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"version": "3.12.36"
|
||||
},
|
||||
"v4": {
|
||||
"version": "4.0.0-beta.108"
|
||||
"version": "4.0.0-beta.109"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user