Compare commits

...

75 Commits

Author SHA1 Message Date
Andras Bacsai
81b916724e Merge pull request #1303 from coollabsio/next
v4.0.0-beta.75
2023-10-11 12:06:15 +02:00
Andras Bacsai
9540f60fa2 fix: dashboard goto link 2023-10-11 12:04:30 +02:00
Andras Bacsai
8eb1686125 fix: transactional email link 2023-10-11 12:04:23 +02:00
Andras Bacsai
daf3710a5e fix: add new team button 2023-10-11 12:04:14 +02:00
Andras Bacsai
1067f37e4d fix: deleted team and it is the current one 2023-10-11 12:03:59 +02:00
Andras Bacsai
3b3c0b94e5 version++ 2023-10-11 12:03:39 +02:00
Andras Bacsai
8b0a0d67da Merge pull request #1302 from coollabsio/next
v4.0.0-beta.74
2023-10-11 11:09:19 +02:00
Andras Bacsai
5541c135df feat: proxy logs on the ui 2023-10-11 11:00:40 +02:00
Andras Bacsai
2552cb2208 ui: able to select environment on new resource 2023-10-11 10:19:03 +02:00
Andras Bacsai
f001e9bc34 improve dashboard 2023-10-11 10:08:37 +02:00
Andras Bacsai
f943fdc5be fix: use only ip addresses for servers 2023-10-11 09:57:35 +02:00
Andras Bacsai
a4f1fcba58 move subscription to livewire + show manage subscription button for people already subscribed once 2023-10-11 09:55:05 +02:00
Andras Bacsai
68091b44fc fix: contact link 2023-10-11 09:54:01 +02:00
Andras Bacsai
9f8caac91c dev: coolify proxy access logs exposed in dev 2023-10-11 09:31:30 +02:00
Andras Bacsai
8082dc1a01 fix: use port exposed for reverse proxy 2023-10-11 09:23:31 +02:00
Andras Bacsai
a71cf5bc66 cleanup 2023-10-10 15:36:06 +02:00
Andras Bacsai
0775074509 version++ 2023-10-10 14:34:38 +02:00
Andras Bacsai
242d2fb283 Merge pull request #1300 from coollabsio/next
v4.0.0-beta.73
2023-10-10 14:29:44 +02:00
Andras Bacsai
5646818965 version++ 2023-10-10 14:26:31 +02:00
Andras Bacsai
ffc5320940 fix: backupfailed notification is forced 2023-10-10 14:17:16 +02:00
Andras Bacsai
7c10c55b1c fix: only send email if transactional email set 2023-10-10 14:14:41 +02:00
Andras Bacsai
7c96b6207a Merge pull request #1299 from coollabsio/next
v4.0.0-beta.72
2023-10-10 14:06:11 +02:00
Andras Bacsai
0be8ffbdc9 feat: add dockerfile location 2023-10-10 14:02:43 +02:00
Andras Bacsai
24fa56762e fix: database backups 2023-10-10 13:10:43 +02:00
Andras Bacsai
84d8e35411 fix: tcp proxy for dbs 2023-10-10 11:42:35 +02:00
Andras Bacsai
3d3ccc435c ui: fix 2023-10-10 11:29:33 +02:00
Andras Bacsai
be3b01472e ui: fix 2023-10-10 11:28:57 +02:00
Andras Bacsai
de6f5b1105 fix: goto 2023-10-10 11:27:39 +02:00
Andras Bacsai
14d9c06dcd feat: able to deploy docker images 2023-10-10 11:16:38 +02:00
Andras Bacsai
8abfaa1967 fix: no env goto envs from dashboard 2023-10-10 10:57:56 +02:00
Andras Bacsai
46f7ae9588 ui: updated dashboard 2023-10-10 10:56:11 +02:00
Andras Bacsai
f2c32b9aeb fixes 2023-10-09 20:37:42 +02:00
Andras Bacsai
3dab1eb92e fix: server saving 2023-10-09 20:12:03 +02:00
Andras Bacsai
9c22e01716 wip: dockerimage 2023-10-09 15:49:48 +02:00
Andras Bacsai
d1c47a4062 version++ 2023-10-09 15:22:51 +02:00
Andras Bacsai
6c3f97d9ae Merge pull request #1297 from coollabsio/next
v4.0.0-beta.71
2023-10-09 15:19:35 +02:00
Andras Bacsai
ebd8e2ce40 fix: check connection 2023-10-09 15:08:28 +02:00
Andras Bacsai
b650f3f754 fix: contact docs 2023-10-09 14:52:24 +02:00
Andras Bacsai
f33ba40478 fix help 2023-10-09 14:48:51 +02:00
Andras Bacsai
5cea9c4603 isInstanceAdmin() 2023-10-09 14:38:44 +02:00
Andras Bacsai
d32832fabc update 2023-10-09 14:32:30 +02:00
Andras Bacsai
165f0a3d4a feat: add email verification for cloud 2023-10-09 14:20:55 +02:00
Andras Bacsai
f14995200b fix: do not reset unreachable count 2023-10-09 12:41:21 +02:00
Andras Bacsai
12bb2ecc4a update 2023-10-09 12:13:30 +02:00
Andras Bacsai
933ec5741d fix: server unreachable count 2023-10-09 12:07:42 +02:00
Andras Bacsai
8004a40139 updates 2023-10-09 11:49:38 +02:00
Andras Bacsai
a6209fbe5c package updates 2023-10-09 11:45:23 +02:00
Andras Bacsai
b47c327b55 Merge pull request #1296 from coollabsio/next
fix: small
2023-10-09 11:24:06 +02:00
Andras Bacsai
25434a7acd fix: small 2023-10-09 11:23:51 +02:00
Andras Bacsai
0de042dbac Merge pull request #1295 from coollabsio/next
v4.0.0-beta.70
2023-10-09 11:23:32 +02:00
Andras Bacsai
eb9e2203b0 fix: fqdn could be null 2023-10-09 11:10:04 +02:00
Andras Bacsai
dcaa7a6ad7 fix: server validation process 2023-10-09 11:00:18 +02:00
Andras Bacsai
5b584a6c6d Merge pull request #1292 from vaporii/main
corrected 'orAGnize' to 'orGAnize'
2023-10-09 09:24:16 +02:00
vaporii
c40ea6f1da corrected 'orAGnize' to 'orGAnize' 2023-10-08 15:01:09 -05:00
Andras Bacsai
61a7b9ac94 fix: able to set base dir for Dockerfile build pack 2023-10-07 12:54:19 +02:00
Andras Bacsai
9e81416fef fix: better unreachable/revived server statuses 2023-10-07 00:51:01 +02:00
Andras Bacsai
c58706e3e4 Merge pull request #1291 from adiologydev/next
fix(create): flex wrap on server & network selection
2023-10-06 20:55:15 +02:00
Andras Bacsai
45bca8649b fix: public repository names 2023-10-06 20:48:03 +02:00
Andras Bacsai
b095b88281 fix: contribution guide 2023-10-06 20:45:35 +02:00
Aditya Tripathi
cb41584137 fix(create): flex wrap on server & network selection 2023-10-06 22:31:20 +05:30
Andras Bacsai
6659153804 version++
fix: unreachable servers
2023-10-06 15:32:46 +02:00
Andras Bacsai
44c429a224 Merge pull request #1290 from coollabsio/next
v4.0.0-beta.69
2023-10-06 14:54:14 +02:00
Andras Bacsai
fe9c501c1d ui 2023-10-06 14:51:50 +02:00
Andras Bacsai
4e94b4a0c1 fix: private repository 2023-10-06 14:50:49 +02:00
Andras Bacsai
2f4d7c0e43 feat: deploy private repo with ssh key 2023-10-06 14:39:30 +02:00
Andras Bacsai
e443fc394a fix: select branch on other git 2023-10-06 13:52:15 +02:00
Andras Bacsai
5b56c50f03 feat: init version of any git deployment 2023-10-06 13:46:42 +02:00
Andras Bacsai
3adeb2f73f fix: set smtp notifications on by default 2023-10-06 12:32:38 +02:00
Andras Bacsai
9eaa13a08a update 2023-10-06 11:17:35 +02:00
Andras Bacsai
df5a4a9667 fix: contact details in emails
fix: pricing plans
2023-10-06 11:16:29 +02:00
Andras Bacsai
26048339d6 Merge pull request #1289 from coollabsio/next
v4.0.0-beta.68
2023-10-06 10:58:12 +02:00
Andras Bacsai
d85af3fefc fix: ui for self-hosted email settings 2023-10-06 10:57:35 +02:00
Andras Bacsai
575338609b fix 2023-10-06 10:47:48 +02:00
Andras Bacsai
d32e43ef37 fix: test emails only available for user owned smtp/resend 2023-10-06 10:42:32 +02:00
Andras Bacsai
a96ef1bfab use instance settigns by default 2023-10-06 10:30:32 +02:00
146 changed files with 1980 additions and 776 deletions

View File

@@ -1,11 +1,3 @@
############################################################################################################
# Development Environment
# User and group id for the user that will run the application inside the container
# Run in your terminal: `id -u` and `id -g` and that's the results
USERID=
GROUPID=
############################################################################################################
APP_NAME=Coolify-localhost
APP_ID=development
APP_ENV=local
@@ -13,6 +5,7 @@ APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
APP_PORT=8000
MUX_ENABLED=false
DUSK_DRIVER_URL=http://selenium:4444

View File

@@ -15,11 +15,12 @@ You can ask for guidance anytime on our
## 2) Set your environment variables
- Copy [.env.development.example](./.env.development.example) to .env.
- If necessary, set `USERID` & `GROUPID` accordingly (read in .env file).
## 3) Start & setup Coolify
- 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.
- 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

View File

@@ -36,7 +36,7 @@ You can find the installation script [here](./scripts/install.sh).
## Support
Contact us [here](https://docs.coollabs.io/contact).
Contact us [here](https://coolify.io/docs/contact).
## Recognitions

View File

@@ -58,6 +58,11 @@ class CreateNewUser implements CreatesNewUsers
'password' => Hash::make($input['password']),
]);
$team = $user->teams()->first();
if (isCloud()) {
$user->sendVerificationEmail();
} else {
$user->markEmailAsVerified();
}
}
// Set session variable
session(['currentTeam' => $user->currentTeam = $team]);

View File

@@ -2,12 +2,14 @@
namespace App\Actions\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Server;
use App\Models\StandaloneDocker;
class InstallDocker
{
public function __invoke(Server $server)
use AsAction;
public function handle(Server $server)
{
$dockerVersion = '24.0';
$config = base64_encode('{

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Console\Commands;
use App\Models\Server;
use Illuminate\Console\Command;
class Cloud extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'cloud:unused-servers';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Get Unused Servers from Cloud';
/**
* Execute the console command.
*/
public function handle()
{
Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended',true)->each(function($server){
$this->info($server->name);
});
}
}

View File

@@ -32,7 +32,6 @@ class Kernel extends ConsoleKernel
$schedule->command('horizon:snapshot')->everyFiveMinutes();
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
$schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
// $schedule->job(new DockerCleanupJob)->everyTenMinutes()->onOneServer();
$this->instance_auto_update($schedule);
$this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
@@ -48,7 +47,11 @@ class Kernel extends ConsoleKernel
}
private function check_resources($schedule)
{
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true);
if (isCloud()) {
$servers = Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false);
} else {
$servers = Server::all();
}
foreach ($servers as $server) {
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
}

View File

@@ -46,15 +46,6 @@ class Controller extends BaseController
}
return redirect()->route('login')->with('error', 'Invalid credentials.');
}
public function subscription()
{
if (!isCloud()) {
abort(404);
}
return view('subscription.index', [
'settings' => InstanceSettings::get(),
]);
}
public function license()
{

View File

@@ -1,32 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\PrivateKey;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
class ServerController extends Controller
{
use AuthorizesRequests, ValidatesRequests;
public function new_server()
{
$privateKeys = PrivateKey::ownedByCurrentTeam()->get();
if (!isCloud()) {
return view('server.create', [
'limit_reached' => false,
'private_keys' => $privateKeys,
]);
}
$team = currentTeam();
$servers = $team->servers->count();
['serverLimit' => $serverLimit] = $team->limits;
$limit_reached = $servers >= $serverLimit;
return view('server.create', [
'limit_reached' => $limit_reached,
'private_keys' => $privateKeys,
]);
}
}

View File

@@ -38,8 +38,7 @@ class Kernel extends HttpKernel
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\CheckForcePasswordReset::class,
\App\Http\Middleware\IsSubscriptionValid::class,
\App\Http\Middleware\IsBoardingFlow::class,
\App\Http\Middleware\DecideWhatToDoWithUser::class,
],

View File

