Compare commits

...

94 Commits

Author SHA1 Message Date
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
Andras Bacsai
1bfedf69f2 Merge pull request #1288 from coollabsio/next
v4.0.0-beta.67
2023-10-06 10:24:13 +02:00
Andras Bacsai
208fe7d87b revert 2023-10-06 10:11:04 +02:00
Andras Bacsai
a6d58b5d72 fix: decrease max horizon processes to get lower memory usage 2023-10-06 10:10:47 +02:00
Andras Bacsai
277b4276e6 small fixes 2023-10-06 10:08:50 +02:00
Andras Bacsai
f6adc9285a fix: services - do not remove unnecessary things for now 2023-10-06 10:08:46 +02:00
Andras Bacsai
f03bbe0e95 feat: basedir / monorepo initial support 2023-10-06 10:07:25 +02:00
Andras Bacsai
535375193c cloud: add shared email option to everyone 2023-10-05 14:46:39 +02:00
Andras Bacsai
d79c063fd6 ui: notifications 2023-10-05 14:44:17 +02:00
Andras Bacsai
35f45492e3 fix: email notifications subscription fixed 2023-10-05 14:37:16 +02:00
Andras Bacsai
ab8a7893d9 composer update 2023-10-05 13:57:59 +02:00
Andras Bacsai
76b8d048d4 fix: PR deployments use the first fqdn as base 2023-10-05 13:35:16 +02:00
Andras Bacsai
0e583334e7 version++ 2023-10-05 11:39:02 +02:00
Andras Bacsai
0ad8ca224f Merge pull request #1287 from coollabsio/next
v4.0.0-beta.66
2023-10-05 11:28:50 +02:00
Andras Bacsai
050e56f69a fix: traefik labelling in case of several http and https domain added 2023-10-05 11:27:50 +02:00
Andras Bacsai
6099ac11d9 version++ 2023-10-05 11:26:45 +02:00
Andras Bacsai
adac728a60 Merge pull request #1286 from coollabsio/next
v4.0.0-beta.65
2023-10-05 11:00:56 +02:00
Andras Bacsai
e2e64e36a0 feat: disable service, required version 2023-10-05 10:58:08 +02:00
Andras Bacsai
762af66cbf add deprecated templates in dev 2023-10-05 10:48:26 +02:00
Andras Bacsai
b08f525bd4 fix: move dev data to volumes to prevent permission issues 2023-10-05 08:54:03 +02:00
Andras Bacsai
5ae16b195c remove ray 2023-10-05 08:50:01 +02:00
Andras Bacsai
91db1953ff fix: delete event to deleting 2023-10-05 08:46:26 +02:00
Andras Bacsai
1c8f92d3b7 fix: remove SERVICE_ from deployable compose 2023-10-05 08:40:17 +02:00
Andras Bacsai
4075572dbc fix: visible version number 2023-10-05 08:32:20 +02:00
Andras Bacsai
2971e360d7 revert last commits 2023-10-04 18:06:25 +02:00
Andras Bacsai
32bb2780f2 rename composes 2023-10-04 15:43:29 +02:00
Andras Bacsai
af69575b29 fix: remove SERVICE_ stuff from raw compose
feat: multiport predefined compose
fix: use predefined name as prefix for fqdn
2023-10-04 15:40:08 +02:00
Andras Bacsai
d4a7d0d25f fix: traefik labels for multiport deployments 2023-10-04 15:39:23 +02:00
Andras Bacsai
45f9def0f6 fix: dev compose files 2023-10-04 14:40:33 +02:00
Andras Bacsai
5a90eed7ef fix: compose parser updated 2023-10-04 14:40:26 +02:00
Andras Bacsai
38e1f17edf feat: multiselect removable resources 2023-10-04 14:40:04 +02:00
Andras Bacsai
1651845e20 version++ 2023-10-04 11:54:56 +02:00
Andras Bacsai
38e96548b5 Merge pull request #1285 from coollabsio/next
v4.0.0-beta.64
2023-10-04 11:33:21 +02:00
Andras Bacsai
47e4126dca fix: compose magic 2023-10-04 11:32:27 +02:00
Andras Bacsai
e0b175ab07 version++ 2023-10-04 11:05:40 +02:00
Andras Bacsai
93ec785f4f Merge pull request #1284 from coollabsio/next
v4.0.0-beta.63
2023-10-04 11:01:05 +02:00
Andras Bacsai
e849addab8 dev: switch back to /data (volume errors) 2023-10-04 10:57:44 +02:00
Andras Bacsai
a5e6975dac fix: ui 2023-10-04 10:34:44 +02:00
Andras Bacsai
4ac8e1cc67 fix: services file/dir read from server
ui: fix storages layout
2023-10-04 09:58:39 +02:00
Andras Bacsai
1a5e3a7836 sync volume script 2023-10-03 15:48:07 +02:00
Andras Bacsai
4498d1ed4b fix: service logs visible if the whole service stack is not running 2023-10-03 15:08:23 +02:00
170 changed files with 3082 additions and 1024 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/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

@@ -8,6 +8,7 @@ use App\Models\StandalonePostgresql;
use Illuminate\Console\Command;
use function Laravel\Prompts\confirm;
use function Laravel\Prompts\multiselect;
use function Laravel\Prompts\select;
class ResourcesDelete extends Command
@@ -50,16 +51,18 @@ class ResourcesDelete extends Command
$this->error('There are no applications to delete.');
return;
}
$application = select(
$applicationsToDelete = multiselect(
'What application do you want to delete?',
$applications->pluck('name')->toArray(),
);
$application = $applications->where('name', $application)->first();
$confirmed = confirm("Are you sure you want to delete {$application->name}?");
$confirmed = confirm("Are you sure you want to delete all selected resources?");
if (!$confirmed) {
return;
}
$application->delete();
foreach ($applicationsToDelete as $application) {
$toDelete = $applications->where('name', $application)->first();
$toDelete->delete();
}
}
private function deleteDatabase()
{
@@ -68,16 +71,19 @@ class ResourcesDelete extends Command
$this->error('There are no databases to delete.');
return;
}
$database = select(
$databasesToDelete = multiselect(
'What database do you want to delete?',
$databases->pluck('name')->toArray(),
);
$database = $databases->where('name', $database)->first();
$confirmed = confirm("Are you sure you want to delete {$database->name}?");
$confirmed = confirm("Are you sure you want to delete all selected resources?");
if (!$confirmed) {
return;
}
$database->delete();
foreach ($databasesToDelete as $database) {
$toDelete = $databases->where('name', $database)->first();
$toDelete->delete();
}
}
private function deleteService()
{
@@ -86,15 +92,17 @@ class ResourcesDelete extends Command
$this->error('There are no services to delete.');
return;
}
$service = select(
$servicesToDelete = multiselect(
'What service do you want to delete?',
$services->pluck('name')->toArray(),
);
$service = $services->where('name', $service)->first();
$confirmed = confirm("Are you sure you want to delete {$service->name}?");
$confirmed = confirm("Are you sure you want to delete all selected resources?");
if (!$confirmed) {
return;
}
$service->delete();
foreach ($servicesToDelete as $service) {
$toDelete = $services->where('name', $service)->first();
$toDelete->delete();
}
}
}

View File

@@ -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();
}

View File

