mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-25 12:33:35 +00:00
Compare commits
28 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b47c327b55 | ||
|
|
25434a7acd | ||
|
|
0de042dbac | ||
|
|
eb9e2203b0 | ||
|
|
dcaa7a6ad7 | ||
|
|
5b584a6c6d | ||
|
|
c40ea6f1da | ||
|
|
61a7b9ac94 | ||
|
|
9e81416fef | ||
|
|
c58706e3e4 | ||
|
|
45bca8649b | ||
|
|
b095b88281 | ||
|
|
cb41584137 | ||
|
|
6659153804 | ||
|
|
44c429a224 | ||
|
|
fe9c501c1d | ||
|
|
4e94b4a0c1 | ||
|
|
2f4d7c0e43 | ||
|
|
e443fc394a | ||
|
|
5b56c50f03 | ||
|
|
3adeb2f73f | ||
|
|
9eaa13a08a | ||
|
|
df5a4a9667 | ||
|
|
26048339d6 | ||
|
|
d85af3fefc | ||
|
|
575338609b | ||
|
|
d32e43ef37 | ||
|
|
a96ef1bfab |
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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('{
|
||||
|
||||
33
app/Console/Commands/Cloud.php
Normal file
33
app/Console/Commands/Cloud.php
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,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();
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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', [
|
||||
|
||||
29
app/Http/Livewire/Server/Create.php
Normal file
29
app/Http/Livewire/Server/Create.php
Normal 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');
|
||||
}
|
||||
}
|
||||
28
app/Http/Livewire/Server/Destination/Show.php
Normal file
28
app/Http/Livewire/Server/Destination/Show.php
Normal 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');
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
31
app/Http/Livewire/Server/PrivateKey/Show.php
Normal file
31
app/Http/Livewire/Server/PrivateKey/Show.php
Normal 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(['name', 'proxy'])->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');
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
28
app/Http/Livewire/Server/Proxy/Show.php
Normal file
28
app/Http/Livewire/Server/Proxy/Show.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Server\Proxy;
|
||||
|
||||
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.proxy.show');
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -32,36 +32,34 @@ class ShowPrivateKey extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function checkConnection()
|
||||
public function checkConnection($install = false)
|
||||
{
|
||||
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.');
|
||||
$install && $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.');
|
||||
$install && $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.');
|
||||
$dockerInstalled = $this->server->validateDockerEngine();
|
||||
if ($dockerInstalled) {
|
||||
$install && $this->emit('success', 'Docker Engine is installed.<br> Checking version.');
|
||||
} 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.');
|
||||
$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);
|
||||
} finally {
|
||||
$this->emit('proxyStatusUpdated');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
private string $commit;
|
||||
private bool $force_rebuild;
|
||||
|
||||
private GithubApp|GitlabApp $source;
|
||||
private GithubApp|GitlabApp|string $source = 'other';
|
||||
private StandaloneDocker|SwarmDocker $destination;
|
||||
private Server $server;
|
||||
private ApplicationPreview|null $preview = null;
|
||||
@@ -80,7 +80,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 +92,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();
|
||||
@@ -162,12 +165,12 @@ 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()
|
||||
@@ -251,7 +254,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 +339,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 +361,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 +398,13 @@ 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 +456,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 +466,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)
|
||||
|
||||
@@ -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'];
|
||||
|
||||
@@ -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,49 @@ 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;
|
||||
$serverUptimeCheckNumberMax = 3;
|
||||
while (true) {
|
||||
ray('checking # ' . $serverUptimeCheckNumber);
|
||||
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
|
||||
$this->server->settings()->update(['is_reachable' => false]);
|
||||
$this->server->team->notify(new Unreachable($this->server));
|
||||
send_internal_notification('Server unreachable: ' . $this->server->name);
|
||||
if ($this->server->unreachable_email_sent === false) {
|
||||
ray('Server unreachable, sending notification...');
|
||||
$this->server->team->notify(new Unreachable($this->server));
|
||||
}
|
||||
$this->server->settings()->update([
|
||||
'is_reachable' => false,
|
||||
]);
|
||||
$this->server->update(['unreachable_email_sent' => true]);
|
||||
return;
|
||||
}
|
||||
$result = $this->checkServerConnection();
|
||||
$result = $this->server->validateConnection();
|
||||
if ($result) {
|
||||
break;
|
||||
}
|
||||
$serverUptimeCheckNumber++;
|
||||
sleep(5);
|
||||
}
|
||||
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 +283,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -68,6 +82,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,6 +95,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}/commits/{$this->git_branch}";
|
||||
}
|
||||
return $this->git_repository;
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -224,6 +240,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,6 +257,7 @@ class Application extends BaseModel
|
||||
if ($this->dockerfile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
public function isHealthcheckDisabled(): bool
|
||||
|
||||
@@ -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,13 @@ class Server extends BaseModel
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::saving(function ($server) {
|
||||
$server->forceFill([
|
||||
'ip' => Str::of($server->ip)->trim(),
|
||||
'user' => Str::of($server->user)->trim(),
|
||||
]);
|
||||
});
|
||||
|
||||
static::created(function ($server) {
|
||||
ServerSetting::create([
|
||||
'server_id' => $server->id,
|
||||
@@ -199,4 +207,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);
|
||||
}
|
||||
}
|
||||
|
||||
66
app/Notifications/Server/Revived.php
Normal file
66
app/Notifications/Server/Revived.php
Normal 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("✅ Server ({$this->server->name}) revived.");
|
||||
$mail->view('emails.server-revived', [
|
||||
'name' => $this->server->name,
|
||||
]);
|
||||
return $mail;
|
||||
}
|
||||
|
||||
public function toDiscord(): string
|
||||
{
|
||||
$message = "✅ Server '{$this->server->name}' revived. All automations & integrations are turned on again!";
|
||||
return $message;
|
||||
}
|
||||
public function toTelegram(): array
|
||||
{
|
||||
return [
|
||||
"message" => "✅ Server '{$this->server->name}' revived. All automations & integrations are turned on again!"
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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,7 +23,21 @@ 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
|
||||
@@ -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 = "⛔ 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" => "⛔ 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."
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
@@ -183,6 +186,9 @@ function validateServer(Server $server, bool $throwError = false)
|
||||
$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,
|
||||
@@ -203,6 +209,11 @@ function validateServer(Server $server, bool $throwError = false)
|
||||
$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,
|
||||
@@ -213,7 +224,9 @@ function validateServer(Server $server, bool $throwError = false)
|
||||
$server->settings->is_usable = false;
|
||||
throw $e;
|
||||
} finally {
|
||||
if (data_get($server, 'settings')) $server->settings->save();
|
||||
if (data_get($server, 'settings')) {
|
||||
$server->settings->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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.70',
|
||||
// When left empty or `null` the Laravel environment will be used
|
||||
'environment' => config('app.env'),
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<?php
|
||||
|
||||
return '4.0.0-beta.67';
|
||||
return '4.0.0-beta.70';
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
};
|
||||
31
database/migrations/2023_09_23_111819_add_server_emails.php
Normal file
31
database/migrations/2023_09_23_111819_add_server_emails.php
Normal 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);
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)') }}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
6
resources/views/emails/server-revived.blade.php
Normal file
6
resources/views/emails/server-revived.blade.php
Normal 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>
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
</x-forms.button>
|
||||
@endif
|
||||
@if (isEmailEnabled($team) &&
|
||||
auth()->user()->isAdminFromSession())
|
||||
auth()->user()->isAdminFromSession() &&
|
||||
isTestEmailEnabled($team))
|
||||
<x-forms.button onclick="sendTestEmail.showModal()"
|
||||
class="text-white normal-case btn btn-xs no-animation btn-primary">
|
||||
Send Test Email
|
||||
@@ -31,15 +32,22 @@
|
||||
|
||||
</div>
|
||||
</form>
|
||||
@if ($this->sharedEmailEnabled)
|
||||
<div class="w-64 pb-4">
|
||||
<x-forms.checkbox instantSave="instantSaveInstance" id="team.use_instance_email_settings"
|
||||
label="Use hosted email service" />
|
||||
</div>
|
||||
@if (isCloud())
|
||||
@if ($this->sharedEmailEnabled)
|
||||
<div class="w-64 pb-4">
|
||||
<x-forms.checkbox instantSave="instantSaveInstance" id="team.use_instance_email_settings"
|
||||
label="Use Hosted Email Service" />
|
||||
</div>
|
||||
@else
|
||||
<div class="pb-4 w-96">
|
||||
<x-forms.checkbox disabled id="team.use_instance_email_settings"
|
||||
label="Use Hosted Email Service (Pro+ subscription required)" />
|
||||
</div>
|
||||
@endif
|
||||
@else
|
||||
<div class="pb-4 w-96">
|
||||
<x-forms.checkbox disabled id="team.use_instance_email_settings"
|
||||
label="Use hosted email service (Pro+ subscription required)" />
|
||||
<x-forms.checkbox instantSave="instantSaveInstance" id="team.use_instance_email_settings"
|
||||
label="Use system wide (transactional) email settings" />
|
||||
</div>
|
||||
@endif
|
||||
@if (!$team->use_instance_email_settings)
|
||||
|
||||
@@ -19,38 +19,50 @@
|
||||
</x-forms.button>
|
||||
</div>
|
||||
@if (!$application->dockerfile)
|
||||
<div class="flex items-end gap-2">
|
||||
<x-forms.select id="application.build_pack" label="Build Pack" required>
|
||||
<option value="nixpacks">Nixpacks</option>
|
||||
<option value="dockerfile">Dockerfile</option>
|
||||
</x-forms.select>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex gap-2">
|
||||
<x-forms.select id="application.build_pack" label="Build Pack" required>
|
||||
<option value="nixpacks">Nixpacks</option>
|
||||
<option value="dockerfile">Dockerfile</option>
|
||||
</x-forms.select>
|
||||
@if ($application->settings->is_static)
|
||||
<x-forms.select id="application.static_image" label="Static Image" required>
|
||||
<option value="nginx:alpine">nginx:alpine</option>
|
||||
<option disabled value="apache:alpine">apache:alpine</option>
|
||||
</x-forms.select>
|
||||
@endif
|
||||
</div>
|
||||
@if ($application->could_set_build_commands())
|
||||
<div class="w-64">
|
||||
<x-forms.checkbox instantSave id="is_static" label="Is it a static site?"
|
||||
helper="If your application is a static site or the final build assets should be served as a static site, enable this." />
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
@if ($application->settings->is_static)
|
||||
<x-forms.select id="application.static_image" label="Static Image" required>
|
||||
<option value="nginx:alpine">nginx:alpine</option>
|
||||
<option disabled value="apache:alpine">apache:alpine</option>
|
||||
</x-forms.select>
|
||||
@endif
|
||||
|
||||
<h3>Build</h3>
|
||||
@if ($application->could_set_build_commands())
|
||||
<h3>Build</h3>
|
||||
<div class="flex flex-col gap-2 xl:flex-row">
|
||||
<x-forms.input placeholder="pnpm install" id="application.install_command"
|
||||
label="Install Command" />
|
||||
<x-forms.input placeholder="pnpm build" id="application.build_command" label="Build Command" />
|
||||
<x-forms.input placeholder="pnpm start" id="application.start_command" label="Start Command" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 xl:flex-row">
|
||||
<x-forms.input placeholder="/" id="application.base_directory" label="Base Directory"
|
||||
helper="Directory to use as root. Useful for monorepos." />
|
||||
@endif
|
||||
|
||||
<div class="flex flex-col gap-2 xl:flex-row">
|
||||
<x-forms.input placeholder="/" id="application.base_directory" label="Base Directory"
|
||||
helper="Directory to use as root. Useful for monorepos." />
|
||||
@if ($application->could_set_build_commands())
|
||||
@if ($application->settings->is_static)
|
||||
<x-forms.input placeholder="/dist" id="application.publish_directory" label="Publish Directory"
|
||||
required />
|
||||
@else
|
||||
<x-forms.input placeholder="/" id="application.publish_directory" label="Publish Directory" />
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
@if ($application->dockerfile)
|
||||
<x-forms.textarea label="Dockerfile" id="application.dockerfile" rows="6"> </x-forms.textarea>
|
||||
@endif
|
||||
@@ -69,10 +81,7 @@
|
||||
</div>
|
||||
<h3>Advanced</h3>
|
||||
<div class="flex flex-col">
|
||||
@if ($application->could_set_build_commands())
|
||||
<x-forms.checkbox instantSave id="is_static" label="Is it a static site?"
|
||||
helper="If your application is a static site or the final build assets should be served as a static site, enable this." />
|
||||
@endif
|
||||
|
||||
<x-forms.checkbox
|
||||
helper="Your application will be available only on https if your domain starts with https://..."
|
||||
instantSave id="is_force_https_enabled" label="Force Https" />
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<x-external-link />
|
||||
</x-forms.button>
|
||||
</a>
|
||||
@if (!$application->source->is_public)
|
||||
@if (data_get($application, 'source.is_public') === false)
|
||||
<a target="_blank" class="hover:no-underline" href="{{ get_installation_path($application->source) }}">
|
||||
<x-forms.button>
|
||||
Open Git App
|
||||
|
||||
@@ -22,8 +22,13 @@
|
||||
<h3 class="pt-8 pb-2">Details</h3>
|
||||
<div class="flex flex-col gap-2 pb-6">
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input disabled id="git_branch" label="Selected branch"
|
||||
helper="You can select other branches after configuration is done." />
|
||||
@if ($git_source === 'other')
|
||||
<x-forms.input id="git_branch" label="Selected branch"
|
||||
helper="You can select other branches after configuration is done." />
|
||||
@else
|
||||
<x-forms.input disabled id="git_branch" label="Selected branch"
|
||||
helper="You can select other branches after configuration is done." />
|
||||
@endif
|
||||
@if ($is_static)
|
||||
<x-forms.input id="publish_directory" label="Publish Directory"
|
||||
helper="If there is a build process involved (like Svelte, React, Next, etc..), please specify the output directory for the build assets." />
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<div class="box group" wire:click="setType('private-gh-app')">
|
||||
<div class="flex flex-col mx-6">
|
||||
<div class="font-bold text-white group-hover:text-white">
|
||||
Private Repository
|
||||
Private Repository (with GitHub App)
|
||||
</div>
|
||||
<div class="text-xs group-hover:text-white">
|
||||
You can deploy public & private repositories through your GitHub Apps.
|
||||
@@ -36,7 +36,7 @@
|
||||
Private Repository (with deploy key)
|
||||
</div>
|
||||
<div class="text-xs group-hover:text-white">
|
||||
You can deploy public & private repositories with a simple deploy key.
|
||||
You can deploy public & private repositories with a simple deploy key (SSH key).
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -130,7 +130,7 @@
|
||||
<li class="step step-secondary">Select a Server</li>
|
||||
<li class="step">Select a Destination</li>
|
||||
</ul>
|
||||
<div class="flex flex-col justify-center gap-2 text-left xl:flex-row">
|
||||
<div class="flex flex-col justify-center gap-2 text-left xl:flex-row xl:flex-wrap">
|
||||
@forelse($servers as $server)
|
||||
<div class="box group" wire:click="setServer({{ $server }})">
|
||||
<div class="flex flex-col mx-6">
|
||||
@@ -158,7 +158,7 @@
|
||||
<li class="step step-secondary">Select a Server</li>
|
||||
<li class="step step-secondary">Select a Destination</li>
|
||||
</ul>
|
||||
<div class="flex flex-col justify-center gap-2 text-left xl:flex-row">
|
||||
<div class="flex flex-col justify-center gap-2 text-left xl:flex-row xl:flex-wrap">
|
||||
@foreach ($standaloneDockers as $standaloneDocker)
|
||||
<div class="box group" wire:click="setDestination('{{ $standaloneDocker->uuid }}')">
|
||||
<div class="flex flex-col mx-6">
|
||||
|
||||
9
resources/views/livewire/server/create.blade.php
Normal file
9
resources/views/livewire/server/create.blade.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<div>
|
||||
@if ($private_keys->count() === 0)
|
||||
<h1>Create Private Key</h1>
|
||||
<div class="subtitle">You need to create a private key before you can create a server.</div>
|
||||
<livewire:private-key.create from="server" />
|
||||
@else
|
||||
<livewire:server.new.by-ip :private_keys="$private_keys" :limit_reached="$limit_reached" />
|
||||
@endif
|
||||
</div>
|
||||
@@ -0,0 +1,4 @@
|
||||
<div>
|
||||
<x-server.navbar :server="$server" :parameters="$parameters" />
|
||||
<livewire:destination.show :server="$server" />
|
||||
</div>
|
||||
@@ -1,4 +1,4 @@
|
||||
<div>
|
||||
<div x-init="$wire.validateServer(false)">
|
||||
<x-modal yesOrNo modalId="deleteServer" modalTitle="Delete Server">
|
||||
<x-slot:modalBody>
|
||||
<p>This server will be deleted. It is not reversible. <br>Please think again..</p>
|
||||
@@ -25,6 +25,11 @@
|
||||
@else
|
||||
Server is reachable and validated.
|
||||
@endif
|
||||
@if ((!$server->settings->is_reachable || !$server->settings->is_usable) && $server->id !== 0)
|
||||
<x-forms.button class="mt-8 mb-4 font-bold box-without-bg bg-coollabs hover:bg-coollabs-100" wire:click.prevent='validateServer' isHighlighted>
|
||||
Validate Server & Install Docker Engine
|
||||
</x-forms.button>
|
||||
@endif
|
||||
<div class="flex flex-col gap-2 pt-4">
|
||||
<div class="flex flex-col w-full gap-2 lg:flex-row">
|
||||
<x-forms.input id="server.name" label="Name" required />
|
||||
@@ -42,27 +47,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-64">
|
||||
<x-forms.checkbox instantSave helper="If you are using Cloudflare Tunnels, enable this. It will proxy all ssh requests to your server through Cloudflare.<span class='text-warning'>Coolify does not install/setup Cloudflare (cloudflared) on your server.</span>"
|
||||
<x-forms.checkbox instantSave
|
||||
helper="If you are using Cloudflare Tunnels, enable this. It will proxy all ssh requests to your server through Cloudflare.<span class='text-warning'>Coolify does not install/setup Cloudflare (cloudflared) on your server.</span>"
|
||||
id="server.settings.is_cloudflare_tunnel" label="Cloudflare Tunnel" />
|
||||
</div>
|
||||
</div>
|
||||
@if (!$server->settings->is_reachable)
|
||||
<x-forms.button class="mt-8 mb-4 box" wire:click.prevent='validateServer'>
|
||||
Validate Server
|
||||
</x-forms.button>
|
||||
@endif
|
||||
@if ($server->settings->is_reachable && !$server->settings->is_usable && $server->id !== 0)
|
||||
@if ($dockerInstallationStarted)
|
||||
<x-forms.button class="mt-8 mb-4 box" wire:click.prevent='validateServer'>
|
||||
Validate Server
|
||||
</x-forms.button>
|
||||
@else
|
||||
<x-forms.button class="mt-8 mb-4 box" onclick="installDocker.showModal()"
|
||||
wire:click.prevent='installDocker' isHighlighted>
|
||||
Install Docker Engine 24.0
|
||||
</x-forms.button>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
@if ($server->isFunctional())
|
||||
<h3 class="py-4">Settings</h3>
|
||||
<x-forms.input id="cleanup_after_percentage" label="Disk Cleanup threshold (%)" required
|
||||
@@ -80,4 +70,11 @@
|
||||
Delete
|
||||
</x-forms.button>
|
||||
@endif
|
||||
|
||||
<script>
|
||||
Livewire.on('installDocker', () => {
|
||||
console.log('asd');
|
||||
installDocker.showModal();
|
||||
})
|
||||
</script>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<x-layout>
|
||||
<x-server.navbar :server="$server" />
|
||||
<div>
|
||||
<x-server.navbar :server="$server" :parameters="$parameters" />
|
||||
<livewire:server.show-private-key :server="$server" :privateKeys="$privateKeys" />
|
||||
</x-layout>
|
||||
</div>
|
||||
4
resources/views/livewire/server/proxy/show.blade.php
Normal file
4
resources/views/livewire/server/proxy/show.blade.php
Normal file
@@ -0,0 +1,4 @@
|
||||
<div>
|
||||
<x-server.navbar :server="$server" :parameters="$parameters" />
|
||||
<livewire:server.proxy :server="$server" />
|
||||
</div>
|
||||
@@ -1,7 +1,7 @@
|
||||
<div>
|
||||
<x-modal noSubmit modalId="installDocker">
|
||||
<x-modal modalId="installDocker">
|
||||
<x-slot:modalBody>
|
||||
<livewire:activity-monitor header="Installation Logs" />
|
||||
<livewire:activity-monitor header="Docker Installation Logs" />
|
||||
</x-slot:modalBody>
|
||||
<x-slot:modalSubmit>
|
||||
<x-forms.button onclick="installDocker.close()" type="submit">
|
||||
@@ -9,6 +9,6 @@
|
||||
</x-forms.button>
|
||||
</x-slot:modalSubmit>
|
||||
</x-modal>
|
||||
<x-server.navbar :server="$server" />
|
||||
<x-server.navbar :server="$server" :parameters="$parameters" />
|
||||
<livewire:server.form :server="$server" />
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
<x-forms.button type="submit">
|
||||
Save
|
||||
</x-forms.button>
|
||||
@if (isEmailEnabled($settings))
|
||||
@if (isEmailEnabled($settings) &&
|
||||
auth()->user()->isAdminFromSession() &&
|
||||
isTestEmailEnabled($settings))
|
||||
<x-forms.button onclick="sendTestEmail.showModal()"
|
||||
class="text-white normal-case btn btn-xs no-animation btn-primary">
|
||||
Send Test Email
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<x-layout>
|
||||
|
||||
</x-layout>
|
||||
@@ -1,4 +0,0 @@
|
||||
<x-layout>
|
||||
<x-server.navbar :server="$server" />
|
||||
<livewire:destination.show :server="$server" />
|
||||
</x-layout>
|
||||
@@ -1,4 +0,0 @@
|
||||
<x-layout>
|
||||
<x-server.navbar :server="$server" />
|
||||
<livewire:server.proxy :server="$server" />
|
||||
</x-layout>
|
||||
@@ -1,4 +0,0 @@
|
||||
<x-layout>
|
||||
<x-server.navbar :server="$server" />
|
||||
<livewire:server.form :server="$server" />
|
||||
</x-layout>
|
||||
@@ -13,6 +13,10 @@ use App\Http\Livewire\Dev\Compose as Compose;
|
||||
use App\Http\Livewire\Dashboard;
|
||||
use App\Http\Livewire\Project\Shared\Logs;
|
||||
use App\Http\Livewire\Server\All;
|
||||
use App\Http\Livewire\Server\Create;
|
||||
use App\Http\Livewire\Server\Destination\Show as DestinationShow;
|
||||
use App\Http\Livewire\Server\PrivateKey\Show as PrivateKeyShow;
|
||||
use App\Http\Livewire\Server\Proxy\Show as ProxyShow;
|
||||
use App\Http\Livewire\Server\Show;
|
||||
use App\Http\Livewire\Waitlist\Index as WaitlistIndex;
|
||||
use App\Models\GithubApp;
|
||||
@@ -102,18 +106,11 @@ Route::middleware(['auth'])->group(function () {
|
||||
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::get('/servers', All::class)->name('server.all');
|
||||
Route::get('/server/new', [ServerController::class, 'new_server'])->name('server.create');
|
||||
Route::get('/server/new', Create::class)->name('server.create');
|
||||
Route::get('/server/{server_uuid}', Show::class)->name('server.show');
|
||||
Route::get('/server/{server_uuid}/proxy', fn () => view('server.proxy', [
|
||||
'server' => Server::ownedByCurrentTeam(['name', 'proxy'])->whereUuid(request()->server_uuid)->firstOrFail(),
|
||||
]))->name('server.proxy');
|
||||
Route::get('/server/{server_uuid}/private-key', fn () => view('server.private-key', [
|
||||
'server' => Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->firstOrFail(),
|
||||
'privateKeys' => PrivateKey::ownedByCurrentTeam()->get()->where('is_git_related', false),
|
||||
]))->name('server.private-key');
|
||||
Route::get('/server/{server_uuid}/destinations', fn () => view('server.destinations', [
|
||||
'server' => Server::ownedByCurrentTeam(['name', 'proxy'])->whereUuid(request()->server_uuid)->firstOrFail()
|
||||
]))->name('server.destinations');
|
||||
Route::get('/server/{server_uuid}/proxy', ProxyShow::class)->name('server.proxy');
|
||||
Route::get('/server/{server_uuid}/private-key', PrivateKeyShow::class)->name('server.private-key');
|
||||
Route::get('/server/{server_uuid}/destinations', DestinationShow::class)->name('server.destinations');
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
},
|
||||
"fider": {
|
||||
"documentation": "https://fider.io/docs",
|
||||
"slogan": "A platform to collect and oragnize customer feedback.",
|
||||
"slogan": "A platform to collect and organize customer feedback.",
|
||||
"compose": "c2VydmljZXM6CiAgZmlkZXI6CiAgICBpbWFnZTogZ2V0ZmlkZXIvZmlkZXI6c3RhYmxlCiAgICBlbnZpcm9ubWVudDoKICAgICAgQkFTRV9VUkw6ICRTRVJWSUNFX0ZRRE5fRklERVIKICAgICAgREFUQUJBU0VfVVJMOiBwb3N0Z3JlczovLyRTRVJWSUNFX1VTRVJfTVlTUUw6JFNFUlZJQ0VfUEFTU1dPUkRfTVlTUUxAZGF0YWJhc2U6NTQzMi9maWRlcj9zc2xtb2RlPWRpc2FibGUKICAgICAgSldUX1NFQ1JFVDogJFNFUlZJQ0VfUEFTU1dPUkRfNjRfRklERVIKICAgICAgRU1BSUxfTk9SRVBMWTogJHtFTUFJTF9OT1JFUExZOi1ub3JlcGx5QGV4YW1wbGUuY29tfQogICAgICBFTUFJTF9NQUlMR1VOX0FQSTogJEVNQUlMX01BSUxHVU5fQVBJCiAgICAgIEVNQUlMX01BSUxHVU5fRE9NQUlOOiAkRU1BSUxfTUFJTEdVTl9ET01BSU4KICAgICAgRU1BSUxfTUFJTEdVTl9SRUdJT046ICRFTUFJTF9NQUlMR1VOX1JFR0lPTgogICAgICBFTUFJTF9TTVRQX0hPU1Q6ICR7RU1BSUxfU01UUF9IT1NUOi1zbXRwLm1haWxndW4uY29tfQogICAgICBFTUFJTF9TTVRQX1BPUlQ6ICR7RU1BSUxfU01UUF9QT1JUOi01ODd9CiAgICAgIEVNQUlMX1NNVFBfVVNFUk5BTUU6ICR7RU1BSUxfU01UUF9VU0VSTkFNRTotcG9zdG1hc3RlckBtYWlsZ3VuLmNvbX0KICAgICAgRU1BSUxfU01UUF9QQVNTV09SRDogJEVNQUlMX1NNVFBfUEFTU1dPUkQKICAgICAgRU1BSUxfU01UUF9FTkFCTEVfU1RBUlRUTFM6ICRFTUFJTF9TTVRQX0VOQUJMRV9TVEFSVFRMUwogICAgICBFTUFJTF9BV1NTRVNfUkVHSU9OOiAkRU1BSUxfQVdTU0VTX1JFR0lPTgogICAgICBFTUFJTF9BV1NTRVNfQUNDRVNTX0tFWV9JRDogJEVNQUlMX0FXU1NFU19BQ0NFU1NfS0VZX0lECiAgICAgIEVNQUlMX0FXU1NFU19TRUNSRVRfQUNDRVNTX0tFWTogJEVNQUlMX0FXU1NFU19TRUNSRVRfQUNDRVNTX0tFWQogIGRhdGFiYXNlOgogICAgaW1hZ2U6IHBvc3RncmVzOjEyCiAgICB2b2x1bWVzOgogICAgICAtIHBnX2RhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhCiAgICBlbnZpcm9ubWVudDoKICAgICAgUE9TVEdSRVNfVVNFUjogJFNFUlZJQ0VfVVNFUl9NWVNRTAogICAgICBQT1NUR1JFU19QQVNTV09SRDogJFNFUlZJQ0VfUEFTU1dPUkRfTVlTUUwKICAgICAgUE9TVEdSRVNfREI6ICR7UE9TVEdSRVNfREI6LWZpZGVyfQo="
|
||||
},
|
||||
"ghost": {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"version": "3.12.36"
|
||||
},
|
||||
"v4": {
|
||||
"version": "4.0.0-beta.67"
|
||||
"version": "4.0.0-beta.70"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user