Compare commits

...

93 Commits

Author SHA1 Message Date
Andras Bacsai
40a239ddda Merge pull request #1550 from coollabsio/next
refactor: gitlab manual webhooks
2023-12-15 14:19:49 +01:00
Andras Bacsai
99d07981cf fix 2023-12-15 14:19:29 +01:00
Andras Bacsai
b3ee6b7144 fix: add debug output to gitlab webhooks 2023-12-15 14:17:53 +01:00
Andras Bacsai
468ad7d904 fix: no action in webhooks 2023-12-15 14:09:14 +01:00
Andras Bacsai
f1aa97e374 Merge pull request #1548 from coollabsio/next
fix: compose domains & links
2023-12-15 11:01:03 +01:00
Andras Bacsai
3b6d3343c7 fix: domains for compose bp 2023-12-15 11:00:51 +01:00
Andras Bacsai
ab2f9f073f Merge pull request #1546 from stooit/fix/docker-compose-multidomain
fix: Multiple domain links in docker compose applications.
2023-12-15 10:39:01 +01:00
Andras Bacsai
67131152cc fix: reset domains on compose file change 2023-12-15 10:37:45 +01:00
Andras Bacsai
3bda289428 fix: ui for adding new destination 2023-12-15 10:24:02 +01:00
Andras Bacsai
fadfa0ad8e Merge pull request #1547 from coollabsio/next
fix: server checking status
2023-12-15 10:01:58 +01:00
Andras Bacsai
11a957c6c9 fix: server checking status 2023-12-15 10:01:14 +01:00
Stuart Rowlands
b46de99af9 Fixes multi-domain links in docker compose applications. 2023-12-14 18:16:17 -08:00
Andras Bacsai
03420076c9 Merge pull request #1545 from coollabsio/next
v4.0.0-beta.163
2023-12-14 15:41:16 +01:00
Andras Bacsai
549446abdf fix: handle other types of generated values 2023-12-14 15:34:05 +01:00
Andras Bacsai
06ab2145ca fix: improve server status check times 2023-12-14 15:33:46 +01:00
Andras Bacsai
5d088e530e version++ 2023-12-14 15:33:34 +01:00
Andras Bacsai
123e6eddd7 fix: only check server status in container status job 2023-12-14 15:33:25 +01:00
Andras Bacsai
2c17e431ac Merge pull request #1544 from coollabsio/next
fix: backup executions view
2023-12-14 15:01:35 +01:00
Andras Bacsai
fe6073ba7d fix: backup executions view 2023-12-14 15:01:19 +01:00
Andras Bacsai
8a9ad04744 Merge pull request #1540 from coollabsio/next
v4.0.0-beta.162
2023-12-14 14:58:30 +01:00
Andras Bacsai
75d1ec4f42 feat: pull latest images for services 2023-12-14 14:50:38 +01:00
Andras Bacsai
a0abde8652 ui: add image name to service stack + better options visibility 2023-12-14 14:24:54 +01:00
Andras Bacsai
db13dd9304 fix: revert random container job delay 2023-12-13 15:40:57 +01:00
Andras Bacsai
638bcf9732 update 2023-12-13 15:34:33 +01:00
Andras Bacsai
b06b465ffa fix: add catch all route 2023-12-13 15:29:45 +01:00
Andras Bacsai
02c8b9f471 fix: password reset / invitation link requests 2023-12-13 15:22:37 +01:00
Andras Bacsai
1ff1664b6c fix: copy invitation 2023-12-13 14:44:11 +01:00
Andras Bacsai
52d84c5e9e refactor: clone project 2023-12-13 14:22:23 +01:00
Andras Bacsai
e0289e2949 feat: randomly sleep between executions 2023-12-13 12:35:56 +01:00
Andras Bacsai
ff8d8371ad fix: check queued deployments as well 2023-12-13 12:13:20 +01:00
Andras Bacsai
69343f974a soft delete models 2023-12-13 12:08:12 +01:00
Andras Bacsai
2dc175be63 fix: null notify 2023-12-13 12:01:27 +01:00
Andras Bacsai
d93bf97919 cleanup on start 2023-12-13 12:01:21 +01:00
Andras Bacsai
4ea8916d53 fix: update Coolify script 2023-12-13 11:55:08 +01:00
Andras Bacsai
f7fca69a23 update sentry key 2023-12-13 11:53:50 +01:00
Andras Bacsai
f954ee15c3 fix: init script echos 2023-12-13 11:53:01 +01:00
Andras Bacsai
3c54e01d87 improve more 2023-12-13 11:35:53 +01:00
Andras Bacsai
00d708610d improve local dev + contribution guide 2023-12-13 11:12:53 +01:00
Andras Bacsai
f3b04c1ef9 refactor: custom labels 2023-12-13 09:23:27 +01:00
Andras Bacsai
6b751f965b Merge pull request #1539 from coollabsio/next
v4.0.0-beta.161
2023-12-12 16:47:56 +01:00
Andras Bacsai
d3c9894479 fix: labels 2023-12-12 16:45:46 +01:00
Andras Bacsai
f68aace445 fix: non-ascii chars in labels 2023-12-12 16:34:05 +01:00
Andras Bacsai
f042c70b3c fix: labelling 2023-12-12 15:48:51 +01:00
Andras Bacsai
2116d79aad Merge pull request #1538 from coollabsio/next
v4.0.0-beta.160
2023-12-12 14:32:24 +01:00
Andras Bacsai
4bc63e283c fix: service env variable ovewritten if it has a default value 2023-12-12 14:28:11 +01:00
Andras Bacsai
d5804f99c2 Merge pull request #1537 from coollabsio/next
v4.0.0-beta.159
2023-12-12 13:25:40 +01:00
Andras Bacsai
dfc353ce54 fix: ignore if dynamic config could not be set 2023-12-12 13:20:26 +01:00
Andras Bacsai
8f65ddb754 Merge pull request #1536 from coollabsio/next
v4.0.0-beta.158
2023-12-12 12:41:46 +01:00
Andras Bacsai
29e750f0b2 hmm 2023-12-12 12:31:29 +01:00
Andras Bacsai
b24661b876 fix 2023-12-12 12:13:14 +01:00
Andras Bacsai
bbbd605f32 fix: comma in traefik custom labels 2023-12-12 12:10:46 +01:00
Andras Bacsai
ff13cb4e26 fix: init 2023-12-12 12:01:53 +01:00
Andras Bacsai
5cb572b6a5 fix: run init command after production seeder 2023-12-12 11:54:10 +01:00
Andras Bacsai
d8b97e06cf wip: fix for comma in labels 2023-12-11 23:34:18 +01:00
Andras Bacsai
becb4df950 Merge pull request #1535 from coollabsio/next
fix: is autoupdate not null
2023-12-11 23:27:12 +01:00
Andras Bacsai
20dc2b47fe fix: is autoupdate not null 2023-12-11 23:26:49 +01:00
Andras Bacsai
67df166c20 Merge pull request #1534 from coollabsio/next
v4.0.0-beta.157
2023-12-11 23:20:05 +01:00
Andras Bacsai
87c3d0048c fix 2023-12-11 21:43:53 +01:00
Andras Bacsai
1c71ac78e2 fix 2023-12-11 21:35:09 +01:00
Andras Bacsai
41181cac12 asdasdasd time to sleep 2023-12-11 21:30:13 +01:00
Andras Bacsai
41b6df0e6e fix 2023-12-11 21:26:21 +01:00
Andras Bacsai
4f3f98be0a fix 2023-12-11 21:26:18 +01:00
Andras Bacsai
7a97a4b69c fixes 2023-12-11 21:23:33 +01:00
Andras Bacsai
6ae87466ca fix: only allow to modify in .env file if AUTOUPDATE is set 2023-12-11 21:19:45 +01:00
Andras Bacsai
5159d47159 fix install script 2023-12-11 21:07:40 +01:00
Andras Bacsai
0138d04080 Merge pull request #1525 from American-Cloud/main
fix: Install script
2023-12-11 20:57:35 +01:00
Andras Bacsai
c803768e5f set autoupdate 2023-12-11 20:55:58 +01:00
Andras Bacsai
60c8e0d625 feat: disable autoupdate 2023-12-11 20:40:05 +01:00
Andras Bacsai
dd99ad0af8 fix fox 2023-12-11 20:29:40 +01:00
Andras Bacsai
24a1f02af5 version++ 2023-12-11 20:27:49 +01:00
Andras Bacsai
601a1e128e fixes 2023-12-11 20:22:31 +01:00
Andras Bacsai
ccb9769e67 finally works? 2023-12-11 20:13:41 +01:00
Andras Bacsai
d79da996d3 fix 2023-12-11 20:01:54 +01:00
Andras Bacsai
4f800f5331 hmm, why 2023-12-11 19:46:46 +01:00
Andras Bacsai
a19a58338c debug on 2023-12-11 19:39:27 +01:00
Andras Bacsai
8a80dbd5d8 fix 2023-12-11 19:36:44 +01:00
Andras Bacsai
ec5cca7b3e feat: autoupdate env during seed 2023-12-11 19:34:23 +01:00
Andras Bacsai
ce721c1764 fix 2023-12-11 19:30:37 +01:00
Andras Bacsai
f4d7c4f942 update 2023-12-11 19:25:35 +01:00
Andras Bacsai
40716550ec fix 2023-12-11 19:16:17 +01:00
Andras Bacsai
f0ee26cd86 fix realtimePort 2023-12-11 19:11:29 +01:00
Andras Bacsai
423dfc6280 fix 2023-12-11 19:02:06 +01:00
Andras Bacsai
6d9a66ff1b fix: websocket 2023-12-11 18:48:00 +01:00
Andras Bacsai
17c8872130 fix: realtime connection?! 2023-12-11 18:06:29 +01:00
Andras Bacsai
3ffa2b6b8d fix: add ipv6 2023-12-11 16:34:36 +01:00
Andras Bacsai
35134f2327 fix: pusher host 2023-12-11 16:32:41 +01:00
Andras Bacsai
8ed4b540e1 Merge pull request #1533 from coollabsio/next
v4.0.0-.beta.156
2023-12-11 15:24:15 +01:00
Andras Bacsai
47202a7951 fix: db status check 2023-12-11 15:23:41 +01:00
Andras Bacsai
fe6e76ad0d fix 2023-12-11 15:09:36 +01:00
Andras Bacsai
e9920f05f5 fix: proxy logs 2023-12-11 15:08:40 +01:00
Andras Bacsai
57a39f12bb version++ 2023-12-11 14:56:11 +01:00
Jason Hollis
c1f6bf41f5 fix: Install script parse version
* Allow version to be passed with v or V at the beginning of
      version.  This allows users to pass along the actual github tagged
      version as it is listed on github.
    * Linting updates
2023-12-07 16:58:17 -05:00
Jason Hollis
9df0a2e545 fix: Better handling of errors with install script 2023-12-07 12:08:43 -05:00
81 changed files with 937 additions and 539 deletions

View File

