mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-31 20:59:23 +00:00
Compare commits
49 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53975fcf61 | ||
|
|
76296c1f19 | ||
|
|
c25baf69e1 | ||
|
|
f952512615 | ||
|
|
c6557eada8 | ||
|
|
2c2d74c0d6 | ||
|
|
028a2eb275 | ||
|
|
ce7fad5bef | ||
|
|
cd7852e4f9 | ||
|
|
7b022a2482 | ||
|
|
12d9b6538b | ||
|
|
335788c2d6 | ||
|
|
2352e4a71d | ||
|
|
dc03179bd1 | ||
|
|
cc72f416e8 | ||
|
|
3b67d0a8de | ||
|
|
0135ba7e89 | ||
|
|
a28a28cd23 | ||
|
|
b52680a2d8 | ||
|
|
0670e6c1d6 | ||
|
|
c3882b75c1 | ||
|
|
64b6f86a36 | ||
|
|
b9efc22253 | ||
|
|
e3d9eb0154 | ||
|
|
66f3967479 | ||
|
|
c54439e84c | ||
|
|
db4a4c74fc | ||
|
|
5c7ef80219 | ||
|
|
243d1c06fc | ||
|
|
ef25f7d800 | ||
|
|
45640ffdb1 | ||
|
|
378291b209 | ||
|
|
3583e552f1 | ||
|
|
d7dfeaf988 | ||
|
|
7fe5eca661 | ||
|
|
0dff57e69f | ||
|
|
f4803ad58b | ||
|
|
2d7bbbe300 | ||
|
|
928b68043b | ||
|
|
b21add0210 | ||
|
|
c41ffd6bfb | ||
|
|
b4874c7df3 | ||
|
|
c505a6ce9c | ||
|
|
706e4b13ee | ||
|
|
4af471ee31 | ||
|
|
87062e4e22 | ||
|
|
500ba0fab8 | ||
|
|
1c72c127d5 | ||
|
|
69bb4ae5ee |
@@ -11,29 +11,34 @@ class StopApplication
|
|||||||
public function handle(Application $application)
|
public function handle(Application $application)
|
||||||
{
|
{
|
||||||
$server = $application->destination->server;
|
$server = $application->destination->server;
|
||||||
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
if ($server->isSwarm()) {
|
||||||
if ($containers->count() > 0) {
|
instant_remote_process(["docker stack rm {$application->uuid}" ], $server);
|
||||||
foreach ($containers as $container) {
|
} else {
|
||||||
$containerName = data_get($container, 'Names');
|
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
||||||
if ($containerName) {
|
if ($containers->count() > 0) {
|
||||||
instant_remote_process(
|
foreach ($containers as $container) {
|
||||||
["docker rm -f {$containerName}"],
|
$containerName = data_get($container, 'Names');
|
||||||
$server
|
if ($containerName) {
|
||||||
);
|
instant_remote_process(
|
||||||
|
["docker rm -f {$containerName}"],
|
||||||
|
$server
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// TODO: make notification for application
|
||||||
|
// $application->environment->project->team->notify(new StatusChanged($application));
|
||||||
}
|
}
|
||||||
// TODO: make notification for application
|
// Delete Preview Deployments
|
||||||
// $application->environment->project->team->notify(new StatusChanged($application));
|
$previewDeployments = $application->previews;
|
||||||
}
|
foreach ($previewDeployments as $previewDeployment) {
|
||||||
// Delete Preview Deployments
|
$containers = getCurrentApplicationContainerStatus($server, $application->id, $previewDeployment->pull_request_id);
|
||||||
$previewDeployments = $application->previews;
|
foreach ($containers as $container) {
|
||||||
foreach ($previewDeployments as $previewDeployment) {
|
$name = str_replace('/', '', $container['Names']);
|
||||||
$containers = getCurrentApplicationContainerStatus($server, $application->id, $previewDeployment->pull_request_id);
|
instant_remote_process(["docker rm -f $name"], $application->destination->server, throwError: false);
|
||||||
foreach ($containers as $container) {
|
}
|
||||||
$name = str_replace('/', '', $container['Names']);
|
|
||||||
instant_remote_process(["docker rm -f $name"], $application->destination->server, throwError: false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,35 +17,42 @@ class CheckProxy
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$status = getContainerStatus($server, 'coolify-proxy');
|
if ($server->isSwarm()) {
|
||||||
if ($status === 'running') {
|
$status = getContainerStatus($server, 'coolify-proxy_traefik');
|
||||||
$server->proxy->set('status', 'running');
|
$server->proxy->set('status', $status);
|
||||||
$server->save();
|
$server->save();
|
||||||
return false;
|
return false;
|
||||||
}
|
} else {
|
||||||
$ip = $server->ip;
|
$status = getContainerStatus($server, 'coolify-proxy');
|
||||||
if ($server->id === 0) {
|
if ($status === 'running') {
|
||||||
$ip = 'host.docker.internal';
|
$server->proxy->set('status', 'running');
|
||||||
}
|
$server->save();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$ip = $server->ip;
|
||||||
|
if ($server->id === 0) {
|
||||||
|
$ip = 'host.docker.internal';
|
||||||
|
}
|
||||||
|
|
||||||
$connection80 = @fsockopen($ip, '80');
|
$connection80 = @fsockopen($ip, '80');
|
||||||
$connection443 = @fsockopen($ip, '443');
|
$connection443 = @fsockopen($ip, '443');
|
||||||
$port80 = is_resource($connection80) && fclose($connection80);
|
$port80 = is_resource($connection80) && fclose($connection80);
|
||||||
$port443 = is_resource($connection443) && fclose($connection443);
|
$port443 = is_resource($connection443) && fclose($connection443);
|
||||||
if ($port80) {
|
if ($port80) {
|
||||||
if ($fromUI) {
|
if ($fromUI) {
|
||||||
throw new \Exception("Port 80 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
|
throw new \Exception("Port 80 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if ($port443) {
|
||||||
if ($port443) {
|
if ($fromUI) {
|
||||||
if ($fromUI) {
|
throw new \Exception("Port 443 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
|
||||||
throw new \Exception("Port 443 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
|
} else {
|
||||||
} else {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class StartProxy
|
|||||||
public function handle(Server $server, bool $async = true): string|Activity
|
public function handle(Server $server, bool $async = true): string|Activity
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|
||||||
$proxyType = $server->proxyType();
|
$proxyType = $server->proxyType();
|
||||||
$commands = collect([]);
|
$commands = collect([]);
|
||||||
$proxy_path = get_proxy_path();
|
$proxy_path = get_proxy_path();
|
||||||
@@ -24,18 +25,29 @@ class StartProxy
|
|||||||
$docker_compose_yml_base64 = base64_encode($configuration);
|
$docker_compose_yml_base64 = base64_encode($configuration);
|
||||||
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
||||||
$server->save();
|
$server->save();
|
||||||
$commands = $commands->merge([
|
if ($server->isSwarm()) {
|
||||||
"mkdir -p $proxy_path && cd $proxy_path",
|
$commands = $commands->merge([
|
||||||
"echo 'Creating required Docker Compose file.'",
|
"mkdir -p $proxy_path && cd $proxy_path",
|
||||||
"echo 'Pulling docker image.'",
|
"echo 'Creating required Docker Compose file.'",
|
||||||
'docker compose pull',
|
"echo 'Starting coolify-proxy.'",
|
||||||
"echo 'Stopping existing coolify-proxy.'",
|
"cd $proxy_path && docker stack deploy -c docker-compose.yml coolify-proxy",
|
||||||
"docker compose down -v --remove-orphans > /dev/null 2>&1",
|
"echo 'Proxy started successfully.'"
|
||||||
"echo 'Starting coolify-proxy.'",
|
]);
|
||||||
'docker compose up -d --remove-orphans',
|
} else {
|
||||||
"echo 'Proxy started successfully.'"
|
$commands = $commands->merge([
|
||||||
]);
|
"mkdir -p $proxy_path && cd $proxy_path",
|
||||||
$commands = $commands->merge(connectProxyToNetworks($server));
|
"echo 'Creating required Docker Compose file.'",
|
||||||
|
"echo 'Pulling docker image.'",
|
||||||
|
'docker compose pull',
|
||||||
|
"echo 'Stopping existing coolify-proxy.'",
|
||||||
|
"docker compose down -v --remove-orphans > /dev/null 2>&1",
|
||||||
|
"echo 'Starting coolify-proxy.'",
|
||||||
|
'docker compose up -d --remove-orphans',
|
||||||
|
"echo 'Proxy started successfully.'"
|
||||||
|
]);
|
||||||
|
$commands = $commands->merge(connectProxyToNetworks($server));
|
||||||
|
}
|
||||||
|
|
||||||
if ($async) {
|
if ($async) {
|
||||||
$activity = remote_process($commands, $server);
|
$activity = remote_process($commands, $server);
|
||||||
return $activity;
|
return $activity;
|
||||||
@@ -46,11 +58,9 @@ class StartProxy
|
|||||||
$server->save();
|
$server->save();
|
||||||
return 'OK';
|
return 'OK';
|
||||||
}
|
}
|
||||||
} catch(\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
ray($e);
|
ray($e);
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ class InstallDocker
|
|||||||
}
|
}
|
||||||
$command = $command->merge([
|
$command = $command->merge([
|
||||||
"echo 'Installing Docker Engine...'",
|
"echo 'Installing Docker Engine...'",
|
||||||
"curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh",
|
"curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh || curl https://get.docker.com | sh -s -- --version {$dockerVersion}",
|
||||||
"echo 'Configuring Docker Engine (merging existing configuration with the required)...'",
|
"echo 'Configuring Docker Engine (merging existing configuration with the required)...'",
|
||||||
"test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-`date +\"%Y%m%d-%H%M%S\"`\" || echo '{$config}' | base64 -d > /etc/docker/daemon.json",
|
"test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-`date +\"%Y%m%d-%H%M%S\"`\" || echo '{$config}' | base64 -d > /etc/docker/daemon.json",
|
||||||
"echo '{$config}' | base64 -d > /etc/docker/daemon.json.coolify",
|
"echo '{$config}' | base64 -d > /etc/docker/daemon.json.coolify",
|
||||||
@@ -76,10 +76,20 @@ class InstallDocker
|
|||||||
"echo 'Restarting Docker Engine...'",
|
"echo 'Restarting Docker Engine...'",
|
||||||
"systemctl enable docker >/dev/null 2>&1 || true",
|
"systemctl enable docker >/dev/null 2>&1 || true",
|
||||||
"systemctl restart docker",
|
"systemctl restart docker",
|
||||||
"echo 'Creating default Docker network (coolify)...'",
|
|
||||||
"docker network create --attachable coolify >/dev/null 2>&1 || true",
|
|
||||||
"echo 'Done!'"
|
|
||||||
]);
|
]);
|
||||||
|
if ($server->isSwarm()) {
|
||||||
|
$command = $command->merge([
|
||||||
|
"docker network create --attachable --driver overlay coolify-overlay >/dev/null 2>&1 || true",
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$command = $command->merge([
|
||||||
|
"docker network create --attachable coolify >/dev/null 2>&1 || true",
|
||||||
|
]);
|
||||||
|
$command = $command->merge([
|
||||||
|
"echo 'Done!'",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
return remote_process($command, $server);
|
return remote_process($command, $server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ class InstallLogDrain
|
|||||||
$type = 'highlight';
|
$type = 'highlight';
|
||||||
} else if ($server->settings->is_logdrain_axiom_enabled) {
|
} else if ($server->settings->is_logdrain_axiom_enabled) {
|
||||||
$type = 'axiom';
|
$type = 'axiom';
|
||||||
|
} else if ($server->settings->is_logdrain_custom_enabled) {
|
||||||
|
$type = 'custom';
|
||||||
} else {
|
} else {
|
||||||
$type = 'none';
|
$type = 'none';
|
||||||
}
|
}
|
||||||
@@ -114,15 +116,23 @@ class InstallLogDrain
|
|||||||
json_date_format iso8601
|
json_date_format iso8601
|
||||||
tls On
|
tls On
|
||||||
");
|
");
|
||||||
|
} else if ($type === 'custom') {
|
||||||
|
if (!$server->settings->is_logdrain_custom_enabled) {
|
||||||
|
throw new \Exception('Custom log drain is not enabled.');
|
||||||
|
}
|
||||||
|
$config = base64_encode($server->settings->logdrain_custom_config);
|
||||||
|
$parsers = base64_encode($server->settings->logdrain_custom_config_parser);
|
||||||
} else {
|
} else {
|
||||||
throw new \Exception('Unknown log drain type.');
|
throw new \Exception('Unknown log drain type.');
|
||||||
}
|
}
|
||||||
$parsers = base64_encode("
|
if ($type !== 'custom') {
|
||||||
|
$parsers = base64_encode("
|
||||||
[PARSER]
|
[PARSER]
|
||||||
Name empty_line_skipper
|
Name empty_line_skipper
|
||||||
Format regex
|
Format regex
|
||||||
Regex /^(?!\s*$).+/
|
Regex /^(?!\s*$).+/
|
||||||
");
|
");
|
||||||
|
}
|
||||||
$compose = base64_encode("
|
$compose = base64_encode("
|
||||||
services:
|
services:
|
||||||
coolify-log-drain:
|
coolify-log-drain:
|
||||||
@@ -179,6 +189,12 @@ Files:
|
|||||||
"echo AXIOM_DATASET_NAME={$server->settings->logdrain_axiom_dataset_name} >> $config_path/.env",
|
"echo AXIOM_DATASET_NAME={$server->settings->logdrain_axiom_dataset_name} >> $config_path/.env",
|
||||||
"echo AXIOM_API_KEY={$server->settings->logdrain_axiom_api_key} >> $config_path/.env",
|
"echo AXIOM_API_KEY={$server->settings->logdrain_axiom_api_key} >> $config_path/.env",
|
||||||
];
|
];
|
||||||
|
} else if ($type === 'custom') {
|
||||||
|
$add_envs_command = [
|
||||||
|
"touch $config_path/.env"
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
throw new \Exception('Unknown log drain type.');
|
||||||
}
|
}
|
||||||
$restart_command = [
|
$restart_command = [
|
||||||
"echo 'Stopping old Fluent Bit'",
|
"echo 'Stopping old Fluent Bit'",
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class StartService
|
|||||||
$commands[] = "echo 'Pulling images.'";
|
$commands[] = "echo 'Pulling images.'";
|
||||||
$commands[] = "docker compose pull";
|
$commands[] = "docker compose pull";
|
||||||
$commands[] = "echo 'Starting containers.'";
|
$commands[] = "echo 'Starting containers.'";
|
||||||
$commands[] = "docker compose up -d --remove-orphans --force-recreate";
|
$commands[] = "docker compose up -d --remove-orphans --force-recreate --build";
|
||||||
$commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true";
|
$commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true";
|
||||||
$compose = data_get($service,'docker_compose',[]);
|
$compose = data_get($service,'docker_compose',[]);
|
||||||
$serviceNames = data_get(Yaml::parse($compose),'services',[]);
|
$serviceNames = data_get(Yaml::parse($compose),'services',[]);
|
||||||
|
|||||||
@@ -30,20 +30,21 @@ class Init extends Command
|
|||||||
$this->alive();
|
$this->alive();
|
||||||
$cleanup = $this->option('cleanup');
|
$cleanup = $this->option('cleanup');
|
||||||
if ($cleanup) {
|
if ($cleanup) {
|
||||||
|
echo "Running cleanup\n";
|
||||||
$this->cleanup_stucked_resources();
|
$this->cleanup_stucked_resources();
|
||||||
$this->cleanup_ssh();
|
// $this->cleanup_ssh();
|
||||||
}
|
}
|
||||||
$this->cleanup_in_progress_application_deployments();
|
$this->cleanup_in_progress_application_deployments();
|
||||||
$this->cleanup_stucked_helper_containers();
|
$this->cleanup_stucked_helper_containers();
|
||||||
}
|
}
|
||||||
private function cleanup_stucked_helper_containers() {
|
private function cleanup_stucked_helper_containers()
|
||||||
|
{
|
||||||
$servers = Server::all();
|
$servers = Server::all();
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
if ($server->isFunctional()) {
|
if ($server->isFunctional()) {
|
||||||
CleanupHelperContainersJob::dispatch($server);
|
CleanupHelperContainersJob::dispatch($server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
private function alive()
|
private function alive()
|
||||||
{
|
{
|
||||||
@@ -62,21 +63,23 @@ class Init extends Command
|
|||||||
echo "Error in alive: {$e->getMessage()}\n";
|
echo "Error in alive: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function cleanup_ssh()
|
// private function cleanup_ssh()
|
||||||
{
|
// {
|
||||||
try {
|
|
||||||
$files = Storage::allFiles('ssh/keys');
|
// TODO: it will cleanup id.root@host.docker.internal
|
||||||
foreach ($files as $file) {
|
// try {
|
||||||
Storage::delete($file);
|
// $files = Storage::allFiles('ssh/keys');
|
||||||
}
|
// foreach ($files as $file) {
|
||||||
$files = Storage::allFiles('ssh/mux');
|
// Storage::delete($file);
|
||||||
foreach ($files as $file) {
|
// }
|
||||||
Storage::delete($file);
|
// $files = Storage::allFiles('ssh/mux');
|
||||||
}
|
// foreach ($files as $file) {
|
||||||
} catch (\Throwable $e) {
|
// Storage::delete($file);
|
||||||
echo "Error in cleaning ssh: {$e->getMessage()}\n";
|
// }
|
||||||
}
|
// } catch (\Throwable $e) {
|
||||||
}
|
// echo "Error in cleaning ssh: {$e->getMessage()}\n";
|
||||||
|
// }
|
||||||
|
// }
|
||||||
private function cleanup_in_progress_application_deployments()
|
private function cleanup_in_progress_application_deployments()
|
||||||
{
|
{
|
||||||
// Cleanup any failed deployments
|
// Cleanup any failed deployments
|
||||||
@@ -98,15 +101,15 @@ class Init extends Command
|
|||||||
$applications = Application::all();
|
$applications = Application::all();
|
||||||
foreach ($applications as $application) {
|
foreach ($applications as $application) {
|
||||||
if (!data_get($application, 'environment')) {
|
if (!data_get($application, 'environment')) {
|
||||||
ray('Application without environment', $application->name);
|
echo 'Application without environment' . $application->name . 'deleting\n';
|
||||||
$application->delete();
|
|
||||||
}
|
|
||||||
if (!data_get($application, 'destination.server')) {
|
|
||||||
ray('Application without server', $application->name);
|
|
||||||
$application->delete();
|
$application->delete();
|
||||||
}
|
}
|
||||||
if (!$application->destination()) {
|
if (!$application->destination()) {
|
||||||
ray('Application without destination', $application->name);
|
echo 'Application without destination' . $application->name . 'deleting\n';
|
||||||
|
$application->delete();
|
||||||
|
}
|
||||||
|
if (!data_get($application, 'destination.server')) {
|
||||||
|
echo 'Application without server' . $application->name . 'deleting\n';
|
||||||
$application->delete();
|
$application->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,15 +120,15 @@ class Init extends Command
|
|||||||
$postgresqls = StandalonePostgresql::all();
|
$postgresqls = StandalonePostgresql::all();
|
||||||
foreach ($postgresqls as $postgresql) {
|
foreach ($postgresqls as $postgresql) {
|
||||||
if (!data_get($postgresql, 'environment')) {
|
if (!data_get($postgresql, 'environment')) {
|
||||||
ray('Postgresql without environment', $postgresql->name);
|
echo 'Postgresql without environment' . $postgresql->name . 'deleting\n';
|
||||||
$postgresql->delete();
|
|
||||||
}
|
|
||||||
if (!data_get($postgresql, 'destination.server')) {
|
|
||||||
ray('Postgresql without server', $postgresql->name);
|
|
||||||
$postgresql->delete();
|
$postgresql->delete();
|
||||||
}
|
}
|
||||||
if (!$postgresql->destination()) {
|
if (!$postgresql->destination()) {
|
||||||
ray('Postgresql without destination', $postgresql->name);
|
echo 'Postgresql without destination' . $postgresql->name . 'deleting\n';
|
||||||
|
$postgresql->delete();
|
||||||
|
}
|
||||||
|
if (!data_get($postgresql, 'destination.server')) {
|
||||||
|
echo 'Postgresql without server' . $postgresql->name . 'deleting\n';
|
||||||
$postgresql->delete();
|
$postgresql->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,15 +139,15 @@ class Init extends Command
|
|||||||
$redis = StandaloneRedis::all();
|
$redis = StandaloneRedis::all();
|
||||||
foreach ($redis as $redis) {
|
foreach ($redis as $redis) {
|
||||||
if (!data_get($redis, 'environment')) {
|
if (!data_get($redis, 'environment')) {
|
||||||
ray('Redis without environment', $redis->name);
|
echo 'Redis without environment' . $redis->name . 'deleting\n';
|
||||||
$redis->delete();
|
|
||||||
}
|
|
||||||
if (!data_get($redis, 'destination.server')) {
|
|
||||||
ray('Redis without server', $redis->name);
|
|
||||||
$redis->delete();
|
$redis->delete();
|
||||||
}
|
}
|
||||||
if (!$redis->destination()) {
|
if (!$redis->destination()) {
|
||||||
ray('Redis without destination', $redis->name);
|
echo 'Redis without destination' . $redis->name . 'deleting\n';
|
||||||
|
$redis->delete();
|
||||||
|
}
|
||||||
|
if (!data_get($redis, 'destination.server')) {
|
||||||
|
echo 'Redis without server' . $redis->name . 'deleting\n';
|
||||||
$redis->delete();
|
$redis->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,15 +159,15 @@ class Init extends Command
|
|||||||
$mongodbs = StandaloneMongodb::all();
|
$mongodbs = StandaloneMongodb::all();
|
||||||
foreach ($mongodbs as $mongodb) {
|
foreach ($mongodbs as $mongodb) {
|
||||||
if (!data_get($mongodb, 'environment')) {
|
if (!data_get($mongodb, 'environment')) {
|
||||||
ray('Mongodb without environment', $mongodb->name);
|
echo 'Mongodb without environment' . $mongodb->name . 'deleting\n';
|
||||||
$mongodb->delete();
|
|
||||||
}
|
|
||||||
if (!data_get($mongodb, 'destination.server')) {
|
|
||||||
ray('Mongodb without server', $mongodb->name);
|
|
||||||
$mongodb->delete();
|
$mongodb->delete();
|
||||||
}
|
}
|
||||||
if (!$mongodb->destination()) {
|
if (!$mongodb->destination()) {
|
||||||
ray('Mongodb without destination', $mongodb->name);
|
echo 'Mongodb without destination' . $mongodb->name . 'deleting\n';
|
||||||
|
$mongodb->delete();
|
||||||
|
}
|
||||||
|
if (!data_get($mongodb, 'destination.server')) {
|
||||||
|
echo 'Mongodb without server' . $mongodb->name . 'deleting\n';
|
||||||
$mongodb->delete();
|
$mongodb->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,15 +179,15 @@ class Init extends Command
|
|||||||
$mysqls = StandaloneMysql::all();
|
$mysqls = StandaloneMysql::all();
|
||||||
foreach ($mysqls as $mysql) {
|
foreach ($mysqls as $mysql) {
|
||||||
if (!data_get($mysql, 'environment')) {
|
if (!data_get($mysql, 'environment')) {
|
||||||
ray('Mysql without environment', $mysql->name);
|
echo 'Mysql without environment' . $mysql->name . 'deleting\n';
|
||||||
$mysql->delete();
|
|
||||||
}
|
|
||||||
if (!data_get($mysql, 'destination.server')) {
|
|
||||||
ray('Mysql without server', $mysql->name);
|
|
||||||
$mysql->delete();
|
$mysql->delete();
|
||||||
}
|
}
|
||||||
if (!$mysql->destination()) {
|
if (!$mysql->destination()) {
|
||||||
ray('Mysql without destination', $mysql->name);
|
echo 'Mysql without destination' . $mysql->name . 'deleting\n';
|
||||||
|
$mysql->delete();
|
||||||
|
}
|
||||||
|
if (!data_get($mysql, 'destination.server')) {
|
||||||
|
echo 'Mysql without server' . $mysql->name . 'deleting\n';
|
||||||
$mysql->delete();
|
$mysql->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,15 +199,15 @@ class Init extends Command
|
|||||||
$mariadbs = StandaloneMariadb::all();
|
$mariadbs = StandaloneMariadb::all();
|
||||||
foreach ($mariadbs as $mariadb) {
|
foreach ($mariadbs as $mariadb) {
|
||||||
if (!data_get($mariadb, 'environment')) {
|
if (!data_get($mariadb, 'environment')) {
|
||||||
ray('Mariadb without environment', $mariadb->name);
|
echo 'Mariadb without environment' . $mariadb->name . 'deleting\n';
|
||||||
$mariadb->delete();
|
|
||||||
}
|
|
||||||
if (!data_get($mariadb, 'destination.server')) {
|
|
||||||
ray('Mariadb without server', $mariadb->name);
|
|
||||||
$mariadb->delete();
|
$mariadb->delete();
|
||||||
}
|
}
|
||||||
if (!$mariadb->destination()) {
|
if (!$mariadb->destination()) {
|
||||||
ray('Mariadb without destination', $mariadb->name);
|
echo 'Mariadb without destination' . $mariadb->name . 'deleting\n';
|
||||||
|
$mariadb->delete();
|
||||||
|
}
|
||||||
|
if (!data_get($mariadb, 'destination.server')) {
|
||||||
|
echo 'Mariadb without server' . $mariadb->name . 'deleting\n';
|
||||||
$mariadb->delete();
|
$mariadb->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -216,15 +219,15 @@ class Init extends Command
|
|||||||
$services = Service::all();
|
$services = Service::all();
|
||||||
foreach ($services as $service) {
|
foreach ($services as $service) {
|
||||||
if (!data_get($service, 'environment')) {
|
if (!data_get($service, 'environment')) {
|
||||||
ray('Service without environment', $service->name);
|
echo 'Service without environment' . $service->name . 'deleting\n';
|
||||||
$service->delete();
|
|
||||||
}
|
|
||||||
if (!data_get($service, 'server')) {
|
|
||||||
ray('Service without server', $service->name);
|
|
||||||
$service->delete();
|
$service->delete();
|
||||||
}
|
}
|
||||||
if (!$service->destination()) {
|
if (!$service->destination()) {
|
||||||
ray('Service without destination', $service->name);
|
echo 'Service without destination' . $service->name . 'deleting\n';
|
||||||
|
$service->delete();
|
||||||
|
}
|
||||||
|
if (!data_get($service, 'server')) {
|
||||||
|
echo 'Service without server' . $service->name . 'deleting\n';
|
||||||
$service->delete();
|
$service->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -235,7 +238,7 @@ class Init extends Command
|
|||||||
$serviceApplications = ServiceApplication::all();
|
$serviceApplications = ServiceApplication::all();
|
||||||
foreach ($serviceApplications as $service) {
|
foreach ($serviceApplications as $service) {
|
||||||
if (!data_get($service, 'service')) {
|
if (!data_get($service, 'service')) {
|
||||||
ray('ServiceApplication without service', $service->name);
|
echo 'ServiceApplication without service' . $service->name . 'deleting\n';
|
||||||
$service->delete();
|
$service->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,7 +249,7 @@ class Init extends Command
|
|||||||
$serviceDatabases = ServiceDatabase::all();
|
$serviceDatabases = ServiceDatabase::all();
|
||||||
foreach ($serviceDatabases as $service) {
|
foreach ($serviceDatabases as $service) {
|
||||||
if (!data_get($service, 'service')) {
|
if (!data_get($service, 'service')) {
|
||||||
ray('ServiceDatabase without service', $service->name);
|
echo 'ServiceDatabase without service' . $service->name . 'deleting\n';
|
||||||
$service->delete();
|
$service->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ class Index extends Component
|
|||||||
public ?string $remoteServerHost = null;
|
public ?string $remoteServerHost = null;
|
||||||
public ?int $remoteServerPort = 22;
|
public ?int $remoteServerPort = 22;
|
||||||
public ?string $remoteServerUser = 'root';
|
public ?string $remoteServerUser = 'root';
|
||||||
|
public bool $isSwarmManager = false;
|
||||||
|
public bool $isCloudflareTunnel = false;
|
||||||
public ?Server $createdServer = null;
|
public ?Server $createdServer = null;
|
||||||
|
|
||||||
public Collection $projects;
|
public Collection $projects;
|
||||||
@@ -182,7 +184,10 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
'private_key_id' => $this->createdPrivateKey->id,
|
'private_key_id' => $this->createdPrivateKey->id,
|
||||||
'team_id' => currentTeam()->id,
|
'team_id' => currentTeam()->id,
|
||||||
]);
|
]);
|
||||||
$this->createdServer->save();
|
$this->createdServer->settings->is_swarm_manager = $this->isSwarmManager;
|
||||||
|
$this->createdServer->settings->is_cloudflare_tunnel = $this->isCloudflareTunnel;
|
||||||
|
$this->createdServer->settings->save();
|
||||||
|
$this->createdServer->addInitialNetwork();
|
||||||
$this->validateServer();
|
$this->validateServer();
|
||||||
}
|
}
|
||||||
public function validateServer()
|
public function validateServer()
|
||||||
@@ -197,6 +202,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
]);
|
]);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->serverReachable = false;
|
$this->serverReachable = false;
|
||||||
|
$this->createdServer->delete();
|
||||||
return handleError(error: $e, livewire: $this);
|
return handleError(error: $e, livewire: $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,79 +22,19 @@ class DockerCompose extends Component
|
|||||||
$this->query = request()->query();
|
$this->query = request()->query();
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
$this->dockerComposeRaw = 'services:
|
$this->dockerComposeRaw = 'services:
|
||||||
ghost:
|
appsmith:
|
||||||
image: ghost:5
|
build:
|
||||||
volumes:
|
context: .
|
||||||
- ~/configs:/etc/configs/:ro
|
dockerfile_inline: |
|
||||||
- ./var/lib/ghost/content:/tmp/ghost2/content:ro
|
FROM nginx
|
||||||
- /var/lib/ghost/content:/tmp/ghost/content:rw
|
ARG GIT_COMMIT
|
||||||
- ghost-content-data:/var/lib/ghost/content
|
ARG GIT_BRANCH
|
||||||
- type: volume
|
RUN echo "Hello World ${GIT_COMMIT} ${GIT_BRANCH}"
|
||||||
source: mydata
|
args:
|
||||||
target: /data
|
- GIT_COMMIT=cdc3b19
|
||||||
- type: bind
|
- GIT_BRANCH=${GIT_BRANCH}
|
||||||
source: ./var/lib/ghost/data
|
|
||||||
target: /data
|
|
||||||
- type: bind
|
|
||||||
source: /tmp
|
|
||||||
target: /tmp
|
|
||||||
labels:
|
|
||||||
- "test.label=true"
|
|
||||||
ports:
|
|
||||||
- "3000"
|
|
||||||
- "3000-3005"
|
|
||||||
- "8000:8000"
|
|
||||||
- "9090-9091:8080-8081"
|
|
||||||
- "49100:22"
|
|
||||||
- "127.0.0.1:8001:8001"
|
|
||||||
- "127.0.0.1:5000-5010:5000-5010"
|
|
||||||
- "127.0.0.1::5000"
|
|
||||||
- "6060:6060/udp"
|
|
||||||
- "12400-12500:1240"
|
|
||||||
- target: 80
|
|
||||||
published: 8080
|
|
||||||
protocol: tcp
|
|
||||||
mode: host
|
|
||||||
networks:
|
|
||||||
- some-network
|
|
||||||
- other-network
|
|
||||||
environment:
|
environment:
|
||||||
- database__client=${DATABASE_CLIENT:-mysql}
|
- APPSMITH_MAIL_ENABLED=${APPSMITH_MAIL_ENABLED}
|
||||||
- database__connection__database=${MYSQL_DATABASE:-ghost}
|
|
||||||
- database__connection__host=${DATABASE_CONNECTION_HOST:-mysql}
|
|
||||||
- test=${TEST:?true}
|
|
||||||
- url=$SERVICE_FQDN_GHOST
|
|
||||||
- database__connection__user=$SERVICE_USER_MYSQL
|
|
||||||
- database__connection__password=$SERVICE_PASSWORD_MYSQL
|
|
||||||
depends_on:
|
|
||||||
- mysql
|
|
||||||
mysql:
|
|
||||||
image: mysql:8.0
|
|
||||||
volumes:
|
|
||||||
- ghost-mysql-data:/var/lib/mysql
|
|
||||||
environment:
|
|
||||||
- MYSQL_USER=${SERVICE_USER_MYSQL}
|
|
||||||
- MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL}
|
|
||||||
- MYSQL_DATABASE=$MYSQL_DATABASE
|
|
||||||
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQLROOT}
|
|
||||||
- SESSION_SECRET
|
|
||||||
minio:
|
|
||||||
image: minio/minio
|
|
||||||
environment:
|
|
||||||
RACK_ENV: development
|
|
||||||
A: $A
|
|
||||||
SHOW: ${SHOW}
|
|
||||||
SHOW1: ${SHOW2-show1}
|
|
||||||
SHOW2: ${SHOW3:-show2}
|
|
||||||
SHOW3: ${SHOW4?show3}
|
|
||||||
SHOW4: ${SHOW5:?show4}
|
|
||||||
SHOW5: ${SERVICE_USER_MINIO}
|
|
||||||
SHOW6: ${SERVICE_PASSWORD_MINIO}
|
|
||||||
SHOW7: ${SERVICE_PASSWORD_64_MINIO}
|
|
||||||
SHOW8: ${SERVICE_BASE64_64_MINIO}
|
|
||||||
SHOW9: ${SERVICE_BASE64_128_MINIO}
|
|
||||||
SHOW10: ${SERVICE_BASE64_MINIO}
|
|
||||||
SHOW11:
|
|
||||||
';
|
';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
public function checkStatus()
|
public function checkStatus()
|
||||||
{
|
{
|
||||||
dispatch_sync(new ContainerStatusJob($this->service->server));
|
dispatch(new ContainerStatusJob($this->service->server));
|
||||||
$this->refreshStacks();
|
$this->refreshStacks();
|
||||||
}
|
}
|
||||||
public function refreshStacks()
|
public function refreshStacks()
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ class Show extends Component
|
|||||||
{
|
{
|
||||||
$this->validate();
|
$this->validate();
|
||||||
$this->env->save();
|
$this->env->save();
|
||||||
ray($this->env);
|
|
||||||
$this->emit('success', 'Environment variable updated successfully.');
|
$this->emit('success', 'Environment variable updated successfully.');
|
||||||
$this->emit('refreshEnvs');
|
$this->emit('refreshEnvs');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,16 @@
|
|||||||
|
|
||||||
namespace App\Http\Livewire\Project\Shared;
|
namespace App\Http\Livewire\Project\Shared;
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use App\Models\Service;
|
||||||
|
use App\Models\ServiceApplication;
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\StandaloneRedis;
|
||||||
use Illuminate\Support\Facades\Process;
|
use Illuminate\Support\Facades\Process;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
@@ -10,17 +19,44 @@ class GetLogs extends Component
|
|||||||
{
|
{
|
||||||
public string $outputs = '';
|
public string $outputs = '';
|
||||||
public string $errors = '';
|
public string $errors = '';
|
||||||
|
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $resource;
|
||||||
|
public ServiceApplication|ServiceDatabase|null $servicesubtype = null;
|
||||||
public Server $server;
|
public Server $server;
|
||||||
public ?string $container = null;
|
public ?string $container = null;
|
||||||
public ?bool $streamLogs = false;
|
public ?bool $streamLogs = false;
|
||||||
public ?bool $showTimeStamps = true;
|
public ?bool $showTimeStamps = true;
|
||||||
public int $numberOfLines = 100;
|
public int $numberOfLines = 100;
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
if ($this->resource->getMorphClass() === 'App\Models\Application') {
|
||||||
|
$this->showTimeStamps = $this->resource->settings->is_include_timestamps;
|
||||||
|
} else {
|
||||||
|
if ($this->servicesubtype) {
|
||||||
|
$this->showTimeStamps = $this->servicesubtype->is_include_timestamps;
|
||||||
|
} else {
|
||||||
|
$this->showTimeStamps = $this->resource->is_include_timestamps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
public function doSomethingWithThisChunkOfOutput($output)
|
public function doSomethingWithThisChunkOfOutput($output)
|
||||||
{
|
{
|
||||||
$this->outputs .= removeAnsiColors($output);
|
$this->outputs .= removeAnsiColors($output);
|
||||||
}
|
}
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
|
if ($this->resource->getMorphClass() === 'App\Models\Application') {
|
||||||
|
$this->resource->settings->is_include_timestamps = $this->showTimeStamps;
|
||||||
|
$this->resource->settings->save();
|
||||||
|
} else {
|
||||||
|
if ($this->servicesubtype) {
|
||||||
|
$this->servicesubtype->is_include_timestamps = $this->showTimeStamps;
|
||||||
|
$this->servicesubtype->save();
|
||||||
|
} else {
|
||||||
|
$this->resource->is_include_timestamps = $this->showTimeStamps;
|
||||||
|
$this->resource->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public function getLogs($refresh = false)
|
public function getLogs($refresh = false)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class Logs extends Component
|
|||||||
public $parameters;
|
public $parameters;
|
||||||
public $query;
|
public $query;
|
||||||
public $status;
|
public $status;
|
||||||
|
public $serviceSubType;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
@@ -64,6 +65,11 @@ class Logs extends Component
|
|||||||
} else if (data_get($this->parameters, 'service_uuid')) {
|
} else if (data_get($this->parameters, 'service_uuid')) {
|
||||||
$this->type = 'service';
|
$this->type = 'service';
|
||||||
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
|
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
|
||||||
|
$service_name = data_get($this->parameters, 'service_name');
|
||||||
|
$this->serviceSubType = $this->resource->applications()->where('name', $service_name)->first();
|
||||||
|
if (!$this->serviceSubType) {
|
||||||
|
$this->serviceSubType = $this->resource->databases()->where('name', $service_name)->first();
|
||||||
|
}
|
||||||
$this->status = $this->resource->status;
|
$this->status = $this->resource->status;
|
||||||
$this->server = $this->resource->server;
|
$this->server = $this->resource->server;
|
||||||
$this->container = data_get($this->parameters, 'service_name') . '-' . $this->resource->uuid;
|
$this->container = data_get($this->parameters, 'service_name') . '-' . $this->resource->uuid;
|
||||||
|
|||||||
@@ -17,14 +17,15 @@ class Form extends Component
|
|||||||
protected $listeners = ['serverRefresh'];
|
protected $listeners = ['serverRefresh'];
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'server.name' => 'required|min:6',
|
'server.name' => 'required',
|
||||||
'server.description' => 'nullable',
|
'server.description' => 'nullable',
|
||||||
'server.ip' => 'required',
|
'server.ip' => 'required',
|
||||||
'server.user' => 'required',
|
'server.user' => 'required',
|
||||||
'server.port' => 'required',
|
'server.port' => 'required',
|
||||||
'server.settings.is_cloudflare_tunnel' => 'required',
|
'server.settings.is_cloudflare_tunnel' => 'required|boolean',
|
||||||
'server.settings.is_reachable' => 'required',
|
'server.settings.is_reachable' => 'required',
|
||||||
'server.settings.is_part_of_swarm' => 'required',
|
'server.settings.is_swarm_manager' => 'required|boolean',
|
||||||
|
// 'server.settings.is_swarm_worker' => 'required|boolean',
|
||||||
'wildcard_domain' => 'nullable|url',
|
'wildcard_domain' => 'nullable|url',
|
||||||
];
|
];
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
@@ -34,8 +35,9 @@ class Form extends Component
|
|||||||
'server.user' => 'User',
|
'server.user' => 'User',
|
||||||
'server.port' => 'Port',
|
'server.port' => 'Port',
|
||||||
'server.settings.is_cloudflare_tunnel' => 'Cloudflare Tunnel',
|
'server.settings.is_cloudflare_tunnel' => 'Cloudflare Tunnel',
|
||||||
'server.settings.is_reachable' => 'is reachable',
|
'server.settings.is_reachable' => 'Is reachable',
|
||||||
'server.settings.is_part_of_swarm' => 'is part of swarm'
|
'server.settings.is_swarm_manager' => 'Swarm Manager',
|
||||||
|
// 'server.settings.is_swarm_worker' => 'Swarm Worker',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -49,9 +51,14 @@ class Form extends Component
|
|||||||
}
|
}
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
refresh_server_connection($this->server->privateKey);
|
try {
|
||||||
$this->validateServer();
|
refresh_server_connection($this->server->privateKey);
|
||||||
$this->server->settings->save();
|
$this->validateServer(false);
|
||||||
|
$this->server->settings->save();
|
||||||
|
$this->emit('success', 'Server updated successfully.');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public function installDocker()
|
public function installDocker()
|
||||||
{
|
{
|
||||||
@@ -100,6 +107,12 @@ class Form extends Component
|
|||||||
$install && $this->installDocker();
|
$install && $this->installDocker();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
$swarmInstalled = $this->server->validateDockerSwarm();
|
||||||
|
if ($swarmInstalled) {
|
||||||
|
$install && $this->emit('success', 'Docker Swarm is initiated.');
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ class LogDrains extends Component
|
|||||||
'server.settings.is_logdrain_axiom_enabled' => 'required|boolean',
|
'server.settings.is_logdrain_axiom_enabled' => 'required|boolean',
|
||||||
'server.settings.logdrain_axiom_dataset_name' => 'required|string',
|
'server.settings.logdrain_axiom_dataset_name' => 'required|string',
|
||||||
'server.settings.logdrain_axiom_api_key' => 'required|string',
|
'server.settings.logdrain_axiom_api_key' => 'required|string',
|
||||||
|
'server.settings.is_logdrain_custom_enabled' => 'required|boolean',
|
||||||
|
'server.settings.logdrain_custom_config' => 'required|string',
|
||||||
|
'server.settings.logdrain_custom_config_parser' => 'nullable',
|
||||||
];
|
];
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
'server.settings.is_logdrain_newrelic_enabled' => 'New Relic log drain',
|
'server.settings.is_logdrain_newrelic_enabled' => 'New Relic log drain',
|
||||||
@@ -29,6 +32,9 @@ class LogDrains extends Component
|
|||||||
'server.settings.is_logdrain_axiom_enabled' => 'Axiom log drain',
|
'server.settings.is_logdrain_axiom_enabled' => 'Axiom log drain',
|
||||||
'server.settings.logdrain_axiom_dataset_name' => 'Axiom dataset name',
|
'server.settings.logdrain_axiom_dataset_name' => 'Axiom dataset name',
|
||||||
'server.settings.logdrain_axiom_api_key' => 'Axiom API key',
|
'server.settings.logdrain_axiom_api_key' => 'Axiom API key',
|
||||||
|
'server.settings.is_logdrain_custom_enabled' => 'Custom log drain',
|
||||||
|
'server.settings.logdrain_custom_config' => 'Custom log drain configuration',
|
||||||
|
'server.settings.logdrain_custom_config_parser' => 'Custom log drain configuration parser',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -84,6 +90,7 @@ class LogDrains extends Component
|
|||||||
$this->server->settings->update([
|
$this->server->settings->update([
|
||||||
'is_logdrain_highlight_enabled' => false,
|
'is_logdrain_highlight_enabled' => false,
|
||||||
'is_logdrain_axiom_enabled' => false,
|
'is_logdrain_axiom_enabled' => false,
|
||||||
|
'is_logdrain_custom_enabled' => false,
|
||||||
]);
|
]);
|
||||||
} else if ($type === 'highlight') {
|
} else if ($type === 'highlight') {
|
||||||
$this->validate([
|
$this->validate([
|
||||||
@@ -93,6 +100,7 @@ class LogDrains extends Component
|
|||||||
$this->server->settings->update([
|
$this->server->settings->update([
|
||||||
'is_logdrain_newrelic_enabled' => false,
|
'is_logdrain_newrelic_enabled' => false,
|
||||||
'is_logdrain_axiom_enabled' => false,
|
'is_logdrain_axiom_enabled' => false,
|
||||||
|
'is_logdrain_custom_enabled' => false,
|
||||||
]);
|
]);
|
||||||
} else if ($type === 'axiom') {
|
} else if ($type === 'axiom') {
|
||||||
$this->validate([
|
$this->validate([
|
||||||
@@ -103,6 +111,18 @@ class LogDrains extends Component
|
|||||||
$this->server->settings->update([
|
$this->server->settings->update([
|
||||||
'is_logdrain_newrelic_enabled' => false,
|
'is_logdrain_newrelic_enabled' => false,
|
||||||
'is_logdrain_highlight_enabled' => false,
|
'is_logdrain_highlight_enabled' => false,
|
||||||
|
'is_logdrain_custom_enabled' => false,
|
||||||
|
]);
|
||||||
|
} else if ($type === 'custom') {
|
||||||
|
$this->validate([
|
||||||
|
'server.settings.is_logdrain_custom_enabled' => 'required|boolean',
|
||||||
|
'server.settings.logdrain_custom_config' => 'required|string',
|
||||||
|
'server.settings.logdrain_custom_config_parser' => 'nullable',
|
||||||
|
]);
|
||||||
|
$this->server->settings->update([
|
||||||
|
'is_logdrain_newrelic_enabled' => false,
|
||||||
|
'is_logdrain_highlight_enabled' => false,
|
||||||
|
'is_logdrain_axiom_enabled' => false,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
$this->server->settings->save();
|
$this->server->settings->save();
|
||||||
@@ -121,6 +141,10 @@ class LogDrains extends Component
|
|||||||
$this->server->settings->update([
|
$this->server->settings->update([
|
||||||
'is_logdrain_axiom_enabled' => false,
|
'is_logdrain_axiom_enabled' => false,
|
||||||
]);
|
]);
|
||||||
|
} else if ($type === 'custom') {
|
||||||
|
$this->server->settings->update([
|
||||||
|
'is_logdrain_custom_enabled' => false,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
handleError($e, $this);
|
handleError($e, $this);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class ByIp extends Component
|
|||||||
public string $ip;
|
public string $ip;
|
||||||
public string $user = 'root';
|
public string $user = 'root';
|
||||||
public int $port = 22;
|
public int $port = 22;
|
||||||
public bool $is_part_of_swarm = false;
|
public bool $is_swarm_manager = false;
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'name' => 'required|string',
|
'name' => 'required|string',
|
||||||
@@ -29,6 +29,7 @@ class ByIp extends Component
|
|||||||
'ip' => 'required',
|
'ip' => 'required',
|
||||||
'user' => 'required|string',
|
'user' => 'required|string',
|
||||||
'port' => 'required|integer',
|
'port' => 'required|integer',
|
||||||
|
'is_swarm_manager' => 'required|boolean',
|
||||||
];
|
];
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
'name' => 'Name',
|
'name' => 'Name',
|
||||||
@@ -36,6 +37,7 @@ class ByIp extends Component
|
|||||||
'ip' => 'IP Address/Domain',
|
'ip' => 'IP Address/Domain',
|
||||||
'user' => 'User',
|
'user' => 'User',
|
||||||
'port' => 'Port',
|
'port' => 'Port',
|
||||||
|
'is_swarm_manager' => 'Swarm Manager',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -72,11 +74,11 @@ class ByIp extends Component
|
|||||||
'proxy' => [
|
'proxy' => [
|
||||||
"type" => ProxyTypes::TRAEFIK_V2->value,
|
"type" => ProxyTypes::TRAEFIK_V2->value,
|
||||||
"status" => ProxyStatus::EXITED->value,
|
"status" => ProxyStatus::EXITED->value,
|
||||||
]
|
],
|
||||||
|
|
||||||
]);
|
]);
|
||||||
$server->settings->is_part_of_swarm = $this->is_part_of_swarm;
|
$server->settings->is_swarm_manager = $this->is_swarm_manager;
|
||||||
$server->settings->save();
|
$server->settings->save();
|
||||||
|
$server->addInitialNetwork();
|
||||||
return redirect()->route('server.show', $server->uuid);
|
return redirect()->route('server.show', $server->uuid);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
|||||||
@@ -58,11 +58,25 @@ class Deploy extends Component
|
|||||||
|
|
||||||
public function stop()
|
public function stop()
|
||||||
{
|
{
|
||||||
instant_remote_process([
|
try {
|
||||||
"docker rm -f coolify-proxy",
|
if ($this->server->isSwarm()) {
|
||||||
], $this->server);
|
instant_remote_process([
|
||||||
$this->server->proxy->status = 'exited';
|
"docker service rm coolify-proxy_traefik",
|
||||||
$this->server->save();
|
], $this->server);
|
||||||
$this->emit('proxyStatusUpdated');
|
$this->server->proxy->status = 'exited';
|
||||||
|
$this->server->save();
|
||||||
|
$this->emit('proxyStatusUpdated');
|
||||||
|
} else {
|
||||||
|
instant_remote_process([
|
||||||
|
"docker rm -f coolify-proxy",
|
||||||
|
], $this->server);
|
||||||
|
$this->server->proxy->status = 'exited';
|
||||||
|
$this->server->save();
|
||||||
|
$this->emit('proxyStatusUpdated');
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,12 @@ class Status extends Component
|
|||||||
public int $numberOfPolls = 0;
|
public int $numberOfPolls = 0;
|
||||||
|
|
||||||
protected $listeners = ['proxyStatusUpdated', 'startProxyPolling'];
|
protected $listeners = ['proxyStatusUpdated', 'startProxyPolling'];
|
||||||
|
public function mount() {
|
||||||
|
$this->checkProxy();
|
||||||
|
}
|
||||||
public function startProxyPolling()
|
public function startProxyPolling()
|
||||||
{
|
{
|
||||||
$this->polling = true;
|
$this->checkProxy();
|
||||||
}
|
}
|
||||||
public function proxyStatusUpdated()
|
public function proxyStatusUpdated()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ class PricingPlans extends Component
|
|||||||
public bool $isTrial = false;
|
public bool $isTrial = false;
|
||||||
public function mount() {
|
public function mount() {
|
||||||
$this->isTrial = !data_get(currentTeam(),'subscription.stripe_trial_already_ended');
|
$this->isTrial = !data_get(currentTeam(),'subscription.stripe_trial_already_ended');
|
||||||
|
if (config('constants.limits.trial_period') == 0) {
|
||||||
|
$this->isTrial = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public function subscribeStripe($type)
|
public function subscribeStripe($type)
|
||||||
{
|
{
|
||||||
@@ -63,6 +66,7 @@ class PricingPlans extends Component
|
|||||||
];
|
];
|
||||||
|
|
||||||
if (!data_get($team,'subscription.stripe_trial_already_ended')) {
|
if (!data_get($team,'subscription.stripe_trial_already_ended')) {
|
||||||
|
if (config('constants.limits.trial_period') > 0) {
|
||||||
$payload['subscription_data'] = [
|
$payload['subscription_data'] = [
|
||||||
'trial_period_days' => config('constants.limits.trial_period'),
|
'trial_period_days' => config('constants.limits.trial_period'),
|
||||||
'trial_settings' => [
|
'trial_settings' => [
|
||||||
@@ -71,6 +75,7 @@ class PricingPlans extends Component
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
}
|
||||||
$payload['payment_method_collection'] = 'if_required';
|
$payload['payment_method_collection'] = 'if_required';
|
||||||
}
|
}
|
||||||
$customer = currentTeam()->subscription?->stripe_customer_id ?? null;
|
$customer = currentTeam()->subscription?->stripe_customer_id ?? null;
|
||||||
|
|||||||
@@ -156,25 +156,27 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
// Generate custom host<->ip mapping
|
// Generate custom host<->ip mapping
|
||||||
$allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server);
|
$allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server);
|
||||||
$allContainers = format_docker_command_output_to_json($allContainers);
|
if (!is_null($allContainers)) {
|
||||||
$ips = collect([]);
|
$allContainers = format_docker_command_output_to_json($allContainers);
|
||||||
if (count($allContainers) > 0) {
|
$ips = collect([]);
|
||||||
$allContainers = $allContainers[0];
|
if (count($allContainers) > 0) {
|
||||||
foreach ($allContainers as $container) {
|
$allContainers = $allContainers[0];
|
||||||
$containerName = data_get($container, 'Name');
|
foreach ($allContainers as $container) {
|
||||||
if ($containerName === 'coolify-proxy') {
|
$containerName = data_get($container, 'Name');
|
||||||
continue;
|
if ($containerName === 'coolify-proxy') {
|
||||||
}
|
continue;
|
||||||
$containerIp = data_get($container, 'IPv4Address');
|
}
|
||||||
if ($containerName && $containerIp) {
|
$containerIp = data_get($container, 'IPv4Address');
|
||||||
$containerIp = str($containerIp)->before('/');
|
if ($containerName && $containerIp) {
|
||||||
$ips->put($containerName, $containerIp->value());
|
$containerIp = str($containerIp)->before('/');
|
||||||
|
$ips->put($containerName, $containerIp->value());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$this->addHosts = $ips->map(function ($ip, $name) {
|
||||||
|
return "--add-host $name:$ip";
|
||||||
|
})->implode(' ');
|
||||||
}
|
}
|
||||||
$this->addHosts = $ips->map(function ($ip, $name) {
|
|
||||||
return "--add-host $name:$ip";
|
|
||||||
})->implode(' ');
|
|
||||||
|
|
||||||
if ($this->application->dockerfile_target_build) {
|
if ($this->application->dockerfile_target_build) {
|
||||||
$this->buildTarget = " --target {$this->application->dockerfile_target_build} ";
|
$this->buildTarget = " --target {$this->application->dockerfile_target_build} ";
|
||||||
@@ -214,6 +216,17 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
}
|
}
|
||||||
if ($this->application->docker_registry_image_name && $this->application->build_pack !== 'dockerimage') {
|
if ($this->application->docker_registry_image_name && $this->application->build_pack !== 'dockerimage') {
|
||||||
$this->push_to_docker_registry();
|
$this->push_to_docker_registry();
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
$this->application_deployment_queue->addLogEntry("Creating / updating stack.");
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, "cd {$this->workdir} && docker stack deploy --with-registry-auth -c docker-compose.yml {$this->application->uuid}")
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"echo 'Stack deployed. It may take a few minutes to fully available in your swarm.'"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||||
$this->application->isConfigurationChanged(true);
|
$this->application->isConfigurationChanged(true);
|
||||||
@@ -290,42 +303,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
ray($e);
|
ray($e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// private function deploy_docker_compose()
|
|
||||||
// {
|
|
||||||
// $dockercompose_base64 = base64_encode($this->application->dockercompose);
|
|
||||||
// $this->execute_remote_command(
|
|
||||||
// [
|
|
||||||
// "echo 'Starting deployment of {$this->application->name}.'"
|
|
||||||
// ],
|
|
||||||
// );
|
|
||||||
// $this->prepare_builder_image();
|
|
||||||
// $this->execute_remote_command(
|
|
||||||
// [
|
|
||||||
// executeInDocker($this->deployment_uuid, "echo '$dockercompose_base64' | base64 -d > $this->workdir/docker-compose.yaml")
|
|
||||||
// ],
|
|
||||||
// );
|
|
||||||
// $this->build_image_name = Str::lower("{$this->customRepository}:build");
|
|
||||||
// $this->production_image_name = Str::lower("{$this->application->uuid}:latest");
|
|
||||||
// $this->save_environment_variables();
|
|
||||||
// $containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id);
|
|
||||||
// ray($containers);
|
|
||||||
// if ($containers->count() > 0) {
|
|
||||||
// foreach ($containers as $container) {
|
|
||||||
// $containerName = data_get($container, 'Names');
|
|
||||||
// if ($containerName) {
|
|
||||||
// instant_remote_process(
|
|
||||||
// ["docker rm -f {$containerName}"],
|
|
||||||
// $this->application->destination->server
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// $this->execute_remote_command(
|
|
||||||
// ["echo -n 'Starting services (could take a while)...'"],
|
|
||||||
// [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
private function generate_image_names()
|
private function generate_image_names()
|
||||||
{
|
{
|
||||||
if ($this->application->dockerfile) {
|
if ($this->application->dockerfile) {
|
||||||
@@ -370,6 +347,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->generate_image_names();
|
$this->generate_image_names();
|
||||||
$this->check_image_locally_or_remotely();
|
$this->check_image_locally_or_remotely();
|
||||||
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
|
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
|
||||||
|
$this->create_workdir();
|
||||||
$this->generate_compose_file();
|
$this->generate_compose_file();
|
||||||
$this->rolling_update();
|
$this->rolling_update();
|
||||||
return;
|
return;
|
||||||
@@ -402,7 +380,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$envs->push($env->key . '=' . $env->value);
|
$envs->push($env->key . '=' . $env->value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ray($envs);
|
|
||||||
$envs_base64 = base64_encode($envs->implode("\n"));
|
$envs_base64 = base64_encode($envs->implode("\n"));
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
@@ -466,11 +443,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->clone_repository();
|
$this->clone_repository();
|
||||||
$this->generate_image_names();
|
$this->generate_image_names();
|
||||||
$this->cleanup_git();
|
$this->cleanup_git();
|
||||||
|
$this->application->loadComposeFile(isInit: false);
|
||||||
$composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id);
|
$composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id);
|
||||||
$yaml = Yaml::dump($composeFile->toArray(), 10);
|
$yaml = Yaml::dump($composeFile->toArray(), 10);
|
||||||
$this->docker_compose_base64 = base64_encode($yaml);
|
$this->docker_compose_base64 = base64_encode($yaml);
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yaml"), "hidden" => true
|
executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}{$this->docker_compose_location}"), "hidden" => true
|
||||||
]);
|
]);
|
||||||
$this->save_environment_variables();
|
$this->save_environment_variables();
|
||||||
$this->stop_running_container(force: true);
|
$this->stop_running_container(force: true);
|
||||||
@@ -479,13 +457,34 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
$networkId = "{$this->application->uuid}-{$this->pull_request_id}";
|
$networkId = "{$this->application->uuid}-{$this->pull_request_id}";
|
||||||
}
|
}
|
||||||
$this->execute_remote_command([
|
if ($this->server->isSwarm()) {
|
||||||
"docker network create --attachable '{$networkId}' >/dev/null || true", "hidden" => true, "ignore_errors" => true
|
// TODO
|
||||||
], [
|
} else {
|
||||||
"docker network connect {$networkId} coolify-proxy || true", "hidden" => true, "ignore_errors" => true
|
$this->execute_remote_command([
|
||||||
]);
|
"docker network create --attachable '{$networkId}' >/dev/null || true", "hidden" => true, "ignore_errors" => true
|
||||||
|
], [
|
||||||
|
"docker network connect {$networkId} coolify-proxy || true", "hidden" => true, "ignore_errors" => true
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if (isset($this->docker_compose_base64)) {
|
||||||
|
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
|
||||||
|
$composeFileName = "$this->configuration_dir/docker-compose.yml";
|
||||||
|
if ($this->pull_request_id !== 0) {
|
||||||
|
$composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yml";
|
||||||
|
}
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
"mkdir -p $this->configuration_dir"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"echo '{$this->docker_compose_base64}' | base64 -d > $composeFileName",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"echo '{$readme}' > $this->configuration_dir/README.md",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
$this->start_by_compose_file();
|
$this->start_by_compose_file();
|
||||||
$this->application->loadComposeFile(isInit: false);
|
|
||||||
}
|
}
|
||||||
private function deploy_dockerfile_buildpack()
|
private function deploy_dockerfile_buildpack()
|
||||||
{
|
{
|
||||||
@@ -528,6 +527,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if (!$this->force_rebuild) {
|
if (!$this->force_rebuild) {
|
||||||
$this->check_image_locally_or_remotely();
|
$this->check_image_locally_or_remotely();
|
||||||
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
|
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
|
||||||
|
$this->create_workdir();
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
"echo 'No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.'",
|
"echo 'No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.'",
|
||||||
]);
|
]);
|
||||||
@@ -570,74 +570,83 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
private function rolling_update()
|
private function rolling_update()
|
||||||
{
|
{
|
||||||
if (count($this->application->ports_mappings_array) > 0) {
|
if ($this->server->isSwarm()) {
|
||||||
$this->execute_remote_command(
|
// Skip this.
|
||||||
[
|
|
||||||
"echo '\n----------------------------------------'",
|
|
||||||
],
|
|
||||||
["echo -n 'Application has ports mapped to the host system, rolling update is not supported.'"],
|
|
||||||
);
|
|
||||||
$this->stop_running_container(force: true);
|
|
||||||
$this->start_by_compose_file();
|
|
||||||
} else {
|
} else {
|
||||||
$this->execute_remote_command(
|
if (count($this->application->ports_mappings_array) > 0) {
|
||||||
[
|
$this->execute_remote_command(
|
||||||
"echo '\n----------------------------------------'",
|
[
|
||||||
],
|
"echo '\n----------------------------------------'",
|
||||||
["echo -n 'Rolling update started.'"],
|
],
|
||||||
);
|
["echo -n 'Application has ports mapped to the host system, rolling update is not supported.'"],
|
||||||
$this->start_by_compose_file();
|
);
|
||||||
$this->health_check();
|
$this->stop_running_container(force: true);
|
||||||
$this->stop_running_container();
|
$this->start_by_compose_file();
|
||||||
|
} else {
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
"echo '\n----------------------------------------'",
|
||||||
|
],
|
||||||
|
["echo -n 'Rolling update started.'"],
|
||||||
|
);
|
||||||
|
$this->start_by_compose_file();
|
||||||
|
$this->health_check();
|
||||||
|
$this->stop_running_container();
|
||||||
|
$this->application_deployment_queue->addLogEntry("Rolling update completed.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function health_check()
|
private function health_check()
|
||||||
{
|
{
|
||||||
if ($this->application->isHealthcheckDisabled()) {
|
if ($this->server->isSwarm()) {
|
||||||
$this->newVersionIsHealthy = true;
|
// Implement healthcheck for swarm
|
||||||
return;
|
} else {
|
||||||
}
|
if ($this->application->isHealthcheckDisabled()) {
|
||||||
// ray('New container name: ', $this->container_name);
|
$this->newVersionIsHealthy = true;
|
||||||
if ($this->container_name) {
|
return;
|
||||||
$counter = 1;
|
}
|
||||||
$this->execute_remote_command(
|
// ray('New container name: ', $this->container_name);
|
||||||
[
|
if ($this->container_name) {
|
||||||
"echo 'Waiting for healthcheck to pass on the new container.'"
|
$counter = 1;
|
||||||
]
|
|
||||||
);
|
|
||||||
if ($this->full_healthcheck_url) {
|
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"echo 'Healthcheck URL (inside the container): {$this->full_healthcheck_url}'"
|
"echo 'Waiting for healthcheck to pass on the new container.'"
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
if ($this->full_healthcheck_url) {
|
||||||
while ($counter < $this->application->health_check_retries) {
|
|
||||||
$this->execute_remote_command(
|
|
||||||
[
|
|
||||||
"docker inspect --format='{{json .State.Health.Status}}' {$this->container_name}",
|
|
||||||
"hidden" => true,
|
|
||||||
"save" => "health_check"
|
|
||||||
],
|
|
||||||
|
|
||||||
);
|
|
||||||
$this->execute_remote_command(
|
|
||||||
[
|
|
||||||
"echo 'Attempt {$counter} of {$this->application->health_check_retries} | Healthcheck status: {$this->saved_outputs->get('health_check')}'"
|
|
||||||
],
|
|
||||||
);
|
|
||||||
if (Str::of($this->saved_outputs->get('health_check'))->contains('healthy')) {
|
|
||||||
$this->newVersionIsHealthy = true;
|
|
||||||
$this->application->update(['status' => 'running']);
|
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"echo 'New container is healthy.'"
|
"echo 'Healthcheck URL (inside the container): {$this->full_healthcheck_url}'"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
while ($counter < $this->application->health_check_retries) {
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
"docker inspect --format='{{json .State.Health.Status}}' {$this->container_name}",
|
||||||
|
"hidden" => true,
|
||||||
|
"save" => "health_check"
|
||||||
|
],
|
||||||
|
|
||||||
|
);
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
"echo 'Attempt {$counter} of {$this->application->health_check_retries} | Healthcheck status: {$this->saved_outputs->get('health_check')}'"
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
break;
|
if (Str::of($this->saved_outputs->get('health_check'))->contains('healthy')) {
|
||||||
|
$this->newVersionIsHealthy = true;
|
||||||
|
$this->application->update(['status' => 'running']);
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
"echo 'New container is healthy.'"
|
||||||
|
],
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$counter++;
|
||||||
|
sleep($this->application->health_check_interval);
|
||||||
}
|
}
|
||||||
$counter++;
|
|
||||||
sleep($this->application->health_check_interval);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -666,7 +675,14 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
|
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
private function create_workdir()
|
||||||
|
{
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
"command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->workdir}")
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
private function prepare_builder_image()
|
private function prepare_builder_image()
|
||||||
{
|
{
|
||||||
$helperImage = config('coolify.helper_image');
|
$helperImage = config('coolify.helper_image');
|
||||||
@@ -675,9 +691,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->dockerConfigFileExists = instant_remote_process(["test -f {$this->serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $this->server);
|
$this->dockerConfigFileExists = instant_remote_process(["test -f {$this->serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $this->server);
|
||||||
|
|
||||||
if ($this->dockerConfigFileExists === 'OK') {
|
if ($this->dockerConfigFileExists === 'OK') {
|
||||||
$runCommand = "docker run -d --network {$this->destination->network} -v /:/host --name {$this->deployment_uuid} --rm -v {$this->serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
|
$runCommand = "docker run -d --network {$this->destination->network} --name {$this->deployment_uuid} --rm -v {$this->serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
|
||||||
} else {
|
} else {
|
||||||
$runCommand = "docker run -d --network {$this->destination->network} -v /:/host --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
|
$runCommand = "docker run -d --network {$this->destination->network} --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
|
||||||
}
|
}
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
@@ -690,6 +706,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
[
|
[
|
||||||
"command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->basedir}")
|
"command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->basedir}")
|
||||||
],
|
],
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
private function deploy_to_additional_destinations()
|
private function deploy_to_additional_destinations()
|
||||||
@@ -844,26 +861,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->env_args = $this->env_args->implode(' ');
|
$this->env_args = $this->env_args->implode(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function modify_compose_file()
|
|
||||||
{
|
|
||||||
// ray("{$this->workdir}{$this->docker_compose_location}");
|
|
||||||
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->docker_compose_location}"), "hidden" => true, "save" => 'compose_file']);
|
|
||||||
if ($this->saved_outputs->get('compose_file')) {
|
|
||||||
$compose = $this->saved_outputs->get('compose_file');
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$yaml = Yaml::parse($compose);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
throw new \Exception($e->getMessage());
|
|
||||||
}
|
|
||||||
$services = data_get($yaml, 'services');
|
|
||||||
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
|
|
||||||
$definedNetwork = collect([$this->application->uuid]);
|
|
||||||
|
|
||||||
$services = collect($services)->map(function ($service, $serviceName) use ($topLevelNetworks, $definedNetwork) {
|
|
||||||
$serviceNetworks = collect(data_get($service, 'networks', []));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
private function generate_compose_file()
|
private function generate_compose_file()
|
||||||
{
|
{
|
||||||
$ports = $this->application->settings->is_static ? [80] : $this->application->ports_exposes_array;
|
$ports = $this->application->settings->is_static ? [80] : $this->application->ports_exposes_array;
|
||||||
@@ -884,21 +881,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
}
|
}
|
||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
$labels = collect(generateLabelsApplication($this->application, $this->preview));
|
$labels = collect(generateLabelsApplication($this->application, $this->preview));
|
||||||
|
|
||||||
// $newHostLabel = $newLabels->filter(function ($label) {
|
|
||||||
// return str($label)->contains('Host');
|
|
||||||
// });
|
|
||||||
// $labels = $labels->reject(function ($label) {
|
|
||||||
// return str($label)->contains('Host');
|
|
||||||
// });
|
|
||||||
// ray($labels,$newLabels);
|
|
||||||
// $labels = $labels->map(function ($label) {
|
|
||||||
// $pattern = '/([a-zA-Z0-9]+)-(\d+)-(http|https)/';
|
|
||||||
// $replacement = "$1-pr-{$this->pull_request_id}-$2-$3";
|
|
||||||
// $newLabel = preg_replace($pattern, $replacement, $label);
|
|
||||||
// return $newLabel;
|
|
||||||
// });
|
|
||||||
// $labels = $labels->merge($newHostLabel);
|
|
||||||
}
|
}
|
||||||
$labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray();
|
$labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray();
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
@@ -909,7 +891,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
'container_name' => $this->container_name,
|
'container_name' => $this->container_name,
|
||||||
'restart' => RESTART_MODE,
|
'restart' => RESTART_MODE,
|
||||||
'environment' => $environment_variables,
|
'environment' => $environment_variables,
|
||||||
'labels' => $labels,
|
|
||||||
'expose' => $ports,
|
'expose' => $ports,
|
||||||
'networks' => [
|
'networks' => [
|
||||||
$this->destination->network,
|
$this->destination->network,
|
||||||
@@ -941,6 +922,48 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
data_forget($docker_compose, 'services.' . $this->container_name . '.container_name');
|
||||||
|
data_forget($docker_compose, 'services.' . $this->container_name . '.expose');
|
||||||
|
data_forget($docker_compose, 'services.' . $this->container_name . '.restart');
|
||||||
|
|
||||||
|
data_forget($docker_compose, 'services.' . $this->container_name . '.mem_limit');
|
||||||
|
data_forget($docker_compose, 'services.' . $this->container_name . '.memswap_limit');
|
||||||
|
data_forget($docker_compose, 'services.' . $this->container_name . '.mem_swappiness');
|
||||||
|
data_forget($docker_compose, 'services.' . $this->container_name . '.mem_reservation');
|
||||||
|
data_forget($docker_compose, 'services.' . $this->container_name . '.cpus');
|
||||||
|
data_forget($docker_compose, 'services.' . $this->container_name . '.cpuset');
|
||||||
|
data_forget($docker_compose, 'services.' . $this->container_name . '.cpu_shares');
|
||||||
|
|
||||||
|
$docker_compose['services'][$this->container_name]['deploy'] = [
|
||||||
|
'placement' => [
|
||||||
|
'constraints' => [
|
||||||
|
'node.role == worker'
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'mode' => 'replicated',
|
||||||
|
'replicas' => 1,
|
||||||
|
'update_config' => [
|
||||||
|
'order' => 'start-first'
|
||||||
|
],
|
||||||
|
'rollback_config' => [
|
||||||
|
'order' => 'start-first'
|
||||||
|
],
|
||||||
|
'labels' => $labels,
|
||||||
|
'resources' => [
|
||||||
|
'limits' => [
|
||||||
|
'cpus' => $this->application->limits_cpus,
|
||||||
|
'memory' => $this->application->limits_memory,
|
||||||
|
],
|
||||||
|
'reservations' => [
|
||||||
|
'cpus' => $this->application->limits_cpus,
|
||||||
|
'memory' => $this->application->limits_memory,
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$docker_compose['services'][$this->container_name]['labels'] = $labels;
|
||||||
|
}
|
||||||
if ($this->server->isLogDrainEnabled() && $this->application->isLogDrainEnabled()) {
|
if ($this->server->isLogDrainEnabled() && $this->application->isLogDrainEnabled()) {
|
||||||
$docker_compose['services'][$this->container_name]['logging'] = [
|
$docker_compose['services'][$this->container_name]['logging'] = [
|
||||||
'driver' => 'fluentd',
|
'driver' => 'fluentd',
|
||||||
@@ -988,6 +1011,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
// 'dockerfile' => $this->workdir . $this->dockerfile_location,
|
// 'dockerfile' => $this->workdir . $this->dockerfile_location,
|
||||||
// ];
|
// ];
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
|
||||||
|
|
||||||
|
data_forget($docker_compose, 'services.' . $this->container_name);
|
||||||
|
|
||||||
$this->docker_compose = Yaml::dump($docker_compose, 10);
|
$this->docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$this->docker_compose_base64 = base64_encode($this->docker_compose);
|
$this->docker_compose_base64 = base64_encode($this->docker_compose);
|
||||||
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]);
|
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]);
|
||||||
@@ -1204,7 +1232,6 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
[executeInDocker($this->deployment_uuid, "docker rm -f $containerName >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
|
[executeInDocker($this->deployment_uuid, "docker rm -f $containerName >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
$this->application_deployment_queue->addLogEntry("Rolling update completed.");
|
|
||||||
} else {
|
} else {
|
||||||
$this->application_deployment_queue->addLogEntry("New container is not healthy, rolling back to the old container.");
|
$this->application_deployment_queue->addLogEntry("New container is not healthy, rolling back to the old container.");
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
@@ -1226,6 +1253,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true],
|
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
$this->application_deployment_queue->addLogEntry("New container started.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generate_build_env_variables()
|
private function generate_build_env_variables()
|
||||||
@@ -1259,7 +1287,6 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->value}");
|
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->value}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ray($dockerfile->implode("\n"));
|
|
||||||
$dockerfile_base64 = base64_encode($dockerfile->implode("\n"));
|
$dockerfile_base64 = base64_encode($dockerfile->implode("\n"));
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}{$this->dockerfile_location}"),
|
executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}{$this->dockerfile_location}"),
|
||||||
@@ -1289,9 +1316,13 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
["echo 'Oops something is not okay, are you okay? 😢'", 'type' => 'err'],
|
["echo 'Oops something is not okay, are you okay? 😢'", 'type' => 'err'],
|
||||||
["echo '{$exception->getMessage()}'", 'type' => 'err'],
|
["echo '{$exception->getMessage()}'", 'type' => 'err'],
|
||||||
["echo -n 'Deployment failed. Removing the new version of your application.'", 'type' => 'err'],
|
|
||||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true]
|
|
||||||
);
|
);
|
||||||
|
if ($this->application->build_pack !== 'dockercompose') {
|
||||||
|
$this->execute_remote_command(
|
||||||
|
["echo -n 'Deployment failed. Removing the new version of your application.'", 'type' => 'err'],
|
||||||
|
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$this->next(ApplicationDeploymentStatus::FAILED->value);
|
$this->next(ApplicationDeploymentStatus::FAILED->value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if (!$this->server->isServerReady()) {
|
if (!$this->server->isServerReady()) {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
$containers = instant_remote_process(["docker container ls -q"], $this->server);
|
$containers = instant_remote_process(["docker container ls -q"], $this->server, false);
|
||||||
if (!$containers) {
|
if (!$containers) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartDatabaseProxy;
|
||||||
use App\Actions\Proxy\CheckProxy;
|
use App\Actions\Proxy\CheckProxy;
|
||||||
use App\Actions\Proxy\StartProxy;
|
use App\Actions\Proxy\StartProxy;
|
||||||
use App\Models\ApplicationPreview;
|
use App\Models\ApplicationPreview;
|
||||||
@@ -21,10 +22,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public function __construct(public Server $server)
|
|
||||||
{
|
|
||||||
$this->handle();
|
|
||||||
}
|
|
||||||
public function middleware(): array
|
public function middleware(): array
|
||||||
{
|
{
|
||||||
return [(new WithoutOverlapping($this->server->id))->dontRelease()];
|
return [(new WithoutOverlapping($this->server->id))->dontRelease()];
|
||||||
@@ -35,6 +32,12 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
return $this->server->id;
|
return $this->server->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function __construct(public Server $server)
|
||||||
|
{
|
||||||
|
if (isDev()) $this->handle();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
// ray("checking container statuses for {$this->server->id}");
|
// ray("checking container statuses for {$this->server->id}");
|
||||||
@@ -42,23 +45,62 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if (!$this->server->isServerReady()) {
|
if (!$this->server->isServerReady()) {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server);
|
if ($this->server->isSwarm()) {
|
||||||
|
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
|
||||||
|
$containerReplicase = 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);
|
||||||
|
$containerReplicase = null;
|
||||||
|
}
|
||||||
|
if (is_null($containers)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
$containers = format_docker_command_output_to_json($containers);
|
$containers = format_docker_command_output_to_json($containers);
|
||||||
|
if ($containerReplicase) {
|
||||||
|
$containerReplicase = format_docker_command_output_to_json($containerReplicase);
|
||||||
|
foreach ($containerReplicase 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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
$applications = $this->server->applications();
|
$applications = $this->server->applications();
|
||||||
$databases = $this->server->databases();
|
$databases = $this->server->databases();
|
||||||
$services = $this->server->services()->get();
|
$services = $this->server->services()->get();
|
||||||
$previews = $this->server->previews();
|
$previews = $this->server->previews();
|
||||||
|
|
||||||
$foundApplications = [];
|
$foundApplications = [];
|
||||||
$foundApplicationPreviews = [];
|
$foundApplicationPreviews = [];
|
||||||
$foundDatabases = [];
|
$foundDatabases = [];
|
||||||
$foundServices = [];
|
$foundServices = [];
|
||||||
|
|
||||||
foreach ($containers as $container) {
|
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');
|
$containerStatus = data_get($container, 'State.Status');
|
||||||
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
|
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
|
||||||
$containerStatus = "$containerStatus ($containerHealth)";
|
$containerStatus = "$containerStatus ($containerHealth)";
|
||||||
$labels = data_get($container, 'Config.Labels');
|
|
||||||
$labels = Arr::undot(format_docker_labels_to_json($labels));
|
$labels = Arr::undot(format_docker_labels_to_json($labels));
|
||||||
$applicationId = data_get($labels, 'coolify.applicationId');
|
$applicationId = data_get($labels, 'coolify.applicationId');
|
||||||
if ($applicationId) {
|
if ($applicationId) {
|
||||||
@@ -94,11 +136,25 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if ($uuid) {
|
if ($uuid) {
|
||||||
$database = $databases->where('uuid', $uuid)->first();
|
$database = $databases->where('uuid', $uuid)->first();
|
||||||
if ($database) {
|
if ($database) {
|
||||||
|
$isPublic = data_get($database, 'is_public');
|
||||||
$foundDatabases[] = $database->id;
|
$foundDatabases[] = $database->id;
|
||||||
$statusFromDb = $database->status;
|
$statusFromDb = $database->status;
|
||||||
if ($statusFromDb !== $containerStatus) {
|
if ($statusFromDb !== $containerStatus) {
|
||||||
$database->update(['status' => $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 {
|
} else {
|
||||||
// Notify user that this container should not be there.
|
// Notify user that this container should not be there.
|
||||||
}
|
}
|
||||||
@@ -245,7 +301,11 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
// Check if proxy is running
|
// Check if proxy is running
|
||||||
$this->server->proxyType();
|
$this->server->proxyType();
|
||||||
$foundProxyContainer = $containers->filter(function ($value, $key) {
|
$foundProxyContainer = $containers->filter(function ($value, $key) {
|
||||||
return data_get($value, 'Name') === '/coolify-proxy';
|
if ($this->server->isSwarm()) {
|
||||||
|
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'Name') === '/coolify-proxy';
|
||||||
|
}
|
||||||
})->first();
|
})->first();
|
||||||
if (!$foundProxyContainer) {
|
if (!$foundProxyContainer) {
|
||||||
try {
|
try {
|
||||||
@@ -253,8 +313,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if ($shouldStart) {
|
if ($shouldStart) {
|
||||||
StartProxy::run($this->server, false);
|
StartProxy::run($this->server, false);
|
||||||
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||||
} else {
|
|
||||||
ray('Proxy could not be started.');
|
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
ray($e);
|
ray($e);
|
||||||
|
|||||||
@@ -50,11 +50,14 @@ class Application extends BaseModel
|
|||||||
}
|
}
|
||||||
public function link()
|
public function link()
|
||||||
{
|
{
|
||||||
return route('project.application.configuration', [
|
if (data_get($this, 'environment.project.uuid')) {
|
||||||
'project_uuid' => $this->environment->project->uuid,
|
return route('project.application.configuration', [
|
||||||
'environment_name' => $this->environment->name,
|
'project_uuid' => data_get($this, 'environment.project.uuid'),
|
||||||
'application_uuid' => $this->uuid
|
'environment_name' => data_get($this, 'environment.name'),
|
||||||
]);
|
'application_uuid' => data_get($this, 'uuid')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
public function settings()
|
public function settings()
|
||||||
{
|
{
|
||||||
@@ -584,12 +587,12 @@ class Application extends BaseModel
|
|||||||
$commands = collect([]);
|
$commands = collect([]);
|
||||||
if ($dockerConfigFileExists === 'OK') {
|
if ($dockerConfigFileExists === 'OK') {
|
||||||
$commands->push([
|
$commands->push([
|
||||||
"command" => "docker run -d --network $network -v /:/host --name $deploymentUuid --rm -v {$serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock $helperImage",
|
"command" => "docker run -d --network $network --name $deploymentUuid --rm -v {$serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock $helperImage",
|
||||||
"hidden" => true,
|
"hidden" => true,
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
$commands->push([
|
$commands->push([
|
||||||
"command" => "docker run -d --network {$network} -v /:/host --name {$deploymentUuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}",
|
"command" => "docker run -d --network {$network} --name {$deploymentUuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}",
|
||||||
"hidden" => true,
|
"hidden" => true,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -614,7 +617,7 @@ class Application extends BaseModel
|
|||||||
function loadComposeFile($isInit = false)
|
function loadComposeFile($isInit = false)
|
||||||
{
|
{
|
||||||
$initialDockerComposeLocation = $this->docker_compose_location;
|
$initialDockerComposeLocation = $this->docker_compose_location;
|
||||||
$initialDockerComposePrLocation = $this->docker_compose_pr_location;
|
// $initialDockerComposePrLocation = $this->docker_compose_pr_location;
|
||||||
if ($this->build_pack === 'dockercompose') {
|
if ($this->build_pack === 'dockercompose') {
|
||||||
if ($isInit && $this->docker_compose_raw) {
|
if ($isInit && $this->docker_compose_raw) {
|
||||||
return;
|
return;
|
||||||
@@ -623,11 +626,11 @@ class Application extends BaseModel
|
|||||||
['commands' => $cloneCommand] = $this->generateGitImportCommands(deployment_uuid: $uuid, only_checkout: true, exec_in_docker: false, custom_base_dir: '.');
|
['commands' => $cloneCommand] = $this->generateGitImportCommands(deployment_uuid: $uuid, only_checkout: true, exec_in_docker: false, custom_base_dir: '.');
|
||||||
$workdir = rtrim($this->base_directory, '/');
|
$workdir = rtrim($this->base_directory, '/');
|
||||||
$composeFile = $this->docker_compose_location;
|
$composeFile = $this->docker_compose_location;
|
||||||
$prComposeFile = $this->docker_compose_pr_location;
|
// $prComposeFile = $this->docker_compose_pr_location;
|
||||||
$fileList = collect([".$composeFile"]);
|
$fileList = collect([".$workdir$composeFile"]);
|
||||||
if ($composeFile !== $prComposeFile) {
|
// if ($composeFile !== $prComposeFile) {
|
||||||
$fileList->push(".$prComposeFile");
|
// $fileList->push(".$prComposeFile");
|
||||||
}
|
// }
|
||||||
$commands = collect([
|
$commands = collect([
|
||||||
"mkdir -p /tmp/{$uuid} && cd /tmp/{$uuid}",
|
"mkdir -p /tmp/{$uuid} && cd /tmp/{$uuid}",
|
||||||
$cloneCommand,
|
$cloneCommand,
|
||||||
@@ -645,24 +648,24 @@ class Application extends BaseModel
|
|||||||
$this->docker_compose_raw = $composeFileContent;
|
$this->docker_compose_raw = $composeFileContent;
|
||||||
$this->save();
|
$this->save();
|
||||||
}
|
}
|
||||||
if ($composeFile === $prComposeFile) {
|
// if ($composeFile === $prComposeFile) {
|
||||||
$this->docker_compose_pr_raw = $composeFileContent;
|
// $this->docker_compose_pr_raw = $composeFileContent;
|
||||||
$this->save();
|
// $this->save();
|
||||||
} else {
|
// } else {
|
||||||
$commands = collect([
|
// $commands = collect([
|
||||||
"cd /tmp/{$uuid}",
|
// "cd /tmp/{$uuid}",
|
||||||
"cat .$workdir$prComposeFile",
|
// "cat .$workdir$prComposeFile",
|
||||||
]);
|
// ]);
|
||||||
$composePrFileContent = instant_remote_process($commands, $this->destination->server, false);
|
// $composePrFileContent = instant_remote_process($commands, $this->destination->server, false);
|
||||||
if (!$composePrFileContent) {
|
// if (!$composePrFileContent) {
|
||||||
$this->docker_compose_pr_location = $initialDockerComposePrLocation;
|
// $this->docker_compose_pr_location = $initialDockerComposePrLocation;
|
||||||
$this->save();
|
// $this->save();
|
||||||
throw new \Exception("Could not load compose file from $workdir$prComposeFile");
|
// throw new \Exception("Could not load compose file from $workdir$prComposeFile");
|
||||||
} else {
|
// } else {
|
||||||
$this->docker_compose_pr_raw = $composePrFileContent;
|
// $this->docker_compose_pr_raw = $composePrFileContent;
|
||||||
$this->save();
|
// $this->save();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
$commands = collect([
|
$commands = collect([
|
||||||
"rm -rf /tmp/{$uuid}",
|
"rm -rf /tmp/{$uuid}",
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Actions\Server\InstallLogDrain;
|
|
||||||
use App\Actions\Server\InstallNewRelic;
|
|
||||||
use App\Enums\ApplicationDeploymentStatus;
|
use App\Enums\ApplicationDeploymentStatus;
|
||||||
use App\Enums\ProxyStatus;
|
use App\Enums\ProxyStatus;
|
||||||
use App\Enums\ProxyTypes;
|
use App\Enums\ProxyTypes;
|
||||||
@@ -18,7 +16,7 @@ use Illuminate\Support\Sleep;
|
|||||||
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
||||||
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Stringable;
|
use Illuminate\Support\Stringable;
|
||||||
|
|
||||||
class Server extends BaseModel
|
class Server extends BaseModel
|
||||||
{
|
{
|
||||||
@@ -37,25 +35,10 @@ class Server extends BaseModel
|
|||||||
}
|
}
|
||||||
$server->forceFill($payload);
|
$server->forceFill($payload);
|
||||||
});
|
});
|
||||||
|
|
||||||
static::created(function ($server) {
|
static::created(function ($server) {
|
||||||
ServerSetting::create([
|
ServerSetting::create([
|
||||||
'server_id' => $server->id,
|
'server_id' => $server->id,
|
||||||
]);
|
]);
|
||||||
if ($server->id === 0) {
|
|
||||||
StandaloneDocker::create([
|
|
||||||
'id' => 0,
|
|
||||||
'name' => 'coolify',
|
|
||||||
'network' => 'coolify',
|
|
||||||
'server_id' => $server->id,
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
StandaloneDocker::create([
|
|
||||||
'name' => 'coolify',
|
|
||||||
'network' => 'coolify',
|
|
||||||
'server_id' => $server->id,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
static::deleting(function ($server) {
|
static::deleting(function ($server) {
|
||||||
$server->destinations()->each(function ($destination) {
|
$server->destinations()->each(function ($destination) {
|
||||||
@@ -84,7 +67,7 @@ class Server extends BaseModel
|
|||||||
{
|
{
|
||||||
$teamId = currentTeam()->id;
|
$teamId = currentTeam()->id;
|
||||||
$selectArray = collect($select)->concat(['id']);
|
$selectArray = collect($select)->concat(['id']);
|
||||||
return Server::whereTeamId($teamId)->with('settings')->select($selectArray->all())->orderBy('name');
|
return Server::whereTeamId($teamId)->with('settings','swarmDockers','standaloneDockers')->select($selectArray->all())->orderBy('name');
|
||||||
}
|
}
|
||||||
|
|
||||||
static public function isUsable()
|
static public function isUsable()
|
||||||
@@ -103,7 +86,39 @@ class Server extends BaseModel
|
|||||||
{
|
{
|
||||||
return $this->hasOne(ServerSetting::class);
|
return $this->hasOne(ServerSetting::class);
|
||||||
}
|
}
|
||||||
|
public function addInitialNetwork() {
|
||||||
|
if ($this->id === 0) {
|
||||||
|
if ($this->isSwarm()) {
|
||||||
|
SwarmDocker::create([
|
||||||
|
'id' => 0,
|
||||||
|
'name' => 'coolify',
|
||||||
|
'network' => 'coolify-overlay',
|
||||||
|
'server_id' => $this->id,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
StandaloneDocker::create([
|
||||||
|
'id' => 0,
|
||||||
|
'name' => 'coolify',
|
||||||
|
'network' => 'coolify',
|
||||||
|
'server_id' => $this->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($this->isSwarm()) {
|
||||||
|
SwarmDocker::create([
|
||||||
|
'name' => 'coolify-overlay',
|
||||||
|
'network' => 'coolify-overlay',
|
||||||
|
'server_id' => $this->id,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
StandaloneDocker::create([
|
||||||
|
'name' => 'coolify-overlay',
|
||||||
|
'network' => 'coolify',
|
||||||
|
'server_id' => $this->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
public function proxyType()
|
public function proxyType()
|
||||||
{
|
{
|
||||||
$proxyType = $this->proxy->get('type');
|
$proxyType = $this->proxy->get('type');
|
||||||
@@ -332,7 +347,7 @@ class Server extends BaseModel
|
|||||||
}
|
}
|
||||||
public function isLogDrainEnabled()
|
public function isLogDrainEnabled()
|
||||||
{
|
{
|
||||||
return $this->settings->is_logdrain_newrelic_enabled || $this->settings->is_logdrain_highlight_enabled || $this->settings->is_logdrain_axiom_enabled;
|
return $this->settings->is_logdrain_newrelic_enabled || $this->settings->is_logdrain_highlight_enabled || $this->settings->is_logdrain_axiom_enabled || $this->settings->is_logdrain_custom_enabled;
|
||||||
}
|
}
|
||||||
public function validateOS(): bool | Stringable
|
public function validateOS(): bool | Stringable
|
||||||
{
|
{
|
||||||
@@ -359,12 +374,16 @@ class Server extends BaseModel
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public function isSwarm()
|
||||||
|
{
|
||||||
|
return data_get($this, 'settings.is_swarm_manager') || data_get($this, 'settings.is_swarm_worker');
|
||||||
|
}
|
||||||
public function validateConnection()
|
public function validateConnection()
|
||||||
{
|
{
|
||||||
|
$server = Server::find($this->id);
|
||||||
if ($this->skipServer()) {
|
if ($this->skipServer()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$uptime = instant_remote_process(['uptime'], $this, false);
|
$uptime = instant_remote_process(['uptime'], $this, false);
|
||||||
if (!$uptime) {
|
if (!$uptime) {
|
||||||
$this->settings()->update([
|
$this->settings()->update([
|
||||||
@@ -375,14 +394,14 @@ class Server extends BaseModel
|
|||||||
$this->settings()->update([
|
$this->settings()->update([
|
||||||
'is_reachable' => true,
|
'is_reachable' => true,
|
||||||
]);
|
]);
|
||||||
$this->update([
|
$server->update([
|
||||||
'unreachable_count' => 0,
|
'unreachable_count' => 0,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data_get($this, 'unreachable_notification_sent') === true) {
|
if (data_get($this, 'unreachable_notification_sent') === true) {
|
||||||
$this->team->notify(new Revived($this));
|
$this->team->notify(new Revived($this));
|
||||||
$this->update(['unreachable_notification_sent' => false]);
|
$server->update(['unreachable_notification_sent' => false]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -400,7 +419,20 @@ class Server extends BaseModel
|
|||||||
}
|
}
|
||||||
$this->settings->is_usable = true;
|
$this->settings->is_usable = true;
|
||||||
$this->settings->save();
|
$this->settings->save();
|
||||||
$this->validateCoolifyNetwork();
|
$this->validateCoolifyNetwork(isSwarm: false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public function validateDockerSwarm()
|
||||||
|
{
|
||||||
|
$swarmStatus = instant_remote_process(["docker info|grep -i swarm"], $this, false);
|
||||||
|
$swarmStatus = str($swarmStatus)->trim()->after(':')->trim();
|
||||||
|
if ($swarmStatus === 'inactive') {
|
||||||
|
throw new \Exception('Docker Swarm is not initiated. Please join the server to a swarm before continuing.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$this->settings->is_usable = true;
|
||||||
|
$this->settings->save();
|
||||||
|
$this->validateCoolifyNetwork(isSwarm: true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
public function validateDockerEngineVersion()
|
public function validateDockerEngineVersion()
|
||||||
@@ -417,9 +449,13 @@ class Server extends BaseModel
|
|||||||
$this->settings->save();
|
$this->settings->save();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
public function validateCoolifyNetwork()
|
public function validateCoolifyNetwork($isSwarm = false)
|
||||||
{
|
{
|
||||||
return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false);
|
if ($isSwarm) {
|
||||||
|
return instant_remote_process(["docker network create --attachable --driver overlay coolify-overlay >/dev/null 2>&1 || true"], $this, false);
|
||||||
|
} else {
|
||||||
|
return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public function executeRemoteCommand(Collection $commands, ?ApplicationDeploymentQueue $loggingModel = null)
|
public function executeRemoteCommand(Collection $commands, ?ApplicationDeploymentQueue $loggingModel = null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -131,6 +131,20 @@ class Service extends BaseModel
|
|||||||
}
|
}
|
||||||
$fields->put('Weblate', $data);
|
$fields->put('Weblate', $data);
|
||||||
break;
|
break;
|
||||||
|
case str($image)?->contains('meilisearch'):
|
||||||
|
$data = collect([]);
|
||||||
|
$SERVICE_PASSWORD_MEILISEARCH = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_MEILISEARCH')->first();
|
||||||
|
if ($SERVICE_PASSWORD_MEILISEARCH) {
|
||||||
|
$data = $data->merge([
|
||||||
|
'API Key' => [
|
||||||
|
'key' => data_get($SERVICE_PASSWORD_MEILISEARCH, 'key'),
|
||||||
|
'value' => data_get($SERVICE_PASSWORD_MEILISEARCH, 'value'),
|
||||||
|
'isPassword' => true,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$fields->put('Meilisearch', $data);
|
||||||
|
break;
|
||||||
case str($image)?->contains('ghost'):
|
case str($image)?->contains('ghost'):
|
||||||
$data = collect([]);
|
$data = collect([]);
|
||||||
$MAIL_OPTIONS_AUTH_PASS = $this->environment_variables()->where('key', 'MAIL_OPTIONS_AUTH_PASS')->first();
|
$MAIL_OPTIONS_AUTH_PASS = $this->environment_variables()->where('key', 'MAIL_OPTIONS_AUTH_PASS')->first();
|
||||||
@@ -193,6 +207,7 @@ class Service extends BaseModel
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ray($fields);
|
||||||
$databases = $this->databases()->get();
|
$databases = $this->databases()->get();
|
||||||
|
|
||||||
foreach ($databases as $database) {
|
foreach ($databases as $database) {
|
||||||
@@ -363,11 +378,14 @@ class Service extends BaseModel
|
|||||||
}
|
}
|
||||||
public function link()
|
public function link()
|
||||||
{
|
{
|
||||||
return route('project.service.configuration', [
|
if (data_get($this, 'environment.project.uuid')) {
|
||||||
'project_uuid' => $this->environment->project->uuid,
|
return route('project.service.configuration', [
|
||||||
'environment_name' => $this->environment->name,
|
'project_uuid' => data_get($this, 'environment.project.uuid'),
|
||||||
'service_uuid' => $this->uuid
|
'environment_name' => data_get($this, 'environment.name'),
|
||||||
]);
|
'service_uuid' => data_get($this, 'uuid')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
public function documentation()
|
public function documentation()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -43,11 +43,14 @@ class StandaloneMariadb extends BaseModel
|
|||||||
}
|
}
|
||||||
public function link()
|
public function link()
|
||||||
{
|
{
|
||||||
return route('project.database.configuration', [
|
if (data_get($this, 'environment.project.uuid')) {
|
||||||
'project_uuid' => $this->environment->project->uuid,
|
return route('project.database.configuration', [
|
||||||
'environment_name' => $this->environment->name,
|
'project_uuid' => data_get($this, 'environment.project.uuid'),
|
||||||
'database_uuid' => $this->uuid
|
'environment_name' => data_get($this, 'environment.name'),
|
||||||
]);
|
'database_uuid' => data_get($this, 'uuid')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
public function isLogDrainEnabled()
|
public function isLogDrainEnabled()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -50,11 +50,14 @@ class StandaloneMongodb extends BaseModel
|
|||||||
}
|
}
|
||||||
public function link()
|
public function link()
|
||||||
{
|
{
|
||||||
return route('project.database.configuration', [
|
if (data_get($this, 'environment.project.uuid')) {
|
||||||
'project_uuid' => $this->environment->project->uuid,
|
return route('project.database.configuration', [
|
||||||
'environment_name' => $this->environment->name,
|
'project_uuid' => data_get($this, 'environment.project.uuid'),
|
||||||
'database_uuid' => $this->uuid
|
'environment_name' => data_get($this, 'environment.name'),
|
||||||
]);
|
'database_uuid' => data_get($this, 'uuid')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
public function mongoInitdbRootPassword(): Attribute
|
public function mongoInitdbRootPassword(): Attribute
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -43,11 +43,14 @@ class StandaloneMysql extends BaseModel
|
|||||||
}
|
}
|
||||||
public function link()
|
public function link()
|
||||||
{
|
{
|
||||||
return route('project.database.configuration', [
|
if (data_get($this, 'environment.project.uuid')) {
|
||||||
'project_uuid' => $this->environment->project->uuid,
|
return route('project.database.configuration', [
|
||||||
'environment_name' => $this->environment->name,
|
'project_uuid' => data_get($this, 'environment.project.uuid'),
|
||||||
'database_uuid' => $this->uuid
|
'environment_name' => data_get($this, 'environment.name'),
|
||||||
]);
|
'database_uuid' => data_get($this, 'uuid')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
public function type(): string
|
public function type(): string
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -43,11 +43,14 @@ class StandalonePostgresql extends BaseModel
|
|||||||
}
|
}
|
||||||
public function link()
|
public function link()
|
||||||
{
|
{
|
||||||
return route('project.database.configuration', [
|
if (data_get($this, 'environment.project.uuid')) {
|
||||||
'project_uuid' => $this->environment->project->uuid,
|
return route('project.database.configuration', [
|
||||||
'environment_name' => $this->environment->name,
|
'project_uuid' => data_get($this, 'environment.project.uuid'),
|
||||||
'database_uuid' => $this->uuid
|
'environment_name' => data_get($this, 'environment.name'),
|
||||||
]);
|
'database_uuid' => data_get($this, 'uuid')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
public function isLogDrainEnabled()
|
public function isLogDrainEnabled()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -38,11 +38,14 @@ class StandaloneRedis extends BaseModel
|
|||||||
}
|
}
|
||||||
public function link()
|
public function link()
|
||||||
{
|
{
|
||||||
return route('project.database.configuration', [
|
if (data_get($this, 'environment.project.uuid')) {
|
||||||
'project_uuid' => $this->environment->project->uuid,
|
return route('project.database.configuration', [
|
||||||
'environment_name' => $this->environment->name,
|
'project_uuid' => data_get($this, 'environment.project.uuid'),
|
||||||
'database_uuid' => $this->uuid
|
'environment_name' => data_get($this, 'environment.name'),
|
||||||
]);
|
'database_uuid' => data_get($this, 'uuid')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
public function isLogDrainEnabled()
|
public function isLogDrainEnabled()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,13 +4,57 @@ namespace App\Models;
|
|||||||
|
|
||||||
class SwarmDocker extends BaseModel
|
class SwarmDocker extends BaseModel
|
||||||
{
|
{
|
||||||
|
protected $guarded = [];
|
||||||
|
|
||||||
public function applications()
|
public function applications()
|
||||||
{
|
{
|
||||||
return $this->morphMany(Application::class, 'destination');
|
return $this->morphMany(Application::class, 'destination');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function postgresqls()
|
||||||
|
{
|
||||||
|
return $this->morphMany(StandalonePostgresql::class, 'destination');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function redis()
|
||||||
|
{
|
||||||
|
return $this->morphMany(StandaloneRedis::class, 'destination');
|
||||||
|
}
|
||||||
|
public function mongodbs()
|
||||||
|
{
|
||||||
|
return $this->morphMany(StandaloneMongodb::class, 'destination');
|
||||||
|
}
|
||||||
|
public function mysqls()
|
||||||
|
{
|
||||||
|
return $this->morphMany(StandaloneMysql::class, 'destination');
|
||||||
|
}
|
||||||
|
public function mariadbs()
|
||||||
|
{
|
||||||
|
return $this->morphMany(StandaloneMariadb::class, 'destination');
|
||||||
|
}
|
||||||
|
|
||||||
public function server()
|
public function server()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Server::class);
|
return $this->belongsTo(Server::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function services()
|
||||||
|
{
|
||||||
|
return $this->morphMany(Service::class, 'destination');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function databases()
|
||||||
|
{
|
||||||
|
$postgresqls = $this->postgresqls;
|
||||||
|
$redis = $this->redis;
|
||||||
|
$mongodbs = $this->mongodbs;
|
||||||
|
$mysqls = $this->mysqls;
|
||||||
|
$mariadbs = $this->mariadbs;
|
||||||
|
return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attachedTo()
|
||||||
|
{
|
||||||
|
return $this->applications?->count() > 0 || $this->databases()->count() > 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,12 +97,12 @@ function prepareHelperContainer(Server $server, string $network, string $deploym
|
|||||||
$commands = collect([]);
|
$commands = collect([]);
|
||||||
if ($dockerConfigFileExists === 'OK') {
|
if ($dockerConfigFileExists === 'OK') {
|
||||||
$commands->push([
|
$commands->push([
|
||||||
"command" => "docker run -d --network $network -v /:/host --name $deploymentUuid --rm -v {$serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock $helperImage",
|
"command" => "docker run -d --network $network --name $deploymentUuid --rm -v {$serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock $helperImage",
|
||||||
"hidden" => true,
|
"hidden" => true,
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
$commands->push([
|
$commands->push([
|
||||||
"command" => "docker run -d --network {$network} -v /:/host --name {$deploymentUuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}",
|
"command" => "docker run -d --network {$network} --name {$deploymentUuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}",
|
||||||
"hidden" => true,
|
"hidden" => true,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,7 +93,11 @@ function executeInDocker(string $containerId, string $command)
|
|||||||
|
|
||||||
function getContainerStatus(Server $server, string $container_id, bool $all_data = false, bool $throwError = false)
|
function getContainerStatus(Server $server, string $container_id, bool $all_data = false, bool $throwError = false)
|
||||||
{
|
{
|
||||||
$container = instant_remote_process(["docker inspect --format '{{json .}}' {$container_id}"], $server, $throwError);
|
if ($server->isSwarm()) {
|
||||||
|
$container = instant_remote_process(["docker service ls --filter 'name={$container_id}' --format '{{json .}}' "], $server, $throwError);
|
||||||
|
} else {
|
||||||
|
$container = instant_remote_process(["docker inspect --format '{{json .}}' {$container_id}"], $server, $throwError);
|
||||||
|
}
|
||||||
if (!$container) {
|
if (!$container) {
|
||||||
return 'exited';
|
return 'exited';
|
||||||
}
|
}
|
||||||
@@ -101,7 +105,19 @@ function getContainerStatus(Server $server, string $container_id, bool $all_data
|
|||||||
if ($all_data) {
|
if ($all_data) {
|
||||||
return $container[0];
|
return $container[0];
|
||||||
}
|
}
|
||||||
return data_get($container[0], 'State.Status', 'exited');
|
if ($server->isSwarm()) {
|
||||||
|
$replicas = data_get($container[0], 'Replicas');
|
||||||
|
$replicas = explode('/', $replicas);
|
||||||
|
$active = (int)$replicas[0];
|
||||||
|
$total = (int)$replicas[1];
|
||||||
|
if ($active === $total) {
|
||||||
|
return 'running';
|
||||||
|
} else {
|
||||||
|
return 'starting';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return data_get($container[0], 'State.Status', 'exited');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateApplicationContainerName(Application $application, $pull_request_id = 0)
|
function generateApplicationContainerName(Application $application, $pull_request_id = 0)
|
||||||
@@ -275,8 +291,11 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
|||||||
return $labels->all();
|
return $labels->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
function isDatabaseImage(string $image)
|
function isDatabaseImage(?string $image = null)
|
||||||
{
|
{
|
||||||
|
if (is_null($image)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
$image = str($image);
|
$image = str($image);
|
||||||
if ($image->contains(':')) {
|
if ($image->contains(':')) {
|
||||||
$image = str($image);
|
$image = str($image);
|
||||||
|
|||||||
@@ -54,9 +54,15 @@ function connectProxyToNetworks(Server $server)
|
|||||||
function generate_default_proxy_configuration(Server $server)
|
function generate_default_proxy_configuration(Server $server)
|
||||||
{
|
{
|
||||||
$proxy_path = get_proxy_path();
|
$proxy_path = get_proxy_path();
|
||||||
$networks = collect($server->standaloneDockers)->map(function ($docker) {
|
if ($server->isSwarm()) {
|
||||||
return $docker['network'];
|
$networks = collect($server->swarmDockers)->map(function ($docker) {
|
||||||
})->unique();
|
return $docker['network'];
|
||||||
|
})->unique();
|
||||||
|
} else {
|
||||||
|
$networks = collect($server->standaloneDockers)->map(function ($docker) {
|
||||||
|
return $docker['network'];
|
||||||
|
})->unique();
|
||||||
|
}
|
||||||
if ($networks->count() === 0) {
|
if ($networks->count() === 0) {
|
||||||
$networks = collect(['coolify']);
|
$networks = collect(['coolify']);
|
||||||
}
|
}
|
||||||
@@ -66,6 +72,16 @@ function generate_default_proxy_configuration(Server $server)
|
|||||||
"external" => true,
|
"external" => true,
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
$labels = [
|
||||||
|
"traefik.enable=true",
|
||||||
|
"traefik.http.routers.traefik.entrypoints=http",
|
||||||
|
"traefik.http.routers.traefik.middlewares=traefik-basic-auth@file",
|
||||||
|
"traefik.http.routers.traefik.service=api@internal",
|
||||||
|
"traefik.http.services.traefik.loadbalancer.server.port=8080",
|
||||||
|
// Global Middlewares
|
||||||
|
"traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https",
|
||||||
|
"traefik.http.middlewares.gzip.compress=true",
|
||||||
|
];
|
||||||
$config = [
|
$config = [
|
||||||
"version" => "3.8",
|
"version" => "3.8",
|
||||||
"networks" => $array_of_networks->toArray(),
|
"networks" => $array_of_networks->toArray(),
|
||||||
@@ -102,7 +118,6 @@ function generate_default_proxy_configuration(Server $server)
|
|||||||
"--entrypoints.https.address=:443",
|
"--entrypoints.https.address=:443",
|
||||||
"--entrypoints.http.http.encodequerysemicolons=true",
|
"--entrypoints.http.http.encodequerysemicolons=true",
|
||||||
"--entrypoints.https.http.encodequerysemicolons=true",
|
"--entrypoints.https.http.encodequerysemicolons=true",
|
||||||
"--providers.docker=true",
|
|
||||||
"--providers.docker.exposedbydefault=false",
|
"--providers.docker.exposedbydefault=false",
|
||||||
"--providers.file.directory=/traefik/dynamic/",
|
"--providers.file.directory=/traefik/dynamic/",
|
||||||
"--providers.file.watch=true",
|
"--providers.file.watch=true",
|
||||||
@@ -110,16 +125,7 @@ function generate_default_proxy_configuration(Server $server)
|
|||||||
"--certificatesresolvers.letsencrypt.acme.storage=/traefik/acme.json",
|
"--certificatesresolvers.letsencrypt.acme.storage=/traefik/acme.json",
|
||||||
"--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=http",
|
"--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=http",
|
||||||
],
|
],
|
||||||
"labels" => [
|
"labels" => $labels,
|
||||||
"traefik.enable=true",
|
|
||||||
"traefik.http.routers.traefik.entrypoints=http",
|
|
||||||
"traefik.http.routers.traefik.middlewares=traefik-basic-auth@file",
|
|
||||||
"traefik.http.routers.traefik.service=api@internal",
|
|
||||||
"traefik.http.services.traefik.loadbalancer.server.port=8080",
|
|
||||||
// Global Middlewares
|
|
||||||
"traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https",
|
|
||||||
"traefik.http.middlewares.gzip.compress=true",
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
@@ -128,7 +134,24 @@ function generate_default_proxy_configuration(Server $server)
|
|||||||
$config['services']['traefik']['command'][] = "--accesslog.filepath=/traefik/access.log";
|
$config['services']['traefik']['command'][] = "--accesslog.filepath=/traefik/access.log";
|
||||||
$config['services']['traefik']['command'][] = "--accesslog.bufferingsize=100";
|
$config['services']['traefik']['command'][] = "--accesslog.bufferingsize=100";
|
||||||
}
|
}
|
||||||
$config = Yaml::dump($config, 4, 2);
|
if ($server->isSwarm()) {
|
||||||
|
data_forget($config, 'services.traefik.container_name');
|
||||||
|
data_forget($config, 'services.traefik.restart');
|
||||||
|
data_forget($config, 'services.traefik.labels');
|
||||||
|
|
||||||
|
$config['services']['traefik']['command'][] = "--providers.docker.swarmMode=true";
|
||||||
|
$config['services']['traefik']['deploy'] = [
|
||||||
|
"labels" => $labels,
|
||||||
|
"placement" => [
|
||||||
|
"constraints" => [
|
||||||
|
"node.role==manager",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$config['services']['traefik']['command'][] = "--providers.docker=true";
|
||||||
|
}
|
||||||
|
$config = Yaml::dump($config, 12, 2);
|
||||||
SaveConfiguration::run($server, $config);
|
SaveConfiguration::run($server, $config);
|
||||||
return $config;
|
return $config;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,6 +123,9 @@ function instant_remote_process(Collection|array $command, Server $server, $thro
|
|||||||
}
|
}
|
||||||
return excludeCertainErrors($process->errorOutput(), $exitCode);
|
return excludeCertainErrors($process->errorOutput(), $exitCode);
|
||||||
}
|
}
|
||||||
|
if ($output === 'null') {
|
||||||
|
$output = null;
|
||||||
|
}
|
||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
function excludeCertainErrors(string $errorOutput, ?int $exitCode = null)
|
function excludeCertainErrors(string $errorOutput, ?int $exitCode = null)
|
||||||
|
|||||||
@@ -863,7 +863,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
$key = Str::of($variableName);
|
$key = Str::of($variableName);
|
||||||
$value = Str::of($variable);
|
$value = Str::of($variable);
|
||||||
}
|
}
|
||||||
// TODO: here is the problem
|
|
||||||
if ($key->startsWith('SERVICE_FQDN')) {
|
if ($key->startsWith('SERVICE_FQDN')) {
|
||||||
if ($isNew || $savedService->fqdn === null) {
|
if ($isNew || $savedService->fqdn === null) {
|
||||||
$name = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower();
|
$name = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower();
|
||||||
@@ -914,13 +913,17 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ($value?->startsWith('$')) {
|
if ($value?->startsWith('$')) {
|
||||||
$value = Str::of(replaceVariables($value));
|
|
||||||
$key = $value;
|
|
||||||
$foundEnv = EnvironmentVariable::where([
|
$foundEnv = EnvironmentVariable::where([
|
||||||
'key' => $key,
|
'key' => $key,
|
||||||
'service_id' => $resource->id,
|
'service_id' => $resource->id,
|
||||||
])->first();
|
])->first();
|
||||||
|
$value = Str::of(replaceVariables($value));
|
||||||
|
$key = $value;
|
||||||
if ($value->startsWith('SERVICE_')) {
|
if ($value->startsWith('SERVICE_')) {
|
||||||
|
$foundEnv = EnvironmentVariable::where([
|
||||||
|
'key' => $key,
|
||||||
|
'service_id' => $resource->id,
|
||||||
|
])->first();
|
||||||
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
|
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
|
||||||
if ($command->value() === 'FQDN' || $command->value() === 'URL') {
|
if ($command->value() === 'FQDN' || $command->value() === 'URL') {
|
||||||
if (Str::lower($forService) === $serviceName) {
|
if (Str::lower($forService) === $serviceName) {
|
||||||
@@ -1000,7 +1003,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
if ($savedService->serviceType()) {
|
if ($savedService->serviceType()) {
|
||||||
$fqdns = generateServiceSpecificFqdns($savedService, forTraefik: true);
|
$fqdns = generateServiceSpecificFqdns($savedService, forTraefik: true);
|
||||||
} else {
|
} else {
|
||||||
$fqdns = collect(data_get($savedService, 'fqdns'));
|
$fqdns = collect(data_get($savedService, 'fqdns'))->filter();
|
||||||
}
|
}
|
||||||
$defaultLabels = defaultLabels($resource->id, $containerName, type: 'service', subType: $isDatabase ? 'database' : 'application', subId: $savedService->id);
|
$defaultLabels = defaultLabels($resource->id, $containerName, type: 'service', subType: $isDatabase ? 'database' : 'application', subId: $savedService->id);
|
||||||
$serviceLabels = $serviceLabels->merge($defaultLabels);
|
$serviceLabels = $serviceLabels->merge($defaultLabels);
|
||||||
@@ -1099,6 +1102,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
$serviceNetworks = collect(data_get($service, 'networks', []));
|
$serviceNetworks = collect(data_get($service, 'networks', []));
|
||||||
$serviceVariables = collect(data_get($service, 'environment', []));
|
$serviceVariables = collect(data_get($service, 'environment', []));
|
||||||
$serviceLabels = collect(data_get($service, 'labels', []));
|
$serviceLabels = collect(data_get($service, 'labels', []));
|
||||||
|
$serviceBuildVariables = collect(data_get($service, 'build.args', []));
|
||||||
|
$serviceVariables = $serviceVariables->merge($serviceBuildVariables);
|
||||||
if ($serviceLabels->count() > 0) {
|
if ($serviceLabels->count() > 0) {
|
||||||
$removedLabels = collect([]);
|
$removedLabels = collect([]);
|
||||||
$serviceLabels = $serviceLabels->filter(function ($serviceLabel, $serviceLabelName) use ($removedLabels) {
|
$serviceLabels = $serviceLabels->filter(function ($serviceLabel, $serviceLabelName) use ($removedLabels) {
|
||||||
@@ -1145,6 +1150,52 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
data_set($service, 'volumes', $serviceVolumes->toArray());
|
data_set($service, 'volumes', $serviceVolumes->toArray());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (count($serviceVolumes) > 0) {
|
||||||
|
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes) {
|
||||||
|
if (is_string($volume)) {
|
||||||
|
$volume = str($volume);
|
||||||
|
if ($volume->contains(':')) {
|
||||||
|
$name = $volume->before(':');
|
||||||
|
$mount = $volume->after(':');
|
||||||
|
if ($name->startsWith('.') || $name->startsWith('~')) {
|
||||||
|
$dir = base_configuration_dir() . '/applications/' . $resource->uuid;
|
||||||
|
if ($name->startsWith('.')) {
|
||||||
|
$name = $name->replaceFirst('.', $dir);
|
||||||
|
}
|
||||||
|
if ($name->startsWith('~')) {
|
||||||
|
$name = $name->replaceFirst('~', $dir);
|
||||||
|
}
|
||||||
|
$volume = str("$name:$mount");
|
||||||
|
} else {
|
||||||
|
$topLevelVolumes->put($name->value(), [
|
||||||
|
'name' => $name->value(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (is_array($volume)) {
|
||||||
|
$source = data_get($volume, 'source');
|
||||||
|
if ($source) {
|
||||||
|
if (str($source, '.') || str($source, '~')) {
|
||||||
|
$dir = base_configuration_dir() . '/applications/' . $resource->uuid;
|
||||||
|
if (str($source, '.')) {
|
||||||
|
$source = str('.', $dir, $source);
|
||||||
|
}
|
||||||
|
if (str($source, '~')) {
|
||||||
|
$source = str('~', $dir, $source);
|
||||||
|
}
|
||||||
|
data_set($volume, 'source', $source);
|
||||||
|
} else {
|
||||||
|
data_set($volume, 'source', $source);
|
||||||
|
$topLevelVolumes->put($source, [
|
||||||
|
'name' => $source,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $volume->value();
|
||||||
|
});
|
||||||
|
data_set($service, 'volumes', $serviceVolumes->toArray());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Decide if the service is a database
|
// Decide if the service is a database
|
||||||
$isDatabase = isDatabaseImage(data_get_str($service, 'image'));
|
$isDatabase = isDatabaseImage(data_get_str($service, 'image'));
|
||||||
@@ -1271,13 +1322,18 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ($value?->startsWith('$')) {
|
if ($value?->startsWith('$')) {
|
||||||
$value = Str::of(replaceVariables($value));
|
|
||||||
$key = $value;
|
|
||||||
$foundEnv = EnvironmentVariable::where([
|
$foundEnv = EnvironmentVariable::where([
|
||||||
'key' => $key,
|
'key' => $key,
|
||||||
'application_id' => $resource->id,
|
'service_id' => $resource->id,
|
||||||
])->first();
|
])->first();
|
||||||
|
$value = Str::of(replaceVariables($value));
|
||||||
|
$key = $value;
|
||||||
|
|
||||||
if ($value->startsWith('SERVICE_')) {
|
if ($value->startsWith('SERVICE_')) {
|
||||||
|
$foundEnv = EnvironmentVariable::where([
|
||||||
|
'key' => $key,
|
||||||
|
'application_id' => $resource->id,
|
||||||
|
])->first();
|
||||||
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
|
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
|
||||||
if ($command->value() === 'FQDN' || $command->value() === 'URL') {
|
if ($command->value() === 'FQDN' || $command->value() === 'URL') {
|
||||||
if (Str::lower($forService) === $serviceName) {
|
if (Str::lower($forService) === $serviceName) {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ return [
|
|||||||
'official' => 'https://cdn.coollabs.io/coolify/service-templates.json',
|
'official' => 'https://cdn.coollabs.io/coolify/service-templates.json',
|
||||||
],
|
],
|
||||||
'limits' => [
|
'limits' => [
|
||||||
'trial_period' => 7,
|
'trial_period' => 0,
|
||||||
'server' => [
|
'server' => [
|
||||||
'zero' => 0,
|
'zero' => 0,
|
||||||
'self-hosted' => 999999999999,
|
'self-hosted' => 999999999999,
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
return [
|
return [
|
||||||
|
|
||||||
// @see https://docs.sentry.io/product/sentry-basics/dsn-explainer/
|
// @see https://docs.sentry.io/product/sentry-basics/dsn-explainer/
|
||||||
'dsn' => 'https://396748153b19c469f5ceff50f1664323@o1082494.ingest.sentry.io/4505347448045568',
|
'dsn' => 'https://bea22abf110618b07252032aa2e07859@o1082494.ingest.sentry.io/4505347448045568',
|
||||||
|
|
||||||
// The release version of your application
|
// The release version of your application
|
||||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||||
'release' => '4.0.0-beta.147',
|
'release' => '4.0.0-beta.152',
|
||||||
// When left empty or `null` the Laravel environment will be used
|
// When left empty or `null` the Laravel environment will be used
|
||||||
'environment' => config('app.env'),
|
'environment' => config('app.env'),
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return '4.0.0-beta.147';
|
return '4.0.0-beta.152';
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?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('swarm_dockers', function (Blueprint $table) {
|
||||||
|
$table->string('network');
|
||||||
|
|
||||||
|
$table->unique(['server_id', 'network']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('swarm_dockers', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('network');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?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('server_settings', function (Blueprint $table) {
|
||||||
|
$table->renameColumn('is_part_of_swarm', 'is_swarm_manager');
|
||||||
|
$table->boolean('is_swarm_worker')->default(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('server_settings', function (Blueprint $table) {
|
||||||
|
$table->renameColumn('is_swarm_manager', 'is_part_of_swarm');
|
||||||
|
$table->dropColumn('is_swarm_worker');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('application_settings', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_include_timestamps')->default(false);
|
||||||
|
});
|
||||||
|
Schema::table('service_applications', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_include_timestamps')->default(false);
|
||||||
|
});
|
||||||
|
Schema::table('service_databases', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_include_timestamps')->default(false);
|
||||||
|
});
|
||||||
|
Schema::table('standalone_mysqls', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_include_timestamps')->default(false);
|
||||||
|
});
|
||||||
|
Schema::table('standalone_postgresqls', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_include_timestamps')->default(false);
|
||||||
|
});
|
||||||
|
Schema::table('standalone_redis', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_include_timestamps')->default(false);
|
||||||
|
});
|
||||||
|
Schema::table('standalone_mongodbs', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_include_timestamps')->default(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('application_settings', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_include_timestamps');
|
||||||
|
});
|
||||||
|
Schema::table('service_applications', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_include_timestamps');
|
||||||
|
});
|
||||||
|
Schema::table('service_databases', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_include_timestamps');
|
||||||
|
});
|
||||||
|
Schema::table('standalone_mysqls', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_include_timestamps');
|
||||||
|
});
|
||||||
|
Schema::table('standalone_postgresqls', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_include_timestamps');
|
||||||
|
});
|
||||||
|
Schema::table('standalone_redis', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_include_timestamps');
|
||||||
|
});
|
||||||
|
Schema::table('standalone_mongodbs', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_include_timestamps');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?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('server_settings', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_logdrain_custom_enabled')->default(false);
|
||||||
|
$table->text('logdrain_custom_config')->nullable();
|
||||||
|
$table->text('logdrain_custom_config_parser')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('server_settings', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_logdrain_custom_enabled');
|
||||||
|
$table->dropColumn('logdrain_custom_config');
|
||||||
|
$table->dropColumn('logdrain_custom_config_parser');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -13,9 +13,9 @@ class SwarmDockerSeeder extends Seeder
|
|||||||
*/
|
*/
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
SwarmDocker::create([
|
// SwarmDocker::create([
|
||||||
'name' => 'Swarm Docker 1',
|
// 'name' => 'Swarm Docker 1',
|
||||||
'server_id' => 1,
|
// 'server_id' => 1,
|
||||||
]);
|
// ]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
#!/command/execlineb -P
|
#!/command/execlineb -P
|
||||||
php /var/www/html/artisan app:init
|
php /var/www/html/artisan app:init --cleanup
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
<div class="flex-1"></div>
|
<div class="flex-1"></div>
|
||||||
@if ($application->build_pack === 'dockercompose' && is_null($application->docker_compose_raw))
|
@if ($application->build_pack === 'dockercompose' && is_null($application->docker_compose_raw))
|
||||||
<div>Please load a Compose file.</div>
|
<div>Please load a Compose file.</div>
|
||||||
|
@elseif ($application->destination->server->isSwarm() && str($application->docker_registry_image_name)->isEmpty())
|
||||||
|
Swarm Deployments requires a Docker Image in a Registry.
|
||||||
@else
|
@else
|
||||||
<x-applications.advanced :application="$application" />
|
<x-applications.advanced :application="$application" />
|
||||||
@if ($application->status !== 'exited')
|
@if ($application->status !== 'exited')
|
||||||
|
|||||||
@@ -21,8 +21,11 @@
|
|||||||
</label>
|
</label>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
<div class="py-2 text-center"><span class="font-bold text-warning">{{ config('constants.limits.trial_period') }}
|
@if (config('constants.limits.trial_period') > 0)
|
||||||
days trial</span> included on all plans, without credit card details.</div>
|
<div class="py-2 text-center"><span
|
||||||
|
class="font-bold text-warning">{{ config('constants.limits.trial_period') }}
|
||||||
|
days trial</span> included on all plans, without credit card details.</div>
|
||||||
|
@endif
|
||||||
<div x-show="selected === 'monthly'" class="flex justify-center h-10 mt-3 text-sm leading-6 ">
|
<div x-show="selected === 'monthly'" class="flex justify-center h-10 mt-3 text-sm leading-6 ">
|
||||||
<div>Save <span class="font-bold text-warning">10%</span> annually with the yearly plans.
|
<div>Save <span class="font-bold text-warning">10%</span> annually with the yearly plans.
|
||||||
</div>
|
</div>
|
||||||
@@ -255,8 +258,8 @@
|
|||||||
<div class="flex items-start gap-4 text-xl tracking-tight">Need official support for
|
<div class="flex items-start gap-4 text-xl tracking-tight">Need official support for
|
||||||
your self-hosted instance?
|
your self-hosted instance?
|
||||||
<x-forms.button>
|
<x-forms.button>
|
||||||
<a class="font-bold text-white hover:no-underline"
|
<a class="font-bold text-white hover:no-underline" href="{{ config('coolify.docs') }}">Contact
|
||||||
href="{{ config('coolify.docs') }}">Contact Us</a>
|
Us</a>
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -131,16 +131,16 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@if (!$serverReachable)
|
@if (!$serverReachable)
|
||||||
This server is not reachable with the following public key.
|
This server is not reachable with the following public key.
|
||||||
<br /> <br />
|
<br /> <br />
|
||||||
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for user
|
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for user
|
||||||
'root' or skip the boarding process and add a new private key manually to Coolify and to the
|
'root' or skip the boarding process and add a new private key manually to Coolify and to the
|
||||||
server.
|
server.
|
||||||
<x-forms.input readonly id="serverPublicKey"></x-forms.input>
|
<x-forms.input readonly id="serverPublicKey"></x-forms.input>
|
||||||
<x-forms.button class="box" wire:target="validateServer"
|
<x-forms.button class="box" wire:target="validateServer" wire:click="validateServer">Check
|
||||||
wire:click="validateServer">Check again
|
again
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
@endif
|
@endif
|
||||||
</x-slot:actions>
|
</x-slot:actions>
|
||||||
<x-slot:explanation>
|
<x-slot:explanation>
|
||||||
<p>Private Keys are used to connect to a remote server through a secure shell, called SSH.</p>
|
<p>Private Keys are used to connect to a remote server through a secure shell, called SSH.</p>
|
||||||
@@ -200,14 +200,18 @@
|
|||||||
label="Description" id="remoteServerDescription" />
|
label="Description" id="remoteServerDescription" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<x-forms.input required placeholder="127.0.0.1" label="IP Address"
|
<x-forms.input required placeholder="127.0.0.1" label="IP Address" id="remoteServerHost" />
|
||||||
id="remoteServerHost" />
|
|
||||||
<x-forms.input required placeholder="Port number of your server. Default is 22."
|
<x-forms.input required placeholder="Port number of your server. Default is 22."
|
||||||
label="Port" id="remoteServerPort" />
|
label="Port" id="remoteServerPort" />
|
||||||
<x-forms.input required readonly
|
<x-forms.input required readonly
|
||||||
placeholder="Username to connect to your server. Default is root." label="Username"
|
placeholder="Username to connect to your server. Default is root." label="Username"
|
||||||
id="remoteServerUser" />
|
id="remoteServerUser" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="w-64">
|
||||||
|
<x-forms.checkbox
|
||||||
|
helper="If you are using Cloudflare Tunnels, enable this. It will proxy all ssh requests to your server through Cloudflare.<br><span class='text-warning'>Coolify does not install/setup Cloudflare (cloudflared) on your server.</span>"
|
||||||
|
id="isCloudflareTunnel" label="Cloudflare Tunnel" />
|
||||||
|
</div>
|
||||||
<x-forms.button type="submit">Check Connection</x-forms.button>
|
<x-forms.button type="submit">Check Connection</x-forms.button>
|
||||||
</form>
|
</form>
|
||||||
</x-slot:actions>
|
</x-slot:actions>
|
||||||
@@ -226,7 +230,7 @@
|
|||||||
</x-slot:question>
|
</x-slot:question>
|
||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
<x-forms.button class="justify-center box" wire:click="installDocker">
|
<x-forms.button class="justify-center box" wire:click="installDocker">
|
||||||
Let's do it!</x-forms.button>
|
Let's do it!</x-forms.button>
|
||||||
@if ($dockerInstallationStarted)
|
@if ($dockerInstallationStarted)
|
||||||
<x-forms.button class="justify-center box" wire:click="dockerInstalledOrSkipped">
|
<x-forms.button class="justify-center box" wire:click="dockerInstalledOrSkipped">
|
||||||
Validate Server & Continue</x-forms.button>
|
Validate Server & Continue</x-forms.button>
|
||||||
@@ -234,7 +238,10 @@
|
|||||||
</x-slot:actions>
|
</x-slot:actions>
|
||||||
<x-slot:explanation>
|
<x-slot:explanation>
|
||||||
<p>This will install the latest Docker Engine on your server, configure a few things to be able
|
<p>This will install the latest Docker Engine on your server, configure a few things to be able
|
||||||
to run optimal.<br><br>Minimum Docker Engine version is: 22<br><br>To manually install Docker Engine, check <a target="_blank" class="underline text-warning" href="https://coolify.io/docs/servers#install-docker-engine-manually">this documentation</a>.</p>
|
to run optimal.<br><br>Minimum Docker Engine version is: 22<br><br>To manually install Docker
|
||||||
|
Engine, check <a target="_blank" class="underline text-warning"
|
||||||
|
href="https://coolify.io/docs/servers#install-docker-engine-manually">this
|
||||||
|
documentation</a>.</p>
|
||||||
</x-slot:explanation>
|
</x-slot:explanation>
|
||||||
</x-boarding-step>
|
</x-boarding-step>
|
||||||
|
|
||||||
|
|||||||
@@ -69,21 +69,39 @@
|
|||||||
@endif
|
@endif
|
||||||
@if ($application->build_pack !== 'dockerimage' && $application->build_pack !== 'dockercompose')
|
@if ($application->build_pack !== 'dockerimage' && $application->build_pack !== 'dockercompose')
|
||||||
<h3>Docker Registry</h3>
|
<h3>Docker Registry</h3>
|
||||||
<div>Push the built image to a docker registry. More info <a class="underline"
|
@if ($application->destination->server->isSwarm())
|
||||||
href="https://coolify.io/docs/docker-registries" target="_blank">here</a>.</div>
|
<div>Docker Swarm requires the image to be available in a registry. More info <a class="underline"
|
||||||
|
href="https://coolify.io/docs/docker-registries" target="_blank">here</a>.</div>
|
||||||
|
@else
|
||||||
|
<div>Push the built image to a docker registry. More info <a class="underline"
|
||||||
|
href="https://coolify.io/docs/docker-registries" target="_blank">here</a>.</div>
|
||||||
|
@endif
|
||||||
<div class="flex flex-col gap-2 xl:flex-row">
|
<div class="flex flex-col gap-2 xl:flex-row">
|
||||||
@if ($application->build_pack === 'dockerimage')
|
@if ($application->build_pack === 'dockerimage')
|
||||||
<x-forms.input id="application.docker_registry_image_name" label="Docker Image" />
|
@if ($application->destination->server->isSwarm())
|
||||||
<x-forms.input id="application.docker_registry_image_tag" label="Docker Image Tag" />
|
<x-forms.input required id="application.docker_registry_image_name" label="Docker Image" />
|
||||||
|
<x-forms.input id="application.docker_registry_image_tag" label="Docker Image Tag" />
|
||||||
|
@else
|
||||||
|
<x-forms.input id="application.docker_registry_image_name" label="Docker Image" />
|
||||||
|
<x-forms.input id="application.docker_registry_image_tag" label="Docker Image Tag" />
|
||||||
|
@endif
|
||||||
@else
|
@else
|
||||||
<x-forms.input id="application.docker_registry_image_name"
|
@if ($application->destination->server->isSwarm())
|
||||||
helper="Empty means it won't push the image to a docker registry."
|
<x-forms.input id="application.docker_registry_image_name" required label="Docker Image" />
|
||||||
placeholder="Empty means it won't push the image to a docker registry."
|
<x-forms.input id="application.docker_registry_image_tag"
|
||||||
label="Docker Image" />
|
helper="If set, it will tag the built image with this tag too. <br><br>Example: If you set it to 'latest', it will push the image with the commit sha tag + with the latest tag."
|
||||||
<x-forms.input id="application.docker_registry_image_tag"
|
label="Docker Image Tag" />
|
||||||
placeholder="Empty means only push commit sha tag."
|
@else
|
||||||
helper="If set, it will tag the built image with this tag too. <br><br>Example: If you set it to 'latest', it will push the image with the commit sha tag + with the latest tag."
|
<x-forms.input id="application.docker_registry_image_name"
|
||||||
label="Docker Image Tag" />
|
helper="Empty means it won't push the image to a docker registry."
|
||||||
|
placeholder="Empty means it won't push the image to a docker registry."
|
||||||
|
label="Docker Image" />
|
||||||
|
<x-forms.input id="application.docker_registry_image_tag"
|
||||||
|
placeholder="Empty means only push commit sha tag."
|
||||||
|
helper="If set, it will tag the built image with this tag too. <br><br>Example: If you set it to 'latest', it will push the image with the commit sha tag + with the latest tag."
|
||||||
|
label="Docker Image Tag" />
|
||||||
|
@endif
|
||||||
|
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -140,8 +158,8 @@
|
|||||||
@endif
|
@endif
|
||||||
@if ($application->build_pack === 'dockercompose')
|
@if ($application->build_pack === 'dockercompose')
|
||||||
<x-forms.button wire:click="loadComposeFile">Reload Compose File</x-forms.button>
|
<x-forms.button wire:click="loadComposeFile">Reload Compose File</x-forms.button>
|
||||||
<x-forms.textarea rows="10" readonly id="application.docker_compose" label="Docker Compose Content"
|
<x-forms.textarea rows="10" readonly id="application.docker_compose"
|
||||||
helper="You need to modify the docker compose file." />
|
label="Docker Compose Content" helper="You need to modify the docker compose file." />
|
||||||
{{-- <x-forms.textarea rows="10" readonly id="application.docker_compose_pr"
|
{{-- <x-forms.textarea rows="10" readonly id="application.docker_compose_pr"
|
||||||
label="Docker PR Compose Content" helper="You need to modify the docker compose file." /> --}}
|
label="Docker PR Compose Content" helper="You need to modify the docker compose file." /> --}}
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
@if ($loop->first)
|
@if ($loop->first)
|
||||||
<h2 class="pb-4">Logs</h2>
|
<h2 class="pb-4">Logs</h2>
|
||||||
@endif
|
@endif
|
||||||
<livewire:project.shared.get-logs :server="$server" :container="$container" />
|
<livewire:project.shared.get-logs :server="$server" :resource="$resource" :container="$container" />
|
||||||
@empty
|
@empty
|
||||||
<div>No containers are not running.</div>
|
<div>No containers are not running.</div>
|
||||||
@endforelse
|
@endforelse
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
<h1>Logs</h1>
|
<h1>Logs</h1>
|
||||||
<livewire:project.database.heading :database="$resource" />
|
<livewire:project.database.heading :database="$resource" />
|
||||||
<div class="pt-4">
|
<div class="pt-4">
|
||||||
<livewire:project.shared.get-logs :server="$server" :container="$container" />
|
<livewire:project.shared.get-logs :resource="$resource" :server="$server" :container="$container" />
|
||||||
</div>
|
</div>
|
||||||
@elseif ($type === 'service')
|
@elseif ($type === 'service')
|
||||||
<livewire:project.service.navbar :service="$resource" :parameters="$parameters" :query="$query" />
|
<livewire:project.service.navbar :service="$resource" :parameters="$parameters" :query="$query" />
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 pl-8">
|
<div class="flex-1 pl-8">
|
||||||
<livewire:project.shared.get-logs :server="$server" :container="$container" />
|
<livewire:project.shared.get-logs :server="$server" :resource="$resource" :servicesubtype="$serviceSubType" :container="$container" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
@@ -9,33 +9,36 @@
|
|||||||
helper="See details in our <a target='_blank' class='text-white underline' href='https://coolify.io/docs/api-authentication'>documentation</a>."
|
helper="See details in our <a target='_blank' class='text-white underline' href='https://coolify.io/docs/api-authentication'>documentation</a>."
|
||||||
label="Deploy Webhook (auth required)" id="deploywebhook"></x-forms.input>
|
label="Deploy Webhook (auth required)" id="deploywebhook"></x-forms.input>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
@if ($resource->type() !== 'service')
|
||||||
<h3>Manual Git Webhooks</h3>
|
<div>
|
||||||
@if ($githubManualWebhook && $gitlabManualWebhook)
|
<h3>Manual Git Webhooks</h3>
|
||||||
<form wire:submit.prevent='saveSecret' class="flex flex-col gap-2">
|
@if ($githubManualWebhook && $gitlabManualWebhook)
|
||||||
<div class="flex items-end gap-2">
|
<form wire:submit.prevent='saveSecret' class="flex flex-col gap-2">
|
||||||
<x-forms.input helper="Content Type in GitHub configuration could be json or form-urlencoded."
|
<div class="flex items-end gap-2">
|
||||||
readonly label="GitHub" id="githubManualWebhook"></x-forms.input>
|
<x-forms.input helper="Content Type in GitHub configuration could be json or form-urlencoded."
|
||||||
<x-forms.input type="password"
|
readonly label="GitHub" id="githubManualWebhook"></x-forms.input>
|
||||||
helper="Need to set a secret to be able to use this webhook. It should match with the secret in GitHub."
|
<x-forms.input type="password"
|
||||||
label="GitHub Webhook Secret" id="resource.manual_webhook_secret_github"></x-forms.input>
|
helper="Need to set a secret to be able to use this webhook. It should match with the secret in GitHub."
|
||||||
|
label="GitHub Webhook Secret" id="resource.manual_webhook_secret_github"></x-forms.input>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<a target="_blank" class="flex hover:no-underline" href="{{ $resource?->gitWebhook }}">
|
||||||
|
<x-forms.button>Webhook Configuration on GitHub
|
||||||
|
<x-external-link />
|
||||||
|
</x-forms.button>
|
||||||
|
</a>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<x-forms.input readonly label="GitLab" id="gitlabManualWebhook"></x-forms.input>
|
||||||
|
<x-forms.input type="password"
|
||||||
|
helper="Need to set a secret to be able to use this webhook. It should match with the secret in GitLab."
|
||||||
|
label="GitLab Webhook Secret" id="resource.manual_webhook_secret_gitlab"></x-forms.input>
|
||||||
|
</div>
|
||||||
|
<x-forms.button type="submit">Save</x-forms.button>
|
||||||
|
</form>
|
||||||
|
@else
|
||||||
|
You are using an official Git App. You do not need manual webhooks.
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
</div>
|
|
||||||
<a target="_blank" class="flex hover:no-underline" href="{{ $resource?->gitWebhook }}">
|
|
||||||
<x-forms.button>Webhook Configuration on GitHub
|
|
||||||
<x-external-link />
|
|
||||||
</x-forms.button>
|
|
||||||
</a>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<x-forms.input readonly label="GitLab" id="gitlabManualWebhook"></x-forms.input>
|
|
||||||
<x-forms.input type="password"
|
|
||||||
helper="Need to set a secret to be able to use this webhook. It should match with the secret in GitLab."
|
|
||||||
label="GitLab Webhook Secret" id="resource.manual_webhook_secret_gitlab"></x-forms.input>
|
|
||||||
</div>
|
|
||||||
<x-forms.button type="submit">Save</x-forms.button>
|
|
||||||
</form>
|
|
||||||
@else
|
|
||||||
You are using an official Git App. You do not need manual webhooks.
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -25,10 +25,17 @@
|
|||||||
@if ($loop->first)
|
@if ($loop->first)
|
||||||
<h3 class="pt-4">Defined resources</h3>
|
<h3 class="pt-4">Defined resources</h3>
|
||||||
@endif
|
@endif
|
||||||
<a class="flex gap-2 p-1 hover:bg-coolgray-100 hover:no-underline" href="{{ $resource->link() }}">
|
@if ($resource->link())
|
||||||
<div class="w-64">{{ str($resource->type())->headline() }}</div>
|
<a class="flex gap-2 p-1 hover:bg-coolgray-100 hover:no-underline" href="{{ $resource->link() }}">
|
||||||
<div>{{ $resource->name }}</div>
|
<div class="w-64">{{ str($resource->type())->headline() }}</div>
|
||||||
</a>
|
<div>{{ $resource->name }}</div>
|
||||||
|
</a>
|
||||||
|
@else
|
||||||
|
<div class="flex gap-2 p-1 hover:bg-coolgray-100 hover:no-underline">
|
||||||
|
<div class="w-64">{{ str($resource->type())->headline() }}</div>
|
||||||
|
<div>{{ $resource->name }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
@empty
|
@empty
|
||||||
@endforelse
|
@endforelse
|
||||||
</div>
|
</div>
|
||||||
@@ -38,10 +45,17 @@
|
|||||||
@if ($loop->first)
|
@if ($loop->first)
|
||||||
<h3 class="pt-4">Defined resources</h3>
|
<h3 class="pt-4">Defined resources</h3>
|
||||||
@endif
|
@endif
|
||||||
<a class="flex gap-2 p-1 hover:bg-coolgray-100 hover:no-underline" href="{{ $resource->link() }}">
|
@if ($resource->link())
|
||||||
<div class="w-64">{{ str($resource->type())->headline() }}</div>
|
<a class="flex gap-2 p-1 hover:bg-coolgray-100 hover:no-underline" href="{{ $resource->link() }}">
|
||||||
<div>{{ $resource->name }}</div>
|
<div class="w-64">{{ str($resource->type())->headline() }}</div>
|
||||||
</a>
|
<div>{{ $resource->name }}</div>
|
||||||
|
</a>
|
||||||
|
@else
|
||||||
|
<div class="flex gap-2 p-1 hover:bg-coolgray-100 hover:no-underline">
|
||||||
|
<div class="w-64">{{ str($resource->type())->headline() }}</div>
|
||||||
|
<div>{{ $resource->name }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
@empty
|
@empty
|
||||||
@endforelse
|
@endforelse
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -38,8 +38,7 @@
|
|||||||
<x-forms.input id="server.description" label="Description" />
|
<x-forms.input id="server.description" label="Description" />
|
||||||
<x-forms.input placeholder="https://example.com" id="wildcard_domain" label="Wildcard Domain"
|
<x-forms.input placeholder="https://example.com" id="wildcard_domain" label="Wildcard Domain"
|
||||||
helper="Wildcard domain for your applications. If you set this, you will get a random generated domain for your new applications.<br><span class='font-bold text-white'>Example:</span><br>In case you set:<span class='text-helper'>https://example.com</span> your applications will get:<br> <span class='text-helper'>https://randomId.example.com</span>" />
|
helper="Wildcard domain for your applications. If you set this, you will get a random generated domain for your new applications.<br><span class='font-bold text-white'>Example:</span><br>In case you set:<span class='text-helper'>https://example.com</span> your applications will get:<br> <span class='text-helper'>https://randomId.example.com</span>" />
|
||||||
{{-- <x-forms.checkbox disabled type="checkbox" id="server.settings.is_part_of_swarm"
|
|
||||||
label="Is it part of a Swarm cluster?" /> --}}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col w-full gap-2 lg:flex-row">
|
<div class="flex flex-col w-full gap-2 lg:flex-row">
|
||||||
<x-forms.input id="server.ip" label="IP Address/Domain"
|
<x-forms.input id="server.ip" label="IP Address/Domain"
|
||||||
@@ -49,13 +48,17 @@
|
|||||||
<x-forms.input type="number" id="server.port" label="Port" required />
|
<x-forms.input type="number" id="server.port" label="Port" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (!$server->isLocalhost())
|
<div class="w-64">
|
||||||
<div class="w-64">
|
@if (!$server->isLocalhost())
|
||||||
<x-forms.checkbox instantSave
|
<x-forms.checkbox instantSave
|
||||||
helper="If you are using Cloudflare Tunnels, enable this. It will proxy all ssh requests to your server through Cloudflare.<span class='text-warning'>Coolify does not install/setup Cloudflare (cloudflared) on your server.</span>"
|
helper="If you are using Cloudflare Tunnels, enable this. It will proxy all ssh requests to your server through Cloudflare.<span class='text-warning'>Coolify does not install/setup Cloudflare (cloudflared) on your server.</span>"
|
||||||
id="server.settings.is_cloudflare_tunnel" label="Cloudflare Tunnel" />
|
id="server.settings.is_cloudflare_tunnel" label="Cloudflare Tunnel" />
|
||||||
</div>
|
@endif
|
||||||
@endif
|
{{-- <x-forms.checkbox instantSave type="checkbox" id="server.settings.is_swarm_manager"
|
||||||
|
label="Is it a Swarm Manager?" /> --}}
|
||||||
|
{{-- <x-forms.checkbox instantSave type="checkbox" id="server.settings.is_swarm_worker"
|
||||||
|
label="Is it a Swarm Worker?" /> --}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if ($server->isFunctional())
|
@if ($server->isFunctional())
|
||||||
|
|||||||
@@ -62,6 +62,25 @@
|
|||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
</div>
|
</div>
|
||||||
</form> --}}
|
</form> --}}
|
||||||
|
<h3>Custom FluentBit configuration</h3>
|
||||||
|
<div class="w-32">
|
||||||
|
<x-forms.checkbox instantSave='instantSave("custom")' id="server.settings.is_logdrain_custom_enabled"
|
||||||
|
label="Enabled" />
|
||||||
|
</div>
|
||||||
|
<form wire:submit.prevent='submit("custom")' class="flex flex-col">
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<x-forms.textarea rows="6" required id="server.settings.logdrain_custom_config"
|
||||||
|
label="Custom FluentBit Configuration" />
|
||||||
|
<x-forms.textarea id="server.settings.logdrain_custom_config_parser"
|
||||||
|
label="Custom Parser Configuration" />
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end gap-4 pt-6">
|
||||||
|
<x-forms.button type="submit">
|
||||||
|
Save
|
||||||
|
</x-forms.button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<x-limit-reached name="servers" />
|
<x-limit-reached name="servers" />
|
||||||
@else
|
@else
|
||||||
<h1>Create a new Server</h1>
|
<h1>Create a new Server</h1>
|
||||||
<div class="subtitle ">Servers are the main blocks of your infrastructure.</div>
|
<div class="subtitle">Servers are the main blocks of your infrastructure.</div>
|
||||||
<form class="flex flex-col gap-2" wire:submit.prevent='submit'>
|
<form class="flex flex-col gap-2" wire:submit.prevent='submit'>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<x-forms.input id="name" label="Name" required />
|
<x-forms.input id="name" label="Name" required />
|
||||||
@@ -25,6 +25,10 @@
|
|||||||
@endif
|
@endif
|
||||||
@endforeach
|
@endforeach
|
||||||
</x-forms.select>
|
</x-forms.select>
|
||||||
|
{{-- <div class="w-64">
|
||||||
|
<x-forms.checkbox type="checkbox" id="is_swarm_manager"
|
||||||
|
label="Is it a Swarm Manager?" />
|
||||||
|
</div> --}}
|
||||||
<x-forms.button type="submit">
|
<x-forms.button type="submit">
|
||||||
Save New Server
|
Save New Server
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div>
|
<div>
|
||||||
@if ($server->isFunctional())
|
@if ($server->isFunctional())
|
||||||
<div class="flex gap-2" @if ($polling) wire:poll.2000ms='checkProxy' @endif>
|
<div class="flex gap-2">
|
||||||
@if (data_get($server, 'proxy.status') === 'running')
|
@if (data_get($server, 'proxy.status') === 'running')
|
||||||
<x-status.running status="Proxy Running" />
|
<x-status.running status="Proxy Running" />
|
||||||
@elseif (data_get($server, 'proxy.status') === 'restarting')
|
@elseif (data_get($server, 'proxy.status') === 'restarting')
|
||||||
|
|||||||
@@ -59,15 +59,21 @@ sles | opensuse-leap | opensuse-tumbleweed)
|
|||||||
esac
|
esac
|
||||||
|
|
||||||
if ! [ -x "$(command -v docker)" ]; then
|
if ! [ -x "$(command -v docker)" ]; then
|
||||||
echo "Docker is not installed. Installing Docker..."
|
echo "Docker is not installed. Installing Docker."
|
||||||
curl https://releases.rancher.com/install-docker/${DOCKER_VERSION}.sh | sh
|
curl https://releases.rancher.com/install-docker/${DOCKER_VERSION}.sh | sh
|
||||||
if [ -x "$(command -v docker)" ]; then
|
if [ -x "$(command -v docker)" ]; then
|
||||||
echo "Docker installed successfully."
|
echo "Docker installed successfully."
|
||||||
else
|
else
|
||||||
echo "Docker installation failed."
|
echo "Docker installation failed with Rancher script. Trying with official script."
|
||||||
echo "Maybe your OS is not supported."
|
curl https://get.docker.com | sh -s -- --version ${DOCKER_VERSION}
|
||||||
echo "Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
|
if [ -x "$(command -v docker)" ]; then
|
||||||
exit 1
|
echo "Docker installed successfully."
|
||||||
|
else
|
||||||
|
echo "Docker installation failed with official script."
|
||||||
|
echo "Maybe your OS is not supported."
|
||||||
|
echo "Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
echo -e "-------------"
|
echo -e "-------------"
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ curl -fsSL $CDN/.env.production -o /data/coolify/source/.env.production
|
|||||||
sort -u -t '=' -k 1,1 /data/coolify/source/.env /data/coolify/source/.env.production | sed '/^$/d' > /data/coolify/source/.env.temp && mv /data/coolify/source/.env.temp /data/coolify/source/.env
|
sort -u -t '=' -k 1,1 /data/coolify/source/.env /data/coolify/source/.env.production | sed '/^$/d' > /data/coolify/source/.env.temp && mv /data/coolify/source/.env.temp /data/coolify/source/.env
|
||||||
|
|
||||||
# Make sure coolify network exists
|
# Make sure coolify network exists
|
||||||
docker network create coolify 2>/dev/null
|
docker network create --attachable coolify 2>/dev/null
|
||||||
|
# docker network create --attachable --driver=overlay coolify-overlay 2>/dev/null
|
||||||
|
|
||||||
docker run --pull always -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper bash -c "LATEST_IMAGE=${1:-} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml up -d --pull always --remove-orphans --force-recreate"
|
docker run --pull always -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper bash -c "LATEST_IMAGE=${1:-} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml up -d --pull always --remove-orphans --force-recreate"
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ services:
|
|||||||
- REDIS_PORT=6379
|
- REDIS_PORT=6379
|
||||||
- WEBSOCKETS_ENABLED=true
|
- WEBSOCKETS_ENABLED=true
|
||||||
postgresql:
|
postgresql:
|
||||||
image: postgres:15-alpine
|
image: postgres:16-alpine
|
||||||
volumes:
|
volumes:
|
||||||
- directus-postgresql-data:/var/lib/postgresql/data
|
- directus-postgresql-data:/var/lib/postgresql/data
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ services:
|
|||||||
retries: 15
|
retries: 15
|
||||||
|
|
||||||
postgresql:
|
postgresql:
|
||||||
image: postgres:15-alpine
|
image: postgres:16-alpine
|
||||||
volumes:
|
volumes:
|
||||||
- gitea-postgresql-data:/var/lib/postgresql/data
|
- gitea-postgresql-data:/var/lib/postgresql/data
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- postgresql
|
- postgresql
|
||||||
postgresql:
|
postgresql:
|
||||||
image: postgres:15-alpine
|
image: postgres:16-alpine
|
||||||
volumes:
|
volumes:
|
||||||
- postgresql-data:/var/lib/postgresql/data
|
- postgresql-data:/var/lib/postgresql/data
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:15-alpine
|
image: postgres:16-alpine
|
||||||
volumes:
|
volumes:
|
||||||
- postgresql-data:/var/lib/postgresql/data
|
- postgresql-data:/var/lib/postgresql/data
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
19
templates/compose/meilisearch.yaml
Normal file
19
templates/compose/meilisearch.yaml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# documentation: https://www.meilisearch.com/docs/learn/configuration/instance_options
|
||||||
|
# slogan: MeiliSearch is a powerful, fast, open-source, easy to use and deploy search engine.
|
||||||
|
# tags: search,engine,fulltext,full,text,meilisearch
|
||||||
|
|
||||||
|
services:
|
||||||
|
meilisearch:
|
||||||
|
image: getmeili/meilisearch:latest
|
||||||
|
environment:
|
||||||
|
- SERVICE_FQDN_MEILISEARCH
|
||||||
|
- MEILI_NO_ANALYTICS=${MEILI_NO_ANALYTICS:-true}
|
||||||
|
- MEILI_ENV=${MEILI_ENV:-production}
|
||||||
|
- MEILI_MASTER_KEY=${SERVICE_PASSWORD_MEILISEARCH}
|
||||||
|
volumes:
|
||||||
|
- meilisearch-data:/meili_data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:7700/health"]
|
||||||
|
interval: 2s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 15
|
||||||
@@ -24,7 +24,7 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- postgresql
|
- postgresql
|
||||||
postgresql:
|
postgresql:
|
||||||
image: postgres:15-alpine
|
image: postgres:16-alpine
|
||||||
volumes:
|
volumes:
|
||||||
- postgresql-data:/var/lib/postgresql/data
|
- postgresql-data:/var/lib/postgresql/data
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ services:
|
|||||||
postgresql:
|
postgresql:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
postgresql:
|
postgresql:
|
||||||
image: postgres:15-alpine
|
image: postgres:16-alpine
|
||||||
volumes:
|
volumes:
|
||||||
- postgresql-data:/var/lib/postgresql/data
|
- postgresql-data:/var/lib/postgresql/data
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ services:
|
|||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["NONE"]
|
test: ["NONE"]
|
||||||
postgresql:
|
postgresql:
|
||||||
image: postgres:15-alpine
|
image: postgres:16-alpine
|
||||||
volumes:
|
volumes:
|
||||||
- postgresql-data:/var/lib/postgresql/data
|
- postgresql-data:/var/lib/postgresql/data
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ services:
|
|||||||
postgresql:
|
postgresql:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
postgresql:
|
postgresql:
|
||||||
image: postgres:15-alpine
|
image: postgres:16-alpine
|
||||||
volumes:
|
volumes:
|
||||||
- postgresql-data:/var/lib/postgresql/data
|
- postgresql-data:/var/lib/postgresql/data
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
"directus-with-postgresql": {
|
"directus-with-postgresql": {
|
||||||
"documentation": "https:\/\/docs.directus.io\/self-hosted\/quickstart.html",
|
"documentation": "https:\/\/docs.directus.io\/self-hosted\/quickstart.html",
|
||||||
"slogan": "Directus is an open-source tool that wraps custom SQL databases with a dynamic API, and provides an intuitive admin app for managing its content.",
|
"slogan": "Directus is an open-source tool that wraps custom SQL databases with a dynamic API, and provides an intuitive admin app for managing its content.",
|
||||||
"compose": "c2VydmljZXM6CiAgZGlyZWN0dXM6CiAgICBpbWFnZTogJ2RpcmVjdHVzL2RpcmVjdHVzOjEwLjcnCiAgICB2b2x1bWVzOgogICAgICAtICdkaXJlY3R1cy11cGxvYWRzOi9kaXJlY3R1cy91cGxvYWRzJwogICAgICAtICdkaXJlY3R1cy1leHRlbnNpb25zOi9kaXJlY3R1cy9leHRlbnNpb25zJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX0RJUkVDVFVTCiAgICAgIC0gS0VZPSRTRVJWSUNFX0JBU0U2NF82NF9LRVkKICAgICAgLSBTRUNSRVQ9JFNFUlZJQ0VfQkFTRTY0XzY0X1NFQ1JFVAogICAgICAtICdBRE1JTl9FTUFJTD0ke0FETUlOX0VNQUlMOi1hZG1pbkBleGFtcGxlLmNvbX0nCiAgICAgIC0gQURNSU5fUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfQURNSU4KICAgICAgLSBEQl9DTElFTlQ9cG9zdGdyZXMKICAgICAgLSBEQl9IT1NUPXBvc3RncmVzcWwKICAgICAgLSBEQl9QT1JUPTU0MzIKICAgICAgLSAnREJfREFUQUJBU0U9JHtQT1NUR1JFU1FMX0RBVEFCQVNFOi1kaXJlY3R1c30nCiAgICAgIC0gREJfVVNFUj0kU0VSVklDRV9VU0VSX1BPU1RHUkVTUUwKICAgICAgLSBEQl9QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMCiAgICAgIC0gUkVESVNfSE9TVD1yZWRpcwogICAgICAtIFJFRElTX1BPUlQ9NjM3OQogICAgICAtIFdFQlNPQ0tFVFNfRU5BQkxFRD10cnVlCiAgcG9zdGdyZXNxbDoKICAgIGltYWdlOiAncG9zdGdyZXM6MTUtYWxwaW5lJwogICAgdm9sdW1lczoKICAgICAgLSAnZGlyZWN0dXMtcG9zdGdyZXNxbC1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTUUx9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTH0nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNRTF9EQVRBQkFTRTotZGlyZWN0dXN9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIHJlZGlzOgogICAgaW1hZ2U6ICdyZWRpczo3LWFscGluZScKICAgIGNvbW1hbmQ6ICdyZWRpcy1zZXJ2ZXIgLS1hcHBlbmRvbmx5IHllcycKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2RpcmVjdHVzLXJlZGlzLWRhdGE6L2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gcmVkaXMtY2xpCiAgICAgICAgLSBwaW5nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK",
|
"compose": "c2VydmljZXM6CiAgZGlyZWN0dXM6CiAgICBpbWFnZTogJ2RpcmVjdHVzL2RpcmVjdHVzOjEwLjcnCiAgICB2b2x1bWVzOgogICAgICAtICdkaXJlY3R1cy11cGxvYWRzOi9kaXJlY3R1cy91cGxvYWRzJwogICAgICAtICdkaXJlY3R1cy1leHRlbnNpb25zOi9kaXJlY3R1cy9leHRlbnNpb25zJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX0RJUkVDVFVTCiAgICAgIC0gS0VZPSRTRVJWSUNFX0JBU0U2NF82NF9LRVkKICAgICAgLSBTRUNSRVQ9JFNFUlZJQ0VfQkFTRTY0XzY0X1NFQ1JFVAogICAgICAtICdBRE1JTl9FTUFJTD0ke0FETUlOX0VNQUlMOi1hZG1pbkBleGFtcGxlLmNvbX0nCiAgICAgIC0gQURNSU5fUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfQURNSU4KICAgICAgLSBEQl9DTElFTlQ9cG9zdGdyZXMKICAgICAgLSBEQl9IT1NUPXBvc3RncmVzcWwKICAgICAgLSBEQl9QT1JUPTU0MzIKICAgICAgLSAnREJfREFUQUJBU0U9JHtQT1NUR1JFU1FMX0RBVEFCQVNFOi1kaXJlY3R1c30nCiAgICAgIC0gREJfVVNFUj0kU0VSVklDRV9VU0VSX1BPU1RHUkVTUUwKICAgICAgLSBEQl9QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMCiAgICAgIC0gUkVESVNfSE9TVD1yZWRpcwogICAgICAtIFJFRElTX1BPUlQ9NjM3OQogICAgICAtIFdFQlNPQ0tFVFNfRU5BQkxFRD10cnVlCiAgcG9zdGdyZXNxbDoKICAgIGltYWdlOiAncG9zdGdyZXM6MTYtYWxwaW5lJwogICAgdm9sdW1lczoKICAgICAgLSAnZGlyZWN0dXMtcG9zdGdyZXNxbC1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTUUx9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTH0nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNRTF9EQVRBQkFTRTotZGlyZWN0dXN9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIHJlZGlzOgogICAgaW1hZ2U6ICdyZWRpczo3LWFscGluZScKICAgIGNvbW1hbmQ6ICdyZWRpcy1zZXJ2ZXIgLS1hcHBlbmRvbmx5IHllcycKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2RpcmVjdHVzLXJlZGlzLWRhdGE6L2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gcmVkaXMtY2xpCiAgICAgICAgLSBwaW5nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK",
|
||||||
"tags": [
|
"tags": [
|
||||||
"directus",
|
"directus",
|
||||||
"cms",
|
"cms",
|
||||||
@@ -208,7 +208,7 @@
|
|||||||
"gitea-with-postgresql": {
|
"gitea-with-postgresql": {
|
||||||
"documentation": "https:\/\/docs.gitea.com",
|
"documentation": "https:\/\/docs.gitea.com",
|
||||||
"slogan": "Gitea (with PostgreSQL)vis a self-hosted, lightweight Git service, offering version control, collaboration, and code hosting.",
|
"slogan": "Gitea (with PostgreSQL)vis a self-hosted, lightweight Git service, offering version control, collaboration, and code hosting.",
|
||||||
"compose": "c2VydmljZXM6CiAgZ2l0ZWE6CiAgICBpbWFnZTogJ2dpdGVhL2dpdGVhOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9HSVRFQV8zMDAwCiAgICAgIC0gVVNFUl9VSUQ9MTAwMAogICAgICAtIFVTRVJfR0lEPTEwMDAKICAgICAgLSBHSVRFQV9fZGF0YWJhc2VfX0RCX1RZUEU9cG9zdGdyZXMKICAgICAgLSBHSVRFQV9fZGF0YWJhc2VfX0hPU1Q9cG9zdGdyZXNxbAogICAgICAtICdHSVRFQV9fZGF0YWJhc2VfX05BTUU9JHtQT1NUR1JFU1FMX0RBVEFCQVNFLWdpdGVhfScKICAgICAgLSBHSVRFQV9fZGF0YWJhc2VfX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMCiAgICAgIC0gR0lURUFfX2RhdGFiYXNlX19QQVNTV0Q9JFNFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTAogICAgdm9sdW1lczoKICAgICAgLSAnZ2l0ZWEtZGF0YTovdmFyL2xpYi9naXRlYScKICAgICAgLSAnZ2l0ZWEtdGltZXpvbmU6L2V0Yy90aW1lem9uZTpybycKICAgICAgLSAnZ2l0ZWEtbG9jYWx0aW1lOi9ldGMvbG9jYWx0aW1lOnJvJwogICAgcG9ydHM6CiAgICAgIC0gJzIyMjIyOjIyJwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXNxbDoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjMwMDAnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUKICBwb3N0Z3Jlc3FsOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNS1hbHBpbmUnCiAgICB2b2x1bWVzOgogICAgICAtICdnaXRlYS1wb3N0Z3Jlc3FsLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVNRTH0nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMfScKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU1FMX0RBVEFCQVNFfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCAkJHtQT1NUR1JFU19EQn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK",
|
"compose": "c2VydmljZXM6CiAgZ2l0ZWE6CiAgICBpbWFnZTogJ2dpdGVhL2dpdGVhOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9HSVRFQV8zMDAwCiAgICAgIC0gVVNFUl9VSUQ9MTAwMAogICAgICAtIFVTRVJfR0lEPTEwMDAKICAgICAgLSBHSVRFQV9fZGF0YWJhc2VfX0RCX1RZUEU9cG9zdGdyZXMKICAgICAgLSBHSVRFQV9fZGF0YWJhc2VfX0hPU1Q9cG9zdGdyZXNxbAogICAgICAtICdHSVRFQV9fZGF0YWJhc2VfX05BTUU9JHtQT1NUR1JFU1FMX0RBVEFCQVNFLWdpdGVhfScKICAgICAgLSBHSVRFQV9fZGF0YWJhc2VfX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMCiAgICAgIC0gR0lURUFfX2RhdGFiYXNlX19QQVNTV0Q9JFNFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTAogICAgdm9sdW1lczoKICAgICAgLSAnZ2l0ZWEtZGF0YTovdmFyL2xpYi9naXRlYScKICAgICAgLSAnZ2l0ZWEtdGltZXpvbmU6L2V0Yy90aW1lem9uZTpybycKICAgICAgLSAnZ2l0ZWEtbG9jYWx0aW1lOi9ldGMvbG9jYWx0aW1lOnJvJwogICAgcG9ydHM6CiAgICAgIC0gJzIyMjIyOjIyJwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXNxbDoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjMwMDAnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUKICBwb3N0Z3Jlc3FsOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNi1hbHBpbmUnCiAgICB2b2x1bWVzOgogICAgICAtICdnaXRlYS1wb3N0Z3Jlc3FsLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVNRTH0nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMfScKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU1FMX0RBVEFCQVNFfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCAkJHtQT1NUR1JFU19EQn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK",
|
||||||
"tags": [
|
"tags": [
|
||||||
"version control",
|
"version control",
|
||||||
"collaboration",
|
"collaboration",
|
||||||
@@ -233,7 +233,7 @@
|
|||||||
"grafana-with-postgresql": {
|
"grafana-with-postgresql": {
|
||||||
"documentation": "https:\/\/grafana.com\/docs\/grafana\/latest\/installation\/docker\/",
|
"documentation": "https:\/\/grafana.com\/docs\/grafana\/latest\/installation\/docker\/",
|
||||||
"slogan": "Grafana is the open source analytics & monitoring solution for every database.",
|
"slogan": "Grafana is the open source analytics & monitoring solution for every database.",
|
||||||
"compose": "c2VydmljZXM6CiAgZ3JhZmFuYToKICAgIGltYWdlOiBncmFmYW5hL2dyYWZhbmEtb3NzCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fR1JBRkFOQQogICAgICAtICdHRl9TRVJWRVJfUk9PVF9VUkw9JHtTRVJWSUNFX0ZRRE5fR1JBRkFOQX0nCiAgICAgIC0gJ0dGX1NFUlZFUl9ET01BSU49JHtTRVJWSUNFX0ZRRE5fR1JBRkFOQX0nCiAgICAgIC0gJ0dGX1NFQ1VSSVRZX0FETUlOX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9HUkFGQU5BfScKICAgICAgLSBHRl9EQVRBQkFTRV9UWVBFPXBvc3RncmVzCiAgICAgIC0gR0ZfREFUQUJBU0VfSE9TVD1wb3N0Z3Jlc3FsCiAgICAgIC0gR0ZfREFUQUJBU0VfVVNFUj0kU0VSVklDRV9VU0VSX1BPU1RHUkVTCiAgICAgIC0gR0ZfREFUQUJBU0VfUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVMKICAgICAgLSAnR0ZfREFUQUJBU0VfTkFNRT0ke1BPU1RHUkVTX0RCOi1ncmFmYW5hfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2dyYWZhbmEtZGF0YTovdmFyL2xpYi9ncmFmYW5hJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjMwMDAvYXBpL2hlYWx0aCcKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogICAgZGVwZW5kc19vbjoKICAgICAgLSBwb3N0Z3Jlc3FsCiAgcG9zdGdyZXNxbDoKICAgIGltYWdlOiAncG9zdGdyZXM6MTUtYWxwaW5lJwogICAgdm9sdW1lczoKICAgICAgLSAncG9zdGdyZXNxbC1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogICAgICAtIFBPU1RHUkVTX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LWdyYWZhbmF9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAo=",
|
"compose": "c2VydmljZXM6CiAgZ3JhZmFuYToKICAgIGltYWdlOiBncmFmYW5hL2dyYWZhbmEtb3NzCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fR1JBRkFOQQogICAgICAtICdHRl9TRVJWRVJfUk9PVF9VUkw9JHtTRVJWSUNFX0ZRRE5fR1JBRkFOQX0nCiAgICAgIC0gJ0dGX1NFUlZFUl9ET01BSU49JHtTRVJWSUNFX0ZRRE5fR1JBRkFOQX0nCiAgICAgIC0gJ0dGX1NFQ1VSSVRZX0FETUlOX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9HUkFGQU5BfScKICAgICAgLSBHRl9EQVRBQkFTRV9UWVBFPXBvc3RncmVzCiAgICAgIC0gR0ZfREFUQUJBU0VfSE9TVD1wb3N0Z3Jlc3FsCiAgICAgIC0gR0ZfREFUQUJBU0VfVVNFUj0kU0VSVklDRV9VU0VSX1BPU1RHUkVTCiAgICAgIC0gR0ZfREFUQUJBU0VfUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVMKICAgICAgLSAnR0ZfREFUQUJBU0VfTkFNRT0ke1BPU1RHUkVTX0RCOi1ncmFmYW5hfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2dyYWZhbmEtZGF0YTovdmFyL2xpYi9ncmFmYW5hJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjMwMDAvYXBpL2hlYWx0aCcKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogICAgZGVwZW5kc19vbjoKICAgICAgLSBwb3N0Z3Jlc3FsCiAgcG9zdGdyZXNxbDoKICAgIGltYWdlOiAncG9zdGdyZXM6MTYtYWxwaW5lJwogICAgdm9sdW1lczoKICAgICAgLSAncG9zdGdyZXNxbC1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogICAgICAtIFBPU1RHUkVTX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LWdyYWZhbmF9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAo=",
|
||||||
"tags": [
|
"tags": [
|
||||||
"grafana",
|
"grafana",
|
||||||
"analytics",
|
"analytics",
|
||||||
@@ -304,6 +304,19 @@
|
|||||||
"low-code"
|
"low-code"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"meilisearch": {
|
||||||
|
"documentation": "https:\/\/www.meilisearch.com\/docs\/learn\/configuration\/instance_options",
|
||||||
|
"slogan": "MeiliSearch is a powerful, fast, open-source, easy to use and deploy search engine.",
|
||||||
|
"compose": "c2VydmljZXM6CiAgbWVpbGlzZWFyY2g6CiAgICBpbWFnZTogJ2dldG1laWxpL21laWxpc2VhcmNoOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9NRUlMSVNFQVJDSAogICAgICAtICdNRUlMSV9OT19BTkFMWVRJQ1M9JHtNRUlMSV9OT19BTkFMWVRJQ1M6LXRydWV9JwogICAgICAtICdNRUlMSV9FTlY9JHtNRUlMSV9FTlY6LXByb2R1Y3Rpb259JwogICAgICAtICdNRUlMSV9NQVNURVJfS0VZPSR7U0VSVklDRV9QQVNTV09SRF9NRUlMSVNFQVJDSH0nCiAgICB2b2x1bWVzOgogICAgICAtICdtZWlsaXNlYXJjaC1kYXRhOi9tZWlsaV9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0Ojc3MDAvaGVhbHRoJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1Cg==",
|
||||||
|
"tags": [
|
||||||
|
"search",
|
||||||
|
"engine",
|
||||||
|
"fulltext",
|
||||||
|
"full",
|
||||||
|
"text",
|
||||||
|
"meilisearch"
|
||||||
|
]
|
||||||
|
},
|
||||||
"metube": {
|
"metube": {
|
||||||
"documentation": "https:\/\/github.com\/alexta69\/metube",
|
"documentation": "https:\/\/github.com\/alexta69\/metube",
|
||||||
"slogan": "A web GUI for youtube-dl with playlist support. It enables you to effortlessly download videos from YouTube and dozens of other sites.",
|
"slogan": "A web GUI for youtube-dl with playlist support. It enables you to effortlessly download videos from YouTube and dozens of other sites.",
|
||||||
@@ -346,7 +359,7 @@
|
|||||||
"n8n-with-postgresql": {
|
"n8n-with-postgresql": {
|
||||||
"documentation": "https:\/\/docs.n8n.io\/hosting\/",
|
"documentation": "https:\/\/docs.n8n.io\/hosting\/",
|
||||||
"slogan": "n8n is an extendable workflow automation tool which enables you to connect anything to everything via its open, fair-code model.",
|
"slogan": "n8n is an extendable workflow automation tool which enables you to connect anything to everything via its open, fair-code model.",
|
||||||
"compose": "c2VydmljZXM6CiAgbjhuOgogICAgaW1hZ2U6IGRvY2tlci5uOG4uaW8vbjhuaW8vbjhuCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fTjhOCiAgICAgIC0gJ044Tl9FRElUT1JfQkFTRV9VUkw9JHtTRVJWSUNFX0ZRRE5fTjhOfScKICAgICAgLSAnV0VCSE9PS19VUkw9JHtTRVJWSUNFX0ZRRE5fTjhOfScKICAgICAgLSAnTjhOX0hPU1Q9JHtTRVJWSUNFX1VSTF9OOE59JwogICAgICAtICdHRU5FUklDX1RJTUVaT05FPSJFdXJvcGUvQmVybGluIicKICAgICAgLSAnVFo9IkV1cm9wZS9CZXJsaW4iJwogICAgICAtIERCX1RZUEU9cG9zdGdyZXNkYgogICAgICAtICdEQl9QT1NUR1JFU0RCX0RBVEFCQVNFPSR7UE9TVEdSRVNfREI6LW44bn0nCiAgICAgIC0gREJfUE9TVEdSRVNEQl9IT1NUPXBvc3RncmVzcWwKICAgICAgLSBEQl9QT1NUR1JFU0RCX1BPUlQ9NTQzMgogICAgICAtIERCX1BPU1RHUkVTREJfVVNFUj0kU0VSVklDRV9VU0VSX1BPU1RHUkVTCiAgICAgIC0gREJfUE9TVEdSRVNEQl9TQ0hFTUE9cHVibGljCiAgICAgIC0gREJfUE9TVEdSRVNEQl9QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgdm9sdW1lczoKICAgICAgLSAnbjhuLWRhdGE6L2hvbWUvbm9kZS8ubjhuJwogICAgZGVwZW5kc19vbjoKICAgICAgLSBwb3N0Z3Jlc3FsCiAgcG9zdGdyZXNxbDoKICAgIGltYWdlOiAncG9zdGdyZXM6MTUtYWxwaW5lJwogICAgdm9sdW1lczoKICAgICAgLSAncG9zdGdyZXNxbC1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogICAgICAtIFBPU1RHUkVTX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LW44bn0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==",
|
"compose": "c2VydmljZXM6CiAgbjhuOgogICAgaW1hZ2U6IGRvY2tlci5uOG4uaW8vbjhuaW8vbjhuCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fTjhOCiAgICAgIC0gJ044Tl9FRElUT1JfQkFTRV9VUkw9JHtTRVJWSUNFX0ZRRE5fTjhOfScKICAgICAgLSAnV0VCSE9PS19VUkw9JHtTRVJWSUNFX0ZRRE5fTjhOfScKICAgICAgLSAnTjhOX0hPU1Q9JHtTRVJWSUNFX1VSTF9OOE59JwogICAgICAtICdHRU5FUklDX1RJTUVaT05FPSJFdXJvcGUvQmVybGluIicKICAgICAgLSAnVFo9IkV1cm9wZS9CZXJsaW4iJwogICAgICAtIERCX1RZUEU9cG9zdGdyZXNkYgogICAgICAtICdEQl9QT1NUR1JFU0RCX0RBVEFCQVNFPSR7UE9TVEdSRVNfREI6LW44bn0nCiAgICAgIC0gREJfUE9TVEdSRVNEQl9IT1NUPXBvc3RncmVzcWwKICAgICAgLSBEQl9QT1NUR1JFU0RCX1BPUlQ9NTQzMgogICAgICAtIERCX1BPU1RHUkVTREJfVVNFUj0kU0VSVklDRV9VU0VSX1BPU1RHUkVTCiAgICAgIC0gREJfUE9TVEdSRVNEQl9TQ0hFTUE9cHVibGljCiAgICAgIC0gREJfUE9TVEdSRVNEQl9QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgdm9sdW1lczoKICAgICAgLSAnbjhuLWRhdGE6L2hvbWUvbm9kZS8ubjhuJwogICAgZGVwZW5kc19vbjoKICAgICAgLSBwb3N0Z3Jlc3FsCiAgcG9zdGdyZXNxbDoKICAgIGltYWdlOiAncG9zdGdyZXM6MTYtYWxwaW5lJwogICAgdm9sdW1lczoKICAgICAgLSAncG9zdGdyZXNxbC1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogICAgICAtIFBPU1RHUkVTX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LW44bn0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==",
|
||||||
"tags": [
|
"tags": [
|
||||||
"n8n",
|
"n8n",
|
||||||
"workflow",
|
"workflow",
|
||||||
@@ -473,7 +486,7 @@
|
|||||||
"trigger": {
|
"trigger": {
|
||||||
"documentation": "https:\/\/trigger.dev\/docs\/documentation\/guides\/self-hosting",
|
"documentation": "https:\/\/trigger.dev\/docs\/documentation\/guides\/self-hosting",
|
||||||
"slogan": "The open source Background Jobs framework for TypeScript",
|
"slogan": "The open source Background Jobs framework for TypeScript",
|
||||||
"compose": "c2VydmljZXM6CiAgdHJpZ2dlcjoKICAgIGltYWdlOiAnZ2hjci5pby90cmlnZ2VyZG90ZGV2L3RyaWdnZXIuZGV2OmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9UUklHR0VSCiAgICAgIC0gTE9HSU5fT1JJR0lOPSRTRVJWSUNFX0ZRRE5fVFJJR0dFUgogICAgICAtIEFQUF9PUklHSU49JFNFUlZJQ0VfRlFETl9UUklHR0VSCiAgICAgIC0gTUFHSUNfTElOS19TRUNSRVQ9JFNFUlZJQ0VfUEFTU1dPUkRfNjRfTUFHSUMKICAgICAgLSBFTkNSWVBUSU9OX0tFWT0kU0VSVklDRV9QQVNTV09SRF82NF9FTkNSWVBUSU9OCiAgICAgIC0gU0VTU0lPTl9TRUNSRVQ9JFNFUlZJQ0VfUEFTU1dPUkRfNjRfU0VTU0lPTgogICAgICAtIFBPU1RHUkVTX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogICAgICAtIFBPU1RHUkVTX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LXRyaWdnZXJ9JwogICAgICAtIFBPU1RHUkVTX0hPU1Q9cG9zdGdyZXMKICAgICAgLSAnREFUQUJBU0VfVVJMPXBvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlc3FsOjU0MzIvJFBPU1RHUkVTX0RCJwogICAgICAtICdESVJFQ1RfVVJMPXBvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlc3FsOjU0MzIvJFBPU1RHUkVTX0RCJwogICAgICAtIFJVTlRJTUVfUExBVEZPUk09ZG9ja2VyLWNvbXBvc2UKICAgICAgLSBOT0RFX0VOVj1wcm9kdWN0aW9uCiAgICAgIC0gJ0FVVEhfR0lUSFVCX0NMSUVOVF9JRD0ke0FVVEhfR0lUSFVCX0NMSUVOVF9JRH0nCiAgICAgIC0gJ0FVVEhfR0lUSFVCX0NMSUVOVF9TRUNSRVQ9JHtBVVRIX0dJVEhVQl9DTElFTlRfU0VDUkVUfScKICAgICAgLSAnUkVTRU5EX0FQSV9LRVk9JHtSRVNFTkRfQVBJX0tFWX0nCiAgICAgIC0gJ0ZST01fRU1BSUw9JHtGUk9NX0VNQUlMfScKICAgICAgLSAnUkVQTFlfVE9fRU1BSUw9JHtSRVBMWV9UT19FTUFJTH0nCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3Jlc3FsOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIE5PTkUKICBwb3N0Z3Jlc3FsOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNS1hbHBpbmUnCiAgICB2b2x1bWVzOgogICAgICAtICdwb3N0Z3Jlc3FsLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9TVEdSRVNfVVNFUj0kU0VSVklDRV9VU0VSX1BPU1RHUkVTCiAgICAgIC0gUE9TVEdSRVNfUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVMKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotdHJpZ2dlcn0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==",
|
"compose": "c2VydmljZXM6CiAgdHJpZ2dlcjoKICAgIGltYWdlOiAnZ2hjci5pby90cmlnZ2VyZG90ZGV2L3RyaWdnZXIuZGV2OmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9UUklHR0VSCiAgICAgIC0gTE9HSU5fT1JJR0lOPSRTRVJWSUNFX0ZRRE5fVFJJR0dFUgogICAgICAtIEFQUF9PUklHSU49JFNFUlZJQ0VfRlFETl9UUklHR0VSCiAgICAgIC0gTUFHSUNfTElOS19TRUNSRVQ9JFNFUlZJQ0VfUEFTU1dPUkRfNjRfTUFHSUMKICAgICAgLSBFTkNSWVBUSU9OX0tFWT0kU0VSVklDRV9QQVNTV09SRF82NF9FTkNSWVBUSU9OCiAgICAgIC0gU0VTU0lPTl9TRUNSRVQ9JFNFUlZJQ0VfUEFTU1dPUkRfNjRfU0VTU0lPTgogICAgICAtIFBPU1RHUkVTX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogICAgICAtIFBPU1RHUkVTX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LXRyaWdnZXJ9JwogICAgICAtIFBPU1RHUkVTX0hPU1Q9cG9zdGdyZXMKICAgICAgLSAnREFUQUJBU0VfVVJMPXBvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlc3FsOjU0MzIvJFBPU1RHUkVTX0RCJwogICAgICAtICdESVJFQ1RfVVJMPXBvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlc3FsOjU0MzIvJFBPU1RHUkVTX0RCJwogICAgICAtIFJVTlRJTUVfUExBVEZPUk09ZG9ja2VyLWNvbXBvc2UKICAgICAgLSBOT0RFX0VOVj1wcm9kdWN0aW9uCiAgICAgIC0gJ0FVVEhfR0lUSFVCX0NMSUVOVF9JRD0ke0FVVEhfR0lUSFVCX0NMSUVOVF9JRH0nCiAgICAgIC0gJ0FVVEhfR0lUSFVCX0NMSUVOVF9TRUNSRVQ9JHtBVVRIX0dJVEhVQl9DTElFTlRfU0VDUkVUfScKICAgICAgLSAnUkVTRU5EX0FQSV9LRVk9JHtSRVNFTkRfQVBJX0tFWX0nCiAgICAgIC0gJ0ZST01fRU1BSUw9JHtGUk9NX0VNQUlMfScKICAgICAgLSAnUkVQTFlfVE9fRU1BSUw9JHtSRVBMWV9UT19FTUFJTH0nCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3Jlc3FsOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIE5PTkUKICBwb3N0Z3Jlc3FsOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNi1hbHBpbmUnCiAgICB2b2x1bWVzOgogICAgICAtICdwb3N0Z3Jlc3FsLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9TVEdSRVNfVVNFUj0kU0VSVklDRV9VU0VSX1BPU1RHUkVTCiAgICAgIC0gUE9TVEdSRVNfUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVMKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotdHJpZ2dlcn0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==",
|
||||||
"tags": [
|
"tags": [
|
||||||
"trigger.dev",
|
"trigger.dev",
|
||||||
"background jobs",
|
"background jobs",
|
||||||
@@ -487,7 +500,7 @@
|
|||||||
"umami": {
|
"umami": {
|
||||||
"documentation": "https:\/\/umami.is\/docs\/getting-started",
|
"documentation": "https:\/\/umami.is\/docs\/getting-started",
|
||||||
"slogan": "Umami is a lightweight, self-hosted web analytics platform designed to provide website owners with insights into visitor behavior without compromising user privacy.",
|
"slogan": "Umami is a lightweight, self-hosted web analytics platform designed to provide website owners with insights into visitor behavior without compromising user privacy.",
|
||||||
"compose": "c2VydmljZXM6CiAgdW1hbWk6CiAgICBpbWFnZTogJ2doY3IuaW8vdW1hbWktc29mdHdhcmUvdW1hbWk6cG9zdGdyZXNxbC1sYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fVU1BTUkKICAgICAgLSAnREFUQUJBU0VfVVJMPXBvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlc3FsOjU0MzIvJFBPU1RHUkVTX0RCJwogICAgICAtIERBVEFCQVNFX1RZUEU9cG9zdGdyZXMKICAgICAgLSBBUFBfU0VDUkVUPSRTRVJWSUNFX1BBU1NXT1JEXzY0X1VNQU1JCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3Jlc3FsOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgcG9zdGdyZXNxbDoKICAgIGltYWdlOiAncG9zdGdyZXM6MTUtYWxwaW5lJwogICAgdm9sdW1lczoKICAgICAgLSAncG9zdGdyZXNxbC1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogICAgICAtIFBPU1RHUkVTX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LXVtYW1pfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCAkJHtQT1NUR1JFU19EQn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK",
|
"compose": "c2VydmljZXM6CiAgdW1hbWk6CiAgICBpbWFnZTogJ2doY3IuaW8vdW1hbWktc29mdHdhcmUvdW1hbWk6cG9zdGdyZXNxbC1sYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fVU1BTUkKICAgICAgLSAnREFUQUJBU0VfVVJMPXBvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlc3FsOjU0MzIvJFBPU1RHUkVTX0RCJwogICAgICAtIERBVEFCQVNFX1RZUEU9cG9zdGdyZXMKICAgICAgLSBBUFBfU0VDUkVUPSRTRVJWSUNFX1BBU1NXT1JEXzY0X1VNQU1JCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3Jlc3FsOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgcG9zdGdyZXNxbDoKICAgIGltYWdlOiAncG9zdGdyZXM6MTYtYWxwaW5lJwogICAgdm9sdW1lczoKICAgICAgLSAncG9zdGdyZXNxbC1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogICAgICAtIFBPU1RHUkVTX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LXVtYW1pfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCAkJHtQT1NUR1JFU19EQn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK",
|
||||||
"tags": [
|
"tags": [
|
||||||
"analytics",
|
"analytics",
|
||||||
"insights",
|
"insights",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"version": "3.12.36"
|
"version": "3.12.36"
|
||||||
},
|
},
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.147"
|
"version": "4.0.0-beta.152"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user