mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-31 20:59:23 +00:00
Compare commits
75 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7442d19611 | ||
|
|
d990a5691d | ||
|
|
2181ef381b | ||
|
|
c80f5be974 | ||
|
|
358f6575f8 | ||
|
|
de0f34734b | ||
|
|
33cb2d150d | ||
|
|
5f07b473e9 | ||
|
|
5bcd813792 | ||
|
|
a0bb523507 | ||
|
|
0b4fc38d6b | ||
|
|
82c834915d | ||
|
|
d637675ce3 | ||
|
|
e71c04a0c7 | ||
|
|
885b3bdea7 | ||
|
|
9d6757aeb7 | ||
|
|
d84d0a816b | ||
|
|
0da31c34b5 | ||
|
|
ccdaf59ecb | ||
|
|
3fc9cf90ab | ||
|
|
c4be720b20 | ||
|
|
aabe27efd1 | ||
|
|
a8982379c9 | ||
|
|
6dd0bd0742 | ||
|
|
1d3494a6ba | ||
|
|
ee5eb427c9 | ||
|
|
ad7b5e6e1c | ||
|
|
73bd344147 | ||
|
|
ef448280d8 | ||
|
|
5282248cb4 | ||
|
|
14c9f25c57 | ||
|
|
44b3d08d86 | ||
|
|
5da1f48ae1 | ||
|
|
2bc1b9027c | ||
|
|
1c7ca56756 | ||
|
|
d70faea845 | ||
|
|
fdb5cab875 | ||
|
|
bb6cb8edc9 | ||
|
|
a6ec2b92fb | ||
|
|
436a1d945f | ||
|
|
a6a3abc273 | ||
|
|
c4e702f096 | ||
|
|
31df222798 | ||
|
|
e91939a4ea | ||
|
|
ceccd093d2 | ||
|
|
66bb4e0fc1 | ||
|
|
dd3ff38df7 | ||
|
|
69553ec314 | ||
|
|
7bb1bf0ae3 | ||
|
|
a1a8f1336a | ||
|
|
207fe1d709 | ||
|
|
059535a676 | ||
|
|
765a74ca4f | ||
|
|
e03e4f2e91 | ||
|
|
0ab432d5e6 | ||
|
|
6f25b548c7 | ||
|
|
d55e4bf381 | ||
|
|
ec216254b5 | ||
|
|
fbb36bfe8e | ||
|
|
dd782e75f5 | ||
|
|
2be2f0ac79 | ||
|
|
97943db5f4 | ||
|
|
bbd2748ad7 | ||
|
|
a530804a71 | ||
|
|
8ca8ab82b0 | ||
|
|
024ad8e943 | ||
|
|
4d86b556a4 | ||
|
|
73e38ff951 | ||
|
|
5354561c39 | ||
|
|
223cd37031 | ||
|
|
93a0725a4e | ||
|
|
4d1d4598b6 | ||
|
|
888e96448c | ||
|
|
b84576ce87 | ||
|
|
f2a9a04461 |
@@ -2,14 +2,17 @@
|
||||
|
||||
namespace App\Actions\Proxy;
|
||||
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Models\Server;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class CheckProxy
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(Server $server, $fromUI = false)
|
||||
// It should return if the proxy should be started (true) or not (false)
|
||||
public function handle(Server $server, $fromUI = false): bool
|
||||
{
|
||||
if (! $server->isFunctional()) {
|
||||
return false;
|
||||
@@ -62,22 +65,42 @@ class CheckProxy
|
||||
$ip = 'host.docker.internal';
|
||||
}
|
||||
|
||||
$connection80 = @fsockopen($ip, '80');
|
||||
$connection443 = @fsockopen($ip, '443');
|
||||
$port80 = is_resource($connection80) && fclose($connection80);
|
||||
$port443 = is_resource($connection443) && fclose($connection443);
|
||||
if ($port80) {
|
||||
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>");
|
||||
$portsToCheck = ['80', '443'];
|
||||
|
||||
try {
|
||||
if ($server->proxyType() !== ProxyTypes::NONE->value) {
|
||||
$proxyCompose = CheckConfiguration::run($server);
|
||||
if (isset($proxyCompose)) {
|
||||
$yaml = Yaml::parse($proxyCompose);
|
||||
$portsToCheck = [];
|
||||
if ($server->proxyType() === ProxyTypes::TRAEFIK->value) {
|
||||
$ports = data_get($yaml, 'services.traefik.ports');
|
||||
} elseif ($server->proxyType() === ProxyTypes::CADDY->value) {
|
||||
$ports = data_get($yaml, 'services.caddy.ports');
|
||||
}
|
||||
if (isset($ports)) {
|
||||
foreach ($ports as $port) {
|
||||
$portsToCheck[] = str($port)->before(':')->value();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
$portsToCheck = [];
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
ray($e->getMessage());
|
||||
}
|
||||
if ($port443) {
|
||||
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>");
|
||||
} else {
|
||||
return false;
|
||||
if (count($portsToCheck) === 0) {
|
||||
return false;
|
||||
}
|
||||
foreach ($portsToCheck as $port) {
|
||||
$connection = @fsockopen($ip, $port);
|
||||
if (is_resource($connection) && fclose($connection)) {
|
||||
if ($fromUI) {
|
||||
throw new \Exception("Port $port 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 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
50
app/Console/Commands/CheckApplicationDeploymentQueue.php
Normal file
50
app/Console/Commands/CheckApplicationDeploymentQueue.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class CheckApplicationDeploymentQueue extends Command
|
||||
{
|
||||
protected $signature = 'check:deployment-queue {--force} {--seconds=3600}';
|
||||
|
||||
protected $description = 'Check application deployment queue.';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$seconds = $this->option('seconds');
|
||||
$deployments = ApplicationDeploymentQueue::whereIn('status', [
|
||||
ApplicationDeploymentStatus::IN_PROGRESS,
|
||||
ApplicationDeploymentStatus::QUEUED,
|
||||
])->where('created_at', '<=', now()->subSeconds($seconds))->get();
|
||||
if ($deployments->isEmpty()) {
|
||||
$this->info('No deployments found in the last '.$seconds.' seconds.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->info('Found '.$deployments->count().' deployments created in the last '.$seconds.' seconds.');
|
||||
|
||||
foreach ($deployments as $deployment) {
|
||||
if ($this->option('force')) {
|
||||
$this->info('Deployment '.$deployment->id.' created at '.$deployment->created_at.' is older than '.$seconds.' seconds. Setting status to failed.');
|
||||
$this->cancelDeployment($deployment);
|
||||
} else {
|
||||
$this->info('Deployment '.$deployment->id.' created at '.$deployment->created_at.' is older than '.$seconds.' seconds. Setting status to failed.');
|
||||
if ($this->confirm('Do you want to cancel this deployment?', true)) {
|
||||
$this->cancelDeployment($deployment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function cancelDeployment(ApplicationDeploymentQueue $deployment)
|
||||
{
|
||||
$deployment->update(['status' => ApplicationDeploymentStatus::FAILED]);
|
||||
if ($deployment->server?->isFunctional()) {
|
||||
remote_process(['docker rm -f '.$deployment->deployment_uuid], $deployment->server, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,9 @@ use Illuminate\Console\Command;
|
||||
|
||||
class CleanupApplicationDeploymentQueue extends Command
|
||||
{
|
||||
protected $signature = 'cleanup:application-deployment-queue {--team-id=}';
|
||||
protected $signature = 'cleanup:deployment-queue {--team-id=}';
|
||||
|
||||
protected $description = 'CleanupApplicationDeploymentQueue';
|
||||
protected $description = 'Cleanup application deployment queue.';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\CleanupHelperContainersJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\ScheduledTask;
|
||||
@@ -47,6 +48,17 @@ class CleanupStuckedResources extends Command
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stucked resources: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$applicationsDeploymentQueue = ApplicationDeploymentQueue::get();
|
||||
foreach ($applicationsDeploymentQueue as $applicationDeploymentQueue) {
|
||||
if (is_null($applicationDeploymentQueue->application)) {
|
||||
echo "Deleting stuck application deployment queue: {$applicationDeploymentQueue->id}\n";
|
||||
$applicationDeploymentQueue->delete();
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck application deployment queue: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($applications as $application) {
|
||||
|
||||
@@ -94,7 +94,9 @@ class SshMultiplexingHelper
|
||||
$muxPersistTime = config('constants.ssh.mux_persist_time');
|
||||
|
||||
$scp_command = "timeout $timeout scp ";
|
||||
|
||||
if ($server->isIpv6()) {
|
||||
$scp_command .= '-6 ';
|
||||
}
|
||||
if (self::isMultiplexingEnabled()) {
|
||||
$scp_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
|
||||
self::ensureMultiplexedConnection($server);
|
||||
@@ -136,8 +138,8 @@ class SshMultiplexingHelper
|
||||
|
||||
$ssh_command .= self::getCommonSshOptions($server, $sshKeyLocation, config('constants.ssh.connection_timeout'), config('constants.ssh.server_interval'));
|
||||
|
||||
$command = "PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin && $command";
|
||||
$delimiter = Hash::make($command);
|
||||
$delimiter = base64_encode($delimiter);
|
||||
$command = str_replace($delimiter, '', $command);
|
||||
|
||||
$ssh_command .= "{$server->user}@{$server->ip} 'bash -se' << \\$delimiter".PHP_EOL
|
||||
|
||||
@@ -744,8 +744,10 @@ class ApplicationsController extends Controller
|
||||
$application->destination_id = $destination->id;
|
||||
$application->destination_type = $destination->getMorphClass();
|
||||
$application->environment_id = $environment->id;
|
||||
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||
$application->settings->save();
|
||||
if (isset($useBuildServer)) {
|
||||
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||
$application->settings->save();
|
||||
}
|
||||
$application->save();
|
||||
$application->refresh();
|
||||
if (! $application->settings->is_container_label_readonly_enabled) {
|
||||
@@ -842,8 +844,10 @@ class ApplicationsController extends Controller
|
||||
$application->environment_id = $environment->id;
|
||||
$application->source_type = $githubApp->getMorphClass();
|
||||
$application->source_id = $githubApp->id;
|
||||
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||
$application->settings->save();
|
||||
if (isset($useBuildServer)) {
|
||||
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||
$application->settings->save();
|
||||
}
|
||||
$application->save();
|
||||
$application->refresh();
|
||||
if (! $application->settings->is_container_label_readonly_enabled) {
|
||||
@@ -936,8 +940,10 @@ class ApplicationsController extends Controller
|
||||
$application->destination_id = $destination->id;
|
||||
$application->destination_type = $destination->getMorphClass();
|
||||
$application->environment_id = $environment->id;
|
||||
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||
$application->settings->save();
|
||||
if (isset($useBuildServer)) {
|
||||
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||
$application->settings->save();
|
||||
}
|
||||
$application->save();
|
||||
$application->refresh();
|
||||
if (! $application->settings->is_container_label_readonly_enabled) {
|
||||
@@ -1017,8 +1023,10 @@ class ApplicationsController extends Controller
|
||||
$application->destination_id = $destination->id;
|
||||
$application->destination_type = $destination->getMorphClass();
|
||||
$application->environment_id = $environment->id;
|
||||
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||
$application->settings->save();
|
||||
if (isset($useBuildServer)) {
|
||||
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||
$application->settings->save();
|
||||
}
|
||||
|
||||
$application->git_repository = 'coollabsio/coolify';
|
||||
$application->git_branch = 'main';
|
||||
@@ -1077,8 +1085,10 @@ class ApplicationsController extends Controller
|
||||
$application->destination_id = $destination->id;
|
||||
$application->destination_type = $destination->getMorphClass();
|
||||
$application->environment_id = $environment->id;
|
||||
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||
$application->settings->save();
|
||||
if (isset($useBuildServer)) {
|
||||
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||
$application->settings->save();
|
||||
}
|
||||
|
||||
$application->git_repository = 'coollabsio/coolify';
|
||||
$application->git_branch = 'main';
|
||||
@@ -1555,8 +1565,11 @@ class ApplicationsController extends Controller
|
||||
$instantDeploy = $request->instant_deploy;
|
||||
|
||||
$use_build_server = $request->use_build_server;
|
||||
$application->settings->is_build_server_enabled = $use_build_server;
|
||||
$application->settings->save();
|
||||
|
||||
if (isset($use_build_server)) {
|
||||
$application->settings->is_build_server_enabled = $use_build_server;
|
||||
$application->settings->save();
|
||||
}
|
||||
|
||||
removeUnnecessaryFieldsFromRequest($request);
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Actions\Database\StopDatabase;
|
||||
use App\Events\BackupCreated;
|
||||
use App\Models\S3Storage;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
@@ -24,7 +23,6 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Str;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
@@ -63,31 +61,32 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
public function __construct($backup)
|
||||
{
|
||||
$this->backup = $backup;
|
||||
$this->team = Team::find($backup->team_id);
|
||||
if (is_null($this->team)) {
|
||||
return;
|
||||
}
|
||||
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
|
||||
$this->database = data_get($this->backup, 'database');
|
||||
$this->server = $this->database->service->server;
|
||||
$this->s3 = $this->backup->s3;
|
||||
} else {
|
||||
$this->database = data_get($this->backup, 'database');
|
||||
$this->server = $this->database->destination->server;
|
||||
$this->s3 = $this->backup->s3;
|
||||
}
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
// Check if team is exists
|
||||
if (is_null($this->team)) {
|
||||
StopDatabase::run($this->database);
|
||||
$this->database->delete();
|
||||
$this->team = Team::find($this->backup->team_id);
|
||||
if (! $this->team) {
|
||||
$this->backup->delete();
|
||||
|
||||
return;
|
||||
}
|
||||
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
|
||||
$this->database = data_get($this->backup, 'database');
|
||||
$this->server = $this->database->service->server;
|
||||
$this->s3 = $this->backup->s3;
|
||||
} else {
|
||||
$this->database = data_get($this->backup, 'database');
|
||||
$this->server = $this->database->destination->server;
|
||||
$this->s3 = $this->backup->s3;
|
||||
}
|
||||
if (is_null($this->server)) {
|
||||
throw new \Exception('Server not found?!');
|
||||
}
|
||||
if (is_null($this->database)) {
|
||||
throw new \Exception('Database not found?!');
|
||||
}
|
||||
|
||||
BackupCreated::dispatch($this->team->id);
|
||||
|
||||
@@ -237,7 +236,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
}
|
||||
}
|
||||
$this->backup_dir = backup_dir().'/databases/'.str($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name;
|
||||
|
||||
if ($this->database->name === 'coolify-db') {
|
||||
$databasesToBackup = ['coolify'];
|
||||
$this->directory_name = $this->container_name = 'coolify-db';
|
||||
@@ -250,6 +248,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
try {
|
||||
if (str($databaseType)->contains('postgres')) {
|
||||
$this->backup_file = "/pg-dump-$database-".Carbon::now()->timestamp.'.dmp';
|
||||
if ($this->backup->dump_all) {
|
||||
$this->backup_file = '/pg-dump-all-'.Carbon::now()->timestamp.'.gz';
|
||||
}
|
||||
$this->backup_location = $this->backup_dir.$this->backup_file;
|
||||
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
||||
'database_name' => $database,
|
||||
@@ -278,6 +279,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$this->backup_standalone_mongodb($database);
|
||||
} elseif (str($databaseType)->contains('mysql')) {
|
||||
$this->backup_file = "/mysql-dump-$database-".Carbon::now()->timestamp.'.dmp';
|
||||
if ($this->backup->dump_all) {
|
||||
$this->backup_file = '/mysql-dump-all-'.Carbon::now()->timestamp.'.gz';
|
||||
}
|
||||
$this->backup_location = $this->backup_dir.$this->backup_file;
|
||||
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
||||
'database_name' => $database,
|
||||
@@ -287,6 +291,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$this->backup_standalone_mysql($database);
|
||||
} elseif (str($databaseType)->contains('mariadb')) {
|
||||
$this->backup_file = "/mariadb-dump-$database-".Carbon::now()->timestamp.'.dmp';
|
||||
if ($this->backup->dump_all) {
|
||||
$this->backup_file = '/mariadb-dump-all-'.Carbon::now()->timestamp.'.gz';
|
||||
}
|
||||
$this->backup_location = $this->backup_dir.$this->backup_file;
|
||||
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
||||
'database_name' => $database,
|
||||
@@ -325,7 +332,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
send_internal_notification('DatabaseBackupJob failed with: '.$e->getMessage());
|
||||
throw $e;
|
||||
} finally {
|
||||
BackupCreated::dispatch($this->team->id);
|
||||
if ($this->team) {
|
||||
BackupCreated::dispatch($this->team->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,7 +393,11 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
if ($this->postgres_password) {
|
||||
$backupCommand .= " -e PGPASSWORD=$this->postgres_password";
|
||||
}
|
||||
$backupCommand .= " $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
|
||||
if ($this->backup->dump_all) {
|
||||
$backupCommand .= " $this->container_name pg_dumpall --username {$this->database->postgres_user} | gzip > $this->backup_location";
|
||||
} else {
|
||||
$backupCommand .= " $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
|
||||
}
|
||||
|
||||
$commands[] = $backupCommand;
|
||||
ray($commands);
|
||||
@@ -405,8 +418,11 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
try {
|
||||
$commands[] = 'mkdir -p '.$this->backup_dir;
|
||||
$commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} $database > $this->backup_location";
|
||||
ray($commands);
|
||||
if ($this->backup->dump_all) {
|
||||
$commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} --all-databases --single-transaction --quick --lock-tables=false --compress | gzip > $this->backup_location";
|
||||
} else {
|
||||
$commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} $database > $this->backup_location";
|
||||
}
|
||||
$this->backup_output = instant_remote_process($commands, $this->server);
|
||||
$this->backup_output = trim($this->backup_output);
|
||||
if ($this->backup_output === '') {
|
||||
@@ -424,7 +440,11 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
try {
|
||||
$commands[] = 'mkdir -p '.$this->backup_dir;
|
||||
$commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} $database > $this->backup_location";
|
||||
if ($this->backup->dump_all) {
|
||||
$commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} --all-databases --single-transaction --quick --lock-tables=false --compress > $this->backup_location";
|
||||
} else {
|
||||
$commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} $database > $this->backup_location";
|
||||
}
|
||||
ray($commands);
|
||||
$this->backup_output = instant_remote_process($commands, $this->server);
|
||||
$this->backup_output = trim($this->backup_output);
|
||||
@@ -466,34 +486,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
}
|
||||
}
|
||||
|
||||
// private function upload_to_s3(): void
|
||||
// {
|
||||
// try {
|
||||
// if (is_null($this->s3)) {
|
||||
// return;
|
||||
// }
|
||||
// $key = $this->s3->key;
|
||||
// $secret = $this->s3->secret;
|
||||
// // $region = $this->s3->region;
|
||||
// $bucket = $this->s3->bucket;
|
||||
// $endpoint = $this->s3->endpoint;
|
||||
// $this->s3->testConnection(shouldSave: true);
|
||||
// $configName = new Cuid2;
|
||||
|
||||
// $s3_copy_dir = str($this->backup_location)->replace(backup_dir(), '/var/www/html/storage/app/backups/');
|
||||
// $commands[] = "docker exec coolify bash -c 'mc config host add {$configName} {$endpoint} $key $secret'";
|
||||
// $commands[] = "docker exec coolify bash -c 'mc cp $s3_copy_dir {$configName}/{$bucket}{$this->backup_dir}/'";
|
||||
// instant_remote_process($commands, $this->server);
|
||||
// $this->add_to_backup_output('Uploaded to S3.');
|
||||
// } catch (\Throwable $e) {
|
||||
// $this->add_to_backup_output($e->getMessage());
|
||||
// throw $e;
|
||||
// } finally {
|
||||
// $removeConfigCommands[] = "docker exec coolify bash -c 'mc config remove {$configName}'";
|
||||
// $removeConfigCommands[] = "docker exec coolify bash -c 'mc alias rm {$configName}'";
|
||||
// instant_remote_process($removeConfigCommands, $this->server, false);
|
||||
// }
|
||||
// }
|
||||
private function upload_to_s3(): void
|
||||
{
|
||||
try {
|
||||
@@ -515,10 +507,27 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$this->ensureHelperImageAvailable();
|
||||
|
||||
$fullImageName = $this->getFullImageName();
|
||||
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}";
|
||||
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret";
|
||||
|
||||
if (isDev()) {
|
||||
if ($this->database->name === 'coolify-db') {
|
||||
$backup_location_from = '/var/lib/docker/volumes/coolify_dev_backups_data/_data/coolify/coolify-db-'.$this->server->ip.$this->backup_file;
|
||||
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $backup_location_from:$this->backup_location:ro {$fullImageName}";
|
||||
} else {
|
||||
$backup_location_from = '/var/lib/docker/volumes/coolify_dev_backups_data/_data/databases/'.str($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name.$this->backup_file;
|
||||
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $backup_location_from:$this->backup_location:ro {$fullImageName}";
|
||||
}
|
||||
} else {
|
||||
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}";
|
||||
}
|
||||
if ($this->s3->isHetzner()) {
|
||||
$endpointWithoutBucket = 'https://'.str($endpoint)->after('https://')->after('.')->value();
|
||||
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc alias set --path=off --api=S3v4 temporary {$endpointWithoutBucket} $key $secret";
|
||||
} else {
|
||||
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret";
|
||||
}
|
||||
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/";
|
||||
instant_remote_process($commands, $this->server);
|
||||
|
||||
$this->add_to_backup_output('Uploaded to S3.');
|
||||
} catch (\Throwable $e) {
|
||||
$this->add_to_backup_output($e->getMessage());
|
||||
|
||||
@@ -30,7 +30,7 @@ class Dashboard extends Component
|
||||
|
||||
public function cleanup_queue()
|
||||
{
|
||||
Artisan::queue('cleanup:application-deployment-queue', [
|
||||
Artisan::queue('cleanup:deployment-queue', [
|
||||
'--team-id' => currentTeam()->id,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ class BackupEdit extends Component
|
||||
'backup.save_s3' => 'required|boolean',
|
||||
'backup.s3_storage_id' => 'nullable|integer',
|
||||
'backup.databases_to_backup' => 'nullable',
|
||||
'backup.dump_all' => 'required|boolean',
|
||||
];
|
||||
|
||||
protected $validationAttributes = [
|
||||
@@ -40,6 +41,7 @@ class BackupEdit extends Component
|
||||
'backup.save_s3' => 'Save to S3',
|
||||
'backup.s3_storage_id' => 'S3 Storage',
|
||||
'backup.databases_to_backup' => 'Databases to Backup',
|
||||
'backup.dump_all' => 'Backup All Databases',
|
||||
];
|
||||
|
||||
protected $messages = [
|
||||
|
||||
@@ -108,6 +108,21 @@ class Navbar extends Component
|
||||
|
||||
return;
|
||||
}
|
||||
StopService::run(service: $this->service, dockerCleanup: false);
|
||||
$this->service->parse();
|
||||
$this->dispatch('imagePulled');
|
||||
$activity = StartService::run($this->service);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
}
|
||||
|
||||
public function pullAndRestartEvent()
|
||||
{
|
||||
$this->checkDeployments();
|
||||
if ($this->isDeploymentProgress) {
|
||||
$this->dispatch('error', 'There is a deployment in progress.');
|
||||
|
||||
return;
|
||||
}
|
||||
PullImage::run($this->service);
|
||||
StopService::run(service: $this->service, dockerCleanup: false);
|
||||
$this->service->parse();
|
||||
|
||||
@@ -34,9 +34,9 @@ class Terminal extends Component
|
||||
if ($status !== 'running') {
|
||||
return;
|
||||
}
|
||||
$command = SshMultiplexingHelper::generateSshCommand($server, "docker exec -it {$identifier} sh -c 'if [ -f ~/.profile ]; then . ~/.profile; fi; if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'");
|
||||
$command = SshMultiplexingHelper::generateSshCommand($server, "docker exec -it {$identifier} sh -c 'PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'");
|
||||
} else {
|
||||
$command = SshMultiplexingHelper::generateSshCommand($server, "sh -c 'if [ -f ~/.profile ]; then . ~/.profile; fi; if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'");
|
||||
$command = SshMultiplexingHelper::generateSshCommand($server, 'PATH=$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n "$SHELL" ]; then exec $SHELL; else sh; fi');
|
||||
}
|
||||
|
||||
// ssh command is sent back to frontend then to websocket
|
||||
|
||||
@@ -15,6 +15,8 @@ class ApiTokens extends Component
|
||||
|
||||
public bool $readOnly = true;
|
||||
|
||||
public bool $rootAccess = false;
|
||||
|
||||
public array $permissions = ['read-only'];
|
||||
|
||||
public $isApiEnabled;
|
||||
@@ -35,12 +37,11 @@ class ApiTokens extends Component
|
||||
if ($this->viewSensitiveData) {
|
||||
$this->permissions[] = 'view:sensitive';
|
||||
$this->permissions = array_diff($this->permissions, ['*']);
|
||||
$this->rootAccess = false;
|
||||
} else {
|
||||
$this->permissions = array_diff($this->permissions, ['view:sensitive']);
|
||||
}
|
||||
if (count($this->permissions) == 0) {
|
||||
$this->permissions = ['*'];
|
||||
}
|
||||
$this->makeSureOneIsSelected();
|
||||
}
|
||||
|
||||
public function updatedReadOnly()
|
||||
@@ -48,11 +49,30 @@ class ApiTokens extends Component
|
||||
if ($this->readOnly) {
|
||||
$this->permissions[] = 'read-only';
|
||||
$this->permissions = array_diff($this->permissions, ['*']);
|
||||
$this->rootAccess = false;
|
||||
} else {
|
||||
$this->permissions = array_diff($this->permissions, ['read-only']);
|
||||
}
|
||||
if (count($this->permissions) == 0) {
|
||||
$this->makeSureOneIsSelected();
|
||||
}
|
||||
|
||||
public function updatedRootAccess()
|
||||
{
|
||||
if ($this->rootAccess) {
|
||||
$this->permissions = ['*'];
|
||||
$this->readOnly = false;
|
||||
$this->viewSensitiveData = false;
|
||||
} else {
|
||||
$this->readOnly = true;
|
||||
$this->permissions = ['read-only'];
|
||||
}
|
||||
}
|
||||
|
||||
public function makeSureOneIsSelected()
|
||||
{
|
||||
if (count($this->permissions) == 0) {
|
||||
$this->permissions = ['read-only'];
|
||||
$this->readOnly = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,12 +82,6 @@ class ApiTokens extends Component
|
||||
$this->validate([
|
||||
'description' => 'required|min:3|max:255',
|
||||
]);
|
||||
// if ($this->viewSensitiveData) {
|
||||
// $this->permissions[] = 'view:sensitive';
|
||||
// }
|
||||
// if ($this->readOnly) {
|
||||
// $this->permissions[] = 'read-only';
|
||||
// }
|
||||
$token = auth()->user()->createToken($this->description, $this->permissions);
|
||||
$this->tokens = auth()->user()->tokens;
|
||||
session()->flash('token', $token->plainTextToken);
|
||||
|
||||
@@ -30,6 +30,11 @@ class ConfigureCloudflareTunnels extends Component
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
if (str($this->ssh_domain)->contains('https://')) {
|
||||
$this->ssh_domain = str($this->ssh_domain)->replace('https://', '')->replace('http://', '')->trim();
|
||||
// remove / from the end
|
||||
$this->ssh_domain = str($this->ssh_domain)->replace('/', '');
|
||||
}
|
||||
$server = Server::ownedByCurrentTeam()->where('id', $this->server_id)->firstOrFail();
|
||||
ConfigureCloudflared::dispatch($server, $this->cloudflare_token);
|
||||
$server->settings->is_cloudflare_tunnel = true;
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace App\Livewire\Server\Proxy;
|
||||
|
||||
use App\Actions\Docker\GetContainersStatus;
|
||||
use App\Actions\Proxy\CheckProxy;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Actions\Proxy\StartProxy;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
|
||||
@@ -44,7 +44,10 @@ class Status extends Component
|
||||
}
|
||||
$this->numberOfPolls++;
|
||||
}
|
||||
CheckProxy::run($this->server, true);
|
||||
$shouldStart = CheckProxy::run($this->server, true);
|
||||
if ($shouldStart) {
|
||||
StartProxy::run($this->server, false);
|
||||
}
|
||||
$this->dispatch('proxyStatusUpdated');
|
||||
if ($this->server->proxy->status === 'running') {
|
||||
$this->polling = false;
|
||||
|
||||
@@ -43,15 +43,17 @@ class Create extends Component
|
||||
'endpoint' => 'Endpoint',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
public function updatedEndpoint($value)
|
||||
{
|
||||
if (isDev()) {
|
||||
$this->name = 'Local MinIO';
|
||||
$this->description = 'Local MinIO';
|
||||
$this->key = 'minioadmin';
|
||||
$this->secret = 'minioadmin';
|
||||
$this->bucket = 'local';
|
||||
$this->endpoint = 'http://coolify-minio:9000';
|
||||
if (! str($value)->startsWith('https://') && ! str($value)->startsWith('http://')) {
|
||||
$this->endpoint = 'https://'.$value;
|
||||
$value = $this->endpoint;
|
||||
}
|
||||
|
||||
if (str($value)->contains('your-objectstorage.com') && ! isset($this->bucket)) {
|
||||
$this->bucket = str($value)->after('//')->before('.');
|
||||
} elseif (str($value)->contains('your-objectstorage.com')) {
|
||||
$this->bucket = $this->bucket ?: str($value)->after('//')->before('.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -143,6 +143,9 @@ class Application extends BaseModel
|
||||
}
|
||||
$application->tags()->detach();
|
||||
$application->previews()->delete();
|
||||
foreach ($application->deployment_queue as $deployment) {
|
||||
$deployment->delete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -710,6 +713,11 @@ class Application extends BaseModel
|
||||
return $this->hasMany(ApplicationPreview::class);
|
||||
}
|
||||
|
||||
public function deployment_queue()
|
||||
{
|
||||
return $this->hasMany(ApplicationDeploymentQueue::class);
|
||||
}
|
||||
|
||||
public function destination()
|
||||
{
|
||||
return $this->morphTo();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Carbon;
|
||||
use OpenApi\Attributes as OA;
|
||||
@@ -39,6 +40,20 @@ class ApplicationDeploymentQueue extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
|
||||
public function application(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => Application::find($this->application_id),
|
||||
);
|
||||
}
|
||||
|
||||
public function server(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => Server::find($this->server_id),
|
||||
);
|
||||
}
|
||||
|
||||
public function setStatus(string $status)
|
||||
{
|
||||
$this->update([
|
||||
|
||||
@@ -40,6 +40,16 @@ class S3Storage extends BaseModel
|
||||
return "{$this->endpoint}/{$this->bucket}";
|
||||
}
|
||||
|
||||
public function isHetzner()
|
||||
{
|
||||
return str($this->endpoint)->contains('your-objectstorage.com');
|
||||
}
|
||||
|
||||
public function isDigitalOcean()
|
||||
{
|
||||
return str($this->endpoint)->contains('digitaloceanspaces.com');
|
||||
}
|
||||
|
||||
public function testConnection(bool $shouldSave = false)
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -460,15 +460,6 @@ $schema://$host {
|
||||
|
||||
public function proxyType()
|
||||
{
|
||||
// $proxyType = $this->proxy->get('type');
|
||||
// if ($proxyType === ProxyTypes::NONE->value) {
|
||||
// return $proxyType;
|
||||
// }
|
||||
// if (is_null($proxyType)) {
|
||||
// $this->proxy->type = ProxyTypes::TRAEFIK->value;
|
||||
// $this->proxy->status = ProxyStatus::EXITED->value;
|
||||
// $this->save();
|
||||
// }
|
||||
return data_get($this->proxy, 'type');
|
||||
}
|
||||
|
||||
@@ -1221,4 +1212,9 @@ $schema://$host {
|
||||
|
||||
return instant_remote_process($commands, $this, false);
|
||||
}
|
||||
|
||||
public function isIpv6(): bool
|
||||
{
|
||||
return str($this->ip)->contains(':');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -770,6 +770,30 @@ class Service extends BaseModel
|
||||
}
|
||||
$fields->put('Code Server', $data->toArray());
|
||||
break;
|
||||
case str($image)->contains('elestio/strapi'):
|
||||
$data = collect([]);
|
||||
$license = $this->environment_variables()->where('key', 'STRAPI_LICENSE')->first();
|
||||
if ($license) {
|
||||
$data = $data->merge([
|
||||
'License' => [
|
||||
'key' => data_get($license, 'key'),
|
||||
'value' => data_get($license, 'value'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
$nodeEnv = $this->environment_variables()->where('key', 'NODE_ENV')->first();
|
||||
if ($nodeEnv) {
|
||||
$data = $data->merge([
|
||||
'Node Environment' => [
|
||||
'key' => data_get($nodeEnv, 'key'),
|
||||
'value' => data_get($nodeEnv, 'value'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
$fields->put('Strapi', $data->toArray());
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
$databases = $this->databases()->get();
|
||||
@@ -1108,7 +1132,6 @@ class Service extends BaseModel
|
||||
$real_value = escapeEnvVariables($env->real_value);
|
||||
}
|
||||
}
|
||||
ray("echo \"{$env->key}={$real_value}\" >> .env");
|
||||
$commands[] = "echo \"{$env->key}={$real_value}\" >> .env";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,4 +112,9 @@ class ServiceApplication extends BaseModel
|
||||
{
|
||||
getFilesystemVolumesFromServer($this, $isInit);
|
||||
}
|
||||
|
||||
public function isBackupSolutionAvailable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,4 +115,13 @@ class ServiceDatabase extends BaseModel
|
||||
{
|
||||
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
|
||||
}
|
||||
|
||||
public function isBackupSolutionAvailable()
|
||||
{
|
||||
return str($this->databaseType())->contains('mysql') ||
|
||||
str($this->databaseType())->contains('postgres') ||
|
||||
str($this->databaseType())->contains('postgis') ||
|
||||
str($this->databaseType())->contains('mariadb') ||
|
||||
str($this->databaseType())->contains('mongodb');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,4 +294,9 @@ class StandaloneClickhouse extends BaseModel
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
public function isBackupSolutionAvailable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,4 +294,9 @@ class StandaloneDragonfly extends BaseModel
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
public function isBackupSolutionAvailable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,4 +294,9 @@ class StandaloneKeydb extends BaseModel
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
public function isBackupSolutionAvailable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,4 +294,9 @@ class StandaloneMariadb extends BaseModel
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
public function isBackupSolutionAvailable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,4 +314,9 @@ class StandaloneMongodb extends BaseModel
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
public function isBackupSolutionAvailable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,4 +295,9 @@ class StandaloneMysql extends BaseModel
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
public function isBackupSolutionAvailable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,4 +296,9 @@ class StandalonePostgresql extends BaseModel
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
public function isBackupSolutionAvailable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,4 +290,9 @@ class StandaloneRedis extends BaseModel
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
public function isBackupSolutionAvailable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,16 @@ const RESTART_MODE = 'unless-stopped';
|
||||
const DATABASE_DOCKER_IMAGES = [
|
||||
'bitnami/mariadb',
|
||||
'bitnami/mongodb',
|
||||
'bitnami/mysql',
|
||||
'bitnami/postgresql',
|
||||
'bitnami/redis',
|
||||
'mysql',
|
||||
'bitnami/mysql',
|
||||
'mysql/mysql-server',
|
||||
'mariadb',
|
||||
'postgis/postgis',
|
||||
'postgres',
|
||||
'bitnami/postgresql',
|
||||
'supabase/postgres',
|
||||
'elestio/postgres',
|
||||
'mongo',
|
||||
'redis',
|
||||
'memcached',
|
||||
@@ -33,7 +37,6 @@ const DATABASE_DOCKER_IMAGES = [
|
||||
'neo4j',
|
||||
'influxdb',
|
||||
'clickhouse/clickhouse-server',
|
||||
'supabase/postgres',
|
||||
];
|
||||
const SPECIFIC_SERVICES = [
|
||||
'quay.io/minio/minio',
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
<?php
|
||||
|
||||
use App\Models\S3Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
function set_s3_target(S3Storage $s3)
|
||||
{
|
||||
$is_digital_ocean = false;
|
||||
if ($s3->endpoint) {
|
||||
$is_digital_ocean = Str::contains($s3->endpoint, 'digitaloceanspaces.com');
|
||||
}
|
||||
|
||||
config()->set('filesystems.disks.custom-s3', [
|
||||
'driver' => 's3',
|
||||
'region' => $s3['region'],
|
||||
@@ -17,7 +14,7 @@ function set_s3_target(S3Storage $s3)
|
||||
'bucket' => $s3['bucket'],
|
||||
'endpoint' => $s3['endpoint'],
|
||||
'use_path_style_endpoint' => true,
|
||||
'bucket_endpoint' => $is_digital_ocean,
|
||||
'bucket_endpoint' => $s3->isHetzner() || $s3->isDigitalOcean(),
|
||||
'aws_url' => $s3->awsUrl(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -708,7 +708,9 @@ function getTopLevelNetworks(Service|Application $resource)
|
||||
return $value == $networkName || $key == $networkName;
|
||||
});
|
||||
if (! $networkExists) {
|
||||
$topLevelNetworks->put($networkDetails, null);
|
||||
if (is_string($networkDetails) || is_int($networkDetails)) {
|
||||
$topLevelNetworks->put($networkDetails, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -758,7 +760,9 @@ function getTopLevelNetworks(Service|Application $resource)
|
||||
return $value == $networkName || $key == $networkName;
|
||||
});
|
||||
if (! $networkExists) {
|
||||
$topLevelNetworks->put($networkDetails, null);
|
||||
if (is_string($networkDetails) || is_int($networkDetails)) {
|
||||
$topLevelNetworks->put($networkDetails, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1184,14 +1188,16 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
|
||||
function parseCommandsByLineForSudo(Collection $commands, Server $server): array
|
||||
{
|
||||
$commands = $commands->map(function ($line) {
|
||||
if (! str(trim($line))->startsWith([
|
||||
'cd',
|
||||
'command',
|
||||
'echo',
|
||||
'true',
|
||||
'if',
|
||||
'fi',
|
||||
])) {
|
||||
if (
|
||||
! str(trim($line))->startsWith([
|
||||
'cd',
|
||||
'command',
|
||||
'echo',
|
||||
'true',
|
||||
'if',
|
||||
'fi',
|
||||
])
|
||||
) {
|
||||
return "sudo $line";
|
||||
}
|
||||
|
||||
@@ -1606,7 +1612,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
return $value == $networkName || $key == $networkName;
|
||||
});
|
||||
if (! $networkExists) {
|
||||
$topLevelNetworks->put($networkDetails, null);
|
||||
if (is_string($networkDetails) || is_int($networkDetails)) {
|
||||
$topLevelNetworks->put($networkDetails, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2521,7 +2529,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
return $value == $networkName || $key == $networkName;
|
||||
});
|
||||
if (! $networkExists) {
|
||||
$topLevelNetworks->put($networkDetails, null);
|
||||
if (is_string($networkDetails) || is_int($networkDetails)) {
|
||||
$topLevelNetworks->put($networkDetails, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2982,11 +2992,22 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
||||
$predefinedPort = '8000';
|
||||
}
|
||||
if ($isDatabase) {
|
||||
$savedService = ServiceDatabase::firstOrCreate([
|
||||
'name' => $serviceName,
|
||||
'image' => $image,
|
||||
'service_id' => $resource->id,
|
||||
]);
|
||||
$applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first();
|
||||
if ($applicationFound) {
|
||||
$savedService = $applicationFound;
|
||||
$savedService = ServiceDatabase::firstOrCreate([
|
||||
'name' => $applicationFound->name,
|
||||
'image' => $applicationFound->image,
|
||||
'service_id' => $applicationFound->service_id,
|
||||
]);
|
||||
$applicationFound->delete();
|
||||
} else {
|
||||
$savedService = ServiceDatabase::firstOrCreate([
|
||||
'name' => $serviceName,
|
||||
'image' => $image,
|
||||
'service_id' => $resource->id,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$savedService = ServiceApplication::firstOrCreate([
|
||||
'name' => $serviceName,
|
||||
@@ -3207,12 +3228,24 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
||||
if ($serviceName === 'plausible') {
|
||||
$predefinedPort = '8000';
|
||||
}
|
||||
|
||||
if ($isDatabase) {
|
||||
$savedService = ServiceDatabase::firstOrCreate([
|
||||
'name' => $serviceName,
|
||||
'image' => $image,
|
||||
'service_id' => $resource->id,
|
||||
]);
|
||||
$applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first();
|
||||
if ($applicationFound) {
|
||||
$savedService = $applicationFound;
|
||||
$savedService = ServiceDatabase::firstOrCreate([
|
||||
'name' => $applicationFound->name,
|
||||
'image' => $applicationFound->image,
|
||||
'service_id' => $applicationFound->service_id,
|
||||
]);
|
||||
$applicationFound->delete();
|
||||
} else {
|
||||
$savedService = ServiceDatabase::firstOrCreate([
|
||||
'name' => $serviceName,
|
||||
'image' => $image,
|
||||
'service_id' => $resource->id,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$savedService = ServiceApplication::firstOrCreate([
|
||||
'name' => $serviceName,
|
||||
@@ -3863,14 +3896,19 @@ function convertComposeEnvironmentToArray($environment)
|
||||
{
|
||||
$convertedServiceVariables = collect([]);
|
||||
if (isAssociativeArray($environment)) {
|
||||
// Example: $environment = ['FOO' => 'bar', 'BAZ' => 'qux'];
|
||||
if ($environment instanceof Collection) {
|
||||
$changedEnvironment = collect([]);
|
||||
$environment->each(function ($value, $key) use ($changedEnvironment) {
|
||||
$parts = explode('=', $value, 2);
|
||||
if (count($parts) === 2) {
|
||||
$key = $parts[0];
|
||||
$realValue = $parts[1] ?? '';
|
||||
$changedEnvironment->put($key, $realValue);
|
||||
if (is_numeric($key)) {
|
||||
$parts = explode('=', $value, 2);
|
||||
if (count($parts) === 2) {
|
||||
$key = $parts[0];
|
||||
$realValue = $parts[1] ?? '';
|
||||
$changedEnvironment->put($key, $realValue);
|
||||
} else {
|
||||
$changedEnvironment->put($key, $value);
|
||||
}
|
||||
} else {
|
||||
$changedEnvironment->put($key, $value);
|
||||
}
|
||||
@@ -3880,12 +3918,15 @@ function convertComposeEnvironmentToArray($environment)
|
||||
}
|
||||
$convertedServiceVariables = $environment;
|
||||
} else {
|
||||
// Example: $environment = ['FOO=bar', 'BAZ=qux'];
|
||||
foreach ($environment as $value) {
|
||||
$parts = explode('=', $value, 2);
|
||||
$key = $parts[0];
|
||||
$realValue = $parts[1] ?? '';
|
||||
if ($key) {
|
||||
$convertedServiceVariables->put($key, $realValue);
|
||||
if (is_string($value)) {
|
||||
$parts = explode('=', $value, 2);
|
||||
$key = $parts[0];
|
||||
$realValue = $parts[1] ?? '';
|
||||
if ($key) {
|
||||
$convertedServiceVariables->put($key, $realValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ return [
|
||||
|
||||
// The release version of your application
|
||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||
'release' => '4.0.0-beta.348',
|
||||
'release' => '4.0.0-beta.354',
|
||||
// When left empty or `null` the Laravel environment will be used
|
||||
'environment' => config('app.env'),
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<?php
|
||||
|
||||
return '4.0.0-beta.348';
|
||||
return '4.0.0-beta.354';
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('scheduled_database_backups', function (Blueprint $table) {
|
||||
$table->boolean('dump_all')->default(false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('scheduled_database_backups', function (Blueprint $table) {
|
||||
$table->dropColumn('dump_all');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -58,6 +58,7 @@ services:
|
||||
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID:-coolify}"
|
||||
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY:-coolify}"
|
||||
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}"
|
||||
entrypoint: ["/bin/sh", "/soketi-entrypoint.sh"]
|
||||
vite:
|
||||
image: node:20
|
||||
pull_policy: always
|
||||
|
||||
@@ -113,7 +113,7 @@ services:
|
||||
retries: 10
|
||||
timeout: 2s
|
||||
soketi:
|
||||
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.2'
|
||||
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.3'
|
||||
ports:
|
||||
- "${SOKETI_PORT:-6001}:6001"
|
||||
- "6002:6002"
|
||||
|
||||
@@ -1,9 +1,27 @@
|
||||
FROM quay.io/soketi/soketi:1.6-16-alpine
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
# https://github.com/cloudflare/cloudflared/releases
|
||||
ARG CLOUDFLARED_VERSION=2024.4.1
|
||||
|
||||
WORKDIR /terminal
|
||||
RUN apk add --no-cache openssh-client make g++ python3
|
||||
RUN apk add --no-cache openssh-client make g++ python3 curl
|
||||
COPY docker/coolify-realtime/package.json ./
|
||||
RUN npm i
|
||||
RUN npm rebuild node-pty --update-binary
|
||||
COPY docker/coolify-realtime/soketi-entrypoint.sh /soketi-entrypoint.sh
|
||||
COPY docker/coolify-realtime/terminal-server.js /terminal/terminal-server.js
|
||||
|
||||
RUN /bin/sh -c "if [[ ${TARGETPLATFORM} == 'linux/amd64' ]]; then \
|
||||
echo 'amd64' && \
|
||||
curl -sSL https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \
|
||||
;fi"
|
||||
|
||||
RUN /bin/sh -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \
|
||||
echo 'arm64' && \
|
||||
curl -L https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \
|
||||
;fi"
|
||||
|
||||
|
||||
|
||||
ENTRYPOINT ["/bin/sh", "/soketi-entrypoint.sh"]
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
#!/bin/sh
|
||||
# Function to timestamp logs
|
||||
|
||||
# Check if the first argument is 'watch'
|
||||
if [ "$1" = "watch" ]; then
|
||||
WATCH_MODE="--watch"
|
||||
else
|
||||
WATCH_MODE=""
|
||||
fi
|
||||
|
||||
timestamp() {
|
||||
date "+%Y-%m-%d %H:%M:%S"
|
||||
}
|
||||
|
||||
# Start the terminal server in the background with logging
|
||||
node /terminal/terminal-server.js > >(while read line; do echo "$(timestamp) [TERMINAL] $line"; done) 2>&1 &
|
||||
node $WATCH_MODE /terminal/terminal-server.js > >(while read line; do echo "$(timestamp) [TERMINAL] $line"; done) 2>&1 &
|
||||
TERMINAL_PID=$!
|
||||
|
||||
# Start the Soketi process in the background with logging
|
||||
|
||||
@@ -61,9 +61,13 @@ wss.on('connection', (ws) => {
|
||||
const userSession = { ws, userId, ptyProcess: null, isActive: false };
|
||||
userSessions.set(userId, userSession);
|
||||
|
||||
ws.on('message', (message) => handleMessage(userSession, message));
|
||||
ws.on('message', (message) => {
|
||||
handleMessage(userSession, message);
|
||||
|
||||
});
|
||||
ws.on('error', (err) => handleError(err, userId));
|
||||
ws.on('close', () => handleClose(userId));
|
||||
|
||||
});
|
||||
|
||||
const messageHandlers = {
|
||||
@@ -108,7 +112,6 @@ function parseMessage(message) {
|
||||
|
||||
async function handleCommand(ws, command, userId) {
|
||||
const userSession = userSessions.get(userId);
|
||||
|
||||
if (userSession && userSession.isActive) {
|
||||
const result = await killPtyProcess(userId);
|
||||
if (!result) {
|
||||
@@ -127,6 +130,7 @@ async function handleCommand(ws, command, userId) {
|
||||
cols: 80,
|
||||
rows: 30,
|
||||
cwd: process.env.HOME,
|
||||
env: {},
|
||||
};
|
||||
|
||||
// NOTE: - Initiates a process within the Terminal container
|
||||
@@ -139,13 +143,16 @@ async function handleCommand(ws, command, userId) {
|
||||
|
||||
ws.send('pty-ready');
|
||||
|
||||
ptyProcess.onData((data) => ws.send(data));
|
||||
ptyProcess.onData((data) => {
|
||||
ws.send(data);
|
||||
});
|
||||
|
||||
// when parent closes
|
||||
ptyProcess.onExit(({ exitCode, signal }) => {
|
||||
console.error(`Process exited with code ${exitCode} and signal ${signal}`);
|
||||
ws.send('pty-exited');
|
||||
userSession.isActive = false;
|
||||
|
||||
});
|
||||
|
||||
if (timeout) {
|
||||
@@ -179,7 +186,7 @@ async function killPtyProcess(userId) {
|
||||
|
||||
// session.ptyProcess.kill() wont work here because of https://github.com/moby/moby/issues/9098
|
||||
// patch with https://github.com/moby/moby/issues/9098#issuecomment-189743947
|
||||
session.ptyProcess.write('kill -TERM -$$ && exit\n');
|
||||
session.ptyProcess.write('set +o history\nkill -TERM -$$ && exit\nset -o history\n');
|
||||
|
||||
setTimeout(() => {
|
||||
if (!session.isActive || !session.ptyProcess) {
|
||||
@@ -228,5 +235,5 @@ function extractHereDocContent(commandString) {
|
||||
}
|
||||
|
||||
server.listen(6002, () => {
|
||||
console.log('Server listening on port 6002');
|
||||
console.log('Coolify realtime terminal server listening on port 6002. Let the hacking begin!');
|
||||
});
|
||||
|
||||
@@ -46,6 +46,9 @@ services:
|
||||
- PUSHER_APP_ID
|
||||
- PUSHER_APP_KEY
|
||||
- PUSHER_APP_SECRET
|
||||
- TERMINAL_PROTOCOL
|
||||
- TERMINAL_HOST
|
||||
- TERMINAL_PORT
|
||||
- AUTOUPDATE
|
||||
- SELF_HOSTED
|
||||
- SSH_MUX_ENABLED
|
||||
@@ -110,7 +113,7 @@ services:
|
||||
retries: 10
|
||||
timeout: 2s
|
||||
soketi:
|
||||
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.2'
|
||||
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.3'
|
||||
ports:
|
||||
- "${SOKETI_PORT:-6001}:6001"
|
||||
- "6002:6002"
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"coolify": {
|
||||
"v4": {
|
||||
"version": "4.0.0-beta.348"
|
||||
"version": "4.0.0-beta.354"
|
||||
},
|
||||
"nightly": {
|
||||
"version": "4.0.0-beta.349"
|
||||
"version": "4.0.0-beta.355"
|
||||
},
|
||||
"helper": {
|
||||
"version": "1.0.1"
|
||||
},
|
||||
"realtime": {
|
||||
"version": "1.0.2"
|
||||
"version": "1.0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
15
public/svgs/bitcoin.svg
Normal file
15
public/svgs/bitcoin.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Creator: CorelDRAW 2019 (64-Bit) -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="100%" height="100%" version="1.1" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd"
|
||||
viewBox="0 0 4091.27 4091.73"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:xodm="http://www.corel.com/coreldraw/odm/2003">
|
||||
<g id="Layer_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||
<g id="_1421344023328">
|
||||
<path fill="#F7931A" fill-rule="nonzero" d="M4030.06 2540.77c-273.24,1096.01 -1383.32,1763.02 -2479.46,1489.71 -1095.68,-273.24 -1762.69,-1383.39 -1489.33,-2479.31 273.12,-1096.13 1383.2,-1763.19 2479,-1489.95 1096.06,273.24 1763.03,1383.51 1489.76,2479.57l0.02 -0.02z"/>
|
||||
<path fill="white" fill-rule="nonzero" d="M2947.77 1754.38c40.72,-272.26 -166.56,-418.61 -450,-516.24l91.95 -368.8 -224.5 -55.94 -89.51 359.09c-59.02,-14.72 -119.63,-28.59 -179.87,-42.34l90.16 -361.46 -224.36 -55.94 -92 368.68c-48.84,-11.12 -96.81,-22.11 -143.35,-33.69l0.26 -1.16 -309.59 -77.31 -59.72 239.78c0,0 166.56,38.18 163.05,40.53 90.91,22.69 107.35,82.87 104.62,130.57l-104.74 420.15c6.26,1.59 14.38,3.89 23.34,7.49 -7.49,-1.86 -15.46,-3.89 -23.73,-5.87l-146.81 588.57c-11.11,27.62 -39.31,69.07 -102.87,53.33 2.25,3.26 -163.17,-40.72 -163.17,-40.72l-111.46 256.98 292.15 72.83c54.35,13.63 107.61,27.89 160.06,41.3l-92.9 373.03 224.24 55.94 92 -369.07c61.26,16.63 120.71,31.97 178.91,46.43l-91.69 367.33 224.51 55.94 92.89 -372.33c382.82,72.45 670.67,43.24 791.83,-303.02 97.63,-278.78 -4.86,-439.58 -206.26,-544.44 146.69,-33.83 257.18,-130.31 286.64,-329.61l-0.07 -0.05zm-512.93 719.26c-69.38,278.78 -538.76,128.08 -690.94,90.29l123.28 -494.2c152.17,37.99 640.17,113.17 567.67,403.91zm69.43 -723.3c-63.29,253.58 -453.96,124.75 -580.69,93.16l111.77 -448.21c126.73,31.59 534.85,90.55 468.94,355.05l-0.02 0z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
11
public/svgs/homarr.svg
Normal file
11
public/svgs/homarr.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="484px" height="329px" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g><path style="opacity:0.947" fill="#f95251" d="M 157.5,-0.5 C 158.833,-0.5 160.167,-0.5 161.5,-0.5C 200.102,3.59921 222.268,24.9325 228,63.5C 228.667,88.5 228.667,113.5 228,138.5C 222.962,132.265 216.629,130.265 209,132.5C 208.667,109.167 208.333,85.8333 208,62.5C 202.332,36.3319 186.165,21.9986 159.5,19.5C 141.783,20.273 127.949,27.9397 118,42.5C 112.924,38.3791 107.757,34.3791 102.5,30.5C 115.97,11.5969 134.303,1.26361 157.5,-0.5 Z"/></g>
|
||||
<g><path style="opacity:0.947" fill="#f95251" d="M 321.5,-0.5 C 322.833,-0.5 324.167,-0.5 325.5,-0.5C 348.697,1.26361 367.03,11.5969 380.5,30.5C 375.243,34.3791 370.076,38.3791 365,42.5C 349.005,21.3908 328.505,15.2242 303.5,24C 287.107,31.7273 277.607,44.5606 275,62.5C 274.667,85.8333 274.333,109.167 274,132.5C 266.371,130.265 260.038,132.265 255,138.5C 254.333,113.5 254.333,88.5 255,63.5C 260.732,24.9325 282.898,3.59921 321.5,-0.5 Z"/></g>
|
||||
<g><path style="opacity:0.984" fill="#f95251" d="M -0.5,139.5 C -0.5,137.167 -0.5,134.833 -0.5,132.5C 19.5,132.5 39.5,132.5 59.5,132.5C 51.7127,98.1844 43.3793,64.0177 34.5,30C 83.3876,36.5512 118.554,62.0512 140,106.5C 159.513,155.397 153.846,201.064 123,243.5C 114.899,253.77 105.399,262.437 94.5,269.5C 90.7735,258.059 87.6068,246.392 85,234.5C 39.0428,218.384 10.5428,186.717 -0.5,139.5 Z"/></g>
|
||||
<g><path style="opacity:0.984" fill="#f95251" d="M 483.5,132.5 C 483.5,134.833 483.5,137.167 483.5,139.5C 472.457,186.717 443.957,218.384 398,234.5C 395.393,246.392 392.226,258.059 388.5,269.5C 351.514,243.037 332.514,206.871 331.5,161C 333.865,105.236 359.865,64.9024 409.5,40C 421.97,34.3953 434.97,31.0619 448.5,30C 439.621,64.0177 431.287,98.1844 423.5,132.5C 443.5,132.5 463.5,132.5 483.5,132.5 Z"/></g>
|
||||
<g><path style="opacity:0.95" fill="#f95251" d="M 211.5,170.5 C 225.127,170.958 231.627,177.958 231,191.5C 226.507,203.825 218.007,207.659 205.5,203C 197.535,196.871 195.369,189.037 199,179.5C 201.917,174.637 206.083,171.637 211.5,170.5 Z"/></g>
|
||||
<g><path style="opacity:0.949" fill="#f95251" d="M 265.5,170.5 C 280.848,170.68 287.348,178.347 285,193.5C 279.06,204.76 270.227,207.593 258.5,202C 249.176,192.625 249.176,183.292 258.5,174C 260.925,172.787 263.259,171.621 265.5,170.5 Z"/></g>
|
||||
<g><path style="opacity:0.987" fill="#f95251" d="M 388.5,328.5 C 386.5,328.5 384.5,328.5 382.5,328.5C 285.992,327.966 189.325,326.966 92.5,325.5C 120.96,261.579 170.294,227.913 240.5,224.5C 299.804,226.887 345.304,252.887 377,302.5C 381.676,310.846 385.509,319.513 388.5,328.5 Z"/></g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
6
public/svgs/it-tools.svg
Normal file
6
public/svgs/it-tools.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="512px" height="512px" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g><path style="opacity:0.995" fill="#18a057" d="M 230.5,-0.5 C 247.5,-0.5 264.5,-0.5 281.5,-0.5C 290.12,3.28885 296.287,9.62218 300,18.5C 299.871,28.5889 300.704,38.4222 302.5,48C 326.717,53.6219 349.217,63.1219 370,76.5C 376.469,71.0323 382.636,65.199 388.5,59C 398.925,52.0844 409.591,51.7511 420.5,58C 432,68.1667 442.833,79 453,90.5C 459,100.5 459,110.5 453,120.5C 447.017,127.317 440.851,133.984 434.5,140.5C 447.998,161.821 457.498,184.821 463,209.5C 474.144,210.648 485.31,211.815 496.5,213C 503.496,216.822 508.496,222.322 511.5,229.5C 511.5,246.5 511.5,263.5 511.5,280.5C 508.841,288.664 503.508,294.497 495.5,298C 484.573,299.444 473.573,300.277 462.5,300.5C 457.369,325.4 448.036,348.566 434.5,370C 441.693,377.526 448.526,385.359 455,393.5C 458.667,402.825 458.001,411.825 453,420.5C 443.167,430.333 433.333,440.167 423.5,450C 414.895,457.565 405.229,459.232 394.5,455C 386.322,448.153 378.155,441.32 370,434.5C 348.501,447.828 325.334,457.161 300.5,462.5C 301.01,473.958 300.177,485.291 298,496.5C 294.006,504.342 287.839,509.342 279.5,511.5C 263.167,511.5 246.833,511.5 230.5,511.5C 222.336,508.841 216.503,503.508 213,495.5C 212,484.833 211,474.167 210,463.5C 198.505,459.749 187.005,455.915 175.5,452C 173.842,451.275 173.342,450.108 174,448.5C 192.195,429.971 210.695,411.805 229.5,394C 293.836,401.915 343.336,378.748 378,324.5C 408.025,263.569 400.691,207.569 356,156.5C 315.267,118.147 267.767,106.314 213.5,121C 164.374,138.458 132.874,172.291 119,222.5C 117.485,228.075 116.485,233.742 116,239.5C 115.261,253.906 115.594,268.239 117,282.5C 98.8333,300.667 80.6667,318.833 62.5,337C 61.552,337.483 60.552,337.649 59.5,337.5C 54.8294,325.486 51.1627,313.153 48.5,300.5C 37.4331,300.188 26.4331,299.355 15.5,298C 7.189,293.843 1.85567,287.343 -0.5,278.5C -0.5,262.833 -0.5,247.167 -0.5,231.5C 2.22646,223.578 7.22646,217.411 14.5,213C 25.7307,211.098 37.0641,210.265 48.5,210.5C 53.718,185.511 63.0513,162.178 76.5,140.5C 71.2189,133.716 65.3855,127.383 59,121.5C 51.8558,110.353 52.1892,99.353 60,88.5C 69.8333,78.6667 79.6667,68.8333 89.5,59C 98.3002,53.1199 107.633,52.1199 117.5,56C 125.629,62.4604 133.463,69.2938 141,76.5C 162.028,62.9274 184.861,53.4274 209.5,48C 210.93,37.2282 212.097,26.3949 213,15.5C 216.503,7.49214 222.336,2.15881 230.5,-0.5 Z"/></g>
|
||||
<g><path style="opacity:0.99" fill="#1d1d1d" d="M 52.5,511.5 C 46.5,511.5 40.5,511.5 34.5,511.5C 16.5,506.167 4.83333,494.5 -0.5,476.5C -0.5,470.167 -0.5,463.833 -0.5,457.5C 1.14258,451.876 3.64258,446.542 7,441.5C 55.9723,391.528 105.306,341.861 155,292.5C 141.187,245.876 152.02,206.042 187.5,173C 216.657,150.235 248.991,143.902 284.5,154C 289.202,158.922 290.702,164.755 289,171.5C 275,186.167 261,200.833 247,215.5C 234.564,236.922 238.73,254.422 259.5,268C 272.178,272.999 284.178,271.666 295.5,264C 309.596,248.899 324.596,234.899 340.5,222C 353.68,220.169 360.513,226.003 361,239.5C 365.554,290.573 345.387,328.073 300.5,352C 273.591,363.046 246.258,364.379 218.5,356C 169.472,405.361 120.139,454.361 70.5,503C 64.8398,506.712 58.8398,509.545 52.5,511.5 Z"/></g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
8
public/svgs/strapi.svg
Normal file
8
public/svgs/strapi.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 22.1867C0 11.7278 0 6.49832 3.24916 3.24916C6.49832 0 11.7278 0 22.1867 0H41.8133C52.2722 0 57.5017 0 60.7508 3.24916C64 6.49832 64 11.7278 64 22.1867V41.8133C64 52.2722 64 57.5017 60.7508 60.7508C57.5017 64 52.2722 64 41.8133 64H22.1867C11.7278 64 6.49832 64 3.24916 60.7508C0 57.5017 0 52.2722 0 41.8133V22.1867Z" fill="#4945FF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M44.156 19.4131H22.6094V30.4004H33.596V41.3864H44.5827V19.8398C44.5827 19.6041 44.3917 19.4131 44.156 19.4131Z" fill="white"/>
|
||||
<rect x="33.1719" y="30.4004" width="0.426667" height="0.426667" fill="white"/>
|
||||
<path d="M22.6172 30.4004H33.1772C33.4128 30.4004 33.6039 30.5914 33.6039 30.8271V41.3871H23.0439C22.8082 41.3871 22.6172 41.196 22.6172 40.9604V30.4004Z" fill="#9593FF"/>
|
||||
<path d="M33.6016 41.3867H44.5882L33.9657 52.0092C33.8314 52.1436 33.6016 52.0484 33.6016 51.8584V41.3867Z" fill="#9593FF"/>
|
||||
<path d="M22.6151 30.3998H12.1434C11.9534 30.3998 11.8582 30.17 11.9926 30.0356L22.6151 19.4131V30.3998Z" fill="#9593FF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -16,6 +16,7 @@ export function initializeTerminalComponent() {
|
||||
paused: false,
|
||||
MAX_PENDING_WRITES: 5,
|
||||
keepAliveInterval: null,
|
||||
reconnectInterval: null,
|
||||
|
||||
init() {
|
||||
this.setupTerminal();
|
||||
@@ -48,6 +49,9 @@ export function initializeTerminalComponent() {
|
||||
document.addEventListener(event, () => {
|
||||
this.checkIfProcessIsRunningAndKillIt();
|
||||
clearInterval(this.keepAliveInterval);
|
||||
if (this.reconnectInterval) {
|
||||
clearInterval(this.reconnectInterval);
|
||||
}
|
||||
}, { once: true });
|
||||
});
|
||||
|
||||
@@ -103,11 +107,27 @@ export function initializeTerminalComponent() {
|
||||
};
|
||||
this.socket.onclose = () => {
|
||||
console.log('WebSocket connection closed');
|
||||
|
||||
this.reconnect();
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
reconnect() {
|
||||
if (this.reconnectInterval) {
|
||||
clearInterval(this.reconnectInterval);
|
||||
}
|
||||
this.reconnectInterval = setInterval(() => {
|
||||
console.log('Attempting to reconnect...');
|
||||
this.initializeWebSocket();
|
||||
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
||||
console.log('Reconnected successfully');
|
||||
clearInterval(this.reconnectInterval);
|
||||
this.reconnectInterval = null;
|
||||
window.location.reload();
|
||||
}
|
||||
}, 2000);
|
||||
},
|
||||
|
||||
handleSocketMessage(event) {
|
||||
this.message = '(connection closed)';
|
||||
if (event.data === 'pty-ready') {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div class="w-96">
|
||||
<form action="/user/confirm-password" method="POST" class="flex flex-col gap-2">
|
||||
@csrf
|
||||
<x-forms.input required type="password" name="password" label="{{ __('input.password') }}" autofocus />
|
||||
<x-forms.input required type="password" name="password" label="{{ __('input.password') }}" />
|
||||
<x-forms.button type="submit">{{ __('auth.confirm_password') }}</x-forms.button>
|
||||
</form>
|
||||
@if ($errors->any())
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
@if (is_transactional_emails_active())
|
||||
<form action="/forgot-password" method="POST" class="flex flex-col gap-2">
|
||||
@csrf
|
||||
<x-forms.input required type="email" name="email" label="{{ __('input.email') }}" autofocus />
|
||||
<x-forms.input required type="email" name="email" label="{{ __('input.email') }}" />
|
||||
<x-forms.button type="submit">{{ __('auth.forgot_password_send_email') }}</x-forms.button>
|
||||
</form>
|
||||
@else
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
@csrf
|
||||
@env('local')
|
||||
<x-forms.input value="test@example.com" type="email" autocomplete="email" name="email"
|
||||
required label="{{ __('input.email') }}" autofocus />
|
||||
required label="{{ __('input.email') }}" />
|
||||
|
||||
<x-forms.input value="password" type="password" autocomplete="current-password" name="password"
|
||||
required label="{{ __('input.password') }}" />
|
||||
@@ -20,7 +20,7 @@
|
||||
</a>
|
||||
@else
|
||||
<x-forms.input type="email" name="email" autocomplete="email" required
|
||||
label="{{ __('input.email') }}" autofocus />
|
||||
label="{{ __('input.email') }}" />
|
||||
<x-forms.input type="password" name="password" autocomplete="current-password" required
|
||||
label="{{ __('input.password') }}" />
|
||||
<a href="/forgot-password" class="text-xs">
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
label="{{ __('input.email') }}" />
|
||||
<div class="flex flex-col gap-2">
|
||||
<x-forms.input required type="password" id="password" name="password"
|
||||
label="{{ __('input.password') }}" autofocus />
|
||||
label="{{ __('input.password') }}" />
|
||||
<x-forms.input required type="password" id="password_confirmation"
|
||||
name="password_confirmation" label="{{ __('input.password.again') }}" />
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<form action="/two-factor-challenge" method="POST" class="flex flex-col gap-2">
|
||||
@csrf
|
||||
<div>
|
||||
<x-forms.input type="number" name="code" autocomplete="one-time-code" label="{{ __('input.code') }}" autofocus />
|
||||
<x-forms.input type="number" name="code" autocomplete="one-time-code" label="{{ __('input.code') }}" />
|
||||
<div x-show="!showRecovery"
|
||||
class="pt-2 text-xs cursor-pointer hover:underline hover:dark:text-white"
|
||||
x-on:click="showRecovery = !showRecovery">Enter
|
||||
|
||||
@@ -222,12 +222,12 @@
|
||||
</template>
|
||||
@endforeach
|
||||
</ul>
|
||||
@if ($confirmWithText && $confirmationText)
|
||||
@if ($confirmWithText)
|
||||
<div class="mb-4">
|
||||
<h4 class="mb-2 text-lg font-semibold">Confirm Actions</h4>
|
||||
<p class="mb-2 text-sm">{{ $confirmationLabel }}</p>
|
||||
<div class="relative mb-2">
|
||||
<input autocomplete="off" type="text" x-model="confirmationText"
|
||||
<input type="text" x-model="confirmationText"
|
||||
class="p-2 pr-10 w-full text-black rounded cursor-text input" readonly>
|
||||
<button @click="copyConfirmationText()"
|
||||
class="absolute right-2 top-1/2 text-gray-500 transform -translate-y-1/2 hover:text-gray-700"
|
||||
@@ -255,7 +255,7 @@
|
||||
class="block mt-4 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ $shortConfirmationLabel }}
|
||||
</label>
|
||||
<input autocomplete="off" type="text" x-model="userConfirmationText"
|
||||
<input type="text" x-model="userConfirmationText"
|
||||
class="p-2 mt-1 w-full text-black rounded input">
|
||||
</div>
|
||||
@endif
|
||||
@@ -272,8 +272,10 @@
|
||||
class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Your Password
|
||||
</label>
|
||||
<input autocomplete="off" type="password" id="password-confirm" x-model="password" class="w-full input"
|
||||
placeholder="Enter your password">
|
||||
<form action="return false">
|
||||
<input type="password" id="password-confirm" x-model="password" class="w-full input"
|
||||
placeholder="Enter your password">
|
||||
</form>
|
||||
<p x-show="passwordError" x-text="passwordError" class="mt-1 text-sm text-red-500"></p>
|
||||
@error('password')
|
||||
<p class="mt-1 text-sm text-red-500">{{ $message }}</p>
|
||||
@@ -296,20 +298,13 @@
|
||||
</template>
|
||||
|
||||
<template x-if="step === 1">
|
||||
@if(isDev() && $submitAction === 'delete')
|
||||
<x-forms.button class="w-auto" isError
|
||||
@click="$wire.delete('hello')">
|
||||
<span x-text="step3ButtonText"></span>
|
||||
</x-forms.button>
|
||||
@else
|
||||
<x-forms.button @click="step++" class="w-auto" isError>
|
||||
<span x-text="step1ButtonText"></span>
|
||||
</x-forms.button>
|
||||
@endif
|
||||
<x-forms.button @click="step++" class="w-auto" isError>
|
||||
<span x-text="step1ButtonText"></span>
|
||||
</x-forms.button>
|
||||
</template>
|
||||
|
||||
<template x-if="step === 2">
|
||||
<x-forms.button x-bind:disabled="confirmationText !== '' && confirmWithText && userConfirmationText !== confirmationText"
|
||||
<x-forms.button x-bind:disabled="confirmWithText && userConfirmationText !== confirmationText"
|
||||
class="w-auto" isError
|
||||
@click="
|
||||
if (dispatchEvent) {
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
x-transition:enter-start="translate-y-full" x-transition:enter-end="translate-y-0"
|
||||
x-transition:leave="transition ease-in duration-300" x-transition:leave-start="translate-y-0"
|
||||
x-transition:leave-end="translate-y-full" x-init="setTimeout(() => { bannerVisible = true }, bannerVisibleAfter);"
|
||||
class="fixed bottom-0 right-0 w-full h-auto duration-300 ease-out sm:px-5 sm:pb-5 sm:w-[26rem] lg:w-full z-[999]"
|
||||
class="fixed bottom-0 right-0 w-full h-auto duration-300 ease-out sm:px-5 sm:pb-5 w-full z-[999]"
|
||||
x-cloak>
|
||||
<div
|
||||
class="flex flex-col items-center justify-between w-full h-full max-w-4xl p-6 mx-auto bg-white border shadow-lg lg:border-t dark:border-coolgray-300 dark:bg-coolgray-100 lg:p-8 lg:flex-row sm:rounded">
|
||||
class="flex items-center flex-col justify-between w-full h-full max-w-4xl p-6 mx-auto bg-white border shadow-lg lg:border-t dark:border-coolgray-300 dark:bg-coolgray-100 lg:p-8 lg:flex-row sm:rounded">
|
||||
<div
|
||||
class="flex flex-col items-start h-full pb-6 text-xs lg:items-center lg:flex-row lg:pb-0 lg:pr-6 lg:space-x-5 dark:text-neutral-300">
|
||||
@if (isset($icon))
|
||||
@@ -23,14 +23,12 @@
|
||||
<p class="">{{ $description }}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-end justify-end w-full pl-3 space-x-3 lg:flex-shrink-0 lg:w-auto">
|
||||
<button
|
||||
@if ($buttonText->attributes->whereStartsWith('@click')->first()) @click="bannerVisible=false;{{ $buttonText->attributes->get('@click') }}"
|
||||
@else
|
||||
@else
|
||||
@click="bannerVisible=false;" @endif
|
||||
class="inline-flex items-center justify-center flex-shrink-0 w-1/2 px-4 py-2 text-sm font-medium tracking-wide transition-colors duration-200 rounded-md bg-neutral-100 hover:bg-neutral-200 dark:bg-coolgray-200 lg:w-auto dark:text-neutral-200 dark:hover:bg-coolgray-300 focus:shadow-outline focus:outline-none">
|
||||
class="w-full px-4 py-2 text-sm font-medium tracking-wide transition-colors duration-200 rounded-md bg-neutral-100 hover:bg-neutral-200 dark:bg-coolgray-200 lg:w-auto dark:text-neutral-200 dark:hover:bg-coolgray-300 focus:shadow-outline focus:outline-none">
|
||||
{{ $buttonText }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
'status' => 'Restarting',
|
||||
'lastDeploymentInfo' => null,
|
||||
'lastDeploymentLink' => null,
|
||||
'noLoading' => false,
|
||||
])
|
||||
<div class="flex items-center">
|
||||
<x-loading wire:loading.delay.longer />
|
||||
@if (!$noLoading)
|
||||
<x-loading wire:loading.delay.longer />
|
||||
@endif
|
||||
<span wire:loading.remove.delay.longer class="flex items-center">
|
||||
<div class="badge badge-warning "></div>
|
||||
<div class="pl-2 pr-1 text-xs font-bold tracking-wider dark:text-warning" @if($lastDeploymentInfo) title="{{$lastDeploymentInfo}}" @endif>
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
'status' => 'Running',
|
||||
'lastDeploymentInfo' => null,
|
||||
'lastDeploymentLink' => null,
|
||||
'noLoading' => false,
|
||||
])
|
||||
<div class="flex items-center">
|
||||
<x-loading wire:loading.delay.longer />
|
||||
@if (!$noLoading)
|
||||
<x-loading wire:loading.delay.longer />
|
||||
@endif
|
||||
<span wire:loading.remove.delay.longer class="flex items-center">
|
||||
<div class="badge badge-success "></div>
|
||||
<div class="pl-2 pr-1 text-xs font-bold tracking-wider text-success" @if($lastDeploymentInfo) title="{{$lastDeploymentInfo}}" @endif>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
@props([
|
||||
'status' => 'Stopped',
|
||||
'noLoading' => false,
|
||||
])
|
||||
<div class="flex items-center">
|
||||
<x-loading wire:loading.delay.longer />
|
||||
@if (!$noLoading)
|
||||
<x-loading wire:loading.delay.longer />
|
||||
@endif
|
||||
<span wire:loading.remove.delay.longer class="flex items-center">
|
||||
<div class="badge badge-error "></div>
|
||||
<div class="pl-2 pr-1 text-xs font-bold tracking-wider text-error">{{ str($status)->before(':')->headline() }}</div>
|
||||
|
||||
@@ -32,21 +32,16 @@
|
||||
@if (!isCloud())
|
||||
<x-popup>
|
||||
<x-slot:title>
|
||||
<span class="font-bold text-left text-red-500">WARNING: </span>Realtime Error?!
|
||||
<span class="font-bold text-left text-red-500">WARNING: </span> Cannot connect to real-time service
|
||||
</x-slot:title>
|
||||
<x-slot:description>
|
||||
<span>Coolify could not connect to its real-time service.<br>This will cause unusual problems on the
|
||||
UI
|
||||
if
|
||||
not fixed! <br><br>
|
||||
<div>This will cause unusual problems on the
|
||||
UI! <br><br>
|
||||
Please ensure that you have opened the
|
||||
<a class="underline" href='https://coolify.io/docs/knowledge-base/server/firewall'
|
||||
target='_blank'>required ports</a>,
|
||||
check the
|
||||
related <a class="underline" href='https://coolify.io/docs/knowledge-base/cloudflare/tunnels'
|
||||
target='_blank'>documentation</a> or get
|
||||
target='_blank'>required ports</a> or get
|
||||
help on <a class="underline" href='https://coollabs.io/discord' target='_blank'>Discord</a>.
|
||||
</span>
|
||||
</div>
|
||||
</x-slot:description>
|
||||
<x-slot:button-text @click="disableRealtime()">
|
||||
Acknowledge & Disable This Popup
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<form class="flex flex-col w-full gap-2 rounded" wire:submit='submit'>
|
||||
<x-forms.input autofocus placeholder="Your Cool Project" id="name" label="Name" required />
|
||||
<x-forms.input placeholder="Your Cool Project" id="name" label="Name" required />
|
||||
<x-forms.input placeholder="This is my cool project everyone knows about" id="description" label="Description" />
|
||||
<div class="subtitle">New project will have a default production environment.</div>
|
||||
<x-forms.button type="submit" @click="slideOverOpen=false">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<form class="flex flex-col w-full gap-2 rounded" wire:submit='submit'>
|
||||
<x-forms.input autofocus placeholder="production" id="name" label="Name" required />
|
||||
<x-forms.input placeholder="production" id="name" label="Name" required />
|
||||
<x-forms.button type="submit" @click="slideOverOpen=false">
|
||||
Save
|
||||
</x-forms.button>
|
||||
|
||||
@@ -8,17 +8,14 @@
|
||||
<livewire:project.database.backup-now :backup="$backup" />
|
||||
@endif
|
||||
@if ($backup->database_id !== 0)
|
||||
<x-modal-confirmation
|
||||
title="Confirm Backup Schedule Deletion?"
|
||||
buttonTitle="Delete Backups and Schedule"
|
||||
isErrorButton
|
||||
submitAction="delete"
|
||||
:checkboxes="$checkboxes"
|
||||
:actions="['The selected backup schedule will be deleted.', 'Scheduled backups for this database will be stopped (if this is the only backup schedule for this database).']"
|
||||
confirmationText="{{ $backup->database->name }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Database Name of the scheduled backups below"
|
||||
shortConfirmationLabel="Database Name"
|
||||
/>
|
||||
<x-modal-confirmation title="Confirm Backup Schedule Deletion?" buttonTitle="Delete Backups and Schedule"
|
||||
isErrorButton submitAction="delete" :checkboxes="$checkboxes" :actions="[
|
||||
'The selected backup schedule will be deleted.',
|
||||
'Scheduled backups for this database will be stopped (if this is the only backup schedule for this database).',
|
||||
]"
|
||||
confirmationText="{{ $backup->database->name }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Database Name of the scheduled backups below"
|
||||
shortConfirmationLabel="Database Name" />
|
||||
@endif
|
||||
</div>
|
||||
<div class="w-48 pb-2">
|
||||
@@ -36,23 +33,39 @@
|
||||
</div>
|
||||
@endif
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex gap-2">
|
||||
<h3>Settings</h3>
|
||||
<div class="flex gap-2 flex-col ">
|
||||
@if ($backup->database_type === 'App\Models\StandalonePostgresql')
|
||||
<x-forms.input label="Databases To Backup"
|
||||
helper="Comma separated list of databases to backup. Empty will include the default one."
|
||||
id="backup.databases_to_backup" />
|
||||
<div class="w-48">
|
||||
<x-forms.checkbox label="Backup All Databases" id="backup.dump_all" instantSave />
|
||||
</div>
|
||||
@if (!$backup->dump_all)
|
||||
<x-forms.input label="Databases To Backup"
|
||||
helper="Comma separated list of databases to backup. Empty will include the default one."
|
||||
id="backup.databases_to_backup" />
|
||||
@endif
|
||||
@elseif($backup->database_type === 'App\Models\StandaloneMongodb')
|
||||
<x-forms.input label="Databases To Include"
|
||||
helper="A list of databases to backup. You can specify which collection(s) per database to exclude from the backup. Empty will include all databases and collections.<br><br>Example:<br><br>database1:collection1,collection2|database2:collection3,collection4<br><br> database1 will include all collections except collection1 and collection2. <br>database2 will include all collections except collection3 and collection4.<br><br>Another Example:<br><br>database1:collection1|database2<br><br> database1 will include all collections except collection1.<br>database2 will include ALL collections."
|
||||
id="backup.databases_to_backup" />
|
||||
@elseif($backup->database_type === 'App\Models\StandaloneMysql')
|
||||
<x-forms.input label="Databases To Backup"
|
||||
helper="Comma separated list of databases to backup. Empty will include the default one."
|
||||
id="backup.databases_to_backup" />
|
||||
<div class="w-48">
|
||||
<x-forms.checkbox label="Backup All Databases" id="backup.dump_all" instantSave />
|
||||
</div>
|
||||
@if (!$backup->dump_all)
|
||||
<x-forms.input label="Databases To Backup"
|
||||
helper="Comma separated list of databases to backup. Empty will include the default one."
|
||||
id="backup.databases_to_backup" />
|
||||
@endif
|
||||
@elseif($backup->database_type === 'App\Models\StandaloneMariadb')
|
||||
<x-forms.input label="Databases To Backup"
|
||||
helper="Comma separated list of databases to backup. Empty will include the default one."
|
||||
id="backup.databases_to_backup" />
|
||||
<div class="w-48">
|
||||
<x-forms.checkbox label="Backup All Databases" id="backup.dump_all" instantSave />
|
||||
</div>
|
||||
@if (!$backup->dump_all)
|
||||
<x-forms.input label="Databases To Backup"
|
||||
helper="Comma separated list of databases to backup. Empty will include the default one."
|
||||
id="backup.databases_to_backup" />
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
|
||||
@@ -45,20 +45,11 @@
|
||||
<x-forms.button class="dark:hover:bg-coolgray-400"
|
||||
x-on:click="download_file('{{ data_get($execution, 'id') }}')">Download</x-forms.button>
|
||||
@endif
|
||||
<x-modal-confirmation
|
||||
title="Confirm Backup Deletion?"
|
||||
buttonTitle="Delete"
|
||||
isErrorButton
|
||||
submitAction="deleteBackup({{ data_get($execution, 'id') }})"
|
||||
{{-- :checkboxes="$checkboxes" --}}
|
||||
:actions="[
|
||||
'This backup will be permanently deleted from local storage.'
|
||||
]"
|
||||
confirmationText="{{ data_get($execution, 'filename') }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Backup Filename below"
|
||||
shortConfirmationLabel="Backup Filename"
|
||||
step3ButtonText="Permanently Delete"
|
||||
/>
|
||||
<x-modal-confirmation title="Confirm Backup Deletion?" buttonTitle="Delete" isErrorButton
|
||||
submitAction="deleteBackup({{ data_get($execution, 'id') }})"
|
||||
:actions="['This backup will be permanently deleted from local storage.']" confirmationText="{{ data_get($execution, 'filename') }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Backup Filename below"
|
||||
shortConfirmationLabel="Backup Filename" step3ButtonText="Permanently Delete" />
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
<form class="flex flex-col w-full gap-2 rounded" wire:submit='submit'>
|
||||
<x-forms.input autofocus placeholder="0 0 * * * or daily" id="frequency"
|
||||
<x-forms.input placeholder="0 0 * * * or daily" id="frequency"
|
||||
helper="You can use every_minute, hourly, daily, weekly, monthly, yearly or a cron expression." label="Frequency"
|
||||
required />
|
||||
<x-forms.checkbox id="save_s3" label="Save to S3" />
|
||||
<x-forms.select id="selected_storage_id">
|
||||
@if ($s3s->count() === 0)
|
||||
<option value="0">No S3 Storages found.</option>
|
||||
@else
|
||||
@foreach ($s3s as $s3)
|
||||
<option value="{{ $s3->id }}">{{ $s3->name }}</option>
|
||||
@endforeach
|
||||
@if ($s3s->count() === 0)
|
||||
<div class="text-red-500">No validated S3 Storages found.</div>
|
||||
@else
|
||||
<x-forms.checkbox wire:model.live="save_s3" label="Save to S3" />
|
||||
@if ($save_s3)
|
||||
<x-forms.select id="selected_storage_id" label="Select a validated S3 storage">
|
||||
@foreach ($s3s as $s3)
|
||||
<option value="{{ $s3->id }}">{{ $s3->name }}</option>
|
||||
@endforeach
|
||||
</x-forms.select>
|
||||
@endif
|
||||
</x-forms.select>
|
||||
@endif
|
||||
<x-forms.button type="submit" @click="modalOpen=false">
|
||||
Save
|
||||
</x-forms.button>
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
<h3>Initialization scripts</h3>
|
||||
<x-modal-input buttonTitle="+ Add" title="New Init Script">
|
||||
<form class="flex flex-col w-full gap-2 rounded" wire:submit='save_new_init_script'>
|
||||
<x-forms.input autofocus placeholder="create_test_db.sql" id="new_filename" label="Filename"
|
||||
<x-forms.input placeholder="create_test_db.sql" id="new_filename" label="Filename"
|
||||
required />
|
||||
<x-forms.textarea rows="20" placeholder="CREATE DATABASE test;" id="new_content"
|
||||
label="Content" required />
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
class="items-center justify-center box">+ Add New Resource</a>
|
||||
@else
|
||||
<div x-data="searchComponent()">
|
||||
<x-forms.input autofocus placeholder="Search for name, fqdn..." x-model="search" id="null" />
|
||||
<x-forms.input placeholder="Search for name, fqdn..." x-model="search" id="null" />
|
||||
<div class="grid grid-cols-1 gap-4 pt-4 lg:grid-cols-2 xl:grid-cols-3">
|
||||
<template x-for="item in allFilteredItems" :key="item.uuid">
|
||||
<span>
|
||||
|
||||
@@ -149,6 +149,12 @@
|
||||
<div class="text-xs">{{ $database->status }}</div>
|
||||
</div>
|
||||
<div class="flex items-center px-4">
|
||||
@if ($database->isBackupSolutionAvailable())
|
||||
<a class="mx-4 text-xs font-bold hover:underline"
|
||||
href="{{ route('project.service.index', [...$parameters, 'stack_service_uuid' => $database->uuid]) }}#backups">
|
||||
Backups
|
||||
</a>
|
||||
@endif
|
||||
<a class="mx-4 text-xs font-bold hover:underline"
|
||||
href="{{ route('project.service.index', [...$parameters, 'stack_service_uuid' => $database->uuid]) }}">
|
||||
Settings
|
||||
|
||||
@@ -10,9 +10,7 @@
|
||||
<a class="menu-item" :class="activeTab === 'general' && 'menu-item-active'"
|
||||
@click.prevent="activeTab = 'general'; window.location.hash = 'general'; if(window.location.search) window.location.search = ''"
|
||||
href="#">General</a>
|
||||
@if (str($serviceDatabase?->databaseType())->contains('mysql') ||
|
||||
str($serviceDatabase?->databaseType())->contains('postgres') ||
|
||||
str($serviceDatabase?->databaseType())->contains('mariadb'))
|
||||
@if ($serviceDatabase?->isBackupSolutionAvailable())
|
||||
<a :class="activeTab === 'backups' && 'menu-item-active'" class="menu-item"
|
||||
@click.prevent="activeTab = 'backups'; window.location.hash = 'backups'" href="#">Backups</a>
|
||||
@endif
|
||||
@@ -28,22 +26,25 @@
|
||||
</div>
|
||||
@endisset
|
||||
@isset($serviceDatabase)
|
||||
<x-slot:title>
|
||||
{{ data_get_str($service, 'name')->limit(10) }} > {{ data_get_str($serviceDatabase, 'name')->limit(10) }} | Coolify
|
||||
</x-slot>
|
||||
<x-slot:title>
|
||||
{{ data_get_str($service, 'name')->limit(10) }} >
|
||||
{{ data_get_str($serviceDatabase, 'name')->limit(10) }} | Coolify
|
||||
</x-slot>
|
||||
<div x-cloak x-show="activeTab === 'general'" class="h-full">
|
||||
<livewire:project.service.database :database="$serviceDatabase" />
|
||||
</div>
|
||||
<div x-cloak x-show="activeTab === 'backups'">
|
||||
<div class="flex gap-2 ">
|
||||
<h2 class="pb-4">Scheduled Backups</h2>
|
||||
<x-modal-input buttonTitle="+ Add" title="New Scheduled Backup">
|
||||
<livewire:project.database.create-scheduled-backup :database="$serviceDatabase" :s3s="$s3s" />
|
||||
</x-modal-input>
|
||||
</div>
|
||||
<livewire:project.database.scheduled-backups :database="$serviceDatabase" />
|
||||
</div>
|
||||
@endisset
|
||||
</div>
|
||||
@if ($serviceDatabase->isBackupSolutionAvailable())
|
||||
<div x-cloak x-show="activeTab === 'backups'">
|
||||
<div class="flex gap-2 ">
|
||||
<h2 class="pb-4">Scheduled Backups</h2>
|
||||
<x-modal-input buttonTitle="+ Add" title="New Scheduled Backup">
|
||||
<livewire:project.database.create-scheduled-backup :database="$serviceDatabase" :s3s="$s3s" />
|
||||
</x-modal-input>
|
||||
</div>
|
||||
<livewire:project.database.scheduled-backups :database="$serviceDatabase" />
|
||||
@endif
|
||||
</div>
|
||||
@endisset
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,25 @@
|
||||
</nav>
|
||||
<div class="flex flex-wrap order-first gap-2 items-center sm:order-last">
|
||||
@if (str($service->status())->contains('running'))
|
||||
<button @click="$wire.dispatch('restartEvent')" class="gap-2 button">
|
||||
<x-dropdown>
|
||||
<x-slot:title>
|
||||
Advanced
|
||||
</x-slot>
|
||||
<div class="dropdown-item" @click="$wire.dispatch('pullAndRestartEvent')">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path
|
||||
d="M12.983 8.978c3.955 -.182 7.017 -1.446 7.017 -2.978c0 -1.657 -3.582 -3 -8 -3c-1.661 0 -3.204 .19 -4.483 .515m-2.783 1.228c-.471 .382 -.734 .808 -.734 1.257c0 1.22 1.944 2.271 4.734 2.74" />
|
||||
<path
|
||||
d="M4 6v6c0 1.657 3.582 3 8 3c.986 0 1.93 -.067 2.802 -.19m3.187 -.82c1.251 -.53 2.011 -1.228 2.011 -1.99v-6" />
|
||||
<path d="M4 12v6c0 1.657 3.582 3 8 3c3.217 0 5.991 -.712 7.261 -1.74m.739 -3.26v-4" />
|
||||
<path d="M3 3l18 18" />
|
||||
</svg>
|
||||
Pull Latest Images & Restart
|
||||
</div>
|
||||
</x-dropdown>
|
||||
<x-forms.button title="Restart" @click="$wire.dispatch('restartEvent')">
|
||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2">
|
||||
@@ -30,8 +48,8 @@
|
||||
<path d="M20 4v5h-5" />
|
||||
</g>
|
||||
</svg>
|
||||
Pull Latest Images & Restart
|
||||
</button>
|
||||
Restart
|
||||
</x-forms.button>
|
||||
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" submitAction="stop"
|
||||
:checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]" :confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue"
|
||||
step2ButtonText="Stop Service" :dispatchEvent="true" dispatchEventType="stopEvent">
|
||||
@@ -135,9 +153,13 @@
|
||||
$wire.$call('start');
|
||||
});
|
||||
$wire.$on('restartEvent', () => {
|
||||
$wire.$dispatch('info', 'Pulling new images.');
|
||||
$wire.$dispatch('info', 'Service restart in progress.');
|
||||
$wire.$call('restart');
|
||||
});
|
||||
$wire.$on('pullAndRestartEvent', () => {
|
||||
$wire.$dispatch('info', 'Pulling new images.');
|
||||
$wire.$call('pullAndRestartEvent');
|
||||
});
|
||||
$wire.on('imagePulled', () => {
|
||||
window.dispatchEvent(new CustomEvent('startservice'));
|
||||
$wire.$dispatch('info', 'Restarting service.');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<form class="flex flex-col w-full gap-2 rounded" wire:submit='submit'>
|
||||
<x-forms.input autofocus placeholder="NODE_ENV" id="key" label="Name" required />
|
||||
<x-forms.input placeholder="NODE_ENV" id="key" label="Name" required />
|
||||
<x-forms.textarea x-show="$wire.is_multiline === true" x-cloak id="value" label="Value" required />
|
||||
<x-forms.input x-show="$wire.is_multiline === false" x-cloak placeholder="production" id="value"
|
||||
x-bind:label="$wire.is_multiline === false && 'Value'" required />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<form class="flex flex-col w-full gap-2 rounded" wire:submit='submit'>
|
||||
<x-forms.input autofocus placeholder="Run cron" id="name" label="Name" />
|
||||
<x-forms.input placeholder="Run cron" id="name" label="Name" />
|
||||
<x-forms.input placeholder="php artisan schedule:run" id="command" label="Command" />
|
||||
<x-forms.input placeholder="0 0 * * * or daily"
|
||||
helper="You can use every_minute, hourly, daily, weekly, monthly, yearly or a cron expression." id="frequency"
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
<div class="pb-4">
|
||||
<h2>API Tokens</h2>
|
||||
@if (!$isApiEnabled)
|
||||
<div>API is disabled. If you want to use the API, please enable it in the <a href="{{ route('settings.index') }}" class="underline dark:text-white">Settings</a> menu.</div>
|
||||
<div>API is disabled. If you want to use the API, please enable it in the <a
|
||||
href="{{ route('settings.index') }}" class="underline dark:text-white">Settings</a> menu.</div>
|
||||
@else
|
||||
<div>Tokens are created with the current team as scope. You will only have access to this team's resources.
|
||||
</div>
|
||||
@@ -25,7 +26,7 @@
|
||||
@if ($permissions)
|
||||
@foreach ($permissions as $permission)
|
||||
@if ($permission === '*')
|
||||
<div>All (root/admin access), be careful!</div>
|
||||
<div>Root access, be careful!</div>
|
||||
@else
|
||||
<div>{{ $permission }}</div>
|
||||
@endif
|
||||
@@ -35,6 +36,7 @@
|
||||
</div>
|
||||
<h4>Token Permissions</h4>
|
||||
<div class="w-64">
|
||||
<x-forms.checkbox label="Root Access" wire:model.live="rootAccess"></x-forms.checkbox>
|
||||
<x-forms.checkbox label="Read-only" wire:model.live="readOnly"></x-forms.checkbox>
|
||||
<x-forms.checkbox label="View Sensitive Data" wire:model.live="viewSensitiveData"></x-forms.checkbox>
|
||||
</div>
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
</div>
|
||||
@endif
|
||||
@if (!$server->settings->is_cloudflare_tunnel && $server->isFunctional())
|
||||
<x-modal-input buttonTitle="Automated Configuration" title="Cloudflare Tunnels" class="w-full">
|
||||
<x-modal-input buttonTitle="Automated Configuration" title="Cloudflare Tunnels" class="w-full" :closeOutside="false">
|
||||
<livewire:server.configure-cloudflare-tunnels :server_id="$server->id" />
|
||||
</x-modal-input>
|
||||
@endif
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
@else
|
||||
<form class="flex flex-col w-full gap-2" wire:submit='submit'>
|
||||
<div class="flex w-full gap-2 flex-wrap sm:flex-nowrap">
|
||||
<x-forms.input autofocus id="name" label="Name" required />
|
||||
<x-forms.input id="name" label="Name" required />
|
||||
<x-forms.input id="description" label="Description" />
|
||||
</div>
|
||||
<div class="flex gap-2 flex-wrap sm:flex-nowrap">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<form wire:submit.prevent="addDynamicConfiguration" class="flex flex-col w-full gap-4">
|
||||
<x-forms.input autofocus id="fileName" label="Filename" required />
|
||||
<x-forms.input id="fileName" label="Filename" required />
|
||||
<x-forms.textarea allowTab useMonacoEditor id="value" label="Configuration" required rows="20" />
|
||||
<x-forms.button type="submit" @click="slideOverOpen=false">Save</x-forms.button>
|
||||
</form>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<div x-init="$wire.checkProxy()" class="flex gap-2">
|
||||
@if (data_get($server, 'proxy.status') === 'running')
|
||||
<x-status.running status="Proxy Running" />
|
||||
<x-status.running status="Proxy Running" noLoading />
|
||||
@elseif (data_get($server, 'proxy.status') === 'restarting')
|
||||
<x-status.restarting status="Proxy Restarting" />
|
||||
<x-status.restarting status="Proxy Restarting" noLoading />
|
||||
@elseif (data_get($server, 'proxy.force_stop'))
|
||||
<x-status.stopped status="Proxy Stopped" />
|
||||
<x-status.stopped status="Proxy Stopped" noLoading />
|
||||
@elseif (data_get($server, 'proxy.status') === 'exited')
|
||||
<x-status.stopped status="Proxy Exited" />
|
||||
<x-status.stopped status="Proxy Exited" noLoading />
|
||||
@else
|
||||
<x-status.stopped status="Proxy Not Running" />
|
||||
<x-status.stopped status="Proxy Not Running" noLoading />
|
||||
@endif
|
||||
<x-forms.button wire:click='checkProxy(true)'>Refresh</x-forms.button>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<x-forms.input required label="Name" id="name" />
|
||||
<x-forms.input label="Description" id="description" />
|
||||
</div>
|
||||
<x-forms.input required type="url" label="Endpoint" id="endpoint" />
|
||||
<x-forms.input required type="url" label="Endpoint" wire:model.blur="endpoint" />
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input required label="Bucket" id="bucket" />
|
||||
<x-forms.input required label="Region" id="region" />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<form class="flex flex-col w-full gap-2" wire:submit='submit'>
|
||||
<x-forms.input autofocus id="name" label="Name" required />
|
||||
<x-forms.input id="name" label="Name" required />
|
||||
<x-forms.input id="description" label="Description" />
|
||||
<x-forms.button type="submit">
|
||||
Continue
|
||||
|
||||
17
templates/compose/bitcoin-core.yaml
Normal file
17
templates/compose/bitcoin-core.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
# documentation: https://hub.docker.com/r/ruimarinho/bitcoin-core/
|
||||
# slogan: A self-hosted Bitcoin Core full node.
|
||||
# tags: cryptocurrency,node,blockchain,bitcoin
|
||||
# logo: svgs/bitcoin.svg
|
||||
|
||||
services:
|
||||
bitcoin-core:
|
||||
image: ruimarinho/bitcoin-core:latest
|
||||
environment:
|
||||
- BITCOIN_RPCUSER=${BITCOIN_RPCUSER:-bitcoinuser}
|
||||
- BITCOIN_RPCPASSWORD=${SERVICE_PASSWORD_PASSWORD64}
|
||||
- BITCOIN_NETWORK=${BITCOIN_NETWORK:-mainnet}
|
||||
- BITCOIN_PRINTTOCONSOLE=${BITCOIN_PRINTTOCONSOLE:-1}
|
||||
- BITCOIN_TXINDEX=${BITCOIN_TXINDEX:-1}
|
||||
volumes:
|
||||
- bitcoin_data:/home/bitcoin/.bitcoin
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# documentation: https://docs.docker.com/registry/
|
||||
# slogan: The Docker Registry is lets you distribute Docker images.
|
||||
# slogan: The Docker Registry lets you distribute Docker images.
|
||||
# tags: registry,images,docker
|
||||
# logo: svgs/docker-registry.png
|
||||
# port: 5000
|
||||
|
||||
21
templates/compose/homarr.yaml
Normal file
21
templates/compose/homarr.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
# documentation: https://homarr.dev
|
||||
# slogan: Homarr is a self-hosted homepage for your services.
|
||||
# tags: homarr,self-hosted,homepage
|
||||
# logo: svgs/homarr.svg
|
||||
# port: 7575
|
||||
|
||||
services:
|
||||
homarr:
|
||||
image: ghcr.io/ajnart/homarr:latest
|
||||
environment:
|
||||
- SERVICE_FQDN_HOMARR_7575
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./homarr/configs:/app/data/configs
|
||||
- ./homarr/icons:/app/public/icons
|
||||
- ./homarr/data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:7575"]
|
||||
interval: 5s
|
||||
timeout: 20s
|
||||
retries: 10
|
||||
18
templates/compose/it-tools.yaml
Normal file
18
templates/compose/it-tools.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
# documentation: https://github.com/corentinth/it-tools
|
||||
# slogan: IT Tools is a self-hosted solution for managing various IT tasks.
|
||||
# tags: it-tools,management,self-hosted
|
||||
# logo: svgs/it-tools.svg
|
||||
# port: 80
|
||||
|
||||
services:
|
||||
it-tools:
|
||||
image: corentinth/it-tools:latest
|
||||
environment:
|
||||
- SERVICE_FQDN_ITTOOLS_80
|
||||
volumes:
|
||||
- it-tools-data:/app/data
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://127.0.0.1:80"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
60
templates/compose/strapi.yaml
Normal file
60
templates/compose/strapi.yaml
Normal file
@@ -0,0 +1,60 @@
|
||||
# documentation: https://docs.strapi.io/
|
||||
# slogan: Open-source headless CMS to build powerful APIs with built-in content management.
|
||||
# tags: cms, headless, mysql, api
|
||||
# logo: svgs/strapi.svg
|
||||
# port: 1337
|
||||
|
||||
services:
|
||||
strapi:
|
||||
image: "elestio/strapi-development:latest"
|
||||
environment:
|
||||
- SERVICE_FQDN_STRAPI_1337
|
||||
- DATABASE_CLIENT=postgres
|
||||
- DATABASE_HOST=postgresql
|
||||
- DATABASE_PORT=5432
|
||||
- "DATABASE_NAME=${POSTGRESQL_DATABASE:-strapi}"
|
||||
- DATABASE_USERNAME=$SERVICE_USER_POSTGRESQL
|
||||
- DATABASE_PASSWORD=$SERVICE_PASSWORD_POSTGRESQL
|
||||
- JWT_SECRET=$SERVICE_BASE64_64_SECRET
|
||||
- ADMIN_JWT_SECRET=$SERVICE_BASE64_64_SECRET
|
||||
- APP_KEYS=$SERVICE_BASE64_64_KEY
|
||||
- STRAPI_TELEMETRY_DISABLED=${STRAPI_TELEMETRY_DISABLED:-true}
|
||||
- STRAPI_LICENSE=${STRAPI_LICENSE}
|
||||
- NODE_ENV=${NODE_ENV:-development}
|
||||
- BROWSER=${BROWSER:-true}
|
||||
- STRAPI_PLUGIN_I18N_INIT_LOCALE_CODE=${STRAPI_PLUGIN_I18N_INIT_LOCALE_CODE:-en}
|
||||
- STRAPI_ENFORCE_SOURCEMAPS=${STRAPI_ENFORCE_SOURCEMAPS:-false}
|
||||
- FAST_REFRESH=${FAST_REFRESH:-true}
|
||||
volumes:
|
||||
- "strapi-config:/opt/app/config"
|
||||
- "strapi-src:/opt/app/src"
|
||||
- "strapi-uploads:/opt/app/public/uploads"
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- wget
|
||||
- "-q"
|
||||
- "--spider"
|
||||
- "http://127.0.0.1:1337/"
|
||||
interval: 5s
|
||||
timeout: 20s
|
||||
retries: 10
|
||||
depends_on:
|
||||
postgresql:
|
||||
condition: service_healthy
|
||||
postgresql:
|
||||
image: "elestio/postgres:latest"
|
||||
environment:
|
||||
- "POSTGRES_DB=${POSTGRESQL_DATABASE:-strapi}"
|
||||
- POSTGRES_USER=$SERVICE_USER_POSTGRESQL
|
||||
- POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRESQL
|
||||
- PGDATA=/var/lib/postgresql/data
|
||||
volumes:
|
||||
- "strapi-postgresql-data:/var/lib/postgresql/data"
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD-SHELL
|
||||
- "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"
|
||||
interval: 5s
|
||||
timeout: 20s
|
||||
retries: 10
|
||||
File diff suppressed because one or more lines are too long
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"coolify": {
|
||||
"v4": {
|
||||
"version": "4.0.0-beta.348"
|
||||
"version": "4.0.0-beta.354"
|
||||
},
|
||||
"nightly": {
|
||||
"version": "4.0.0-beta.349"
|
||||
"version": "4.0.0-beta.355"
|
||||
},
|
||||
"helper": {
|
||||
"version": "1.0.1"
|
||||
},
|
||||
"realtime": {
|
||||
"version": "1.0.2"
|
||||
"version": "1.0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user