@@ -22,8 +22,6 @@ You can ask for guidance anytime on our
- Run `spin up` - You can notice that errors will be thrown. Don't worry. - Run `spin up` - You can notice that errors will be thrown. Don't worry.
- If you see weird permission errors, especially on Mac, run `sudo spin up` instead. - If you see weird permission errors, especially on Mac, run `sudo spin up` instead.
- Run `./scripts/run setup:dev` - This will generate a secret key for you, delete any existing database layouts, migrate database to the new layout, and seed your database.
### 4) Start development ### 4) Start development
You can login your Coolify instance at `localhost:8000` with `test@example.com` and `password`. You can login your Coolify instance at `localhost:8000` with `test@example.com` and `password`.
@@ -31,7 +29,6 @@ Your horizon (Laravel scheduler): `localhost:8000/horizon` - Only reachable if y
Mails are caught by Mailpit: `localhost:8025` Mails are caught by Mailpit: `localhost:8025`
## New Service Contribution ## New Service Contribution
Check out the docs [here](https://coolify.io/docs/how-to-add-a-service). Check out the docs [here](https://coolify.io/docs/how-to-add-a-service).

View File

@@ -18,7 +18,7 @@ class UpdateCoolify
try { try {
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();
ray('Running InstanceAutoUpdateJob'); ray('Running InstanceAutoUpdateJob');
$this->server = Server::find(0)->first(); $this->server = Server::find(0);
if (!$this->server) { if (!$this->server) {
return; return;
} }

View File

@@ -11,6 +11,7 @@ class StartService
use AsAction; use AsAction;
public function handle(Service $service) public function handle(Service $service)
{ {
ray('Starting service: ' . $service->name);
$network = $service->destination->network; $network = $service->destination->network;
$service->saveComposeConfigs(); $service->saveComposeConfigs();
$commands[] = "cd " . $service->workdir(); $commands[] = "cd " . $service->workdir();

View File

@@ -10,6 +10,7 @@ class StopService
use AsAction; use AsAction;
public function handle(Service $service) public function handle(Service $service)
{ {
ray('Stopping service: ' . $service->name);
$applications = $service->applications()->get(); $applications = $service->applications()->get();
foreach ($applications as $application) { foreach ($applications as $application) {
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server); instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server);

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Actions\Shared;
use App\Models\Service;
use Lorisleiva\Actions\Concerns\AsAction;
class PullImage
{
use AsAction;
public function handle(Service $resource)
{
$resource->saveComposeConfigs();
$commands[] = "cd " . $resource->workdir();
$commands[] = "echo 'Saved configuration files to {$resource->workdir()}.'";
$commands[] = "docker compose pull";
$server = data_get($resource, 'server');
if (!$server) return;
instant_remote_process($commands, $resource->server);
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Console\Commands;
use App\Models\InstanceSettings;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Process;
class Dev extends Command
{
protected $signature = 'dev:init';
protected $description = 'Init the app in dev mode';
public function handle()
{
// Generate APP_KEY if not exists
if (empty(env('APP_KEY'))) {
echo "Generating APP_KEY.\n";
Artisan::call('key:generate');
}
// Seed database if it's empty
$settings = InstanceSettings::find(0);
if (!$settings) {
echo "Initializing instance, seeding database.\n";
Artisan::call('migrate --seed');
} else {
echo "Instance already initialized.\n";
}
// Set permissions
Process::run(['chmod', '-R', 'o+rwx', '.']);
}
}

View File

@@ -18,7 +18,6 @@ use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis; use App\Models\StandaloneRedis;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
class Init extends Command class Init extends Command
{ {
@@ -36,6 +35,21 @@ class Init extends Command
} }
$this->cleanup_in_progress_application_deployments(); $this->cleanup_in_progress_application_deployments();
$this->cleanup_stucked_helper_containers(); $this->cleanup_stucked_helper_containers();
try {
setup_dynamic_configuration();
} catch (\Throwable $e) {
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
}
$settings = InstanceSettings::get();
if (!is_null(env('AUTOUPDATE', null))) {
if (env('AUTOUPDATE') == true) {
$settings->update(['is_auto_update_enabled' => true]);
} else {
$settings->update(['is_auto_update_enabled' => false]);
}
}
} }
private function cleanup_stucked_helper_containers() private function cleanup_stucked_helper_containers()
{ {
@@ -85,7 +99,7 @@ class Init extends Command
// Cleanup any failed deployments // Cleanup any failed deployments
try { try {
$halted_deployments = ApplicationDeploymentQueue::where('status', '==', 'in_progress')->get(); $halted_deployments = ApplicationDeploymentQueue::where('status', '==', ApplicationDeploymentStatus::IN_PROGRESS)->where('status', '==', ApplicationDeploymentStatus::QUEUED)->get();
foreach ($halted_deployments as $deployment) { foreach ($halted_deployments as $deployment) {
$deployment->status = ApplicationDeploymentStatus::FAILED->value; $deployment->status = ApplicationDeploymentStatus::FAILED->value;
$deployment->save(); $deployment->save();
@@ -101,16 +115,19 @@ class Init extends Command
$applications = Application::all(); $applications = Application::all();
foreach ($applications as $application) { foreach ($applications as $application) {
if (!data_get($application, 'environment')) { if (!data_get($application, 'environment')) {
echo 'Application without environment' . $application->name . 'deleting\n'; echo 'Application without environment: ' . $application->name . ' soft deleting\n';
$application->delete(); $application->delete();
continue;
} }
if (!$application->destination()) { if (!$application->destination()) {
echo 'Application without destination' . $application->name . 'deleting\n'; echo 'Application without destination: ' . $application->name . ' soft deleting\n';
$application->delete(); $application->delete();
continue;
} }
if (!data_get($application, 'destination.server')) { if (!data_get($application, 'destination.server')) {
echo 'Application without server' . $application->name . 'deleting\n'; echo 'Application without server: ' . $application->name . ' soft deleting\n';
$application->delete(); $application->delete();
continue;
} }
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -120,16 +137,19 @@ class Init extends Command
$postgresqls = StandalonePostgresql::all(); $postgresqls = StandalonePostgresql::all();
foreach ($postgresqls as $postgresql) { foreach ($postgresqls as $postgresql) {
if (!data_get($postgresql, 'environment')) { if (!data_get($postgresql, 'environment')) {
echo 'Postgresql without environment' . $postgresql->name . 'deleting\n'; echo 'Postgresql without environment: ' . $postgresql->name . ' soft deleting\n';
$postgresql->delete(); $postgresql->delete();
continue;
} }
if (!$postgresql->destination()) { if (!$postgresql->destination()) {
echo 'Postgresql without destination' . $postgresql->name . 'deleting\n'; echo 'Postgresql without destination: ' . $postgresql->name . ' soft deleting\n';
$postgresql->delete(); $postgresql->delete();
continue;
} }
if (!data_get($postgresql, 'destination.server')) { if (!data_get($postgresql, 'destination.server')) {
echo 'Postgresql without server' . $postgresql->name . 'deleting\n'; echo 'Postgresql without server: ' . $postgresql->name . ' soft deleting\n';
$postgresql->delete(); $postgresql->delete();
continue;
} }
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -139,16 +159,19 @@ class Init extends Command
$redis = StandaloneRedis::all(); $redis = StandaloneRedis::all();
foreach ($redis as $redis) { foreach ($redis as $redis) {
if (!data_get($redis, 'environment')) { if (!data_get($redis, 'environment')) {
echo 'Redis without environment' . $redis->name . 'deleting\n'; echo 'Redis without environment: ' . $redis->name . ' soft deleting\n';
$redis->delete(); $redis->delete();
continue;
} }
if (!$redis->destination()) { if (!$redis->destination()) {
echo 'Redis without destination' . $redis->name . 'deleting\n'; echo 'Redis without destination: ' . $redis->name . ' soft deleting\n';
$redis->delete(); $redis->delete();
continue;
} }
if (!data_get($redis, 'destination.server')) { if (!data_get($redis, 'destination.server')) {
echo 'Redis without server' . $redis->name . 'deleting\n'; echo 'Redis without server: ' . $redis->name . ' soft deleting\n';
$redis->delete(); $redis->delete();
continue;
} }
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -159,16 +182,19 @@ class Init extends Command
$mongodbs = StandaloneMongodb::all(); $mongodbs = StandaloneMongodb::all();
foreach ($mongodbs as $mongodb) { foreach ($mongodbs as $mongodb) {
if (!data_get($mongodb, 'environment')) { if (!data_get($mongodb, 'environment')) {
echo 'Mongodb without environment' . $mongodb->name . 'deleting\n'; echo 'Mongodb without environment: ' . $mongodb->name . ' soft deleting\n';
$mongodb->delete(); $mongodb->delete();
continue;
} }
if (!$mongodb->destination()) { if (!$mongodb->destination()) {
echo 'Mongodb without destination' . $mongodb->name . 'deleting\n'; echo 'Mongodb without destination: ' . $mongodb->name . ' soft deleting\n';
$mongodb->delete(); $mongodb->delete();
continue;
} }
if (!data_get($mongodb, 'destination.server')) { if (!data_get($mongodb, 'destination.server')) {
echo 'Mongodb without server' . $mongodb->name . 'deleting\n'; echo 'Mongodb without server: ' . $mongodb->name . ' soft deleting\n';
$mongodb->delete(); $mongodb->delete();
continue;
} }
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -179,16 +205,19 @@ class Init extends Command
$mysqls = StandaloneMysql::all(); $mysqls = StandaloneMysql::all();
foreach ($mysqls as $mysql) { foreach ($mysqls as $mysql) {
if (!data_get($mysql, 'environment')) { if (!data_get($mysql, 'environment')) {
echo 'Mysql without environment' . $mysql->name . 'deleting\n'; echo 'Mysql without environment: ' . $mysql->name . ' soft deleting\n';
$mysql->delete(); $mysql->delete();
continue;
} }
if (!$mysql->destination()) { if (!$mysql->destination()) {
echo 'Mysql without destination' . $mysql->name . 'deleting\n'; echo 'Mysql without destination: ' . $mysql->name . ' soft deleting\n';
$mysql->delete(); $mysql->delete();
continue;
} }
if (!data_get($mysql, 'destination.server')) { if (!data_get($mysql, 'destination.server')) {
echo 'Mysql without server' . $mysql->name . 'deleting\n'; echo 'Mysql without server: ' . $mysql->name . ' soft deleting\n';
$mysql->delete(); $mysql->delete();
continue;
} }
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -199,16 +228,19 @@ class Init extends Command
$mariadbs = StandaloneMariadb::all(); $mariadbs = StandaloneMariadb::all();
foreach ($mariadbs as $mariadb) { foreach ($mariadbs as $mariadb) {
if (!data_get($mariadb, 'environment')) { if (!data_get($mariadb, 'environment')) {
echo 'Mariadb without environment' . $mariadb->name . 'deleting\n'; echo 'Mariadb without environment: ' . $mariadb->name . ' soft deleting\n';
$mariadb->delete(); $mariadb->delete();
continue;
} }
if (!$mariadb->destination()) { if (!$mariadb->destination()) {
echo 'Mariadb without destination' . $mariadb->name . 'deleting\n'; echo 'Mariadb without destination: ' . $mariadb->name . ' soft deleting\n';
$mariadb->delete(); $mariadb->delete();
continue;
} }
if (!data_get($mariadb, 'destination.server')) { if (!data_get($mariadb, 'destination.server')) {
echo 'Mariadb without server' . $mariadb->name . 'deleting\n'; echo 'Mariadb without server: ' . $mariadb->name . ' soft deleting\n';
$mariadb->delete(); $mariadb->delete();
continue;
} }
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -219,16 +251,19 @@ class Init extends Command
$services = Service::all(); $services = Service::all();
foreach ($services as $service) { foreach ($services as $service) {
if (!data_get($service, 'environment')) { if (!data_get($service, 'environment')) {
echo 'Service without environment' . $service->name . 'deleting\n'; echo 'Service without environment: ' . $service->name . ' soft deleting\n';
$service->delete(); $service->delete();
continue;
} }
if (!$service->destination()) { if (!$service->destination()) {
echo 'Service without destination' . $service->name . 'deleting\n'; echo 'Service without destination: ' . $service->name . ' soft deleting\n';
$service->delete(); $service->delete();
continue;
} }
if (!data_get($service, 'server')) { if (!data_get($service, 'server')) {
echo 'Service without server' . $service->name . 'deleting\n'; echo 'Service without server: ' . $service->name . ' soft deleting\n';
$service->delete(); $service->delete();
continue;
} }
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -238,8 +273,9 @@ class Init extends Command
$serviceApplications = ServiceApplication::all(); $serviceApplications = ServiceApplication::all();
foreach ($serviceApplications as $service) { foreach ($serviceApplications as $service) {
if (!data_get($service, 'service')) { if (!data_get($service, 'service')) {
echo 'ServiceApplication without service' . $service->name . 'deleting\n'; echo 'ServiceApplication without service: ' . $service->name . ' soft deleting\n';
$service->delete(); $service->delete();
continue;
} }
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -249,8 +285,9 @@ class Init extends Command
$serviceDatabases = ServiceDatabase::all(); $serviceDatabases = ServiceDatabase::all();
foreach ($serviceDatabases as $service) { foreach ($serviceDatabases as $service) {
if (!data_get($service, 'service')) { if (!data_get($service, 'service')) {
echo 'ServiceDatabase without service' . $service->name . 'deleting\n'; echo 'ServiceDatabase without service: ' . $service->name . ' soft deleting\n';
$service->delete(); $service->delete();
continue;
} }
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

@@ -39,7 +39,7 @@ class Controller extends BaseController
} else { } else {
$team = $user->teams()->first(); $team = $user->teams()->first();
} }
if (is_null(data_get($user, 'email_verified_at'))){ if (is_null(data_get($user, 'email_verified_at'))) {
$user->email_verified_at = now(); $user->email_verified_at = now();
$user->save(); $user->save();
} }
@@ -79,11 +79,11 @@ class Controller extends BaseController
if (isInstanceAdmin()) { if (isInstanceAdmin()) {
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();
$database = StandalonePostgresql::whereName('coolify-db')->first(); $database = StandalonePostgresql::whereName('coolify-db')->first();
if ($database) {
if ($database->status !== 'running') { if ($database->status !== 'running') {
$database->status = 'running'; $database->status = 'running';
$database->save(); $database->save();
} }
if ($database) {
$s3s = S3Storage::whereTeamId(0)->get(); $s3s = S3Storage::whereTeamId(0)->get();
} }
return view('settings.configuration', [ return view('settings.configuration', [
@@ -137,17 +137,29 @@ class Controller extends BaseController
public function acceptInvitation() public function acceptInvitation()
{ {
try { try {
$invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail(); $resetPassword = request()->query('reset-password');
$invitationUuid = request()->route('uuid');
$invitation = TeamInvitation::whereUuid($invitationUuid)->firstOrFail();
$user = User::whereEmail($invitation->email)->firstOrFail(); $user = User::whereEmail($invitation->email)->firstOrFail();
if (auth()->user()->id !== $user->id) {
abort(401);
}
$invitationValid = $invitation->isValid(); $invitationValid = $invitation->isValid();
if ($invitationValid) { if ($invitationValid) {
$user->teams()->attach($invitation->team->id, ['role' => $invitation->role]); if ($resetPassword) {
refreshSession($invitation->team); $user->update([
'password' => Hash::make($invitationUuid),
'force_password_reset' => true
]);
}
if ($user->teams()->where('team_id', $invitation->team->id)->exists()) {
$invitation->delete(); $invitation->delete();
return redirect()->route('team.index'); return redirect()->route('team.index');
}
$user->teams()->attach($invitation->team->id, ['role' => $invitation->role]);
$invitation->delete();
if (auth()->user()?->id !== $user->id) {
return redirect()->route('login');
}
refreshSession($invitation->team);
return redirect()->route('team.index');
} else { } else {
abort(401); abort(401);
} }

View File

@@ -24,7 +24,7 @@ class CheckForcePasswordReset
} }
$force_password_reset = auth()->user()->force_password_reset; $force_password_reset = auth()->user()->force_password_reset;
if ($force_password_reset) { if ($force_password_reset) {
if ($request->routeIs('auth.force-password-reset') || $request->path() === 'livewire/message/force-password-reset') { if ($request->routeIs('auth.force-password-reset') || $request->path() === 'force-password-reset' || $request->path() === 'livewire/update' || $request->path() === 'logout') {
return $next($request); return $next($request);
} }
return redirect()->route('auth.force-password-reset'); return redirect()->route('auth.force-password-reset');

View File

@@ -11,6 +11,9 @@ class DecideWhatToDoWithUser
{ {
public function handle(Request $request, Closure $next): Response public function handle(Request $request, Closure $next): Response
{ {
if(auth()?->user()?->currentTeam()){
refreshSession(auth()->user()->currentTeam());
}
if (!auth()->user() || !isCloud() || isInstanceAdmin()) { if (!auth()->user() || !isCloud() || isInstanceAdmin()) {
if (!isCloud() && showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) { if (!isCloud() && showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
return redirect('boarding'); return redirect('boarding');

View File

@@ -1,28 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Str;
class IsBoardingFlow
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
// ray()->showQueries()->color('orange');
if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
if (Str::startsWith($request->path(), 'invitations')) {
return $next($request);
}
return redirect('boarding');
}
return $next($request);
}
}

View File

@@ -1,45 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Str;
class IsSubscriptionValid
{
public function handle(Request $request, Closure $next): Response
{
if (isInstanceAdmin()) {
return $next($request);
}
if (!auth()->user() || !isCloud()) {
if ($request->path() === 'subscription') {
return redirect('/');
} else {
return $next($request);
}
}
if (isSubscriptionActive() && $request->path() === 'subscription') {
// ray('active subscription Middleware');
return redirect('/');
}
if (isSubscriptionOnGracePeriod()) {
// ray('is_subscription_in_grace_period Middleware');
return $next($request);
}
if (!isSubscriptionActive() && !isSubscriptionOnGracePeriod()) {
// ray('SubscriptionValid Middleware');
if (!in_array($request->path(), allowedPathsForUnsubscribedAccounts())) {
if (Str::startsWith($request->path(), 'invitations')) {
return $next($request);
}
return redirect('subscription');
} else {
return $next($request);
}
}
return $next($request);
}
}

View File

@@ -267,7 +267,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"ignore_errors" => true, "ignore_errors" => true,
] ]
); );
ApplicationStatusChanged::dispatch(data_get($this->application,'environment.project.team.id')); ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
} }
} }
private function push_to_docker_registry() private function push_to_docker_registry()
@@ -874,11 +874,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$environment_variables = $this->generate_environment_variables($ports); $environment_variables = $this->generate_environment_variables($ports);
if (data_get($this->application, 'custom_labels')) { if (data_get($this->application, 'custom_labels')) {
$labels = collect(str($this->application->custom_labels)->explode(',')); $this->application->parseContainerLabels();
$labels = collect(preg_split("/\r\n|\n|\r/", base64_decode($this->application->custom_labels)));
$labels = $labels->filter(function ($value, $key) { $labels = $labels->filter(function ($value, $key) {
return !Str::startsWith($value, 'coolify.'); return !Str::startsWith($value, 'coolify.');
}); });
$this->application->custom_labels = $labels->implode(','); $this->application->custom_labels = base64_encode($labels->implode("\n"));
$this->application->save(); $this->application->save();
} else { } else {
$labels = collect(generateLabelsApplication($this->application, $this->preview)); $labels = collect(generateLabelsApplication($this->application, $this->preview));
@@ -1247,7 +1248,8 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} }
} }
private function build_by_compose_file() { private function build_by_compose_file()
{
$this->application_deployment_queue->addLogEntry("Pulling & building required images."); $this->application_deployment_queue->addLogEntry("Pulling & building required images.");
if ($this->application->build_pack === 'dockerimage') { if ($this->application->build_pack === 'dockerimage') {
$this->application_deployment_queue->addLogEntry("Pulling latest images from the registry."); $this->application_deployment_queue->addLogEntry("Pulling latest images from the registry.");
@@ -1339,10 +1341,10 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} }
queue_next_deployment($this->application); queue_next_deployment($this->application);
if ($status === ApplicationDeploymentStatus::FINISHED->value) { if ($status === ApplicationDeploymentStatus::FINISHED->value) {
$this->application->environment->project->team->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview)); $this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview));
} }
if ($status === ApplicationDeploymentStatus::FAILED->value) { if ($status === ApplicationDeploymentStatus::FAILED->value) {
$this->application->environment->project->team->notify(new DeploymentFailed($this->application, $this->deployment_uuid, $this->preview)); $this->application->environment->project->team?->notify(new DeploymentFailed($this->application, $this->deployment_uuid, $this->preview));
} }
} }

View File

@@ -44,7 +44,7 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
{ {
// ray("checking log drain statuses for {$this->server->id}"); // ray("checking log drain statuses for {$this->server->id}");
try { try {
if (!$this->server->isServerReady()) { if (!$this->server->isFunctional()) {
return; return;
}; };
$containers = instant_remote_process(["docker container ls -q"], $this->server, false); $containers = instant_remote_process(["docker container ls -q"], $this->server, false);
@@ -63,19 +63,19 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
Sleep::for(10)->seconds(); Sleep::for(10)->seconds();
if ($this->healthcheck()) { if ($this->healthcheck()) {
if ($this->server->log_drain_notification_sent) { if ($this->server->log_drain_notification_sent) {
$this->server->team->notify(new ContainerRestarted('Coolify Log Drainer', $this->server)); $this->server->team?->notify(new ContainerRestarted('Coolify Log Drainer', $this->server));
$this->server->update(['log_drain_notification_sent' => false]); $this->server->update(['log_drain_notification_sent' => false]);
} }
return; return;
} }
if (!$this->server->log_drain_notification_sent) { if (!$this->server->log_drain_notification_sent) {
ray('Log drain container still unhealthy. Sending notification...'); ray('Log drain container still unhealthy. Sending notification...');
$this->server->team->notify(new ContainerStopped('Coolify Log Drainer', $this->server, null)); $this->server->team?->notify(new ContainerStopped('Coolify Log Drainer', $this->server, null));
$this->server->update(['log_drain_notification_sent' => true]); $this->server->update(['log_drain_notification_sent' => true]);
} }
} else { } else {
if ($this->server->log_drain_notification_sent) { if ($this->server->log_drain_notification_sent) {
$this->server->team->notify(new ContainerRestarted('Coolify Log Drainer', $this->server)); $this->server->team?->notify(new ContainerRestarted('Coolify Log Drainer', $this->server));
$this->server->update(['log_drain_notification_sent' => false]); $this->server->update(['log_drain_notification_sent' => false]);
} }
} }

View File

@@ -17,6 +17,7 @@ use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Sleep;
class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
{ {
@@ -37,10 +38,8 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
$this->handle(); $this->handle();
} }
public function handle() public function handle()
{ {
// ray("checking container statuses for {$this->server->id}");
try { try {
if (!$this->server->isServerReady()) { if (!$this->server->isServerReady()) {
return; return;

View File

@@ -286,7 +286,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
if ($this->backup->save_s3) { if ($this->backup->save_s3) {
$this->upload_to_s3(); $this->upload_to_s3();
} }
$this->team->notify(new BackupSuccess($this->backup, $this->database)); $this->team?->notify(new BackupSuccess($this->backup, $this->database));
$this->backup_log->update([ $this->backup_log->update([
'status' => 'success', 'status' => 'success',
'message' => $this->backup_output, 'message' => $this->backup_output,
@@ -302,7 +302,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
]); ]);
} }
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage()); send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
$this->team->notify(new BackupFailed($this->backup, $this->database, $this->backup_output)); $this->team?->notify(new BackupFailed($this->backup, $this->database, $this->backup_output));
throw $e; throw $e;
} }
} }

View File

@@ -32,9 +32,10 @@ class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted
try { try {
$server = $this->resource->destination->server; $server = $this->resource->destination->server;
if (!$server->isFunctional()) { if (!$server->isFunctional()) {
$this->resource->delete(); $this->resource->forceDelete();
return 'Server is not functional'; return 'Server is not functional';
} }
$this->resource->delete();
switch ($this->resource->type()) { switch ($this->resource->type()) {
case 'application': case 'application':
StopApplication::run($this->resource); StopApplication::run($this->resource);
@@ -56,10 +57,9 @@ class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted
break; break;
} }
if ($this->resource->type() === 'service') { if ($this->resource->type() === 'service') {
$this->resource->delete();
DeleteService::dispatch($this->resource); DeleteService::dispatch($this->resource);
} else { } else {
$this->resource->delete(); $this->resource->forceDelete();
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage()); send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage());

View File

@@ -34,7 +34,7 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
{ {
ray("checking server status for {$this->server->id}"); ray("checking server status for {$this->server->id}");
try { try {
if ($this->server->isServerReady()) { if ($this->server->isFunctional()) {
$this->cleanup(notify: false); $this->cleanup(notify: false);
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -54,7 +54,7 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
} else { } else {
$this->server->high_disk_usage_notification_sent = true; $this->server->high_disk_usage_notification_sent = true;
$this->server->save(); $this->server->save();
$this->server->team->notify(new HighDiskUsage($this->server, $this->disk_usage, $this->server->settings->cleanup_after_percentage)); $this->server->team?->notify(new HighDiskUsage($this->server, $this->disk_usage, $this->server->settings->cleanup_after_percentage));
} }
} else { } else {
DockerCleanupJob::dispatchSync($this->server); DockerCleanupJob::dispatchSync($this->server);

View File

@@ -52,7 +52,7 @@ class DiscordSettings extends Component
public function sendTestNotification() public function sendTestNotification()
{ {
$this->team->notify(new Test()); $this->team?->notify(new Test());
$this->dispatch('success', 'Test notification sent.'); $this->dispatch('success', 'Test notification sent.');
} }
} }

View File

@@ -70,7 +70,7 @@ class EmailSettings extends Component
} }
public function sendTestNotification() public function sendTestNotification()
{ {
$this->team->notify(new Test($this->emails)); $this->team?->notify(new Test($this->emails));
$this->dispatch('success', 'Test Email sent successfully.'); $this->dispatch('success', 'Test Email sent successfully.');
} }
public function instantSaveInstance() public function instantSaveInstance()

View File

@@ -58,7 +58,7 @@ class TelegramSettings extends Component
public function sendTestNotification() public function sendTestNotification()
{ {
$this->team->notify(new Test()); $this->team?->notify(new Test());
$this->dispatch('success', 'Test notification sent.'); $this->dispatch('success', 'Test notification sent.');
} }
} }

View File

@@ -95,7 +95,6 @@ class General extends Component
'application.dockerfile_target_build' => 'Dockerfile target build', 'application.dockerfile_target_build' => 'Dockerfile target build',
'application.settings.is_static' => 'Is static', 'application.settings.is_static' => 'Is static',
]; ];
public function mount() public function mount()
{ {
try { try {
@@ -110,11 +109,7 @@ class General extends Component
$this->application->isConfigurationChanged(true); $this->application->isConfigurationChanged(true);
} }
$this->isConfigurationChanged = $this->application->isConfigurationChanged(); $this->isConfigurationChanged = $this->application->isConfigurationChanged();
if (is_null(data_get($this->application, 'custom_labels'))) { $this->customLabels = $this->application->parseContainerLabels();
$this->customLabels = str(implode(",", generateLabelsApplication($this->application)))->replace(',', "\n");
} else {
$this->customLabels = str($this->application->custom_labels)->replace(',', "\n");
}
$this->initialDockerComposeLocation = $this->application->docker_compose_location; $this->initialDockerComposeLocation = $this->application->docker_compose_location;
$this->checkLabelUpdates(); $this->checkLabelUpdates();
} }
@@ -151,7 +146,6 @@ class General extends Component
$this->parsedServiceDomains[$serviceName]['domain'] = $domain; $this->parsedServiceDomains[$serviceName]['domain'] = $domain;
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains); $this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
$this->application->save(); $this->application->save();
$this->dispatch('success', 'Domain generated.');
} }
return $domain; return $domain;
} }
@@ -233,14 +227,11 @@ class General extends Component
if ($this->application->publish_directory && $this->application->publish_directory !== '/') { if ($this->application->publish_directory && $this->application->publish_directory !== '/') {
$this->application->publish_directory = rtrim($this->application->publish_directory, '/'); $this->application->publish_directory = rtrim($this->application->publish_directory, '/');
} }
if (gettype($this->customLabels) === 'string') {
$this->customLabels = str($this->customLabels)->replace(',', "\n");
}
$this->application->custom_labels = $this->customLabels->explode("\n")->implode(',');
if ($this->application->build_pack === 'dockercompose') { if ($this->application->build_pack === 'dockercompose') {
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains); $this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
$this->parsedServices = $this->application->parseCompose(); $this->parsedServices = $this->application->parseCompose();
} }
$this->application->custom_labels = base64_encode($this->customLabels);
$this->application->save(); $this->application->save();
$showToaster && $this->dispatch('success', 'Application settings updated!'); $showToaster && $this->dispatch('success', 'Application settings updated!');
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

@@ -19,12 +19,14 @@ class CloneProject extends Component
public $servers; public $servers;
public ?Environment $environment = null; public ?Environment $environment = null;
public ?int $selectedServer = null; public ?int $selectedServer = null;
public ?int $selectedDestination = null;
public ?Server $server = null; public ?Server $server = null;
public $resources = []; public $resources = [];
public string $newProjectName = ''; public string $newProjectName = '';
protected $messages = [ protected $messages = [
'selectedServer' => 'Please select a server.', 'selectedServer' => 'Please select a server.',
'selectedDestination' => 'Please select a server & destination.',
'newProjectName' => 'Please enter a name for the new project.', 'newProjectName' => 'Please enter a name for the new project.',
]; ];
public function mount($project_uuid) public function mount($project_uuid)
@@ -34,7 +36,7 @@ class CloneProject extends Component
$this->environment = $this->project->environments->where('name', $this->environment_name)->first(); $this->environment = $this->project->environments->where('name', $this->environment_name)->first();
$this->project_id = $this->project->id; $this->project_id = $this->project->id;
$this->servers = currentTeam()->servers; $this->servers = currentTeam()->servers;
$this->newProjectName = $this->project->name . ' (clone)'; $this->newProjectName = str($this->project->name . '-clone-' . (string)new Cuid2(7))->slug();
} }
public function render() public function render()
@@ -42,9 +44,10 @@ class CloneProject extends Component
return view('livewire.project.clone-project'); return view('livewire.project.clone-project');
} }
public function selectServer($server_id) public function selectServer($server_id, $destination_id)
{ {
$this->selectedServer = $server_id; $this->selectedServer = $server_id;
$this->selectedDestination = $destination_id;
$this->server = $this->servers->where('id', $server_id)->first(); $this->server = $this->servers->where('id', $server_id)->first();
} }
@@ -52,7 +55,7 @@ class CloneProject extends Component
{ {
try { try {
$this->validate([ $this->validate([
'selectedServer' => 'required', 'selectedDestination' => 'required',
'newProjectName' => 'required', 'newProjectName' => 'required',
]); ]);
$foundProject = Project::where('name', $this->newProjectName)->first(); $foundProject = Project::where('name', $this->newProjectName)->first();
@@ -81,7 +84,8 @@ class CloneProject extends Component
'fqdn' => generateFqdn($this->server, $uuid), 'fqdn' => generateFqdn($this->server, $uuid),
'status' => 'exited', 'status' => 'exited',
'environment_id' => $newEnvironment->id, 'environment_id' => $newEnvironment->id,
'destination_id' => $this->selectedServer, // This is not correct, but we need to set it to something
'destination_id' => $this->selectedDestination,
]); ]);
$newApplication->save(); $newApplication->save();
$environmentVaribles = $application->environment_variables()->get(); $environmentVaribles = $application->environment_variables()->get();
@@ -107,7 +111,7 @@ class CloneProject extends Component
'status' => 'exited', 'status' => 'exited',
'started_at' => null, 'started_at' => null,
'environment_id' => $newEnvironment->id, 'environment_id' => $newEnvironment->id,
'destination_id' => $this->selectedServer, 'destination_id' => $this->selectedDestination,
]); ]);
$newDatabase->save(); $newDatabase->save();
$environmentVaribles = $database->environment_variables()->get(); $environmentVaribles = $database->environment_variables()->get();
@@ -133,7 +137,7 @@ class CloneProject extends Component
$newService = $service->replicate()->fill([ $newService = $service->replicate()->fill([
'uuid' => $uuid, 'uuid' => $uuid,
'environment_id' => $newEnvironment->id, 'environment_id' => $newEnvironment->id,
'destination_id' => $this->selectedServer, 'destination_id' => $this->selectedDestination,
]); ]);
$newService->save(); $newService->save();
foreach ($newService->applications() as $application) { foreach ($newService->applications() as $application) {

View File

@@ -8,7 +8,7 @@ use Livewire\Component;
class BackupExecutions extends Component class BackupExecutions extends Component
{ {
public $backup; public $backup;
public $executions; public $executions = [];
public $setDeletableBackup; public $setDeletableBackup;
public function getListeners() public function getListeners()
{ {
@@ -65,6 +65,6 @@ class BackupExecutions extends Component
} }
public function refreshBackupExecutions(): void public function refreshBackupExecutions(): void
{ {
$this->executions = $this->backup->executions; $this->executions = data_get($this->backup, 'executions', []);
} }
} }

View File

@@ -2,6 +2,7 @@
namespace App\Livewire\Project\Service; namespace App\Livewire\Project\Service;
use App\Actions\Shared\PullImage;
use App\Actions\Service\StartService; use App\Actions\Service\StartService;
use App\Actions\Service\StopService; use App\Actions\Service\StopService;
use App\Events\ServiceStatusChanged; use App\Events\ServiceStatusChanged;
@@ -69,4 +70,18 @@ class Navbar extends Component
} }
ServiceStatusChanged::dispatch(); ServiceStatusChanged::dispatch();
} }
public function restart()
{
$this->checkDeployments();
if ($this->isDeploymentProgress) {
$this->dispatch('error', 'There is a deployment in progress.');
return;
}
PullImage::run($this->service);
$this->dispatch('image-pulled');
StopService::run($this->service);
$this->service->parse();
$activity = StartService::run($this->service);
$this->dispatch('newMonitorActivity', $activity->id);
}
} }

View File

@@ -70,7 +70,7 @@ class GetLogs extends Component
} }
public function getLogs($refresh = false) public function getLogs($refresh = false)
{ {
if (!$refresh && $this->resource->getMorphClass() === 'App\Models\Service') return; if (!$refresh && $this->resource?->getMorphClass() === 'App\Models\Service') return;
if ($this->container) { if ($this->container) {
if ($this->showTimeStamps) { if ($this->showTimeStamps) {
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} -t {$this->container}"); $sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} -t {$this->container}");

View File

@@ -71,83 +71,7 @@ class Configuration extends Component
private function setup_instance_fqdn() private function setup_instance_fqdn()
{ {
$file = "$this->dynamic_config_path/coolify.yaml"; setup_dynamic_configuration();
if (empty($this->settings->fqdn)) {
instant_remote_process([
"rm -f $file",
], $this->server);
} else {
$url = Url::fromString($this->settings->fqdn);
$host = $url->getHost();
$schema = $url->getScheme();
$traefik_dynamic_conf = [
'http' =>
[
'routers' =>
[
'coolify-http' =>
[
'entryPoints' => [
0 => 'http',
],
'service' => 'coolify',
'rule' => "Host(`{$host}`)",
],
],
'services' =>
[
'coolify' =>
[
'loadBalancer' =>
[
'servers' =>
[
0 =>
[
'url' => 'http://coolify:80',
],
],
],
],
],
],
];
if ($schema === 'https') {
$traefik_dynamic_conf['http']['routers']['coolify-http']['middlewares'] = [
0 => 'redirect-to-https@docker',
];
$traefik_dynamic_conf['http']['routers']['coolify-https'] = [
'entryPoints' => [
0 => 'https',
],
'service' => 'coolify',
'rule' => "Host(`{$host}`)",
'tls' => [
'certresolver' => 'letsencrypt',
],
];
}
$this->save_configuration_to_disk($traefik_dynamic_conf, $file);
}
}
private function save_configuration_to_disk(array $traefik_dynamic_conf, string $file)
{
$yaml = Yaml::dump($traefik_dynamic_conf, 12, 2);
$yaml =
"# This file is automatically generated by Coolify.\n" .
"# Do not edit it manually (only if you know what are you doing).\n\n" .
$yaml;
$base64 = base64_encode($yaml);
instant_remote_process([
"mkdir -p $this->dynamic_config_path",
"echo '$base64' | base64 -d > $file",
], $this->server);
if (config('app.env') == 'local') {
ray($yaml);
}
} }
} }

View File

@@ -106,7 +106,7 @@ class Email extends Component
public function sendTestNotification() public function sendTestNotification()
{ {
$this->settings->notify(new Test($this->emails)); $this->settings?->notify(new Test($this->emails));
$this->dispatch('success', 'Test email sent.'); $this->dispatch('success', 'Test email sent.');
} }
} }

View File

@@ -2,8 +2,10 @@
namespace App\Models; namespace App\Models;
use App\Enums\ApplicationDeploymentStatus;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Spatie\Activitylog\Models\Activity; use Spatie\Activitylog\Models\Activity;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@@ -13,6 +15,7 @@ use Visus\Cuid2\Cuid2;
class Application extends BaseModel class Application extends BaseModel
{ {
use SoftDeletes;
protected $guarded = []; protected $guarded = [];
protected static function booted() protected static function booted()
@@ -338,7 +341,7 @@ class Application extends BaseModel
} }
public function isDeploymentInprogress() public function isDeploymentInprogress()
{ {
$deployments = ApplicationDeploymentQueue::where('application_id', $this->id)->where('status', 'in_progress')->count(); $deployments = ApplicationDeploymentQueue::where('application_id', $this->id)->where('status', ApplicationDeploymentStatus::IN_PROGRESS)->where('status', ApplicationDeploymentStatus::QUEUED)->count();
if ($deployments > 0) { if ($deployments > 0) {
return true; return true;
} }
@@ -963,8 +966,6 @@ class Application extends BaseModel
function loadComposeFile($isInit = false) function loadComposeFile($isInit = false)
{ {
$initialDockerComposeLocation = $this->docker_compose_location; $initialDockerComposeLocation = $this->docker_compose_location;
// $initialDockerComposePrLocation = $this->docker_compose_pr_location;
if ($this->build_pack === 'dockercompose') {
if ($isInit && $this->docker_compose_raw) { if ($isInit && $this->docker_compose_raw) {
return; return;
} }
@@ -1017,11 +1018,46 @@ class Application extends BaseModel
"rm -rf /tmp/{$uuid}", "rm -rf /tmp/{$uuid}",
]); ]);
instant_remote_process($commands, $this->destination->server, false); instant_remote_process($commands, $this->destination->server, false);
$parsedServices = $this->parseCompose();
if ($this->docker_compose_domains) {
$json = collect(json_decode($this->docker_compose_domains));
$names = collect(data_get($parsedServices, 'services'))->keys()->toArray();
$jsonNames = $json->keys()->toArray();
$diff = array_diff($jsonNames, $names);
$json = $json->filter(function ($value, $key) use ($diff) {
return !in_array($key, $diff);
});
if ($json) {
$this->docker_compose_domains = json_encode($json);
} else {
$this->docker_compose_domains = null;
}
$this->save();
}
return [ return [
'parsedServices' => $this->parseCompose(), 'parsedServices' => $parsedServices,
'initialDockerComposeLocation' => $this->docker_compose_location, 'initialDockerComposeLocation' => $this->docker_compose_location,
'initialDockerComposePrLocation' => $this->docker_compose_pr_location, 'initialDockerComposePrLocation' => $this->docker_compose_pr_location,
]; ];
} }
function parseContainerLabels(?ApplicationPreview $preview = null)
{
$customLabels = data_get($this, 'custom_labels');
if (!$customLabels) {
return;
}
if (base64_encode(base64_decode($customLabels, true)) !== $customLabels) {
ray('custom_labels is not base64 encoded');
$this->custom_labels = str($customLabels)->replace(',', "\n");
$this->custom_labels = base64_encode($customLabels);
}
$customLabels = base64_decode($this->custom_labels);
if (mb_detect_encoding($customLabels, 'ASCII', true) === false) {
ray('custom_labels contains non-ascii characters');
$customLabels = str(implode(",", generateLabelsApplication($this, $preview)))->replace(',', "\n");
}
$this->custom_labels = base64_encode($customLabels);
$this->save();
return $customLabels;
} }
} }

View File

@@ -6,6 +6,8 @@ use App\Notifications\Channels\SendsEmail;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Request;
use Spatie\Url\Url; use Spatie\Url\Url;
class InstanceSettings extends Model implements SendsEmail class InstanceSettings extends Model implements SendsEmail

View File

@@ -153,18 +153,19 @@ class Server extends BaseModel
public function isServerReady() public function isServerReady()
{ {
$serverUptimeCheckNumber = $this->unreachable_count; $serverUptimeCheckNumber = $this->unreachable_count;
$serverUptimeCheckNumberMax = 3; $serverUptimeCheckNumberMax = 8;
$currentTime = now()->timestamp; $currentTime = now()->timestamp;
$runtime = 30; $runtime = 50;
$isReady = false; $isReady = false;
// Run for 30 seconds max and check every 5 seconds for 3 times // Run for 50 seconds max and check every 5 seconds for 8 times
while ($currentTime + $runtime > now()->timestamp) { while ($currentTime + $runtime > now()->timestamp) {
ray('serverUptimeCheckNumber: ' . $serverUptimeCheckNumber);
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) { if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
if ($this->unreachable_notification_sent === false) { if ($this->unreachable_notification_sent === false) {
ray('Server unreachable, sending notification...'); ray('Server unreachable, sending notification...');
$this->team->notify(new Unreachable($this)); $this->team?->notify(new Unreachable($this));
$this->update(['unreachable_notification_sent' => true]); $this->update(['unreachable_notification_sent' => true]);
} }
$this->settings()->update([ $this->settings()->update([
@@ -200,7 +201,7 @@ class Server extends BaseModel
'unreachable_count' => $serverUptimeCheckNumber, 'unreachable_count' => $serverUptimeCheckNumber,
]); ]);
Sleep::for(5)->seconds(); Sleep::for(5)->seconds();
return; continue;
} }
$isReady = true; $isReady = true;
break; break;
@@ -401,7 +402,7 @@ class Server extends BaseModel
} }
if (data_get($server, 'unreachable_notification_sent') === true) { if (data_get($server, 'unreachable_notification_sent') === true) {
$server->team->notify(new Revived($server)); $server->team?->notify(new Revived($server));
$server->update(['unreachable_notification_sent' => false]); $server->update(['unreachable_notification_sent' => false]);
} }

View File

@@ -4,11 +4,12 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
class ServiceApplication extends BaseModel class ServiceApplication extends BaseModel
{ {
use HasFactory; use HasFactory, SoftDeletes;
protected $guarded = []; protected $guarded = [];
protected static function booted() protected static function booted()

View File

@@ -3,10 +3,11 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
class ServiceDatabase extends BaseModel class ServiceDatabase extends BaseModel
{ {
use HasFactory; use HasFactory, SoftDeletes;
protected $guarded = []; protected $guarded = [];
protected static function booted() protected static function booted()

View File

@@ -6,10 +6,11 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class StandaloneMariadb extends BaseModel class StandaloneMariadb extends BaseModel
{ {
use HasFactory; use HasFactory,SoftDeletes;
protected $guarded = []; protected $guarded = [];
protected $casts = [ protected $casts = [

View File

@@ -5,10 +5,11 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class StandaloneMongodb extends BaseModel class StandaloneMongodb extends BaseModel
{ {
use HasFactory; use HasFactory, SoftDeletes;
protected $guarded = []; protected $guarded = [];
protected static function booted() protected static function booted()

View File

@@ -5,10 +5,11 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class StandaloneMysql extends BaseModel class StandaloneMysql extends BaseModel
{ {
use HasFactory; use HasFactory, SoftDeletes;
protected $guarded = []; protected $guarded = [];
protected $casts = [ protected $casts = [

View File

@@ -5,10 +5,11 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class StandalonePostgresql extends BaseModel class StandalonePostgresql extends BaseModel
{ {
use HasFactory; use HasFactory, SoftDeletes;
protected $guarded = []; protected $guarded = [];
protected $casts = [ protected $casts = [

View File

@@ -5,10 +5,11 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class StandaloneRedis extends BaseModel class StandaloneRedis extends BaseModel
{ {
use HasFactory; use HasFactory, SoftDeletes;
protected $guarded = []; protected $guarded = [];
protected static function booted() protected static function booted()

View File

@@ -98,7 +98,7 @@ class User extends Authenticatable implements SendsEmail
} }
public function sendPasswordResetNotification($token): void public function sendPasswordResetNotification($token): void
{ {
$this->notify(new TransactionalEmailsResetPassword($token)); $this?->notify(new TransactionalEmailsResetPassword($token));
} }
public function isAdmin() public function isAdmin()

View File

@@ -43,7 +43,7 @@ class Unreachable extends Notification implements ShouldQueue
public function toMail(): MailMessage public function toMail(): MailMessage
{ {
$mail = new MailMessage(); $mail = new MailMessage();
$mail->subject("Coolify: Server ({$this->server->name}) is unreachable after trying to connect to it 3 times"); $mail->subject("Coolify: Your server ({$this->server->name}) is unreachable.");
$mail->view('emails.server-lost-connection', [ $mail->view('emails.server-lost-connection', [
'name' => $this->server->name, 'name' => $this->server->name,
]); ]);
@@ -52,13 +52,13 @@ class Unreachable extends Notification implements ShouldQueue
public function toDiscord(): string public function toDiscord(): string
{ {
$message = "Coolify: Server '{$this->server->name}' is unreachable after trying to connect to it 3 times. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations."; $message = "Coolify: Your server '{$this->server->name}' is unreachable. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations.";
return $message; return $message;
} }
public function toTelegram(): array public function toTelegram(): array
{ {
return [ return [
"message" => "Coolify: Server '{$this->server->name}' is unreachable after trying to connect to it 3 times. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations." "message" => "Coolify: Your server '{$this->server->name}' is unreachable. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations."
]; ];
} }
} }

View File

@@ -16,7 +16,6 @@ class AppServiceProvider extends ServiceProvider
public function boot(): void public function boot(): void
{ {
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class); Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
Http::macro('github', function (string $api_url, string|null $github_access_token = null) { Http::macro('github', function (string $api_url, string|null $github_access_token = null) {
if ($github_access_token) { if ($github_access_token) {
return Http::withHeaders([ return Http::withHeaders([

View File

@@ -2,7 +2,9 @@
use App\Actions\Proxy\SaveConfiguration; use App\Actions\Proxy\SaveConfiguration;
use App\Models\Application; use App\Models\Application;
use App\Models\InstanceSettings;
use App\Models\Server; use App\Models\Server;
use Spatie\Url\Url;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
function get_proxy_path() function get_proxy_path()
@@ -155,7 +157,119 @@ function generate_default_proxy_configuration(Server $server)
SaveConfiguration::run($server, $config); SaveConfiguration::run($server, $config);
return $config; return $config;
} }
function setup_dynamic_configuration()
{
$dynamic_config_path = get_proxy_path() . "/dynamic";
$settings = InstanceSettings::get();
$server = Server::find(0);
if ($server) {
$file = "$dynamic_config_path/coolify.yaml";
if (empty($settings->fqdn)) {
instant_remote_process([
"rm -f $file",
], $server);
} else {
$url = Url::fromString($settings->fqdn);
$host = $url->getHost();
$schema = $url->getScheme();
$traefik_dynamic_conf = [
'http' =>
[
'routers' =>
[
'coolify-http' =>
[
'entryPoints' => [
0 => 'http',
],
'service' => 'coolify',
'rule' => "Host(`{$host}`)",
],
'coolify-realtime-ws' =>
[
'entryPoints' => [
0 => 'http',
],
'service' => 'coolify-realtime',
'rule' => "Host(`{$host}`) && PathPrefix(`/app`)",
],
],
'services' =>
[
'coolify' =>
[
'loadBalancer' =>
[
'servers' =>
[
0 =>
[
'url' => 'http://coolify:80',
],
],
],
],
'coolify-realtime' =>
[
'loadBalancer' =>
[
'servers' =>
[
0 =>
[
'url' => 'http://coolify-realtime:6001',
],
],
],
],
],
],
];
if ($schema === 'https') {
$traefik_dynamic_conf['http']['routers']['coolify-http']['middlewares'] = [
0 => 'redirect-to-https@docker',
];
$traefik_dynamic_conf['http']['routers']['coolify-https'] = [
'entryPoints' => [
0 => 'https',
],
'service' => 'coolify',
'rule' => "Host(`{$host}`)",
'tls' => [
'certresolver' => 'letsencrypt',
],
];
$traefik_dynamic_conf['http']['routers']['coolify-realtime-wss'] = [
'entryPoints' => [
0 => 'https',
],
'service' => 'coolify-realtime',
'rule' => "Host(`{$host}`) && PathPrefix(`/app`)",
'tls' => [
'certresolver' => 'letsencrypt',
],
];
}
$yaml = Yaml::dump($traefik_dynamic_conf, 12, 2);
$yaml =
"# This file is automatically generated by Coolify.\n" .
"# Do not edit it manually (only if you know what are you doing).\n\n" .
$yaml;
$base64 = base64_encode($yaml);
instant_remote_process([
"mkdir -p $dynamic_config_path",
"echo '$base64' | base64 -d > $file",
], $server);
if (config('app.env') == 'local') {
ray($yaml);
}
}
}
}
function setup_default_redirect_404(string|null $redirect_url, Server $server) function setup_default_redirect_404(string|null $redirect_url, Server $server)
{ {
$traefik_dynamic_conf_path = get_proxy_path() . "/dynamic"; $traefik_dynamic_conf_path = get_proxy_path() . "/dynamic";

View File

@@ -29,7 +29,9 @@ use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Support\Stringable; use Illuminate\Support\Stringable;
@@ -967,10 +969,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
} }
} }
} else { } else {
$foundEnv = EnvironmentVariable::where([
'key' => $key,
'service_id' => $resource->id,
])->first();
if ($value->contains(':-')) { if ($value->contains(':-')) {
$key = $value->before(':'); $key = $value->before(':');
$defaultValue = $value->after(':-'); $defaultValue = $value->after(':-');
@@ -987,6 +985,10 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$key = $value; $key = $value;
$defaultValue = null; $defaultValue = null;
} }
$foundEnv = EnvironmentVariable::where([
'key' => $key,
'service_id' => $resource->id,
])->first();
if ($foundEnv) { if ($foundEnv) {
$defaultValue = data_get($foundEnv, 'value'); $defaultValue = data_get($foundEnv, 'value');
} }
@@ -1542,6 +1544,25 @@ function generateEnvValue(string $command)
case 'USER': case 'USER':
$generatedValue = Str::random(16); $generatedValue = Str::random(16);
break; break;
default:
$generatedValue = Str::random(16);
break;
} }
return $generatedValue; return $generatedValue;
} }
function getRealtime()
{
$envDefined = env('PUSHER_PORT');
if (empty($envDefined)) {
$url = Url::fromString(Request::getSchemeAndHttpHost());
$port = $url->getPort();
if ($port) {
return '6001';
} else {
return null;
}
} else {
return $envDefined;
}
}

View File

@@ -128,11 +128,6 @@ function allowedPathsForUnsubscribedAccounts()
'logout', 'logout',
'waitlist', 'waitlist',
'force-password-reset', 'force-password-reset',
// 'livewire/message/force-password-reset',
// 'livewire/message/check-license',
// 'livewire/message/switch-team',
// 'livewire/message/subscription.pricing-plans',
// 'livewire/message/help',
'livewire/update' 'livewire/update'
]; ];
} }
@@ -141,8 +136,6 @@ function allowedPathsForBoardingAccounts()
return [ return [
...allowedPathsForUnsubscribedAccounts(), ...allowedPathsForUnsubscribedAccounts(),
'boarding', 'boarding',
// 'livewire/message/boarding.index',
// 'livewire/message/activity-monitor',
'livewire/update' 'livewire/update'
]; ];
} }
@@ -151,9 +144,6 @@ function allowedPathsForInvalidAccounts() {
'logout', 'logout',
'verify', 'verify',
'force-password-reset', 'force-password-reset',
// 'livewire/message/force-password-reset',
// 'livewire/message/verify-email',
// 'livewire/message/help',
'livewire/update' 'livewire/update'
]; ];
} }

View File

@@ -3,11 +3,11 @@
return [ return [
// @see https://docs.sentry.io/product/sentry-basics/dsn-explainer/ // @see https://docs.sentry.io/product/sentry-basics/dsn-explainer/
'dsn' => 'https://bea22abf110618b07252032aa2e07859@o1082494.ingest.sentry.io/4505347448045568', 'dsn' => 'https://1bbc8f762199a52aee39196adb3e8d1a@o1082494.ingest.sentry.io/4505347448045568',
// The release version of your application // The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.155', 'release' => '4.0.0-beta.164',
// When left empty or `null` the Laravel environment will be used // When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'), 'environment' => config('app.env'),

View File

@@ -1,3 +1,3 @@
<?php <?php
return '4.0.0-beta.155'; return '4.0.0-beta.164';

View File

@@ -1,23 +0,0 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\StandaloneMongodb>
*/
class StandaloneMongodbFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
//
];
}
}

View File

@@ -0,0 +1,71 @@
<?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('applications', function (Blueprint $table) {
$table->softDeletes();
});
Schema::table('standalone_postgresqls', function (Blueprint $table) {
$table->softDeletes();
});
Schema::table('standalone_redis', function (Blueprint $table) {
$table->softDeletes();
});
Schema::table('standalone_mongodbs', function (Blueprint $table) {
$table->softDeletes();
});
Schema::table('standalone_mysqls', function (Blueprint $table) {
$table->softDeletes();
});
Schema::table('standalone_mariadbs', function (Blueprint $table) {
$table->softDeletes();
});
Schema::table('service_applications', function (Blueprint $table) {
$table->softDeletes();
});
Schema::table('service_databases', function (Blueprint $table) {
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('applications', function (Blueprint $table) {
$table->dropSoftDeletes();
});
Schema::table('standalone_postgresqls', function (Blueprint $table) {
$table->dropSoftDeletes();
});
Schema::table('standalone_redis', function (Blueprint $table) {
$table->dropSoftDeletes();
});
Schema::table('standalone_mongodbs', function (Blueprint $table) {
$table->dropSoftDeletes();
});
Schema::table('standalone_mysqls', function (Blueprint $table) {
$table->dropSoftDeletes();
});
Schema::table('standalone_mariadbs', function (Blueprint $table) {
$table->dropSoftDeletes();
});
Schema::table('service_applications', function (Blueprint $table) {
$table->dropSoftDeletes();
});
Schema::table('service_databases', function (Blueprint $table) {
$table->dropSoftDeletes();
});
}
};

View File

@@ -14,6 +14,7 @@ use App\Models\StandaloneDocker;
use App\Models\Team; use App\Models\Team;
use App\Models\User; use App\Models\User;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Process; use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;

View File

@@ -22,7 +22,7 @@ services:
AUTORUN_LARAVEL_STORAGE_LINK: "false" AUTORUN_LARAVEL_STORAGE_LINK: "false"
AUTORUN_LARAVEL_MIGRATION: "false" AUTORUN_LARAVEL_MIGRATION: "false"
PUSHER_HOST: "${PUSHER_HOST}" PUSHER_HOST: "${PUSHER_HOST}"
PUSHER_PORT: "${PUSHER_PORT:-6001}" PUSHER_PORT: "${PUSHER_PORT}"
PUSHER_SCHEME: "${PUSHER_SCHEME:-http}" PUSHER_SCHEME: "${PUSHER_SCHEME:-http}"
PUSHER_APP_ID: "${PUSHER_APP_ID:-coolify}" PUSHER_APP_ID: "${PUSHER_APP_ID:-coolify}"
PUSHER_APP_KEY: "${PUSHER_APP_KEY:-coolify}" PUSHER_APP_KEY: "${PUSHER_APP_KEY:-coolify}"
@@ -56,7 +56,7 @@ services:
ports: ports:
- "${FORWARD_SOKETI_PORT:-6001}:6001" - "${FORWARD_SOKETI_PORT:-6001}:6001"
environment: environment:
SOKETI_DEBUG: "true" SOKETI_DEBUG: "false"
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID:-coolify}" SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID:-coolify}"
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY:-coolify}" SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY:-coolify}"
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}" SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}"
@@ -70,6 +70,8 @@ services:
volumes: volumes:
- .:/var/www/html:cached - .:/var/www/html:cached
command: sh -c "npm install && npm run dev" command: sh -c "npm install && npm run dev"
networks:
- coolify
testing-host: testing-host:
<<: *testing-host-base <<: *testing-host-base
container_name: coolify-testing-host container_name: coolify-testing-host
@@ -77,15 +79,8 @@ services:
- /:/host - /:/host
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
- /data/coolify/:/data/coolify - /data/coolify/:/data/coolify
# - coolify-data-dev:/data/coolify networks:
# remote-host: - coolify
# <<: *testing-host-base
# container_name: coolify-remote-host
# volumes:
# - /:/host
# - /var/run/docker.sock:/var/run/docker.sock
# - /data/coolify/:/data/coolify
# # - coolify-data-dev:/data/coolify
mailpit: mailpit:
image: "axllent/mailpit:latest" image: "axllent/mailpit:latest"
container_name: coolify-mail container_name: coolify-mail
@@ -115,3 +110,9 @@ volumes:
coolify-pg-data-dev: coolify-pg-data-dev:
coolify-redis-data-dev: coolify-redis-data-dev:
coolify-minio-data-dev: coolify-minio-data-dev:
networks:
coolify:
name: coolify
external: false

View File

@@ -43,6 +43,7 @@ services:
- PUSHER_APP_ID - PUSHER_APP_ID
- PUSHER_APP_KEY - PUSHER_APP_KEY
- PUSHER_APP_SECRET - PUSHER_APP_SECRET
- AUTOUPDATE
- SELF_HOSTED - SELF_HOSTED
- WAITLIST - WAITLIST
- SUBSCRIPTION_PROVIDER - SUBSCRIPTION_PROVIDER

View File

@@ -2,7 +2,7 @@ FROM serversideup/php:8.2-fpm-nginx
ARG TARGETPLATFORM ARG TARGETPLATFORM
# https://github.com/cloudflare/cloudflared/releases # https://github.com/cloudflare/cloudflared/releases
ARG CLOUDFLARED_VERSION=2023.8.2 ARG CLOUDFLARED_VERSION=2023.10.0
ARG POSTGRES_VERSION=15 ARG POSTGRES_VERSION=15
RUN apt-get update RUN apt-get update

View File

@@ -0,0 +1 @@
oneshot

View File

@@ -0,0 +1,5 @@
#!/command/execlineb -P
foreground { composer -d /var/www/html/ install }
foreground { php /var/www/html/artisan migrate --step }
foreground { php /var/www/html/artisan dev:init }

View File

@@ -15,7 +15,7 @@ FROM serversideup/php:8.2-fpm-nginx
ARG TARGETPLATFORM ARG TARGETPLATFORM
# https://github.com/cloudflare/cloudflared/releases # https://github.com/cloudflare/cloudflared/releases
ARG CLOUDFLARED_VERSION=2023.8.2 ARG CLOUDFLARED_VERSION=2023.10.0
ARG POSTGRES_VERSION=15 ARG POSTGRES_VERSION=15
WORKDIR /var/www/html WORKDIR /var/www/html

View File

@@ -1,2 +1,3 @@
#!/command/execlineb -P #!/command/execlineb -P
php /var/www/html/artisan app:init s6-setuidgid webuser
php /var/www/html/artisan app:init --cleanup

View File

@@ -5,25 +5,32 @@
html { html {
@apply text-neutral-400; @apply text-neutral-400;
} }
body { body {
@apply text-sm antialiased scrollbar; @apply text-sm antialiased scrollbar;
} }
button[isError] { button[isError] {
@apply bg-red-600 hover:bg-red-700; @apply bg-red-600 hover:bg-red-700;
} }
.scrollbar { .scrollbar {
@apply scrollbar-thumb-coollabs-100 scrollbar-track-coolgray-200 scrollbar-w-2; @apply scrollbar-thumb-coollabs-100 scrollbar-track-coolgray-200 scrollbar-w-2;
} }
.main { .main {
@apply pt-4 pl-24 pr-10 mx-auto; @apply pt-4 pl-24 pr-10 mx-auto;
} }
.custom-modal { .custom-modal {
@apply flex flex-col gap-2 px-8 py-4 border bg-base-100 border-coolgray-200; @apply flex flex-col gap-2 px-8 py-4 border bg-base-100 border-coolgray-200;
} }
.label-text, .label-text,
label { label {
@apply text-neutral-400; @apply text-neutral-400;
} }
.navbar-main { .navbar-main {
@apply flex items-end gap-6 py-2 border-b-2 border-solid border-coolgray-200; @apply flex items-end gap-6 py-2 border-b-2 border-solid border-coolgray-200;
} }
@@ -31,89 +38,117 @@ label {
.loading { .loading {
@apply w-4 text-warning; @apply w-4 text-warning;
} }
h1 { h1 {
@apply text-3xl font-bold text-white; @apply text-3xl font-bold text-white;
} }
h2 { h2 {
@apply text-2xl font-bold text-white; @apply text-2xl font-bold text-white;
} }
h3 { h3 {
@apply text-xl font-bold text-white; @apply text-xl font-bold text-white;
} }
h4 { h4 {
@apply text-base font-bold text-white; @apply text-base font-bold text-white;
} }
a { a {
@apply text-neutral-400 hover:text-white link link-hover hover:bg-transparent; @apply text-neutral-400 hover:text-white link link-hover hover:bg-transparent;
} }
.kbd-custom { .kbd-custom {
@apply px-2 text-xs border border-dashed rounded border-neutral-700 text-warning; @apply px-2 text-xs border border-dashed rounded border-neutral-700 text-warning;
} }
.icon { .icon {
@apply w-6 h-6; @apply w-6 h-6;
} }
.icon:hover { .icon:hover {
@apply text-white; @apply text-white;
} }
.box { .box {
@apply flex p-2 transition-colors cursor-pointer min-h-[4rem] bg-coolgray-100 hover:bg-coollabs-100 hover:text-white hover:no-underline min-w-[24rem]; @apply flex p-2 transition-colors cursor-pointer min-h-[4rem] bg-coolgray-100 hover:bg-coollabs-100 hover:text-white hover:no-underline min-w-[24rem];
} }
.box-without-bg { .box-without-bg {
@apply flex p-2 transition-colors h-16 min-h-full hover:text-white hover:no-underline min-h-[4rem]; @apply flex p-2 transition-colors min-h-full hover:text-white hover:no-underline min-h-[4rem];
} }
.description { .description {
@apply pt-2 text-xs font-bold text-neutral-500 group-hover:text-white; @apply pt-2 text-xs font-bold text-neutral-500 group-hover:text-white;
} }
.lds-heart { .lds-heart {
animation: lds-heart 1.2s infinite cubic-bezier(0.215, 0.61, 0.355, 1); animation: lds-heart 1.2s infinite cubic-bezier(0.215, 0.61, 0.355, 1);
} }
@keyframes lds-heart { @keyframes lds-heart {
0% { 0% {
transform: scale(1); transform: scale(1);
} }
5% { 5% {
transform: scale(1.2); transform: scale(1.2);
} }
39% { 39% {
transform: scale(0.85); transform: scale(0.85);
} }
45% { 45% {
transform: scale(1); transform: scale(1);
} }
60% { 60% {
transform: scale(0.95); transform: scale(0.95);
} }
100% { 100% {
transform: scale(0.9); transform: scale(0.9);
} }
} }
.bg-coollabs-gradient { .bg-coollabs-gradient {
@apply text-transparent text-white bg-gradient-to-r from-purple-500 via-pink-500 to-red-500; @apply text-transparent text-white bg-gradient-to-r from-purple-500 via-pink-500 to-red-500;
} }
.text-helper { .text-helper {
@apply inline-block font-bold text-warning; @apply inline-block font-bold text-warning;
} }
table { table {
@apply min-w-full divide-y divide-coolgray-200; @apply min-w-full divide-y divide-coolgray-200;
} }
thead { thead {
@apply uppercase; @apply uppercase;
} }
tbody { tbody {
@apply divide-y divide-coolgray-200; @apply divide-y divide-coolgray-200;
} }
tr { tr {
@apply text-neutral-400; @apply text-neutral-400;
} }
tr th { tr th {
@apply px-3 py-3.5 text-left text-white; @apply px-3 py-3.5 text-left text-white;
} }
tr th:first-child { tr th:first-child {
@apply py-3.5 pl-4 pr-3 sm:pl-6; @apply py-3.5 pl-4 pr-3 sm:pl-6;
} }
tr td { tr td {
@apply px-3 py-4 whitespace-nowrap; @apply px-3 py-4 whitespace-nowrap;
} }
tr td:first-child { tr td:first-child {
@apply pl-4 pr-3 font-bold sm:pl-6; @apply pl-4 pr-3 font-bold sm:pl-6;
} }
@@ -121,12 +156,15 @@ tr td:first-child {
.buyme { .buyme {
@apply block px-3 py-2 mt-10 text-sm font-semibold leading-6 text-center text-white rounded-md shadow-sm bg-coolgray-200 hover:bg-coolgray-300 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-coolgray-200 hover:no-underline; @apply block px-3 py-2 mt-10 text-sm font-semibold leading-6 text-center text-white rounded-md shadow-sm bg-coolgray-200 hover:bg-coolgray-300 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-coolgray-200 hover:no-underline;
} }
.subtitle { .subtitle {
@apply pt-2 pb-10; @apply pt-2 pb-10;
} }
.fullscreen { .fullscreen {
@apply fixed top-0 left-0 w-full h-full z-[9999] bg-coolgray-100 overflow-y-auto scrollbar pb-4 ; @apply fixed top-0 left-0 w-full h-full z-[9999] bg-coolgray-100 overflow-y-auto scrollbar pb-4;
} }
input.input-sm { input.input-sm {
@apply pr-10; @apply pr-10;
} }

View File

@@ -1,13 +1,13 @@
<x-layout-simple> <x-layout-simple>
<div class="min-h-screen hero"> <div class="min-h-screen hero">
<div> <div>
<div class="flex flex-col items-center pb-8"> <div class="flex flex-col items-center ">
<a href="{{ route('dashboard') }}"> <a href="{{ route('dashboard') }}">
<div class="text-5xl font-bold tracking-tight text-center text-white">Coolify</div> <div class="text-5xl font-bold tracking-tight text-center text-white">Coolify</div>
</a> </a>
</div> </div>
<div class="flex items-center justify-center pb-4 text-center"> <div class="flex items-center justify-center pb-4 text-center">
<h2>{{ __('auth.reset_password') }}</h2> {{ __('auth.reset_password') }}
</div> </div>
<div> <div>
<form action="/reset-password" method="POST" class="flex flex-col gap-2"> <form action="/reset-password" method="POST" class="flex flex-col gap-2">

View File

@@ -23,9 +23,10 @@
@if (data_get($application, 'build_pack') === 'dockercompose') @if (data_get($application, 'build_pack') === 'dockercompose')
@foreach (collect(json_decode($this->application->docker_compose_domains)) as $fqdn) @foreach (collect(json_decode($this->application->docker_compose_domains)) as $fqdn)
@if (data_get($fqdn, 'domain')) @if (data_get($fqdn, 'domain'))
@foreach (explode(',', data_get($fqdn, 'domain')) as $domain)
<li> <li>
<a class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white" <a class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white"
target="_blank" href="{{ getFqdnWithoutPort(data_get($fqdn, 'domain')) }}"> target="_blank" href="{{ getFqdnWithoutPort($domain) }}">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" <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-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round"> stroke-linejoin="round">
@@ -34,9 +35,10 @@
<path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" />
<path <path
d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" />
</svg>{{ getFqdnWithoutPort(data_get($fqdn, 'domain')) }} </svg>{{ getFqdnWithoutPort($domain) }}
</a> </a>
</li> </li>
@endforeach
@endif @endif
@endforeach @endforeach
@endif @endif

View File

@@ -22,18 +22,20 @@
</svg> </svg>
</div> </div>
@endif @endif
<input {{ $attributes->merge(['class' => $defaultClass . ' pl-10']) }} @required($required) <input value="{{ $value }}" {{ $attributes->merge(['class' => $defaultClass . ' pl-10']) }}
wire:model={{ $id }} wire:dirty.class.remove='text-white' wire:dirty.class="input-warning" @required($required) @if ($id !== 'null') wire:model={{ $id }} @endif
wire:loading.attr="disabled" type="{{ $type }}" @readonly($readonly) @disabled($disabled) wire:dirty.class.remove='text-white' wire:dirty.class="input-warning" wire:loading.attr="disabled"
id="{{ $id }}" name="{{ $name }}" placeholder="{{ $attributes->get('placeholder') }}" type="{{ $type }}" @readonly($readonly) @disabled($disabled) id="{{ $id }}"
name="{{ $name }}" placeholder="{{ $attributes->get('placeholder') }}"
aria-placeholder="{{ $attributes->get('placeholder') }}"> aria-placeholder="{{ $attributes->get('placeholder') }}">
</div> </div>
@else @else
<input {{ $attributes->merge(['class' => $defaultClass]) }} @required($required) @readonly($readonly) <input @if ($value) value="{{ $value }}" @endif
wire:model={{ $id }} wire:dirty.class.remove='text-white' wire:dirty.class="input-warning" {{ $attributes->merge(['class' => $defaultClass]) }} @required($required) @readonly($readonly)
@if ($id !== 'null') wire:model={{ $id }} @endif wire:dirty.class.remove='text-white' wire:dirty.class="input-warning"
wire:loading.attr="disabled" type="{{ $type }}" @disabled($disabled) wire:loading.attr="disabled" type="{{ $type }}" @disabled($disabled)
id="{{ $id }}" name="{{ $name }}" placeholder="{{ $attributes->get('placeholder') }}"> @if ($id !== 'null') id={{ $id }} @endif name="{{ $name }}" placeholder="{{ $attributes->get('placeholder') }}">
@endif @endif
@if (!$label && $helper) @if (!$label && $helper)
<x-helper :helper="$helper" /> <x-helper :helper="$helper" />

View File

@@ -1,4 +1,4 @@
<div class="navbar-main"> <div class="navbar-main" x-data>
<a wire:navigate class="{{ request()->routeIs('project.service.configuration') ? 'text-white' : '' }}" <a wire:navigate class="{{ request()->routeIs('project.service.configuration') ? 'text-white' : '' }}"
href="{{ route('project.service.configuration', $parameters) }}"> href="{{ route('project.service.configuration', $parameters) }}">
<button>Configuration</button> <button>Configuration</button>
@@ -27,6 +27,16 @@
</button> </button>
@endif @endif
@if (serviceStatus($service) === 'running') @if (serviceStatus($service) === 'running')
<button wire:click='restart' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
<svg class="w-5 h-5 text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2">
<path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
<path d="M20 4v5h-5" />
</g>
</svg>
Pull Latest Images & Restart
</button>
<button wire:click='stop' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400"> <button wire:click='stop' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" stroke-width="2" <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" stroke-width="2"
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
@@ -58,3 +68,11 @@
</button> </button>
@endif @endif
</div> </div>
@script
<script>
$wire.on('image-pulled', () => {
startService.showModal();
});
</script>
@endscript

View File

@@ -44,7 +44,6 @@
</dialog> </dialog>
<x-toaster-hub /> <x-toaster-hub />
<x-version class="fixed left-2 bottom-1" /> <x-version class="fixed left-2 bottom-1" />
<script data-navigate-once> <script data-navigate-once>
@auth @auth
window.Pusher = Pusher; window.Pusher = Pusher;
@@ -53,8 +52,8 @@
cluster: "{{ env('PUSHER_HOST') }}" || window.location.hostname, cluster: "{{ env('PUSHER_HOST') }}" || window.location.hostname,
key: "{{ env('PUSHER_APP_KEY') }}" || 'coolify', key: "{{ env('PUSHER_APP_KEY') }}" || 'coolify',
wsHost: "{{ env('PUSHER_HOST') }}" || window.location.hostname, wsHost: "{{ env('PUSHER_HOST') }}" || window.location.hostname,
wsPort: "{{ env('PUSHER_PORT') }}" || 6001, wsPort: "{{ getRealtime() }}",
wssPort: "{{ env('PUSHER_PORT') }}" || 6001, wssPort: "{{ getRealtime() }}",
forceTLS: false, forceTLS: false,
encrypted: true, encrypted: true,
enableStats: false, enableStats: false,

View File

@@ -1,6 +1,7 @@
@extends('layouts.base') @extends('layouts.base')
@section('body') @section('body')
@parent @parent
<x-navbar-subscription />
<main> <main>
{{ $slot }} {{ $slot }}
</main> </main>

View File

@@ -69,7 +69,7 @@
@if ($servers->count() === 1) @if ($servers->count() === 1)
<div class="grid grid-cols-1 gap-2"> <div class="grid grid-cols-1 gap-2">
@else @else
<div class="grid grid-cols-3 gap-2"> <div class="grid grid-cols-1 gap-2 xl:grid-cols-2">
@endif @endif
@foreach ($servers as $server) @foreach ($servers as $server)
<a wire:navigate href="{{ route('server.show', ['server_uuid' => data_get($server, 'uuid')]) }}" <a wire:navigate href="{{ route('server.show', ['server_uuid' => data_get($server, 'uuid')]) }}"

View File

@@ -1,12 +1,12 @@
<div class="min-h-screen hero"> <div class="min-h-screen hero">
<div class="w-96 min-w-fit"> <div class="w-96 min-w-fit">
<div class="flex flex-col items-center pb-8"> <div class="flex flex-col items-center">
<a href="{{ route('dashboard') }}"> <a href="{{ route('dashboard') }}">
<div class="text-5xl font-bold tracking-tight text-center text-white">Coolify</div> <div class="text-5xl font-bold tracking-tight text-center text-white">Coolify</div>
</a> </a>
</div> </div>
<div class="flex items-center justify-center pb-4 text-center"> <div class="flex items-center justify-center pb-4 text-center">
<h2>Set your initial password</h2> Set your initial password
</div> </div>
<form class="flex flex-col gap-2" wire:submit='submit'> <form class="flex flex-col gap-2" wire:submit='submit'>
<x-forms.input id="email" type="email" placeholder="Email" readonly label="Email" /> <x-forms.input id="email" type="email" placeholder="Email" readonly label="Email" />

View File

@@ -1,36 +1,36 @@
<form wire:submit='clone'> <form wire:submit='clone'>
<div class="flex flex-col"> <div class="flex flex-col">
<div class="flex gap-2">
<h1>Clone</h1> <h1>Clone</h1>
<x-forms.button type="submit">Clone to a New Project</x-forms.button> <div class="subtitle ">Quickly clone all resources to a new project</div>
</div>
<div class="subtitle ">Quickly clone a project</div>
</div> </div>
<div class="flex items-end gap-2">
<x-forms.input required id="newProjectName" label="New Project Name" /> <x-forms.input required id="newProjectName" label="New Project Name" />
<x-forms.button type="submit">Clone</x-forms.button>
</div>
<h3 class="pt-4 pb-2">Servers</h3> <h3 class="pt-4 pb-2">Servers</h3>
<div class="grid gap-2 lg:grid-cols-3"> <div class="flex flex-col gap-4">
@foreach ($servers as $srv) @foreach ($servers->sortBy('id') as $server)
<div wire:click="selectServer('{{ $srv->id }}')" <div class="p-4 border border-coolgray-500">
class="cursor-pointer box-without-bg bg-coolgray-200 group" <h3>{{ $server->name }}</h3>
:class="'{{ $selectedServer === $srv->id }}' && 'bg-coollabs'"> <h5>{{ $server->description }}</h5>
<div class="flex flex-col mx-6"> <div class="pt-4 pb-2">Docker Networks</div>
<div :class="'{{ $selectedServer === $srv->id }}' && 'text-white'"> {{ $srv->name }}</div> <div class="grid grid-cols-1 gap-2 pb-4 lg:grid-cols-4">
@isset($selectedServer) @foreach ($server->destinations() as $destination)
<div :class="'{{ $selectedServer === $srv->id }}' && 'text-white pt-2 text-xs font-bold'"> <div class="cursor-pointer box-without-bg bg-coolgray-200 group"
{{ $srv->description }}</div> :class="'{{ $selectedDestination === $destination->id }}' && 'bg-coollabs text-white'"
@else wire:click="selectServer('{{ $server->id }}', '{{ $destination->id }}')">
<div class="description"> {{ $destination->name }}
{{ $srv->description }}</div> </div>
@endisset @endforeach
</div> </div>
</div> </div>
@endforeach @endforeach
</div> </div>
<h3 class="pt-4 pb-2">Resources</h3> <h3 class="pt-4 pb-2">Resources</h3>
<div class="grid grid-cols-1 gap-2"> <div class="grid grid-cols-1 gap-2 p-4 border border-coolgray-500">
@foreach ($environment->applications->sortBy('name') as $application) @foreach ($environment->applications->sortBy('name') as $application)
<div class="p-2 border border-coolgray-200"> <div>
<div class="flex flex-col"> <div class="flex flex-col">
<div class="font-bold text-white">{{ $application->name }}</div> <div class="font-bold text-white">{{ $application->name }}</div>
<div class="description">{{ $application->description }}</div> <div class="description">{{ $application->description }}</div>
@@ -38,7 +38,7 @@
</div> </div>
@endforeach @endforeach
@foreach ($environment->databases()->sortBy('name') as $database) @foreach ($environment->databases()->sortBy('name') as $database)
<div class="p-2 border border-coolgray-200"> <div>
<div class="flex flex-col"> <div class="flex flex-col">
<div class="font-bold text-white">{{ $database->name }}</div> <div class="font-bold text-white">{{ $database->name }}</div>
<div class="description">{{ $database->description }}</div> <div class="description">{{ $database->description }}</div>
@@ -46,7 +46,7 @@
</div> </div>
@endforeach @endforeach
@foreach ($environment->services->sortBy('name') as $service) @foreach ($environment->services->sortBy('name') as $service)
<div class="p-2 border border-coolgray-200"> <div>
<div class="flex flex-col"> <div class="flex flex-col">
<div class="font-bold text-white">{{ $service->name }}</div> <div class="font-bold text-white">{{ $service->name }}</div>
<div class="description">{{ $service->description }}</div> <div class="description">{{ $service->description }}</div>

View File

@@ -230,14 +230,7 @@
<li class="step step-secondary">Select a Server</li> <li class="step step-secondary">Select a Server</li>
<li class="step step-secondary">Select a Destination</li> <li class="step step-secondary">Select a Destination</li>
</ul> </ul>
<a wire:navigate href="{{ route('destination.new', ['server_id' => $server_id]) }}"
class="items-center justify-center pb-10 text-center box-without-bg group bg-coollabs hover:bg-coollabs-100">
<div class="flex flex-col mx-6 ">
<div class="font-bold text-white">
+ Add New
</div>
</div>
</a>
<div class="flex flex-col justify-center gap-2 text-left xl:flex-row xl:flex-wrap"> <div class="flex flex-col justify-center gap-2 text-left xl:flex-row xl:flex-wrap">
@foreach ($standaloneDockers as $standaloneDocker) @foreach ($standaloneDockers as $standaloneDocker)
@@ -260,6 +253,14 @@
</div> </div>
</div> </div>
@endforeach @endforeach
<a wire:navigate href="{{ route('destination.new', ['server_id' => $server_id]) }}"
class="items-center justify-center pb-10 text-center box-without-bg group bg-coollabs hover:bg-coollabs-100">
<div class="flex flex-col mx-6 ">
<div class="font-bold text-white">
+ Add New
</div>
</div>
</a>
</div> </div>
@endif @endif
@if ($current_step === 'existing-postgresql') @if ($current_step === 'existing-postgresql')

View File

@@ -44,15 +44,18 @@
$application->status)->contains(['running']), $application->status)->contains(['running']),
'border-l border-dashed border-warning' => Str::of( 'border-l border-dashed border-warning' => Str::of(
$application->status)->contains(['starting']), $application->status)->contains(['starting']),
'flex gap-2 box group', 'flex gap-2 box-without-bg bg-coolgray-100 hover:text-neutral-300 group',
])> ])>
<a wire:navigate class="flex flex-col flex-1 group-hover:text-white hover:no-underline" <div class="flex flex-row w-full">
href="{{ route('project.service.show', [...$parameters, 'service_name' => $application->name]) }}"> <div class="flex flex-col flex-1">
<div class="pb-2">
@if ($application->human_name) @if ($application->human_name)
{{ Str::headline($application->human_name) }} {{ Str::headline($application->human_name) }}
@else @else
{{ Str::headline($application->name) }} {{ Str::headline($application->name) }}
@endif @endif
<span class="text-xs">({{ $application->image }})</span>
</div>
@if ($application->configuration_required) @if ($application->configuration_required)
<span class="text-xs text-error">(configuration required)</span> <span class="text-xs text-error">(configuration required)</span>
@endif @endif
@@ -63,8 +66,23 @@
<span class="text-xs">{{ Str::limit($application->fqdn, 60) }}</span> <span class="text-xs">{{ Str::limit($application->fqdn, 60) }}</span>
@endif @endif
<div class="text-xs">{{ $application->status }}</div> <div class="text-xs">{{ $application->status }}</div>
</div>
<div class="flex items-center px-4">
<a wire:navigate
class="flex flex-col flex-1 group-hover:text-white hover:no-underline"
href="{{ route('project.service.show', [...$parameters, 'service_name' => $application->name]) }}">
<svg xmlns="http://www.w3.org/2000/svg" class="icon hover:text-warning"
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="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z" />
<path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" />
</svg>
</a> </a>
</div> </div>
</div>
</div>
@endforeach @endforeach
@foreach ($databases as $database) @foreach ($databases as $database)
<div @class([ <div @class([
@@ -74,15 +92,20 @@
$database->status)->contains(['running']), $database->status)->contains(['running']),
'border-l border-dashed border-warning' => Str::of( 'border-l border-dashed border-warning' => Str::of(
$database->status)->contains(['restarting']), $database->status)->contains(['restarting']),
'flex gap-2 box group', 'flex gap-2 box-without-bg bg-coolgray-100 hover:text-neutral-300 group',
])> ])>
<a wire:navigate class="flex flex-col flex-1 group-hover:text-white hover:no-underline"
href="{{ route('project.service.show', [...$parameters, 'service_name' => $database->name]) }}">
<div class="flex flex-row w-full">
<div class="flex flex-col flex-1">
<div class="pb-2">
@if ($database->human_name) @if ($database->human_name)
{{ Str::headline($database->human_name) }} {{ Str::headline($database->human_name) }}
@else @else
{{ Str::headline($database->name) }} {{ Str::headline($database->name) }}
@endif @endif
<span class="text-xs">({{ $database->image }})</span>
</div>
@if ($database->configuration_required) @if ($database->configuration_required)
<span class="text-xs text-error">(configuration required)</span> <span class="text-xs text-error">(configuration required)</span>
@endif @endif
@@ -90,8 +113,23 @@
<span class="text-xs">{{ Str::limit($database->description, 60) }}</span> <span class="text-xs">{{ Str::limit($database->description, 60) }}</span>
@endif @endif
<div class="text-xs">{{ $database->status }}</div> <div class="text-xs">{{ $database->status }}</div>
</div>
<div class="flex items-center px-4">
<a wire:navigate
class="flex flex-col flex-1 group-hover:text-white hover:no-underline"
href="{{ route('project.service.show', [...$parameters, 'service_name' => $database->name]) }}">
<svg xmlns="http://www.w3.org/2000/svg" class="icon hover:text-warning"
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="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z" />
<path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" />
</svg>
</a> </a>
</div> </div>
</div>
</div>
@endforeach @endforeach
</div> </div>
</div> </div>

View File

@@ -21,7 +21,12 @@
</form> </form>
<h2 class="pt-6">Advanced</h2> <h2 class="pt-6">Advanced</h2>
<div class="flex flex-col py-6 text-right w-80"> <div class="flex flex-col py-6 text-right w-80">
@if(!is_null(env('AUTOUPDATE', null)))
<x-forms.checkbox instantSave helper="AUTOUPDATE is set in .env file, you need to modify it there." disabled
id="is_auto_update_enabled" label="Auto Update Coolify" />
@else
<x-forms.checkbox instantSave id="is_auto_update_enabled" label="Auto Update Coolify" /> <x-forms.checkbox instantSave id="is_auto_update_enabled" label="Auto Update Coolify" />
@endif
<x-forms.checkbox instantSave id="is_registration_enabled" label="Registration Allowed" /> <x-forms.checkbox instantSave id="is_registration_enabled" label="Registration Allowed" />
<x-forms.checkbox instantSave id="do_not_track" label="Do Not Track" /> <x-forms.checkbox instantSave id="do_not_track" label="Do Not Track" />
@if ($next_channel) @if ($next_channel)

View File

@@ -18,8 +18,12 @@
<td>{{ $invite->email }}</td> <td>{{ $invite->email }}</td>
<td>{{ $invite->via }}</td> <td>{{ $invite->via }}</td>
<td>{{ $invite->role }}</td> <td>{{ $invite->role }}</td>
<td x-on:click="copyToClipboard('{{ $invite->link }}')"> <td class="flex gap-2" x-data="checkProtocol">
<x-forms.button>Copy Invitation Link</x-forms.button> <template x-if="isHttps">
<x-forms.button x-on:click="copyToClipboard('{{ $invite->link }}')">Copy Invitation
Link</x-forms.button>
</template>
<x-forms.input id="null" type="password" value="{{ $invite->link }}" />
</td> </td>
<td> <td>
<x-forms.button wire:click.prevent='deleteInvitation({{ $invite->id }})'>Revoke <x-forms.button wire:click.prevent='deleteInvitation({{ $invite->id }})'>Revoke
@@ -31,6 +35,15 @@
</tbody> </tbody>
</table> </table>
</div> </div>
@endif @endif
</div> </div>
@script
<script>
Alpine.data('checkProtocol', () => {
return {
isHttps: window.location.protocol === 'https:'
}
})
</script>
@endscript

View File

@@ -240,3 +240,10 @@ Route::middleware(['auth'])->group(function () {
]); ]);
})->name('destination.show'); })->name('destination.show');
}); });
Route::any('/{any}', function () {
if (auth()->user()) {
return redirect('/');
}
return redirect('/login');
})->where('any', '.*');