@@ -69,7 +69,6 @@ class ProjectController extends Controller
if ($type->startsWith('one-click-service-') && !is_null( (int)$server_id)) {
$oneClickServiceName = $type->after('one-click-service-')->value();
$oneClickService = data_get($services, "$oneClickServiceName.compose");
ray($oneClickServiceName);
$oneClickDotEnvs = data_get($services, "$oneClickServiceName.envs", null);
if ($oneClickDotEnvs) {
$oneClickDotEnvs = Str::of(base64_decode($oneClickDotEnvs))->split('/\r\n|\r|\n/');
@@ -93,6 +92,7 @@ class ProjectController extends Controller
$generatedValue = $value;
if ($value->contains('SERVICE_')) {
$command = $value->after('SERVICE_')->beforeLast('_');
// TODO: make it shared with Service.php
switch ($command->value()) {
case 'PASSWORD':
$generatedValue = Str::password(symbols: false);

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

@@ -76,7 +76,6 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
Team::find(currentTeam()->id)->update([
'show_boarding' => false
]);
ray(currentTeam());
refreshSession();
return redirect()->route('dashboard');
}
@@ -221,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,21 @@ use Livewire\Component;
class Dashboard extends Component
{
public int $projects = 0;
public int $servers = 0;
public $projects = [];
public $servers = [];
public int $s3s = 0;
public int $resources = 0;
public function mount()
{
$this->servers = Server::ownedByCurrentTeam()->get()->count();
$this->servers = Server::ownedByCurrentTeam()->get();
$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->projects = $projects;
}
// public function getIptables()
// {

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Http\Livewire\Dev;
use Livewire\Component;
class Compose extends Component
{
public string $compose = '';
public string $base64 = '';
public $services;
public function mount() {
$this->services = getServiceTemplates();
}
public function setService(string $selected) {
$this->base64 = data_get($this->services, $selected . '.compose');
if ($this->base64) {
$this->compose = base64_decode($this->base64);
}
}
public function updatedCompose($value) {
$this->base64 = base64_encode($value);
}
public function render()
{
return view('livewire.dev.compose');
}
}

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

@@ -29,7 +29,8 @@ class Form extends Component
public function generate_real_url()
{
if (data_get($this->application, 'fqdn')) {
$url = Url::fromString($this->application->fqdn);
$firstFqdn = Str::of($this->application->fqdn)->before(',');
$url = Url::fromString($firstFqdn);
$host = $url->getHost();
$this->preview_url_template = Str::of($this->application->preview_url_template)->replace('{{domain}}', $host);
}

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

@@ -50,8 +50,9 @@ class General extends Component
$this->getDbUrl();
}
public function getDbUrl() {
if ($this->database->is_public) {
$this->db_url = "postgres://{$this->database->postgres_user}:{$this->database->postgres_password}@{$this->database->destination->server->ip}:{$this->database->public_port}/{$this->database->postgres_db}";
$this->db_url = "postgres://{$this->database->postgres_user}:{$this->database->postgres_password}@{$this->database->destination->server->getIp}:{$this->database->public_port}/{$this->database->postgres_db}";
} else {
$this->db_url = "postgres://{$this->database->postgres_user}:{$this->database->postgres_password}@{$this->database->uuid}:5432/{$this->database->postgres_db}";
}

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

@@ -29,7 +29,8 @@ class Index extends Component
$this->parameters = get_route_parameters();
$this->query = request()->query();
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
$this->refreshStack();
$this->applications = $this->service->applications->sort();
$this->databases = $this->service->databases->sort();
}
public function saveCompose($raw)
{

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Http\Livewire\Project\Service;
use App\Models\LocalPersistentVolume;
use Livewire\Component;
class Storage extends Component
{
protected $listeners = ['addNewVolume'];
public $resource;
public function render()
{
return view('livewire.project.service.storage');
}
public function addNewVolume($data)
{
try {
LocalPersistentVolume::create([
'name' => $data['name'],
'mount_path' => $data['mount_path'],
'host_path' => $data['host_path'],
'resource_id' => $this->resource->id,
'resource_type' => $this->resource->getMorphClass(),
]);
$this->resource->refresh();
$this->emit('success', 'Storage added successfully');
$this->emit('clearAddStorage');
$this->emit('refreshStorages');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

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

@@ -33,7 +33,7 @@ class Add extends Component
{
$this->validate();
$name = $this->uuid . '-' . $this->name;
$this->emitUp('submit', [
$this->emit('addNewVolume', [
'name' => $name,
'mount_path' => $this->mount_path,
'host_path' => $this->host_path,

View File

@@ -7,30 +7,11 @@ use Livewire\Component;
class All extends Component
{
public bool $isHeaderVisible = true;
public $resource;
protected $listeners = ['refreshStorages', 'submit'];
protected $listeners = ['refreshStorages'];
public function refreshStorages()
{
$this->resource->refresh();
}
public function submit($data)
{
try {
LocalPersistentVolume::create([
'name' => $data['name'],
'mount_path' => $data['mount_path'],
'host_path' => $data['host_path'],
'resource_id' => $this->resource->id,
'resource_type' => $this->resource->getMorphClass(),
]);
$this->resource->refresh();
$this->emit('success', 'Storage added successfully');
$this->emit('clearAddStorage');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

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

@@ -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 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');
}
}

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

@@ -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,16 +45,20 @@ 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;
private string $container_name;
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_workdir;
private string $build_image_name;
private string $production_image_name;
private bool $is_debug_enabled;
@@ -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,15 +86,19 @@ 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;
$this->workdir = "/artifacts/{$this->deployment_uuid}";
$this->basedir = "/artifacts/{$this->deployment_uuid}";
$this->workdir = "{$this->basedir}" . rtrim($this->application->base_directory, '/');
$this->configuration_dir = application_configuration_dir() . "/{$this->application->uuid}";
$this->build_workdir = "{$this->workdir}" . rtrim($this->application->base_directory, '/');
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
ray($this->basedir, $this->workdir);
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
savePrivateKeyToFs($this->server);
$this->saved_outputs = collect();
@@ -131,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();
@@ -169,6 +183,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
);
}
}
private function deploy_docker_compose()
{
$dockercompose_base64 = base64_encode($this->application->dockercompose);
@@ -233,7 +248,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
);
$this->build_image_name = Str::lower("{$this->application->git_repository}:build");
$this->production_image_name = Str::lower("{$this->application->uuid}:latest");
ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
// ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
$this->generate_compose_file();
$this->generate_build_env_variables();
$this->add_build_env_variables_to_dockerfile();
@@ -241,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(
@@ -250,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);
@@ -258,7 +317,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$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();
// ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
if (!$this->force_rebuild) {
$this->execute_remote_command([
@@ -307,7 +366,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->newVersionIsHealthy = true;
return;
}
ray('New container name: ', $this->container_name);
// ray('New container name: ', $this->container_name);
if ($this->container_name) {
$counter = 0;
$this->execute_remote_command(
@@ -335,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.'"
],
@@ -354,12 +410,13 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
{
$this->build_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}-build");
$this->production_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}");
ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
// ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
$this->execute_remote_command([
"echo 'Starting pull request (#{$this->pull_request_id}) deployment of {$this->application->git_repository}:{$this->application->git_branch}.'",
]);
$this->prepare_builder_image();
$this->clone_repository();
$this->set_base_dir();
$this->cleanup_git();
if ($this->application->build_pack === 'nixpacks') {
$this->generate_nixpacks_confs();
@@ -391,24 +448,31 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"hidden" => true,
],
[
"command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->workdir}")
"command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->basedir}")
],
);
}
private function set_base_dir()
{
$this->execute_remote_command(
[
"echo -n 'Setting base directory to {$this->workdir}.'"
],
);
}
private function clone_repository()
{
$this->execute_remote_command(
[
"echo -n 'Importing {$this->application->git_repository}:{$this->application->git_branch} (commit sha {$this->application->git_commit_sha}) to {$this->workdir}. '"
"echo -n 'Importing {$this->application->git_repository}:{$this->application->git_branch} (commit sha {$this->application->git_commit_sha}) to {$this->basedir}. '"
],
[
$this->importing_git_repository()
$this->importing_git_repository(), "hidden" => true
],
[
executeInDocker($this->deployment_uuid, "cd {$this->workdir} && git rev-parse HEAD"),
executeInDocker($this->deployment_uuid, "cd {$this->basedir} && git rev-parse HEAD"),
"hidden" => true,
"save" => "git_commit_sha"
],
@@ -432,23 +496,23 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->source->getMorphClass() == 'App\Models\GithubApp') {
if ($this->source->is_public) {
$git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$this->application->git_repository} {$this->workdir}";
$git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$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));
} else {
$github_access_token = generate_github_installation_token($this->source);
$commands->push(executeInDocker($this->deployment_uuid, "git clone -q -b {$this->application->git_branch} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->application->git_repository}.git {$this->workdir}"));
$commands->push(executeInDocker($this->deployment_uuid, "git clone -q -b {$this->application->git_branch} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->application->git_repository}.git {$this->basedir}"));
}
if ($this->pull_request_id !== 0) {
$commands->push(executeInDocker($this->deployment_uuid, "cd {$this->workdir} && git fetch origin pull/{$this->pull_request_id}/head:$pr_branch_name && git checkout $pr_branch_name"));
$commands->push(executeInDocker($this->deployment_uuid, "cd {$this->basedir} && git fetch origin pull/{$this->pull_request_id}/head:$pr_branch_name && git checkout $pr_branch_name"));
}
return $commands->implode(' && ');
}
}
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"),
@@ -458,18 +522,25 @@ 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)
{
if ($this->application->git_commit_sha !== 'HEAD') {
$git_clone_command = "{$git_clone_command} && cd {$this->workdir} && git -c advice.detachedHead=false checkout {$this->application->git_commit_sha} >/dev/null 2>&1";
$git_clone_command = "{$git_clone_command} && cd {$this->basedir} && git -c advice.detachedHead=false checkout {$this->application->git_commit_sha} >/dev/null 2>&1";
}
if ($this->application->settings->is_git_submodules_enabled) {
$git_clone_command = "{$git_clone_command} && cd {$this->workdir} && git submodule update --init --recursive";
$git_clone_command = "{$git_clone_command} && cd {$this->basedir} && git submodule update --init --recursive";
}
if ($this->application->settings->is_git_lfs_enabled) {
$git_clone_command = "{$git_clone_command} && cd {$this->workdir} && git lfs pull";
$git_clone_command = "{$git_clone_command} && cd {$this->basedir} && git lfs pull";
}
return $git_clone_command;
}
@@ -477,17 +548,24 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function cleanup_git()
{
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "rm -fr {$this->workdir}/.git")],
[executeInDocker($this->deployment_uuid, "rm -fr {$this->basedir}/.git")],
);
}
private function generate_nixpacks_confs()
{
$this->execute_remote_command(
[
"echo -n 'Generating nixpacks configuration.'",
]
);
$nixpacks_command = $this->nixpacks_build_cmd();
$this->execute_remote_command(
[
"echo -n Running: $nixpacks_command",
],
[$this->nixpacks_build_cmd()],
[executeInDocker($this->deployment_uuid, $nixpacks_command)],
[executeInDocker($this->deployment_uuid, "cp {$this->workdir}/.nixpacks/Dockerfile {$this->workdir}/Dockerfile")],
[executeInDocker($this->deployment_uuid, "rm -f {$this->workdir}/.nixpacks/Dockerfile")]
);
@@ -496,7 +574,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function nixpacks_build_cmd()
{
$this->generate_env_variables();
$nixpacks_command = "nixpacks build -o {$this->workdir} {$this->env_args} --no-error-without-start";
$nixpacks_command = "nixpacks build --no-cache -o {$this->workdir} {$this->env_args} --no-error-without-start";
if ($this->application->build_command) {
$nixpacks_command .= " --build-cmd \"{$this->application->build_command}\"";
}
@@ -507,7 +585,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$nixpacks_command .= " --install-cmd \"{$this->application->install_command}\"";
}
$nixpacks_command .= " {$this->workdir}";
return executeInDocker($this->deployment_uuid, $nixpacks_command);
return $nixpacks_command;
}
private function generate_env_variables()
@@ -586,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]);
@@ -628,14 +712,14 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function generate_environment_variables($ports)
{
$environment_variables = collect();
ray('Generate Environment Variables')->green();
// ray('Generate Environment Variables')->green();
if ($this->pull_request_id === 0) {
ray($this->application->runtime_environment_variables)->green();
// ray($this->application->runtime_environment_variables)->green();
foreach ($this->application->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->value");
}
} else {
ray($this->application->runtime_environment_variables_preview)->green();
// ray($this->application->runtime_environment_variables_preview)->green();
foreach ($this->application->runtime_environment_variables_preview as $env) {
$environment_variables->push("$env->key=$env->value");
}
@@ -649,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';
}
@@ -673,7 +757,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function build_image()
{
$this->execute_remote_command([
"echo -n 'Building docker image for your application.'",
"echo -n 'Building docker image for your application. To check the current progress, click on Show Debug Logs.'",
]);
if ($this->application->settings->is_static) {
@@ -742,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) {
@@ -44,6 +60,10 @@ class Application extends BaseModel
{
return $this->morphMany(LocalPersistentVolume::class, 'resource');
}
public function fileStorages()
{
return $this->morphMany(LocalFileVolume::class, 'resource');
}
public function type()
{
@@ -64,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;
}
);
@@ -76,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(
@@ -220,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');
}
@@ -235,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

@@ -31,23 +31,18 @@ class LocalFileVolume extends BaseModel
}
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
ray($isFile);
if ($isFile == 'OK' && $fileVolume->is_directory) {
throw new \Exception("File $path is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.");
} else if ($isDir == 'OK' && !$fileVolume->is_directory) {
throw new \Exception("File $path is a directory on the server, but you are trying to mark it as a file. Please delete the directory on the server or mark it as directory.");
}
if (($isFile == 'NOK' && !$fileVolume->is_directory) || $isFile == 'OK') {
$rootDir = Str::of($path)->dirname();
$commands->push("mkdir -p $rootDir > /dev/null 2>&1 || true");
$commands->push("touch $path > /dev/null 2>&1 || true");
if ($content) {
$content = base64_encode($content);
$commands->push("echo '$content' | base64 -d > $path");
}
} else if ($isDir == 'NOK' && $fileVolume->is_directory) {
if (!$fileVolume->is_directory && $isDir == 'NOK') {
$content = base64_encode($content);
$commands->push("echo '$content' | base64 -d > $path");
} else if ($isDir == 'NOK' && $fileVolume->is_directory) {
$commands->push("mkdir -p $path > /dev/null 2>&1 || true");
}
ray($commands->toArray());
return instant_remote_process($commands, $server);
}
}