@@ -164,7 +164,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
{
$this->validate([
'remoteServerName' => 'required',
'remoteServerHost' => 'required',
'remoteServerHost' => 'required|ip',
'remoteServerPort' => 'required|integer',
'remoteServerUser' => 'required',
]);
@@ -220,7 +220,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
public function installDocker()
{
$this->dockerInstallationStarted = true;
$activity = resolve(InstallDocker::class)($this->createdServer);
$activity = InstallDocker::run($this->createdServer);
$this->emit('newMonitorActivity', $activity->id);
}
public function dockerInstalledOrSkipped()

View File

@@ -9,21 +9,13 @@ use Livewire\Component;
class Dashboard extends Component
{
public int $projects = 0;
public int $servers = 0;
public int $s3s = 0;
public int $resources = 0;
public $projects = [];
public $servers = [];
public function mount()
{
$this->servers = Server::ownedByCurrentTeam()->get()->count();
$this->s3s = S3Storage::ownedByCurrentTeam()->get()->count();
$projects = Project::ownedByCurrentTeam()->get();
foreach ($projects as $project) {
$this->resources += $project->applications->count();
$this->resources += $project->postgresqls->count();
}
$this->projects = $projects->count();
$this->servers = Server::ownedByCurrentTeam()->get();
$this->projects = Project::ownedByCurrentTeam()->get();
}
// public function getIptables()
// {

View File

@@ -17,10 +17,10 @@ class General extends Component
public Application $application;
public Collection $services;
public string $name;
public string|null $fqdn;
public ?string $fqdn = null;
public string $git_repository;
public string $git_branch;
public string|null $git_commit_sha;
public ?string $git_commit_sha = null;
public string $build_pack;
public bool $is_static;
@@ -49,6 +49,9 @@ class General extends Component
'application.ports_exposes' => 'required',
'application.ports_mappings' => 'nullable',
'application.dockerfile' => 'nullable',
'application.docker_registry_image_name' => 'nullable',
'application.docker_registry_image_tag' => 'nullable',
'application.dockerfile_location' => 'nullable',
];
protected $validationAttributes = [
'application.name' => 'name',
@@ -67,6 +70,9 @@ class General extends Component
'application.ports_exposes' => 'Ports exposes',
'application.ports_mappings' => 'Ports mappings',
'application.dockerfile' => 'Dockerfile',
'application.docker_registry_image_name' => 'Docker registry image name',
'application.docker_registry_image_tag' => 'Docker registry image tag',
'application.dockerfile_location' => 'Dockerfile location',
];

View File

@@ -21,11 +21,13 @@ class Heading extends Component
public function check_status()
{
dispatch(new ContainerStatusJob($this->application->destination->server));
$this->application->refresh();
$this->application->previews->each(function ($preview) {
$preview->refresh();
});
if ($this->application->destination->server->isFunctional()) {
dispatch(new ContainerStatusJob($this->application->destination->server));
$this->application->refresh();
$this->application->previews->each(function ($preview) {
$preview->refresh();
});
}
}
public function force_deploy_without_cache()

View File

@@ -25,7 +25,7 @@ class Previews extends Component
public function load_prs()
{
try {
['rate_limit_remaining' => $rate_limit_remaining, 'data' => $data] = git_api(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/pulls");
['rate_limit_remaining' => $rate_limit_remaining, 'data' => $data] = githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/pulls");
$this->rate_limit_remaining = $rate_limit_remaining;
$this->pull_requests = $data->sortBy('number')->values();
} catch (\Throwable $e) {

View File

@@ -8,6 +8,7 @@ class BackupEdit extends Component
{
public $backup;
public $s3s;
public ?string $status = null;
public array $parameters;
protected $rules = [

View File

@@ -0,0 +1,78 @@
<?php
namespace App\Http\Livewire\Project\New;
use App\Models\Application;
use App\Models\Project;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
use Illuminate\Support\Str;
class DockerImage extends Component
{
public string $dockerImage = '';
public array $parameters;
public array $query;
public function mount()
{
$this->parameters = get_route_parameters();
$this->query = request()->query();
}
public function submit()
{
$this->validate([
'dockerImage' => 'required'
]);
$image = Str::of($this->dockerImage)->before(':');
if (Str::of($this->dockerImage)->contains(':')) {
$tag = Str::of($this->dockerImage)->after(':');
} else {
$tag = 'latest';
}
$destination_uuid = $this->query['destination'];
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (!$destination) {
$destination = SwarmDocker::where('uuid', $destination_uuid)->first();
}
if (!$destination) {
throw new \Exception('Destination not found. What?!');
}
$destination_class = $destination->getMorphClass();
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
ray($image,$tag);
$application = Application::create([
'name' => 'docker-image-' . new Cuid2(7),
'repository_project_id' => 0,
'git_repository' => "coollabsio/coolify",
'git_branch' => 'main',
'build_pack' => 'dockerimage',
'ports_exposes' => 80,
'docker_registry_image_name' => $image,
'docker_registry_image_tag' => $tag,
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
'health_check_enabled' => false,
]);
$fqdn = generateFqdn($destination->server, $application->uuid);
$application->update([
'name' => 'docker-image-' . $application->uuid,
'fqdn' => $fqdn
]);
redirect()->route('project.application.configuration', [
'application_uuid' => $application->uuid,
'environment_name' => $environment->name,
'project_uuid' => $project->uuid,
]);
}
public function render()
{
return view('livewire.project.new.docker-image');
}
}

View File

@@ -11,6 +11,7 @@ use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use Livewire\Component;
use Spatie\Url\Url;
use Illuminate\Support\Str;
class GithubPrivateRepositoryDeployKey extends Component
{
@@ -29,7 +30,7 @@ class GithubPrivateRepositoryDeployKey extends Component
public string $repository_url;
public string $branch;
protected $rules = [
'repository_url' => 'required|url',
'repository_url' => 'required',
'branch' => 'required|string',
'port' => 'required|numeric',
'is_static' => 'required|boolean',
@@ -43,8 +44,8 @@ class GithubPrivateRepositoryDeployKey extends Component
'publish_directory' => 'Publish directory',
];
private object $repository_url_parsed;
private GithubApp|GitlabApp|null $git_source = null;
private string $git_host;
private GithubApp|GitlabApp|string $git_source = 'other';
private ?string $git_host = null;
private string $git_repository;
public function mount()
@@ -92,21 +93,38 @@ class GithubPrivateRepositoryDeployKey extends Component
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
$application_init = [
'name' => generate_random_name(),
'git_repository' => $this->git_repository,
'git_branch' => $this->branch,
'git_full_url' => "git@$this->git_host:$this->git_repository.git",
'build_pack' => 'nixpacks',
'ports_exposes' => $this->port,
'publish_directory' => $this->publish_directory,
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
'private_key_id' => $this->private_key_id,
'source_id' => $this->git_source->id,
'source_type' => $this->git_source->getMorphClass()
];
if ($this->git_source === 'other') {
$application_init = [
'name' => generate_random_name(),
'git_repository' => $this->git_repository,
'git_branch' => $this->branch,
'git_full_url' => $this->git_repository,
'build_pack' => 'nixpacks',
'ports_exposes' => $this->port,
'publish_directory' => $this->publish_directory,
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
'private_key_id' => $this->private_key_id,
];
} else {
$application_init = [
'name' => generate_random_name(),
'git_repository' => $this->git_repository,
'git_branch' => $this->branch,
'git_full_url' => "git@$this->git_host:$this->git_repository.git",
'build_pack' => 'nixpacks',
'ports_exposes' => $this->port,
'publish_directory' => $this->publish_directory,
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
'private_key_id' => $this->private_key_id,
'source_id' => $this->git_source->id,
'source_type' => $this->git_source->getMorphClass()
];
}
$application = Application::create($application_init);
$application->settings->is_static = $this->is_static;
$application->settings->save();
@@ -134,10 +152,13 @@ class GithubPrivateRepositoryDeployKey extends Component
if ($this->git_host == 'github.com') {
$this->git_source = GithubApp::where('name', 'Public GitHub')->first();
} elseif ($this->git_host == 'gitlab.com') {
$this->git_source = GitlabApp::where('name', 'Public GitLab')->first();
} elseif ($this->git_host == 'bitbucket.org') {
// Not supported yet
return;
}
if (Str::of($this->repository_url)->startsWith('http')) {
$this->git_host = $this->repository_url_parsed->getHost();
$this->git_repository = $this->repository_url_parsed->getSegment(1) . '/' . $this->repository_url_parsed->getSegment(2);
$this->git_repository = Str::finish("git@$this->git_host:$this->git_repository", '.git');
}
$this->git_source = 'other';
}
}

View File

@@ -27,9 +27,10 @@ class PublicGitRepository extends Component
public int $rate_limit_remaining = 0;
public $rate_limit_reset = 0;
private object $repository_url_parsed;
public GithubApp|GitlabApp|null $git_source = null;
public GithubApp|GitlabApp|string $git_source = 'other';
public string $git_host;
public string $git_repository;
protected $rules = [
'repository_url' => 'required|url',
'port' => 'required|numeric',
@@ -64,7 +65,10 @@ class PublicGitRepository extends Component
}
$this->emit('success', 'Application settings updated!');
}
public function load_any_git()
{
$this->branch_found = true;
}
public function load_branch()
{
try {
@@ -99,21 +103,23 @@ class PublicGitRepository extends Component
if ($this->git_host == 'github.com') {
$this->git_source = GithubApp::where('name', 'Public GitHub')->first();
} elseif ($this->git_host == 'gitlab.com') {
$this->git_source = GitlabApp::where('name', 'Public GitLab')->first();
} elseif ($this->git_host == 'bitbucket.org') {
// Not supported yet
}
if (is_null($this->git_source)) {
throw new \Exception('Git source not found. What?!');
return;
}
$this->git_repository = $this->repository_url;
$this->git_source = 'other';
}
private function get_branch()
{
['rate_limit_remaining' => $this->rate_limit_remaining, 'rate_limit_reset' => $this->rate_limit_reset] = git_api(source: $this->git_source, endpoint: "/repos/{$this->git_repository}/branches/{$this->git_branch}");
$this->rate_limit_reset = Carbon::parse((int)$this->rate_limit_reset)->format('Y-M-d H:i:s');
$this->branch_found = true;
if ($this->git_source === 'other') {
$this->branch_found = true;
return;
}
if ($this->git_source->getMorphClass() === 'App\Models\GithubApp') {
['rate_limit_remaining' => $this->rate_limit_remaining, 'rate_limit_reset' => $this->rate_limit_reset] = githubApi(source: $this->git_source, endpoint: "/repos/{$this->git_repository}/branches/{$this->git_branch}");
$this->rate_limit_reset = Carbon::parse((int)$this->rate_limit_reset)->format('Y-M-d H:i:s');
$this->branch_found = true;
}
}
public function submit()
@@ -136,19 +142,34 @@ class PublicGitRepository extends Component
$project = Project::where('uuid', $project_uuid)->first();
$environment = $project->load(['environments'])->environments->where('name', $environment_name)->first();
$application_init = [
'name' => generate_application_name($this->git_repository, $this->git_branch),
'git_repository' => $this->git_repository,
'git_branch' => $this->git_branch,
'build_pack' => 'nixpacks',
'ports_exposes' => $this->port,
'publish_directory' => $this->publish_directory,
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
'source_id' => $this->git_source->id,
'source_type' => $this->git_source->getMorphClass()
];
if ($this->git_source === 'other') {
$application_init = [
'name' => generate_random_name(),
'git_repository' => $this->git_repository,
'git_branch' => $this->git_branch,
'build_pack' => 'nixpacks',
'ports_exposes' => $this->port,
'publish_directory' => $this->publish_directory,
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
];
} else {
$application_init = [
'name' => generate_application_name($this->git_repository, $this->git_branch),
'git_repository' => $this->git_repository,
'git_branch' => $this->git_branch,
'build_pack' => 'nixpacks',
'ports_exposes' => $this->port,
'publish_directory' => $this->publish_directory,
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
'source_id' => $this->git_source->id,
'source_type' => $this->git_source->getMorphClass()
];
}
$application = Application::create($application_init);
@@ -157,7 +178,6 @@ class PublicGitRepository extends Component
$fqdn = generateFqdn($destination->server, $application->uuid);
$application->fqdn = $fqdn;
$application->name = generate_application_name($this->git_repository, $this->git_branch, $application->uuid);
$application->save();
return redirect()->route('project.application.configuration', [

View File

@@ -2,12 +2,11 @@
namespace App\Http\Livewire\Project\New;
use App\Models\Project;
use App\Models\Server;
use Countable;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
use Livewire\Component;
class Select extends Component
@@ -24,7 +23,8 @@ class Select extends Component
public Collection|array $services = [];
public bool $loadingServices = true;
public bool $loading = false;
public $environments = [];
public ?string $selectedEnvironment = null;
public ?string $existingPostgresqlUrl = null;
protected $queryString = [
@@ -37,8 +37,18 @@ class Select extends Component
if (isDev()) {
$this->existingPostgresqlUrl = 'postgres://coolify:password@coolify-db:5432';
}
$projectUuid = data_get($this->parameters, 'project_uuid');
$this->environments = Project::whereUuid($projectUuid)->first()->environments;
$this->selectedEnvironment = data_get($this->parameters, 'environment_name');
}
public function updatedSelectedEnvironment()
{
return redirect()->route('project.resources.new', [
'project_uuid' => $this->parameters['project_uuid'],
'environment_name' => $this->selectedEnvironment,
]);
}
// public function addExistingPostgresql()
// {
// try {

View File

@@ -31,7 +31,9 @@ class Danger extends Component
$destination = $this->resource->destination->getMorphClass()::where('id', $this->resource->destination->id)->first();
$server = $destination->server;
}
instant_remote_process(["docker rm -f {$this->resource->uuid}"], $server);
if ($this->resource->destination->server->isFunctional()) {
instant_remote_process(["docker rm -f {$this->resource->uuid}"], $server);
}
}
$this->resource->delete();
return redirect()->route('project.resources', [

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Http\Livewire\Server;
use App\Models\PrivateKey;
use Livewire\Component;
class Create extends Component
{
public $private_keys = [];
public bool $limit_reached = false;
public function mount()
{
$this->private_keys = PrivateKey::ownedByCurrentTeam()->get();
if (!isCloud()) {
$this->limit_reached = false;
return;
}
$team = currentTeam();
$servers = $team->servers->count();
['serverLimit' => $serverLimit] = $team->limits;
$this->limit_reached = $servers >= $serverLimit;
}
public function render()
{
return view('livewire.server.create');
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Http\Livewire\Server\Destination;
use App\Models\Server;
use Livewire\Component;
class Show extends Component
{
public ?Server $server = null;
public $parameters = [];
public function mount()
{
$this->parameters = get_route_parameters();
try {
$this->server = Server::ownedByCurrentTeam(['name', 'proxy'])->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) {
return redirect()->route('server.all');
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.server.destination.show');
}
}

View File

@@ -11,11 +11,12 @@ class Form extends Component
{
use AuthorizesRequests;
public Server $server;
public $uptime;
public $dockerVersion;
public string|null $wildcard_domain = null;
public bool $isValidConnection = false;
public bool $isValidDocker = false;
public ?string $wildcard_domain = null;
public int $cleanup_after_percentage;
public bool $dockerInstallationStarted = false;
protected $listeners = ['serverRefresh'];
protected $rules = [
'server.name' => 'required|min:6',
@@ -44,37 +45,49 @@ class Form extends Component
$this->wildcard_domain = $this->server->settings->wildcard_domain;
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
}
public function instantSave() {
public function serverRefresh() {
$this->validateServer();
}
public function instantSave()
{
refresh_server_connection($this->server->privateKey);
$this->validateServer();
$this->server->settings->save();
}
public function installDocker()
{
$this->emit('installDocker');
$this->dockerInstallationStarted = true;
$activity = resolve(InstallDocker::class)($this->server);
$activity = InstallDocker::run($this->server);
$this->emit('newMonitorActivity', $activity->id);
}
public function validateServer()
public function validateServer($install = true)
{
try {
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server, true);
$uptime = $this->server->validateConnection();
if ($uptime) {
$this->uptime = $uptime;
$this->emit('success', 'Server is reachable.');
$install && $this->emit('success', 'Server is reachable.');
} else {
$this->emit('error', 'Server is not reachable.');
$install &&$this->emit('error', 'Server is not reachable. Please check your connection and private key configuration.');
return;
}
if ($dockerVersion) {
$this->dockerVersion = $dockerVersion;
$this->emit('success', 'Docker Engine 23+ is installed!');
$dockerInstalled = $this->server->validateDockerEngine();
if ($dockerInstalled) {
$install && $this->emit('success', 'Docker Engine is installed.<br> Checking version.');
} else {
$this->emit('error', 'No Docker Engine or older than 23 version installed.');
$install && $this->installDocker();
return;
}
$dockerVersion = $this->server->validateDockerEngineVersion();
if ($dockerVersion) {
$install && $this->emit('success', 'Docker Engine version is 23+.');
} else {
$install && $this->installDocker();
return;
}
} catch (\Throwable $e) {
return handleError($e, $this, customErrorMessage: "Server is not reachable: ");
return handleError($e, $this);
} finally {
$this->emit('proxyStatusUpdated');
}

View File

@@ -11,13 +11,13 @@ class ByIp extends Component
{
public $private_keys;
public $limit_reached;
public int|null $private_key_id = null;
public ?int $private_key_id = null;
public $new_private_key_name;
public $new_private_key_description;
public $new_private_key_value;
public string $name;
public string|null $description = null;
public ?string $description = null;
public string $ip;
public string $user = 'root';
public int $port = 22;
@@ -26,16 +26,16 @@ class ByIp extends Component
protected $rules = [
'name' => 'required|string',
'description' => 'nullable|string',
'ip' => 'required',
'ip' => 'required|ip',
'user' => 'required|string',
'port' => 'required|integer',
];
protected $validationAttributes = [
'name' => 'name',
'description' => 'description',
'ip' => 'ip',
'user' => 'user',
'port' => 'port',
'name' => 'Name',
'description' => 'Description',
'ip' => 'IP Address',
'user' => 'User',
'port' => 'Port',
];
public function mount()

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Http\Livewire\Server\PrivateKey;
use App\Models\PrivateKey;
use App\Models\Server;
use Livewire\Component;
class Show extends Component
{
public ?Server $server = null;
public $privateKeys = [];
public $parameters = [];
public function mount()
{
$this->parameters = get_route_parameters();
try {
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) {
return redirect()->route('server.all');
}
$this->privateKeys = PrivateKey::ownedByCurrentTeam()->get()->where('is_git_related', false);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.server.private-key.show');
}
}

View File

@@ -11,7 +11,7 @@ class Deploy extends Component
public Server $server;
public bool $traefikDashboardAvailable = false;
public ?string $currentRoute = null;
protected $listeners = ['proxyStatusUpdated', 'traefikDashboardAvailable'];
protected $listeners = ['proxyStatusUpdated', 'traefikDashboardAvailable', 'serverRefresh' => 'proxyStatusUpdated'];
public function mount() {
$this->currentRoute = request()->route()->getName();

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Http\Livewire\Server\Proxy;
use App\Models\Server;
use Livewire\Component;
class Logs extends Component
{
public ?Server $server = null;
public $parameters = [];
public function mount()
{
$this->parameters = get_route_parameters();
try {
$this->server = Server::ownedByCurrentTeam(['name', 'proxy'])->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) {
return redirect()->route('server.all');
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.server.proxy.logs');
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Http\Livewire\Server\Proxy;
use App\Models\Server;
use Livewire\Component;
class Show extends Component
{
public ?Server $server = null;
public $parameters = [];
protected $listeners = ['proxyStatusUpdated'];
public function proxyStatusUpdated()
{
$this->server->refresh();
}
public function mount()
{
$this->parameters = get_route_parameters();
try {
$this->server = Server::ownedByCurrentTeam(['name', 'proxy'])->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) {
return redirect()->route('server.all');
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.server.proxy.show');
}
}

View File

@@ -26,7 +26,9 @@ class Status extends Component
}
public function getProxyStatusWithNoti()
{
$this->emit('success', 'Refreshed proxy status.');
$this->getProxyStatus();
if ($this->server->isFunctional()) {
$this->emit('success', 'Refreshed proxy status.');
$this->getProxyStatus();
}
}
}

View File

@@ -10,8 +10,10 @@ class Show extends Component
{
use AuthorizesRequests;
public ?Server $server = null;
public $parameters = [];
public function mount()
{
$this->parameters = get_route_parameters();
try {
$this->server = Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user', 'proxy'])->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) {
@@ -21,6 +23,10 @@ class Show extends Component
return handleError($e, $this);
}
}
public function submit()
{
$this->emit('serverRefresh');
}
public function render()
{
return view('livewire.server.show');

View File

@@ -35,31 +35,13 @@ class ShowPrivateKey extends Component
public function checkConnection()
{
try {
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server, true);
$uptime = $this->server->validateConnection();
if ($uptime) {
$this->server->settings->update([
'is_reachable' => true
]);
$this->emit('success', 'Server is reachable with this private key.');
$this->emit('success', 'Server is reachable.');
} else {
$this->server->settings->update([
'is_reachable' => false,
'is_usable' => false
]);
$this->emit('error', 'Server is not reachable with this private key.');
$this->emit('error', 'Server is not reachable. Please check your connection and private key configuration.');
return;
}
if ($dockerVersion) {
$this->server->settings->update([
'is_usable' => true
]);
$this->emit('success', 'Server is usable for Coolify.');
} else {
$this->server->settings->update([
'is_usable' => false
]);
$this->emit('error', 'Old (lower than 23) or no Docker version detected. Install Docker Engine on the General tab.');
}
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Http\Livewire\Subscription;
use App\Models\InstanceSettings;
use Livewire\Component;
class Show extends Component
{
public InstanceSettings $settings;
public bool $alreadySubscribed = false;
public function mount() {
if (!isCloud()) {
return redirect('/');
}
$this->settings = InstanceSettings::get();
$this->alreadySubscribed = currentTeam()->subscription()->exists();
}
public function stripeCustomerPortal() {
$session = getStripeCustomerPortalSession(currentTeam());
if (is_null($session)) {
return;
}
return redirect($session->url);
}
public function render()
{
return view('livewire.subscription.show')->layout('layouts.subscription');
}
}

View File

@@ -64,7 +64,7 @@ class Create extends Component
}
$this->storage->team_id = currentTeam()->id;
$this->storage->testConnection();
$this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
$this->storage->is_usable = true;
$this->storage->save();
return redirect()->route('team.storages.show', $this->storage->uuid);
} catch (\Throwable $e) {

View File

@@ -9,6 +9,7 @@ class Form extends Component
{
public S3Storage $storage;
protected $rules = [
'storage.is_usable' => 'nullable|boolean',
'storage.name' => 'nullable|min:3|max:255',
'storage.description' => 'nullable|min:3|max:255',
'storage.region' => 'required|max:255',
@@ -18,6 +19,7 @@ class Form extends Component
'storage.endpoint' => 'required|url|max:255',
];
protected $validationAttributes = [
'storage.is_usable' => 'Is Usable',
'storage.name' => 'Name',
'storage.description' => 'Description',
'storage.region' => 'Region',

View File

@@ -5,10 +5,11 @@ namespace App\Http\Livewire;
use App\Actions\Server\UpdateCoolify;
use App\Models\InstanceSettings;
use Livewire\Component;
use Masmerise\Toaster\Toaster;
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
class Upgrade extends Component
{
use WithRateLimiting;
public bool $showProgress = false;
public bool $isUpgradeAvailable = false;
public string $latestVersion = '';
@@ -31,6 +32,7 @@ class Upgrade extends Component
public function upgrade()
{
try {
$this->rateLimit(1, 30);
if ($this->showProgress) {
return;
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Http\Livewire;
use Livewire\Component;
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
class VerifyEmail extends Component
{
use WithRateLimiting;
public function again() {
try {
$this->rateLimit(1, 300);
auth()->user()->sendVerificationEmail();
$this->emit('success', 'Email verification link sent!');
} catch(\Exception $e) {
ray($e);
return handleError($e,$this);
}
}
public function render()
{
return view('livewire.verify-email');
}
}

View File

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

View File

@@ -45,7 +45,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private string $commit;
private bool $force_rebuild;
private GithubApp|GitlabApp $source;
private ?string $dockerImage = null;
private ?string $dockerImageTag = null;
private GithubApp|GitlabApp|string $source = 'other';
private StandaloneDocker|SwarmDocker $destination;
private Server $server;
private ApplicationPreview|null $preview = null;
@@ -54,6 +57,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private string|null $currently_running_container_name = null;
private string $basedir;
private string $workdir;
private ?string $build_pack = null;
private string $configuration_dir;
private string $build_image_name;
private string $production_image_name;
@@ -62,6 +66,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private $env_args;
private $docker_compose;
private $docker_compose_base64;
private string $dockerfile_location = '/Dockerfile';
private $log_model;
private Collection $saved_outputs;
@@ -73,6 +78,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->application_deployment_queue = ApplicationDeploymentQueue::find($application_deployment_queue_id);
$this->log_model = $this->application_deployment_queue;
$this->application = Application::find($this->application_deployment_queue->application_id);
$this->build_pack = data_get($this->application, 'build_pack');
$this->application_deployment_queue_id = $application_deployment_queue_id;
$this->deployment_uuid = $this->application_deployment_queue->deployment_uuid;
@@ -80,7 +86,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->commit = $this->application_deployment_queue->commit;
$this->force_rebuild = $this->application_deployment_queue->force_rebuild;
$this->source = $this->application->source->getMorphClass()::where('id', $this->application->source->id)->first();
$source = data_get($this->application, 'source');
if ($source) {
$this->source = $source->getMorphClass()::where('id', $this->application->source->id)->first();
}
$this->destination = $this->application->destination->getMorphClass()::where('id', $this->application->destination->id)->first();
$this->server = $this->destination->server;
@@ -89,7 +98,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->configuration_dir = application_configuration_dir() . "/{$this->application->uuid}";
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
ray($this->basedir,$this->workdir);
ray($this->basedir, $this->workdir);
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
savePrivateKeyToFs($this->server);
$this->saved_outputs = collect();
@@ -132,6 +141,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
try {
if ($this->application->dockerfile) {
$this->deploy_simple_dockerfile();
} else if ($this->application->build_pack === 'dockerimage') {
$this->deploy_dockerimage();
} else if ($this->application->build_pack === 'dockerfile') {
$this->deploy_dockerfile();
} else {
if ($this->pull_request_id !== 0) {
$this->deploy_pull_request();
@@ -162,14 +175,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
]
);
}
// $this->execute_remote_command(
// [
// "docker rm -f {$this->deployment_uuid} >/dev/null 2>&1",
// "hidden" => true,
// ]
// );
$this->execute_remote_command(
[
"docker rm -f {$this->deployment_uuid} >/dev/null 2>&1",
"hidden" => true,
]
);
}
}
private function deploy_docker_compose()
{
$dockercompose_base64 = base64_encode($this->application->dockercompose);
@@ -242,6 +256,50 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->rolling_update();
}
private function deploy_dockerimage()
{
$this->dockerImage = $this->application->docker_registry_image_name;
$this->dockerImageTag = $this->application->docker_registry_image_tag;
ray("echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.'");
$this->execute_remote_command(
[
"echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.'"
],
);
$this->production_image_name = Str::lower("{$this->dockerImage}:{$this->dockerImageTag}");
$this->prepare_builder_image();
$this->generate_compose_file();
$this->rolling_update();
}
private function deploy_dockerfile()
{
if (data_get($this->application, 'dockerfile_location')) {
$this->dockerfile_location = $this->application->dockerfile_location;
}
$this->execute_remote_command(
[
"echo 'Starting deployment of {$this->application->git_repository}:{$this->application->git_branch}.'"
],
);
$this->prepare_builder_image();
$this->clone_repository();
$this->set_base_dir();
$tag = Str::of("{$this->commit}-{$this->application->id}-{$this->pull_request_id}");
if (strlen($tag) > 128) {
$tag = $tag->substr(0, 128);
}
$this->build_image_name = Str::lower("{$this->application->git_repository}:{$tag}-build");
$this->production_image_name = Str::lower("{$this->application->uuid}:{$tag}");
// ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
$this->cleanup_git();
$this->generate_compose_file();
$this->generate_build_env_variables();
$this->add_build_env_variables_to_dockerfile();
// $this->build_image();
$this->rolling_update();
}
private function deploy()
{
$this->execute_remote_command(
@@ -251,7 +309,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
);
$this->prepare_builder_image();
$this->clone_repository();
$this->set_base_dir();
$tag = Str::of("{$this->commit}-{$this->application->id}-{$this->pull_request_id}");
if (strlen($tag) > 128) {
$tag = $tag->substr(0, 128);
@@ -336,9 +394,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if (Str::of($this->saved_outputs->get('health_check'))->contains('healthy')) {
$this->newVersionIsHealthy = true;
$this->execute_remote_command(
[
"echo 'New version of your application is healthy.'"
],
[
"echo 'Rolling update completed.'"
],
@@ -361,6 +416,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
]);
$this->prepare_builder_image();
$this->clone_repository();
$this->set_base_dir();
$this->cleanup_git();
if ($this->application->build_pack === 'nixpacks') {
$this->generate_nixpacks_confs();
@@ -397,7 +453,14 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
);
}
private function set_base_dir()
{
$this->execute_remote_command(
[
"echo -n 'Setting base directory to {$this->workdir}.'"
],
);
}
private function clone_repository()
{
@@ -449,7 +512,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
if ($this->application->deploymentType() === 'deploy_key') {
$private_key = base64_encode($this->application->private_key->private_key);
$git_clone_command = "GIT_SSH_COMMAND=\"ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_full_url} {$this->workdir}";
$git_clone_command = "GIT_SSH_COMMAND=\"ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_full_url} {$this->basedir}";
$git_clone_command = $this->set_git_import_settings($git_clone_command);
$commands = collect([
executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh"),
@@ -459,6 +522,13 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
]);
return $commands->implode(' && ');
}
if ($this->application->deploymentType() === 'other') {
$git_clone_command = "{$git_clone_command} {$this->application->git_repository} {$this->basedir}";
$git_clone_command = $this->set_git_import_settings($git_clone_command);
$commands->push(executeInDocker($this->deployment_uuid, $git_clone_command));
ray($commands);
return $commands->implode(' && ');
}
}
private function set_git_import_settings($git_clone_command)
@@ -550,7 +620,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
'container_name' => $this->container_name,
'restart' => RESTART_MODE,
'environment' => $environment_variables,
'labels' => generateLabelsApplication($this->application, $this->preview),
'labels' => generateLabelsApplication($this->application, $this->preview, $ports),
'expose' => $ports,
'networks' => [
$this->destination->network,
@@ -594,6 +664,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}
if ($this->build_pack === 'dockerfile') {
$docker_compose['services'][$this->container_name]['build'] = [
'context' => $this->workdir,
'dockerfile' => $this->workdir . $this->dockerfile_location,
];
}
$this->docker_compose = Yaml::dump($docker_compose, 10);
$this->docker_compose_base64 = base64_encode($this->docker_compose);
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]);
@@ -657,7 +733,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function generate_healthcheck_commands()
{
if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile') {
if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile' || $this->application->build_pack === 'dockerimage') {
// TODO: disabled HC because there are several ways to hc a simple docker image, hard to figure out a good way. Like some docker images (pocketbase) does not have curl.
return 'exit 0';
}
@@ -750,7 +826,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
{
$this->execute_remote_command(
["echo -n 'Starting application (could take a while).'"],
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true],
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d >/dev/null"), "hidden" => true],
);
}

View File

@@ -66,7 +66,7 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
private function update_comment()
{
['data' => $data] = git_api(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/comments/{$this->preview->pull_request_issue_comment_id}", method: 'patch', data: [
['data' => $data] = githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/comments/{$this->preview->pull_request_issue_comment_id}", method: 'patch', data: [
'body' => $this->body,
], throwError: false);
if (data_get($data, 'message') === 'Not Found') {
@@ -77,7 +77,7 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
private function create_comment()
{
['data' => $data] = git_api(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/{$this->pull_request_id}/comments", method: 'post', data: [
['data' => $data] = githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/{$this->pull_request_id}/comments", method: 'post', data: [
'body' => $this->body,
]);
$this->preview->pull_request_issue_comment_id = $data['id'];

View File

@@ -7,6 +7,7 @@ use App\Models\ApplicationPreview;
use App\Models\Server;
use App\Notifications\Container\ContainerRestarted;
use App\Notifications\Container\ContainerStopped;
use App\Notifications\Server\Revived;
use App\Notifications\Server\Unreachable;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
@@ -40,33 +41,60 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
{
return $this->server->uuid;
}
private function checkServerConnection()
{
$uptime = instant_remote_process(['uptime'], $this->server, false);
if (!is_null($uptime)) {
return true;
}
}
public function handle(): void
public function handle()
{
try {
// ray("checking server status for {$this->server->name}");
// ray()->clearAll();
$serverUptimeCheckNumber = 0;
$serverUptimeCheckNumber = $this->server->unreachable_count;
$serverUptimeCheckNumberMax = 3;
while (true) {
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
$this->server->settings()->update(['is_reachable' => false]);
$this->server->team->notify(new Unreachable($this->server));
return;
// ray('checking # ' . $serverUptimeCheckNumber);
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
if ($this->server->unreachable_email_sent === false) {
ray('Server unreachable, sending notification...');
// $this->server->team->notify(new Unreachable($this->server));
$this->server->update(['unreachable_email_sent' => true]);
}
$result = $this->checkServerConnection();
if ($result) {
break;
}
$serverUptimeCheckNumber++;
sleep(5);
$this->server->settings()->update([
'is_reachable' => false,
]);
return;
}
$result = $this->server->validateConnection();
if ($result) {
$this->server->settings()->update([
'is_reachable' => true,
]);
$this->server->update([
'unreachable_count' => 0,
]);
} else {
$serverUptimeCheckNumber++;
$this->server->settings()->update([
'is_reachable' => false,
]);
$this->server->update([
'unreachable_count' => $serverUptimeCheckNumber,
]);
return;
}
if (data_get($this->server, 'unreachable_email_sent') === true) {
ray('Server is reachable again, sending notification...');
// $this->server->team->notify(new Revived($this->server));
$this->server->update(['unreachable_email_sent' => false]);
}
if (
data_get($this->server, 'settings.is_reachable') === false ||
data_get($this->server, 'settings.is_usable') === false
) {
$this->server->settings()->update([
'is_reachable' => true,
'is_usable' => true
]);
}
// $this->server->validateDockerEngine(true);
$containers = instant_remote_process(["docker container ls -q"], $this->server);
if (!$containers) {
return;
@@ -266,7 +294,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
} catch (\Throwable $e) {
send_internal_notification('ContainerStatusJob failed with: ' . $e->getMessage());
ray($e->getMessage());
throw $e;
return handleError($e);
}
}
}

View File

@@ -31,7 +31,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
public ?string $container_name = null;
public ?ScheduledDatabaseBackupExecution $backup_log = null;
public string $backup_status;
public string $backup_status = 'failed';
public ?string $backup_location = null;
public string $backup_dir;
public string $backup_file;
@@ -74,7 +74,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
$ip = Str::slug($this->server->ip);
$this->backup_dir = backup_dir() . "/coolify" . "/coolify-db-$ip";
}
$this->backup_file = "/pg_dump-" . Carbon::now()->timestamp . ".dump";
$this->backup_file = "/pg-backup-customformat-" . Carbon::now()->timestamp . ".backup";
$this->backup_location = $this->backup_dir . $this->backup_file;
$this->backup_log = ScheduledDatabaseBackupExecution::create([
@@ -90,10 +90,17 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
$this->upload_to_s3();
}
$this->save_backup_logs();
$this->team->notify(new BackupSuccess($this->backup, $this->database));
$this->backup_status = 'success';
} catch (\Throwable $e) {
ray($e->getMessage());
$this->backup_status = 'failed';
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
$this->team->notify(new BackupFailed($this->backup, $this->database, $this->backup_output));
throw $e;
} finally {
$this->backup_log->update([
'status' => $this->backup_status,
]);
}
}
@@ -103,28 +110,15 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
ray($this->backup_dir);
$commands[] = "mkdir -p " . $this->backup_dir;
$commands[] = "docker exec $this->container_name pg_dump -Fc -U {$this->database->postgres_user} > $this->backup_location";
$this->backup_output = instant_remote_process($commands, $this->server);
$this->backup_output = trim($this->backup_output);
if ($this->backup_output === '') {
$this->backup_output = null;
}
ray('Backup done for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location);
$this->backup_status = 'success';
$this->team->notify(new BackupSuccess($this->backup, $this->database));
} catch (\Throwable $e) {
$this->backup_status = 'failed';
$this->add_to_backup_output($e->getMessage());
ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $e->getMessage());
$this->team->notify(new BackupFailed($this->backup, $this->database, $this->backup_output));
} finally {
$this->backup_log->update([
'status' => $this->backup_status,
]);
}
}
@@ -163,11 +157,16 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
}
$key = $this->s3->key;
$secret = $this->s3->secret;
// $region = $this->s3->region;
// $region = $this->s3->region;
$bucket = $this->s3->bucket;
$endpoint = $this->s3->endpoint;
$this->s3->testConnection();
if (isDev()) {
$commands[] = "docker run --pull=always -d --network {$this->database->destination->network} --name backup-of-{$this->backup->uuid} --rm -v coolify_coolify-data-dev:/data/coolify:ro ghcr.io/coollabsio/coolify-helper >/dev/null 2>&1";
} else {
$commands[] = "docker run --pull=always -d --network {$this->database->destination->network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro ghcr.io/coollabsio/coolify-helper >/dev/null 2>&1";
}
$commands[] = "docker run --pull=always -d --network {$this->database->destination->network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro ghcr.io/coollabsio/coolify-helper";
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret";
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/";
instant_remote_process($commands, $this->server);
@@ -175,7 +174,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
ray('Uploaded to S3. ' . $this->backup_location . ' to s3://' . $bucket . $this->backup_dir);
} catch (\Throwable $e) {
$this->add_to_backup_output($e->getMessage());
ray($e->getMessage());
throw $e;
} finally {
$command = "docker rm -f backup-of-{$this->backup->uuid}";
instant_remote_process([$command], $this->server);

View File

@@ -5,6 +5,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Spatie\Activitylog\Models\Activity;
use Illuminate\Support\Str;
class Application extends BaseModel
{
@@ -12,6 +13,19 @@ class Application extends BaseModel
protected static function booted()
{
static::saving(function ($application) {
if ($application->fqdn == '') {
$application->fqdn = null;
}
$application->forceFill([
'fqdn' => $application->fqdn,
'install_command' => Str::of($application->install_command)->trim(),
'build_command' => Str::of($application->build_command)->trim(),
'start_command' => Str::of($application->start_command)->trim(),
'base_directory' => Str::of($application->base_directory)->trim(),
'publish_directory' => Str::of($application->publish_directory)->trim(),
]);
});
static::created(function ($application) {
ApplicationSetting::create([
'application_id' => $application->id,
@@ -19,11 +33,13 @@ class Application extends BaseModel
});
static::deleting(function ($application) {
// Stop Container
instant_remote_process(
["docker rm -f {$application->uuid}"],
$application->destination->server,
false
);
if ($application->destination->server->isFunctional()) {
instant_remote_process(
["docker rm -f {$application->uuid}"],
$application->destination->server,
false
);
}
$application->settings()->delete();
$storages = $application->persistentStorages()->get();
foreach ($storages as $storage) {
@@ -68,6 +84,7 @@ class Application extends BaseModel
if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) {
return "{$this->source->html_url}/{$this->git_repository}/tree/{$this->git_branch}";
}
return $this->git_repository;
}
);
@@ -80,10 +97,25 @@ class Application extends BaseModel
if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) {
return "{$this->source->html_url}/{$this->git_repository}/commits/{$this->git_branch}";
}
return $this->git_repository;
}
);
}
public function dockerfileLocation(): Attribute
{
return Attribute::make(
set: function ($value) {
if (is_null($value) || $value === '') {
return '/Dockerfile';
} else {
if ($value !== '/') {
return Str::start(Str::replaceEnd('/', '', $value), '/');
}
return Str::start($value, '/');
}
}
);
}
public function baseDirectory(): Attribute
{
return Attribute::make(
@@ -224,6 +256,8 @@ class Application extends BaseModel
return 'deploy_key';
} else if (data_get($this, 'source')) {
return 'source';
} else {
return 'other';
}
throw new \Exception('No deployment type found');
}
@@ -239,12 +273,14 @@ class Application extends BaseModel
if ($this->dockerfile) {
return false;
}
if ($this->build_pack === 'dockerimage') {
return false;
}
return true;
}
public function isHealthcheckDisabled(): bool
{
if (data_get($this, 'dockerfile') || data_get($this, 'build_pack') === 'dockerfile' || data_get($this, 'health_check_enabled') === false) {
ray('dockerfile');
if (data_get($this, 'health_check_enabled') === false) {
return true;
}
return false;

View File

@@ -3,6 +3,8 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Facades\Storage;
class S3Storage extends BaseModel
{
@@ -10,6 +12,7 @@ class S3Storage extends BaseModel
protected $guarded = [];
protected $casts = [
'is_usable' => 'boolean',
'key' => 'encrypted',
'secret' => 'encrypted',
];
@@ -19,7 +22,15 @@ class S3Storage extends BaseModel
$selectArray = collect($select)->concat(['id']);
return S3Storage::whereTeamId(currentTeam()->id)->select($selectArray->all())->orderBy('name');
}
public function isUsable()
{
return $this->is_usable;
}
public function team()
{
return $this->belongsTo(Team::class);
}
public function awsUrl()
{
return "{$this->endpoint}/{$this->bucket}";
@@ -27,7 +38,34 @@ class S3Storage extends BaseModel
public function testConnection()
{
set_s3_target($this);
return \Storage::disk('custom-s3')->files();
try {
set_s3_target($this);
Storage::disk('custom-s3')->files();
$this->unusable_email_sent = false;
$this->is_usable = true;
return;
} catch (\Throwable $e) {
$this->is_usable = false;
if ($this->unusable_email_sent === false && is_transactional_emails_active()) {
$mail = new MailMessage();
$mail->subject('Coolify: S3 Storage Connection Error');
$mail->view('emails.s3-connection-error', ['name' => $this->name, 'reason' => $e->getMessage(), 'url' => route('team.storages.show', ['storage_uuid' => $this->uuid])]);
$users = collect([]);
$members = $this->team->members()->get();
foreach ($members as $user) {
if ($user->isAdmin()) {
$users->push($user);
}
}
foreach ($users as $user) {
send_user_an_email($mail, $user->email);
}
$this->unusable_email_sent = true;
}
throw $e;
} finally {
$this->save();
}
}
}

View File

@@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
use Illuminate\Support\Str;
class Server extends BaseModel
{
@@ -15,6 +16,17 @@ class Server extends BaseModel
protected static function booted()
{
static::saving(function ($server) {
$payload = [];
if ($server->user) {
$payload['user'] = Str::of($server->user)->trim();
}
if ($server->ip) {
$payload['ip'] = Str::of($server->ip)->trim();
}
$server->forceFill($payload);
});
static::created(function ($server) {
ServerSetting::create([
'server_id' => $server->id,
@@ -199,4 +211,48 @@ class Server extends BaseModel
{
return $this->settings->is_reachable && $this->settings->is_usable;
}
public function validateConnection()
{
$uptime = instant_remote_process(['uptime'], $this, false);
if (!$uptime) {
$this->settings->is_reachable = false;
$this->settings->save();
return false;
}
$this->settings->is_reachable = true;
$this->settings->save();
return true;
}
public function validateDockerEngine($throwError = false)
{
$dockerBinary = instant_remote_process(["command -v docker"], $this, false);
if (is_null($dockerBinary)) {
$this->settings->is_usable = false;
$this->settings->save();
if ($throwError) {
throw new \Exception('Server is not usable.');
}
return false;
}
$this->settings->is_usable = true;
$this->settings->save();
$this->validateCoolifyNetwork();
return true;
}
public function validateDockerEngineVersion()
{
$dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $this, false);
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
if (is_null($dockerVersion)) {
$this->settings->is_usable = false;
$this->settings->save();
return false;
}
$this->settings->is_usable = true;
$this->settings->save();
return true;
}
public function validateCoolifyNetwork() {
return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false);
}
}

View File

@@ -33,7 +33,7 @@ class Subscription extends Model
}
if (isStripe()) {
if (!$this->stripe_plan_id) {
return 'zero';
return 'zero';
}
$subscription = Subscription::where('id', $this->id)->first();
if (!$subscription) {

View File

@@ -48,6 +48,7 @@ class Team extends Model implements SendsDiscord, SendsEmail
}
return explode(',', $recipients);
}
public function limits(): Attribute
{
return Attribute::make(
@@ -125,7 +126,7 @@ class Team extends Model implements SendsDiscord, SendsEmail
public function s3s()
{
return $this->hasMany(S3Storage::class);
return $this->hasMany(S3Storage::class)->where('is_usable', true);
}
public function trialEnded() {
foreach ($this->servers as $server) {

View File

@@ -6,8 +6,12 @@ use App\Notifications\Channels\SendsEmail;
use App\Notifications\TransactionalEmails\ResetPassword as TransactionalEmailsResetPassword;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\URL;
use Laravel\Fortify\TwoFactorAuthenticatable;
use Laravel\Sanctum\HasApiTokens;
@@ -54,6 +58,23 @@ class User extends Authenticatable implements SendsEmail
return $this->email;
}
public function sendVerificationEmail()
{
$mail = new MailMessage();
$url = Url::temporarySignedRoute(
'verify.verify',
Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
[
'id' => $this->getKey(),
'hash' => sha1($this->getEmailForVerification()),
]
);
$mail->view('emails.email-verification', [
'url' => $url,
]);
$mail->subject('Coolify Cloud: Verify your email.');
send_user_an_email($mail, $this->email);
}
public function sendPasswordResetNotification($token): void
{
$this->notify(new TransactionalEmailsResetPassword($token));
@@ -61,7 +82,7 @@ class User extends Authenticatable implements SendsEmail
public function isAdmin()
{
return data_get($this->pivot,'role') === 'admin' || data_get($this->pivot,'role') === 'owner';
return data_get($this->pivot, 'role') === 'admin' || data_get($this->pivot, 'role') === 'owner';
}
public function isAdminFromSession()
@@ -79,7 +100,7 @@ class User extends Authenticatable implements SendsEmail
return true;
}
$team = $teams->where('id', session('currentTeam')->id)->first();
$role = data_get($team,'pivot.role');
$role = data_get($team, 'pivot.role');
return $role === 'admin' || $role === 'owner';
}
@@ -96,7 +117,10 @@ class User extends Authenticatable implements SendsEmail
public function currentTeam()
{
return Cache::remember('team:' . auth()->user()->id, 3600, function() {
return Cache::remember('team:' . auth()->user()->id, 3600, function () {
if (is_null(data_get(session('currentTeam'), 'id'))) {
return auth()->user()->teams[0];
}
return Team::find(session('currentTeam')->id);
});
}

View File

@@ -52,10 +52,10 @@ class DeploymentFailed extends Notification implements ShouldQueue
$pull_request_id = data_get($this->preview, 'pull_request_id', 0);
$fqdn = $this->fqdn;
if ($pull_request_id === 0) {
$mail->subject(' Deployment failed of ' . $this->application_name . '.');
$mail->subject('Coolify: Deployment failed of ' . $this->application_name . '.');
} else {
$fqdn = $this->preview->fqdn;
$mail->subject(' Deployment failed of pull request #' . $this->preview->pull_request_id . ' of ' . $this->application_name . '.');
$mail->subject('Coolify: Deployment failed of pull request #' . $this->preview->pull_request_id . ' of ' . $this->application_name . '.');
}
$mail->view('emails.application-deployment-failed', [
'name' => $this->application_name,
@@ -69,10 +69,10 @@ class DeploymentFailed extends Notification implements ShouldQueue
public function toDiscord(): string
{
if ($this->preview) {
$message = ' Pull request #' . $this->preview->pull_request_id . ' of **' . $this->application_name . '** (' . $this->preview->fqdn . ') deployment failed: ';
$message = 'Coolify: Pull request #' . $this->preview->pull_request_id . ' of **' . $this->application_name . '** (' . $this->preview->fqdn . ') deployment failed: ';
$message .= '[View Deployment Logs](' . $this->deployment_url . ')';
} else {
$message = ' Deployment failed of **' . $this->application_name . '** (' . $this->fqdn . '): ';
$message = 'Coolify: Deployment failed of **' . $this->application_name . '** (' . $this->fqdn . '): ';
$message .= '[View Deployment Logs](' . $this->deployment_url . ')';
}
return $message;
@@ -80,9 +80,9 @@ class DeploymentFailed extends Notification implements ShouldQueue
public function toTelegram(): array
{
if ($this->preview) {
$message = ' Pull request #' . $this->preview->pull_request_id . ' of **' . $this->application_name . '** (' . $this->preview->fqdn . ') deployment failed: ';
$message = 'Coolify: Pull request #' . $this->preview->pull_request_id . ' of **' . $this->application_name . '** (' . $this->preview->fqdn . ') deployment failed: ';
} else {
$message = ' Deployment failed of **' . $this->application_name . '** (' . $this->fqdn . '): ';
$message = 'Coolify: Deployment failed of **' . $this->application_name . '** (' . $this->fqdn . '): ';
}
return [
"message" => $message,

View File

@@ -52,10 +52,10 @@ class DeploymentSuccess extends Notification implements ShouldQueue
$pull_request_id = data_get($this->preview, 'pull_request_id', 0);
$fqdn = $this->fqdn;
if ($pull_request_id === 0) {
$mail->subject(" New version is deployed of {$this->application_name}");
$mail->subject("Coolify: New version is deployed of {$this->application_name}");
} else {
$fqdn = $this->preview->fqdn;
$mail->subject(" Pull request #{$pull_request_id} of {$this->application_name} deployed successfully");
$mail->subject("Coolify: Pull request #{$pull_request_id} of {$this->application_name} deployed successfully");
}
$mail->view('emails.application-deployment-success', [
'name' => $this->application_name,
@@ -69,7 +69,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue
public function toDiscord(): string
{
if ($this->preview) {
$message = ' New PR' . $this->preview->pull_request_id . ' version successfully deployed of ' . $this->application_name . '
$message = 'Coolify: New PR' . $this->preview->pull_request_id . ' version successfully deployed of ' . $this->application_name . '
';
if ($this->preview->fqdn) {
@@ -77,7 +77,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue
}
$message .= '[Deployment logs](' . $this->deployment_url . ')';
} else {
$message = ' New version successfully deployed of ' . $this->application_name . '
$message = 'Coolify: New version successfully deployed of ' . $this->application_name . '
';
if ($this->fqdn) {
@@ -90,7 +90,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue
public function toTelegram(): array
{
if ($this->preview) {
$message = ' New PR' . $this->preview->pull_request_id . ' version successfully deployed of ' . $this->application_name . '';
$message = 'Coolify: New PR' . $this->preview->pull_request_id . ' version successfully deployed of ' . $this->application_name . '';
if ($this->preview->fqdn) {
$buttons[] = [
"text" => "Open Application",

View File

@@ -45,7 +45,7 @@ class StatusChanged extends Notification implements ShouldQueue
{
$mail = new MailMessage();
$fqdn = $this->fqdn;
$mail->subject(" {$this->application_name} has been stopped");
$mail->subject("Coolify: {$this->application_name} has been stopped");
$mail->view('emails.application-status-changes', [
'name' => $this->application_name,
'fqdn' => $fqdn,
@@ -56,7 +56,7 @@ class StatusChanged extends Notification implements ShouldQueue
public function toDiscord(): string
{
$message = ' ' . $this->application_name . ' has been stopped.
$message = 'Coolify: ' . $this->application_name . ' has been stopped.
';
$message .= '[Open Application in Coolify](' . $this->application_url . ')';
@@ -64,7 +64,7 @@ class StatusChanged extends Notification implements ShouldQueue
}
public function toTelegram(): array
{
$message = ' ' . $this->application_name . ' has been stopped.';
$message = 'Coolify: ' . $this->application_name . ' has been stopped.';
return [
"message" => $message,
"buttons" => [

View File

@@ -27,7 +27,7 @@ class ContainerRestarted extends Notification implements ShouldQueue
public function toMail(): MailMessage
{
$mail = new MailMessage();
$mail->subject(" Container ({$this->name}) has been restarted automatically on {$this->server->name}");
$mail->subject("Coolify: Container ({$this->name}) has been restarted automatically on {$this->server->name}");
$mail->view('emails.container-restarted', [
'containerName' => $this->name,
'serverName' => $this->server->name,
@@ -38,12 +38,12 @@ class ContainerRestarted extends Notification implements ShouldQueue
public function toDiscord(): string
{
$message = " Container ({$this->name}) has been restarted automatically on {$this->server->name}";
$message = "Coolify: Container ({$this->name}) has been restarted automatically on {$this->server->name}";
return $message;
}
public function toTelegram(): array
{
$message = " Container ({$this->name}) has been restarted automatically on {$this->server->name}";
$message = "Coolify: Container ({$this->name}) has been restarted automatically on {$this->server->name}";
$payload = [
"message" => $message,
];

View File

@@ -26,7 +26,7 @@ class ContainerStopped extends Notification implements ShouldQueue
public function toMail(): MailMessage
{
$mail = new MailMessage();
$mail->subject(" Container {$this->name} has been stopped on {$this->server->name}");
$mail->subject("Coolify: Container ({$this->name}) has been stopped on {$this->server->name}");
$mail->view('emails.container-stopped', [
'containerName' => $this->name,
'serverName' => $this->server->name,
@@ -37,12 +37,12 @@ class ContainerStopped extends Notification implements ShouldQueue
public function toDiscord(): string
{
$message = " Container {$this->name} has been stopped on {$this->server->name}";
$message = "Coolify: Container ({$this->name}) has been stopped on {$this->server->name}";
return $message;
}
public function toTelegram(): array
{
$message = " Container ($this->name} has been stopped on {$this->server->name}";
$message = "Coolify: Container ($this->name} has been stopped on {$this->server->name}";
$payload = [
"message" => $message,
];

View File

@@ -3,8 +3,11 @@
namespace App\Notifications\Database;
use App\Models\ScheduledDatabaseBackup;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\TelegramChannel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Channels\MailChannel;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
@@ -24,13 +27,13 @@ class BackupFailed extends Notification implements ShouldQueue
public function via(object $notifiable): array
{
return setNotificationChannels($notifiable, 'database_backups');
return [DiscordChannel::class, TelegramChannel::class, MailChannel::class];
}
public function toMail(): MailMessage
{
$mail = new MailMessage();
$mail->subject(" [ACTION REQUIRED] Backup FAILED for {$this->database->name}");
$mail->subject("Coolify: [ACTION REQUIRED] Backup FAILED for {$this->database->name}");
$mail->view('emails.backup-failed', [
'name' => $this->name,
'frequency' => $this->frequency,
@@ -41,11 +44,11 @@ class BackupFailed extends Notification implements ShouldQueue
public function toDiscord(): string
{
return " Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}";
return "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}";
}
public function toTelegram(): array
{
$message = " Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}";
$message = "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}";
return [
"message" => $message,
];

View File

@@ -30,7 +30,7 @@ class BackupSuccess extends Notification implements ShouldQueue
public function toMail(): MailMessage
{
$mail = new MailMessage();
$mail->subject(" Backup successfully done for {$this->database->name}");
$mail->subject("Coolify: Backup successfully done for {$this->database->name}");
$mail->view('emails.backup-success', [
'name' => $this->name,
'frequency' => $this->frequency,
@@ -40,11 +40,11 @@ class BackupSuccess extends Notification implements ShouldQueue
public function toDiscord(): string
{
return " Database backup for {$this->name} with frequency of {$this->frequency} was successful.";
return "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was successful.";
}
public function toTelegram(): array
{
$message = " Database backup for {$this->name} with frequency of {$this->frequency} was successful.";
$message = "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was successful.";
return [
"message" => $message,
];

View File

@@ -0,0 +1,66 @@
<?php
namespace App\Notifications\Server;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\EmailChannel;
use App\Notifications\Channels\TelegramChannel;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class Revived extends Notification implements ShouldQueue
{
use Queueable;
public $tries = 1;
public function __construct(public Server $server)
{
if ($this->server->unreachable_email_sent === false) {
return;
}
}
public function via(object $notifiable): array
{
$channels = [];
$isEmailEnabled = isEmailEnabled($notifiable);
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
if ($isDiscordEnabled) {
$channels[] = DiscordChannel::class;
}
if ($isEmailEnabled ) {
$channels[] = EmailChannel::class;
}
if ($isTelegramEnabled) {
$channels[] = TelegramChannel::class;
}
return $channels;
}
public function toMail(): MailMessage
{
$mail = new MailMessage();
$mail->subject("Coolify: Server ({$this->server->name}) revived.");
$mail->view('emails.server-revived', [
'name' => $this->server->name,
]);
return $mail;
}
public function toDiscord(): string
{
$message = "Coolify: Server '{$this->server->name}' revived. All automations & integrations are turned on again!";
return $message;
}
public function toTelegram(): array
{
return [
"message" => "Coolify: Server '{$this->server->name}' revived. All automations & integrations are turned on again!"
];
}
}

View File

@@ -3,6 +3,9 @@
namespace App\Notifications\Server;
use App\Models\Server;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\EmailChannel;
use App\Notifications\Channels\TelegramChannel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
@@ -20,13 +23,27 @@ class Unreachable extends Notification implements ShouldQueue
public function via(object $notifiable): array
{
return setNotificationChannels($notifiable, 'status_changes');
$channels = [];
$isEmailEnabled = isEmailEnabled($notifiable);
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
if ($isDiscordEnabled) {
$channels[] = DiscordChannel::class;
}
if ($isEmailEnabled ) {
$channels[] = EmailChannel::class;
}
if ($isTelegramEnabled) {
$channels[] = TelegramChannel::class;
}
return $channels;
}
public function toMail(): MailMessage
{
$mail = new MailMessage();
$mail->subject(" Server ({$this->server->name}) is unreachable after trying to connect to it 5 times");
$mail->subject("Coolify: Server ({$this->server->name}) is unreachable after trying to connect to it 5 times");
$mail->view('emails.server-lost-connection', [
'name' => $this->server->name,
]);
@@ -35,13 +52,13 @@ class Unreachable extends Notification implements ShouldQueue
public function toDiscord(): string
{
$message = " Server '{$this->server->name}' is unreachable after trying to connect to it 5 times. All automations & integrations are turned off! Please check your server! IMPORTANT: You have to validate your server again after you fix the issue.";
$message = "Coolify: Server '{$this->server->name}' is unreachable after trying to connect to it 5 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.";
return $message;
}
public function toTelegram(): array
{
return [
"message" => " Server '{$this->server->name}' is unreachable after trying to connect to it 5 times. All automations & integrations are turned off! Please check your server! IMPORTANT: You have to validate your server again after you fix the issue."
"message" => "Coolify: Server '{$this->server->name}' is unreachable after trying to connect to it 5 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."
];
}
}

View File

@@ -24,14 +24,14 @@ class Test extends Notification implements ShouldQueue
public function toMail(): MailMessage
{
$mail = new MailMessage();
$mail->subject("Test Email");
$mail->subject("Coolify: Test Email");
$mail->view('emails.test');
return $mail;
}
public function toDiscord(): string
{
$message = 'This is a test Discord notification from Coolify.';
$message = 'Coolify: This is a test Discord notification from Coolify.';
$message .= "\n\n";
$message .= '[Go to your dashboard](' . base_url() . ')';
return $message;
@@ -39,7 +39,7 @@ class Test extends Notification implements ShouldQueue
public function toTelegram(): array
{
return [
"message" => 'This is a test Telegram notification from Coolify.',
"message" => 'Coolify: This is a test Telegram notification from Coolify.',
"buttons" => [
[
"text" => "Go to your dashboard",

View File

@@ -30,7 +30,7 @@ class InvitationLink extends Notification implements ShouldQueue
$invitation_team = Team::find($invitation->team->id);
$mail = new MailMessage();
$mail->subject('Invitation for ' . $invitation_team->name);
$mail->subject('Coolify: Invitation for ' . $invitation_team->name);
$mail->view('emails.invitation-link', [
'team' => $invitation_team->name,
'email' => $this->user->email,

View File

@@ -50,7 +50,7 @@ class ResetPassword extends Notification
protected function buildMailMessage($url)
{
$mail = new MailMessage();
$mail->subject('Reset Password');
$mail->subject('Coolify: Reset Password');
$mail->view('emails.reset-password', ['url' => $url, 'count' => config('auth.passwords.' . config('auth.defaults.passwords') . '.expire')]);
return $mail;
}

View File

@@ -25,7 +25,7 @@ class Test extends Notification implements ShouldQueue
public function toMail(): MailMessage
{
$mail = new MailMessage();
$mail->subject('Test Email');
$mail->subject('Coolify: Test Email');
$mail->view('emails.test');
return $mail;
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\View\Components\Server;
use App\Models\Server;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class Sidebar extends Component
{
/**
* Create a new component instance.
*/
public function __construct(public Server $server, public $parameters)
{
//
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
return view('components.server.sidebar');
}
}

View File

@@ -147,7 +147,7 @@ function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'applica
}
return $labels;
}
function fqdnLabelsForTraefik(Collection $domains, bool $is_force_https_enabled)
function fqdnLabelsForTraefik(Collection $domains, bool $is_force_https_enabled, $onlyPort = null)
{
$labels = collect([]);
$labels->push('traefik.enable=true');
@@ -158,7 +158,9 @@ function fqdnLabelsForTraefik(Collection $domains, bool $is_force_https_enabled)
$path = $url->getPath();
$schema = $url->getScheme();
$port = $url->getPort();
if (is_null($port) && !is_null($onlyPort)) {
$port = $onlyPort;
}
$http_label = "{$uuid}-http";
$https_label = "{$uuid}-https";
@@ -203,9 +205,12 @@ function fqdnLabelsForTraefik(Collection $domains, bool $is_force_https_enabled)
return $labels;
}
function generateLabelsApplication(Application $application, ?ApplicationPreview $preview = null): array
function generateLabelsApplication(Application $application, ?ApplicationPreview $preview = null, $ports): array
{
$onlyPort = null;
if (count($ports) === 1) {
$onlyPort = $ports[0];
}
$pull_request_id = data_get($preview, 'pull_request_id', 0);
$container_name = generateApplicationContainerName($application, $pull_request_id);
$appId = $application->id;
@@ -221,7 +226,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
$domains = Str::of(data_get($application, 'fqdn'))->explode(',');
}
// Add Traefik labels no matter which proxy is selected
$labels = $labels->merge(fqdnLabelsForTraefik($domains, $application->settings->is_force_https_enabled));
$labels = $labels->merge(fqdnLabelsForTraefik($domains, $application->settings->is_force_https_enabled,$onlyPort));
}
return $labels->all();
}

View File

@@ -50,7 +50,7 @@ function generate_github_jwt_token(GithubApp $source)
return $issuedToken;
}
function git_api(GithubApp|GitlabApp $source, string $endpoint, string $method = 'get', array|null $data = null, bool $throwError = true)
function githubApi(GithubApp|GitlabApp $source, string $endpoint, string $method = 'get', array|null $data = null, bool $throwError = true)
{
if ($source->getMorphClass() == 'App\Models\GithubApp') {
if ($source->is_public) {

View File

@@ -102,6 +102,8 @@ function generate_default_proxy_configuration(Server $server)
];
if (isDev()) {
$config['services']['traefik']['command'][] = "--log.level=debug";
$config['services']['traefik']['command'][] = "--accesslog.filepath=/traefik/access.log";
$config['services']['traefik']['command'][] = "--accesslog.bufferingsize=100";
}
$config = Yaml::dump($config, 4, 2);
SaveConfiguration::run($server, $config);
@@ -204,17 +206,23 @@ stream {
proxy_pass $database->uuid:5432;
}
}
EOF;
$dockerfile = <<< EOF
FROM nginx:stable-alpine
COPY nginx.conf /etc/nginx/nginx.conf
EOF;
$docker_compose = [
'version' => '3.8',
'services' => [
$containerName => [
'build' => [
'context' => $configuration_dir,
'dockerfile' => 'Dockerfile',
],
'image' => "nginx:stable-alpine",
'container_name' => $containerName,
'restart' => RESTART_MODE,
'volumes' => [
"$configuration_dir/nginx.conf:/etc/nginx/nginx.conf:ro",
],
'ports' => [
"$database->public_port:$database->public_port",
],
@@ -243,13 +251,13 @@ EOF;
];
$dockercompose_base64 = base64_encode(Yaml::dump($docker_compose, 4, 2));
$nginxconf_base64 = base64_encode($nginxconf);
$dockerfile_base64 = base64_encode($dockerfile);
instant_remote_process([
"mkdir -p $configuration_dir",
"echo '{$dockerfile_base64}' | base64 -d > $configuration_dir/Dockerfile",
"echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf",
"echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml",
"docker compose --project-directory {$configuration_dir} up -d >/dev/null",
"docker compose --project-directory {$configuration_dir} up --build -d >/dev/null",
], $database->destination->server);
}
function stopPostgresProxy(StandalonePostgresql $database)

View File

@@ -7,6 +7,8 @@ use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\PrivateKey;
use App\Models\Server;
use App\Notifications\Server\Revived;
use App\Notifications\Server\Unreachable;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
@@ -85,7 +87,7 @@ function generateSshCommand(Server $server, string $command, bool $isMux = true)
if ($isMux && config('coolify.mux_enabled')) {
$ssh_command .= '-o ControlMaster=auto -o ControlPersist=1m -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r ';
}
if (data_get($server,'settings.is_cloudflare_tunnel')) {
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
$ssh_command .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" ';
}
$command = "PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin && $command";
@@ -122,13 +124,14 @@ function instant_remote_process(Collection|array $command, Server $server, $thro
}
return $output;
}
function excludeCertainErrors(string $errorOutput, ?int $exitCode = null) {
function excludeCertainErrors(string $errorOutput, ?int $exitCode = null)
{
$ignoredErrors = collect([
'Permission denied (publickey',
'Could not resolve hostname',
]);
$ignored = false;
foreach ($ignoredErrors as $ignoredError) {
foreach ($ignoredErrors as $ignoredError) {
if (Str::contains($errorOutput, $ignoredError)) {
$ignored = true;
break;
@@ -177,45 +180,55 @@ function refresh_server_connection(PrivateKey $private_key)
}
}
function validateServer(Server $server, bool $throwError = false)
{
try {
$uptime = instant_remote_process(['uptime'], $server, $throwError);
if (!$uptime) {
$server->settings->is_reachable = false;
return [
"uptime" => null,
"dockerVersion" => null,
];
}
$server->settings->is_reachable = true;
instant_remote_process(["docker ps"], $server, $throwError);
$dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $server, $throwError);
if (!$dockerVersion) {
$dockerVersion = null;
return [
"uptime" => $uptime,
"dockerVersion" => null,
];
}
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
if (is_null($dockerVersion)) {
$server->settings->is_usable = false;
} else {
$server->settings->is_usable = true;
}
return [
"uptime" => $uptime,
"dockerVersion" => $dockerVersion,
];
} catch (\Throwable $e) {
$server->settings->is_reachable = false;
$server->settings->is_usable = false;
throw $e;
} finally {
if (data_get($server, 'settings')) $server->settings->save();
}
}
// function validateServer(Server $server, bool $throwError = false)
// {
// try {
// $uptime = instant_remote_process(['uptime'], $server, $throwError);
// if (!$uptime) {
// $server->settings->is_reachable = false;
// $server->team->notify(new Unreachable($server));
// $server->unreachable_email_sent = true;
// $server->save();
// return [
// "uptime" => null,
// "dockerVersion" => null,
// ];
// }
// $server->settings->is_reachable = true;
// instant_remote_process(["docker ps"], $server, $throwError);
// $dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $server, $throwError);
// if (!$dockerVersion) {
// $dockerVersion = null;
// return [
// "uptime" => $uptime,
// "dockerVersion" => null,
// ];
// }
// $dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
// if (is_null($dockerVersion)) {
// $server->settings->is_usable = false;
// } else {
// $server->settings->is_usable = true;
// if (data_get($server, 'unreachable_email_sent') === true) {
// $server->team->notify(new Revived($server));
// $server->unreachable_email_sent = false;
// $server->save();
// }
// }
// return [
// "uptime" => $uptime,
// "dockerVersion" => $dockerVersion,
// ];
// } catch (\Throwable $e) {
// $server->settings->is_reachable = false;
// $server->settings->is_usable = false;
// throw $e;
// } finally {
// if (data_get($server, 'settings')) {
// $server->settings->save();
// }
// }
// }
function checkRequiredCommands(Server $server)
{

View File

@@ -310,6 +310,7 @@ function send_internal_notification(string $message): void
$baseUrl = config('app.name');
$team = Team::find(0);
$team->notify(new GeneralNotification("👀 {$baseUrl}: " . $message));
ray("👀 {$baseUrl}: " . $message);
} catch (\Throwable $e) {
ray($e->getMessage());
}
@@ -343,6 +344,15 @@ function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null
);
}
}
function isTestEmailEnabled($notifiable)
{
if (data_get($notifiable, 'use_instance_email_settings') && isInstanceAdmin()) {
return true;
} else if (data_get($notifiable, 'smtp_enabled') || data_get($notifiable, 'resend_enabled') && auth()->user()->isAdminFromSession()) {
return true;
}
return false;
}
function isEmailEnabled($notifiable)
{
return data_get($notifiable, 'smtp_enabled') || data_get($notifiable, 'resend_enabled') || data_get($notifiable, 'use_instance_email_settings');

View File

@@ -110,7 +110,10 @@ function getStripeCustomerPortalSession(Team $team)
{
Stripe::setApiKey(config('subscription.stripe_api_key'));
$return_url = route('team.index');
$stripe_customer_id = $team->subscription->stripe_customer_id;
$stripe_customer_id = data_get($team,'subscription.stripe_customer_id');
if (!$stripe_customer_id) {
return null;
}
$session = \Stripe\BillingPortal\Session::create([
'customer' => $stripe_customer_id,
'return_url' => $return_url,
@@ -122,14 +125,14 @@ function allowedPathsForUnsubscribedAccounts()
return [
'subscription',
'login',
'register',
'logout',
'waitlist',
'force-password-reset',
'logout',
'livewire/message/force-password-reset',
'livewire/message/check-license',
'livewire/message/switch-team',
'livewire/message/subscription.pricing-plans'
'livewire/message/subscription.pricing-plans',
'livewire/message/help'
];
}
function allowedPathsForBoardingAccounts()
@@ -141,3 +144,11 @@ function allowedPathsForBoardingAccounts()
'livewire/message/activity-monitor'
];
}
function allowedPathsForInvalidAccounts() {
return [
'logout',
'verify',
'livewire/message/verify-email',
'livewire/message/help'
];
}

136
composer.lock generated
View File

@@ -62,16 +62,16 @@
},
{
"name": "aws/aws-sdk-php",
"version": "3.283.0",
"version": "3.283.2",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "5084c03431ecda0003e35d7fc7a12eeca4242685"
"reference": "6616677d76e39af28138512740199d38a461859f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/5084c03431ecda0003e35d7fc7a12eeca4242685",
"reference": "5084c03431ecda0003e35d7fc7a12eeca4242685",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/6616677d76e39af28138512740199d38a461859f",
"reference": "6616677d76e39af28138512740199d38a461859f",
"shasum": ""
},
"require": {
@@ -151,9 +151,9 @@
"support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.283.0"
"source": "https://github.com/aws/aws-sdk-php/tree/3.283.2"
},
"time": "2023-10-04T18:08:32+00:00"
"time": "2023-10-06T18:09:54+00:00"
},
{
"name": "bacon/bacon-qr-code",
@@ -1083,16 +1083,16 @@
},
{
"name": "egulias/email-validator",
"version": "4.0.1",
"version": "4.0.2",
"source": {
"type": "git",
"url": "https://github.com/egulias/EmailValidator.git",
"reference": "3a85486b709bc384dae8eb78fb2eec649bdb64ff"
"reference": "ebaaf5be6c0286928352e054f2d5125608e5405e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/3a85486b709bc384dae8eb78fb2eec649bdb64ff",
"reference": "3a85486b709bc384dae8eb78fb2eec649bdb64ff",
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/ebaaf5be6c0286928352e054f2d5125608e5405e",
"reference": "ebaaf5be6c0286928352e054f2d5125608e5405e",
"shasum": ""
},
"require": {
@@ -1101,8 +1101,8 @@
"symfony/polyfill-intl-idn": "^1.26"
},
"require-dev": {
"phpunit/phpunit": "^9.5.27",
"vimeo/psalm": "^4.30"
"phpunit/phpunit": "^10.2",
"vimeo/psalm": "^5.12"
},
"suggest": {
"ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation"
@@ -1138,7 +1138,7 @@
],
"support": {
"issues": "https://github.com/egulias/EmailValidator/issues",
"source": "https://github.com/egulias/EmailValidator/tree/4.0.1"
"source": "https://github.com/egulias/EmailValidator/tree/4.0.2"
},
"funding": [
{
@@ -1146,7 +1146,7 @@
"type": "github"
}
],
"time": "2023-01-14T14:17:03+00:00"
"time": "2023-10-06T06:47:41+00:00"
},
{
"name": "fruitcake/php-cors",
@@ -2724,16 +2724,16 @@
},
{
"name": "league/flysystem",
"version": "3.16.0",
"version": "3.17.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem.git",
"reference": "4fdf372ca6b63c6e281b1c01a624349ccb757729"
"reference": "bd4c9b26849d82364119c68429541f1631fba94b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/4fdf372ca6b63c6e281b1c01a624349ccb757729",
"reference": "4fdf372ca6b63c6e281b1c01a624349ccb757729",
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/bd4c9b26849d82364119c68429541f1631fba94b",
"reference": "bd4c9b26849d82364119c68429541f1631fba94b",
"shasum": ""
},
"require": {
@@ -2751,8 +2751,8 @@
"symfony/http-client": "<5.2"
},
"require-dev": {
"async-aws/s3": "^1.5",
"async-aws/simple-s3": "^1.1",
"async-aws/s3": "^1.5 || ^2.0",
"async-aws/simple-s3": "^1.1 || ^2.0",
"aws/aws-sdk-php": "^3.220.0",
"composer/semver": "^3.0",
"ext-fileinfo": "*",
@@ -2798,7 +2798,7 @@
],
"support": {
"issues": "https://github.com/thephpleague/flysystem/issues",
"source": "https://github.com/thephpleague/flysystem/tree/3.16.0"
"source": "https://github.com/thephpleague/flysystem/tree/3.17.0"
},
"funding": [
{
@@ -2810,7 +2810,7 @@
"type": "github"
}
],
"time": "2023-09-07T19:22:17+00:00"
"time": "2023-10-05T20:15:05+00:00"
},
{
"name": "league/flysystem-aws-s3-v3",
@@ -3557,16 +3557,16 @@
},
{
"name": "nette/schema",
"version": "v1.2.4",
"version": "v1.2.5",
"source": {
"type": "git",
"url": "https://github.com/nette/schema.git",
"reference": "c9ff517a53903b3d4e29ec547fb20feecb05b8ab"
"reference": "0462f0166e823aad657c9224d0f849ecac1ba10a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/schema/zipball/c9ff517a53903b3d4e29ec547fb20feecb05b8ab",
"reference": "c9ff517a53903b3d4e29ec547fb20feecb05b8ab",
"url": "https://api.github.com/repos/nette/schema/zipball/0462f0166e823aad657c9224d0f849ecac1ba10a",
"reference": "0462f0166e823aad657c9224d0f849ecac1ba10a",
"shasum": ""
},
"require": {
@@ -3613,9 +3613,9 @@
],
"support": {
"issues": "https://github.com/nette/schema/issues",
"source": "https://github.com/nette/schema/tree/v1.2.4"
"source": "https://github.com/nette/schema/tree/v1.2.5"
},
"time": "2023-08-05T18:56:25+00:00"
"time": "2023-10-05T20:37:59+00:00"
},
{
"name": "nette/utils",
@@ -6757,16 +6757,16 @@
},
{
"name": "stripe/stripe-php",
"version": "v12.5.0",
"version": "v12.6.0",
"source": {
"type": "git",
"url": "https://github.com/stripe/stripe-php.git",
"reference": "a4249b4a90437844f6c35e8701f8c68acd206f56"
"reference": "e0c15e4cbf252e708b937482bb1a50fa7e01bc9d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/stripe/stripe-php/zipball/a4249b4a90437844f6c35e8701f8c68acd206f56",
"reference": "a4249b4a90437844f6c35e8701f8c68acd206f56",
"url": "https://api.github.com/repos/stripe/stripe-php/zipball/e0c15e4cbf252e708b937482bb1a50fa7e01bc9d",
"reference": "e0c15e4cbf252e708b937482bb1a50fa7e01bc9d",
"shasum": ""
},
"require": {
@@ -6811,9 +6811,9 @@
],
"support": {
"issues": "https://github.com/stripe/stripe-php/issues",
"source": "https://github.com/stripe/stripe-php/tree/v12.5.0"
"source": "https://github.com/stripe/stripe-php/tree/v12.6.0"
},
"time": "2023-09-28T23:06:27+00:00"
"time": "2023-10-05T18:01:43+00:00"
},
{
"name": "symfony/console",
@@ -10258,16 +10258,16 @@
"packages-dev": [
{
"name": "brianium/paratest",
"version": "v7.2.8",
"version": "v7.2.9",
"source": {
"type": "git",
"url": "https://github.com/paratestphp/paratest.git",
"reference": "882b02d197328138686bb06ce7d8cbb98fc0a16c"
"reference": "1f9e41c0779be4540654d92a9314016713f5e62c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paratestphp/paratest/zipball/882b02d197328138686bb06ce7d8cbb98fc0a16c",
"reference": "882b02d197328138686bb06ce7d8cbb98fc0a16c",
"url": "https://api.github.com/repos/paratestphp/paratest/zipball/1f9e41c0779be4540654d92a9314016713f5e62c",
"reference": "1f9e41c0779be4540654d92a9314016713f5e62c",
"shasum": ""
},
"require": {
@@ -10275,13 +10275,13 @@
"ext-pcre": "*",
"ext-reflection": "*",
"ext-simplexml": "*",
"fidry/cpu-core-counter": "^0.4.1 || ^0.5.1",
"fidry/cpu-core-counter": "^0.5.1",
"jean85/pretty-package-versions": "^2.0.5",
"php": "~8.1.0 || ~8.2.0 || ~8.3.0",
"phpunit/php-code-coverage": "^10.1.3",
"phpunit/php-file-iterator": "^4.0.2",
"phpunit/php-code-coverage": "^10.1.7",
"phpunit/php-file-iterator": "^4.1.0",
"phpunit/php-timer": "^6.0",
"phpunit/phpunit": "^10.3.2",
"phpunit/phpunit": "^10.4.0",
"sebastian/environment": "^6.0.1",
"symfony/console": "^6.3.4",
"symfony/process": "^6.3.4"
@@ -10290,8 +10290,8 @@
"doctrine/coding-standard": "^12.0.0",
"ext-pcov": "*",
"ext-posix": "*",
"infection/infection": "^0.27.0",
"phpstan/phpstan": "^1.10.32",
"infection/infection": "^0.27.3",
"phpstan/phpstan": "^1.10.37",
"phpstan/phpstan-deprecation-rules": "^1.1.4",
"phpstan/phpstan-phpunit": "^1.3.14",
"phpstan/phpstan-strict-rules": "^1.5.1",
@@ -10337,7 +10337,7 @@
],
"support": {
"issues": "https://github.com/paratestphp/paratest/issues",
"source": "https://github.com/paratestphp/paratest/tree/v7.2.8"
"source": "https://github.com/paratestphp/paratest/tree/v7.2.9"
},
"funding": [
{
@@ -10349,7 +10349,7 @@
"type": "paypal"
}
],
"time": "2023-10-04T13:38:04+00:00"
"time": "2023-10-06T07:53:04+00:00"
},
{
"name": "fakerphp/faker",
@@ -10983,35 +10983,35 @@
},
{
"name": "pestphp/pest",
"version": "v2.20.0",
"version": "v2.21.0",
"source": {
"type": "git",
"url": "https://github.com/pestphp/pest.git",
"reference": "a8b785f69e44ae3f902cbf08fe6b79359ba46945"
"reference": "2ffafd445d42c8b7b7e1874bde1c29945767a49d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pestphp/pest/zipball/a8b785f69e44ae3f902cbf08fe6b79359ba46945",
"reference": "a8b785f69e44ae3f902cbf08fe6b79359ba46945",
"url": "https://api.github.com/repos/pestphp/pest/zipball/2ffafd445d42c8b7b7e1874bde1c29945767a49d",
"reference": "2ffafd445d42c8b7b7e1874bde1c29945767a49d",
"shasum": ""
},
"require": {
"brianium/paratest": "^7.2.7",
"brianium/paratest": "^7.2.9",
"nunomaduro/collision": "^7.9.0",
"nunomaduro/termwind": "^1.15.1",
"pestphp/pest-plugin": "^2.1.1",
"pestphp/pest-plugin-arch": "^2.3.3",
"php": "^8.1.0",
"phpunit/phpunit": "^10.3.5"
"phpunit/phpunit": "^10.4.0"
},
"conflict": {
"phpunit/phpunit": ">10.3.5",
"phpunit/phpunit": ">10.4.0",
"sebastian/exporter": "<5.1.0",
"webmozart/assert": "<1.11.0"
},
"require-dev": {
"pestphp/pest-dev-tools": "^2.16.0",
"pestphp/pest-plugin-type-coverage": "^2.2.0",
"pestphp/pest-plugin-type-coverage": "^2.4.0",
"symfony/process": "^6.3.4"
},
"bin": [
@@ -11070,7 +11070,7 @@
],
"support": {
"issues": "https://github.com/pestphp/pest/issues",
"source": "https://github.com/pestphp/pest/tree/v2.20.0"
"source": "https://github.com/pestphp/pest/tree/v2.21.0"
},
"funding": [
{
@@ -11082,7 +11082,7 @@
"type": "github"
}
],
"time": "2023-09-29T18:05:52+00:00"
"time": "2023-10-06T12:33:39+00:00"
},
{
"name": "pestphp/pest-plugin",
@@ -11454,16 +11454,16 @@
},
{
"name": "phpstan/phpstan",
"version": "1.10.37",
"version": "1.10.38",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "058ba07e92f744d4dcf6061ae75283d0c6456f2e"
"reference": "5302bb402c57f00fb3c2c015bac86e0827e4b691"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/058ba07e92f744d4dcf6061ae75283d0c6456f2e",
"reference": "058ba07e92f744d4dcf6061ae75283d0c6456f2e",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/5302bb402c57f00fb3c2c015bac86e0827e4b691",
"reference": "5302bb402c57f00fb3c2c015bac86e0827e4b691",
"shasum": ""
},
"require": {
@@ -11512,7 +11512,7 @@
"type": "tidelift"
}
],
"time": "2023-10-02T16:18:37+00:00"
"time": "2023-10-06T14:19:14+00:00"
},
{
"name": "phpunit/php-code-coverage",
@@ -11837,16 +11837,16 @@
},
{
"name": "phpunit/phpunit",
"version": "10.3.5",
"version": "10.4.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "747c3b2038f1139e3dcd9886a3f5a948648b7503"
"reference": "9784e877e3700de37475545bdbdce8383ff53d25"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/747c3b2038f1139e3dcd9886a3f5a948648b7503",
"reference": "747c3b2038f1139e3dcd9886a3f5a948648b7503",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9784e877e3700de37475545bdbdce8383ff53d25",
"reference": "9784e877e3700de37475545bdbdce8383ff53d25",
"shasum": ""
},
"require": {
@@ -11886,7 +11886,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "10.3-dev"
"dev-main": "10.4-dev"
}
},
"autoload": {
@@ -11918,7 +11918,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.3.5"
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.4.0"
},
"funding": [
{
@@ -11934,7 +11934,7 @@
"type": "tidelift"
}
],
"time": "2023-09-19T05:42:37+00:00"
"time": "2023-10-06T03:41:22+00:00"
},
{
"name": "sebastian/cli-parser",

View File

@@ -1,6 +1,7 @@
<?php
return [
'docs' => 'https://coolify.io/docs/contact',
'self_hosted' => env('SELF_HOSTED', true),
'waitlist' => env('WAITLIST', false),
'license_url' => 'https://licenses.coollabs.io',

View File

@@ -7,7 +7,7 @@ return [
// The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.67',
'release' => '4.0.0-beta.75',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),

View File

@@ -1,3 +1,3 @@
<?php
return '4.0.0-beta.67';
return '4.0.0-beta.75';

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('teams', function (Blueprint $table) {
$table->boolean('use_instance_email_settings')->default(true)->change();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('teams', function (Blueprint $table) {
$table->boolean('use_instance_email_settings')->default(false)->change();
});
}
};

View File

@@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('teams', function (Blueprint $table) {
$table->boolean('smtp_notifications_deployments')->default(true)->change();
$table->boolean('smtp_notifications_status_changes')->default(true)->change();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('teams', function (Blueprint $table) {
$table->boolean('smtp_notifications_deployments')->default(false)->change();
$table->boolean('smtp_notifications_status_changes')->default(false)->change();
});
}
};

View File

@@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('servers', function (Blueprint $table) {
$table->boolean('unreachable_email_sent')->default(false);
$table->dropColumn('unreachable_count');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('servers', function (Blueprint $table) {
$table->dropColumn('unreachable_email_sent');
$table->integer('unreachable_count')->default(0);
});
}
};

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('servers', function (Blueprint $table) {
$table->integer('unreachable_count')->default(0);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('servers', function (Blueprint $table) {
$table->dropColumn('unreachable_count');
});
}
};

View File

@@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('s3_storages', function (Blueprint $table) {
$table->boolean('is_usable')->default(false);
$table->boolean('unusable_email_sent')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('s3_storages', function (Blueprint $table) {
$table->dropColumn('is_usable');
$table->dropColumn('unusable_email_sent');
});
}
};

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('applications', function (Blueprint $table) {
$table->string('dockerfile_location')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('applications', function (Blueprint $table) {
$table->dropColumn('dockerfile_location');
});
}
};

View File

@@ -34,14 +34,16 @@ services:
POSTGRES_DB: "${DB_DATABASE:-coolify}"
POSTGRES_HOST_AUTH_METHOD: "trust"
volumes:
- coolify-pg-data-dev:/var/lib/postgresql/data
- ./_data/coolify/_volumes/database/:/var/lib/postgresql/data
# - coolify-pg-data-dev:/var/lib/postgresql/data
redis:
ports:
- "${FORWARD_REDIS_PORT:-6379}:6379"
env_file:
- .env
volumes:
- coolify-redis-data-dev:/data
- ./_data/coolify/_volumes/redis/:/data
# - coolify-redis-data-dev:/data
vite:
image: node:19
working_dir: /var/www/html
@@ -56,7 +58,8 @@ services:
volumes:
- /:/host
- /var/run/docker.sock:/var/run/docker.sock
- coolify-data-dev:/data/coolify
- ./_data/coolify/:/data/coolify
# - coolify-data-dev:/data/coolify
mailpit:
image: "axllent/mailpit:latest"
container_name: coolify-mail
@@ -76,7 +79,8 @@ services:
MINIO_ACCESS_KEY: "${MINIO_ACCESS_KEY:-minioadmin}"
MINIO_SECRET_KEY: "${MINIO_SECRET_KEY:-minioadmin}"
volumes:
- coolify-minio-data-dev:/data
- ./_data/coolify/_volumes/minio/:/data
# - coolify-minio-data-dev:/data
networks:
- coolify

View File

@@ -8,9 +8,9 @@ ARG DOCKER_COMPOSE_VERSION=2.21.0
# https://github.com/docker/buildx/releases
ARG DOCKER_BUILDX_VERSION=0.11.2
# https://github.com/buildpacks/pack/releases
ARG PACK_VERSION=0.30.0
ARG PACK_VERSION=0.31.0
# https://github.com/railwayapp/nixpacks/releases
ARG NIXPACKS_VERSION=1.16.0
ARG NIXPACKS_VERSION=1.17.0
USER root
WORKDIR /artifacts

View File

@@ -20,5 +20,5 @@
"input.code": "One-time code",
"input.recovery_code": "Recovery code",
"button.save": "Save",
"repository.url": "<span class='text-helper'>Examples</span><br>https://github.com/coollabsio/coolify-examples <span class='text-helper'>main</span> branch will be selected<br><br>https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify <span class='text-helper'>nodejs-fastify</span> branch will be selected"
}
"repository.url": "<span class='text-helper'>Examples</span><br>For Public repositories, use <span class='text-helper'>https://...</span>.<br>For Private repositories, use <span class='text-helper'>git@...</span>.<br><br>https://github.com/coollabsio/coolify-examples <span class='text-helper'>main</span> branch will be selected<br>https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify <span class='text-helper'>nodejs-fastify</span> branch will be selected.<br>https://gitea.com/sedlav/expressjs.git <span class='text-helper'>main</span> branch will be selected.<br>https://gitlab.com/andrasbacsai/nodejs-example.git <span class='text-helper'>main</span> branch will be selected."
}

58
package-lock.json generated
View File

@@ -6,18 +6,18 @@
"": {
"dependencies": {
"@tailwindcss/typography": "0.5.10",
"alpinejs": "3.13.0",
"daisyui": "3.7.7",
"alpinejs": "3.13.1",
"daisyui": "3.9.2",
"tailwindcss-scrollbar": "0.1.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "4.3.4",
"@vitejs/plugin-vue": "4.4.0",
"autoprefixer": "10.4.16",
"axios": "1.5.0",
"laravel-vite-plugin": "0.8.0",
"postcss": "8.4.30",
"axios": "1.5.1",
"laravel-vite-plugin": "0.8.1",
"postcss": "8.4.31",
"tailwindcss": "3.3.3",
"vite": "4.4.9",
"vite": "4.4.11",
"vue": "3.3.4"
}
},
@@ -503,9 +503,9 @@
}
},
"node_modules/@vitejs/plugin-vue": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.3.4.tgz",
"integrity": "sha512-ciXNIHKPriERBisHFBvnTbfKa6r9SAesOYXeGDzgegcvy9Q4xdScSHAmKbNT0M3O0S9LKhIf5/G+UYG4NnnzYw==",
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.4.0.tgz",
"integrity": "sha512-xdguqb+VUwiRpSg+nsc2HtbAUSGak25DXYvpQQi4RVU1Xq1uworyoH/md9Rfd8zMmPR/pSghr309QNcftUVseg==",
"dev": true,
"engines": {
"node": "^14.18.0 || >=16.0.0"
@@ -683,9 +683,9 @@
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA=="
},
"node_modules/alpinejs": {
"version": "3.13.0",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.0.tgz",
"integrity": "sha512-7FYR1Yz3evIjlJD1mZ3SYWSw+jlOmQGeQ1QiSufSQ6J84XMQFkzxm6OobiZ928SfqhGdoIp2SsABNsS4rXMMJw==",
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.1.tgz",
"integrity": "sha512-/LZ7mumW02V7AV5xTTftJFHS0I3KOXLl7tHm4xpxXAV+HJ/zjTT0n8MU7RZ6UoGPhmO/i+KEhQojaH/0RsH5tg==",
"dependencies": {
"@vue/reactivity": "~3.1.1"
}
@@ -756,9 +756,9 @@
}
},
"node_modules/axios": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.5.0.tgz",
"integrity": "sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==",
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz",
"integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==",
"dev": true,
"dependencies": {
"follow-redirects": "^1.15.0",
@@ -953,15 +953,15 @@
"dev": true
},
"node_modules/daisyui": {
"version": "3.7.7",
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-3.7.7.tgz",
"integrity": "sha512-2/nFdW/6R9MMnR8tTm07jPVyPaZwpUSkVsFAADb7Oq8N2Ynbls57laDdNqxTCUmn0QvcZi01TKl8zQbAwRfw1w==",
"version": "3.9.2",
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-3.9.2.tgz",
"integrity": "sha512-yJZ1QjHUaL+r9BkquTdzNHb7KIgAJVFh0zbOXql2Wu0r7zx5qZNLxclhjN0WLoIpY+o2h/8lqXg7ijj8oTigOw==",
"dependencies": {
"colord": "^2.9",
"css-selector-tokenizer": "^0.8",
"postcss": "^8",
"postcss-js": "^4",
"tailwindcss": "^3"
"tailwindcss": "^3.1"
},
"engines": {
"node": ">=16.9.0"
@@ -1289,9 +1289,9 @@
}
},
"node_modules/laravel-vite-plugin": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-0.8.0.tgz",
"integrity": "sha512-6VjLI+azBpeK6rWBiKcb/En5GnTdYpL0U4zS8gXYvb2/VSq4mlau5H3NWpSktUDBMM1b97LLgICx5zevi8IY0w==",
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-0.8.1.tgz",
"integrity": "sha512-fxzUDjOA37kOsYq8dP+3oPIlw8/kJVXwu0hOXLun82R1LpV02shGeWGYKx2lbpKffL5I0sfPPjfqbYxuqBluAA==",
"dev": true,
"dependencies": {
"picocolors": "^1.0.0",
@@ -1516,9 +1516,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.30",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.30.tgz",
"integrity": "sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g==",
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"funding": [
{
"type": "opencollective",
@@ -1920,9 +1920,9 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/vite": {
"version": "4.4.9",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz",
"integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==",
"version": "4.4.11",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.4.11.tgz",
"integrity": "sha512-ksNZJlkcU9b0lBwAGZGGaZHCMqHsc8OpgtoYhsQ4/I2v5cnpmmmqe5pM4nv/4Hn6G/2GhTdj0DhZh2e+Er1q5A==",
"dev": true,
"dependencies": {
"esbuild": "^0.18.10",

View File

@@ -6,19 +6,19 @@
"build": "vite build"
},
"devDependencies": {
"@vitejs/plugin-vue": "4.3.4",
"@vitejs/plugin-vue": "4.4.0",
"autoprefixer": "10.4.16",
"axios": "1.5.0",
"laravel-vite-plugin": "0.8.0",
"postcss": "8.4.30",
"axios": "1.5.1",
"laravel-vite-plugin": "0.8.1",
"postcss": "8.4.31",
"tailwindcss": "3.3.3",
"vite": "4.4.9",
"vite": "4.4.11",
"vue": "3.3.4"
},
"dependencies": {
"@tailwindcss/typography": "0.5.10",
"alpinejs": "3.13.0",
"daisyui": "3.7.7",
"alpinejs": "3.13.1",
"daisyui": "3.9.2",
"tailwindcss-scrollbar": "0.1.0"
}
}

View File

@@ -56,7 +56,7 @@ a {
@apply flex items-center p-2 transition-colors cursor-pointer min-h-16 bg-coolgray-200 hover:bg-coollabs-100 hover:text-white hover:no-underline min-w-[24rem];
}
.box-without-bg {
@apply flex items-center p-2 transition-colors min-h-16 hover:text-white hover:no-underline min-w-[24rem];
@apply flex items-center p-2 transition-colors min-h-16 hover:text-white hover:no-underline min-w-[24rem];
}
.lds-heart {

View File

@@ -9,12 +9,12 @@
@if ($is_registration_enabled)
@if (config('coolify.waitlist'))
<a href="/waitlist"
class="text-xs normal-case hover:no-underline btn btn-sm bg-coollabs-gradient">
class="text-xs text-center text-white normal-case bg-transparent border-none rounded no-animation hover:no-underline btn btn-sm bg-coollabs-gradient">
Join the waitlist
</a>
@else
<a href="/register"
class="text-xs normal-case hover:no-underline btn btn-sm bg-coollabs-gradient">
class="text-xs text-center text-white normal-case bg-transparent border-none rounded no-animation hover:no-underline btn btn-sm bg-coollabs-gradient">
{{ __('auth.register_now') }}
</a>
@endif

View File

@@ -0,0 +1,12 @@
<x-layout-subscription>
<div class="min-h-screen hero">
<div class="min-w-fit">
<h1> Verification Email Sent </h1>
<div class="flex justify-center gap-2 text-center">
<br>To activate your account, please open the email and follow the
instructions.
</div>
<livewire:verify-email />
</div>
</div>
</x-layout-subscription>

View File

@@ -16,7 +16,7 @@
<x-applications.advanced :application="$application" />
@if ($application->status !== 'exited')
<button wire:click='deploy' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
<button title="With rolling update if possible" wire:click='deploy' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-warning" viewBox="0 0 24 24" stroke-width="2"
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
@@ -25,7 +25,7 @@
</path>
<path d="M7.05 11.038v-3.988"></path>
</svg>
Restart
Redeploy
</button>
<button wire:click='stop' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" stroke-width="2"

View File

@@ -3,4 +3,4 @@
Thank you,<br>
{{ config('app.name') ?? 'Coolify' }}
{{ Illuminate\Mail\Markdown::parse('[Contact Support](https://docs.coollabs.io/contact)') }}
{{ Illuminate\Mail\Markdown::parse('[Contact Support](https://coolify.io/docs/contact)') }}

View File

@@ -81,7 +81,7 @@
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
clip-rule="evenodd" />
</svg>
1 server <x-helper helper="Bring Your Own Server. All you need is n SSH connection." />
1 server <x-helper helper="Bring Your Own Server." />
</li>
<li class="flex gap-x-3">
<svg class="flex-none w-5 h-6 text-warning" viewBox="0 0 20 20" fill="currentColor"
@@ -90,7 +90,16 @@
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
clip-rule="evenodd" />
</svg>
Basic Support
Included Email System
</li>
<li class="flex gap-x-3">
<svg class="flex-none w-5 h-6 text-warning" viewBox="0 0 20 20" fill="currentColor"
aria-hidden="true">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
clip-rule="evenodd" />
</svg>
Email Support
</li>
<li class="flex font-bold text-white gap-x-3">
<svg width="512" height="512" class="flex-none w-5 h-6 text-green-600"
@@ -139,7 +148,7 @@
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
clip-rule="evenodd" />
</svg>
10 servers <x-helper helper="Bring Your Own Server. All you need is n SSH connection." />
10 servers <x-helper helper="Bring Your Own Server." />
</li>
<li class="flex gap-x-3">
<svg class="flex-none w-5 h-6 text-warning" viewBox="0 0 20 20" fill="currentColor"
@@ -157,7 +166,7 @@
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
clip-rule="evenodd" />
</svg>
Email Support
Priority Email Support
</li>
<li class="flex font-bold text-white gap-x-3">
<svg width="512" height="512" class="flex-none w-5 h-6 text-green-600"
@@ -206,7 +215,7 @@
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
clip-rule="evenodd" />
</svg>
? servers <x-helper helper="Bring Your Own Server. All you need is n SSH connection." />
? servers <x-helper helper="Bring Your Own Server." />
</li>
<li class="flex gap-x-3">
@@ -247,7 +256,7 @@
your self-hosted instance?
<x-forms.button>
<a class="font-bold text-white hover:no-underline"
href="https://docs.coollabs.io/contact">Contact Us</a>
href="{{ config('coolify.docs') }}">Contact Us</a>
</x-forms.button>
</div>
</div>

View File

@@ -8,25 +8,25 @@
<nav class="navbar-main">
<a class="{{ request()->routeIs('server.show') ? 'text-white' : '' }}"
href="{{ route('server.show', [
'server_uuid' => Route::current()->parameters()['server_uuid'],
'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}">
<button>General</button>
</a>
<a class="{{ request()->routeIs('server.private-key') ? 'text-white' : '' }}"
href="{{ route('server.private-key', [
'server_uuid' => Route::current()->parameters()['server_uuid'],
'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}">
<button>Private Key</button>
</a>
<a class="{{ request()->routeIs('server.proxy') ? 'text-white' : '' }}"
href="{{ route('server.proxy', [
'server_uuid' => Route::current()->parameters()['server_uuid'],
'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}">
<button>Proxy</button>
</a>
<a class="{{ request()->routeIs('server.destinations') ? 'text-white' : '' }}"
href="{{ route('server.destinations', [
'server_uuid' => Route::current()->parameters()['server_uuid'],
'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}">
<button>Destinations</button>
</a>

View File

@@ -0,0 +1,20 @@
<div>
@if ($server->isFunctional())
<div class="flex h-full pr-4">
<div class="flex flex-col gap-4 min-w-fit">
<a class="{{ request()->routeIs('server.proxy') ? 'text-white' : '' }}"
href="{{ route('server.proxy', $parameters) }}">
<button>Configuration</button>
</a>
@if (data_get($server, 'proxy.type') !== 'NONE')
<a class="{{ request()->routeIs('server.proxy.logs') ? 'text-white' : '' }}"
href="{{ route('server.proxy.logs', $parameters) }}">
<button>Logs</button>
</a>
@endif
</div>
</div>
@else
<div>Server is not validated. Validate first.</div>
@endif
</div>

View File

@@ -1,11 +1,14 @@
<div class="pb-6">
<h1>Team</h1>
<div class="flex items-end gap-2">
<h1>Team</h1>
<a href="/team/new"><x-forms.button>+ New Team</x-forms.button></a>
</div>
<nav class="flex pt-2 pb-10">
<ol class="inline-flex items-center">
<li>
<div class="flex items-center">
<span>Currently active team: <span
class="text-warning">{{ session('currentTeam.name') }}</span></span>
class="text-warning">{{ session('currentTeam.name') }}</span></span>
</div>
</li>
</ol>

View File

@@ -5,10 +5,6 @@ Container ({{ $containerName }}) has been restarted automatically on {{$serverNa
@if ($containerName === 'coolify-proxy')
Coolify Proxy should run on your server as you have FQDNs set up in one of your resources.
Note: The proxy should not stop unexpectedly, so please check what is going on your server.
If you don't want to use Coolify Proxy, please remove FQDN from your resources or set Proxy type to Custom(None).
@endif

View File

@@ -0,0 +1,3 @@
<x-emails.layout>
Verify your email [here]({{ $url }}).
</x-emails.layout>

View File

@@ -0,0 +1,6 @@
<x-emails.layout>
Connection could not be establised with one of your S3 Storage ({{ $name }}). Please fix it
[here]({{ $url }}).
{{ $reason }}
</x-emails.layout>

View File

@@ -4,7 +4,7 @@ Coolify cannot connect to your server ({{$name}}). Please check your server and
All automations & integrations are turned off!
IMPORTANT: You have to validate your server again after you fix the issue.
IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations.
If you have any questions, please contact us.

View File

@@ -0,0 +1,6 @@
<x-emails.layout>
Your server ({{$name}}) was offline for a while, but it is back online now. All automations & integrations are turned on again.
</x-emails.layout>

View File

@@ -10,7 +10,7 @@
<a href="/">
<x-forms.button>Go back home</x-forms.button>
</a>
<a target="_blank" class="text-xs" href="https://docs.coollabs.io/contact.html">Contact
<a target="_blank" class="text-xs" href="{{ config('coolify.docs') }}">Contact
support
<x-external-link />
</a>

Some files were not shown because too many files have changed in this diff Show More