View File

@@ -65,9 +65,9 @@ Route::get('/source/github/install', function () {
}); });
Route::post('/source/gitlab/events/manual', function () { Route::post('/source/gitlab/events/manual', function () {
try { try {
$return_payloads = collect([]);
$payload = request()->collect(); $payload = request()->collect();
$headers = request()->headers->all(); $headers = request()->headers->all();
ray($payload, $headers);
$x_gitlab_token = data_get($headers, 'x-gitlab-token.0'); $x_gitlab_token = data_get($headers, 'x-gitlab-token.0');
$x_gitlab_event = data_get($payload, 'object_kind'); $x_gitlab_event = data_get($payload, 'object_kind');
if ($x_gitlab_event === 'push') { if ($x_gitlab_event === 'push') {
@@ -77,20 +77,27 @@ Route::post('/source/gitlab/events/manual', function () {
$branch = Str::after($branch, 'refs/heads/'); $branch = Str::after($branch, 'refs/heads/');
} }
if (!$branch) { if (!$branch) {
return response('Nothing to do. No branch found in the request.'); $return_payloads->push([
'status' => 'failed',
'message' => 'Nothing to do. No branch found in the request.',
]);
return response($return_payloads);
} }
ray('Manual Webhook GitLab Push Event with branch: ' . $branch); ray('Manual Webhook GitLab Push Event with branch: ' . $branch);
} }
if ($x_gitlab_event === 'merge_request') { if ($x_gitlab_event === 'merge_request') {
$action = data_get($payload, 'object_attributes.action'); $action = data_get($payload, 'object_attributes.action');
ray($action);
$branch = data_get($payload, 'object_attributes.source_branch'); $branch = data_get($payload, 'object_attributes.source_branch');
$base_branch = data_get($payload, 'object_attributes.target_branch'); $base_branch = data_get($payload, 'object_attributes.target_branch');
$full_name = data_get($payload, 'project.path_with_namespace'); $full_name = data_get($payload, 'project.path_with_namespace');
$pull_request_id = data_get($payload, 'object_attributes.iid'); $pull_request_id = data_get($payload, 'object_attributes.iid');
$pull_request_html_url = data_get($payload, 'object_attributes.url'); $pull_request_html_url = data_get($payload, 'object_attributes.url');
if (!$branch) { if (!$branch) {
return response('Nothing to do. No branch found in the request.'); $return_payloads->push([
'status' => 'failed',
'message' => 'Nothing to do. No branch found in the request.',
]);
return response($return_payloads);
} }
ray('Webhook GitHub Pull Request Event with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id); ray('Webhook GitHub Pull Request Event with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id);
} }
@@ -98,23 +105,41 @@ Route::post('/source/gitlab/events/manual', function () {
if ($x_gitlab_event === 'push') { if ($x_gitlab_event === 'push') {
$applications = $applications->where('git_branch', $branch)->get(); $applications = $applications->where('git_branch', $branch)->get();
if ($applications->isEmpty()) { if ($applications->isEmpty()) {
return response("Nothing to do. No applications found with deploy key set, branch is '$branch' and Git Repository name has $full_name."); $return_payloads->push([
'status' => 'failed',
'message' => "Nothing to do. No applications found with deploy key set, branch is '$branch' and Git Repository name has $full_name.",
]);
return response($return_payloads);
} }
} }
if ($x_gitlab_event === 'merge_request') { if ($x_gitlab_event === 'merge_request') {
$applications = $applications->where('git_branch', $base_branch)->get(); $applications = $applications->where('git_branch', $base_branch)->get();
if ($applications->isEmpty()) { if ($applications->isEmpty()) {
return response("Nothing to do. No applications found with branch '$base_branch'."); $return_payloads->push([
'status' => 'failed',
'message' => "Nothing to do. No applications found with branch '$base_branch'.",
]);
return response($return_payloads);
} }
} }
foreach ($applications as $application) { foreach ($applications as $application) {
$webhook_secret = data_get($application, 'manual_webhook_secret_gitlab'); $webhook_secret = data_get($application, 'manual_webhook_secret_gitlab');
if ($webhook_secret !== $x_gitlab_token) { if ($webhook_secret !== $x_gitlab_token) {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Invalid token.',
]);
ray('Invalid signature'); ray('Invalid signature');
continue; continue;
} }
$isFunctional = $application->destination->server->isFunctional(); $isFunctional = $application->destination->server->isFunctional();
if (!$isFunctional) { if (!$isFunctional) {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Server is not functional',
]);
ray('Server is not functional: ' . $application->destination->server->name); ray('Server is not functional: ' . $application->destination->server->name);
continue; continue;
} }
@@ -129,6 +154,11 @@ Route::post('/source/gitlab/events/manual', function () {
is_webhook: true is_webhook: true
); );
} else { } else {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Deployments disabled',
]);
ray('Deployments disabled for ' . $application->name); ray('Deployments disabled for ' . $application->name);
} }
} }
@@ -154,25 +184,48 @@ Route::post('/source/gitlab/events/manual', function () {
git_type: 'gitlab' git_type: 'gitlab'
); );
ray('Deploying preview for ' . $application->name . ' with branch ' . $branch . ' and base branch ' . $base_branch . ' and pull request id ' . $pull_request_id); ray('Deploying preview for ' . $application->name . ' with branch ' . $branch . ' and base branch ' . $base_branch . ' and pull request id ' . $pull_request_id);
return response('Preview Deployment queued.'); $return_payloads->push([
'application' => $application->name,
'status' => 'success',
'message' => 'Preview Deployment queued',
]);
} else { } else {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Preview deployments disabled',
]);
ray('Preview deployments disabled for ' . $application->name); ray('Preview deployments disabled for ' . $application->name);
return response('Nothing to do. Preview Deployments disabled.');
} }
} } else if ($action === 'closed') {
if ($action === 'closed') {
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if ($found) { if ($found) {
$found->delete(); $found->delete();
$container_name = generateApplicationContainerName($application, $pull_request_id); $container_name = generateApplicationContainerName($application, $pull_request_id);
// ray('Stopping container: ' . $container_name); // ray('Stopping container: ' . $container_name);
instant_remote_process(["docker rm -f $container_name"], $application->destination->server); instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
return response('Preview Deployment closed.'); $return_payloads->push([
'application' => $application->name,
'status' => 'success',
'message' => 'Preview Deployment closed',
]);
return response($return_payloads);
} }
return response('Nothing to do. No Preview Deployment found'); $return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'No Preview Deployment found',
]);
} else {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'No action found. Contact us for debugging.',
]);
} }
} }
} }
return response($return_payloads);
} catch (Exception $e) { } catch (Exception $e) {
ray($e->getMessage()); ray($e->getMessage());
return handleError($e); return handleError($e);

View File

@@ -1,12 +1,17 @@
#!/bin/bash #!/bin/bash
## Do not modify this file. You will lose the ability to install and auto-update! ## Do not modify this file. You will lose the ability to install and auto-update!
set -e # Exit immediately if a command exits with a non-zero status
## $1 could be empty, so we need to disable this check
#set -u # Treat unset variables as an error and exit
set -o pipefail # Cause a pipeline to return the status of the last command that exited with a non-zero status
VERSION="1.1.0" VERSION="1.1.0"
DOCKER_VERSION="24.0" DOCKER_VERSION="24.0"
CDN="https://cdn.coollabs.io/coolify" CDN="https://cdn.coollabs.io/coolify"
OS_TYPE=$(cat /etc/os-release | grep -w "ID" | cut -d "=" -f 2 | tr -d '"') OS_TYPE=$(grep -w "ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"')
OS_VERSION=$(cat /etc/os-release | grep -w "VERSION_ID" | cut -d "=" -f 2 | tr -d '"') OS_VERSION=$(grep -w "VERSION_ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"')
LATEST_VERSION=$(curl --silent $CDN/versions.json | grep -i version | sed -n '2p' | xargs | awk '{print $2}' | tr -d ',') LATEST_VERSION=$(curl --silent $CDN/versions.json | grep -i version | sed -n '2p' | xargs | awk '{print $2}' | tr -d ',')
DATE=$(date +"%Y%m%d-%H%M%S") DATE=$(date +"%Y%m%d-%H%M%S")
@@ -26,6 +31,8 @@ esac
# Ovewrite LATEST_VERSION if user pass a version number # Ovewrite LATEST_VERSION if user pass a version number
if [ "$1" != "" ]; then if [ "$1" != "" ]; then
LATEST_VERSION=$1 LATEST_VERSION=$1
LATEST_VERSION="${LATEST_VERSION,,}"
LATEST_VERSION="${LATEST_VERSION#v}"
fi fi
echo -e "-------------" echo -e "-------------"
@@ -79,8 +86,8 @@ fi
echo -e "-------------" echo -e "-------------"
echo -e "Check Docker Configuration..." echo -e "Check Docker Configuration..."
mkdir -p /etc/docker mkdir -p /etc/docker
# shellcheck disable=SC2015
test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json /etc/docker/daemon.json.original-$DATE || cat >/etc/docker/daemon.json <<EOL test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json /etc/docker/daemon.json.original-"$DATE" || cat >/etc/docker/daemon.json <<EOL
{ {
"log-driver": "json-file", "log-driver": "json-file",
"log-opts": { "log-opts": {
@@ -98,11 +105,15 @@ cat >/etc/docker/daemon.json.coolify <<EOL
} }
} }
EOL EOL
cat <<<$(jq . /etc/docker/daemon.json.coolify) >/etc/docker/daemon.json.coolify TEMP_FILE=$(mktemp)
cat <<<$(jq -s '.[0] * .[1]' /etc/docker/daemon.json /etc/docker/daemon.json.coolify) >/etc/docker/daemon.json if ! jq -s '.[0] * .[1]' /etc/docker/daemon.json /etc/docker/daemon.json.coolify >"$TEMP_FILE"; then
echo "Error merging JSON files"
exit 1
fi
mv "$TEMP_FILE" /etc/docker/daemon.json
if [ -s /etc/docker/daemon.json.original-$DATE ]; then if [ -s /etc/docker/daemon.json.original-"$DATE" ]; then
DIFF=$(diff <(jq --sort-keys . /etc/docker/daemon.json) <(jq --sort-keys . /etc/docker/daemon.json.original-$DATE)) DIFF=$(diff <(jq --sort-keys . /etc/docker/daemon.json) <(jq --sort-keys . /etc/docker/daemon.json.original-"$DATE"))
if [ "$DIFF" != "" ]; then if [ "$DIFF" != "" ]; then
echo "Docker configuration updated, restart docker daemon..." echo "Docker configuration updated, restart docker daemon..."
systemctl restart docker systemctl restart docker
@@ -145,6 +156,14 @@ fi
# Merge .env and .env.production. New values will be added to .env # Merge .env and .env.production. New values will be added to .env
sort -u -t '=' -k 1,1 /data/coolify/source/.env /data/coolify/source/.env.production | sed '/^$/d' >/data/coolify/source/.env.temp && mv /data/coolify/source/.env.temp /data/coolify/source/.env sort -u -t '=' -k 1,1 /data/coolify/source/.env /data/coolify/source/.env.production | sed '/^$/d' >/data/coolify/source/.env.temp && mv /data/coolify/source/.env.temp /data/coolify/source/.env
if [ "$AUTOUPDATE" = "false" ]; then
if ! grep -q "AUTOUPDATE=" /data/coolify/source/.env; then
echo "AUTOUPDATE=false" >>/data/coolify/source/.env
else
sed -i "s|AUTOUPDATE=.*|AUTOUPDATE=false|g" /data/coolify/source/.env
fi
fi
# Generate an ssh key (ed25519) at /data/coolify/ssh/keys/id.root@host.docker.internal # Generate an ssh key (ed25519) at /data/coolify/ssh/keys/id.root@host.docker.internal
if [ ! -f /data/coolify/ssh/keys/id.root@host.docker.internal ]; then if [ ! -f /data/coolify/ssh/keys/id.root@host.docker.internal ]; then
ssh-keygen -t ed25519 -a 100 -f /data/coolify/ssh/keys/id.root@host.docker.internal -q -N "" -C root@coolify ssh-keygen -t ed25519 -a 100 -f /data/coolify/ssh/keys/id.root@host.docker.internal -q -N "" -C root@coolify
@@ -163,11 +182,11 @@ if [ ! -f ~/.ssh/authorized_keys ]; then
addSshKey addSshKey
fi fi
if [ -z "$(grep -w "root@coolify" ~/.ssh/authorized_keys)" ]; then if ! grep -qw "root@coolify" ~/.ssh/authorized_keys; then
addSshKey addSshKey
fi fi
bash /data/coolify/source/upgrade.sh ${LATEST_VERSION:-latest} bash /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}"
echo -e "\nCongratulations! Your Coolify instance is ready to use.\n" echo -e "\nCongratulations! Your Coolify instance is ready to use.\n"
echo "Please visit http://$(curl -4s https://ifconfig.io):8000 to get started." echo "Please visit http://$(curl -4s https://ifconfig.io):8000 to get started."

View File

@@ -20,47 +20,50 @@ function help {
compgen -A function | cat -n compgen -A function | cat -n
} }
function setup:dev { # function dev:init {
docker exec coolify bash -c "composer install" # docker exec coolify bash -c "php artisan migrate --seed"
docker exec coolify bash -c "php artisan key:generate" # echo "Need to update privileges on a few files. I need your password for that."
docker exec coolify bash -c "php artisan migrate:fresh --seed" # sudo chmod -R o+rwx .
sudo chmod -R o+rwx . # }
}
function sync:v3 { # function sync:v3 {
if [ -z "$1" ]; then # if [ -z "$1" ]; then
echo -e "Please provide a version.\n\nExample: run sync:v3 3.12.32" # echo -e "Please provide a version.\n\nExample: run sync:v3 3.12.32"
exit 1 # exit 1
fi # fi
skopeo copy --all docker://ghcr.io/coollabsio/coolify:$1 docker://coollabsio/coolify:$1 # skopeo copy --all docker://ghcr.io/coollabsio/coolify:$1 docker://coollabsio/coolify:$1
} # }
function sync:bunny { function sync:bunny {
php artisan sync:bunny --env=secrets php artisan sync:bunny --env=secrets
} }
function queue { # function queue {
bash spin exec -u webuser coolify php artisan queue:listen # bash spin exec -u webuser coolify php artisan queue:listen
} # }
function horizon { # function horizon {
bash spin exec -u webuser coolify php artisan horizon -vvv # bash spin exec -u webuser coolify php artisan horizon -vvv
} # }
function schedule { # function schedule {
bash spin exec -u webuser coolify php artisan schedule:work # bash spin exec -u webuser coolify php artisan schedule:work
} # }
function schedule:run { # function schedule:run {
bash spin exec -u webuser coolify php artisan schedule:run # bash spin exec -u webuser coolify php artisan schedule:run
} # }
function db { # function db {
bash spin exec -u webuser coolify php artisan db # bash spin exec -u webuser coolify php artisan db
} # }
# function db:seed {
# bash spin exec -u webuser coolify php artisan migrate --seed
# }
function db:migrate { # function db:migrate {
bash spin exec -u webuser coolify php artisan migrate # bash spin exec -u webuser coolify php artisan migrate --step
} # }
function db:reset { function db:reset {
bash spin exec -u webuser coolify php artisan migrate:fresh --seed bash spin exec -u webuser coolify php artisan migrate:fresh --seed
@@ -98,9 +101,9 @@ function tinker {
} }
function build:helper { # function build:helper {
act -W .github/workflows/coolify-helper.yml --secret-file .env.secrets # act -W .github/workflows/coolify-helper.yml --secret-file .env.secrets
} # }
function default { function default {
help help
} }

View File

@@ -4,7 +4,7 @@
"version": "3.12.36" "version": "3.12.36"
}, },
"v4": { "v4": {
"version": "4.0.0-beta.154" "version": "4.0.0-beta.164"
} }
} }
} }