View File

@@ -22,7 +22,7 @@ class Project extends BaseModel
'project_id' => $project->id,
]);
});
static::deleted(function ($project) {
static::deleting(function ($project) {
$project->environments()->delete();
$project->settings()->delete();
});

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) {
$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

@@ -5,8 +5,10 @@ namespace App\Models;
use App\Enums\ProxyStatus;
use App\Enums\ProxyTypes;
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
{
@@ -14,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,
@@ -120,7 +133,20 @@ class Server extends BaseModel
{
return $this->hasMany(Service::class);
}
public function getIp(): Attribute
{
return Attribute::make(
get: function () {
if (isDev()) {
return '127.0.0.1';
}
if ($this->ip === 'host.docker.internal') {
return base_ip();
}
return $this->ip;
}
);
}
public function previews()
{
return $this->destinations()->map(function ($standaloneDocker) {
@@ -185,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

@@ -16,7 +16,7 @@ class Service extends BaseModel
protected static function booted()
{
static::deleted(function ($service) {
static::deleting(function ($service) {
$storagesToDelete = collect([]);
foreach ($service->applications()->get() as $application) {
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server, false);
@@ -117,7 +117,7 @@ class Service extends BaseModel
public function parse(bool $isNew = false): Collection
{
// ray()->clearAll();
ray()->clearAll();
if ($this->docker_compose_raw) {
try {
$yaml = Yaml::parse($this->docker_compose_raw);
@@ -266,7 +266,7 @@ class Service extends BaseModel
// Collect/create/update volumes
if ($serviceVolumes->count() > 0) {
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($savedService, $topLevelVolumes, $isNew) {
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($savedService, $topLevelVolumes) {
$type = null;
$source = null;
$target = null;
@@ -384,9 +384,23 @@ class Service extends BaseModel
$value = Str::of($variable);
}
if ($key->startsWith('SERVICE_FQDN')) {
if (is_null(data_get($savedService, 'fqdn'))) {
$fqdn = generateFqdn($this->server, $containerName);
if (substr_count($key->value(), '_') === 2 && $key->contains("=")) {
if ($isNew) {
$name = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower();
$fqdn = generateFqdn($this->server, "{$name->value()}-{$this->uuid}");
if (substr_count($key->value(), '_') === 3) {
// SERVICE_FQDN_UMAMI_1000
$port = $key->afterLast('_');
} else {
// SERVICE_FQDN_UMAMI
$port = null;
}
if ($port) {
$fqdn = "$fqdn:$port";
}
if (substr_count($key->value(), '_') >= 2) {
if (is_null($value)) {
$value = Str::of('/');
}
$path = $value->value();
if ($generatedServiceFQDNS->count() > 0) {
$alreadyGenerated = $generatedServiceFQDNS->has($key->value());
@@ -400,11 +414,22 @@ class Service extends BaseModel
}
$fqdn = "$fqdn$path";
}
if (!$isDatabase) {
if ($savedService->fqdn) {
$fqdn = $savedService->fqdn . ',' . $fqdn;
} else {
$fqdn = $fqdn;
}
$savedService->fqdn = $fqdn;
$savedService->save();
}
}
// data_forget($service, "environment.$variableName");
// $yaml = data_forget($yaml, "services.$serviceName.environment.$variableName");
// if (count(data_get($yaml, 'services.' . $serviceName . '.environment')) === 0) {
// $yaml = data_forget($yaml, "services.$serviceName.environment");
// }
continue;
}
if ($value?->startsWith('$')) {
@@ -419,10 +444,17 @@ class Service extends BaseModel
$forService = $value->afterLast('_');
$generatedValue = null;
if ($command->value() === 'FQDN' || $command->value() === 'URL') {
$fqdn = generateFqdn($this->server, $containerName);
if (Str::lower($forService) === $serviceName) {
$fqdn = generateFqdn($this->server, $containerName);
} else {
$fqdn = generateFqdn($this->server, Str::lower($forService) . '-' . $this->uuid);
}
if ($foundEnv) {
$fqdn = data_get($foundEnv, 'value');
} else {
if ($command->value() === 'URL') {
$fqdn = Str::of($fqdn)->after('://')->value();
}
EnvironmentVariable::create([
'key' => $key,
'value' => $fqdn,
@@ -431,10 +463,11 @@ class Service extends BaseModel
'is_preview' => false,
]);
}
if (!$isDatabase) {
$savedService->fqdn = $fqdn;
$savedService->save();
if ($command->value() === 'FQDN') {
$savedService->fqdn = $fqdn;
$savedService->save();
}
}
} else {
switch ($command) {
@@ -507,25 +540,27 @@ class Service extends BaseModel
$serviceLabels = $serviceLabels->merge($defaultLabels);
if (!$isDatabase && $fqdns->count() > 0) {
if ($fqdns) {
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($fqdns, $containerName, true));
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($fqdns, true));
}
}
data_set($service, 'labels', $serviceLabels->toArray());
data_forget($service, 'is_database');
data_set($service, 'restart', RESTART_MODE);
data_set($service, 'container_name', $containerName);
data_forget($service, 'volumes.*.content');
data_forget($service, 'volumes.*.isDirectory');
// Remove unnecessary variables from service.environment
$withoutServiceEnvs = collect([]);
collect(data_get($service, 'environment'))->each(function ($value, $key) use ($withoutServiceEnvs) {
if (!Str::of($key)->startsWith('$SERVICE_')) {
$withoutServiceEnvs->put($key, $value);
}
});
data_set($service, 'environment', $withoutServiceEnvs->toArray());
// $withoutServiceEnvs = collect([]);
// collect(data_get($service, 'environment'))->each(function ($value, $key) use ($withoutServiceEnvs) {
// ray($key, $value);
// if (!Str::of($key)->startsWith('$SERVICE_') && !Str::of($value)->startsWith('SERVICE_')) {
// $k = Str::of($value)->before("=");
// $v = Str::of($value)->after("=");
// $withoutServiceEnvs->put($k->value(), $v->value());
// }
// });
// ray($withoutServiceEnvs);
// data_set($service, 'environment', $withoutServiceEnvs->toArray());
return $service;
});
$finalServices = [
@@ -535,7 +570,7 @@ class Service extends BaseModel
'networks' => $topLevelNetworks->toArray(),
];
$this->docker_compose_raw = Yaml::dump($yaml, 10, 2);
$this->docker_compose = Yaml::dump($finalServices, 10, 2);
$this->docker_compose = Yaml::dump($finalServices, 10, 2);
$this->save();
$this->saveComposeConfigs();
return collect([]);

View File

@@ -3,7 +3,6 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Symfony\Component\Yaml\Yaml;
class ServiceDatabase extends BaseModel
{

View File

@@ -28,7 +28,7 @@ class StandalonePostgresql extends BaseModel
'is_readonly' => true
]);
});
static::deleted(function ($database) {
static::deleting(function ($database) {
// Stop Container
instant_remote_process(
["docker rm -f {$database->uuid}"],
@@ -76,6 +76,11 @@ class StandalonePostgresql extends BaseModel
return $this->belongsTo(Environment::class);
}
public function fileStorages()
{
return $this->morphMany(LocalFileVolume::class, 'resource');
}
public function destination()
{
return $this->morphTo();

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,7 @@ 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 () {
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

@@ -15,7 +15,6 @@ class EmailChannel
try {
$this->bootConfigs($notifiable);
$recepients = $notifiable->getRecepients($notification);
ray($recepients);
if (count($recepients) === 0) {
throw new Exception('No email recipients found');
}

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

@@ -30,7 +30,7 @@ class BackupFailed extends Notification implements ShouldQueue
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 +41,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

@@ -1,12 +1,12 @@
<?php
use App\Enums\ProxyTypes;
use App\Models\Application;
use App\Models\ApplicationPreview;
use App\Models\Server;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Spatie\Url\Url;
use Visus\Cuid2\Cuid2;
function getCurrentApplicationContainerStatus(Server $server, int $id): Collection
{
@@ -147,20 +147,20 @@ function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'applica
}
return $labels;
}
function fqdnLabelsForTraefik(Collection $domains, $container_name, $is_force_https_enabled)
function fqdnLabelsForTraefik(Collection $domains, bool $is_force_https_enabled)
{
$labels = collect([]);
$labels->push('traefik.enable=true');
foreach ($domains as $domain) {
$uuid = (string)new Cuid2(7);
$url = Url::fromString($domain);
$host = $url->getHost();
$path = $url->getPath();
$schema = $url->getScheme();
$port = $url->getPort();
$slug = Str::slug($host . $path);
$http_label = "{$container_name}-{$slug}-http";
$https_label = "{$container_name}-{$slug}-https";
$http_label = "{$uuid}-http";
$https_label = "{$uuid}-https";
if ($schema === 'https') {
// Set labels for https
@@ -221,7 +221,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, $container_name, $application->settings->is_force_https_enabled));
$labels = $labels->merge(fqdnLabelsForTraefik($domains, $application->settings->is_force_https_enabled));
}
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

@@ -204,17 +204,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 +249,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

@@ -85,28 +85,39 @@ function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase $oneS
} else {
$fileLocation = $path;
}
ray($path,$fileLocation);
// Exists and is a file
$isFile = instant_remote_process(["test -f $fileLocation && echo OK || echo NOK"], $server);
// Exists and is a directory
$isDir = instant_remote_process(["test -d $fileLocation && echo OK || echo NOK"], $server);
if ($isFile === 'NOK' &&!$fileVolume->is_directory && $isInit) {
$fileVolume->saveStorageOnServer($oneService);
continue;
}
if ($isFile == 'OK' && !$fileVolume->is_directory) {
if ($isFile == 'OK') {
// If its a file & exists
$filesystemContent = instant_remote_process(["cat $fileLocation"], $server);
if (base64_encode($filesystemContent) != base64_encode($content)) {
$fileVolume->content = $filesystemContent;
$fileVolume->save();
}
} else {
if ($isDir == 'OK') {
$fileVolume->content = null;
$fileVolume->is_directory = true;
$fileVolume->save();
} else {
$fileVolume->content = null;
$fileVolume->is_directory = false;
$fileVolume->save();
}
$fileVolume->content = $filesystemContent;
$fileVolume->is_directory = false;
$fileVolume->save();
} else if ($isDir == 'OK') {
// If its a directory & exists
$fileVolume->content = null;
$fileVolume->is_directory = true;
$fileVolume->save();
} else if ($isFile == 'NOK' && $isDir == 'NOK' && !$fileVolume->is_directory && $isInit && $content) {
// Does not exists (no dir or file), not flagged as directory, is init, has content
$fileVolume->content = $content;
$fileVolume->is_directory = false;
$fileVolume->save();
$content = base64_encode($content);
$dir = Str::of($fileLocation)->dirname();
instant_remote_process([
"mkdir -p $dir",
"echo '$content' | base64 -d > $fileLocation"
], $server);
} else if ($isFile == 'NOK' && $isDir == 'NOK' && $fileVolume->is_directory && $isInit) {
$fileVolume->content = null;
$fileVolume->is_directory = true;
$fileVolume->save();
instant_remote_process(["mkdir -p $fileLocation"], $server);
}
}
} catch (\Throwable $e) {
@@ -126,13 +137,18 @@ function updateCompose($resource)
// Update FQDN
$variableName = "SERVICE_FQDN_" . Str::of($resource->name)->upper();
ray($variableName);
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
if ($generatedEnv) {
$generatedEnv->value = $resource->fqdn;
$generatedEnv->save();
}
$variableName = "SERVICE_URL_" . Str::of($resource->name)->upper();
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
if ($generatedEnv) {
$url = Str::of($resource->fqdn)->after('://');
$generatedEnv->value = $url;
$generatedEnv->save();
}
$dockerComposeRaw = Yaml::dump($dockerCompose, 10, 2);
$resource->service->docker_compose_raw = $dockerComposeRaw;

View File

@@ -14,6 +14,7 @@ use Illuminate\Mail\Message;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Route;
@@ -309,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());
}
@@ -342,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');
@@ -352,13 +363,14 @@ function setNotificationChannels($notifiable, $event)
$isEmailEnabled = isEmailEnabled($notifiable);
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
$isSubscribedToEmailEvent = data_get($notifiable, "smtp_notifications_$event");
$isSubscribedToDiscordEvent = data_get($notifiable, "discord_notifications_$event");
$isSubscribedToTelegramEvent = data_get($notifiable, "telegram_notifications_$event");
if ($isDiscordEnabled && $isSubscribedToDiscordEvent) {
$channels[] = DiscordChannel::class;
}
if ($isEmailEnabled) {
if ($isEmailEnabled && $isSubscribedToEmailEvent) {
$channels[] = EmailChannel::class;
}
if ($isTelegramEnabled && $isSubscribedToTelegramEvent) {
@@ -405,7 +417,7 @@ function generateFqdn(Server $server, string $random)
$host = $url->getHost();
$path = $url->getPath() === '/' ? '' : $url->getPath();
$scheme = $url->getScheme();
$finalFqdn = "$scheme://{$random}.$host$path" ;
$finalFqdn = "$scheme://{$random}.$host$path";
return $finalFqdn;
}
function sslip(Server $server)
@@ -425,6 +437,16 @@ function getServiceTemplates()
if (isDev()) {
$services = File::get(base_path('templates/service-templates.json'));
$services = collect(json_decode($services))->sortKeys();
$deprecated = File::get(base_path('templates/deprecated.json'));
$deprecated = collect(json_decode($deprecated))->sortKeys();
$services = $services->merge($deprecated);
$version = config('version');
$services = $services->map(function ($service) use ($version) {
if (version_compare($version, data_get($service, 'minVersion', '0.0.0'), '<')) {
$service->disabled = true;
}
return $service;
});
} else {
$services = Http::get(config('constants.services.official'));
if ($services->failed()) {

View File

@@ -122,14 +122,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 +141,11 @@ function allowedPathsForBoardingAccounts()
'livewire/message/activity-monitor'
];
}
function allowedPathsForInvalidAccounts() {
return [
'logout',
'verify',
'livewire/message/verify-email',
'livewire/message/help'
];
}

391
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -31,9 +31,9 @@ return [
'ultimate' => 25,
],
'email' => [
'zero' => false,
'zero' => true,
'self-hosted' => true,
'basic' => false,
'basic' => true,
'pro' => true,
'ultimate' => true,
],

View File

@@ -1,6 +1,7 @@
<?php
return [
'docs' => 'https://coolify.io/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.62',
'release' => '4.0.0-beta.72',
// 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.62';
return '4.0.0-beta.72';

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,14 @@ services:
POSTGRES_DB: "${DB_DATABASE:-coolify}"
POSTGRES_HOST_AUTH_METHOD: "trust"
volumes:
- ./_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:
- ./_data/coolify/_volumes/redis/:/data
- coolify-redis-data-dev:/data
vite:
image: node:19
working_dir: /var/www/html
@@ -56,7 +56,7 @@ services:
volumes:
- /:/host
- /var/run/docker.sock:/var/run/docker.sock
- ./_data/coolify/:/data/coolify
- coolify-data-dev:/data/coolify
mailpit:
image: "axllent/mailpit:latest"
container_name: coolify-mail
@@ -76,6 +76,12 @@ services:
MINIO_ACCESS_KEY: "${MINIO_ACCESS_KEY:-minioadmin}"
MINIO_SECRET_KEY: "${MINIO_SECRET_KEY:-minioadmin}"
volumes:
- ./_data/coolify/_volumes/minio/:/data
- coolify-minio-data-dev:/data
networks:
- coolify
volumes:
coolify-data-dev:
coolify-pg-data-dev:
coolify-redis-data-dev:
coolify-minio-data-dev:

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

@@ -0,0 +1,756 @@
x-logging: &x-logging
logging:
driver: 'json-file'
options:
max-file: '5'
max-size: '10m'
x-image: &x-image
image: appwrite/appwrite:1.4.3
x-image-assistant: &x-image-assistant
image: appwrite/assistant:0.2.1
version: '3'
services:
appwrite:
<<: *x-image
container_name: appwrite
<<: *x-logging
labels:
- traefik.constraint-label-stack=appwrite
- traefik.docker.network=appwrite
- traefik.http.services.appwrite_api.loadbalancer.server.port=80
#http
- traefik.http.routers.appwrite_api_http.entrypoints=web
- traefik.http.routers.appwrite_api_http.rule=PathPrefix(`/`)
- traefik.http.routers.appwrite_api_http.service=appwrite_api
# https
- traefik.http.routers.appwrite_api_https.entrypoints=websecure
- traefik.http.routers.appwrite_api_https.rule=PathPrefix(`/`)
- traefik.http.routers.appwrite_api_https.service=appwrite_api
- traefik.http.routers.appwrite_api_https.tls=true
volumes:
- appwrite-uploads:/storage/uploads:rw
- appwrite-cache:/storage/cache:rw
- appwrite-config:/storage/config:rw
- appwrite-certificates:/storage/certificates:rw
- appwrite-functions:/storage/functions:rw
depends_on:
- mariadb
- redis
# - clamav
- influxdb
environment:
- SERVICE_FQDN_APPWRITE=/
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_LOCALE
- _APP_CONSOLE_WHITELIST_ROOT
- _APP_CONSOLE_WHITELIST_EMAILS
- _APP_CONSOLE_WHITELIST_IPS
- _APP_SYSTEM_EMAIL_NAME
- _APP_SYSTEM_EMAIL_ADDRESS
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_SYSTEM_RESPONSE_FORMAT
- _APP_OPTIONS_ABUSE
- _APP_OPTIONS_FORCE_HTTPS
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_DOMAIN_TARGET
- _APP_DOMAIN_FUNCTIONS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_SMTP_HOST
- _APP_SMTP_PORT
- _APP_SMTP_SECURE
- _APP_SMTP_USERNAME
- _APP_SMTP_PASSWORD
- _APP_USAGE_STATS
- _APP_INFLUXDB_HOST
- _APP_INFLUXDB_PORT
- _APP_STORAGE_LIMIT
- _APP_STORAGE_PREVIEW_LIMIT
- _APP_STORAGE_ANTIVIRUS
- _APP_STORAGE_ANTIVIRUS_HOST
- _APP_STORAGE_ANTIVIRUS_PORT
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
- _APP_STORAGE_S3_SECRET
- _APP_STORAGE_S3_REGION
- _APP_STORAGE_S3_BUCKET
- _APP_STORAGE_DO_SPACES_ACCESS_KEY
- _APP_STORAGE_DO_SPACES_SECRET
- _APP_STORAGE_DO_SPACES_REGION
- _APP_STORAGE_DO_SPACES_BUCKET
- _APP_STORAGE_BACKBLAZE_ACCESS_KEY
- _APP_STORAGE_BACKBLAZE_SECRET
- _APP_STORAGE_BACKBLAZE_REGION
- _APP_STORAGE_BACKBLAZE_BUCKET
- _APP_STORAGE_LINODE_ACCESS_KEY
- _APP_STORAGE_LINODE_SECRET
- _APP_STORAGE_LINODE_REGION
- _APP_STORAGE_LINODE_BUCKET
- _APP_STORAGE_WASABI_ACCESS_KEY
- _APP_STORAGE_WASABI_SECRET
- _APP_STORAGE_WASABI_REGION
- _APP_STORAGE_WASABI_BUCKET
- _APP_FUNCTIONS_SIZE_LIMIT
- _APP_FUNCTIONS_TIMEOUT
- _APP_FUNCTIONS_BUILD_TIMEOUT
- _APP_FUNCTIONS_CPUS
- _APP_FUNCTIONS_MEMORY
- _APP_FUNCTIONS_RUNTIMES
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_STATSD_HOST
- _APP_STATSD_PORT
- _APP_MAINTENANCE_INTERVAL
- _APP_MAINTENANCE_RETENTION_EXECUTION
- _APP_MAINTENANCE_RETENTION_CACHE
- _APP_MAINTENANCE_RETENTION_ABUSE
- _APP_MAINTENANCE_RETENTION_AUDIT
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
- _APP_MAINTENANCE_RETENTION_SCHEDULES
- _APP_SMS_PROVIDER
- _APP_SMS_FROM
- _APP_GRAPHQL_MAX_BATCH_SIZE
- _APP_GRAPHQL_MAX_COMPLEXITY
- _APP_GRAPHQL_MAX_DEPTH
- _APP_VCS_GITHUB_APP_NAME
- _APP_VCS_GITHUB_PRIVATE_KEY
- _APP_VCS_GITHUB_APP_ID
- _APP_VCS_GITHUB_WEBHOOK_SECRET
- _APP_VCS_GITHUB_CLIENT_SECRET
- _APP_VCS_GITHUB_CLIENT_ID
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
- _APP_ASSISTANT_OPENAI_API_KEY
appwrite-realtime:
<<: *x-image
entrypoint: realtime
container_name: appwrite-realtime
<<: *x-logging
labels:
- "traefik.constraint-label-stack=appwrite"
- "traefik.docker.network=appwrite"
- "traefik.http.services.appwrite_realtime.loadbalancer.server.port=80"
#ws
- traefik.http.routers.appwrite_realtime_ws.entrypoints=web
- traefik.http.routers.appwrite_realtime_ws.rule=PathPrefix(`/v1/realtime`)
- traefik.http.routers.appwrite_realtime_ws.service=appwrite_realtime
# wss
- traefik.http.routers.appwrite_realtime_wss.entrypoints=websecure
- traefik.http.routers.appwrite_realtime_wss.rule=PathPrefix(`/v1/realtime`)
- traefik.http.routers.appwrite_realtime_wss.service=appwrite_realtime
- traefik.http.routers.appwrite_realtime_wss.tls=true
- traefik.http.routers.appwrite_realtime_wss.tls.certresolver=dns
networks:
- appwrite
depends_on:
- mariadb
- redis
environment:
- SERVICE_FQDN_APPWRITE=/v1/realtime
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPTIONS_ABUSE
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_USAGE_STATS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-audits:
<<: *x-image
entrypoint: worker-audits
<<: *x-logging
container_name: appwrite-worker-audits
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-webhooks:
<<: *x-image
entrypoint: worker-webhooks
<<: *x-logging
container_name: appwrite-worker-webhooks
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-deletes:
<<: *x-image
entrypoint: worker-deletes
<<: *x-logging
container_name: appwrite-worker-deletes
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
volumes:
- appwrite-uploads:/storage/uploads:rw
- appwrite-cache:/storage/cache:rw
- appwrite-functions:/storage/functions:rw
- appwrite-builds:/storage/builds:rw
- appwrite-certificates:/storage/certificates:rw
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
- _APP_STORAGE_S3_SECRET
- _APP_STORAGE_S3_REGION
- _APP_STORAGE_S3_BUCKET
- _APP_STORAGE_DO_SPACES_ACCESS_KEY
- _APP_STORAGE_DO_SPACES_SECRET
- _APP_STORAGE_DO_SPACES_REGION
- _APP_STORAGE_DO_SPACES_BUCKET
- _APP_STORAGE_BACKBLAZE_ACCESS_KEY
- _APP_STORAGE_BACKBLAZE_SECRET
- _APP_STORAGE_BACKBLAZE_REGION
- _APP_STORAGE_BACKBLAZE_BUCKET
- _APP_STORAGE_LINODE_ACCESS_KEY
- _APP_STORAGE_LINODE_SECRET
- _APP_STORAGE_LINODE_REGION
- _APP_STORAGE_LINODE_BUCKET
- _APP_STORAGE_WASABI_ACCESS_KEY
- _APP_STORAGE_WASABI_SECRET
- _APP_STORAGE_WASABI_REGION
- _APP_STORAGE_WASABI_BUCKET
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
appwrite-worker-databases:
<<: *x-image
entrypoint: worker-databases
<<: *x-logging
container_name: appwrite-worker-databases
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-builds:
<<: *x-image
entrypoint: worker-builds
<<: *x-logging
container_name: appwrite-worker-builds
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
volumes:
- appwrite-functions:/storage/functions:rw
- appwrite-builds:/storage/builds:rw
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_VCS_GITHUB_APP_NAME
- _APP_VCS_GITHUB_PRIVATE_KEY
- _APP_VCS_GITHUB_APP_ID
- _APP_FUNCTIONS_TIMEOUT
- _APP_FUNCTIONS_BUILD_TIMEOUT
- _APP_FUNCTIONS_CPUS
- _APP_FUNCTIONS_MEMORY
- _APP_OPTIONS_FORCE_HTTPS
- _APP_DOMAIN
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
- _APP_STORAGE_S3_SECRET
- _APP_STORAGE_S3_REGION
- _APP_STORAGE_S3_BUCKET
- _APP_STORAGE_DO_SPACES_ACCESS_KEY
- _APP_STORAGE_DO_SPACES_SECRET
- _APP_STORAGE_DO_SPACES_REGION
- _APP_STORAGE_DO_SPACES_BUCKET
- _APP_STORAGE_BACKBLAZE_ACCESS_KEY
- _APP_STORAGE_BACKBLAZE_SECRET
- _APP_STORAGE_BACKBLAZE_REGION
- _APP_STORAGE_BACKBLAZE_BUCKET
- _APP_STORAGE_LINODE_ACCESS_KEY
- _APP_STORAGE_LINODE_SECRET
- _APP_STORAGE_LINODE_REGION
- _APP_STORAGE_LINODE_BUCKET
- _APP_STORAGE_WASABI_ACCESS_KEY
- _APP_STORAGE_WASABI_SECRET
- _APP_STORAGE_WASABI_REGION
- _APP_STORAGE_WASABI_BUCKET
appwrite-worker-certificates:
<<: *x-image
entrypoint: worker-certificates
<<: *x-logging
container_name: appwrite-worker-certificates
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
volumes:
- appwrite-config:/storage/config:rw
- appwrite-certificates:/storage/certificates:rw
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_DOMAIN_TARGET
- _APP_DOMAIN_FUNCTIONS
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-functions:
<<: *x-image
entrypoint: worker-functions
<<: *x-logging
container_name: appwrite-worker-functions
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
- openruntimes-executor
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_FUNCTIONS_TIMEOUT
- _APP_FUNCTIONS_BUILD_TIMEOUT
- _APP_FUNCTIONS_CPUS
- _APP_FUNCTIONS_MEMORY
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
- _APP_USAGE_STATS
- _APP_DOCKER_HUB_USERNAME
- _APP_DOCKER_HUB_PASSWORD
- _APP_LOGGING_CONFIG
- _APP_LOGGING_PROVIDER
appwrite-worker-mails:
<<: *x-image
entrypoint: worker-mails
<<: *x-logging
container_name: appwrite-worker-mails
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_SYSTEM_EMAIL_NAME
- _APP_SYSTEM_EMAIL_ADDRESS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_SMTP_HOST
- _APP_SMTP_PORT
- _APP_SMTP_SECURE
- _APP_SMTP_USERNAME
- _APP_SMTP_PASSWORD
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-messaging:
<<: *x-image
entrypoint: worker-messaging
<<: *x-logging
container_name: appwrite-worker-messaging
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_SMS_PROVIDER
- _APP_SMS_FROM
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-migrations:
<<: *x-image
entrypoint: worker-migrations
<<: *x-logging
container_name: appwrite-worker-migrations
restart: unless-stopped
networks:
- appwrite
depends_on:
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_DOMAIN_TARGET
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
appwrite-maintenance:
<<: *x-image
entrypoint: maintenance
<<: *x-logging
container_name: appwrite-maintenance
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_DOMAIN
- _APP_DOMAIN_TARGET
- _APP_DOMAIN_FUNCTIONS
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_MAINTENANCE_INTERVAL
- _APP_MAINTENANCE_RETENTION_EXECUTION
- _APP_MAINTENANCE_RETENTION_CACHE
- _APP_MAINTENANCE_RETENTION_ABUSE
- _APP_MAINTENANCE_RETENTION_AUDIT
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
- _APP_MAINTENANCE_RETENTION_SCHEDULES
appwrite-usage:
<<: *x-image
entrypoint: usage
container_name: appwrite-usage
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
- influxdb
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_INFLUXDB_HOST
- _APP_INFLUXDB_PORT
- _APP_USAGE_AGGREGATION_INTERVAL
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_USAGE_STATS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-schedule:
<<: *x-image
entrypoint: schedule
container_name: appwrite-schedule
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
- mariadb
- redis
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
appwrite-assistant:
<<: *x-image-assistant
container_name: appwrite-assistant
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
environment:
- _APP_ASSISTANT_OPENAI_API_KEY
openruntimes-executor:
container_name: openruntimes-executor
hostname: appwrite-executor
<<: *x-logging
stop_signal: SIGINT
image: openruntimes/executor:0.4.1
networks:
- appwrite
- runtimes
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- appwrite-builds:/storage/builds:rw
- appwrite-functions:/storage/functions:rw
# Host mount nessessary to share files between executor and runtimes.
# It's not possible to share mount file between 2 containers without host mount (copying is too slow)
- /tmp:/tmp:rw
environment:
- OPR_EXECUTOR_INACTIVE_TRESHOLD=$_APP_FUNCTIONS_INACTIVE_THRESHOLD
- OPR_EXECUTOR_MAINTENANCE_INTERVAL=$_APP_FUNCTIONS_MAINTENANCE_INTERVAL
- OPR_EXECUTOR_NETWORK=$_APP_FUNCTIONS_RUNTIMES_NETWORK
- OPR_EXECUTOR_DOCKER_HUB_USERNAME=$_APP_DOCKER_HUB_USERNAME
- OPR_EXECUTOR_DOCKER_HUB_PASSWORD=$_APP_DOCKER_HUB_PASSWORD
- OPR_EXECUTOR_ENV=$_APP_ENV
- OPR_EXECUTOR_RUNTIMES=$_APP_FUNCTIONS_RUNTIMES
- OPR_EXECUTOR_SECRET=$_APP_EXECUTOR_SECRET
- OPR_EXECUTOR_LOGGING_PROVIDER=$_APP_LOGGING_PROVIDER
- OPR_EXECUTOR_LOGGING_CONFIG=$_APP_LOGGING_CONFIG
- OPR_EXECUTOR_STORAGE_DEVICE=$_APP_STORAGE_DEVICE
- OPR_EXECUTOR_STORAGE_S3_ACCESS_KEY=$_APP_STORAGE_S3_ACCESS_KEY
- OPR_EXECUTOR_STORAGE_S3_SECRET=$_APP_STORAGE_S3_SECRET
- OPR_EXECUTOR_STORAGE_S3_REGION=$_APP_STORAGE_S3_REGION
- OPR_EXECUTOR_STORAGE_S3_BUCKET=$_APP_STORAGE_S3_BUCKET
- OPR_EXECUTOR_STORAGE_DO_SPACES_ACCESS_KEY=$_APP_STORAGE_DO_SPACES_ACCESS_KEY
- OPR_EXECUTOR_STORAGE_DO_SPACES_SECRET=$_APP_STORAGE_DO_SPACES_SECRET
- OPR_EXECUTOR_STORAGE_DO_SPACES_REGION=$_APP_STORAGE_DO_SPACES_REGION
- OPR_EXECUTOR_STORAGE_DO_SPACES_BUCKET=$_APP_STORAGE_DO_SPACES_BUCKET
- OPR_EXECUTOR_STORAGE_BACKBLAZE_ACCESS_KEY=$_APP_STORAGE_BACKBLAZE_ACCESS_KEY
- OPR_EXECUTOR_STORAGE_BACKBLAZE_SECRET=$_APP_STORAGE_BACKBLAZE_SECRET
- OPR_EXECUTOR_STORAGE_BACKBLAZE_REGION=$_APP_STORAGE_BACKBLAZE_REGION
- OPR_EXECUTOR_STORAGE_BACKBLAZE_BUCKET=$_APP_STORAGE_BACKBLAZE_BUCKET
- OPR_EXECUTOR_STORAGE_LINODE_ACCESS_KEY=$_APP_STORAGE_LINODE_ACCESS_KEY
- OPR_EXECUTOR_STORAGE_LINODE_SECRET=$_APP_STORAGE_LINODE_SECRET
- OPR_EXECUTOR_STORAGE_LINODE_REGION=$_APP_STORAGE_LINODE_REGION
- OPR_EXECUTOR_STORAGE_LINODE_BUCKET=$_APP_STORAGE_LINODE_BUCKET
- OPR_EXECUTOR_STORAGE_WASABI_ACCESS_KEY=$_APP_STORAGE_WASABI_ACCESS_KEY
- OPR_EXECUTOR_STORAGE_WASABI_SECRET=$_APP_STORAGE_WASABI_SECRET
- OPR_EXECUTOR_STORAGE_WASABI_REGION=$_APP_STORAGE_WASABI_REGION
- OPR_EXECUTOR_STORAGE_WASABI_BUCKET=$_APP_STORAGE_WASABI_BUCKET
mariadb:
image: mariadb:10.7 # fix issues when upgrading using: mysql_upgrade -u root -p
container_name: appwrite-mariadb
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
volumes:
- appwrite-mariadb:/var/lib/mysql:rw
environment:
- MYSQL_ROOT_PASSWORD=${_APP_DB_ROOT_PASS}
- MYSQL_DATABASE=${_APP_DB_SCHEMA}
- MYSQL_USER=${_APP_DB_USER}
- MYSQL_PASSWORD=${_APP_DB_PASS}
command: 'mysqld --innodb-flush-method=fsync'
redis:
image: redis:7.0.4-alpine
container_name: appwrite-redis
<<: *x-logging
restart: unless-stopped
command: >
redis-server
--maxmemory 512mb
--maxmemory-policy allkeys-lru
--maxmemory-samples 5
networks:
- appwrite
volumes:
- appwrite-redis:/data:rw
# clamav:
# image: appwrite/clamav:1.2.0
# container_name: appwrite-clamav
# restart: unless-stopped
# networks:
# - appwrite
# volumes:
# - appwrite-uploads:/storage/uploads
influxdb:
image: appwrite/influxdb:1.5.0
container_name: appwrite-influxdb
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
volumes:
- appwrite-influxdb:/var/lib/influxdb:rw
telegraf:
image: appwrite/telegraf:1.4.0
container_name: appwrite-telegraf
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
environment:
- _APP_INFLUXDB_HOST
- _APP_INFLUXDB_PORT
networks:
gateway:
name: gateway
appwrite:
name: appwrite
runtimes:
name: runtimes
volumes:
appwrite-mariadb:
appwrite-redis:
appwrite-cache:
appwrite-uploads:
appwrite-certificates:
appwrite-functions:
appwrite-builds:
appwrite-influxdb:
appwrite-config:

View File

@@ -11,7 +11,8 @@ services:
- database__connection__password=$SERVICE_PASSWORD_MYSQL
- database__connection__database=${MYSQL_DATABASE-ghost}
depends_on:
- mysql
mysql:
condition: service_healthy
mysql:
image: mysql:8.0
volumes:
@@ -20,4 +21,9 @@ services:
- MYSQL_USER=${SERVICE_USER_MYSQL}
- MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL}
- MYSQL_DATABASE=${MYSQL_DATABASE}
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQL_ROOT}
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQLROOT}
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 5s
retries: 10

View File

@@ -0,0 +1,16 @@
services:
minio:
image: quay.io/minio/minio:RELEASE.2023-09-30T07-02-29Z
command: server /data --console-address ":9001"
environment:
SERVICE_FQDN_MINIO_9000:
SERVICE_FQDN_CONSOLE_9001:
MINIO_ROOT_USER: $SERVICE_USER_MINIO
MINIO_ROOT_PASSWORD: $SERVICE_PASSWORD_MINIO
volumes:
- minio-data:/data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 5s
timeout: 20s
retries: 10

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