mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-12 18:15:58 +00:00
61 files changed
+1500
-447
No files matched your search
@@ -33,7 +33,6 @@ class StartClickhouse
|
||||
$environment_variables = $this->generate_environment_variables();
|
||||
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
'services' => [
|
||||
$container_name => [
|
||||
'image' => $this->database->image,
|
||||
|
||||
@@ -107,7 +107,6 @@ class StartDatabaseProxy
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
EOF;
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
'services' => [
|
||||
$proxyContainerName => [
|
||||
'build' => [
|
||||
|
||||
@@ -36,7 +36,6 @@ class StartDragonfly
|
||||
$environment_variables = $this->generate_environment_variables();
|
||||
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
'services' => [
|
||||
$container_name => [
|
||||
'image' => $this->database->image,
|
||||
|
||||
@@ -37,7 +37,6 @@ class StartKeydb
|
||||
$this->add_custom_keydb();
|
||||
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
'services' => [
|
||||
$container_name => [
|
||||
'image' => $this->database->image,
|
||||
@@ -96,7 +95,7 @@ class StartKeydb
|
||||
if (count($volume_names) > 0) {
|
||||
$docker_compose['volumes'] = $volume_names;
|
||||
}
|
||||
if (!is_null($this->database->keydb_conf)) {
|
||||
if (!is_null($this->database->keydb_conf) || !empty($this->database->keydb_conf)) {
|
||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||
'type' => 'bind',
|
||||
'source' => $this->configuration_dir . '/keydb.conf',
|
||||
@@ -162,7 +161,7 @@ class StartKeydb
|
||||
}
|
||||
private function add_custom_keydb()
|
||||
{
|
||||
if (is_null($this->database->keydb_conf)) {
|
||||
if (is_null($this->database->keydb_conf) || empty($this->database->keydb_conf)) {
|
||||
return;
|
||||
}
|
||||
$filename = 'keydb.conf';
|
||||
|
||||
@@ -32,7 +32,6 @@ class StartMariadb
|
||||
$environment_variables = $this->generate_environment_variables();
|
||||
$this->add_custom_mysql();
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
'services' => [
|
||||
$container_name => [
|
||||
'image' => $this->database->image,
|
||||
@@ -90,7 +89,7 @@ class StartMariadb
|
||||
if (count($volume_names) > 0) {
|
||||
$docker_compose['volumes'] = $volume_names;
|
||||
}
|
||||
if (!is_null($this->database->mariadb_conf)) {
|
||||
if (!is_null($this->database->mariadb_conf) || !empty($this->database->mariadb_conf)) {
|
||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||
'type' => 'bind',
|
||||
'source' => $this->configuration_dir . '/custom-config.cnf',
|
||||
@@ -165,7 +164,7 @@ class StartMariadb
|
||||
}
|
||||
private function add_custom_mysql()
|
||||
{
|
||||
if (is_null($this->database->mariadb_conf)) {
|
||||
if (is_null($this->database->mariadb_conf) || empty($this->database->mariadb_conf)) {
|
||||
return;
|
||||
}
|
||||
$filename = 'custom-config.cnf';
|
||||
|
||||
@@ -35,7 +35,6 @@ class StartMongodb
|
||||
$this->add_custom_mongo_conf();
|
||||
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
'services' => [
|
||||
$container_name => [
|
||||
'image' => $this->database->image,
|
||||
@@ -97,7 +96,7 @@ class StartMongodb
|
||||
if (count($volume_names) > 0) {
|
||||
$docker_compose['volumes'] = $volume_names;
|
||||
}
|
||||
if (!is_null($this->database->mongo_conf)) {
|
||||
if (!is_null($this->database->mongo_conf) || !empty($this->database->mongo_conf)) {
|
||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||
'type' => 'bind',
|
||||
'source' => $this->configuration_dir . '/mongod.conf',
|
||||
@@ -178,7 +177,7 @@ class StartMongodb
|
||||
}
|
||||
private function add_custom_mongo_conf()
|
||||
{
|
||||
if (is_null($this->database->mongo_conf)) {
|
||||
if (is_null($this->database->mongo_conf) || empty($this->database->mongo_conf)) {
|
||||
return;
|
||||
}
|
||||
$filename = 'mongod.conf';
|
||||
|
||||
@@ -32,7 +32,6 @@ class StartMysql
|
||||
$environment_variables = $this->generate_environment_variables();
|
||||
$this->add_custom_mysql();
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
'services' => [
|
||||
$container_name => [
|
||||
'image' => $this->database->image,
|
||||
@@ -90,7 +89,7 @@ class StartMysql
|
||||
if (count($volume_names) > 0) {
|
||||
$docker_compose['volumes'] = $volume_names;
|
||||
}
|
||||
if (!is_null($this->database->mysql_conf)) {
|
||||
if (!is_null($this->database->mysql_conf) || !empty($this->database->mysql_conf)) {
|
||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||
'type' => 'bind',
|
||||
'source' => $this->configuration_dir . '/custom-config.cnf',
|
||||
@@ -165,7 +164,7 @@ class StartMysql
|
||||
}
|
||||
private function add_custom_mysql()
|
||||
{
|
||||
if (is_null($this->database->mysql_conf)) {
|
||||
if (is_null($this->database->mysql_conf) || empty($this->database->mysql_conf)) {
|
||||
return;
|
||||
}
|
||||
$filename = 'custom-config.cnf';
|
||||
|
||||
@@ -35,7 +35,6 @@ class StartPostgresql
|
||||
$this->add_custom_conf();
|
||||
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
'services' => [
|
||||
$container_name => [
|
||||
'image' => $this->database->image,
|
||||
@@ -78,7 +77,6 @@ class StartPostgresql
|
||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||
}
|
||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||
ray('Log Drain Enabled');
|
||||
$docker_compose['services'][$container_name]['logging'] = [
|
||||
'driver' => 'fluentd',
|
||||
'options' => [
|
||||
@@ -107,7 +105,7 @@ class StartPostgresql
|
||||
];
|
||||
}
|
||||
}
|
||||
if (!is_null($this->database->postgres_conf)) {
|
||||
if (!is_null($this->database->postgres_conf) && !empty($this->database->postgres_conf)) {
|
||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||
'type' => 'bind',
|
||||
'source' => $this->configuration_dir . '/custom-postgres.conf',
|
||||
@@ -165,8 +163,6 @@ class StartPostgresql
|
||||
private function generate_environment_variables()
|
||||
{
|
||||
$environment_variables = collect();
|
||||
ray('Generate Environment Variables')->green();
|
||||
ray($this->database->runtime_environment_variables)->green();
|
||||
foreach ($this->database->runtime_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
@@ -203,11 +199,16 @@ class StartPostgresql
|
||||
}
|
||||
private function add_custom_conf()
|
||||
{
|
||||
if (is_null($this->database->postgres_conf)) {
|
||||
if (is_null($this->database->postgres_conf) || empty($this->database->postgres_conf)) {
|
||||
return;
|
||||
}
|
||||
$filename = 'custom-postgres.conf';
|
||||
$content = $this->database->postgres_conf;
|
||||
if (!str($content)->contains('listen_addresses')) {
|
||||
$content .= "\nlisten_addresses = '*'";
|
||||
$this->database->postgres_conf = $content;
|
||||
$this->database->save();
|
||||
}
|
||||
$content_base64 = base64_encode($content);
|
||||
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ class StartRedis
|
||||
$this->add_custom_redis();
|
||||
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
'services' => [
|
||||
$container_name => [
|
||||
'image' => $this->database->image,
|
||||
@@ -100,7 +99,7 @@ class StartRedis
|
||||
if (count($volume_names) > 0) {
|
||||
$docker_compose['volumes'] = $volume_names;
|
||||
}
|
||||
if (!is_null($this->database->redis_conf)) {
|
||||
if (!is_null($this->database->redis_conf) || !empty($this->database->redis_conf)) {
|
||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||
'type' => 'bind',
|
||||
'source' => $this->configuration_dir . '/redis.conf',
|
||||
@@ -166,7 +165,7 @@ class StartRedis
|
||||
}
|
||||
private function add_custom_redis()
|
||||
{
|
||||
if (is_null($this->database->redis_conf)) {
|
||||
if (is_null($this->database->redis_conf) || empty($this->database->redis_conf)) {
|
||||
return;
|
||||
}
|
||||
$filename = 'redis.conf';
|
||||
|
||||
@@ -0,0 +1,657 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Docker;
|
||||
|
||||
use App\Actions\Database\StartDatabaseProxy;
|
||||
use App\Actions\Proxy\CheckProxy;
|
||||
use App\Actions\Proxy\StartProxy;
|
||||
use App\Actions\Shared\ComplexStatusCheck;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\Server;
|
||||
use App\Models\ServiceDatabase;
|
||||
use App\Notifications\Container\ContainerRestarted;
|
||||
use App\Notifications\Container\ContainerStopped;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class GetContainersStatus
|
||||
{
|
||||
use AsAction;
|
||||
public $applications;
|
||||
public $server;
|
||||
|
||||
public function handle(Server $server)
|
||||
{
|
||||
if (isDev()) {
|
||||
$server = Server::find(0);
|
||||
}
|
||||
$this->server = $server;
|
||||
if (!$this->server->isFunctional()) {
|
||||
return 'Server is not ready.';
|
||||
};
|
||||
$this->applications = $this->server->applications();
|
||||
$skip_these_applications = collect([]);
|
||||
foreach ($this->applications as $application) {
|
||||
if ($application->additional_servers->count() > 0) {
|
||||
$skip_these_applications->push($application);
|
||||
ComplexStatusCheck::run($application);
|
||||
$this->applications = $this->applications->filter(function ($value, $key) use ($application) {
|
||||
return $value->id !== $application->id;
|
||||
});
|
||||
}
|
||||
}
|
||||
$this->applications = $this->applications->filter(function ($value, $key) use ($skip_these_applications) {
|
||||
return !$skip_these_applications->pluck('id')->contains($value->id);
|
||||
});
|
||||
$this->old_way();
|
||||
// if ($this->server->isSwarm()) {
|
||||
// $this->old_way();
|
||||
// } else {
|
||||
// if (!$this->server->is_metrics_enabled) {
|
||||
// $this->old_way();
|
||||
// return;
|
||||
// }
|
||||
// $sentinel_found = instant_remote_process(["docker inspect coolify-sentinel"], $this->server, false);
|
||||
// $sentinel_found = json_decode($sentinel_found, true);
|
||||
// $status = data_get($sentinel_found, '0.State.Status', 'exited');
|
||||
// if ($status === 'running') {
|
||||
// ray('Checking with Sentinel');
|
||||
// $this->sentinel();
|
||||
// } else {
|
||||
// ray('Checking the Old way');
|
||||
// $this->old_way();
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
private function sentinel()
|
||||
{
|
||||
try {
|
||||
$containers = $this->server->getContainers();
|
||||
if ($containers->count() === 0) {
|
||||
return;
|
||||
}
|
||||
$databases = $this->server->databases();
|
||||
$services = $this->server->services()->get();
|
||||
$previews = $this->server->previews();
|
||||
$foundApplications = [];
|
||||
$foundApplicationPreviews = [];
|
||||
$foundDatabases = [];
|
||||
$foundServices = [];
|
||||
|
||||
foreach ($containers as $container) {
|
||||
$labels = Arr::undot(data_get($container, 'labels'));
|
||||
$containerStatus = data_get($container, 'state');
|
||||
$containerHealth = data_get($container, 'health_status', 'unhealthy');
|
||||
$containerStatus = "$containerStatus ($containerHealth)";
|
||||
$applicationId = data_get($labels, 'coolify.applicationId');
|
||||
if ($applicationId) {
|
||||
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
||||
if ($pullRequestId) {
|
||||
if (str($applicationId)->contains('-')) {
|
||||
$applicationId = str($applicationId)->before('-');
|
||||
}
|
||||
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
||||
if ($preview) {
|
||||
$foundApplicationPreviews[] = $preview->id;
|
||||
$statusFromDb = $preview->status;
|
||||
if ($statusFromDb !== $containerStatus) {
|
||||
$preview->update(['status' => $containerStatus]);
|
||||
}
|
||||
} else {
|
||||
//Notify user that this container should not be there.
|
||||
}
|
||||
} else {
|
||||
$application = $this->applications->where('id', $applicationId)->first();
|
||||
if ($application) {
|
||||
$foundApplications[] = $application->id;
|
||||
$statusFromDb = $application->status;
|
||||
if ($statusFromDb !== $containerStatus) {
|
||||
$application->update(['status' => $containerStatus]);
|
||||
}
|
||||
} else {
|
||||
//Notify user that this container should not be there.
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$uuid = data_get($labels, 'com.docker.compose.service');
|
||||
$type = data_get($labels, 'coolify.type');
|
||||
if ($uuid) {
|
||||
if ($type === 'service') {
|
||||
$database_id = data_get($labels, 'coolify.service.subId');
|
||||
if ($database_id) {
|
||||
$service_db = ServiceDatabase::where('id', $database_id)->first();
|
||||
if ($service_db) {
|
||||
$uuid = $service_db->service->uuid;
|
||||
$isPublic = data_get($service_db, 'is_public');
|
||||
if ($isPublic) {
|
||||
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
||||
if ($this->server->isSwarm()) {
|
||||
// TODO: fix this with sentinel
|
||||
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||
} else {
|
||||
return data_get($value, 'name') === "$uuid-proxy";
|
||||
}
|
||||
})->first();
|
||||
if (!$foundTcpProxy) {
|
||||
StartDatabaseProxy::run($service_db);
|
||||
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$database = $databases->where('uuid', $uuid)->first();
|
||||
if ($database) {
|
||||
$isPublic = data_get($database, 'is_public');
|
||||
$foundDatabases[] = $database->id;
|
||||
$statusFromDb = $database->status;
|
||||
if ($statusFromDb !== $containerStatus) {
|
||||
$database->update(['status' => $containerStatus]);
|
||||
}
|
||||
if ($isPublic) {
|
||||
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
||||
if ($this->server->isSwarm()) {
|
||||
// TODO: fix this with sentinel
|
||||
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||
} else {
|
||||
return data_get($value, 'name') === "$uuid-proxy";
|
||||
}
|
||||
})->first();
|
||||
if (!$foundTcpProxy) {
|
||||
StartDatabaseProxy::run($database);
|
||||
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Notify user that this container should not be there.
|
||||
}
|
||||
}
|
||||
}
|
||||
if (data_get($container, 'name') === 'coolify-db') {
|
||||
$foundDatabases[] = 0;
|
||||
}
|
||||
}
|
||||
$serviceLabelId = data_get($labels, 'coolify.serviceId');
|
||||
if ($serviceLabelId) {
|
||||
$subType = data_get($labels, 'coolify.service.subType');
|
||||
$subId = data_get($labels, 'coolify.service.subId');
|
||||
$service = $services->where('id', $serviceLabelId)->first();
|
||||
if (!$service) {
|
||||
continue;
|
||||
}
|
||||
if ($subType === 'application') {
|
||||
$service = $service->applications()->where('id', $subId)->first();
|
||||
} else {
|
||||
$service = $service->databases()->where('id', $subId)->first();
|
||||
}
|
||||
if ($service) {
|
||||
$foundServices[] = "$service->id-$service->name";
|
||||
$statusFromDb = $service->status;
|
||||
if ($statusFromDb !== $containerStatus) {
|
||||
// ray('Updating status: ' . $containerStatus);
|
||||
$service->update(['status' => $containerStatus]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$exitedServices = collect([]);
|
||||
foreach ($services as $service) {
|
||||
$apps = $service->applications()->get();
|
||||
$dbs = $service->databases()->get();
|
||||
foreach ($apps as $app) {
|
||||
if (in_array("$app->id-$app->name", $foundServices)) {
|
||||
continue;
|
||||
} else {
|
||||
$exitedServices->push($app);
|
||||
}
|
||||
}
|
||||
foreach ($dbs as $db) {
|
||||
if (in_array("$db->id-$db->name", $foundServices)) {
|
||||
continue;
|
||||
} else {
|
||||
$exitedServices->push($db);
|
||||
}
|
||||
}
|
||||
}
|
||||
$exitedServices = $exitedServices->unique('id');
|
||||
foreach ($exitedServices as $exitedService) {
|
||||
if (str($exitedService->status)->startsWith('exited')) {
|
||||
continue;
|
||||
}
|
||||
$name = data_get($exitedService, 'name');
|
||||
$fqdn = data_get($exitedService, 'fqdn');
|
||||
$containerName = $name ? "$name, available at $fqdn" : $fqdn;
|
||||
$projectUuid = data_get($service, 'environment.project.uuid');
|
||||
$serviceUuid = data_get($service, 'uuid');
|
||||
$environmentName = data_get($service, 'environment.name');
|
||||
|
||||
if ($projectUuid && $serviceUuid && $environmentName) {
|
||||
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/service/" . $serviceUuid;
|
||||
} else {
|
||||
$url = null;
|
||||
}
|
||||
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
$exitedService->update(['status' => 'exited']);
|
||||
}
|
||||
|
||||
$notRunningApplications = $this->applications->pluck('id')->diff($foundApplications);
|
||||
foreach ($notRunningApplications as $applicationId) {
|
||||
$application = $this->applications->where('id', $applicationId)->first();
|
||||
if (str($application->status)->startsWith('exited')) {
|
||||
continue;
|
||||
}
|
||||
$application->update(['status' => 'exited']);
|
||||
|
||||
$name = data_get($application, 'name');
|
||||
$fqdn = data_get($application, 'fqdn');
|
||||
|
||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||
|
||||
$projectUuid = data_get($application, 'environment.project.uuid');
|
||||
$applicationUuid = data_get($application, 'uuid');
|
||||
$environment = data_get($application, 'environment.name');
|
||||
|
||||
if ($projectUuid && $applicationUuid && $environment) {
|
||||
$url = base_url() . '/project/' . $projectUuid . "/" . $environment . "/application/" . $applicationUuid;
|
||||
} else {
|
||||
$url = null;
|
||||
}
|
||||
|
||||
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
}
|
||||
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
||||
foreach ($notRunningApplicationPreviews as $previewId) {
|
||||
$preview = $previews->where('id', $previewId)->first();
|
||||
if (str($preview->status)->startsWith('exited')) {
|
||||
continue;
|
||||
}
|
||||
$preview->update(['status' => 'exited']);
|
||||
|
||||
$name = data_get($preview, 'name');
|
||||
$fqdn = data_get($preview, 'fqdn');
|
||||
|
||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||
|
||||
$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;
|
||||
} else {
|
||||
$url = null;
|
||||
}
|
||||
|
||||
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
}
|
||||
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
||||
foreach ($notRunningDatabases as $database) {
|
||||
$database = $databases->where('id', $database)->first();
|
||||
if (str($database->status)->startsWith('exited')) {
|
||||
continue;
|
||||
}
|
||||
$database->update(['status' => 'exited']);
|
||||
|
||||
$name = data_get($database, 'name');
|
||||
$fqdn = data_get($database, 'fqdn');
|
||||
|
||||
$containerName = $name;
|
||||
|
||||
$projectUuid = data_get($database, 'environment.project.uuid');
|
||||
$environmentName = data_get($database, 'environment.name');
|
||||
$databaseUuid = data_get($database, 'uuid');
|
||||
|
||||
if ($projectUuid && $databaseUuid && $environmentName) {
|
||||
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/database/" . $databaseUuid;
|
||||
} else {
|
||||
$url = null;
|
||||
}
|
||||
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
}
|
||||
|
||||
// Check if proxy is running
|
||||
$this->server->proxyType();
|
||||
$foundProxyContainer = $containers->filter(function ($value, $key) {
|
||||
if ($this->server->isSwarm()) {
|
||||
// TODO: fix this with sentinel
|
||||
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
||||
} else {
|
||||
return data_get($value, 'name') === 'coolify-proxy';
|
||||
}
|
||||
})->first();
|
||||
if (!$foundProxyContainer) {
|
||||
try {
|
||||
$shouldStart = CheckProxy::run($this->server);
|
||||
if ($shouldStart) {
|
||||
StartProxy::run($this->server, false);
|
||||
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
}
|
||||
} else {
|
||||
$this->server->proxy->status = data_get($foundProxyContainer, 'state');
|
||||
$this->server->save();
|
||||
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
||||
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
|
||||
ray($e->getMessage());
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
private function old_way()
|
||||
{
|
||||
if ($this->server->isSwarm()) {
|
||||
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
|
||||
$containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
|
||||
} else {
|
||||
// Precheck for containers
|
||||
$containers = instant_remote_process(["docker container ls -q"], $this->server, false);
|
||||
if (!$containers) {
|
||||
return;
|
||||
}
|
||||
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false);
|
||||
$containerReplicates = null;
|
||||
}
|
||||
if (is_null($containers)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$containers = format_docker_command_output_to_json($containers);
|
||||
if ($containerReplicates) {
|
||||
$containerReplicates = format_docker_command_output_to_json($containerReplicates);
|
||||
foreach ($containerReplicates as $containerReplica) {
|
||||
$name = data_get($containerReplica, 'Name');
|
||||
$containers = $containers->map(function ($container) use ($name, $containerReplica) {
|
||||
if (data_get($container, 'Spec.Name') === $name) {
|
||||
$replicas = data_get($containerReplica, 'Replicas');
|
||||
$running = str($replicas)->explode('/')[0];
|
||||
$total = str($replicas)->explode('/')[1];
|
||||
if ($running === $total) {
|
||||
data_set($container, 'State.Status', 'running');
|
||||
data_set($container, 'State.Health.Status', 'healthy');
|
||||
} else {
|
||||
data_set($container, 'State.Status', 'starting');
|
||||
data_set($container, 'State.Health.Status', 'unhealthy');
|
||||
}
|
||||
}
|
||||
return $container;
|
||||
});
|
||||
}
|
||||
}
|
||||
$databases = $this->server->databases();
|
||||
$services = $this->server->services()->get();
|
||||
$previews = $this->server->previews();
|
||||
$foundApplications = [];
|
||||
$foundApplicationPreviews = [];
|
||||
$foundDatabases = [];
|
||||
$foundServices = [];
|
||||
|
||||
foreach ($containers as $container) {
|
||||
if ($this->server->isSwarm()) {
|
||||
$labels = data_get($container, 'Spec.Labels');
|
||||
$uuid = data_get($labels, 'coolify.name');
|
||||
} else {
|
||||
$labels = data_get($container, 'Config.Labels');
|
||||
}
|
||||
$containerStatus = data_get($container, 'State.Status');
|
||||
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
|
||||
$containerStatus = "$containerStatus ($containerHealth)";
|
||||
$labels = Arr::undot(format_docker_labels_to_json($labels));
|
||||
$applicationId = data_get($labels, 'coolify.applicationId');
|
||||
if ($applicationId) {
|
||||
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
||||
if ($pullRequestId) {
|
||||
if (str($applicationId)->contains('-')) {
|
||||
$applicationId = str($applicationId)->before('-');
|
||||
}
|
||||
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
||||
if ($preview) {
|
||||
$foundApplicationPreviews[] = $preview->id;
|
||||
$statusFromDb = $preview->status;
|
||||
if ($statusFromDb !== $containerStatus) {
|
||||
$preview->update(['status' => $containerStatus]);
|
||||
}
|
||||
} else {
|
||||
//Notify user that this container should not be there.
|
||||
}
|
||||
} else {
|
||||
$application = $this->applications->where('id', $applicationId)->first();
|
||||
if ($application) {
|
||||
$foundApplications[] = $application->id;
|
||||
$statusFromDb = $application->status;
|
||||
if ($statusFromDb !== $containerStatus) {
|
||||
$application->update(['status' => $containerStatus]);
|
||||
}
|
||||
} else {
|
||||
//Notify user that this container should not be there.
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$uuid = data_get($labels, 'com.docker.compose.service');
|
||||
$type = data_get($labels, 'coolify.type');
|
||||
|
||||
if ($uuid) {
|
||||
if ($type === 'service') {
|
||||
$database_id = data_get($labels, 'coolify.service.subId');
|
||||
if ($database_id) {
|
||||
$service_db = ServiceDatabase::where('id', $database_id)->first();
|
||||
if ($service_db) {
|
||||
$uuid = $service_db->service->uuid;
|
||||
$isPublic = data_get($service_db, 'is_public');
|
||||
if ($isPublic) {
|
||||
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
||||
if ($this->server->isSwarm()) {
|
||||
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||
} else {
|
||||
return data_get($value, 'Name') === "/$uuid-proxy";
|
||||
}
|
||||
})->first();
|
||||
if (!$foundTcpProxy) {
|
||||
StartDatabaseProxy::run($service_db);
|
||||
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$database = $databases->where('uuid', $uuid)->first();
|
||||
if ($database) {
|
||||
$isPublic = data_get($database, 'is_public');
|
||||
$foundDatabases[] = $database->id;
|
||||
$statusFromDb = $database->status;
|
||||
if ($statusFromDb !== $containerStatus) {
|
||||
$database->update(['status' => $containerStatus]);
|
||||
}
|
||||
if ($isPublic) {
|
||||
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
||||
if ($this->server->isSwarm()) {
|
||||
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||
} else {
|
||||
return data_get($value, 'Name') === "/$uuid-proxy";
|
||||
}
|
||||
})->first();
|
||||
if (!$foundTcpProxy) {
|
||||
StartDatabaseProxy::run($database);
|
||||
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Notify user that this container should not be there.
|
||||
}
|
||||
}
|
||||
}
|
||||
if (data_get($container, 'Name') === '/coolify-db') {
|
||||
$foundDatabases[] = 0;
|
||||
}
|
||||
}
|
||||
$serviceLabelId = data_get($labels, 'coolify.serviceId');
|
||||
if ($serviceLabelId) {
|
||||
$subType = data_get($labels, 'coolify.service.subType');
|
||||
$subId = data_get($labels, 'coolify.service.subId');
|
||||
$service = $services->where('id', $serviceLabelId)->first();
|
||||
if (!$service) {
|
||||
continue;
|
||||
}
|
||||
if ($subType === 'application') {
|
||||
$service = $service->applications()->where('id', $subId)->first();
|
||||
} else {
|
||||
$service = $service->databases()->where('id', $subId)->first();
|
||||
}
|
||||
if ($service) {
|
||||
$foundServices[] = "$service->id-$service->name";
|
||||
$statusFromDb = $service->status;
|
||||
if ($statusFromDb !== $containerStatus) {
|
||||
// ray('Updating status: ' . $containerStatus);
|
||||
$service->update(['status' => $containerStatus]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$exitedServices = collect([]);
|
||||
foreach ($services as $service) {
|
||||
$apps = $service->applications()->get();
|
||||
$dbs = $service->databases()->get();
|
||||
foreach ($apps as $app) {
|
||||
if (in_array("$app->id-$app->name", $foundServices)) {
|
||||
continue;
|
||||
} else {
|
||||
$exitedServices->push($app);
|
||||
}
|
||||
}
|
||||
foreach ($dbs as $db) {
|
||||
if (in_array("$db->id-$db->name", $foundServices)) {
|
||||
continue;
|
||||
} else {
|
||||
$exitedServices->push($db);
|
||||
}
|
||||
}
|
||||
}
|
||||
$exitedServices = $exitedServices->unique('id');
|
||||
foreach ($exitedServices as $exitedService) {
|
||||
if (str($exitedService->status)->startsWith('exited')) {
|
||||
continue;
|
||||
}
|
||||
$name = data_get($exitedService, 'name');
|
||||
$fqdn = data_get($exitedService, 'fqdn');
|
||||
$containerName = $name ? "$name, available at $fqdn" : $fqdn;
|
||||
$projectUuid = data_get($service, 'environment.project.uuid');
|
||||
$serviceUuid = data_get($service, 'uuid');
|
||||
$environmentName = data_get($service, 'environment.name');
|
||||
|
||||
if ($projectUuid && $serviceUuid && $environmentName) {
|
||||
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/service/" . $serviceUuid;
|
||||
} else {
|
||||
$url = null;
|
||||
}
|
||||
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
$exitedService->update(['status' => 'exited']);
|
||||
}
|
||||
|
||||
$notRunningApplications = $this->applications->pluck('id')->diff($foundApplications);
|
||||
foreach ($notRunningApplications as $applicationId) {
|
||||
$application = $this->applications->where('id', $applicationId)->first();
|
||||
if (str($application->status)->startsWith('exited')) {
|
||||
continue;
|
||||
}
|
||||
$application->update(['status' => 'exited']);
|
||||
|
||||
$name = data_get($application, 'name');
|
||||
$fqdn = data_get($application, 'fqdn');
|
||||
|
||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||
|
||||
$projectUuid = data_get($application, 'environment.project.uuid');
|
||||
$applicationUuid = data_get($application, 'uuid');
|
||||
$environment = data_get($application, 'environment.name');
|
||||
|
||||
if ($projectUuid && $applicationUuid && $environment) {
|
||||
$url = base_url() . '/project/' . $projectUuid . "/" . $environment . "/application/" . $applicationUuid;
|
||||
} else {
|
||||
$url = null;
|
||||
}
|
||||
|
||||
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
}
|
||||
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
||||
foreach ($notRunningApplicationPreviews as $previewId) {
|
||||
$preview = $previews->where('id', $previewId)->first();
|
||||
if (str($preview->status)->startsWith('exited')) {
|
||||
continue;
|
||||
}
|
||||
$preview->update(['status' => 'exited']);
|
||||
|
||||
$name = data_get($preview, 'name');
|
||||
$fqdn = data_get($preview, 'fqdn');
|
||||
|
||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||
|
||||
$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;
|
||||
} else {
|
||||
$url = null;
|
||||
}
|
||||
|
||||
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
}
|
||||
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
||||
foreach ($notRunningDatabases as $database) {
|
||||
$database = $databases->where('id', $database)->first();
|
||||
if (str($database->status)->startsWith('exited')) {
|
||||
continue;
|
||||
}
|
||||
$database->update(['status' => 'exited']);
|
||||
|
||||
$name = data_get($database, 'name');
|
||||
$fqdn = data_get($database, 'fqdn');
|
||||
|
||||
$containerName = $name;
|
||||
|
||||
$projectUuid = data_get($database, 'environment.project.uuid');
|
||||
$environmentName = data_get($database, 'environment.name');
|
||||
$databaseUuid = data_get($database, 'uuid');
|
||||
|
||||
if ($projectUuid && $databaseUuid && $environmentName) {
|
||||
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/database/" . $databaseUuid;
|
||||
} else {
|
||||
$url = null;
|
||||
}
|
||||
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
}
|
||||
|
||||
// Check if proxy is running
|
||||
$this->server->proxyType();
|
||||
$foundProxyContainer = $containers->filter(function ($value, $key) {
|
||||
if ($this->server->isSwarm()) {
|
||||
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
||||
} else {
|
||||
return data_get($value, 'Name') === '/coolify-proxy';
|
||||
}
|
||||
})->first();
|
||||
if (!$foundProxyContainer) {
|
||||
try {
|
||||
$shouldStart = CheckProxy::run($this->server);
|
||||
if ($shouldStart) {
|
||||
StartProxy::run($this->server, false);
|
||||
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
}
|
||||
} else {
|
||||
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
|
||||
$this->server->save();
|
||||
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
||||
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Server;
|
||||
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use App\Models\Server;
|
||||
|
||||
class StartSentinel
|
||||
{
|
||||
use AsAction;
|
||||
public function handle(Server $server, $version = 'latest', bool $restart = false)
|
||||
{
|
||||
if ($restart) {
|
||||
instant_remote_process(['docker rm -f coolify-sentinel'], $server, false);
|
||||
}
|
||||
instant_remote_process([
|
||||
"docker run --rm --pull always -d -e \"SCHEDULER=true\" --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/metrics:/app/metrics -v /data/coolify/logs:/app/logs --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 ghcr.io/coollabsio/sentinel:$version",
|
||||
"chown -R 9999:root /data/coolify/metrics /data/coolify/logs",
|
||||
"chmod -R 700 /data/coolify/metrics /data/coolify/logs"
|
||||
], $server, false);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ use App\Jobs\ScheduledTaskJob;
|
||||
use App\Jobs\InstanceAutoUpdateJob;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Jobs\PullHelperImageJob;
|
||||
use App\Jobs\PullSentinelImageJob;
|
||||
use App\Jobs\ServerStatusJob;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
@@ -57,7 +58,10 @@ class Kernel extends ConsoleKernel
|
||||
{
|
||||
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
|
||||
foreach ($servers as $server) {
|
||||
$schedule->job(new PullHelperImageJob($server))->everyTenMinutes()->onOneServer();
|
||||
if (config('coolify.is_sentinel_enabled')) {
|
||||
$schedule->job(new PullSentinelImageJob($server))->everyFiveMinutes()->onOneServer();
|
||||
}
|
||||
$schedule->job(new PullHelperImageJob($server))->everyFiveMinutes()->onOneServer();
|
||||
}
|
||||
}
|
||||
private function check_resources($schedule)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Actions\Docker\GetContainersStatus;
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Enums\ProcessStatus;
|
||||
use App\Events\ApplicationStatusChanged;
|
||||
@@ -302,7 +303,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
|
||||
if ($this->server->isProxyShouldRun()) {
|
||||
dispatch(new ContainerStatusJob($this->server));
|
||||
GetContainersStatus::dispatch($this->server);
|
||||
// dispatch(new ContainerStatusJob($this->server));
|
||||
}
|
||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||
if ($this->pull_request_id !== 0) {
|
||||
@@ -1020,7 +1022,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
"command" => "docker rm -f {$this->deployment_uuid}",
|
||||
"ignore_errors" => true,
|
||||
"hidden" => true
|
||||
],
|
||||
]
|
||||
);
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
$runCommand,
|
||||
"hidden" => true,
|
||||
@@ -1287,7 +1291,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->application->parseHealthcheckFromDockerfile($dockerfile);
|
||||
}
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
'services' => [
|
||||
$this->container_name => [
|
||||
'image' => $this->production_image_name,
|
||||
|
||||
+313
-319
@@ -2,15 +2,8 @@
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Actions\Database\StartDatabaseProxy;
|
||||
use App\Actions\Proxy\CheckProxy;
|
||||
use App\Actions\Proxy\StartProxy;
|
||||
use App\Actions\Shared\ComplexStatusCheck;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Actions\Docker\GetContainersStatus;
|
||||
use App\Models\Server;
|
||||
use App\Models\ServiceDatabase;
|
||||
use App\Notifications\Container\ContainerRestarted;
|
||||
use App\Notifications\Container\ContainerStopped;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
@@ -18,7 +11,6 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
@@ -44,335 +36,337 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
|
||||
public function handle()
|
||||
{
|
||||
if (!$this->server->isFunctional()) {
|
||||
return 'Server is not ready.';
|
||||
};
|
||||
$applications = $this->server->applications();
|
||||
$skip_these_applications = collect([]);
|
||||
foreach ($applications as $application) {
|
||||
if ($application->additional_servers->count() > 0) {
|
||||
$skip_these_applications->push($application);
|
||||
ComplexStatusCheck::run($application);
|
||||
$applications = $applications->filter(function ($value, $key) use ($application) {
|
||||
return $value->id !== $application->id;
|
||||
});
|
||||
}
|
||||
}
|
||||
$applications = $applications->filter(function ($value, $key) use ($skip_these_applications) {
|
||||
return !$skip_these_applications->pluck('id')->contains($value->id);
|
||||
});
|
||||
try {
|
||||
if ($this->server->isSwarm()) {
|
||||
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
|
||||
$containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
|
||||
} else {
|
||||
// Precheck for containers
|
||||
$containers = instant_remote_process(["docker container ls -q"], $this->server, false);
|
||||
if (!$containers) {
|
||||
return;
|
||||
}
|
||||
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false);
|
||||
$containerReplicates = null;
|
||||
}
|
||||
if (is_null($containers)) {
|
||||
return;
|
||||
}
|
||||
GetContainersStatus::run($this->server);
|
||||
return;
|
||||
// if (!$this->server->isFunctional()) {
|
||||
// return 'Server is not ready.';
|
||||
// };
|
||||
// $applications = $this->server->applications();
|
||||
// $skip_these_applications = collect([]);
|
||||
// foreach ($applications as $application) {
|
||||
// if ($application->additional_servers->count() > 0) {
|
||||
// $skip_these_applications->push($application);
|
||||
// ComplexStatusCheck::run($application);
|
||||
// $applications = $applications->filter(function ($value, $key) use ($application) {
|
||||
// return $value->id !== $application->id;
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// $applications = $applications->filter(function ($value, $key) use ($skip_these_applications) {
|
||||
// return !$skip_these_applications->pluck('id')->contains($value->id);
|
||||
// });
|
||||
// try {
|
||||
// if ($this->server->isSwarm()) {
|
||||
// $containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
|
||||
// $containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
|
||||
// } else {
|
||||
// // Precheck for containers
|
||||
// $containers = instant_remote_process(["docker container ls -q"], $this->server, false);
|
||||
// if (!$containers) {
|
||||
// return;
|
||||
// }
|
||||
// $containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false);
|
||||
// $containerReplicates = null;
|
||||
// }
|
||||
// if (is_null($containers)) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
$containers = format_docker_command_output_to_json($containers);
|
||||
if ($containerReplicates) {
|
||||
$containerReplicates = format_docker_command_output_to_json($containerReplicates);
|
||||
foreach ($containerReplicates as $containerReplica) {
|
||||
$name = data_get($containerReplica, 'Name');
|
||||
$containers = $containers->map(function ($container) use ($name, $containerReplica) {
|
||||
if (data_get($container, 'Spec.Name') === $name) {
|
||||
$replicas = data_get($containerReplica, 'Replicas');
|
||||
$running = str($replicas)->explode('/')[0];
|
||||
$total = str($replicas)->explode('/')[1];
|
||||
if ($running === $total) {
|
||||
data_set($container, 'State.Status', 'running');
|
||||
data_set($container, 'State.Health.Status', 'healthy');
|
||||
} else {
|
||||
data_set($container, 'State.Status', 'starting');
|
||||
data_set($container, 'State.Health.Status', 'unhealthy');
|
||||
}
|
||||
}
|
||||
return $container;
|
||||
});
|
||||
}
|
||||
}
|
||||
$databases = $this->server->databases();
|
||||
$services = $this->server->services()->get();
|
||||
$previews = $this->server->previews();
|
||||
$foundApplications = [];
|
||||
$foundApplicationPreviews = [];
|
||||
$foundDatabases = [];
|
||||
$foundServices = [];
|
||||
// $containers = format_docker_command_output_to_json($containers);
|
||||
// if ($containerReplicates) {
|
||||
// $containerReplicates = format_docker_command_output_to_json($containerReplicates);
|
||||
// foreach ($containerReplicates as $containerReplica) {
|
||||
// $name = data_get($containerReplica, 'Name');
|
||||
// $containers = $containers->map(function ($container) use ($name, $containerReplica) {
|
||||
// if (data_get($container, 'Spec.Name') === $name) {
|
||||
// $replicas = data_get($containerReplica, 'Replicas');
|
||||
// $running = str($replicas)->explode('/')[0];
|
||||
// $total = str($replicas)->explode('/')[1];
|
||||
// if ($running === $total) {
|
||||
// data_set($container, 'State.Status', 'running');
|
||||
// data_set($container, 'State.Health.Status', 'healthy');
|
||||
// } else {
|
||||
// data_set($container, 'State.Status', 'starting');
|
||||
// data_set($container, 'State.Health.Status', 'unhealthy');
|
||||
// }
|
||||
// }
|
||||
// return $container;
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// $databases = $this->server->databases();
|
||||
// $services = $this->server->services()->get();
|
||||
// $previews = $this->server->previews();
|
||||
// $foundApplications = [];
|
||||
// $foundApplicationPreviews = [];
|
||||
// $foundDatabases = [];
|
||||
// $foundServices = [];
|
||||
|
||||
foreach ($containers as $container) {
|
||||
if ($this->server->isSwarm()) {
|
||||
$labels = data_get($container, 'Spec.Labels');
|
||||
$uuid = data_get($labels, 'coolify.name');
|
||||
} else {
|
||||
$labels = data_get($container, 'Config.Labels');
|
||||
}
|
||||
$containerStatus = data_get($container, 'State.Status');
|
||||
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
|
||||
$containerStatus = "$containerStatus ($containerHealth)";
|
||||
$labels = Arr::undot(format_docker_labels_to_json($labels));
|
||||
$applicationId = data_get($labels, 'coolify.applicationId');
|
||||
if ($applicationId) {
|
||||
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
||||
if ($pullRequestId) {
|
||||
if (str($applicationId)->contains('-')) {
|
||||
$applicationId = str($applicationId)->before('-');
|
||||
}
|
||||
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
||||
if ($preview) {
|
||||
$foundApplicationPreviews[] = $preview->id;
|
||||
$statusFromDb = $preview->status;
|
||||
if ($statusFromDb !== $containerStatus) {
|
||||
$preview->update(['status' => $containerStatus]);
|
||||
}
|
||||
} else {
|
||||
//Notify user that this container should not be there.
|
||||
}
|
||||
} else {
|
||||
$application = $applications->where('id', $applicationId)->first();
|
||||
if ($application) {
|
||||
$foundApplications[] = $application->id;
|
||||
$statusFromDb = $application->status;
|
||||
if ($statusFromDb !== $containerStatus) {
|
||||
$application->update(['status' => $containerStatus]);
|
||||
}
|
||||
} else {
|
||||
//Notify user that this container should not be there.
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$uuid = data_get($labels, 'com.docker.compose.service');
|
||||
$type = data_get($labels, 'coolify.type');
|
||||
// foreach ($containers as $container) {
|
||||
// if ($this->server->isSwarm()) {
|
||||
// $labels = data_get($container, 'Spec.Labels');
|
||||
// $uuid = data_get($labels, 'coolify.name');
|
||||
// } else {
|
||||
// $labels = data_get($container, 'Config.Labels');
|
||||
// }
|
||||
// $containerStatus = data_get($container, 'State.Status');
|
||||
// $containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
|
||||
// $containerStatus = "$containerStatus ($containerHealth)";
|
||||
// $labels = Arr::undot(format_docker_labels_to_json($labels));
|
||||
// $applicationId = data_get($labels, 'coolify.applicationId');
|
||||
// if ($applicationId) {
|
||||
// $pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
||||
// if ($pullRequestId) {
|
||||
// if (str($applicationId)->contains('-')) {
|
||||
// $applicationId = str($applicationId)->before('-');
|
||||
// }
|
||||
// $preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
||||
// if ($preview) {
|
||||
// $foundApplicationPreviews[] = $preview->id;
|
||||
// $statusFromDb = $preview->status;
|
||||
// if ($statusFromDb !== $containerStatus) {
|
||||
// $preview->update(['status' => $containerStatus]);
|
||||
// }
|
||||
// } else {
|
||||
// //Notify user that this container should not be there.
|
||||
// }
|
||||
// } else {
|
||||
// $application = $applications->where('id', $applicationId)->first();
|
||||
// if ($application) {
|
||||
// $foundApplications[] = $application->id;
|
||||
// $statusFromDb = $application->status;
|
||||
// if ($statusFromDb !== $containerStatus) {
|
||||
// $application->update(['status' => $containerStatus]);
|
||||
// }
|
||||
// } else {
|
||||
// //Notify user that this container should not be there.
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// $uuid = data_get($labels, 'com.docker.compose.service');
|
||||
// $type = data_get($labels, 'coolify.type');
|
||||
|
||||
if ($uuid) {
|
||||
if ($type === 'service') {
|
||||
$database_id = data_get($labels, 'coolify.service.subId');
|
||||
if ($database_id) {
|
||||
$service_db = ServiceDatabase::where('id', $database_id)->first();
|
||||
if ($service_db) {
|
||||
$uuid = $service_db->service->uuid;
|
||||
$isPublic = data_get($service_db, 'is_public');
|
||||
if ($isPublic) {
|
||||
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
||||
if ($this->server->isSwarm()) {
|
||||
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||
} else {
|
||||
return data_get($value, 'Name') === "/$uuid-proxy";
|
||||
}
|
||||
})->first();
|
||||
if (!$foundTcpProxy) {
|
||||
StartDatabaseProxy::run($service_db);
|
||||
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$database = $databases->where('uuid', $uuid)->first();
|
||||
if ($database) {
|
||||
$isPublic = data_get($database, 'is_public');
|
||||
$foundDatabases[] = $database->id;
|
||||
$statusFromDb = $database->status;
|
||||
if ($statusFromDb !== $containerStatus) {
|
||||
$database->update(['status' => $containerStatus]);
|
||||
}
|
||||
if ($isPublic) {
|
||||
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
||||
if ($this->server->isSwarm()) {
|
||||
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||
} else {
|
||||
return data_get($value, 'Name') === "/$uuid-proxy";
|
||||
}
|
||||
})->first();
|
||||
if (!$foundTcpProxy) {
|
||||
StartDatabaseProxy::run($database);
|
||||
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Notify user that this container should not be there.
|
||||
}
|
||||
}
|
||||
}
|
||||
if (data_get($container, 'Name') === '/coolify-db') {
|
||||
$foundDatabases[] = 0;
|
||||
}
|
||||
}
|
||||
$serviceLabelId = data_get($labels, 'coolify.serviceId');
|
||||
if ($serviceLabelId) {
|
||||
$subType = data_get($labels, 'coolify.service.subType');
|
||||
$subId = data_get($labels, 'coolify.service.subId');
|
||||
$service = $services->where('id', $serviceLabelId)->first();
|
||||
if (!$service) {
|
||||
continue;
|
||||
}
|
||||
if ($subType === 'application') {
|
||||
$service = $service->applications()->where('id', $subId)->first();
|
||||
} else {
|
||||
$service = $service->databases()->where('id', $subId)->first();
|
||||
}
|
||||
if ($service) {
|
||||
$foundServices[] = "$service->id-$service->name";
|
||||
$statusFromDb = $service->status;
|
||||
if ($statusFromDb !== $containerStatus) {
|
||||
// ray('Updating status: ' . $containerStatus);
|
||||
$service->update(['status' => $containerStatus]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$exitedServices = collect([]);
|
||||
foreach ($services as $service) {
|
||||
$apps = $service->applications()->get();
|
||||
$dbs = $service->databases()->get();
|
||||
foreach ($apps as $app) {
|
||||
if (in_array("$app->id-$app->name", $foundServices)) {
|
||||
continue;
|
||||
} else {
|
||||
$exitedServices->push($app);
|
||||
}
|
||||
}
|
||||
foreach ($dbs as $db) {
|
||||
if (in_array("$db->id-$db->name", $foundServices)) {
|
||||
continue;
|
||||
} else {
|
||||
$exitedServices->push($db);
|
||||
}
|
||||
}
|
||||
}
|
||||
$exitedServices = $exitedServices->unique('id');
|
||||
foreach ($exitedServices as $exitedService) {
|
||||
if (str($exitedService->status)->startsWith('exited')) {
|
||||
continue;
|
||||
}
|
||||
$name = data_get($exitedService, 'name');
|
||||
$fqdn = data_get($exitedService, 'fqdn');
|
||||
$containerName = $name ? "$name, available at $fqdn" : $fqdn;
|
||||
$projectUuid = data_get($service, 'environment.project.uuid');
|
||||
$serviceUuid = data_get($service, 'uuid');
|
||||
$environmentName = data_get($service, 'environment.name');
|
||||
// if ($uuid) {
|
||||
// if ($type === 'service') {
|
||||
// $database_id = data_get($labels, 'coolify.service.subId');
|
||||
// if ($database_id) {
|
||||
// $service_db = ServiceDatabase::where('id', $database_id)->first();
|
||||
// if ($service_db) {
|
||||
// $uuid = $service_db->service->uuid;
|
||||
// $isPublic = data_get($service_db, 'is_public');
|
||||
// if ($isPublic) {
|
||||
// $foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
||||
// if ($this->server->isSwarm()) {
|
||||
// return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||
// } else {
|
||||
// return data_get($value, 'Name') === "/$uuid-proxy";
|
||||
// }
|
||||
// })->first();
|
||||
// if (!$foundTcpProxy) {
|
||||
// StartDatabaseProxy::run($service_db);
|
||||
// // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// $database = $databases->where('uuid', $uuid)->first();
|
||||
// if ($database) {
|
||||
// $isPublic = data_get($database, 'is_public');
|
||||
// $foundDatabases[] = $database->id;
|
||||
// $statusFromDb = $database->status;
|
||||
// if ($statusFromDb !== $containerStatus) {
|
||||
// $database->update(['status' => $containerStatus]);
|
||||
// }
|
||||
// if ($isPublic) {
|
||||
// $foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
||||
// if ($this->server->isSwarm()) {
|
||||
// return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||
// } else {
|
||||
// return data_get($value, 'Name') === "/$uuid-proxy";
|
||||
// }
|
||||
// })->first();
|
||||
// if (!$foundTcpProxy) {
|
||||
// StartDatabaseProxy::run($database);
|
||||
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// // Notify user that this container should not be there.
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if (data_get($container, 'Name') === '/coolify-db') {
|
||||
// $foundDatabases[] = 0;
|
||||
// }
|
||||
// }
|
||||
// $serviceLabelId = data_get($labels, 'coolify.serviceId');
|
||||
// if ($serviceLabelId) {
|
||||
// $subType = data_get($labels, 'coolify.service.subType');
|
||||
// $subId = data_get($labels, 'coolify.service.subId');
|
||||
// $service = $services->where('id', $serviceLabelId)->first();
|
||||
// if (!$service) {
|
||||
// continue;
|
||||
// }
|
||||
// if ($subType === 'application') {
|
||||
// $service = $service->applications()->where('id', $subId)->first();
|
||||
// } else {
|
||||
// $service = $service->databases()->where('id', $subId)->first();
|
||||
// }
|
||||
// if ($service) {
|
||||
// $foundServices[] = "$service->id-$service->name";
|
||||
// $statusFromDb = $service->status;
|
||||
// if ($statusFromDb !== $containerStatus) {
|
||||
// // ray('Updating status: ' . $containerStatus);
|
||||
// $service->update(['status' => $containerStatus]);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// $exitedServices = collect([]);
|
||||
// foreach ($services as $service) {
|
||||
// $apps = $service->applications()->get();
|
||||
// $dbs = $service->databases()->get();
|
||||
// foreach ($apps as $app) {
|
||||
// if (in_array("$app->id-$app->name", $foundServices)) {
|
||||
// continue;
|
||||
// } else {
|
||||
// $exitedServices->push($app);
|
||||
// }
|
||||
// }
|
||||
// foreach ($dbs as $db) {
|
||||
// if (in_array("$db->id-$db->name", $foundServices)) {
|
||||
// continue;
|
||||
// } else {
|
||||
// $exitedServices->push($db);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// $exitedServices = $exitedServices->unique('id');
|
||||
// foreach ($exitedServices as $exitedService) {
|
||||
// if (str($exitedService->status)->startsWith('exited')) {
|
||||
// continue;
|
||||
// }
|
||||
// $name = data_get($exitedService, 'name');
|
||||
// $fqdn = data_get($exitedService, 'fqdn');
|
||||
// $containerName = $name ? "$name, available at $fqdn" : $fqdn;
|
||||
// $projectUuid = data_get($service, 'environment.project.uuid');
|
||||
// $serviceUuid = data_get($service, 'uuid');
|
||||
// $environmentName = data_get($service, 'environment.name');
|
||||
|
||||
if ($projectUuid && $serviceUuid && $environmentName) {
|
||||
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/service/" . $serviceUuid;
|
||||
} else {
|
||||
$url = null;
|
||||
}
|
||||
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
$exitedService->update(['status' => 'exited']);
|
||||
}
|
||||
// if ($projectUuid && $serviceUuid && $environmentName) {
|
||||
// $url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/service/" . $serviceUuid;
|
||||
// } else {
|
||||
// $url = null;
|
||||
// }
|
||||
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
// $exitedService->update(['status' => 'exited']);
|
||||
// }
|
||||
|
||||
$notRunningApplications = $applications->pluck('id')->diff($foundApplications);
|
||||
foreach ($notRunningApplications as $applicationId) {
|
||||
$application = $applications->where('id', $applicationId)->first();
|
||||
if (str($application->status)->startsWith('exited')) {
|
||||
continue;
|
||||
}
|
||||
$application->update(['status' => 'exited']);
|
||||
// $notRunningApplications = $applications->pluck('id')->diff($foundApplications);
|
||||
// foreach ($notRunningApplications as $applicationId) {
|
||||
// $application = $applications->where('id', $applicationId)->first();
|
||||
// if (str($application->status)->startsWith('exited')) {
|
||||
// continue;
|
||||
// }
|
||||
// $application->update(['status' => 'exited']);
|
||||
|
||||
$name = data_get($application, 'name');
|
||||
$fqdn = data_get($application, 'fqdn');
|
||||
// $name = data_get($application, 'name');
|
||||
// $fqdn = data_get($application, 'fqdn');
|
||||
|
||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||
// $containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||
|
||||
$projectUuid = data_get($application, 'environment.project.uuid');
|
||||
$applicationUuid = data_get($application, 'uuid');
|
||||
$environment = data_get($application, 'environment.name');
|
||||
// $projectUuid = data_get($application, 'environment.project.uuid');
|
||||
// $applicationUuid = data_get($application, 'uuid');
|
||||
// $environment = data_get($application, 'environment.name');
|
||||
|
||||
if ($projectUuid && $applicationUuid && $environment) {
|
||||
$url = base_url() . '/project/' . $projectUuid . "/" . $environment . "/application/" . $applicationUuid;
|
||||
} else {
|
||||
$url = null;
|
||||
}
|
||||
// if ($projectUuid && $applicationUuid && $environment) {
|
||||
// $url = base_url() . '/project/' . $projectUuid . "/" . $environment . "/application/" . $applicationUuid;
|
||||
// } else {
|
||||
// $url = null;
|
||||
// }
|
||||
|
||||
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
}
|
||||
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
||||
foreach ($notRunningApplicationPreviews as $previewId) {
|
||||
$preview = $previews->where('id', $previewId)->first();
|
||||
if (str($preview->status)->startsWith('exited')) {
|
||||
continue;
|
||||
}
|
||||
$preview->update(['status' => 'exited']);
|
||||
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
// }
|
||||
// $notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
||||
// foreach ($notRunningApplicationPreviews as $previewId) {
|
||||
// $preview = $previews->where('id', $previewId)->first();
|
||||
// if (str($preview->status)->startsWith('exited')) {
|
||||
// continue;
|
||||
// }
|
||||
// $preview->update(['status' => 'exited']);
|
||||
|
||||
$name = data_get($preview, 'name');
|
||||
$fqdn = data_get($preview, 'fqdn');
|
||||
// $name = data_get($preview, 'name');
|
||||
// $fqdn = data_get($preview, 'fqdn');
|
||||
|
||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||
// $containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||
|
||||
$projectUuid = data_get($preview, 'application.environment.project.uuid');
|
||||
$environmentName = data_get($preview, 'application.environment.name');
|
||||
$applicationUuid = data_get($preview, 'application.uuid');
|
||||
// $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;
|
||||
} else {
|
||||
$url = null;
|
||||
}
|
||||
// if ($projectUuid && $applicationUuid && $environmentName) {
|
||||
// $url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/application/" . $applicationUuid;
|
||||
// } else {
|
||||
// $url = null;
|
||||
// }
|
||||
|
||||
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
}
|
||||
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
||||
foreach ($notRunningDatabases as $database) {
|
||||
$database = $databases->where('id', $database)->first();
|
||||
if (str($database->status)->startsWith('exited')) {
|
||||
continue;
|
||||
}
|
||||
$database->update(['status' => 'exited']);
|
||||
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
// }
|
||||
// $notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
||||
// foreach ($notRunningDatabases as $database) {
|
||||
// $database = $databases->where('id', $database)->first();
|
||||
// if (str($database->status)->startsWith('exited')) {
|
||||
// continue;
|
||||
// }
|
||||
// $database->update(['status' => 'exited']);
|
||||
|
||||
$name = data_get($database, 'name');
|
||||
$fqdn = data_get($database, 'fqdn');
|
||||
// $name = data_get($database, 'name');
|
||||
// $fqdn = data_get($database, 'fqdn');
|
||||
|
||||
$containerName = $name;
|
||||
// $containerName = $name;
|
||||
|
||||
$projectUuid = data_get($database, 'environment.project.uuid');
|
||||
$environmentName = data_get($database, 'environment.name');
|
||||
$databaseUuid = data_get($database, 'uuid');
|
||||
// $projectUuid = data_get($database, 'environment.project.uuid');
|
||||
// $environmentName = data_get($database, 'environment.name');
|
||||
// $databaseUuid = data_get($database, 'uuid');
|
||||
|
||||
if ($projectUuid && $databaseUuid && $environmentName) {
|
||||
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/database/" . $databaseUuid;
|
||||
} else {
|
||||
$url = null;
|
||||
}
|
||||
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
}
|
||||
// if ($projectUuid && $databaseUuid && $environmentName) {
|
||||
// $url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/database/" . $databaseUuid;
|
||||
// } else {
|
||||
// $url = null;
|
||||
// }
|
||||
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
// }
|
||||
|
||||
// Check if proxy is running
|
||||
$this->server->proxyType();
|
||||
$foundProxyContainer = $containers->filter(function ($value, $key) {
|
||||
if ($this->server->isSwarm()) {
|
||||
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
||||
} else {
|
||||
return data_get($value, 'Name') === '/coolify-proxy';
|
||||
}
|
||||
})->first();
|
||||
if (!$foundProxyContainer) {
|
||||
try {
|
||||
$shouldStart = CheckProxy::run($this->server);
|
||||
if ($shouldStart) {
|
||||
StartProxy::run($this->server, false);
|
||||
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
}
|
||||
} else {
|
||||
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
|
||||
$this->server->save();
|
||||
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
||||
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
|
||||
ray($e->getMessage());
|
||||
return handleError($e);
|
||||
}
|
||||
// // Check if proxy is running
|
||||
// $this->server->proxyType();
|
||||
// $foundProxyContainer = $containers->filter(function ($value, $key) {
|
||||
// if ($this->server->isSwarm()) {
|
||||
// return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
||||
// } else {
|
||||
// return data_get($value, 'Name') === '/coolify-proxy';
|
||||
// }
|
||||
// })->first();
|
||||
// if (!$foundProxyContainer) {
|
||||
// try {
|
||||
// $shouldStart = CheckProxy::run($this->server);
|
||||
// if ($shouldStart) {
|
||||
// StartProxy::run($this->server, false);
|
||||
// $this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||
// }
|
||||
// } catch (\Throwable $e) {
|
||||
// ray($e);
|
||||
// }
|
||||
// } else {
|
||||
// $this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
|
||||
// $this->server->save();
|
||||
// $connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
||||
// instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||
// }
|
||||
// } catch (\Throwable $e) {
|
||||
// send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
|
||||
// ray($e->getMessage());
|
||||
// return handleError($e);
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Actions\Server\StartSentinel;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class PullSentinelImageJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $timeout = 1000;
|
||||
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping($this->server->uuid))];
|
||||
}
|
||||
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return $this->server->uuid;
|
||||
}
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
$version = get_latest_sentinel_version();
|
||||
if (!$version) {
|
||||
ray('Failed to get latest Sentinel version');
|
||||
return;
|
||||
}
|
||||
$local_version = instant_remote_process(['docker exec coolify-sentinel sh -c "curl http://127.0.0.1:8888/api/version"'], $this->server, false);
|
||||
if (empty($local_version)) {
|
||||
$local_version = '0.0.0';
|
||||
}
|
||||
if (version_compare($local_version, $version, '<')) {
|
||||
StartSentinel::run($this->server, $version, true);
|
||||
return;
|
||||
}
|
||||
ray('Sentinel image is up to date');
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('PullSentinelImageJob failed with: ' . $e->getMessage());
|
||||
ray($e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,9 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if ($this->server->isFunctional()) {
|
||||
$this->cleanup(notify: false);
|
||||
$this->removeCoolifyYaml();
|
||||
if (config('coolify.is_sentinel_enabled')) {
|
||||
$this->server->checkSentinel();
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('ServerStatusJob failed with: ' . $e->getMessage());
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Livewire\Project\Application;
|
||||
|
||||
use App\Actions\Application\StopApplication;
|
||||
use App\Actions\Docker\GetContainersStatus;
|
||||
use App\Events\ApplicationStatusChanged;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Jobs\ServerStatusJob;
|
||||
@@ -32,7 +33,8 @@ class Heading extends Component
|
||||
public function check_status($showNotification = false)
|
||||
{
|
||||
if ($this->application->destination->server->isFunctional()) {
|
||||
dispatch(new ContainerStatusJob($this->application->destination->server));
|
||||
GetContainersStatus::dispatch($this->application->destination->server);
|
||||
// dispatch(new ContainerStatusJob($this->application->destination->server));
|
||||
} else {
|
||||
dispatch(new ServerStatusJob($this->application->destination->server));
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ use App\Actions\Database\StartMysql;
|
||||
use App\Actions\Database\StartPostgresql;
|
||||
use App\Actions\Database\StartRedis;
|
||||
use App\Actions\Database\StopDatabase;
|
||||
use App\Actions\Docker\GetContainersStatus;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use Livewire\Component;
|
||||
|
||||
@@ -44,7 +45,8 @@ class Heading extends Component
|
||||
|
||||
public function check_status($showNotification = false)
|
||||
{
|
||||
dispatch_sync(new ContainerStatusJob($this->database->destination->server));
|
||||
GetContainersStatus::run($this->database->destination->server);
|
||||
// dispatch_sync(new ContainerStatusJob($this->database->destination->server));
|
||||
$this->database->refresh();
|
||||
if ($showNotification) $this->dispatch('success', 'Database status updated.');
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ class GithubPrivateRepository extends Component
|
||||
'repository_project_id' => $this->selected_repository_id,
|
||||
'git_repository' => "{$this->selected_repository_owner}/{$this->selected_repository_repo}",
|
||||
'git_branch' => $this->selected_branch_name,
|
||||
'build_pack' => 'nixpacks',
|
||||
'build_pack' => $this->build_pack,
|
||||
'ports_exposes' => $this->port,
|
||||
'publish_directory' => $this->publish_directory,
|
||||
'environment_id' => $environment->id,
|
||||
@@ -162,6 +162,9 @@ class GithubPrivateRepository extends Component
|
||||
$application->settings->is_static = $this->is_static;
|
||||
$application->settings->save();
|
||||
|
||||
if ($this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
|
||||
$application->health_check_enabled = false;
|
||||
}
|
||||
$fqdn = generateFqdn($destination->server, $application->uuid);
|
||||
$application->fqdn = $fqdn;
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
public $current_step = 'private_keys';
|
||||
public $parameters;
|
||||
public $query;
|
||||
public $private_keys =[];
|
||||
public $private_keys = [];
|
||||
public int $private_key_id;
|
||||
|
||||
public int $port = 3000;
|
||||
@@ -125,7 +125,7 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
'name' => generate_random_name(),
|
||||
'git_repository' => $this->git_repository,
|
||||
'git_branch' => $this->branch,
|
||||
'build_pack' => 'nixpacks',
|
||||
'build_pack' => $this->build_pack,
|
||||
'ports_exposes' => $this->port,
|
||||
'publish_directory' => $this->publish_directory,
|
||||
'environment_id' => $environment->id,
|
||||
@@ -138,7 +138,7 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
'name' => generate_random_name(),
|
||||
'git_repository' => $this->git_repository,
|
||||
'git_branch' => $this->branch,
|
||||
'build_pack' => 'nixpacks',
|
||||
'build_pack' => $this->build_pack,
|
||||
'ports_exposes' => $this->port,
|
||||
'publish_directory' => $this->publish_directory,
|
||||
'environment_id' => $environment->id,
|
||||
@@ -149,7 +149,9 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
'source_type' => $this->git_source->getMorphClass()
|
||||
];
|
||||
}
|
||||
|
||||
if ($this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
|
||||
$application_init['health_check_enabled'] = false;
|
||||
}
|
||||
$application = Application::create($application_init);
|
||||
$application->settings->is_static = $this->is_static;
|
||||
$application->settings->save();
|
||||
|
||||
@@ -205,6 +205,9 @@ class PublicGitRepository extends Component
|
||||
];
|
||||
}
|
||||
|
||||
if ($this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
|
||||
$application_init['health_check_enabled'] = false;
|
||||
}
|
||||
$application = Application::create($application_init);
|
||||
|
||||
$application->settings->is_static = $this->is_static;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Livewire\Project\Service;
|
||||
|
||||
use App\Actions\Docker\GetContainersStatus;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Models\Service;
|
||||
use Livewire\Component;
|
||||
@@ -64,7 +65,8 @@ class Configuration extends Component
|
||||
public function check_status()
|
||||
{
|
||||
try {
|
||||
dispatch_sync(new ContainerStatusJob($this->service->server));
|
||||
GetContainersStatus::run($this->service->server);
|
||||
// dispatch_sync(new ContainerStatusJob($this->service->server));
|
||||
$this->dispatch('refresh')->self();
|
||||
$this->dispatch('updateStatus');
|
||||
} catch (\Exception $e) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Livewire\Project\Shared;
|
||||
|
||||
use App\Actions\Application\StopApplicationOneServer;
|
||||
use App\Actions\Docker\GetContainersStatus;
|
||||
use App\Events\ApplicationStatusChanged;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Models\Server;
|
||||
@@ -90,7 +91,8 @@ class Destination extends Component
|
||||
}
|
||||
public function refreshServers()
|
||||
{
|
||||
ContainerStatusJob::dispatchSync($this->resource->destination->server);
|
||||
GetContainersStatus::run($this->resource->destination->server);
|
||||
// ContainerStatusJob::dispatchSync($this->resource->destination->server);
|
||||
$this->loadData();
|
||||
$this->dispatch('refresh');
|
||||
ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id'));
|
||||
|
||||
@@ -122,7 +122,7 @@ class ExecuteContainerCommand extends Component
|
||||
if ($server->isForceDisabled()) {
|
||||
throw new \RuntimeException('Server is disabled.');
|
||||
}
|
||||
$cmd = 'sh -c "if [ -f ~/.profile ]; then . ~/.profile; fi; ' . str_replace('"', '\"', $this->command) . '"';
|
||||
$cmd = "sh -c 'if [ -f ~/.profile ]; then . ~/.profile; fi; " . str_replace("'", "'\''", $this->command) . "'";
|
||||
if (!empty($this->workDir)) {
|
||||
$exec = "docker exec -w {$this->workDir} {$container_name} {$cmd}";
|
||||
} else {
|
||||
|
||||
@@ -27,7 +27,7 @@ class Logs extends Component
|
||||
public $query;
|
||||
public $status;
|
||||
public $serviceSubType;
|
||||
|
||||
public $cpu;
|
||||
public function loadContainers($server_id)
|
||||
{
|
||||
try {
|
||||
@@ -49,6 +49,14 @@ class Logs extends Component
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function loadMetrics()
|
||||
{
|
||||
return;
|
||||
$server = data_get($this->resource, 'destination.server');
|
||||
if ($server->isFunctional()) {
|
||||
$this->cpu = $server->getMetrics();
|
||||
}
|
||||
}
|
||||
public function mount()
|
||||
{
|
||||
try {
|
||||
@@ -95,6 +103,7 @@ class Logs extends Component
|
||||
}
|
||||
}
|
||||
$this->containers = $this->containers->sort();
|
||||
$this->loadMetrics();
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Livewire\Server\Proxy;
|
||||
|
||||
use App\Actions\Docker\GetContainersStatus;
|
||||
use App\Actions\Proxy\CheckProxy;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Models\Server;
|
||||
@@ -49,7 +50,8 @@ class Status extends Component
|
||||
public function getProxyStatus()
|
||||
{
|
||||
try {
|
||||
dispatch_sync(new ContainerStatusJob($this->server));
|
||||
GetContainersStatus::run($this->server);
|
||||
// dispatch_sync(new ContainerStatusJob($this->server));
|
||||
$this->dispatch('proxyStatusUpdated');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
|
||||
@@ -26,6 +26,7 @@ class Deployments extends Component
|
||||
"server_id",
|
||||
"status"
|
||||
])->sortBy('id')->groupBy('server_name')->toArray();
|
||||
$this->dispatch('deployments', $this->deployments_per_tag_per_server);
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,12 @@ class Index extends Component
|
||||
public $webhook = null;
|
||||
public $deployments_per_tag_per_server = [];
|
||||
|
||||
protected $listeners = ['deployments' => 'update_deployments'];
|
||||
|
||||
public function update_deployments($deployments)
|
||||
{
|
||||
$this->deployments_per_tag_per_server = $deployments;
|
||||
}
|
||||
public function tag_updated()
|
||||
{
|
||||
if ($this->tag == "") {
|
||||
@@ -39,14 +45,13 @@ class Index extends Component
|
||||
public function redeploy_all()
|
||||
{
|
||||
try {
|
||||
$message = collect([]);
|
||||
$this->applications->each(function ($resource) use ($message) {
|
||||
$this->applications->each(function ($resource){
|
||||
$deploy = new Deploy();
|
||||
$message->push($deploy->deploy_resource($resource));
|
||||
$deploy->deploy_resource($resource);
|
||||
});
|
||||
$this->services->each(function ($resource) use ($message) {
|
||||
$this->services->each(function ($resource) {
|
||||
$deploy = new Deploy();
|
||||
$message->push($deploy->deploy_resource($resource));
|
||||
$deploy->deploy_resource($resource);
|
||||
});
|
||||
$this->dispatch('success', 'Mass deployment started.');
|
||||
} catch (\Exception $e) {
|
||||
|
||||
+63
-1
@@ -3,11 +3,14 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Actions\Server\InstallDocker;
|
||||
use App\Actions\Server\StartSentinel;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Jobs\PullSentinelImageJob;
|
||||
use App\Notifications\Server\Revived;
|
||||
use App\Notifications\Server\Unreachable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
||||
@@ -462,6 +465,36 @@ $schema://$host {
|
||||
Storage::disk('ssh-keys')->delete($sshKeyFileLocation);
|
||||
Storage::disk('ssh-mux')->delete($this->muxFilename());
|
||||
}
|
||||
public function checkSentinel()
|
||||
{
|
||||
ray("Checking sentinel on server: {$this->name}");
|
||||
if ($this->is_metrics_enabled) {
|
||||
$sentinel_found = instant_remote_process(["docker inspect coolify-sentinel"], $this, false);
|
||||
$sentinel_found = json_decode($sentinel_found, true);
|
||||
$status = data_get($sentinel_found, '0.State.Status', 'exited');
|
||||
if ($status !== 'running') {
|
||||
ray('Sentinel is not running, starting it...');
|
||||
PullSentinelImageJob::dispatch($this);
|
||||
} else {
|
||||
ray('Sentinel is running');
|
||||
}
|
||||
}
|
||||
}
|
||||
public function getMetrics()
|
||||
{
|
||||
if ($this->is_metrics_enabled) {
|
||||
$from = now()->subMinutes(5)->toIso8601ZuluString();
|
||||
$cpu = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl http://localhost:8888/api/cpu/history?from=$from'"], $this, false);
|
||||
$cpu = str($cpu)->explode("\n")->skip(1)->all();
|
||||
$parsedCollection = collect($cpu)->flatMap(function ($item) {
|
||||
return collect(explode("\n", trim($item)))->map(function ($line) {
|
||||
list($time, $value) = explode(',', trim($line));
|
||||
return [(int) $time, (float) $value];
|
||||
});
|
||||
})->toArray();
|
||||
return $parsedCollection;
|
||||
}
|
||||
}
|
||||
public function isServerReady(int $tries = 3)
|
||||
{
|
||||
if ($this->skipServer()) {
|
||||
@@ -548,7 +581,36 @@ $schema://$host {
|
||||
{
|
||||
return instant_remote_process(["docker start $id"], $this);
|
||||
}
|
||||
public function loadUnmanagedContainers()
|
||||
public function getContainers(): Collection
|
||||
{
|
||||
$sentinel_found = instant_remote_process(["docker inspect coolify-sentinel"], $this, false);
|
||||
$sentinel_found = json_decode($sentinel_found, true);
|
||||
$status = data_get($sentinel_found, '0.State.Status', 'exited');
|
||||
if ($status === 'running') {
|
||||
$containers = instant_remote_process(['docker exec coolify-sentinel sh -c "curl http://127.0.0.1:8888/api/containers"'], $this, false);
|
||||
if (is_null($containers)) {
|
||||
return collect([]);
|
||||
}
|
||||
$containers = data_get(json_decode($containers, true), 'containers', []);
|
||||
return collect($containers);
|
||||
} else {
|
||||
if ($this->isSwarm()) {
|
||||
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this, false);
|
||||
} else {
|
||||
$containers = instant_remote_process(["docker container ls -q"], $this, false);
|
||||
if (!$containers) {
|
||||
return collect([]);
|
||||
}
|
||||
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this, false);
|
||||
}
|
||||
if (is_null($containers)) {
|
||||
return collect([]);
|
||||
}
|
||||
|
||||
return format_docker_command_output_to_json($containers);
|
||||
}
|
||||
}
|
||||
public function loadUnmanagedContainers(): Collection
|
||||
{
|
||||
if ($this->isFunctional()) {
|
||||
$containers = instant_remote_process(["docker ps -a --format '{{json .}}'"], $this);
|
||||
|
||||
+32
-8
@@ -171,7 +171,7 @@ class Service extends BaseModel
|
||||
],
|
||||
]);
|
||||
}
|
||||
$fields->put('Tolgee', $data);
|
||||
$fields->put('Tolgee', $data->toArray());
|
||||
break;
|
||||
case str($image)?->contains('logto'):
|
||||
$data = collect([]);
|
||||
@@ -195,7 +195,7 @@ class Service extends BaseModel
|
||||
],
|
||||
]);
|
||||
}
|
||||
$fields->put('Logto', $data);
|
||||
$fields->put('Logto', $data->toArray());
|
||||
break;
|
||||
case str($image)?->contains('unleash-server'):
|
||||
$data = collect([]);
|
||||
@@ -218,7 +218,7 @@ class Service extends BaseModel
|
||||
],
|
||||
]);
|
||||
}
|
||||
$fields->put('Unleash', $data);
|
||||
$fields->put('Unleash', $data->toArray());
|
||||
break;
|
||||
case str($image)?->contains('grafana'):
|
||||
$data = collect([]);
|
||||
@@ -241,7 +241,7 @@ class Service extends BaseModel
|
||||
],
|
||||
]);
|
||||
}
|
||||
$fields->put('Grafana', $data);
|
||||
$fields->put('Grafana', $data->toArray());
|
||||
break;
|
||||
case str($image)?->contains('directus'):
|
||||
$data = collect([]);
|
||||
@@ -267,7 +267,7 @@ class Service extends BaseModel
|
||||
],
|
||||
]);
|
||||
}
|
||||
$fields->put('Directus', $data);
|
||||
$fields->put('Directus', $data->toArray());
|
||||
break;
|
||||
case str($image)?->contains('kong'):
|
||||
$data = collect([]);
|
||||
@@ -370,7 +370,7 @@ class Service extends BaseModel
|
||||
],
|
||||
]);
|
||||
}
|
||||
$fields->put('Weblate', $data);
|
||||
$fields->put('Weblate', $data->toArray());
|
||||
break;
|
||||
case str($image)?->contains('meilisearch'):
|
||||
$data = collect([]);
|
||||
@@ -384,7 +384,7 @@ class Service extends BaseModel
|
||||
],
|
||||
]);
|
||||
}
|
||||
$fields->put('Meilisearch', $data);
|
||||
$fields->put('Meilisearch', $data->toArray());
|
||||
break;
|
||||
case str($image)?->contains('ghost'):
|
||||
$data = collect([]);
|
||||
@@ -444,7 +444,31 @@ class Service extends BaseModel
|
||||
]);
|
||||
}
|
||||
|
||||
$fields->put('Ghost', $data);
|
||||
$fields->put('Ghost', $data->toArray());
|
||||
break;
|
||||
default:
|
||||
$data = collect([]);
|
||||
$admin_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first();
|
||||
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first();
|
||||
$data = $data->merge([
|
||||
'User' => [
|
||||
'key' => 'SERVICE_USER_ADMIN',
|
||||
'value' => data_get($admin_user, 'value', 'admin'),
|
||||
'readonly' => true,
|
||||
'rules' => 'required',
|
||||
],
|
||||
]);
|
||||
if ($admin_password) {
|
||||
$data = $data->merge([
|
||||
'Password' => [
|
||||
'key' => 'SERVICE_PASSWORD_ADMIN',
|
||||
'value' => data_get($admin_password, 'value'),
|
||||
'rules' => 'required',
|
||||
'isPassword' => true,
|
||||
],
|
||||
]);
|
||||
}
|
||||
$fields->put('Admin', $data->toArray());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Notifications\Server;
|
||||
|
||||
use App\Actions\Docker\GetContainersStatus;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Bus\Queueable;
|
||||
@@ -22,7 +23,8 @@ class Revived extends Notification implements ShouldQueue
|
||||
if ($this->server->unreachable_notification_sent === false) {
|
||||
return;
|
||||
}
|
||||
dispatch(new ContainerStatusJob($server));
|
||||
GetContainersStatus::dispatch($server);
|
||||
// dispatch(new ContainerStatusJob($server));
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
|
||||
@@ -147,6 +147,18 @@ function get_route_parameters(): array
|
||||
return Route::current()->parameters();
|
||||
}
|
||||
|
||||
function get_latest_sentinel_version(): string
|
||||
{
|
||||
try {
|
||||
$response = Http::get('https://cdn.coollabs.io/coolify/versions.json');
|
||||
$versions = $response->json();
|
||||
return data_get($versions, 'coolify.sentinel.version');
|
||||
} catch (\Throwable $e) {
|
||||
//throw $e;
|
||||
ray($e->getMessage());
|
||||
return '0.0.0';
|
||||
}
|
||||
}
|
||||
function get_latest_version_of_coolify(): string
|
||||
{
|
||||
try {
|
||||
@@ -637,7 +649,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
$allServices = getServiceTemplates();
|
||||
$topLevelVolumes = collect(data_get($yaml, 'volumes', []));
|
||||
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
|
||||
$dockerComposeVersion = data_get($yaml, 'version') ?? '3.8';
|
||||
$services = data_get($yaml, 'services');
|
||||
|
||||
$generatedServiceFQDNS = collect([]);
|
||||
@@ -1192,7 +1203,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
return $service;
|
||||
});
|
||||
$finalServices = [
|
||||
'version' => $dockerComposeVersion,
|
||||
'services' => $services->toArray(),
|
||||
'volumes' => $topLevelVolumes->toArray(),
|
||||
'networks' => $topLevelNetworks->toArray(),
|
||||
@@ -1230,7 +1240,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
$topLevelVolumes = collect([]);
|
||||
}
|
||||
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
|
||||
$dockerComposeVersion = data_get($yaml, 'version') ?? '3.8';
|
||||
$services = data_get($yaml, 'services');
|
||||
|
||||
$generatedServiceFQDNS = collect([]);
|
||||
@@ -1661,7 +1670,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
});
|
||||
}
|
||||
$finalServices = [
|
||||
'version' => $dockerComposeVersion,
|
||||
'services' => $services->toArray(),
|
||||
'volumes' => $topLevelVolumes->toArray(),
|
||||
'networks' => $topLevelNetworks->toArray(),
|
||||
|
||||
@@ -14,4 +14,5 @@ return [
|
||||
'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper:latest'),
|
||||
'is_horizon_enabled' => env('HORIZON_ENABLED', true),
|
||||
'is_scheduler_enabled' => env('SCHEDULER_ENABLED', true),
|
||||
'is_sentinel_enabled' => env('SENTINEL_ENABLED', false),
|
||||
];
|
||||
+1
-1
@@ -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.276',
|
||||
'release' => '4.0.0-beta.277',
|
||||
// When left empty or `null` the Laravel environment will be used
|
||||
'environment' => config('app.env'),
|
||||
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
<?php
|
||||
|
||||
return '4.0.0-beta.276';
|
||||
return '4.0.0-beta.277';
|
||||
@@ -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('servers', function (Blueprint $table) {
|
||||
$table->boolean('is_metrics_enabled')->default(true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('servers', function (Blueprint $table) {
|
||||
$table->dropColumn('is_metrics_enabled');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,3 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
coolify:
|
||||
build:
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
coolify:
|
||||
image: "ghcr.io/coollabsio/coolify:${LATEST_IMAGE:-latest}"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
coolify-testing-host:
|
||||
init: true
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
coolify:
|
||||
container_name: coolify
|
||||
@@ -11,7 +10,6 @@ services:
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
container_name: coolify-db
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
$handle = fopen("/tmp/export.csv", "w");
|
||||
App\Models\Team::chunk(100, function ($teams) use ($handle) {
|
||||
foreach ($teams as $team) {
|
||||
if ($team->subscription->stripe_invoice_paid == true) {
|
||||
foreach ($team->members as $member) {
|
||||
fputcsv($handle, [$member->email, $member->name], ",");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
fclose($handle);
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="163.03" height="30.38" viewBox="0 0 43.135 8.038" xmlns:v="https://vecta.io/nano"><circle cx="4.019" cy="4.019" r="3.149" fill="#fff" stroke="#0055d4" stroke-width="1.74"/><path d="M11.457 7.303q-.566 0-.879-.322-.313-.331-.313-.932V.712L11.5.572v5.442q0 .305.253.305.139 0 .244-.052l.253.879q-.357.157-.792.157zm2.619-4.754v4.615H12.84V2.549zM13.449.172q.331 0 .54.209.218.2.218.514 0 .313-.218.522-.209.2-.54.2-.331 0-.54-.2-.209-.209-.209-.522 0-.313.209-.514.209-.209.54-.209zm3.319 2.238q.975 0 1.672.557l-.47.705q-.583-.366-1.149-.366-.305 0-.47.113-.165.113-.165.305 0 .139.07.235.078.096.279.183.209.087.618.209.731.2 1.088.54.357.331.357.914 0 .462-.27.801-.261.34-.714.522-.453.174-1.01.174-.583 0-1.062-.174-.479-.183-.819-.496l.61-.679q.583.453 1.237.453.348 0 .549-.131.209-.139.209-.374 0-.183-.078-.287-.078-.104-.287-.192-.209-.096-.653-.218-.697-.192-1.036-.54-.331-.357-.331-.879 0-.392.226-.705.226-.313.636-.488.418-.183.967-.183zm5.342 4.536q-.253.174-.575.261-.313.096-.627.096-.714-.009-1.08-.409-.366-.401-.366-1.176V3.42h-.688v-.871h.688v-1.01l1.237-.148v1.158h1.062l-.122.871h-.94v2.273q0 .331.113.479.113.148.348.148.235 0 .522-.157zm5.493-4.536q.549 0 .879.374.34.374.34 1.019v3.361h-1.237V4.012q0-.679-.453-.679-.244 0-.427.157-.183.157-.374.488v3.187h-1.237V4.012q0-.679-.453-.679-.244 0-.427.165-.183.157-.366.479v3.187h-1.237V2.549h1.071l.096.575q.261-.348.583-.531.331-.183.758-.183.392 0 .679.2.287.192.418.549.287-.374.618-.557.34-.192.766-.192zm4.148 0q1.036 0 1.62.653.583.644.583 1.794 0 .731-.27 1.289-.261.549-.766.853-.496.305-1.176.305-1.036 0-1.628-.644-.583-.653-.583-1.803 0-.731.261-1.28.27-.557.766-.862.505-.305 1.193-.305zm0 .923q-.47 0-.705.374-.226.366-.226 1.149 0 .784.226 1.158.235.366.697.366.462 0 .688-.366.235-.374.235-1.158 0-.784-.226-1.149-.226-.374-.688-.374zm5.271-.923q.61 0 .949.374.34.366.34 1.019v3.361h-1.237V4.012q0-.374-.131-.522-.122-.157-.374-.157-.261 0-.479.165-.209.157-.409.479v3.187h-1.237V2.549h1.071l.096.583q.287-.357.627-.54.348-.183.784-.183zM40.2.572v6.592h-1.237V.712zm2.804 1.977l-1.472 2.029 1.602 2.586h-1.402l-1.489-2.525 1.48-2.09z"/></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -203,7 +203,7 @@ tr td:first-child {
|
||||
@apply flex lg:flex-row flex-col p-2 transition-colors cursor-pointer min-h-[4rem] dark:bg-coolgray-100 dark:text-white bg-neutral-50 border border-neutral-200 dark:border-black hover:bg-neutral-100 dark:hover:bg-coollabs-100 dark:hover:text-white hover:text-black hover:no-underline text-black;
|
||||
}
|
||||
.box-without-bg {
|
||||
@apply flex p-2 transition-colors dark:hover:text-white hover:no-underline min-h-[4rem] border border-neutral-200 dark:border-black;
|
||||
@apply flex p-2 transition-colors dark:hover:text-white hover:no-underline min-h-[4rem] border border-neutral-200 dark:border-black;
|
||||
}
|
||||
.box-without-bg-without-border {
|
||||
@apply flex p-2 transition-colors dark:hover:text-white hover:no-underline min-h-[4rem];
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none"
|
||||
viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
Your subscription has been activated! Welcome onboard! <br>It could take a few seconds before your
|
||||
subscription is activated.<br> Please be patient.
|
||||
subscription is activated.<br> Please be patient.
|
||||
</div>
|
||||
@endif
|
||||
<h3 class="pb-4">Projects</h3>
|
||||
@@ -23,23 +23,24 @@
|
||||
@if (data_get($project, 'environments')->count() === 1) onclick="gotoProject('{{ data_get($project, 'uuid') }}', '{{ data_get($project, 'environments.0.name', 'production') }}')"
|
||||
@else
|
||||
onclick="window.location.href = '{{ route('project.show', ['project_uuid' => data_get($project, 'uuid')]) }}'" @endif>
|
||||
<div class="flex flex-col justify-center flex-1 mx-6">
|
||||
<div class="box-title">{{ $project->name }}</div>
|
||||
<div class="box-description">
|
||||
{{ $project->description }}</div>
|
||||
</div>
|
||||
<span
|
||||
class="flex items-center justify-center gap-2 pt-4 pb-2 mr-4 text-xs lg:py-0 lg:justify-normal">
|
||||
<a class="hover:underline"
|
||||
href="{{ route('project.resource.create', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
|
||||
<span class="p-2 font-bold">+
|
||||
Add Resource</span>
|
||||
</a>
|
||||
<a class="font-bold hover:underline"
|
||||
<div class="flex flex-1 mx-6">
|
||||
<div class="flex flex-col justify-center flex-1">
|
||||
<div class="box-title">{{ $project->name }}</div>
|
||||
<div class="box-description">
|
||||
{{ $project->description }}</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-center gap-2 text-xs font-bold ">
|
||||
<a class="hover:underline"
|
||||
href="{{ route('project.resource.create', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
|
||||
<span class="p-2 font-bold">+
|
||||
Add Resource</span>
|
||||
</a>
|
||||
<a class="hover:underline"
|
||||
href="{{ route('project.edit', ['project_uuid' => data_get($project, 'uuid')]) }}">
|
||||
Settings
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
@@ -33,14 +33,16 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<x-loading wire:loading wire:target="loadRepositories({{ $ghapp->id }})" />
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
@if ($current_step === 'repository')
|
||||
@if ($repositories->count() > 0)
|
||||
<div class="flex items-end gap-2">
|
||||
<x-forms.select class="w-full" label="Repository"
|
||||
wire:model="selected_repository_id">
|
||||
<x-forms.select class="w-full" label="Repository" wire:model="selected_repository_id">
|
||||
@foreach ($repositories as $repo)
|
||||
@if ($loop->first)
|
||||
<option selected value="{{ data_get($repo, 'id') }}">
|
||||
|
||||
@@ -54,4 +54,78 @@
|
||||
@endforelse
|
||||
</div>
|
||||
@endif
|
||||
{{-- <section x-data="apex_app" class="container p-5 mx-auto my-20 bg-white drop-shadow-xl rounded-xl">
|
||||
<div class="w-full" x-ref="chart"></div>
|
||||
</section>
|
||||
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
|
||||
<script>
|
||||
document.addEventListener("alpine:init", () => {
|
||||
Alpine.data("apex_app", () => ({
|
||||
data: @js($cpu),
|
||||
init() {
|
||||
let chart = new ApexCharts(this.$refs.chart, this.options);
|
||||
chart.render();
|
||||
this.$watch("data", () => {
|
||||
chart.updateOptions(this.options);
|
||||
});
|
||||
},
|
||||
|
||||
get options() {
|
||||
return {
|
||||
colors: [function({
|
||||
value,
|
||||
seriesIndex,
|
||||
w
|
||||
}) {
|
||||
if (value < 55) {
|
||||
return '#7E36AF'
|
||||
} else {
|
||||
return '#D9534F'
|
||||
}
|
||||
}, function({
|
||||
value,
|
||||
seriesIndex,
|
||||
w
|
||||
}) {
|
||||
if (value < 111) {
|
||||
return '#7E36AF'
|
||||
} else {
|
||||
return '#D9534F'
|
||||
}
|
||||
}],
|
||||
|
||||
xaxis: {
|
||||
type: 'datetime'
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
series: [{
|
||||
name: "Series name",
|
||||
data: this.data
|
||||
}],
|
||||
tooltip: {
|
||||
enabled: true
|
||||
},
|
||||
chart: {
|
||||
stroke: {
|
||||
curve: 'smooth',
|
||||
},
|
||||
height: 500,
|
||||
width: "100%",
|
||||
type: "line",
|
||||
toolbar: {
|
||||
show: true
|
||||
},
|
||||
animations: {
|
||||
initialAnimation: {
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}));
|
||||
});
|
||||
</script> --}}
|
||||
</div>
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="pb-4">
|
||||
<div class="flex flex-col flex-wrap gap-2">
|
||||
@foreach ($servers->sortBy('id') as $server)
|
||||
<h5>Server: <span class="font-bold text-white">{{ $server->name }}</span></h5>
|
||||
<h5>Server: <span class="font-bold text-dark dark:text-white">{{ $server->name }}</span></h5>
|
||||
@foreach ($server->destinations() as $destination)
|
||||
<x-modal-confirmation action="cloneTo({{ data_get($destination, 'id') }})">
|
||||
<x:slot name="content">
|
||||
@@ -33,7 +33,7 @@
|
||||
</div>
|
||||
<div class="flex flex-col flex-wrap gap-2">
|
||||
@forelse ($projects as $project)
|
||||
<h5>Project: <span class="font-bold text-white">{{ $project->name }}</span></h5>
|
||||
<h5>Project: <span class="font-bold text-dark dark:text-white">{{ $project->name }}</span></h5>
|
||||
|
||||
@foreach ($project->environments as $environment)
|
||||
<x-modal-confirmation action="moveTo({{ data_get($environment, 'id') }})">
|
||||
|
||||
@@ -11,18 +11,26 @@
|
||||
@forelse ($project->environments as $environment)
|
||||
<div class="gap-2 border border-transparent cursor-pointer box group" x-data
|
||||
x-on:click="goto('{{ $project->uuid }}','{{ $environment->name }}')">
|
||||
<a class="flex flex-col justify-center flex-1 mx-6 hover:no-underline"
|
||||
href="{{ route('project.resource.index', [$project->uuid, $environment->name]) }}">
|
||||
<div class="font-bold dark:text-white"> {{ $environment->name }}</div>
|
||||
<div class="description">
|
||||
{{ $environment->description }}</div>
|
||||
</a>
|
||||
<div class="flex items-center justify-center gap-2 pt-4 pb-2 mr-4 text-xs lg:py-0 lg:justify-normal">
|
||||
<div class="flex flex-1 mx-6">
|
||||
<a class="flex flex-col justify-center flex-1"
|
||||
href="{{ route('project.resource.index', [$project->uuid, $environment->name]) }}">
|
||||
<div class="font-bold dark:text-white"> {{ $environment->name }}</div>
|
||||
<div class="description">
|
||||
{{ $environment->description }}</div>
|
||||
</a>
|
||||
<div class="flex items-center justify-center gap-2 text-xs">
|
||||
<a class="font-bold hover:underline"
|
||||
href="{{ route('project.environment.edit', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => $environment->name]) }}">
|
||||
Settings
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{{-- <div class="flex items-center justify-center gap-2 pt-4 pb-2 mr-4 text-xs lg:py-0 lg:justify-normal">
|
||||
<a class="mx-4 font-bold hover:underline"
|
||||
href="{{ route('project.environment.edit', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => $environment->name]) }}">
|
||||
Settings
|
||||
</a>
|
||||
</div>
|
||||
</div> --}}
|
||||
</div>
|
||||
@empty
|
||||
<p>No environments found.</p>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div>
|
||||
<x-server.navbar :server="$server" :parameters="$parameters" />
|
||||
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'managed' }" class="flex h-full flex-col md:flex-row gap-8">
|
||||
<div class="flex md:flex-col gap-4 flex-row">
|
||||
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'managed' }" class="flex flex-col h-full gap-8 md:flex-row">
|
||||
<div class="flex flex-row gap-4 md:flex-col">
|
||||
<a :class="activeTab === 'managed' && 'dark:text-white'"
|
||||
@click.prevent="activeTab = 'managed'; window.location.hash = 'managed'" href="#">Managed</a>
|
||||
<a :class="activeTab === 'unmanaged' && 'dark:text-white'"
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
<div wire:poll.1000ms="get_deployments" class="grid grid-cols-1">
|
||||
<div wire:poll.2000ms="get_deployments" wire:init='get_deployments'>
|
||||
@forelse ($deployments_per_tag_per_server as $server_name => $deployments)
|
||||
<h4 class="py-4">{{ $server_name }}</h4>
|
||||
<div class="grid grid-cols-1 gap-2 lg:grid-cols-3">
|
||||
<div class="grid grid-cols-1 gap-2">
|
||||
@foreach ($deployments as $deployment)
|
||||
<div @class([
|
||||
'box-without-bg dark:bg-coolgray-100 bg-white gap-2 cursor-pointer group border-l-2 border-dotted',
|
||||
<a href="{{ data_get($deployment, 'deployment_url') }}" @class([
|
||||
'box-without-bg-without-border dark:bg-coolgray-100 bg-white gap-2 cursor-pointer group border-l-2',
|
||||
'dark:border-coolgray-300' => data_get($deployment, 'status') === 'queued',
|
||||
'border-yellow-500' => data_get($deployment, 'status') === 'in_progress',
|
||||
'dark:border-yellow-500' =>
|
||||
data_get($deployment, 'status') === 'in_progress',
|
||||
])>
|
||||
<a href="{{ data_get($deployment, 'deployment_url') }}">
|
||||
<div class="flex flex-col mx-6">
|
||||
<div class="box-title">
|
||||
{{ data_get($deployment, 'application_name') }}
|
||||
</div>
|
||||
<div class="box-description">
|
||||
{{ str(data_get($deployment, 'status'))->headline() }}
|
||||
</div>
|
||||
<div class="flex flex-col mx-6">
|
||||
<div class="box-title">
|
||||
{{ data_get($deployment, 'application_name') }}
|
||||
</div>
|
||||
<div class="flex-1"></div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="box-description">
|
||||
{{ str(data_get($deployment, 'status'))->headline() }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1"></div>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
@empty
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<h1>Tags</h1>
|
||||
<div class="flex flex-col pb-6 ">
|
||||
<div class="subtitle">Tags help you to perform actions on multiple resources.</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div class="">
|
||||
@if ($tags->count() === 0)
|
||||
<div>No tags yet defined yet. Go to a resource and add a tag there.</div>
|
||||
@else
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M19.5 13.572l-7.5 7.428l-7.5 -7.428m0 0a5 5 0 1 1 7.5 -6.566a5 5 0 1 1 7.5 6.572" />
|
||||
</svg>
|
||||
Inprogress
|
||||
In progress
|
||||
@else
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6 text-pink-500 transition-colors hover:text-pink-300" viewBox="0 0 24 24"
|
||||
|
||||
+6
-1
@@ -12,6 +12,11 @@ DOCKER_VERSION="24.0"
|
||||
CDN="https://cdn.coollabs.io/coolify"
|
||||
OS_TYPE=$(grep -w "ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"')
|
||||
|
||||
# Check if the OS is manjaro, if so, change it to arch
|
||||
if [ "$OS_TYPE" = "manjaro" ]; then
|
||||
OS_TYPE="arch"
|
||||
fi
|
||||
|
||||
if [ "$OS_TYPE" = "arch" ]; then
|
||||
OS_VERSION="rolling"
|
||||
else
|
||||
@@ -255,7 +260,7 @@ fi
|
||||
|
||||
echo -e "-------------"
|
||||
|
||||
mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,webhooks-during-maintenance}
|
||||
mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,webhooks-during-maintenance,metrics,logs}
|
||||
mkdir -p /data/coolify/ssh/{keys,mux}
|
||||
mkdir -p /data/coolify/proxy/dynamic
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ x-logging: &x-logging
|
||||
options:
|
||||
max-file: '5'
|
||||
max-size: '10m'
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
appwrite:
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
# logo: svgs/authentik.png
|
||||
# port: 9000
|
||||
|
||||
version: "3.4"
|
||||
|
||||
services:
|
||||
postgresql:
|
||||
image: docker.io/library/postgres:12-alpine
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
# logo: svgs/glitchtip.png
|
||||
# port: 8080
|
||||
|
||||
version: "3.8"
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# documentation: https://listmonk.app/
|
||||
# slogan: Self-hosted newsletter and mailing list manager
|
||||
# tags: newsletter, mailing list, self-hosted, open source
|
||||
# logo: svgs/listmonk.svg
|
||||
# port: 9000
|
||||
|
||||
services:
|
||||
listmonk:
|
||||
image: listmonk/listmonk:latest
|
||||
environment:
|
||||
- SERVICE_FQDN_LISTMONK_9000
|
||||
- LISTMONK_app__address=0.0.0.0:9000
|
||||
- LISTMONK_db__host=postgres
|
||||
- LISTMONK_db__name=listmonk
|
||||
- LISTMONK_db__user=$SERVICE_USER_POSTGRES
|
||||
- LISTMONK_db__password=$SERVICE_PASSWORD_POSTGRES
|
||||
- LISTMONK_db__port=5432
|
||||
- LISTMONK_app__admin_username=admin
|
||||
- LISTMONK_app__admin_password=$SERVICE_PASSWORD_ADMIN
|
||||
- TZ=Etc/UTC
|
||||
volumes:
|
||||
- "listmonk-data:/listmonk/uploads"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:9000"]
|
||||
interval: 5s
|
||||
timeout: 20s
|
||||
retries: 10
|
||||
listmonk-initial-database-setup:
|
||||
image: listmonk/listmonk:latest
|
||||
command: "./listmonk --install --yes --idempotent"
|
||||
restart: "no"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
- LISTMONK_db__host=postgres
|
||||
- LISTMONK_db__name=listmonk
|
||||
- LISTMONK_db__user=$SERVICE_USER_POSTGRES
|
||||
- LISTMONK_db__password=$SERVICE_PASSWORD_POSTGRES
|
||||
- LISTMONK_db__port=5432
|
||||
postgres:
|
||||
image: "postgres:latest"
|
||||
environment:
|
||||
- POSTGRES_DB=listmonk
|
||||
- POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
|
||||
- POSTGRES_USER=$SERVICE_USER_POSTGRES
|
||||
volumes:
|
||||
- "pg-data:/var/lib/postgresql/data"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
||||
interval: 5s
|
||||
timeout: 20s
|
||||
retries: 10
|
||||
@@ -2,7 +2,6 @@
|
||||
# slogan: Penpot is the first Open Source design and prototyping platform for product teams.
|
||||
# tags: penpot,design,prototyping,figma,open,source
|
||||
|
||||
version: "3.5"
|
||||
services:
|
||||
frontend:
|
||||
image: "penpotapp/frontend:latest"
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
# tags: analytics, privacy, google, alternative
|
||||
# port: 8000
|
||||
|
||||
version: "3.3"
|
||||
services:
|
||||
plausible:
|
||||
image: plausible/analytics:v2.0
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"appwrite": {
|
||||
"documentation": "https:\/\/appwrite.io",
|
||||
"slogan": "A backend-as-a-service platform that simplifies the web & mobile app development.",
|
||||
"compose": "eC1sb2dnaW5nOgogIGxvZ2dpbmc6CiAgICBkcml2ZXI6IGpzb24tZmlsZQogICAgb3B0aW9uczoKICAgICAgbWF4LWZpbGU6ICc1JwogICAgICBtYXgtc2l6ZTogMTBtCnZlcnNpb246ICczJwpzZXJ2aWNlczoKICBhcHB3cml0ZToKICAgIGltYWdlOiAnYXBwd3JpdGUvYXBwd3JpdGU6MS41JwogICAgY29udGFpbmVyX25hbWU6IGFwcHdyaXRlCiAgICBsb2dnaW5nOgogICAgICBkcml2ZXI6IGpzb24tZmlsZQogICAgICBvcHRpb25zOgogICAgICAgIG1heC1maWxlOiAnNScKICAgICAgICBtYXgtc2l6ZTogMTBtCiAgICB2b2x1bWVzOgogICAgICAtICdhcHB3cml0ZS11cGxvYWRzOi9zdG9yYWdlL3VwbG9hZHM6cncnCiAgICAgIC0gJ2FwcHdyaXRlLWNhY2hlOi9zdG9yYWdlL2NhY2hlOnJ3JwogICAgICAtICdhcHB3cml0ZS1jb25maWc6L3N0b3JhZ2UvY29uZmlnOnJ3JwogICAgICAtICdhcHB3cml0ZS1jZXJ0aWZpY2F0ZXM6L3N0b3JhZ2UvY2VydGlmaWNhdGVzOnJ3JwogICAgICAtICdhcHB3cml0ZS1mdW5jdGlvbnM6L3N0b3JhZ2UvZnVuY3Rpb25zOnJ3JwogICAgZGVwZW5kc19vbjoKICAgICAgLSBhcHB3cml0ZS1tYXJpYWRiCiAgICAgIC0gYXBwd3JpdGUtcmVkaXMKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9BUFBXUklURT0vCiAgICAgIC0gX0FQUF9FTlYKICAgICAgLSBfQVBQX1dPUktFUl9QRVJfQ09SRQogICAgICAtIF9BUFBfTE9DQUxFCiAgICAgIC0gX0FQUF9DT05TT0xFX1dISVRFTElTVF9ST09UCiAgICAgIC0gX0FQUF9DT05TT0xFX1dISVRFTElTVF9FTUFJTFMKICAgICAgLSBfQVBQX0NPTlNPTEVfV0hJVEVMSVNUX0lQUwogICAgICAtIF9BUFBfQ09OU09MRV9IT1NUTkFNRVMKICAgICAgLSBfQVBQX1NZU1RFTV9FTUFJTF9OQU1FCiAgICAgIC0gX0FQUF9TWVNURU1fRU1BSUxfQUREUkVTUwogICAgICAtIF9BUFBfU1lTVEVNX1NFQ1VSSVRZX0VNQUlMX0FERFJFU1MKICAgICAgLSBfQVBQX1NZU1RFTV9SRVNQT05TRV9GT1JNQVQKICAgICAgLSBfQVBQX09QVElPTlNfQUJVU0UKICAgICAgLSBfQVBQX09QVElPTlNfRk9SQ0VfSFRUUFMKICAgICAgLSBfQVBQX09QRU5TU0xfS0VZX1YxCiAgICAgIC0gX0FQUF9ET01BSU49JFNFUlZJQ0VfRlFETl9BUFBXUklURQogICAgICAtIF9BUFBfRE9NQUlOX1RBUkdFVD0kU0VSVklDRV9GUUROX0FQUFdSSVRFCiAgICAgIC0gX0FQUF9ET01BSU5fRlVOQ1RJT05TPSRTRVJWSUNFX0ZRRE5fQVBQV1JJVEUKICAgICAgLSBfQVBQX1JFRElTX0hPU1QKICAgICAgLSBfQVBQX1JFRElTX1BPUlQKICAgICAgLSBfQVBQX1JFRElTX1VTRVIKICAgICAgLSBfQVBQX1JFRElTX1BBU1MKICAgICAgLSBfQVBQX0RCX0hPU1QKICAgICAgLSBfQVBQX0RCX1BPUlQKICAgICAgLSBfQVBQX0RCX1NDSEVNQQogICAgICAtIF9BUFBfREJfVVNFUgogICAgICAtIF9BUFBfREJfUEFTUwogICAgICAtIF9BUFBfU01UUF9IT1NUCiAgICAgIC0gX0FQUF9TTVRQX1BPUlQKICAgICAgLSBfQVBQX1NNVFBfU0VDVVJFCiAgICAgIC0gX0FQUF9TTVRQX1VTRVJOQU1FCiAgICAgIC0gX0FQUF9TTVRQX1BBU1NXT1JECiAgICAgIC0gX0FQUF9VU0FHRV9TVEFUUwogICAgICAtIF9BUFBfU1RPUkFHRV9MSU1JVAogICAgICAtIF9BUFBfU1RPUkFHRV9QUkVWSUVXX0xJTUlUCiAgICAgIC0gX0FQUF9TVE9SQUdFX0FOVElWSVJVUwogICAgICAtIF9BUFBfU1RPUkFHRV9BTlRJVklSVVNfSE9TVAogICAgICAtIF9BUFBfU1RPUkFHRV9BTlRJVklSVVNfUE9SVAogICAgICAtIF9BUFBfU1RPUkFHRV9ERVZJQ0UKICAgICAgLSBfQVBQX1NUT1JBR0VfUzNfQUNDRVNTX0tFWQogICAgICAtIF9BUFBfU1RPUkFHRV9TM19TRUNSRVQKICAgICAgLSBfQVBQX1NUT1JBR0VfUzNfUkVHSU9OCiAgICAgIC0gX0FQUF9TVE9SQUdFX1MzX0JVQ0tFVAogICAgICAtIF9BUFBfU1RPUkFHRV9ET19TUEFDRVNfQUNDRVNTX0tFWQogICAgICAtIF9BUFBfU1RPUkFHRV9ET19TUEFDRVNfU0VDUkVUCiAgICAgIC0gX0FQUF9TVE9SQUdFX0RPX1NQQUNFU19SRUdJT04KICAgICAgLSBfQVBQX1NUT1JBR0VfRE9fU1BBQ0VTX0JVQ0tFVAogICAgICAtIF9BUFBfU1RPUkFHRV9CQUNLQkxBWkVfQUNDRVNTX0tFWQogICAgICAtIF9BUFBfU1RPUkFHRV9CQUNLQkxBWkVfU0VDUkVUCiAgICAgIC0gX0FQUF9TVE9SQUdFX0JBQ0tCTEFaRV9SRUdJT04KICAgICAgLSBfQVBQX1NUT1JBR0VfQkFDS0JMQVpFX0JVQ0tFVAogICAgICAtIF9BUFBfU1RPUkFHRV9MSU5PREVfQUNDRVNTX0tFWQogICAgICAtIF9BUFBfU1RPUkFHRV9MSU5PREVfU0VDUkVUCiAgICAgIC0gX0FQUF9TVE9SQUdFX0xJTk9ERV9SRUdJT04KICAgICAgLSBfQVBQX1NUT1JBR0VfTElOT0RFX0JVQ0tFVAogICAgICAtIF9BUFBfU1RPUkFHRV9XQVNBQklfQUNDRVNTX0tFWQogICAgICAtIF9BUFBfU1RPUkFHRV9XQVNBQklfU0VDUkVUCiAgICAgIC0gX0FQUF9TVE9SQUdFX1dBU0FCSV9SRUdJT04KICAgICAgLSBfQVBQX1NUT1JBR0VfV0FTQUJJX0JVQ0tFVAogICAgICAtIF9BUFBfRlVOQ1RJT05TX1NJWkVfTElNSVQKICAgICAgLSBfQVBQX0ZVTkNUSU9OU19USU1FT1VUCiAgICAgIC0gX0FQUF9GVU5DVElPTlNfQlVJTERfVElNRU9VVAogICAgICAtIF9BUFBfRlVOQ1RJT05TX0NQVVMKICAgICAgLSBfQVBQX0ZVTkNUSU9OU19NRU1PUlkKICAgICAgLSBfQVBQX0ZVTkNUSU9OU19SVU5USU1FUwogICAgICAtIF9BUFBfRVhFQ1VUT1JfU0VDUkVUCiAgICAgIC0gX0FQUF9FWEVDVVRPUl9IT1NUCiAgICAgIC0gX0FQUF9MT0dHSU5HX1BST1ZJREVSCiAgICAgIC0gX0FQUF9MT0dHSU5HX0NPTkZJRwogICAgICAtIF9BUFBfTUFJTlRFTkFOQ0VfSU5URVJWQUwKICAgICAgLSBfQVBQX01BSU5URU5BTkNFX0RFTEFZCiAgICAgIC0gX0FQUF9NQUlOVEVOQU5DRV9SRVRFTlRJT05fRVhFQ1VUSU9OCiAgICAgIC0gX0FQUF9NQUlOVEVOQU5DRV9SRVRFTlRJT05fQ0FDSEUKICAgICAgLSBfQVBQX01BSU5URU5BTkNFX1JFVEVOVElPTl9BQlVTRQogICAgICAtIF9BUFBfTUFJTlRFTkFOQ0VfUkVURU5USU9OX0FVRElUCiAgICAgIC0gX0FQUF9NQUlOVEVOQU5DRV9SRVRFTlRJT05fVVNBR0VfSE9VUkxZCiAgICAgIC0gX0FQUF9NQUlOVEVOQU5DRV9SRVRFTlRJT05fU0NIRURVTEVTCiAgICAgIC0gX0FQUF9TTVNfUFJPVklERVIKICAgICAgLSBfQVBQX1NNU19GUk9NCiAgICAgIC0gX0FQUF9HUkFQSFFMX01BWF9CQVRDSF9TSVpFCiAgICAgIC0gX0FQUF9HUkFQSFFMX01BWF9DT01QTEVYSVRZCiAgICAgIC0gX0FQUF9HUkFQSFFMX01BWF9ERVBUSAogICAgICAtIF9BUFBfVkNTX0dJVEhVQl9BUFBfTkFNRQogICAgICAtIF9BUFBfVkNTX0dJVEhVQl9QUklWQVRFX0tFWQogICAgICAtIF9BUFBfVkNTX0dJVEhVQl9BUFBfSUQKICAgICAgLSBfQVBQX1ZDU19HSVRIVUJfV0VCSE9PS19TRUNSRVQKICAgICAgLSBfQVBQX1ZDU19HSVRIVUJfQ0xJRU5UX1NFQ1JFVAogICAgICAtIF9BUFBfVkNTX0dJVEhVQl9DTElFTlRfSUQKICAgICAgLSBfQVBQX01JR1JBVElPTlNfRklSRUJBU0VfQ0xJRU5UX0lECiAgICAgIC0gX0FQUF9NSUdSQVRJT05TX0ZJUkVCQVNFX0NMSUVOVF9TRUNSRVQKICAgICAgLSBfQVBQX0FTU0lTVEFOVF9PUEVOQUlfQVBJX0tFWQogIGFwcHdyaXRlLXJlYWx0aW1lOgogICAgaW1hZ2U6ICdhcHB3cml0ZS9hcHB3cml0ZToxLjUnCiAgICBlbnRyeXBvaW50OiByZWFsdGltZQogICAgbG9nZ2luZzoLine truncated
|
||||
"compose": "eC1sb2dnaW5nOgogIGxvZ2dpbmc6CiAgICBkcml2ZXI6IGpzb24tZmlsZQogICAgb3B0aW9uczoKICAgICAgbWF4LWZpbGU6ICc1JwogICAgICBtYXgtc2l6ZTogMTBtCnNlcnZpY2VzOgogIGFwcHdyaXRlOgogICAgaW1hZ2U6ICdhcHB3cml0ZS9hcHB3cml0ZToxLjUnCiAgICBjb250YWluZXJfbmFtZTogYXBwd3JpdGUKICAgIGxvZ2dpbmc6CiAgICAgIGRyaXZlcjoganNvbi1maWxlCiAgICAgIG9wdGlvbnM6CiAgICAgICAgbWF4LWZpbGU6ICc1JwogICAgICAgIG1heC1zaXplOiAxMG0KICAgIHZvbHVtZXM6CiAgICAgIC0gJ2FwcHdyaXRlLXVwbG9hZHM6L3N0b3JhZ2UvdXBsb2FkczpydycKICAgICAgLSAnYXBwd3JpdGUtY2FjaGU6L3N0b3JhZ2UvY2FjaGU6cncnCiAgICAgIC0gJ2FwcHdyaXRlLWNvbmZpZzovc3RvcmFnZS9jb25maWc6cncnCiAgICAgIC0gJ2FwcHdyaXRlLWNlcnRpZmljYXRlczovc3RvcmFnZS9jZXJ0aWZpY2F0ZXM6cncnCiAgICAgIC0gJ2FwcHdyaXRlLWZ1bmN0aW9uczovc3RvcmFnZS9mdW5jdGlvbnM6cncnCiAgICBkZXBlbmRzX29uOgogICAgICAtIGFwcHdyaXRlLW1hcmlhZGIKICAgICAgLSBhcHB3cml0ZS1yZWRpcwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX0FQUFdSSVRFPS8KICAgICAgLSBfQVBQX0VOVgogICAgICAtIF9BUFBfV09SS0VSX1BFUl9DT1JFCiAgICAgIC0gX0FQUF9MT0NBTEUKICAgICAgLSBfQVBQX0NPTlNPTEVfV0hJVEVMSVNUX1JPT1QKICAgICAgLSBfQVBQX0NPTlNPTEVfV0hJVEVMSVNUX0VNQUlMUwogICAgICAtIF9BUFBfQ09OU09MRV9XSElURUxJU1RfSVBTCiAgICAgIC0gX0FQUF9DT05TT0xFX0hPU1ROQU1FUwogICAgICAtIF9BUFBfU1lTVEVNX0VNQUlMX05BTUUKICAgICAgLSBfQVBQX1NZU1RFTV9FTUFJTF9BRERSRVNTCiAgICAgIC0gX0FQUF9TWVNURU1fU0VDVVJJVFlfRU1BSUxfQUREUkVTUwogICAgICAtIF9BUFBfU1lTVEVNX1JFU1BPTlNFX0ZPUk1BVAogICAgICAtIF9BUFBfT1BUSU9OU19BQlVTRQogICAgICAtIF9BUFBfT1BUSU9OU19GT1JDRV9IVFRQUwogICAgICAtIF9BUFBfT1BFTlNTTF9LRVlfVjEKICAgICAgLSBfQVBQX0RPTUFJTj0kU0VSVklDRV9GUUROX0FQUFdSSVRFCiAgICAgIC0gX0FQUF9ET01BSU5fVEFSR0VUPSRTRVJWSUNFX0ZRRE5fQVBQV1JJVEUKICAgICAgLSBfQVBQX0RPTUFJTl9GVU5DVElPTlM9JFNFUlZJQ0VfRlFETl9BUFBXUklURQogICAgICAtIF9BUFBfUkVESVNfSE9TVAogICAgICAtIF9BUFBfUkVESVNfUE9SVAogICAgICAtIF9BUFBfUkVESVNfVVNFUgogICAgICAtIF9BUFBfUkVESVNfUEFTUwogICAgICAtIF9BUFBfREJfSE9TVAogICAgICAtIF9BUFBfREJfUE9SVAogICAgICAtIF9BUFBfREJfU0NIRU1BCiAgICAgIC0gX0FQUF9EQl9VU0VSCiAgICAgIC0gX0FQUF9EQl9QQVNTCiAgICAgIC0gX0FQUF9TTVRQX0hPU1QKICAgICAgLSBfQVBQX1NNVFBfUE9SVAogICAgICAtIF9BUFBfU01UUF9TRUNVUkUKICAgICAgLSBfQVBQX1NNVFBfVVNFUk5BTUUKICAgICAgLSBfQVBQX1NNVFBfUEFTU1dPUkQKICAgICAgLSBfQVBQX1VTQUdFX1NUQVRTCiAgICAgIC0gX0FQUF9TVE9SQUdFX0xJTUlUCiAgICAgIC0gX0FQUF9TVE9SQUdFX1BSRVZJRVdfTElNSVQKICAgICAgLSBfQVBQX1NUT1JBR0VfQU5USVZJUlVTCiAgICAgIC0gX0FQUF9TVE9SQUdFX0FOVElWSVJVU19IT1NUCiAgICAgIC0gX0FQUF9TVE9SQUdFX0FOVElWSVJVU19QT1JUCiAgICAgIC0gX0FQUF9TVE9SQUdFX0RFVklDRQogICAgICAtIF9BUFBfU1RPUkFHRV9TM19BQ0NFU1NfS0VZCiAgICAgIC0gX0FQUF9TVE9SQUdFX1MzX1NFQ1JFVAogICAgICAtIF9BUFBfU1RPUkFHRV9TM19SRUdJT04KICAgICAgLSBfQVBQX1NUT1JBR0VfUzNfQlVDS0VUCiAgICAgIC0gX0FQUF9TVE9SQUdFX0RPX1NQQUNFU19BQ0NFU1NfS0VZCiAgICAgIC0gX0FQUF9TVE9SQUdFX0RPX1NQQUNFU19TRUNSRVQKICAgICAgLSBfQVBQX1NUT1JBR0VfRE9fU1BBQ0VTX1JFR0lPTgogICAgICAtIF9BUFBfU1RPUkFHRV9ET19TUEFDRVNfQlVDS0VUCiAgICAgIC0gX0FQUF9TVE9SQUdFX0JBQ0tCTEFaRV9BQ0NFU1NfS0VZCiAgICAgIC0gX0FQUF9TVE9SQUdFX0JBQ0tCTEFaRV9TRUNSRVQKICAgICAgLSBfQVBQX1NUT1JBR0VfQkFDS0JMQVpFX1JFR0lPTgogICAgICAtIF9BUFBfU1RPUkFHRV9CQUNLQkxBWkVfQlVDS0VUCiAgICAgIC0gX0FQUF9TVE9SQUdFX0xJTk9ERV9BQ0NFU1NfS0VZCiAgICAgIC0gX0FQUF9TVE9SQUdFX0xJTk9ERV9TRUNSRVQKICAgICAgLSBfQVBQX1NUT1JBR0VfTElOT0RFX1JFR0lPTgogICAgICAtIF9BUFBfU1RPUkFHRV9MSU5PREVfQlVDS0VUCiAgICAgIC0gX0FQUF9TVE9SQUdFX1dBU0FCSV9BQ0NFU1NfS0VZCiAgICAgIC0gX0FQUF9TVE9SQUdFX1dBU0FCSV9TRUNSRVQKICAgICAgLSBfQVBQX1NUT1JBR0VfV0FTQUJJX1JFR0lPTgogICAgICAtIF9BUFBfU1RPUkFHRV9XQVNBQklfQlVDS0VUCiAgICAgIC0gX0FQUF9GVU5DVElPTlNfU0laRV9MSU1JVAogICAgICAtIF9BUFBfRlVOQ1RJT05TX1RJTUVPVVQKICAgICAgLSBfQVBQX0ZVTkNUSU9OU19CVUlMRF9USU1FT1VUCiAgICAgIC0gX0FQUF9GVU5DVElPTlNfQ1BVUwogICAgICAtIF9BUFBfRlVOQ1RJT05TX01FTU9SWQogICAgICAtIF9BUFBfRlVOQ1RJT05TX1JVTlRJTUVTCiAgICAgIC0gX0FQUF9FWEVDVVRPUl9TRUNSRVQKICAgICAgLSBfQVBQX0VYRUNVVE9SX0hPU1QKICAgICAgLSBfQVBQX0xPR0dJTkdfUFJPVklERVIKICAgICAgLSBfQVBQX0xPR0dJTkdfQ09ORklHCiAgICAgIC0gX0FQUF9NQUlOVEVOQU5DRV9JTlRFUlZBTAogICAgICAtIF9BUFBfTUFJTlRFTkFOQ0VfREVMQVkKICAgICAgLSBfQVBQX01BSU5URU5BTkNFX1JFVEVOVElPTl9FWEVDVVRJT04KICAgICAgLSBfQVBQX01BSU5URU5BTkNFX1JFVEVOVElPTl9DQUNIRQogICAgICAtIF9BUFBfTUFJTlRFTkFOQ0VfUkVURU5USU9OX0FCVVNFCiAgICAgIC0gX0FQUF9NQUlOVEVOQU5DRV9SRVRFTlRJT05fQVVESVQKICAgICAgLSBfQVBQX01BSU5URU5BTkNFX1JFVEVOVElPTl9VU0FHRV9IT1VSTFkKICAgICAgLSBfQVBQX01BSU5URU5BTkNFX1JFVEVOVElPTl9TQ0hFRFVMRVMKICAgICAgLSBfQVBQX1NNU19QUk9WSURFUgogICAgICAtIF9BUFBfU01TX0ZST00KICAgICAgLSBfQVBQX0dSQVBIUUxfTUFYX0JBVENIX1NJWkUKICAgICAgLSBfQVBQX0dSQVBIUUxfTUFYX0NPTVBMRVhJVFkKICAgICAgLSBfQVBQX0dSQVBIUUxfTUFYX0RFUFRICiAgICAgIC0gX0FQUF9WQ1NfR0lUSFVCX0FQUF9OQU1FCiAgICAgIC0gX0FQUF9WQ1NfR0lUSFVCX1BSSVZBVEVfS0VZCiAgICAgIC0gX0FQUF9WQ1NfR0lUSFVCX0FQUF9JRAogICAgICAtIF9BUFBfVkNTX0dJVEhVQl9XRUJIT09LX1NFQ1JFVAogICAgICAtIF9BUFBfVkNTX0dJVEhVQl9DTElFTlRfU0VDUkVUCiAgICAgIC0gX0FQUF9WQ1NfR0lUSFVCX0NMSUVOVF9JRAogICAgICAtIF9BUFBfTUlHUkFUSU9OU19GSVJFQkFTRV9DTElFTlRfSUQKICAgICAgLSBfQVBQX01JR1JBVElPTlNfRklSRUJBU0VfQ0xJRU5UX1NFQ1JFVAogICAgICAtIF9BUFBfQVNTSVNUQU5UX09QRU5BSV9BUElfS0VZCiAgYXBwd3JpdGUtcmVhbHRpbWU6CiAgICBpbWFnZTogJ2FwcHdyaXRlL2FwcHdyaXRlOjEuNScKICAgIGVudHJ5cG9pbnQ6IHJlYWx0aW1lCiAgICBsb2dnaW5nOgogICAgICBkcml2ZXILine truncated
|
||||
"tags": [
|
||||
"backend-as-a-service",
|
||||
"platform"
|
||||
@@ -41,7 +41,7 @@
|
||||
"authentik": {
|
||||
"documentation": "https:\/\/docs.goauthentik.io\/docs\/installation\/docker-compose",
|
||||
"slogan": "An open-source Identity Provider, focused on flexibility and versatility.",
|
||||
"compose": "dmVyc2lvbjogJzMuNCcKc2VydmljZXM6CiAgcG9zdGdyZXNxbDoKICAgIGltYWdlOiAnZG9ja2VyLmlvL2xpYnJhcnkvcG9zdGdyZXM6MTItYWxwaW5lJwogICAgcmVzdGFydDogdW5sZXNzLXN0b3BwZWQKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtZCBhdXRoZW50aWsgLVUgJCR7U0VSVklDRV9VU0VSX1BPU1RHUkVTUUx9JwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1CiAgICB2b2x1bWVzOgogICAgICAtICdhdXRoZW50aWstZGI6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMfScKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMfScKICAgICAgLSBQT1NUR1JFU19EQj1hdXRoZW50aWsKICByZWRpczoKICAgIGltYWdlOiAnZG9ja2VyLmlvL2xpYnJhcnkvcmVkaXM6YWxwaW5lJwogICAgY29tbWFuZDogJy0tc2F2ZSA2MCAxIC0tbG9nbGV2ZWwgd2FybmluZycKICAgIHJlc3RhcnQ6IHVubGVzcy1zdG9wcGVkCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3JlZGlzLWNsaSBwaW5nIHwgZ3JlcCBQT05HJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1CiAgICB2b2x1bWVzOgogICAgICAtICdyZWRpczovZGF0YScKICBhdXRoZW50aWstc2VydmVyOgogICAgaW1hZ2U6ICdnaGNyLmlvL2dvYXV0aGVudGlrL3NlcnZlcjoke0FVVEhFTlRJS19UQUc6LTIwMjQuMi4yfScKICAgIHJlc3RhcnQ6IHVubGVzcy1zdG9wcGVkCiAgICBjb21tYW5kOiBzZXJ2ZXIKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9BVVRIRU5USUtTRVJWRVJfOTAwMAogICAgICAtIEFVVEhFTlRJS19SRURJU19fSE9TVD1yZWRpcwogICAgICAtIEFVVEhFTlRJS19QT1NUR1JFU1FMX19IT1NUPXBvc3RncmVzcWwKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVNRTH0nCiAgICAgIC0gQVVUSEVOVElLX1BPU1RHUkVTUUxfX05BTUU9YXV0aGVudGlrCiAgICAgIC0gJ0FVVEhFTlRJS19QT1NUR1JFU1FMX19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTH0nCiAgICAgIC0gJ0FVVEhFTlRJS19TRUNSRVRfS0VZPSR7U0VSVklDRV9QQVNTV09SRF82NF9BVVRIRU5USUtTRVJWRVJ9JwogICAgICAtICdBVVRIRU5USUtfRVJST1JfUkVQT1JUSU5HX19FTkFCTEVEPSR7QVVUSEVOVElLX0VSUk9SX1JFUE9SVElOR19fRU5BQkxFRDotdHJ1ZX0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fSE9TVD0ke0FVVEhFTlRJS19FTUFJTF9fSE9TVH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fUE9SVD0ke0FVVEhFTlRJS19FTUFJTF9fUE9SVH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFUk5BTUU9JHtBVVRIRU5USUtfRU1BSUxfX1VTRVJOQU1FfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19QQVNTV09SRD0ke0FVVEhFTlRJS19FTUFJTF9fUEFTU1dPUkR9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX1VTRV9UTFM9JHtBVVRIRU5USUtfRU1BSUxfX1VTRV9UTFN9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX1VTRV9TU0w9JHtBVVRIRU5USUtfRU1BSUxfX1VTRV9TU0x9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX1RJTUVPVVQ9JHtBVVRIRU5USUtfRU1BSUxfX1RJTUVPVVR9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX0ZST009JHtBVVRIRU5USUtfRU1BSUxfX0ZST019JwogICAgdm9sdW1lczoKICAgICAgLSAnLi9tZWRpYTovbWVkaWEnCiAgICAgIC0gJy4vY3VzdG9tLXRlbXBsYXRlczovdGVtcGxhdGVzJwogICAgZGVwZW5kc19vbjoKICAgICAgLSBwb3N0Z3Jlc3FsCiAgICAgIC0gcmVkaXMKICBhdXRoZW50aWstd29ya2VyOgogICAgaW1hZ2U6ICdnaGNyLmlvL2dvYXV0aGVudGlrL3NlcnZlcjoke0FVVEhFTlRJS19UQUc6LTIwMjQuMi4yfScKICAgIHJlc3RhcnQ6IHVubGVzcy1zdG9wcGVkCiAgICBjb21tYW5kOiB3b3JrZXIKICAgIGVudmlyb25tZW50OgogICAgICAtIEFVVEhFTlRJS19SRURJU19fSE9TVD1yZWRpcwogICAgICAtIEFVVEhFTlRJS19QT1NUR1JFU1FMX19IT1NUPXBvc3RncmVzcWwKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVNRTH0nCiAgICAgIC0gQVVUSEVOVElLX1BPU1RHUkVTUUxfX05BTUU9YXV0aGVudGlrCiAgICAgIC0gJ0FVVEhFTlRJS19QT1NUR1JFU1FMX19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTH0nCiAgICAgIC0gJ0FVVEhFTlRJS19TRUNSRVRfS0VZPSR7U0VSVklDRV9QQVNTV09SRF82NF9BVVRIRU5USUtTRVJWRVJ9JwogICAgICAtICdBVVRIRU5USUtfRVJST1JfUkVQT1JUSU5HX19FTkFCTEVEPSR7QVVUSEVOVElLX0VSUk9SX1JFUE9SVElOR19fRU5BQkxFRH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fSE9TVD0ke0FVVEhFTlRJS19FTUFJTF9fSE9TVH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fUE9SVD0ke0FVVEhFTlRJS19FTUFJTF9fUE9SVH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFUk5BTUU9JHtBVVRIRU5USUtfRU1BSUxfX1VTRVJOQU1FfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19QQVNTV09SRD0ke0FVVEhFTlRJS19FTUFJTF9fUEFTU1dPUkR9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX1VTRV9UTFM9JHtBVVRIRU5USUtfRU1BSUxfX1VTRV9UTFN9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX1VTRV9TU0w9JHtBVVRIRU5USUtfRU1BSUxfX1VTRV9TU0x9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX1RJTUVPVVQ9JHtBVVRIRU5USUtfRU1BSUxfX1RJTUVPVVR9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX0ZST009JHtBVVRIRU5USUtfRU1BSUxfX0ZST019JwogICAgdXNlcjogcm9vdAogICAgdm9sdW1lczoKICAgICAgLSAnL3Zhci9ydW4vZG9ja2VyLnNvY2s6L3Zhci9ydW4vZG9ja2VyLnNvY2snCiAgICAgIC0gJy4vbWVkaWE6L21lZGlhJwogICAgICAtICcuL2NlcnRzOi9jZXJ0cycKICAgICAgLSAnLi9jdXN0b20tdGVtcGxhdGVzOi90ZW1wbGF0ZXMnCiAgICBkZXBlbmRzX29uOgogICAgICAtIHBvc3RncmVzcWwKICAgICAgLSByZWRpcwo=",
|
||||
"compose": "c2VydmljZXM6CiAgcG9zdGdyZXNxbDoKICAgIGltYWdlOiAnZG9ja2VyLmlvL2xpYnJhcnkvcG9zdGdyZXM6MTItYWxwaW5lJwogICAgcmVzdGFydDogdW5sZXNzLXN0b3BwZWQKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtZCBhdXRoZW50aWsgLVUgJCR7U0VSVklDRV9VU0VSX1BPU1RHUkVTUUx9JwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1CiAgICB2b2x1bWVzOgogICAgICAtICdhdXRoZW50aWstZGI6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMfScKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMfScKICAgICAgLSBQT1NUR1JFU19EQj1hdXRoZW50aWsKICByZWRpczoKICAgIGltYWdlOiAnZG9ja2VyLmlvL2xpYnJhcnkvcmVkaXM6YWxwaW5lJwogICAgY29tbWFuZDogJy0tc2F2ZSA2MCAxIC0tbG9nbGV2ZWwgd2FybmluZycKICAgIHJlc3RhcnQ6IHVubGVzcy1zdG9wcGVkCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3JlZGlzLWNsaSBwaW5nIHwgZ3JlcCBQT05HJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1CiAgICB2b2x1bWVzOgogICAgICAtICdyZWRpczovZGF0YScKICBhdXRoZW50aWstc2VydmVyOgogICAgaW1hZ2U6ICdnaGNyLmlvL2dvYXV0aGVudGlrL3NlcnZlcjoke0FVVEhFTlRJS19UQUc6LTIwMjQuMi4yfScKICAgIHJlc3RhcnQ6IHVubGVzcy1zdG9wcGVkCiAgICBjb21tYW5kOiBzZXJ2ZXIKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9BVVRIRU5USUtTRVJWRVJfOTAwMAogICAgICAtIEFVVEhFTlRJS19SRURJU19fSE9TVD1yZWRpcwogICAgICAtIEFVVEhFTlRJS19QT1NUR1JFU1FMX19IT1NUPXBvc3RncmVzcWwKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVNRTH0nCiAgICAgIC0gQVVUSEVOVElLX1BPU1RHUkVTUUxfX05BTUU9YXV0aGVudGlrCiAgICAgIC0gJ0FVVEhFTlRJS19QT1NUR1JFU1FMX19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTH0nCiAgICAgIC0gJ0FVVEhFTlRJS19TRUNSRVRfS0VZPSR7U0VSVklDRV9QQVNTV09SRF82NF9BVVRIRU5USUtTRVJWRVJ9JwogICAgICAtICdBVVRIRU5USUtfRVJST1JfUkVQT1JUSU5HX19FTkFCTEVEPSR7QVVUSEVOVElLX0VSUk9SX1JFUE9SVElOR19fRU5BQkxFRDotdHJ1ZX0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fSE9TVD0ke0FVVEhFTlRJS19FTUFJTF9fSE9TVH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fUE9SVD0ke0FVVEhFTlRJS19FTUFJTF9fUE9SVH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFUk5BTUU9JHtBVVRIRU5USUtfRU1BSUxfX1VTRVJOQU1FfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19QQVNTV09SRD0ke0FVVEhFTlRJS19FTUFJTF9fUEFTU1dPUkR9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX1VTRV9UTFM9JHtBVVRIRU5USUtfRU1BSUxfX1VTRV9UTFN9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX1VTRV9TU0w9JHtBVVRIRU5USUtfRU1BSUxfX1VTRV9TU0x9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX1RJTUVPVVQ9JHtBVVRIRU5USUtfRU1BSUxfX1RJTUVPVVR9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX0ZST009JHtBVVRIRU5USUtfRU1BSUxfX0ZST019JwogICAgdm9sdW1lczoKICAgICAgLSAnLi9tZWRpYTovbWVkaWEnCiAgICAgIC0gJy4vY3VzdG9tLXRlbXBsYXRlczovdGVtcGxhdGVzJwogICAgZGVwZW5kc19vbjoKICAgICAgLSBwb3N0Z3Jlc3FsCiAgICAgIC0gcmVkaXMKICBhdXRoZW50aWstd29ya2VyOgogICAgaW1hZ2U6ICdnaGNyLmlvL2dvYXV0aGVudGlrL3NlcnZlcjoke0FVVEhFTlRJS19UQUc6LTIwMjQuMi4yfScKICAgIHJlc3RhcnQ6IHVubGVzcy1zdG9wcGVkCiAgICBjb21tYW5kOiB3b3JrZXIKICAgIGVudmlyb25tZW50OgogICAgICAtIEFVVEhFTlRJS19SRURJU19fSE9TVD1yZWRpcwogICAgICAtIEFVVEhFTlRJS19QT1NUR1JFU1FMX19IT1NUPXBvc3RncmVzcWwKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVNRTH0nCiAgICAgIC0gQVVUSEVOVElLX1BPU1RHUkVTUUxfX05BTUU9YXV0aGVudGlrCiAgICAgIC0gJ0FVVEhFTlRJS19QT1NUR1JFU1FMX19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTH0nCiAgICAgIC0gJ0FVVEhFTlRJS19TRUNSRVRfS0VZPSR7U0VSVklDRV9QQVNTV09SRF82NF9BVVRIRU5USUtTRVJWRVJ9JwogICAgICAtICdBVVRIRU5USUtfRVJST1JfUkVQT1JUSU5HX19FTkFCTEVEPSR7QVVUSEVOVElLX0VSUk9SX1JFUE9SVElOR19fRU5BQkxFRH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fSE9TVD0ke0FVVEhFTlRJS19FTUFJTF9fSE9TVH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fUE9SVD0ke0FVVEhFTlRJS19FTUFJTF9fUE9SVH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFUk5BTUU9JHtBVVRIRU5USUtfRU1BSUxfX1VTRVJOQU1FfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19QQVNTV09SRD0ke0FVVEhFTlRJS19FTUFJTF9fUEFTU1dPUkR9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX1VTRV9UTFM9JHtBVVRIRU5USUtfRU1BSUxfX1VTRV9UTFN9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX1VTRV9TU0w9JHtBVVRIRU5USUtfRU1BSUxfX1VTRV9TU0x9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX1RJTUVPVVQ9JHtBVVRIRU5USUtfRU1BSUxfX1RJTUVPVVR9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX0ZST009JHtBVVRIRU5USUtfRU1BSUxfX0ZST019JwogICAgdXNlcjogcm9vdAogICAgdm9sdW1lczoKICAgICAgLSAnL3Zhci9ydW4vZG9ja2VyLnNvY2s6L3Zhci9ydW4vZG9ja2VyLnNvY2snCiAgICAgIC0gJy4vbWVkaWE6L21lZGlhJwogICAgICAtICcuL2NlcnRzOi9jZXJ0cycKICAgICAgLSAnLi9jdXN0b20tdGVtcGxhdGVzOi90ZW1wbGF0ZXMnCiAgICBkZXBlbmRzX29uOgogICAgICAtIHBvc3RncmVzcWwKICAgICAgLSByZWRpcwo=",
|
||||
"tags": [
|
||||
"identity",
|
||||
"login",
|
||||
@@ -403,7 +403,7 @@
|
||||
"glitchtip": {
|
||||
"documentation": "https:\/\/glitchtip.com",
|
||||
"slogan": "GlitchTip is a self-hosted, open-source error tracking system.",
|
||||
"compose": "dmVyc2lvbjogJzMuOCcKc2VydmljZXM6CiAgcG9zdGdyZXM6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE2LWFscGluZScKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTUUx9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTH0nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNRTF9EQVRBQkFTRTotZ2xpdGNodGlwfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3BnLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIHJlZGlzOgogICAgaW1hZ2U6IHJlZGlzCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gcmVkaXMtY2xpCiAgICAgICAgLSBwaW5nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICB3ZWI6CiAgICBpbWFnZTogZ2xpdGNodGlwL2dsaXRjaHRpcAogICAgZGVwZW5kc19vbjoKICAgICAgLSBwb3N0Z3JlcwogICAgICAtIHJlZGlzCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fR0xJVENIVElQXzgwODAKICAgICAgLSAnREFUQUJBU0VfVVJMPXBvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMOiRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTUUxAcG9zdGdyZXM6NTQzMi8ke1BPU1RHUkVTUUxfREFUQUJBU0U6LWdsaXRjaHRpcH0nCiAgICAgIC0gU0VDUkVUX0tFWT0kU0VSVklDRV9CQVNFNjRfNjRfRU5DUllQVElPTgogICAgICAtICdFTUFJTF9VUkw9JHtFTUFJTF9VUkw6LWNvbnNvbGVtYWlsOi8vfScKICAgICAgLSAnR0xJVENIVElQX0RPTUFJTj0ke1NFUlZJQ0VfRlFETl9HTElUQ0hUSVB9JwogICAgICAtICdERUZBVUxUX0ZST01fRU1BSUw9JHtERUZBVUxUX0ZST01fRU1BSUw6LXRlc3RAZXhhbXBsZS5jb219JwogICAgICAtICdDRUxFUllfV09SS0VSX0FVVE9TQ0FMRT0ke0NFTEVSWV9XT1JLRVJfQVVUT1NDQUxFOi0xLDN9JwogICAgICAtICdDRUxFUllfV09SS0VSX01BWF9UQVNLU19QRVJfQ0hJTEQ9JHtDRUxFUllfV09SS0VSX01BWF9UQVNLU19QRVJfQ0hJTEQ6LTEwMDAwfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3VwbG9hZHM6L2NvZGUvdXBsb2FkcycKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBlY2hvCiAgICAgICAgLSBvawogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgd29ya2VyOgogICAgaW1hZ2U6IGdsaXRjaHRpcC9nbGl0Y2h0aXAKICAgIGNvbW1hbmQ6IC4vYmluL3J1bi1jZWxlcnktd2l0aC1iZWF0LnNoCiAgICBkZXBlbmRzX29uOgogICAgICAtIHBvc3RncmVzCiAgICAgIC0gcmVkaXMKICAgIGVudmlyb25tZW50OgogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXM6Ly8kU0VSVklDRV9VU0VSX1BPU1RHUkVTUUw6JFNFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTEBwb3N0Z3Jlczo1NDMyLyR7UE9TVEdSRVNRTF9EQVRBQkFTRTotZ2xpdGNodGlwfScKICAgICAgLSBTRUNSRVRfS0VZPSRTRVJWSUNFX0JBU0U2NF82NF9FTkNSWVBUSU9OCiAgICAgIC0gJ0VNQUlMX1VSTD0ke0VNQUlMX1VSTDotY29uc29sZW1haWw6Ly99JwogICAgICAtICdERUZBVUxUX0ZST01fRU1BSUw9JHtERUZBVUxUX0ZST01fRU1BSUw6LXRlc3RAZXhhbXBsZS5jb219JwogICAgICAtICdDRUxFUllfV09SS0VSX0FVVE9TQ0FMRT0ke0NFTEVSWV9XT1JLRVJfQVVUT1NDQUxFOi0xLDN9JwogICAgICAtICdDRUxFUllfV09SS0VSX01BWF9UQVNLU19QRVJfQ0hJTEQ9JHtDRUxFUllfV09SS0VSX01BWF9UQVNLU19QRVJfQ0hJTEQ6LTEwMDAwfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3VwbG9hZHM6L2NvZGUvdXBsb2FkcycKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBlY2hvCiAgICAgICAgLSBvawogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgbWlncmF0ZToKICAgIGltYWdlOiBnbGl0Y2h0aXAvZ2xpdGNodGlwCiAgICByZXN0YXJ0OiAnbm8nCiAgICBkZXBlbmRzX29uOgogICAgICAtIHBvc3RncmVzCiAgICAgIC0gcmVkaXMKICAgIGNvbW1hbmQ6ICcuL21hbmFnZS5weSBtaWdyYXRlJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ0RBVEFCQVNFX1VSTD1wb3N0Z3JlczovLyRTRVJWSUNFX1VTRVJfUE9TVEdSRVNRTDokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMQHBvc3RncmVzOjU0MzIvJHtQT1NUR1JFU1FMX0RBVEFCQVNFOi1nbGl0Y2h0aXB9JwogICAgICAtIFNFQ1JFVF9LRVk9JFNFUlZJQ0VfQkFTRTY0XzY0X0VOQ1JZUFRJT04KICAgICAgLSAnRU1BSUxfVVJMPSR7RU1BSUxfVVJMOi1jb25zb2xlbWFpbDovL30nCiAgICAgIC0gJ0RFRkFVTFRfRlJPTV9FTUFJTD0ke0RFRkFVTFRfRlJPTV9FTUFJTDotdGVzdEBleGFtcGxlLmNvbX0nCiAgICAgIC0gJ0NFTEVSWV9XT1JLRVJfQVVUT1NDQUxFPSR7Q0VMRVJZX1dPUktFUl9BVVRPU0NBTEU6LTEsM30nCiAgICAgIC0gJ0NFTEVSWV9XT1JLRVJfTUFYX1RBU0tTX1BFUl9DSElMRD0ke0NFTEVSWV9XT1JLRVJfTUFYX1RBU0tTX1BFUl9DSElMRDotMTAwMDB9Jwo=",
|
||||
"compose": "c2VydmljZXM6CiAgcG9zdGdyZXM6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE2LWFscGluZScKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTUUx9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTH0nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNRTF9EQVRBQkFTRTotZ2xpdGNodGlwfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3BnLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIHJlZGlzOgogICAgaW1hZ2U6IHJlZGlzCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gcmVkaXMtY2xpCiAgICAgICAgLSBwaW5nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICB3ZWI6CiAgICBpbWFnZTogZ2xpdGNodGlwL2dsaXRjaHRpcAogICAgZGVwZW5kc19vbjoKICAgICAgLSBwb3N0Z3JlcwogICAgICAtIHJlZGlzCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fR0xJVENIVElQXzgwODAKICAgICAgLSAnREFUQUJBU0VfVVJMPXBvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMOiRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTUUxAcG9zdGdyZXM6NTQzMi8ke1BPU1RHUkVTUUxfREFUQUJBU0U6LWdsaXRjaHRpcH0nCiAgICAgIC0gU0VDUkVUX0tFWT0kU0VSVklDRV9CQVNFNjRfNjRfRU5DUllQVElPTgogICAgICAtICdFTUFJTF9VUkw9JHtFTUFJTF9VUkw6LWNvbnNvbGVtYWlsOi8vfScKICAgICAgLSAnR0xJVENIVElQX0RPTUFJTj0ke1NFUlZJQ0VfRlFETl9HTElUQ0hUSVB9JwogICAgICAtICdERUZBVUxUX0ZST01fRU1BSUw9JHtERUZBVUxUX0ZST01fRU1BSUw6LXRlc3RAZXhhbXBsZS5jb219JwogICAgICAtICdDRUxFUllfV09SS0VSX0FVVE9TQ0FMRT0ke0NFTEVSWV9XT1JLRVJfQVVUT1NDQUxFOi0xLDN9JwogICAgICAtICdDRUxFUllfV09SS0VSX01BWF9UQVNLU19QRVJfQ0hJTEQ9JHtDRUxFUllfV09SS0VSX01BWF9UQVNLU19QRVJfQ0hJTEQ6LTEwMDAwfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3VwbG9hZHM6L2NvZGUvdXBsb2FkcycKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBlY2hvCiAgICAgICAgLSBvawogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgd29ya2VyOgogICAgaW1hZ2U6IGdsaXRjaHRpcC9nbGl0Y2h0aXAKICAgIGNvbW1hbmQ6IC4vYmluL3J1bi1jZWxlcnktd2l0aC1iZWF0LnNoCiAgICBkZXBlbmRzX29uOgogICAgICAtIHBvc3RncmVzCiAgICAgIC0gcmVkaXMKICAgIGVudmlyb25tZW50OgogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXM6Ly8kU0VSVklDRV9VU0VSX1BPU1RHUkVTUUw6JFNFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTEBwb3N0Z3Jlczo1NDMyLyR7UE9TVEdSRVNRTF9EQVRBQkFTRTotZ2xpdGNodGlwfScKICAgICAgLSBTRUNSRVRfS0VZPSRTRVJWSUNFX0JBU0U2NF82NF9FTkNSWVBUSU9OCiAgICAgIC0gJ0VNQUlMX1VSTD0ke0VNQUlMX1VSTDotY29uc29sZW1haWw6Ly99JwogICAgICAtICdERUZBVUxUX0ZST01fRU1BSUw9JHtERUZBVUxUX0ZST01fRU1BSUw6LXRlc3RAZXhhbXBsZS5jb219JwogICAgICAtICdDRUxFUllfV09SS0VSX0FVVE9TQ0FMRT0ke0NFTEVSWV9XT1JLRVJfQVVUT1NDQUxFOi0xLDN9JwogICAgICAtICdDRUxFUllfV09SS0VSX01BWF9UQVNLU19QRVJfQ0hJTEQ9JHtDRUxFUllfV09SS0VSX01BWF9UQVNLU19QRVJfQ0hJTEQ6LTEwMDAwfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3VwbG9hZHM6L2NvZGUvdXBsb2FkcycKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBlY2hvCiAgICAgICAgLSBvawogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgbWlncmF0ZToKICAgIGltYWdlOiBnbGl0Y2h0aXAvZ2xpdGNodGlwCiAgICByZXN0YXJ0OiAnbm8nCiAgICBkZXBlbmRzX29uOgogICAgICAtIHBvc3RncmVzCiAgICAgIC0gcmVkaXMKICAgIGNvbW1hbmQ6ICcuL21hbmFnZS5weSBtaWdyYXRlJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ0RBVEFCQVNFX1VSTD1wb3N0Z3JlczovLyRTRVJWSUNFX1VTRVJfUE9TVEdSRVNRTDokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMQHBvc3RncmVzOjU0MzIvJHtQT1NUR1JFU1FMX0RBVEFCQVNFOi1nbGl0Y2h0aXB9JwogICAgICAtIFNFQ1JFVF9LRVk9JFNFUlZJQ0VfQkFTRTY0XzY0X0VOQ1JZUFRJT04KICAgICAgLSAnRU1BSUxfVVJMPSR7RU1BSUxfVVJMOi1jb25zb2xlbWFpbDovL30nCiAgICAgIC0gJ0RFRkFVTFRfRlJPTV9FTUFJTD0ke0RFRkFVTFRfRlJPTV9FTUFJTDotdGVzdEBleGFtcGxlLmNvbX0nCiAgICAgIC0gJ0NFTEVSWV9XT1JLRVJfQVVUT1NDQUxFPSR7Q0VMRVJZX1dPUktFUl9BVVRPU0NBTEU6LTEsM30nCiAgICAgIC0gJ0NFTEVSWV9XT1JLRVJfTUFYX1RBU0tTX1BFUl9DSElMRD0ke0NFTEVSWV9XT1JLRVJfTUFYX1RBU0tTX1BFUl9DSElMRDotMTAwMDB9Jwo=",
|
||||
"tags": [
|
||||
"error",
|
||||
"tracking",
|
||||
@@ -505,6 +505,20 @@
|
||||
"minversion": "0.0.0",
|
||||
"port": "7512"
|
||||
},
|
||||
"listmonk": {
|
||||
"documentation": "https:\/\/listmonk.app\/",
|
||||
"slogan": "Self-hosted newsletter and mailing list manager",
|
||||
"compose": "c2VydmljZXM6CiAgbGlzdG1vbms6CiAgICBpbWFnZTogJ2xpc3Rtb25rL2xpc3Rtb25rOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9MSVNUTU9OS185MDAwCiAgICAgIC0gJ0xJU1RNT05LX2FwcF9fYWRkcmVzcz0wLjAuMC4wOjkwMDAnCiAgICAgIC0gTElTVE1PTktfZGJfX2hvc3Q9cG9zdGdyZXMKICAgICAgLSBMSVNUTU9OS19kYl9fbmFtZT1saXN0bW9uawogICAgICAtIExJU1RNT05LX2RiX191c2VyPSRTRVJWSUNFX1VTRVJfUE9TVEdSRVMKICAgICAgLSBMSVNUTU9OS19kYl9fcGFzc3dvcmQ9JFNFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVMKICAgICAgLSBMSVNUTU9OS19kYl9fcG9ydD01NDMyCiAgICAgIC0gTElTVE1PTktfYXBwX19hZG1pbl91c2VybmFtZT1hZG1pbgogICAgICAtIExJU1RNT05LX2FwcF9fYWRtaW5fcGFzc3dvcmQ9JFNFUlZJQ0VfUEFTU1dPUkRfQURNSU4KICAgICAgLSBUWj1FdGMvVVRDCiAgICB2b2x1bWVzOgogICAgICAtICdsaXN0bW9uay1kYXRhOi9saXN0bW9uay91cGxvYWRzJwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSB3Z2V0CiAgICAgICAgLSAnLXEnCiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo5MDAwJwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgbGlzdG1vbmstaW5pdGlhbC1kYXRhYmFzZS1zZXR1cDoKICAgIGltYWdlOiAnbGlzdG1vbmsvbGlzdG1vbms6bGF0ZXN0JwogICAgY29tbWFuZDogJy4vbGlzdG1vbmsgLS1pbnN0YWxsIC0teWVzIC0taWRlbXBvdGVudCcKICAgIHJlc3RhcnQ6ICdubycKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBMSVNUTU9OS19kYl9faG9zdD1wb3N0Z3JlcwogICAgICAtIExJU1RNT05LX2RiX19uYW1lPWxpc3Rtb25rCiAgICAgIC0gTElTVE1PTktfZGJfX3VzZXI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogICAgICAtIExJU1RNT05LX2RiX19wYXNzd29yZD0kU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgICAtIExJU1RNT05LX2RiX19wb3J0PTU0MzIKICBwb3N0Z3JlczoKICAgIGltYWdlOiAncG9zdGdyZXM6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9TVEdSRVNfREI9bGlzdG1vbmsKICAgICAgLSBQT1NUR1JFU19QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgICAtIFBPU1RHUkVTX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogICAgdm9sdW1lczoKICAgICAgLSAncGctZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==",
|
||||
"tags": [
|
||||
"newsletter",
|
||||
"mailing list",
|
||||
"self-hosted",
|
||||
"open source"
|
||||
],
|
||||
"logo": "svgs\/listmonk.svg",
|
||||
"minversion": "0.0.0",
|
||||
"port": "9000"
|
||||
},
|
||||
"logto": {
|
||||
"documentation": "https:\/\/docs.logto.io\/docs\/tutorials\/get-started\/#logto-oss-self-hosted",
|
||||
"slogan": "Logto offers a comprehensive identity solution covering both the front and backend, complete with pre-built infrastructure and enterprise-grade solutions.",
|
||||
@@ -731,7 +745,7 @@
|
||||
"penpot": {
|
||||
"documentation": "https:\/\/help.penpot.app\/technical-guide\/getting-started\/#install-with-docker",
|
||||
"slogan": "Penpot is the first Open Source design and prototyping platform for product teams.",
|
||||
"compose": "dmVyc2lvbjogJzMuNScKc2VydmljZXM6CiAgZnJvbnRlbmQ6CiAgICBpbWFnZTogJ3BlbnBvdGFwcC9mcm9udGVuZDpsYXRlc3QnCiAgICB2b2x1bWVzOgogICAgICAtICdwZW5wb3QtYXNzZXRzOi9vcHQvZGF0YS9hc3NldHMnCiAgICBkZXBlbmRzX29uOgogICAgICAtIHBlbnBvdC1iYWNrZW5kCiAgICAgIC0gcGVucG90LWV4cG9ydGVyCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fRlJPTlRFTkQKICAgICAgLSAnUEVOUE9UX0ZMQUdTPSR7UEVOUE9UX0ZST05URU5EX0ZMQUdTOi1lbmFibGUtbG9naW4td2l0aC1wYXNzd29yZH0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly9sb2NhbGhvc3Q6ODAnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUKICBwZW5wb3QtYmFja2VuZDoKICAgIGltYWdlOiAncGVucG90YXBwL2JhY2tlbmQ6bGF0ZXN0JwogICAgdm9sdW1lczoKICAgICAgLSAncGVucG90LWFzc2V0czovb3B0L2RhdGEvYXNzZXRzJwogICAgZGVwZW5kc19vbjoKICAgICAgLSBwb3N0Z3JlcwogICAgICAtIHJlZGlzCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUEVOUE9UX0ZMQUdTPSR7UEVOUE9UX0JBQ0tFTkRfRkxBR1M6LWVuYWJsZS1sb2dpbi13aXRoLXBhc3N3b3JkIGVuYWJsZS1zbXRwIGVuYWJsZS1wcmVwbC1zZXJ2ZXJ9JwogICAgICAtIFBFTlBPVF9IVFRQX1NFUlZFUl9QT1JUPTYwNjAKICAgICAgLSBQRU5QT1RfU0VDUkVUX0tFWT0kU0VSVklDRV9SRUFMQkFTRTY0XzY0X1BFTlBPVAogICAgICAtIFBFTlBPVF9QVUJMSUNfVVJJPSRTRVJWSUNFX0ZRRE5fRlJPTlRFTkQKICAgICAgLSAnUEVOUE9UX0JBQ0tFTkRfVVJJPWh0dHA6Ly9wZW5wb3QtYmFja2VuZCcKICAgICAgLSAnUEVOUE9UX0VYUE9SVEVSX1VSST1odHRwOi8vcGVucG90LWV4cG9ydGVyJwogICAgICAtICdQRU5QT1RfREFUQUJBU0VfVVJJPXBvc3RncmVzcWw6Ly9wb3N0Z3Jlcy8ke1BPU1RHUkVTX0RCOi1wZW5wb3R9JwogICAgICAtICdQRU5QT1RfREFUQUJBU0VfVVNFUk5BTUU9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdQRU5QT1RfREFUQUJBU0VfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICAgICAgLSAnUEVOUE9UX1JFRElTX1VSST1yZWRpczovL3JlZGlzLzAnCiAgICAgIC0gUEVOUE9UX0FTU0VUU19TVE9SQUdFX0JBQ0tFTkQ9YXNzZXRzLWZzCiAgICAgIC0gUEVOUE9UX1NUT1JBR0VfQVNTRVRTX0ZTX0RJUkVDVE9SWT0vb3B0L2RhdGEvYXNzZXRzCiAgICAgIC0gJ1BFTlBPVF9URUxFTUVUUllfRU5BQkxFRD0ke1BFTlBPVF9URUxFTUVUUllfRU5BQkxFRDotZmFsc2V9JwogICAgICAtICdQRU5QT1RfU01UUF9ERUZBVUxUX0ZST009JHtQRU5QT1RfU01UUF9ERUZBVUxUX0ZST006LW5vLXJlcGx5QGV4YW1wbGUuY29tfScKICAgICAgLSAnUEVOUE9UX1NNVFBfREVGQVVMVF9SRVBMWV9UTz0ke1BFTlBPVF9TTVRQX0RFRkFVTFRfUkVQTFlfVE86LW5vLXJlcGx5QGV4YW1wbGUuY29tfScKICAgICAgLSAnUEVOUE9UX1NNVFBfSE9TVD0ke1BFTlBPVF9TTVRQX0hPU1Q6LW1haWxwaXR9JwogICAgICAtICdQRU5QT1RfU01UUF9QT1JUPSR7UEVOUE9UX1NNVFBfUE9SVDotMTAyNX0nCiAgICAgIC0gJ1BFTlBPVF9TTVRQX1VTRVJOQU1FPSR7UEVOUE9UX1NNVFBfVVNFUk5BTUU6LXBlbnBvdH0nCiAgICAgIC0gJ1BFTlBPVF9TTVRQX1BBU1NXT1JEPSR7UEVOUE9UX1NNVFBfUEFTU1dPUkQ6LXBlbnBvdH0nCiAgICAgIC0gJ1BFTlBPVF9TTVRQX1RMUz0ke1BFTlBPVF9TTVRQX1RMUzotZmFsc2V9JwogICAgICAtICdQRU5QT1RfU01UUF9TU0w9JHtQRU5QT1RfU01UUF9TU0w6LWZhbHNlfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDo2MDYwJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1CiAgcGVucG90LWV4cG9ydGVyOgogICAgaW1hZ2U6ICdwZW5wb3RhcHAvZXhwb3J0ZXI6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUEVOUE9UX1BVQkxJQ19VUkk9JFNFUlZJQ0VfRlFETl9GUk9OVEVORAogICAgICAtICdQRU5QT1RfUkVESVNfVVJJPXJlZGlzOi8vcmVkaXMvMCcKICBtYWlscGl0OgogICAgaW1hZ2U6ICdheGxsZW50L21haWxwaXQ6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX01BSUxQSVRfODAyNQogIHBvc3RncmVzOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3BlbnBvdC1wb3N0Z3Jlc3FsLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9TVEdSRVNfSU5JVERCX0FSR1M9LS1kYXRhLWNoZWNrc3VtcwogICAgICAtIFBPU1RHUkVTX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogICAgICAtIFBPU1RHUkVTX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LXBlbnBvdH0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOjctYWxwaW5lJwogICAgY29tbWFuZDogJ3JlZGlzLXNlcnZlciAtLWFwcGVuZG9ubHkgeWVzJwogICAgdm9sdW1lczoKICAgICAgLSAncGVucG90LXJlZGlzLWRhdGE6L2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gcmVkaXMtY2xpCiAgICAgICAgLSBwaW5nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK",
|
||||
"compose": "c2VydmljZXM6CiAgZnJvbnRlbmQ6CiAgICBpbWFnZTogJ3BlbnBvdGFwcC9mcm9udGVuZDpsYXRlc3QnCiAgICB2b2x1bWVzOgogICAgICAtICdwZW5wb3QtYXNzZXRzOi9vcHQvZGF0YS9hc3NldHMnCiAgICBkZXBlbmRzX29uOgogICAgICAtIHBlbnBvdC1iYWNrZW5kCiAgICAgIC0gcGVucG90LWV4cG9ydGVyCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fRlJPTlRFTkQKICAgICAgLSAnUEVOUE9UX0ZMQUdTPSR7UEVOUE9UX0ZST05URU5EX0ZMQUdTOi1lbmFibGUtbG9naW4td2l0aC1wYXNzd29yZH0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly9sb2NhbGhvc3Q6ODAnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUKICBwZW5wb3QtYmFja2VuZDoKICAgIGltYWdlOiAncGVucG90YXBwL2JhY2tlbmQ6bGF0ZXN0JwogICAgdm9sdW1lczoKICAgICAgLSAncGVucG90LWFzc2V0czovb3B0L2RhdGEvYXNzZXRzJwogICAgZGVwZW5kc19vbjoKICAgICAgLSBwb3N0Z3JlcwogICAgICAtIHJlZGlzCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUEVOUE9UX0ZMQUdTPSR7UEVOUE9UX0JBQ0tFTkRfRkxBR1M6LWVuYWJsZS1sb2dpbi13aXRoLXBhc3N3b3JkIGVuYWJsZS1zbXRwIGVuYWJsZS1wcmVwbC1zZXJ2ZXJ9JwogICAgICAtIFBFTlBPVF9IVFRQX1NFUlZFUl9QT1JUPTYwNjAKICAgICAgLSBQRU5QT1RfU0VDUkVUX0tFWT0kU0VSVklDRV9SRUFMQkFTRTY0XzY0X1BFTlBPVAogICAgICAtIFBFTlBPVF9QVUJMSUNfVVJJPSRTRVJWSUNFX0ZRRE5fRlJPTlRFTkQKICAgICAgLSAnUEVOUE9UX0JBQ0tFTkRfVVJJPWh0dHA6Ly9wZW5wb3QtYmFja2VuZCcKICAgICAgLSAnUEVOUE9UX0VYUE9SVEVSX1VSST1odHRwOi8vcGVucG90LWV4cG9ydGVyJwogICAgICAtICdQRU5QT1RfREFUQUJBU0VfVVJJPXBvc3RncmVzcWw6Ly9wb3N0Z3Jlcy8ke1BPU1RHUkVTX0RCOi1wZW5wb3R9JwogICAgICAtICdQRU5QT1RfREFUQUJBU0VfVVNFUk5BTUU9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdQRU5QT1RfREFUQUJBU0VfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICAgICAgLSAnUEVOUE9UX1JFRElTX1VSST1yZWRpczovL3JlZGlzLzAnCiAgICAgIC0gUEVOUE9UX0FTU0VUU19TVE9SQUdFX0JBQ0tFTkQ9YXNzZXRzLWZzCiAgICAgIC0gUEVOUE9UX1NUT1JBR0VfQVNTRVRTX0ZTX0RJUkVDVE9SWT0vb3B0L2RhdGEvYXNzZXRzCiAgICAgIC0gJ1BFTlBPVF9URUxFTUVUUllfRU5BQkxFRD0ke1BFTlBPVF9URUxFTUVUUllfRU5BQkxFRDotZmFsc2V9JwogICAgICAtICdQRU5QT1RfU01UUF9ERUZBVUxUX0ZST009JHtQRU5QT1RfU01UUF9ERUZBVUxUX0ZST006LW5vLXJlcGx5QGV4YW1wbGUuY29tfScKICAgICAgLSAnUEVOUE9UX1NNVFBfREVGQVVMVF9SRVBMWV9UTz0ke1BFTlBPVF9TTVRQX0RFRkFVTFRfUkVQTFlfVE86LW5vLXJlcGx5QGV4YW1wbGUuY29tfScKICAgICAgLSAnUEVOUE9UX1NNVFBfSE9TVD0ke1BFTlBPVF9TTVRQX0hPU1Q6LW1haWxwaXR9JwogICAgICAtICdQRU5QT1RfU01UUF9QT1JUPSR7UEVOUE9UX1NNVFBfUE9SVDotMTAyNX0nCiAgICAgIC0gJ1BFTlBPVF9TTVRQX1VTRVJOQU1FPSR7UEVOUE9UX1NNVFBfVVNFUk5BTUU6LXBlbnBvdH0nCiAgICAgIC0gJ1BFTlBPVF9TTVRQX1BBU1NXT1JEPSR7UEVOUE9UX1NNVFBfUEFTU1dPUkQ6LXBlbnBvdH0nCiAgICAgIC0gJ1BFTlBPVF9TTVRQX1RMUz0ke1BFTlBPVF9TTVRQX1RMUzotZmFsc2V9JwogICAgICAtICdQRU5QT1RfU01UUF9TU0w9JHtQRU5QT1RfU01UUF9TU0w6LWZhbHNlfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDo2MDYwJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1CiAgcGVucG90LWV4cG9ydGVyOgogICAgaW1hZ2U6ICdwZW5wb3RhcHAvZXhwb3J0ZXI6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUEVOUE9UX1BVQkxJQ19VUkk9JFNFUlZJQ0VfRlFETl9GUk9OVEVORAogICAgICAtICdQRU5QT1RfUkVESVNfVVJJPXJlZGlzOi8vcmVkaXMvMCcKICBtYWlscGl0OgogICAgaW1hZ2U6ICdheGxsZW50L21haWxwaXQ6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX01BSUxQSVRfODAyNQogIHBvc3RncmVzOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3BlbnBvdC1wb3N0Z3Jlc3FsLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9TVEdSRVNfSU5JVERCX0FSR1M9LS1kYXRhLWNoZWNrc3VtcwogICAgICAtIFBPU1RHUkVTX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogICAgICAtIFBPU1RHUkVTX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LXBlbnBvdH0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOjctYWxwaW5lJwogICAgY29tbWFuZDogJ3JlZGlzLXNlcnZlciAtLWFwcGVuZG9ubHkgeWVzJwogICAgdm9sdW1lczoKICAgICAgLSAncGVucG90LXJlZGlzLWRhdGE6L2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gcmVkaXMtY2xpCiAgICAgICAgLSBwaW5nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK",
|
||||
"tags": [
|
||||
"penpot",
|
||||
"design",
|
||||
|
||||
+4
-1
@@ -1,7 +1,10 @@
|
||||
{
|
||||
"coolify": {
|
||||
"v4": {
|
||||
"version": "4.0.0-beta.276"
|
||||
"version": "4.0.0-beta.277"
|
||||
},
|
||||
"sentinel": {
|
||||
"version": "0.0.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user