Merge branch 'next' into patch-1
This commit is contained in:
commit
01977839f7
60
README.md
60
README.md
@ -1,17 +1,21 @@
|
|||||||
|
![Latest Release Version](https://img.shields.io/badge/dynamic/json?labelColor=grey&color=6366f1&label=Latest_released_version&url=https%3A%2F%2Fcdn.coollabs.io%2Fcoolify%2Fversions.json&query=coolify.v4.version&style=for-the-badge
|
||||||
|
)
|
||||||
|
|
||||||
[![Bounty Issues](https://img.shields.io/static/v1?labelColor=grey&color=6366f1&label=Algora&message=%F0%9F%92%8E+Bounty+issues&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties/new)
|
[![Bounty Issues](https://img.shields.io/static/v1?labelColor=grey&color=6366f1&label=Algora&message=%F0%9F%92%8E+Bounty+issues&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties/new)
|
||||||
[![Open Bounties](https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Fcoollabsio%2Fbounties%3Fstatus%3Dopen&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties?status=open)
|
[![Open Bounties](https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Fcoollabsio%2Fbounties%3Fstatus%3Dopen&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties?status=open)
|
||||||
[![Rewarded Bounties](https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Fcoollabsio%2Fbounties%3Fstatus%3Dcompleted&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties?status=completed)
|
[![Rewarded Bounties](https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Fcoollabsio%2Fbounties%3Fstatus%3Dcompleted&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties?status=completed)
|
||||||
|
|
||||||
# About the Project
|
# About the Project
|
||||||
|
|
||||||
Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc.
|
Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc.
|
||||||
|
|
||||||
It helps you to manage your servers, applications, databases on your own hardware, all you need is SSH connection. You can manage VPS, Bare Metal, Raspberry PI's anything.
|
It helps you manage your servers, applications, and databases on your own hardware; you only need an SSH connection. You can manage VPS, Bare Metal, Raspberry PIs, and anything else.
|
||||||
|
|
||||||
Imagine if you could have the ease of a cloud but with your own servers. That is **Coolify**.
|
Imagine having the ease of a cloud but with your own servers. That is **Coolify**.
|
||||||
|
|
||||||
No vendor lock-in, which means that all the configuration for your applications/databases/etc are saved to your server. So if you decide to stop using Coolify (oh nooo), you could still manage your running resources. You just lose the automations and all the magic. 🪄️
|
No vendor lock-in, which means that all the configurations for your applications/databases/etc are saved to your server. So, if you decide to stop using Coolify (oh nooo), you could still manage your running resources. You lose the automations and all the magic. 🪄️
|
||||||
|
|
||||||
For more information, take a look at our landing page [here](https://coolify.io).
|
For more information, take a look at our landing page at [coolify.io](https://coolify.io).
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
@ -22,36 +26,42 @@ # Installation
|
|||||||
|
|
||||||
# Support
|
# Support
|
||||||
|
|
||||||
Contact us [here](https://coolify.io/docs/contact).
|
Contact us at [coolify.io/docs/contact](https://coolify.io/docs/contact).
|
||||||
|
|
||||||
# Donations
|
# Donations
|
||||||
To stay completely free, open-source, no feature behind paywall and evolve the project, we need your help. If you like Coolify, please consider donating to help us fund the future development of the project.
|
To stay completely free and open-source, with no feature behind the paywall and evolve the project, we need your help. If you like Coolify, please consider donating to help us fund the project's future development.
|
||||||
|
|
||||||
https://coolify.io/sponsorships
|
[coolify.io/sponsorships](https://coolify.io/sponsorships)
|
||||||
|
|
||||||
Thank you so much!
|
Thank you so much!
|
||||||
|
|
||||||
Special thanks to our biggest sponsors!
|
Special thanks to our biggest sponsors!
|
||||||
|
|
||||||
<a href="https://cccareers.org/" target="_blank"><img src="./other/logos/ccc-logo.webp" alt="cccareers logo" width="200"/></a>
|
<a href="https://cccareers.org/" target="_blank"><img src="./other/logos/ccc-logo.webp" alt="cccareers logo" width="200"/></a>
|
||||||
<a href="http://htznr.li/CoolifyXHetzner" target="_blank"><img src="./other/logos/hetzner.jpg" alt="hetzner logo" width="200"/></a>
|
<a href="http://htznr.li/CoolifyXHetzner" target="_blank"><img src="./other/logos/hetzner.jpg" alt="hetzner logo" width="150"/></a>
|
||||||
<a href="https://logto.io/?ref=coolify" target="_blank"><img src="./other/logos/logto.webp" alt="logto logo" width="200"/></a>
|
<a href="https://logto.io/?ref=coolify" target="_blank"><img src="./other/logos/logto.webp" alt="logto logo" width="150"/></a>
|
||||||
<a href="https://bc.direct/?utm_source=coolify.io" target="_blank"><img src="./other/logos/bc.png" alt="bc direct logo" width="200"/></a>
|
<a href="https://bc.direct/?ref=coolify.io" target="_blank"><img src="./other/logos/bc.png" alt="bc direct logo" width="200"/></a>
|
||||||
<a href="https://www.quantcdn.io/?utm_source=coolify.io" target="_blank"><img src="./other/logos/quant.svg" alt="quantcdn logo" width="200"/></a>
|
<a href="https://www.quantcdn.io/?ref=coolify.io" target="_blank"><img src="./other/logos/quant.svg" alt="quantcdn logo" width="150"/></a>
|
||||||
<a href="https://arcjet.com/?utm_source=coolify.io" target="_blank"><img src="./other/logos/arcjet.svg" alt="arcjet logo" width="200"/></a>
|
<a href="https://arcjet.com/?ref=coolify.io" target="_blank"><img src="./other/logos/arcjet.svg" alt="arcjet logo" width="200"/></a>
|
||||||
|
<a href="https://supa.guide/?ref=coolify.io" target="_blank"><img src="./other/logos/supaguide.png" alt="supaguide logo" width="200"/></a>
|
||||||
|
<a href="https://tigrisdata.com/?ref=coolify.io" target="_blank"><img src="./other/logos/tigris.svg" alt="tigris logo" width="140"/></a>
|
||||||
|
<a href="https://fractalnetworks.co/?ref=coolify.io" target="_blank"><img src="./other/logos/fractal.svg" alt="fractal logo" width="180"/></a>
|
||||||
|
<a href="https://coolify.ad.vin/?ref=coolify.io" target="_blank"><img src="./other/logos/advin.png" alt="advin logo" width="250"/></a>
|
||||||
|
<a href="https://trieve.ai/?ref=coolify.io" target="_blank"><img src="./other/logos/trieve_bg.png" alt="trieve logo" width="180"/></a>
|
||||||
|
<a href="https://blacksmith.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/blacksmith.svg" alt="blacksmith logo" width="200"/></a>
|
||||||
|
|
||||||
## Github Sponsors ($40+)
|
## Github Sponsors ($40+)
|
||||||
<a href="https://serpapi.com/?utm_source=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a>
|
<a href="https://serpapi.com/?ref=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a>
|
||||||
<a href="https://typebot.io/?utm_source=coolify.io"><img src="https://pbs.twimg.com/profile_images/1509194008366657543/9I-C7uWT_400x400.jpg" width="60px" alt="typebot"/></a>
|
<a href="https://typebot.io/?ref=coolify.io"><img src="https://pbs.twimg.com/profile_images/1509194008366657543/9I-C7uWT_400x400.jpg" width="60px" alt="typebot"/></a>
|
||||||
<a href="https://www.runpod.io/?utm_source=coolify.io">
|
<a href="https://www.runpod.io/?ref=coolify.io">
|
||||||
<svg style="width:60px;height:60px;background:#fff;" xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 200 200"><g><path d="M74.5 51.1c-25.4 14.9-27 16-29.6 20.2-1.8 3-1.9 5.3-1.9 32.3 0 21.7.3 29.4 1.3 30.6 1.9 2.5 46.7 27.9 48.5 27.6 1.5-.3 1.7-3.1 2-27.7.2-21.9 0-27.8-1.1-29.5-.8-1.2-9.9-6.8-20.2-12.6-10.3-5.8-19.4-11.5-20.2-12.7-1.8-2.6-.9-5.9 1.8-7.4 1.6-.8 6.3 0 21.8 4C87.8 78.7 98 81 99.6 81c4.4 0 49.9-25.9 49.9-28.4 0-1.6-3.4-2.8-24-8.2-13.2-3.5-25.1-6.3-26.5-6.3-1.4.1-12.4 5.9-24.5 13z"></path><path d="m137.2 68.1-3.3 2.1 6.3 3.7c3.5 2 6.3 4.3 6.3 5.1 0 .9-8 6.1-19.4 12.6-10.6 6-20 11.9-20.7 12.9-1.2 1.6-1.4 7.2-1.2 29.4.3 24.8.5 27.6 2 27.9 1.8.3 46.6-25.1 48.6-27.6.9-1.2 1.2-8.8 1.2-30.2s-.3-29-1.2-30.2c-1.6-1.9-12.1-7.8-13.9-7.8-.8 0-2.9 1-4.7 2.1z"></path></g></svg></a>
|
<svg style="width:60px;height:60px;background:#fff;" xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 200 200"><g><path d="M74.5 51.1c-25.4 14.9-27 16-29.6 20.2-1.8 3-1.9 5.3-1.9 32.3 0 21.7.3 29.4 1.3 30.6 1.9 2.5 46.7 27.9 48.5 27.6 1.5-.3 1.7-3.1 2-27.7.2-21.9 0-27.8-1.1-29.5-.8-1.2-9.9-6.8-20.2-12.6-10.3-5.8-19.4-11.5-20.2-12.7-1.8-2.6-.9-5.9 1.8-7.4 1.6-.8 6.3 0 21.8 4C87.8 78.7 98 81 99.6 81c4.4 0 49.9-25.9 49.9-28.4 0-1.6-3.4-2.8-24-8.2-13.2-3.5-25.1-6.3-26.5-6.3-1.4.1-12.4 5.9-24.5 13z"></path><path d="m137.2 68.1-3.3 2.1 6.3 3.7c3.5 2 6.3 4.3 6.3 5.1 0 .9-8 6.1-19.4 12.6-10.6 6-20 11.9-20.7 12.9-1.2 1.6-1.4 7.2-1.2 29.4.3 24.8.5 27.6 2 27.9 1.8.3 46.6-25.1 48.6-27.6.9-1.2 1.2-8.8 1.2-30.2s-.3-29-1.2-30.2c-1.6-1.9-12.1-7.8-13.9-7.8-.8 0-2.9 1-4.7 2.1z"></path></g></svg></a>
|
||||||
<a href="https://lightspeed.run/?utm_source=coolify.io"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a>
|
<a href="https://lightspeed.run/?ref=coolify.io"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a>
|
||||||
<a href="https://www.flint.sh/en/home?utm_source=coolify.io"> <img src="https://github.com/Flint-company.png" width="60px" alt="FlintCompany"/></a>
|
<a href="https://www.flint.sh/en/home?ref=coolify.io"> <img src="https://github.com/Flint-company.png" width="60px" alt="FlintCompany"/></a>
|
||||||
<a href="https://americancloud.com/?utm_source=coolify.io"><img src="https://github.com/American-Cloud.png" width="60px" alt="American Cloud"/></a>
|
<a href="https://americancloud.com/?ref=coolify.io"><img src="https://github.com/American-Cloud.png" width="60px" alt="American Cloud"/></a>
|
||||||
<a href="https://cryptojobslist.com/?utm_source=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a>
|
<a href="https://cryptojobslist.com/?ref=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a>
|
||||||
<a href="https://x.com/mrsmith9ja?utm_source=coolify.io"><img width="60px" alt="Thompson Edolo" src="https://github.com/verygreenboi.png"/></a>
|
<a href="https://codext.link/coolify-io?ref=coolify.io"><img src="./other/logos/codext.jpg" width="60px" alt="Codext" /></a>
|
||||||
<a href="https://www.uxwizz.com/?utm_source=coolify.io"><img width="60px" alt="UXWizz" src="https://github.com/UXWizz.png"/></a>
|
<a href="https://x.com/mrsmith9ja?ref=coolify.io"><img width="60px" alt="Thompson Edolo" src="https://github.com/verygreenboi.png"/></a>
|
||||||
|
<a href="https://www.uxwizz.com/?ref=coolify.io"><img width="60px" alt="UXWizz" src="https://github.com/UXWizz.png"/></a>
|
||||||
<a href="https://github.com/Flowko"><img src="https://barrad.me/_ipx/f_webp&s_300x300/younes.jpg" width="60px" alt="Younes Barrad" /></a>
|
<a href="https://github.com/Flowko"><img src="https://barrad.me/_ipx/f_webp&s_300x300/younes.jpg" width="60px" alt="Younes Barrad" /></a>
|
||||||
<a href="https://github.com/automazeio"><img src="https://github.com/automazeio.png" width="60px" alt="Automaze" /></a>
|
<a href="https://github.com/automazeio"><img src="https://github.com/automazeio.png" width="60px" alt="Automaze" /></a>
|
||||||
<a href="https://github.com/corentinclichy"><img src="https://github.com/corentinclichy.png" width="60px" alt="Corentin Clichy" /></a>
|
<a href="https://github.com/corentinclichy"><img src="https://github.com/corentinclichy.png" width="60px" alt="Corentin Clichy" /></a>
|
||||||
@ -83,9 +93,9 @@ ## Individuals
|
|||||||
|
|
||||||
# Cloud
|
# Cloud
|
||||||
|
|
||||||
If you do not want to self-host Coolify, there is a paid cloud version available: https://app.coolify.io
|
If you do not want to self-host Coolify, there is a paid cloud version available: [app.coolify.io](https://app.coolify.io)
|
||||||
|
|
||||||
For more information & pricing, take a look at our landing page [here](https://coolify.io).
|
For more information & pricing, take a look at our landing page [coolify.io](https://coolify.io).
|
||||||
|
|
||||||
## Why should I use the Cloud version?
|
## Why should I use the Cloud version?
|
||||||
The recommended way to use Coolify is to have one server for Coolify and one (or more) for the resources you are deploying. A server is around 4-5$/month.
|
The recommended way to use Coolify is to have one server for Coolify and one (or more) for the resources you are deploying. A server is around 4-5$/month.
|
||||||
@ -109,7 +119,7 @@ # Recognitions
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<a href="https://www.producthunt.com/posts/coolify?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-coolify" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=338273&theme=light" alt="Coolify - An open-source & self-hostable Heroku, Netlify alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
<a href="https://www.producthunt.com/posts/coolify?ref=badge-featured&utm_medium=badge&utm_souce=badge-coolify" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=338273&theme=light" alt="Coolify - An open-source & self-hostable Heroku, Netlify alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||||
|
|
||||||
<a href="https://trendshift.io/repositories/634" target="_blank"><img src="https://trendshift.io/api/badge/repositories/634" alt="coollabsio%2Fcoolify | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
<a href="https://trendshift.io/repositories/634" target="_blank"><img src="https://trendshift.io/api/badge/repositories/634" alt="coollabsio%2Fcoolify | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
|
|
||||||
|
16
app/Actions/Application/LoadComposeFile.php
Normal file
16
app/Actions/Application/LoadComposeFile.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Application;
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class LoadComposeFile
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(Application $application)
|
||||||
|
{
|
||||||
|
$application->loadComposeFile();
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Actions\CoolifyTask;
|
namespace App\Actions\CoolifyTask;
|
||||||
|
|
||||||
use App\Data\CoolifyTaskArgs;
|
use App\Data\CoolifyTaskArgs;
|
||||||
|
use App\Enums\ActivityTypes;
|
||||||
use App\Jobs\CoolifyTask;
|
use App\Jobs\CoolifyTask;
|
||||||
use Spatie\Activitylog\Models\Activity;
|
use Spatie\Activitylog\Models\Activity;
|
||||||
|
|
||||||
@ -40,8 +41,18 @@ public function __construct(CoolifyTaskArgs $remoteProcessArgs)
|
|||||||
|
|
||||||
public function __invoke(): Activity
|
public function __invoke(): Activity
|
||||||
{
|
{
|
||||||
$job = new CoolifyTask($this->activity, ignore_errors: $this->remoteProcessArgs->ignore_errors, call_event_on_finish: $this->remoteProcessArgs->call_event_on_finish, call_event_data: $this->remoteProcessArgs->call_event_data);
|
$job = new CoolifyTask(
|
||||||
dispatch($job);
|
activity: $this->activity,
|
||||||
|
ignore_errors: $this->remoteProcessArgs->ignore_errors,
|
||||||
|
call_event_on_finish: $this->remoteProcessArgs->call_event_on_finish,
|
||||||
|
call_event_data: $this->remoteProcessArgs->call_event_data,
|
||||||
|
);
|
||||||
|
if ($this->remoteProcessArgs->type === ActivityTypes::COMMAND->value) {
|
||||||
|
ray('Dispatching a high priority job');
|
||||||
|
dispatch($job)->onQueue('high');
|
||||||
|
} else {
|
||||||
|
dispatch($job);
|
||||||
|
}
|
||||||
$this->activity->refresh();
|
$this->activity->refresh();
|
||||||
|
|
||||||
return $this->activity;
|
return $this->activity;
|
||||||
|
@ -39,7 +39,7 @@ class RunRemoteProcess
|
|||||||
public function __construct(Activity $activity, bool $hide_from_output = false, bool $ignore_errors = false, $call_event_on_finish = null, $call_event_data = null)
|
public function __construct(Activity $activity, bool $hide_from_output = false, bool $ignore_errors = false, $call_event_on_finish = null, $call_event_data = null)
|
||||||
{
|
{
|
||||||
|
|
||||||
if ($activity->getExtraProperty('type') !== ActivityTypes::INLINE->value) {
|
if ($activity->getExtraProperty('type') !== ActivityTypes::INLINE->value && $activity->getExtraProperty('type') !== ActivityTypes::COMMAND->value) {
|
||||||
throw new \RuntimeException('Incompatible Activity to run a remote command.');
|
throw new \RuntimeException('Incompatible Activity to run a remote command.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
29
app/Actions/Database/RestartDatabase.php
Normal file
29
app/Actions/Database/RestartDatabase.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandaloneClickhouse;
|
||||||
|
use App\Models\StandaloneDragonfly;
|
||||||
|
use App\Models\StandaloneKeydb;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\StandaloneRedis;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class RestartDatabase
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database)
|
||||||
|
{
|
||||||
|
$server = $database->destination->server;
|
||||||
|
if (! $server->isFunctional()) {
|
||||||
|
return 'Server is not functional';
|
||||||
|
}
|
||||||
|
StopDatabase::run($database);
|
||||||
|
|
||||||
|
return StartDatabase::run($database);
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,6 @@
|
|||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
use App\Models\StandaloneClickhouse;
|
use App\Models\StandaloneClickhouse;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
@ -155,11 +154,11 @@ private function generate_environment_variables()
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('CLICKHOUSE_ADMIN_USER'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('CLICKHOUSE_ADMIN_USER'))->isEmpty()) {
|
||||||
$environment_variables->push("CLICKHOUSE_ADMIN_USER={$this->database->clickhouse_admin_user}");
|
$environment_variables->push("CLICKHOUSE_ADMIN_USER={$this->database->clickhouse_admin_user}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('CLICKHOUSE_ADMIN_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('CLICKHOUSE_ADMIN_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("CLICKHOUSE_ADMIN_PASSWORD={$this->database->clickhouse_admin_password}");
|
$environment_variables->push("CLICKHOUSE_ADMIN_PASSWORD={$this->database->clickhouse_admin_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
57
app/Actions/Database/StartDatabase.php
Normal file
57
app/Actions/Database/StartDatabase.php
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandaloneClickhouse;
|
||||||
|
use App\Models\StandaloneDragonfly;
|
||||||
|
use App\Models\StandaloneKeydb;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\StandaloneRedis;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class StartDatabase
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database)
|
||||||
|
{
|
||||||
|
$server = $database->destination->server;
|
||||||
|
if (! $server->isFunctional()) {
|
||||||
|
return 'Server is not functional';
|
||||||
|
}
|
||||||
|
switch ($database->getMorphClass()) {
|
||||||
|
case 'App\Models\StandalonePostgresql':
|
||||||
|
$activity = StartPostgresql::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneRedis':
|
||||||
|
$activity = StartRedis::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneMongodb':
|
||||||
|
$activity = StartMongodb::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneMysql':
|
||||||
|
$activity = StartMysql::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneMariadb':
|
||||||
|
$activity = StartMariadb::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneKeydb':
|
||||||
|
$activity = StartKeydb::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneDragonfly':
|
||||||
|
$activity = StartDragonfly::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneClickhouse':
|
||||||
|
$activity = StartClickhouse::run($database);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ($database->is_public && $database->public_port) {
|
||||||
|
StartDatabaseProxy::dispatch($database);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $activity;
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,6 @@
|
|||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
use App\Models\StandaloneDragonfly;
|
use App\Models\StandaloneDragonfly;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
@ -155,7 +154,7 @@ private function generate_environment_variables()
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("REDIS_PASSWORD={$this->database->dragonfly_password}");
|
$environment_variables->push("REDIS_PASSWORD={$this->database->dragonfly_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
use App\Models\StandaloneKeydb;
|
use App\Models\StandaloneKeydb;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
@ -163,7 +162,7 @@ private function generate_environment_variables()
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("REDIS_PASSWORD={$this->database->keydb_password}");
|
$environment_variables->push("REDIS_PASSWORD={$this->database->keydb_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
use App\Models\StandaloneMariadb;
|
use App\Models\StandaloneMariadb;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
@ -157,18 +156,18 @@ private function generate_environment_variables()
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MARIADB_ROOT_PASSWORD={$this->database->mariadb_root_password}");
|
$environment_variables->push("MARIADB_ROOT_PASSWORD={$this->database->mariadb_root_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_DATABASE'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_DATABASE'))->isEmpty()) {
|
||||||
$environment_variables->push("MARIADB_DATABASE={$this->database->mariadb_database}");
|
$environment_variables->push("MARIADB_DATABASE={$this->database->mariadb_database}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_USER'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_USER'))->isEmpty()) {
|
||||||
$environment_variables->push("MARIADB_USER={$this->database->mariadb_user}");
|
$environment_variables->push("MARIADB_USER={$this->database->mariadb_user}");
|
||||||
}
|
}
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}");
|
$environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
use App\Models\StandaloneMongodb;
|
use App\Models\StandaloneMongodb;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
@ -174,15 +173,15 @@ private function generate_environment_variables()
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) {
|
||||||
$environment_variables->push("MONGO_INITDB_ROOT_USERNAME={$this->database->mongo_initdb_root_username}");
|
$environment_variables->push("MONGO_INITDB_ROOT_USERNAME={$this->database->mongo_initdb_root_username}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_ROOT_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_ROOT_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MONGO_INITDB_ROOT_PASSWORD={$this->database->mongo_initdb_root_password}");
|
$environment_variables->push("MONGO_INITDB_ROOT_PASSWORD={$this->database->mongo_initdb_root_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) {
|
||||||
$environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}");
|
$environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
use App\Models\StandaloneMysql;
|
use App\Models\StandaloneMysql;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
@ -157,18 +156,18 @@ private function generate_environment_variables()
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MYSQL_ROOT_PASSWORD={$this->database->mysql_root_password}");
|
$environment_variables->push("MYSQL_ROOT_PASSWORD={$this->database->mysql_root_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_DATABASE'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_DATABASE'))->isEmpty()) {
|
||||||
$environment_variables->push("MYSQL_DATABASE={$this->database->mysql_database}");
|
$environment_variables->push("MYSQL_DATABASE={$this->database->mysql_database}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_USER'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_USER'))->isEmpty()) {
|
||||||
$environment_variables->push("MYSQL_USER={$this->database->mysql_user}");
|
$environment_variables->push("MYSQL_USER={$this->database->mysql_user}");
|
||||||
}
|
}
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}");
|
$environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
@ -179,18 +178,18 @@ private function generate_environment_variables()
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_USER'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('POSTGRES_USER'))->isEmpty()) {
|
||||||
$environment_variables->push("POSTGRES_USER={$this->database->postgres_user}");
|
$environment_variables->push("POSTGRES_USER={$this->database->postgres_user}");
|
||||||
}
|
}
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('PGUSER'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('PGUSER'))->isEmpty()) {
|
||||||
$environment_variables->push("PGUSER={$this->database->postgres_user}");
|
$environment_variables->push("PGUSER={$this->database->postgres_user}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('POSTGRES_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("POSTGRES_PASSWORD={$this->database->postgres_password}");
|
$environment_variables->push("POSTGRES_PASSWORD={$this->database->postgres_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_DB'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('POSTGRES_DB'))->isEmpty()) {
|
||||||
$environment_variables->push("POSTGRES_DB={$this->database->postgres_db}");
|
$environment_variables->push("POSTGRES_DB={$this->database->postgres_db}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
use App\Models\StandaloneRedis;
|
use App\Models\StandaloneRedis;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
@ -167,7 +166,7 @@ private function generate_environment_variables()
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}");
|
$environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,5 @@ public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|St
|
|||||||
if ($database->is_public) {
|
if ($database->is_public) {
|
||||||
StopDatabaseProxy::run($database);
|
StopDatabaseProxy::run($database);
|
||||||
}
|
}
|
||||||
// TODO: make notification for services
|
|
||||||
// $database->environment->project->team->notify(new StatusChanged($database));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Events\DatabaseStatusChanged;
|
||||||
use App\Models\ServiceDatabase;
|
use App\Models\ServiceDatabase;
|
||||||
use App\Models\StandaloneClickhouse;
|
use App\Models\StandaloneClickhouse;
|
||||||
use App\Models\StandaloneDragonfly;
|
use App\Models\StandaloneDragonfly;
|
||||||
@ -26,7 +27,7 @@ public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|St
|
|||||||
$server = data_get($database, 'service.server');
|
$server = data_get($database, 'service.server');
|
||||||
}
|
}
|
||||||
instant_remote_process(["docker rm -f {$uuid}-proxy"], $server);
|
instant_remote_process(["docker rm -f {$uuid}-proxy"], $server);
|
||||||
$database->is_public = false;
|
|
||||||
$database->save();
|
$database->save();
|
||||||
|
DatabaseStatusChanged::dispatch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
namespace App\Actions\Proxy;
|
namespace App\Actions\Proxy;
|
||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class CheckConfiguration
|
class CheckConfiguration
|
||||||
@ -24,7 +23,7 @@ public function handle(Server $server, bool $reset = false)
|
|||||||
$proxy_configuration = instant_remote_process($payload, $server, false);
|
$proxy_configuration = instant_remote_process($payload, $server, false);
|
||||||
|
|
||||||
if ($reset || ! $proxy_configuration || is_null($proxy_configuration)) {
|
if ($reset || ! $proxy_configuration || is_null($proxy_configuration)) {
|
||||||
$proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value;
|
$proxy_configuration = str(generate_default_proxy_configuration($server))->trim()->value;
|
||||||
}
|
}
|
||||||
if (! $proxy_configuration || is_null($proxy_configuration)) {
|
if (! $proxy_configuration || is_null($proxy_configuration)) {
|
||||||
throw new \Exception('Could not generate proxy configuration');
|
throw new \Exception('Could not generate proxy configuration');
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
namespace App\Actions\Proxy;
|
namespace App\Actions\Proxy;
|
||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class SaveConfiguration
|
class SaveConfiguration
|
||||||
@ -18,7 +17,7 @@ public function handle(Server $server, ?string $proxy_settings = null)
|
|||||||
$proxy_path = $server->proxyPath();
|
$proxy_path = $server->proxyPath();
|
||||||
$docker_compose_yml_base64 = base64_encode($proxy_settings);
|
$docker_compose_yml_base64 = base64_encode($proxy_settings);
|
||||||
|
|
||||||
$server->proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
$server->proxy->last_saved_settings = str($docker_compose_yml_base64)->pipe('md5')->value;
|
||||||
$server->save();
|
$server->save();
|
||||||
|
|
||||||
return instant_remote_process([
|
return instant_remote_process([
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
use App\Events\ProxyStarted;
|
use App\Events\ProxyStarted;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Spatie\Activitylog\Models\Activity;
|
use Spatie\Activitylog\Models\Activity;
|
||||||
|
|
||||||
@ -27,7 +26,7 @@ public function handle(Server $server, bool $async = true): string|Activity
|
|||||||
}
|
}
|
||||||
SaveConfiguration::run($server, $configuration);
|
SaveConfiguration::run($server, $configuration);
|
||||||
$docker_compose_yml_base64 = base64_encode($configuration);
|
$docker_compose_yml_base64 = base64_encode($configuration);
|
||||||
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
$server->proxy->last_applied_settings = str($docker_compose_yml_base64)->pipe('md5')->value;
|
||||||
$server->save();
|
$server->save();
|
||||||
if ($server->isSwarm()) {
|
if ($server->isSwarm()) {
|
||||||
$commands = $commands->merge([
|
$commands = $commands->merge([
|
||||||
|
19
app/Actions/Server/RunCommand.php
Normal file
19
app/Actions/Server/RunCommand.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Enums\ActivityTypes;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class RunCommand
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(Server $server, $command)
|
||||||
|
{
|
||||||
|
$activity = remote_process(command: [$command], server: $server, ignore_errors: true, type: ActivityTypes::COMMAND->value);
|
||||||
|
|
||||||
|
return $activity;
|
||||||
|
}
|
||||||
|
}
|
@ -12,12 +12,15 @@ class StartSentinel
|
|||||||
public function handle(Server $server, $version = 'latest', bool $restart = false)
|
public function handle(Server $server, $version = 'latest', bool $restart = false)
|
||||||
{
|
{
|
||||||
if ($restart) {
|
if ($restart) {
|
||||||
instant_remote_process(['docker rm -f coolify-sentinel'], $server, false);
|
StopSentinel::run($server);
|
||||||
}
|
}
|
||||||
|
$metrics_history = $server->settings->metrics_history_days;
|
||||||
|
$refresh_rate = $server->settings->metrics_refresh_rate_seconds;
|
||||||
|
$token = $server->settings->metrics_token;
|
||||||
instant_remote_process([
|
instant_remote_process([
|
||||||
"docker run --rm --pull always -d -e \"SCHEDULER=true\" --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/metrics:/app/metrics -v /data/coolify/logs:/app/logs --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 ghcr.io/coollabsio/sentinel:$version",
|
"docker run --rm --pull always -d -e \"TOKEN={$token}\" -e \"SCHEDULER=true\" -e \"METRICS_HISTORY={$metrics_history}\" -e \"REFRESH_RATE={$refresh_rate}\" --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/metrics:/app/metrics -v /data/coolify/logs:/app/logs --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 ghcr.io/coollabsio/sentinel:$version",
|
||||||
'chown -R 9999:root /data/coolify/metrics /data/coolify/logs',
|
'chown -R 9999:root /data/coolify/metrics /data/coolify/logs',
|
||||||
'chmod -R 700 /data/coolify/metrics /data/coolify/logs',
|
'chmod -R 700 /data/coolify/metrics /data/coolify/logs',
|
||||||
], $server, false);
|
], $server, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
16
app/Actions/Server/StopSentinel.php
Normal file
16
app/Actions/Server/StopSentinel.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class StopSentinel
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(Server $server)
|
||||||
|
{
|
||||||
|
instant_remote_process(['docker rm -f coolify-sentinel'], $server, false);
|
||||||
|
}
|
||||||
|
}
|
@ -25,7 +25,7 @@ public function handle($manual_update = false)
|
|||||||
if (! $this->server) {
|
if (! $this->server) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CleanupDocker::run($this->server, false);
|
CleanupDocker::dispatch($this->server, false)->onQueue('high');
|
||||||
$this->latestVersion = get_latest_version_of_coolify();
|
$this->latestVersion = get_latest_version_of_coolify();
|
||||||
$this->currentVersion = config('version');
|
$this->currentVersion = config('version');
|
||||||
if (! $manual_update) {
|
if (! $manual_update) {
|
||||||
@ -48,6 +48,7 @@ public function handle($manual_update = false)
|
|||||||
private function update()
|
private function update()
|
||||||
{
|
{
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
|
ray('Running in dev mode');
|
||||||
remote_process([
|
remote_process([
|
||||||
'sleep 10',
|
'sleep 10',
|
||||||
], $this->server);
|
], $this->server);
|
||||||
|
18
app/Actions/Service/RestartService.php
Normal file
18
app/Actions/Service/RestartService.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Service;
|
||||||
|
|
||||||
|
use App\Models\Service;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class RestartService
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(Service $service)
|
||||||
|
{
|
||||||
|
StopService::run($service);
|
||||||
|
|
||||||
|
return StartService::run($service);
|
||||||
|
}
|
||||||
|
}
|
101
app/Console/Commands/CloudCleanupSubscriptions.php
Normal file
101
app/Console/Commands/CloudCleanupSubscriptions.php
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Team;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class CloudCleanupSubscriptions extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'cloud:cleanup-subs';
|
||||||
|
|
||||||
|
protected $description = 'Cleanup subcriptions teams';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (! isCloud()) {
|
||||||
|
$this->error('This command can only be run on cloud');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ray()->clearAll();
|
||||||
|
$this->info('Cleaning up subcriptions teams');
|
||||||
|
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
|
||||||
|
|
||||||
|
$teams = Team::all()->filter(function ($team) {
|
||||||
|
return $team->id !== 0;
|
||||||
|
})->sortBy('id');
|
||||||
|
foreach ($teams as $team) {
|
||||||
|
if ($team) {
|
||||||
|
$this->info("Checking team {$team->id}");
|
||||||
|
}
|
||||||
|
if (! data_get($team, 'subscription')) {
|
||||||
|
$this->disableServers($team);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// If the team has no subscription id and the invoice is paid, we need to reset the invoice paid status
|
||||||
|
if (! (data_get($team, 'subscription.stripe_subscription_id'))) {
|
||||||
|
$this->info("Resetting invoice paid status for team {$team->id} {$team->name}");
|
||||||
|
|
||||||
|
$team->subscription->update([
|
||||||
|
'stripe_invoice_paid' => false,
|
||||||
|
'stripe_trial_already_ended' => false,
|
||||||
|
'stripe_subscription_id' => null,
|
||||||
|
]);
|
||||||
|
$this->disableServers($team);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$subscription = $stripe->subscriptions->retrieve(data_get($team, 'subscription.stripe_subscription_id'), []);
|
||||||
|
$status = data_get($subscription, 'status');
|
||||||
|
if ($status === 'active' || $status === 'past_due') {
|
||||||
|
$team->subscription->update([
|
||||||
|
'stripe_invoice_paid' => true,
|
||||||
|
'stripe_trial_already_ended' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$this->info('Subscription status: '.$status);
|
||||||
|
$this->info('Subscription id: '.data_get($team, 'subscription.stripe_subscription_id'));
|
||||||
|
$confirm = $this->confirm('Do you want to cancel the subscription?', true);
|
||||||
|
if (! $confirm) {
|
||||||
|
$this->info("Skipping team {$team->id} {$team->name}");
|
||||||
|
} else {
|
||||||
|
$this->info("Cancelling subscription for team {$team->id} {$team->name}");
|
||||||
|
$team->subscription->update([
|
||||||
|
'stripe_invoice_paid' => false,
|
||||||
|
'stripe_trial_already_ended' => false,
|
||||||
|
'stripe_subscription_id' => null,
|
||||||
|
]);
|
||||||
|
$this->disableServers($team);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->error($e->getMessage());
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function disableServers(Team $team)
|
||||||
|
{
|
||||||
|
foreach ($team->servers as $server) {
|
||||||
|
if ($server->settings->is_usable === true || $server->settings->is_reachable === true || $server->ip !== '1.2.3.4') {
|
||||||
|
$this->info("Disabling server {$server->id} {$server->name}");
|
||||||
|
$server->settings()->update([
|
||||||
|
'is_usable' => false,
|
||||||
|
'is_reachable' => false,
|
||||||
|
]);
|
||||||
|
$server->update([
|
||||||
|
'ip' => '1.2.3.4',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -9,13 +9,41 @@
|
|||||||
|
|
||||||
class Dev extends Command
|
class Dev extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'dev:init';
|
protected $signature = 'dev {--init} {--generate-openapi}';
|
||||||
|
|
||||||
protected $description = 'Init the app in dev mode';
|
protected $description = 'Helper commands for development.';
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
|
{
|
||||||
|
if ($this->option('init')) {
|
||||||
|
$this->init();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($this->option('generate-openapi')) {
|
||||||
|
$this->generateOpenApi();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateOpenApi()
|
||||||
|
{
|
||||||
|
// Generate OpenAPI documentation
|
||||||
|
echo "Generating OpenAPI documentation.\n";
|
||||||
|
$process = Process::run(['/var/www/html/vendor/bin/openapi', 'app', '-o', 'openapi.yaml']);
|
||||||
|
$error = $process->errorOutput();
|
||||||
|
$error = preg_replace('/^.*an object literal,.*$/m', '', $error);
|
||||||
|
$error = preg_replace('/^\h*\v+/m', '', $error);
|
||||||
|
echo $error;
|
||||||
|
echo $process->output();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function init()
|
||||||
{
|
{
|
||||||
// Generate APP_KEY if not exists
|
// Generate APP_KEY if not exists
|
||||||
|
|
||||||
if (empty(env('APP_KEY'))) {
|
if (empty(env('APP_KEY'))) {
|
||||||
echo "Generating APP_KEY.\n";
|
echo "Generating APP_KEY.\n";
|
||||||
Artisan::call('key:generate');
|
Artisan::call('key:generate');
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
use App\Enums\ApplicationDeploymentStatus;
|
use App\Enums\ApplicationDeploymentStatus;
|
||||||
use App\Jobs\CleanupHelperContainersJob;
|
use App\Jobs\CleanupHelperContainersJob;
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
|
use App\Models\Environment;
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
@ -24,6 +25,8 @@ public function handle()
|
|||||||
get_public_ips();
|
get_public_ips();
|
||||||
$full_cleanup = $this->option('full-cleanup');
|
$full_cleanup = $this->option('full-cleanup');
|
||||||
$cleanup_deployments = $this->option('cleanup-deployments');
|
$cleanup_deployments = $this->option('cleanup-deployments');
|
||||||
|
|
||||||
|
$this->replace_slash_in_environment_name();
|
||||||
if ($cleanup_deployments) {
|
if ($cleanup_deployments) {
|
||||||
echo "Running cleanup deployments.\n";
|
echo "Running cleanup deployments.\n";
|
||||||
$this->cleanup_in_progress_application_deployments();
|
$this->cleanup_in_progress_application_deployments();
|
||||||
@ -150,4 +153,15 @@ private function cleanup_in_progress_application_deployments()
|
|||||||
echo "Error: {$e->getMessage()}\n";
|
echo "Error: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function replace_slash_in_environment_name()
|
||||||
|
{
|
||||||
|
$environments = Environment::all();
|
||||||
|
foreach ($environments as $environment) {
|
||||||
|
if (str_contains($environment->name, '/')) {
|
||||||
|
$environment->name = str_replace('/', '-', $environment->name);
|
||||||
|
$environment->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@ private function register_user()
|
|||||||
if (! $already_registered) {
|
if (! $already_registered) {
|
||||||
$this->password = Str::password();
|
$this->password = Str::password();
|
||||||
User::create([
|
User::create([
|
||||||
'name' => Str::of($this->next_patient->email)->before('@'),
|
'name' => str($this->next_patient->email)->before('@'),
|
||||||
'email' => $this->next_patient->email,
|
'email' => $this->next_patient->email,
|
||||||
'password' => Hash::make($this->password),
|
'password' => Hash::make($this->password),
|
||||||
'force_password_reset' => true,
|
'force_password_reset' => true,
|
||||||
|
@ -61,7 +61,7 @@ private function pull_images($schedule)
|
|||||||
{
|
{
|
||||||
$servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
|
$servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
if (config('coolify.is_sentinel_enabled')) {
|
if ($server->isSentinelEnabled()) {
|
||||||
$schedule->job(new PullSentinelImageJob($server))->everyFiveMinutes()->onOneServer();
|
$schedule->job(new PullSentinelImageJob($server))->everyFiveMinutes()->onOneServer();
|
||||||
}
|
}
|
||||||
$schedule->job(new PullHelperImageJob($server))->everyFiveMinutes()->onOneServer();
|
$schedule->job(new PullHelperImageJob($server))->everyFiveMinutes()->onOneServer();
|
||||||
|
@ -11,6 +11,5 @@ class ServerMetadata extends Data
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
public ?ProxyTypes $type,
|
public ?ProxyTypes $type,
|
||||||
public ?ProxyStatus $status
|
public ?ProxyStatus $status
|
||||||
) {
|
) {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -5,4 +5,5 @@
|
|||||||
enum ActivityTypes: string
|
enum ActivityTypes: string
|
||||||
{
|
{
|
||||||
case INLINE = 'inline';
|
case INLINE = 'inline';
|
||||||
|
case COMMAND = 'command';
|
||||||
}
|
}
|
||||||
|
11
app/Enums/BuildPackTypes.php
Normal file
11
app/Enums/BuildPackTypes.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum BuildPackTypes: string
|
||||||
|
{
|
||||||
|
case NIXPACKS = 'nixpacks';
|
||||||
|
case STATIC = 'static';
|
||||||
|
case DOCKERFILE = 'dockerfile';
|
||||||
|
case DOCKERCOMPOSE = 'dockercompose';
|
||||||
|
}
|
15
app/Enums/NewDatabaseTypes.php
Normal file
15
app/Enums/NewDatabaseTypes.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum NewDatabaseTypes: string
|
||||||
|
{
|
||||||
|
case POSTGRESQL = 'postgresql';
|
||||||
|
case MYSQL = 'mysql';
|
||||||
|
case MONGODB = 'mongodb';
|
||||||
|
case REDIS = 'redis';
|
||||||
|
case MARIADB = 'mariadb';
|
||||||
|
case KEYDB = 'keydb';
|
||||||
|
case DRAGONFLY = 'dragonfly';
|
||||||
|
case CLICKHOUSE = 'clickhouse';
|
||||||
|
}
|
22
app/Enums/NewResourceTypes.php
Normal file
22
app/Enums/NewResourceTypes.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum NewResourceTypes: string
|
||||||
|
{
|
||||||
|
case PUBLIC = 'public';
|
||||||
|
case PRIVATE_GH_APP = 'private-gh-app';
|
||||||
|
case PRIVATE_DEPLOY_KEY = 'private-deploy-key';
|
||||||
|
case DOCKERFILE = 'dockerfile';
|
||||||
|
case DOCKERCOMPOSE = 'dockercompose';
|
||||||
|
case DOCKER_IMAGE = 'docker-image';
|
||||||
|
case SERVICE = 'service';
|
||||||
|
case POSTGRESQL = 'postgresql';
|
||||||
|
case MYSQL = 'mysql';
|
||||||
|
case MONGODB = 'mongodb';
|
||||||
|
case REDIS = 'redis';
|
||||||
|
case MARIADB = 'mariadb';
|
||||||
|
case KEYDB = 'keydb';
|
||||||
|
case DRAGONFLY = 'dragonfly';
|
||||||
|
case CLICKHOUSE = 'clickhouse';
|
||||||
|
}
|
10
app/Enums/RedirectTypes.php
Normal file
10
app/Enums/RedirectTypes.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum RedirectTypes: string
|
||||||
|
{
|
||||||
|
case BOTH = 'both';
|
||||||
|
case WWW = 'www';
|
||||||
|
case NON_WWW = 'non-www';
|
||||||
|
}
|
@ -12,7 +12,7 @@ class DatabaseStatusChanged implements ShouldBroadcast
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
public $userId;
|
public ?string $userId = null;
|
||||||
|
|
||||||
public function __construct($userId = null)
|
public function __construct($userId = null)
|
||||||
{
|
{
|
||||||
@ -20,15 +20,19 @@ public function __construct($userId = null)
|
|||||||
$userId = auth()->user()->id ?? null;
|
$userId = auth()->user()->id ?? null;
|
||||||
}
|
}
|
||||||
if (is_null($userId)) {
|
if (is_null($userId)) {
|
||||||
throw new \Exception('User id is null');
|
return false;
|
||||||
}
|
}
|
||||||
$this->userId = $userId;
|
$this->userId = $userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function broadcastOn(): array
|
public function broadcastOn(): ?array
|
||||||
{
|
{
|
||||||
return [
|
if ($this->userId) {
|
||||||
new PrivateChannel("user.{$this->userId}"),
|
return [
|
||||||
];
|
new PrivateChannel("user.{$this->userId}"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,5 @@ class ProxyStarted
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
public function __construct(public $data)
|
public function __construct(public $data) {}
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ class ServiceStatusChanged implements ShouldBroadcast
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
public $userId;
|
public ?string $userId = null;
|
||||||
|
|
||||||
public function __construct($userId = null)
|
public function __construct($userId = null)
|
||||||
{
|
{
|
||||||
@ -20,15 +20,19 @@ public function __construct($userId = null)
|
|||||||
$userId = auth()->user()->id ?? null;
|
$userId = auth()->user()->id ?? null;
|
||||||
}
|
}
|
||||||
if (is_null($userId)) {
|
if (is_null($userId)) {
|
||||||
throw new \Exception('User id is null');
|
return false;
|
||||||
}
|
}
|
||||||
$this->userId = $userId;
|
$this->userId = $userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function broadcastOn(): array
|
public function broadcastOn(): ?array
|
||||||
{
|
{
|
||||||
return [
|
if ($this->userId) {
|
||||||
new PrivateChannel("user.{$this->userId}"),
|
return [
|
||||||
];
|
new PrivateChannel("user.{$this->userId}"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,4 @@
|
|||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
|
|
||||||
class ProcessException extends Exception
|
class ProcessException extends Exception {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
2539
app/Http/Controllers/Api/ApplicationsController.php
Normal file
2539
app/Http/Controllers/Api/ApplicationsController.php
Normal file
File diff suppressed because it is too large
Load Diff
1804
app/Http/Controllers/Api/DatabasesController.php
Normal file
1804
app/Http/Controllers/Api/DatabasesController.php
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,216 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
|
||||||
|
|
||||||
use App\Actions\Database\StartClickhouse;
|
|
||||||
use App\Actions\Database\StartDragonfly;
|
|
||||||
use App\Actions\Database\StartKeydb;
|
|
||||||
use App\Actions\Database\StartMariadb;
|
|
||||||
use App\Actions\Database\StartMongodb;
|
|
||||||
use App\Actions\Database\StartMysql;
|
|
||||||
use App\Actions\Database\StartPostgresql;
|
|
||||||
use App\Actions\Database\StartRedis;
|
|
||||||
use App\Actions\Service\StartService;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
|
||||||
use App\Models\Server;
|
|
||||||
use App\Models\Tag;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Visus\Cuid2\Cuid2;
|
|
||||||
|
|
||||||
class Deploy extends Controller
|
|
||||||
{
|
|
||||||
public function deployments(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$servers = Server::whereTeamId($teamId)->get();
|
|
||||||
$deployments_per_server = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('server_id', $servers->pluck('id'))->get([
|
|
||||||
'id',
|
|
||||||
'application_id',
|
|
||||||
'application_name',
|
|
||||||
'deployment_url',
|
|
||||||
'pull_request_id',
|
|
||||||
'server_name',
|
|
||||||
'server_id',
|
|
||||||
'status',
|
|
||||||
])->sortBy('id')->toArray();
|
|
||||||
|
|
||||||
return response()->json($deployments_per_server, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function deploy(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
$uuids = $request->query->get('uuid');
|
|
||||||
$tags = $request->query->get('tag');
|
|
||||||
$force = $request->query->get('force') ?? false;
|
|
||||||
|
|
||||||
if ($uuids && $tags) {
|
|
||||||
return response()->json(['error' => 'You can only use uuid or tag, not both.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
|
|
||||||
}
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
if ($tags) {
|
|
||||||
return $this->by_tags($tags, $teamId, $force);
|
|
||||||
} elseif ($uuids) {
|
|
||||||
return $this->by_uuids($uuids, $teamId, $force);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json(['error' => 'You must provide uuid or tag.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function by_uuids(string $uuid, int $teamId, bool $force = false)
|
|
||||||
{
|
|
||||||
$uuids = explode(',', $uuid);
|
|
||||||
$uuids = collect(array_filter($uuids));
|
|
||||||
|
|
||||||
if (count($uuids) === 0) {
|
|
||||||
return response()->json(['error' => 'No UUIDs provided.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
|
|
||||||
}
|
|
||||||
$deployments = collect();
|
|
||||||
$payload = collect();
|
|
||||||
foreach ($uuids as $uuid) {
|
|
||||||
$resource = getResourceByUuid($uuid, $teamId);
|
|
||||||
if ($resource) {
|
|
||||||
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
|
|
||||||
if ($deployment_uuid) {
|
|
||||||
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
|
||||||
} else {
|
|
||||||
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($deployments->count() > 0) {
|
|
||||||
$payload->put('deployments', $deployments->toArray());
|
|
||||||
|
|
||||||
return response()->json($payload->toArray(), 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json(['error' => 'No resources found.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function by_tags(string $tags, int $team_id, bool $force = false)
|
|
||||||
{
|
|
||||||
$tags = explode(',', $tags);
|
|
||||||
$tags = collect(array_filter($tags));
|
|
||||||
|
|
||||||
if (count($tags) === 0) {
|
|
||||||
return response()->json(['error' => 'No TAGs provided.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
|
|
||||||
}
|
|
||||||
$message = collect([]);
|
|
||||||
$deployments = collect();
|
|
||||||
$payload = collect();
|
|
||||||
foreach ($tags as $tag) {
|
|
||||||
$found_tag = Tag::where(['name' => $tag, 'team_id' => $team_id])->first();
|
|
||||||
if (! $found_tag) {
|
|
||||||
// $message->push("Tag {$tag} not found.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$applications = $found_tag->applications()->get();
|
|
||||||
$services = $found_tag->services()->get();
|
|
||||||
if ($applications->count() === 0 && $services->count() === 0) {
|
|
||||||
$message->push("No resources found for tag {$tag}.");
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
foreach ($applications as $resource) {
|
|
||||||
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
|
|
||||||
if ($deployment_uuid) {
|
|
||||||
$deployments->push(['resource_uuid' => $resource->uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
|
||||||
}
|
|
||||||
$message = $message->merge($return_message);
|
|
||||||
}
|
|
||||||
foreach ($services as $resource) {
|
|
||||||
['message' => $return_message] = $this->deploy_resource($resource, $force);
|
|
||||||
$message = $message->merge($return_message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ray($message);
|
|
||||||
if ($message->count() > 0) {
|
|
||||||
$payload->put('message', $message->toArray());
|
|
||||||
if ($deployments->count() > 0) {
|
|
||||||
$payload->put('details', $deployments->toArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json($payload->toArray(), 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json(['error' => 'No resources found with this tag.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function deploy_resource($resource, bool $force = false): array
|
|
||||||
{
|
|
||||||
$message = null;
|
|
||||||
$deployment_uuid = null;
|
|
||||||
if (gettype($resource) !== 'object') {
|
|
||||||
return ['message' => "Resource ($resource) not found.", 'deployment_uuid' => $deployment_uuid];
|
|
||||||
}
|
|
||||||
$type = $resource?->getMorphClass();
|
|
||||||
if ($type === 'App\Models\Application') {
|
|
||||||
$deployment_uuid = new Cuid2(7);
|
|
||||||
queue_application_deployment(
|
|
||||||
application: $resource,
|
|
||||||
deployment_uuid: $deployment_uuid,
|
|
||||||
force_rebuild: $force,
|
|
||||||
);
|
|
||||||
$message = "Application {$resource->name} deployment queued.";
|
|
||||||
} elseif ($type === 'App\Models\StandalonePostgresql') {
|
|
||||||
StartPostgresql::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message = "Database {$resource->name} started.";
|
|
||||||
} elseif ($type === 'App\Models\StandaloneRedis') {
|
|
||||||
StartRedis::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message = "Database {$resource->name} started.";
|
|
||||||
} elseif ($type === 'App\Models\StandaloneKeydb') {
|
|
||||||
StartKeydb::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message = "Database {$resource->name} started.";
|
|
||||||
} elseif ($type === 'App\Models\StandaloneDragonfly') {
|
|
||||||
StartDragonfly::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message = "Database {$resource->name} started.";
|
|
||||||
} elseif ($type === 'App\Models\StandaloneClickhouse') {
|
|
||||||
StartClickhouse::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message = "Database {$resource->name} started.";
|
|
||||||
} elseif ($type === 'App\Models\StandaloneMongodb') {
|
|
||||||
StartMongodb::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message = "Database {$resource->name} started.";
|
|
||||||
} elseif ($type === 'App\Models\StandaloneMysql') {
|
|
||||||
StartMysql::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message = "Database {$resource->name} started.";
|
|
||||||
} elseif ($type === 'App\Models\StandaloneMariadb') {
|
|
||||||
StartMariadb::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message = "Database {$resource->name} started.";
|
|
||||||
} elseif ($type === 'App\Models\Service') {
|
|
||||||
StartService::run($resource);
|
|
||||||
$message = "Service {$resource->name} started. It could take a while, be patient.";
|
|
||||||
}
|
|
||||||
|
|
||||||
return ['message' => $message, 'deployment_uuid' => $deployment_uuid];
|
|
||||||
}
|
|
||||||
}
|
|
317
app/Http/Controllers/Api/DeployController.php
Normal file
317
app/Http/Controllers/Api/DeployController.php
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartDatabase;
|
||||||
|
use App\Actions\Service\StartService;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\Tag;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
|
class DeployController extends Controller
|
||||||
|
{
|
||||||
|
private function removeSensitiveData($deployment)
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
if ($token->can('view:sensitive')) {
|
||||||
|
return serializeApiResponse($deployment);
|
||||||
|
}
|
||||||
|
|
||||||
|
$deployment->makeHidden([
|
||||||
|
'logs',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return serializeApiResponse($deployment);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'List',
|
||||||
|
description: 'List currently running deployments',
|
||||||
|
path: '/deployments',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Deployments'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get all currently running deployments.',
|
||||||
|
content: [
|
||||||
|
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/ApplicationDeploymentQueue'),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function deployments(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$servers = Server::whereTeamId($teamId)->get();
|
||||||
|
$deployments_per_server = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('server_id', $servers->pluck('id'))->get()->sortBy('id');
|
||||||
|
$deployments_per_server = $deployments_per_server->map(function ($deployment) {
|
||||||
|
return $this->removeSensitiveData($deployment);
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json($deployments_per_server);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Get',
|
||||||
|
description: 'Get deployment by UUID.',
|
||||||
|
path: '/deployments/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Deployments'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Deployment Uuid', schema: new OA\Schema(type: 'integer')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get deployment by UUID.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
ref: '#/components/schemas/ApplicationDeploymentQueue',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function deployment_by_uuid(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$uuid = $request->route('uuid');
|
||||||
|
if (! $uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 400);
|
||||||
|
}
|
||||||
|
$deployment = ApplicationDeploymentQueue::where('deployment_uuid', $uuid)->first();
|
||||||
|
if (! $deployment) {
|
||||||
|
return response()->json(['message' => 'Deployment not found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($this->removeSensitiveData($deployment));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Deploy',
|
||||||
|
description: 'Deploy by tag or uuid. `Post` request also accepted.',
|
||||||
|
path: '/deploy',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Deployments'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'tag', in: 'query', description: 'Tag name(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')),
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'query', description: 'Resource UUID(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')),
|
||||||
|
new OA\Parameter(name: 'force', in: 'query', description: 'Force rebuild (without cache)', schema: new OA\Schema(type: 'boolean')),
|
||||||
|
],
|
||||||
|
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get deployment(s) Uuid\'s',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'deployments' => new OA\Property(
|
||||||
|
property: 'deployments',
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string'],
|
||||||
|
'resource_uuid' => ['type' => 'string'],
|
||||||
|
'deployment_uuid' => ['type' => 'string'],
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function deploy(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
$uuids = $request->query->get('uuid');
|
||||||
|
$tags = $request->query->get('tag');
|
||||||
|
$force = $request->query->get('force') ?? false;
|
||||||
|
|
||||||
|
if ($uuids && $tags) {
|
||||||
|
return response()->json(['message' => 'You can only use uuid or tag, not both.'], 400);
|
||||||
|
}
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
if ($tags) {
|
||||||
|
return $this->by_tags($tags, $teamId, $force);
|
||||||
|
} elseif ($uuids) {
|
||||||
|
return $this->by_uuids($uuids, $teamId, $force);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'You must provide uuid or tag.'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function by_uuids(string $uuid, int $teamId, bool $force = false)
|
||||||
|
{
|
||||||
|
$uuids = explode(',', $uuid);
|
||||||
|
$uuids = collect(array_filter($uuids));
|
||||||
|
|
||||||
|
if (count($uuids) === 0) {
|
||||||
|
return response()->json(['message' => 'No UUIDs provided.'], 400);
|
||||||
|
}
|
||||||
|
$deployments = collect();
|
||||||
|
$payload = collect();
|
||||||
|
foreach ($uuids as $uuid) {
|
||||||
|
$resource = getResourceByUuid($uuid, $teamId);
|
||||||
|
if ($resource) {
|
||||||
|
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
|
||||||
|
if ($deployment_uuid) {
|
||||||
|
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
||||||
|
} else {
|
||||||
|
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($deployments->count() > 0) {
|
||||||
|
$payload->put('deployments', $deployments->toArray());
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse($payload->toArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'No resources found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function by_tags(string $tags, int $team_id, bool $force = false)
|
||||||
|
{
|
||||||
|
$tags = explode(',', $tags);
|
||||||
|
$tags = collect(array_filter($tags));
|
||||||
|
|
||||||
|
if (count($tags) === 0) {
|
||||||
|
return response()->json(['message' => 'No TAGs provided.'], 400);
|
||||||
|
}
|
||||||
|
$message = collect([]);
|
||||||
|
$deployments = collect();
|
||||||
|
$payload = collect();
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
$found_tag = Tag::where(['name' => $tag, 'team_id' => $team_id])->first();
|
||||||
|
if (! $found_tag) {
|
||||||
|
// $message->push("Tag {$tag} not found.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$applications = $found_tag->applications()->get();
|
||||||
|
$services = $found_tag->services()->get();
|
||||||
|
if ($applications->count() === 0 && $services->count() === 0) {
|
||||||
|
$message->push("No resources found for tag {$tag}.");
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ($applications as $resource) {
|
||||||
|
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
|
||||||
|
if ($deployment_uuid) {
|
||||||
|
$deployments->push(['resource_uuid' => $resource->uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
||||||
|
}
|
||||||
|
$message = $message->merge($return_message);
|
||||||
|
}
|
||||||
|
foreach ($services as $resource) {
|
||||||
|
['message' => $return_message] = $this->deploy_resource($resource, $force);
|
||||||
|
$message = $message->merge($return_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($message->count() > 0) {
|
||||||
|
$payload->put('message', $message->toArray());
|
||||||
|
if ($deployments->count() > 0) {
|
||||||
|
$payload->put('details', $deployments->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse($payload->toArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'No resources found with this tag.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deploy_resource($resource, bool $force = false): array
|
||||||
|
{
|
||||||
|
$message = null;
|
||||||
|
$deployment_uuid = null;
|
||||||
|
if (gettype($resource) !== 'object') {
|
||||||
|
return ['message' => "Resource ($resource) not found.", 'deployment_uuid' => $deployment_uuid];
|
||||||
|
}
|
||||||
|
switch ($resource?->getMorphClass()) {
|
||||||
|
case 'App\Models\Application':
|
||||||
|
$deployment_uuid = new Cuid2(7);
|
||||||
|
queue_application_deployment(
|
||||||
|
application: $resource,
|
||||||
|
deployment_uuid: $deployment_uuid,
|
||||||
|
force_rebuild: $force,
|
||||||
|
);
|
||||||
|
$message = "Application {$resource->name} deployment queued.";
|
||||||
|
break;
|
||||||
|
case 'App\Models\Service':
|
||||||
|
StartService::run($resource);
|
||||||
|
$message = "Service {$resource->name} started. It could take a while, be patient.";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Database resource
|
||||||
|
StartDatabase::dispatch($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message = "Database {$resource->name} started.";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['message' => $message, 'deployment_uuid' => $deployment_uuid];
|
||||||
|
}
|
||||||
|
}
|
@ -1,104 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\InstanceSettings;
|
|
||||||
use App\Models\Project as ModelsProject;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class Domains extends Controller
|
|
||||||
{
|
|
||||||
public function domains(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$projects = ModelsProject::where('team_id', $teamId)->get();
|
|
||||||
$domains = collect();
|
|
||||||
$applications = $projects->pluck('applications')->flatten();
|
|
||||||
$settings = InstanceSettings::get();
|
|
||||||
if ($applications->count() > 0) {
|
|
||||||
foreach ($applications as $application) {
|
|
||||||
$ip = $application->destination->server->ip;
|
|
||||||
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
|
|
||||||
return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', '');
|
|
||||||
});
|
|
||||||
if ($ip === 'host.docker.internal') {
|
|
||||||
if ($settings->public_ipv4) {
|
|
||||||
$domains->push([
|
|
||||||
'domain' => $fqdn,
|
|
||||||
'ip' => $settings->public_ipv4,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
if ($settings->public_ipv6) {
|
|
||||||
$domains->push([
|
|
||||||
'domain' => $fqdn,
|
|
||||||
'ip' => $settings->public_ipv6,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
|
|
||||||
$domains->push([
|
|
||||||
'domain' => $fqdn,
|
|
||||||
'ip' => $ip,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$domains->push([
|
|
||||||
'domain' => $fqdn,
|
|
||||||
'ip' => $ip,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$services = $projects->pluck('services')->flatten();
|
|
||||||
if ($services->count() > 0) {
|
|
||||||
foreach ($services as $service) {
|
|
||||||
$service_applications = $service->applications;
|
|
||||||
if ($service_applications->count() > 0) {
|
|
||||||
foreach ($service_applications as $application) {
|
|
||||||
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
|
|
||||||
return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', '');
|
|
||||||
});
|
|
||||||
if ($ip === 'host.docker.internal') {
|
|
||||||
if ($settings->public_ipv4) {
|
|
||||||
$domains->push([
|
|
||||||
'domain' => $fqdn,
|
|
||||||
'ip' => $settings->public_ipv4,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
if ($settings->public_ipv6) {
|
|
||||||
$domains->push([
|
|
||||||
'domain' => $fqdn,
|
|
||||||
'ip' => $settings->public_ipv6,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
|
|
||||||
$domains->push([
|
|
||||||
'domain' => $fqdn,
|
|
||||||
'ip' => $ip,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$domains->push([
|
|
||||||
'domain' => $fqdn,
|
|
||||||
'ip' => $ip,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$domains = $domains->groupBy('ip')->map(function ($domain) {
|
|
||||||
return $domain->pluck('domain')->flatten();
|
|
||||||
})->map(function ($domain, $ip) {
|
|
||||||
return [
|
|
||||||
'ip' => $ip,
|
|
||||||
'domains' => $domain,
|
|
||||||
];
|
|
||||||
})->values();
|
|
||||||
|
|
||||||
return response()->json($domains);
|
|
||||||
}
|
|
||||||
}
|
|
35
app/Http/Controllers/Api/EnvironmentVariablesController.php
Normal file
35
app/Http/Controllers/Api/EnvironmentVariablesController.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\EnvironmentVariable;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class EnvironmentVariablesController extends Controller
|
||||||
|
{
|
||||||
|
public function delete_env_by_uuid(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$env = EnvironmentVariable::where('uuid', $request->env_uuid)->first();
|
||||||
|
if (! $env) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Environment variable not found.',
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
$found_app = $env->resource()->whereRelation('environment.project.team', 'id', $teamId)->first();
|
||||||
|
if (! $found_app) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Environment variable not found.',
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
$env->delete();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Environment variable deleted.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
51
app/Http/Controllers/Api/OpenApi.php
Normal file
51
app/Http/Controllers/Api/OpenApi.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
#[OA\Info(title: 'Coolify', version: '0.1')]
|
||||||
|
#[OA\Server(url: 'https://app.coolify.io/api/v1')]
|
||||||
|
#[OA\SecurityScheme(
|
||||||
|
type: 'http',
|
||||||
|
scheme: 'bearer',
|
||||||
|
securityScheme: 'bearerAuth',
|
||||||
|
description: 'Go to `Keys & Tokens` / `API tokens` and create a new token. Use the token as the bearer token.')]
|
||||||
|
#[OA\Components(
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
description: 'Invalid token.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'message', type: 'string', example: 'Invalid token.'),
|
||||||
|
]
|
||||||
|
)),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
description: 'Unauthenticated.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'message', type: 'string', example: 'Unauthenticated.'),
|
||||||
|
]
|
||||||
|
)),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
description: 'Resource not found.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'message', type: 'string', example: 'Resource not found.'),
|
||||||
|
]
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
)]
|
||||||
|
class OpenApi
|
||||||
|
{
|
||||||
|
// This class is used to generate OpenAPI documentation
|
||||||
|
// for the Coolify API. It is not a controller and does
|
||||||
|
// not contain any routes. It is used to define the
|
||||||
|
// OpenAPI metadata and security scheme for the API.
|
||||||
|
}
|
184
app/Http/Controllers/Api/OtherController.php
Normal file
184
app/Http/Controllers/Api/OtherController.php
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
class OtherController extends Controller
|
||||||
|
{
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Version',
|
||||||
|
description: 'Get Coolify version.',
|
||||||
|
path: '/version',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Returns the version of the application',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'string',
|
||||||
|
example: 'v4.0.0',
|
||||||
|
)),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function version(Request $request)
|
||||||
|
{
|
||||||
|
return response(config('version'));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Enable API',
|
||||||
|
description: 'Enable API (only with root permissions).',
|
||||||
|
path: '/enable',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Enable API.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'message', type: 'string', example: 'API enabled.'),
|
||||||
|
]
|
||||||
|
)),
|
||||||
|
new OA\Response(
|
||||||
|
response: 403,
|
||||||
|
description: 'You are not allowed to enable the API.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'message', type: 'string', example: 'You are not allowed to enable the API.'),
|
||||||
|
]
|
||||||
|
)),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function enable_api(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
if ($teamId !== '0') {
|
||||||
|
return response()->json(['message' => 'You are not allowed to enable the API.'], 403);
|
||||||
|
}
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
$settings->update(['is_api_enabled' => true]);
|
||||||
|
|
||||||
|
return response()->json(['message' => 'API enabled.'], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Disable API',
|
||||||
|
description: 'Disable API (only with root permissions).',
|
||||||
|
path: '/disable',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Disable API.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'message', type: 'string', example: 'API disabled.'),
|
||||||
|
]
|
||||||
|
)),
|
||||||
|
new OA\Response(
|
||||||
|
response: 403,
|
||||||
|
description: 'You are not allowed to disable the API.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'message', type: 'string', example: 'You are not allowed to disable the API.'),
|
||||||
|
]
|
||||||
|
)),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function disable_api(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
if ($teamId !== '0') {
|
||||||
|
return response()->json(['message' => 'You are not allowed to disable the API.'], 403);
|
||||||
|
}
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
$settings->update(['is_api_enabled' => false]);
|
||||||
|
|
||||||
|
return response()->json(['message' => 'API disabled.'], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function feedback(Request $request)
|
||||||
|
{
|
||||||
|
$content = $request->input('content');
|
||||||
|
$webhook_url = config('coolify.feedback_discord_webhook');
|
||||||
|
if ($webhook_url) {
|
||||||
|
Http::post($webhook_url, [
|
||||||
|
'content' => $content,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Feedback sent.'], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Healthcheck',
|
||||||
|
description: 'Healthcheck endpoint.',
|
||||||
|
path: '/healthcheck',
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Healthcheck endpoint.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'string',
|
||||||
|
example: 'OK',
|
||||||
|
)),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function healthcheck(Request $request)
|
||||||
|
{
|
||||||
|
return 'OK';
|
||||||
|
}
|
||||||
|
}
|
@ -1,44 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\Project as ModelsProject;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class Project extends Controller
|
|
||||||
{
|
|
||||||
public function projects(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$projects = ModelsProject::whereTeamId($teamId)->select('id', 'name', 'uuid')->get();
|
|
||||||
|
|
||||||
return response()->json($projects);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function project_by_uuid(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first()->load(['environments']);
|
|
||||||
|
|
||||||
return response()->json($project);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function environment_details(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
|
||||||
$environment = $project->environments()->whereName(request()->environment_name)->first()->load(['applications', 'postgresqls', 'redis', 'mongodbs', 'mysqls', 'mariadbs', 'services']);
|
|
||||||
|
|
||||||
return response()->json($environment);
|
|
||||||
}
|
|
||||||
}
|
|
147
app/Http/Controllers/Api/ProjectController.php
Normal file
147
app/Http/Controllers/Api/ProjectController.php
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Project;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
class ProjectController extends Controller
|
||||||
|
{
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'List',
|
||||||
|
description: 'list projects.',
|
||||||
|
path: '/projects',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Projects'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get all projects.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/Project')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function projects(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$projects = Project::whereTeamId($teamId)->select('id', 'name', 'uuid')->get();
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse($projects),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Get',
|
||||||
|
description: 'Get project by Uuid.',
|
||||||
|
path: '/projects/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Projects'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'integer')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Project details',
|
||||||
|
content: new OA\JsonContent(ref: '#/components/schemas/Project')),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
description: 'Project not found.',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function project_by_uuid(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$project = Project::whereTeamId($teamId)->whereUuid(request()->uuid)->first()->load(['environments']);
|
||||||
|
if (! $project) {
|
||||||
|
return response()->json(['message' => 'Project not found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
serializeApiResponse($project),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Environment',
|
||||||
|
description: 'Get environment by name.',
|
||||||
|
path: '/projects/{uuid}/{environment_name}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Projects'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'integer')),
|
||||||
|
new OA\Parameter(name: 'environment_name', in: 'path', required: true, description: 'Environment name', schema: new OA\Schema(type: 'string')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Project details',
|
||||||
|
content: new OA\JsonContent(ref: '#/components/schemas/Environment')),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function environment_details(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$project = Project::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
||||||
|
$environment = $project->environments()->whereName(request()->environment_name)->first();
|
||||||
|
if (! $environment) {
|
||||||
|
return response()->json(['message' => 'Environment not found.'], 404);
|
||||||
|
}
|
||||||
|
$environment = $environment->load(['applications', 'postgresqls', 'redis', 'mongodbs', 'mysqls', 'mariadbs', 'services']);
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse($environment));
|
||||||
|
}
|
||||||
|
}
|
@ -5,14 +5,42 @@
|
|||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
class Resources extends Controller
|
class ResourcesController extends Controller
|
||||||
{
|
{
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'List',
|
||||||
|
description: 'Get all resources.',
|
||||||
|
path: '/resources',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Resources'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get all resources',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'string',
|
||||||
|
example: 'Content is very complex. Will be implemented later.',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
public function resources(Request $request)
|
public function resources(Request $request)
|
||||||
{
|
{
|
||||||
$teamId = get_team_id_from_token();
|
$teamId = getTeamIdFromToken();
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
return invalid_token();
|
return invalidTokenResponse();
|
||||||
}
|
}
|
||||||
$projects = Project::where('team_id', $teamId)->get();
|
$projects = Project::where('team_id', $teamId)->get();
|
||||||
$resources = collect();
|
$resources = collect();
|
||||||
@ -34,6 +62,6 @@ public function resources(Request $request)
|
|||||||
return $payload;
|
return $payload;
|
||||||
});
|
});
|
||||||
|
|
||||||
return response()->json($resources);
|
return response()->json(serializeApiResponse($resources));
|
||||||
}
|
}
|
||||||
}
|
}
|
372
app/Http/Controllers/Api/SecurityController.php
Normal file
372
app/Http/Controllers/Api/SecurityController.php
Normal file
@ -0,0 +1,372 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\PrivateKey;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
class SecurityController extends Controller
|
||||||
|
{
|
||||||
|
private function removeSensitiveData($team)
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
if ($token->can('view:sensitive')) {
|
||||||
|
return serializeApiResponse($team);
|
||||||
|
}
|
||||||
|
$team->makeHidden([
|
||||||
|
'private_key',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return serializeApiResponse($team);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'List',
|
||||||
|
description: 'List all private keys.',
|
||||||
|
path: '/security/keys',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Private Keys'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get all private keys.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/PrivateKey')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function keys(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$keys = PrivateKey::where('team_id', $teamId)->get();
|
||||||
|
|
||||||
|
return response()->json($this->removeSensitiveData($keys));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Get',
|
||||||
|
description: 'Get key by UUID.',
|
||||||
|
path: '/security/keys/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Private Keys'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key Uuid', schema: new OA\Schema(type: 'integer')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get all private keys.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/PrivateKey')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
description: 'Private Key not found.',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function key_by_uuid(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = PrivateKey::where('team_id', $teamId)->where('uuid', $request->uuid)->first();
|
||||||
|
|
||||||
|
if (is_null($key)) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Private Key not found.',
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($this->removeSensitiveData($key));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Post(
|
||||||
|
summary: 'Create',
|
||||||
|
description: 'Create a new private key.',
|
||||||
|
path: '/security/keys',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Private Keys'],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
required: true,
|
||||||
|
content: [
|
||||||
|
'application/json' => new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
required: ['private_key'],
|
||||||
|
properties: [
|
||||||
|
'name' => ['type' => 'string'],
|
||||||
|
'description' => ['type' => 'string'],
|
||||||
|
'private_key' => ['type' => 'string'],
|
||||||
|
],
|
||||||
|
additionalProperties: false,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 201,
|
||||||
|
description: 'The created private key\'s UUID.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'uuid' => ['type' => 'string'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function create_key(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$return = validateIncomingRequest($request);
|
||||||
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
$validator = customApiValidator($request->all(), [
|
||||||
|
'name' => 'string|max:255',
|
||||||
|
'description' => 'string|max:255',
|
||||||
|
'private_key' => 'required|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
$errors = $validator->errors();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => $errors,
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
if (! $request->name) {
|
||||||
|
$request->offsetSet('name', generate_random_name());
|
||||||
|
}
|
||||||
|
if (! $request->description) {
|
||||||
|
$request->offsetSet('description', 'Created by Coolify via API');
|
||||||
|
}
|
||||||
|
$key = PrivateKey::create([
|
||||||
|
'team_id' => $teamId,
|
||||||
|
'name' => $request->name,
|
||||||
|
'description' => $request->description,
|
||||||
|
'private_key' => $request->private_key,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse([
|
||||||
|
'uuid' => $key->uuid,
|
||||||
|
]))->setStatusCode(201);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Patch(
|
||||||
|
summary: 'Update',
|
||||||
|
description: 'Update a private key.',
|
||||||
|
path: '/security/keys',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Private Keys'],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
required: true,
|
||||||
|
content: [
|
||||||
|
'application/json' => new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
required: ['private_key'],
|
||||||
|
properties: [
|
||||||
|
'name' => ['type' => 'string'],
|
||||||
|
'description' => ['type' => 'string'],
|
||||||
|
'private_key' => ['type' => 'string'],
|
||||||
|
],
|
||||||
|
additionalProperties: false,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 201,
|
||||||
|
description: 'The updated private key\'s UUID.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'uuid' => ['type' => 'string'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function update_key(Request $request)
|
||||||
|
{
|
||||||
|
$allowedFields = ['name', 'description', 'private_key'];
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$return = validateIncomingRequest($request);
|
||||||
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$validator = customApiValidator($request->all(), [
|
||||||
|
'name' => 'string|max:255',
|
||||||
|
'description' => 'string|max:255',
|
||||||
|
'private_key' => 'required|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||||
|
if ($validator->fails() || ! empty($extraFields)) {
|
||||||
|
$errors = $validator->errors();
|
||||||
|
if (! empty($extraFields)) {
|
||||||
|
foreach ($extraFields as $field) {
|
||||||
|
$errors->add($field, 'This field is not allowed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => $errors,
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
$foundKey = PrivateKey::where('team_id', $teamId)->where('uuid', $request->uuid)->first();
|
||||||
|
if (is_null($foundKey)) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Private Key not found.',
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
$foundKey->update($request->all());
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse([
|
||||||
|
'uuid' => $foundKey->uuid,
|
||||||
|
]))->setStatusCode(201);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Delete(
|
||||||
|
summary: 'Delete',
|
||||||
|
description: 'Delete a private key.',
|
||||||
|
path: '/security/keys/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Private Keys'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key Uuid', schema: new OA\Schema(type: 'integer')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Private Key deleted.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string', 'example' => 'Private Key deleted.'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
description: 'Private Key not found.',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function delete_key(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
if (! $request->uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = PrivateKey::where('team_id', $teamId)->where('uuid', $request->uuid)->first();
|
||||||
|
if (is_null($key)) {
|
||||||
|
return response()->json(['message' => 'Private Key not found.'], 404);
|
||||||
|
}
|
||||||
|
$key->forceDelete();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Private Key deleted.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -1,62 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\Server as ModelsServer;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class Server extends Controller
|
|
||||||
{
|
|
||||||
public function servers(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$servers = ModelsServer::whereTeamId($teamId)->select('id', 'name', 'uuid', 'ip', 'user', 'port')->get()->load(['settings'])->map(function ($server) {
|
|
||||||
$server['is_reachable'] = $server->settings->is_reachable;
|
|
||||||
$server['is_usable'] = $server->settings->is_usable;
|
|
||||||
|
|
||||||
return $server;
|
|
||||||
});
|
|
||||||
|
|
||||||
return response()->json($servers);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function server_by_uuid(Request $request)
|
|
||||||
{
|
|
||||||
$with_resources = $request->query('resources');
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$server = ModelsServer::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
|
||||||
if (is_null($server)) {
|
|
||||||
return response()->json(['error' => 'Server not found.'], 404);
|
|
||||||
}
|
|
||||||
if ($with_resources) {
|
|
||||||
$server['resources'] = $server->definedResources()->map(function ($resource) {
|
|
||||||
$payload = [
|
|
||||||
'id' => $resource->id,
|
|
||||||
'uuid' => $resource->uuid,
|
|
||||||
'name' => $resource->name,
|
|
||||||
'type' => $resource->type(),
|
|
||||||
'created_at' => $resource->created_at,
|
|
||||||
'updated_at' => $resource->updated_at,
|
|
||||||
];
|
|
||||||
if ($resource->type() === 'service') {
|
|
||||||
$payload['status'] = $resource->status();
|
|
||||||
} else {
|
|
||||||
$payload['status'] = $resource->status;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $payload;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
$server->load(['settings']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json($server);
|
|
||||||
}
|
|
||||||
}
|
|
396
app/Http/Controllers/Api/ServersController.php
Normal file
396
app/Http/Controllers/Api/ServersController.php
Normal file
@ -0,0 +1,396 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\Server as ModelsServer;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
use Stringable;
|
||||||
|
|
||||||
|
class ServersController extends Controller
|
||||||
|
{
|
||||||
|
private function removeSensitiveDataFromSettings($settings)
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
if ($token->can('view:sensitive')) {
|
||||||
|
return serializeApiResponse($settings);
|
||||||
|
}
|
||||||
|
$settings = $settings->makeHidden([
|
||||||
|
'metrics_token',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return serializeApiResponse($settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function removeSensitiveData($server)
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
$server->makeHidden([
|
||||||
|
'id',
|
||||||
|
]);
|
||||||
|
if ($token->can('view:sensitive')) {
|
||||||
|
return serializeApiResponse($server);
|
||||||
|
}
|
||||||
|
|
||||||
|
return serializeApiResponse($server);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'List',
|
||||||
|
description: 'List all servers.',
|
||||||
|
path: '/servers',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Servers'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get all servers.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/Server')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function servers(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$servers = ModelsServer::whereTeamId($teamId)->select('id', 'name', 'uuid', 'ip', 'user', 'port')->get()->load(['settings'])->map(function ($server) {
|
||||||
|
$server['is_reachable'] = $server->settings->is_reachable;
|
||||||
|
$server['is_usable'] = $server->settings->is_usable;
|
||||||
|
|
||||||
|
return $server;
|
||||||
|
});
|
||||||
|
$servers = $servers->map(function ($server) {
|
||||||
|
$settings = $this->removeSensitiveDataFromSettings($server->settings);
|
||||||
|
$server = $this->removeSensitiveData($server);
|
||||||
|
data_set($server, 'settings', $settings);
|
||||||
|
|
||||||
|
return $server;
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json($servers);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Get',
|
||||||
|
description: 'Get server by UUID.',
|
||||||
|
path: '/servers/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Servers'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'integer')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get server by UUID',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
ref: '#/components/schemas/Server'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function server_by_uuid(Request $request)
|
||||||
|
{
|
||||||
|
$with_resources = $request->query('resources');
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$server = ModelsServer::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
||||||
|
if (is_null($server)) {
|
||||||
|
return response()->json(['message' => 'Server not found.'], 404);
|
||||||
|
}
|
||||||
|
if ($with_resources) {
|
||||||
|
$server['resources'] = $server->definedResources()->map(function ($resource) {
|
||||||
|
$payload = [
|
||||||
|
'id' => $resource->id,
|
||||||
|
'uuid' => $resource->uuid,
|
||||||
|
'name' => $resource->name,
|
||||||
|
'type' => $resource->type(),
|
||||||
|
'created_at' => $resource->created_at,
|
||||||
|
'updated_at' => $resource->updated_at,
|
||||||
|
];
|
||||||
|
if ($resource->type() === 'service') {
|
||||||
|
$payload['status'] = $resource->status();
|
||||||
|
} else {
|
||||||
|
$payload['status'] = $resource->status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $payload;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$server->load(['settings']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$settings = $this->removeSensitiveDataFromSettings($server->settings);
|
||||||
|
$server = $this->removeSensitiveData($server);
|
||||||
|
data_set($server, 'settings', $settings);
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse($server));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Resources',
|
||||||
|
description: 'Get resources by server.',
|
||||||
|
path: '/servers/{uuid}/resources',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Servers'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'integer')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get resources by server',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'id' => ['type' => 'integer'],
|
||||||
|
'uuid' => ['type' => 'string'],
|
||||||
|
'name' => ['type' => 'string'],
|
||||||
|
'type' => ['type' => 'string'],
|
||||||
|
'created_at' => ['type' => 'string'],
|
||||||
|
'updated_at' => ['type' => 'string'],
|
||||||
|
'status' => ['type' => 'string'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function resources_by_server(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$server = ModelsServer::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
||||||
|
if (is_null($server)) {
|
||||||
|
return response()->json(['message' => 'Server not found.'], 404);
|
||||||
|
}
|
||||||
|
$server['resources'] = $server->definedResources()->map(function ($resource) {
|
||||||
|
$payload = [
|
||||||
|
'id' => $resource->id,
|
||||||
|
'uuid' => $resource->uuid,
|
||||||
|
'name' => $resource->name,
|
||||||
|
'type' => $resource->type(),
|
||||||
|
'created_at' => $resource->created_at,
|
||||||
|
'updated_at' => $resource->updated_at,
|
||||||
|
];
|
||||||
|
if ($resource->type() === 'service') {
|
||||||
|
$payload['status'] = $resource->status();
|
||||||
|
} else {
|
||||||
|
$payload['status'] = $resource->status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $payload;
|
||||||
|
});
|
||||||
|
$server = $this->removeSensitiveData($server);
|
||||||
|
ray($server);
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse(data_get($server, 'resources')));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Domains',
|
||||||
|
description: 'Get domains by server.',
|
||||||
|
path: '/servers/{uuid}/domains',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Servers'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'integer')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get domains by server',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'ip' => ['type' => 'string'],
|
||||||
|
'domains' => ['type' => 'array', 'items' => ['type' => 'string']],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function domains_by_server(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$uuid = $request->get('uuid');
|
||||||
|
if ($uuid) {
|
||||||
|
$domains = Application::getDomainsByUuid($uuid);
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse($domains));
|
||||||
|
}
|
||||||
|
$projects = Project::where('team_id', $teamId)->get();
|
||||||
|
$domains = collect();
|
||||||
|
$applications = $projects->pluck('applications')->flatten();
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
if ($applications->count() > 0) {
|
||||||
|
foreach ($applications as $application) {
|
||||||
|
$ip = $application->destination->server->ip;
|
||||||
|
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
|
||||||
|
$f = str($fqdn)->replace('http://', '')->replace('https://', '')->explode('/');
|
||||||
|
|
||||||
|
return str(str($f[0])->explode(':')[0]);
|
||||||
|
})->filter(function (Stringable $fqdn) {
|
||||||
|
return $fqdn->isNotEmpty();
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($ip === 'host.docker.internal') {
|
||||||
|
if ($settings->public_ipv4) {
|
||||||
|
$domains->push([
|
||||||
|
'domain' => $fqdn,
|
||||||
|
'ip' => $settings->public_ipv4,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($settings->public_ipv6) {
|
||||||
|
$domains->push([
|
||||||
|
'domain' => $fqdn,
|
||||||
|
'ip' => $settings->public_ipv6,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
|
||||||
|
$domains->push([
|
||||||
|
'domain' => $fqdn,
|
||||||
|
'ip' => $ip,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$domains->push([
|
||||||
|
'domain' => $fqdn,
|
||||||
|
'ip' => $ip,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$services = $projects->pluck('services')->flatten();
|
||||||
|
if ($services->count() > 0) {
|
||||||
|
foreach ($services as $service) {
|
||||||
|
$service_applications = $service->applications;
|
||||||
|
if ($service_applications->count() > 0) {
|
||||||
|
foreach ($service_applications as $application) {
|
||||||
|
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
|
||||||
|
$f = str($fqdn)->replace('http://', '')->replace('https://', '')->explode('/');
|
||||||
|
|
||||||
|
return str(str($f[0])->explode(':')[0]);
|
||||||
|
})->filter(function (Stringable $fqdn) {
|
||||||
|
return $fqdn->isNotEmpty();
|
||||||
|
});
|
||||||
|
if ($ip === 'host.docker.internal') {
|
||||||
|
if ($settings->public_ipv4) {
|
||||||
|
$domains->push([
|
||||||
|
'domain' => $fqdn,
|
||||||
|
'ip' => $settings->public_ipv4,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($settings->public_ipv6) {
|
||||||
|
$domains->push([
|
||||||
|
'domain' => $fqdn,
|
||||||
|
'ip' => $settings->public_ipv6,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
|
||||||
|
$domains->push([
|
||||||
|
'domain' => $fqdn,
|
||||||
|
'ip' => $ip,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$domains->push([
|
||||||
|
'domain' => $fqdn,
|
||||||
|
'ip' => $ip,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$domains = $domains->groupBy('ip')->map(function ($domain) {
|
||||||
|
return $domain->pluck('domain')->flatten();
|
||||||
|
})->map(function ($domain, $ip) {
|
||||||
|
return [
|
||||||
|
'ip' => $ip,
|
||||||
|
'domains' => $domain,
|
||||||
|
];
|
||||||
|
})->values();
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse($domains));
|
||||||
|
}
|
||||||
|
}
|
702
app/Http/Controllers/Api/ServicesController.php
Normal file
702
app/Http/Controllers/Api/ServicesController.php
Normal file
@ -0,0 +1,702 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Actions\Service\RestartService;
|
||||||
|
use App\Actions\Service\StartService;
|
||||||
|
use App\Actions\Service\StopService;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Jobs\DeleteResourceJob;
|
||||||
|
use App\Models\EnvironmentVariable;
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\Service;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
class ServicesController extends Controller
|
||||||
|
{
|
||||||
|
private function removeSensitiveData($service)
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
$service->makeHidden([
|
||||||
|
'id',
|
||||||
|
]);
|
||||||
|
if ($token->can('view:sensitive')) {
|
||||||
|
return serializeApiResponse($service);
|
||||||
|
}
|
||||||
|
|
||||||
|
$service->makeHidden([
|
||||||
|
'docker_compose_raw',
|
||||||
|
'docker_compose',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return serializeApiResponse($service);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'List',
|
||||||
|
description: 'List all services.',
|
||||||
|
path: '/services',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Services'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get all services',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/Service')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function services(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$projects = Project::where('team_id', $teamId)->get();
|
||||||
|
$services = collect();
|
||||||
|
foreach ($projects as $project) {
|
||||||
|
$services->push($project->services()->get());
|
||||||
|
}
|
||||||
|
foreach ($services as $service) {
|
||||||
|
$service = $this->removeSensitiveData($service);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($services->flatten());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Post(
|
||||||
|
summary: 'Create',
|
||||||
|
description: 'Create a one-click service',
|
||||||
|
path: '/services',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Services'],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
required: true,
|
||||||
|
content: new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
required: ['server_uuid', 'project_uuid', 'environment_name', 'type'],
|
||||||
|
properties: [
|
||||||
|
'type' => [
|
||||||
|
'description' => 'The one-click service type',
|
||||||
|
'type' => 'string',
|
||||||
|
'enum' => [
|
||||||
|
'activepieces',
|
||||||
|
'appsmith',
|
||||||
|
'appwrite',
|
||||||
|
'authentik',
|
||||||
|
'babybuddy',
|
||||||
|
'budge',
|
||||||
|
'changedetection',
|
||||||
|
'chatwoot',
|
||||||
|
'classicpress-with-mariadb',
|
||||||
|
'classicpress-with-mysql',
|
||||||
|
'classicpress-without-database',
|
||||||
|
'cloudflared',
|
||||||
|
'code-server',
|
||||||
|
'dashboard',
|
||||||
|
'directus',
|
||||||
|
'directus-with-postgresql',
|
||||||
|
'docker-registry',
|
||||||
|
'docuseal',
|
||||||
|
'docuseal-with-postgres',
|
||||||
|
'dokuwiki',
|
||||||
|
'duplicati',
|
||||||
|
'emby',
|
||||||
|
'embystat',
|
||||||
|
'fider',
|
||||||
|
'filebrowser',
|
||||||
|
'firefly',
|
||||||
|
'formbricks',
|
||||||
|
'ghost',
|
||||||
|
'gitea',
|
||||||
|
'gitea-with-mariadb',
|
||||||
|
'gitea-with-mysql',
|
||||||
|
'gitea-with-postgresql',
|
||||||
|
'glance',
|
||||||
|
'glances',
|
||||||
|
'glitchtip',
|
||||||
|
'grafana',
|
||||||
|
'grafana-with-postgresql',
|
||||||
|
'grocy',
|
||||||
|
'heimdall',
|
||||||
|
'homepage',
|
||||||
|
'jellyfin',
|
||||||
|
'kuzzle',
|
||||||
|
'listmonk',
|
||||||
|
'logto',
|
||||||
|
'mediawiki',
|
||||||
|
'meilisearch',
|
||||||
|
'metabase',
|
||||||
|
'metube',
|
||||||
|
'minio',
|
||||||
|
'moodle',
|
||||||
|
'n8n',
|
||||||
|
'n8n-with-postgresql',
|
||||||
|
'next-image-transformation',
|
||||||
|
'nextcloud',
|
||||||
|
'nocodb',
|
||||||
|
'odoo',
|
||||||
|
'openblocks',
|
||||||
|
'pairdrop',
|
||||||
|
'penpot',
|
||||||
|
'phpmyadmin',
|
||||||
|
'pocketbase',
|
||||||
|
'posthog',
|
||||||
|
'reactive-resume',
|
||||||
|
'rocketchat',
|
||||||
|
'shlink',
|
||||||
|
'slash',
|
||||||
|
'snapdrop',
|
||||||
|
'statusnook',
|
||||||
|
'stirling-pdf',
|
||||||
|
'supabase',
|
||||||
|
'syncthing',
|
||||||
|
'tolgee',
|
||||||
|
'trigger',
|
||||||
|
'trigger-with-external-database',
|
||||||
|
'twenty',
|
||||||
|
'umami',
|
||||||
|
'unleash-with-postgresql',
|
||||||
|
'unleash-without-database',
|
||||||
|
'uptime-kuma',
|
||||||
|
'vaultwarden',
|
||||||
|
'vikunja',
|
||||||
|
'weblate',
|
||||||
|
'whoogle',
|
||||||
|
'wordpress-with-mariadb',
|
||||||
|
'wordpress-with-mysql',
|
||||||
|
'wordpress-without-database',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'name' => ['type' => 'string', 'maxLength' => 255, 'description' => 'Name of the service.'],
|
||||||
|
'description' => ['type' => 'string', 'nullable' => true, 'description' => 'Description of the service.'],
|
||||||
|
'project_uuid' => ['type' => 'string', 'description' => 'Project UUID.'],
|
||||||
|
'environment_name' => ['type' => 'string', 'description' => 'Environment name.'],
|
||||||
|
'server_uuid' => ['type' => 'string', 'description' => 'Server UUID.'],
|
||||||
|
'destination_uuid' => ['type' => 'string', 'description' => 'Destination UUID. Required if server has multiple destinations.'],
|
||||||
|
'instant_deploy' => ['type' => 'boolean', 'default' => false, 'description' => 'Start the service immediately after creation.'],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 201,
|
||||||
|
description: 'Create a service.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'uuid' => ['type' => 'string', 'description' => 'Service UUID.'],
|
||||||
|
'domains' => ['type' => 'array', 'items' => ['type' => 'string'], 'description' => 'Service domains.'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function create_service(Request $request)
|
||||||
|
{
|
||||||
|
$allowedFields = ['type', 'name', 'description', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy'];
|
||||||
|
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$return = validateIncomingRequest($request);
|
||||||
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
$validator = customApiValidator($request->all(), [
|
||||||
|
'type' => 'string|required',
|
||||||
|
'project_uuid' => 'string|required',
|
||||||
|
'environment_name' => 'string|required',
|
||||||
|
'server_uuid' => 'string|required',
|
||||||
|
'destination_uuid' => 'string',
|
||||||
|
'name' => 'string|max:255',
|
||||||
|
'description' => 'string|nullable',
|
||||||
|
'instant_deploy' => 'boolean',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||||
|
if ($validator->fails() || ! empty($extraFields)) {
|
||||||
|
$errors = $validator->errors();
|
||||||
|
if (! empty($extraFields)) {
|
||||||
|
foreach ($extraFields as $field) {
|
||||||
|
$errors->add($field, 'This field is not allowed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => $errors,
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
$serverUuid = $request->server_uuid;
|
||||||
|
$instantDeploy = $request->instant_deploy ?? false;
|
||||||
|
if ($request->is_public && ! $request->public_port) {
|
||||||
|
$request->offsetSet('is_public', false);
|
||||||
|
}
|
||||||
|
$project = Project::whereTeamId($teamId)->whereUuid($request->project_uuid)->first();
|
||||||
|
if (! $project) {
|
||||||
|
return response()->json(['message' => 'Project not found.'], 404);
|
||||||
|
}
|
||||||
|
$environment = $project->environments()->where('name', $request->environment_name)->first();
|
||||||
|
if (! $environment) {
|
||||||
|
return response()->json(['message' => 'Environment not found.'], 404);
|
||||||
|
}
|
||||||
|
$server = Server::whereTeamId($teamId)->whereUuid($serverUuid)->first();
|
||||||
|
if (! $server) {
|
||||||
|
return response()->json(['message' => 'Server not found.'], 404);
|
||||||
|
}
|
||||||
|
$destinations = $server->destinations();
|
||||||
|
if ($destinations->count() == 0) {
|
||||||
|
return response()->json(['message' => 'Server has no destinations.'], 400);
|
||||||
|
}
|
||||||
|
if ($destinations->count() > 1 && ! $request->has('destination_uuid')) {
|
||||||
|
return response()->json(['message' => 'Server has multiple destinations and you do not set destination_uuid.'], 400);
|
||||||
|
}
|
||||||
|
$destination = $destinations->first();
|
||||||
|
$services = get_service_templates();
|
||||||
|
$serviceKeys = $services->keys();
|
||||||
|
if ($serviceKeys->contains($request->type)) {
|
||||||
|
$oneClickServiceName = $request->type;
|
||||||
|
$oneClickService = data_get($services, "$oneClickServiceName.compose");
|
||||||
|
$oneClickDotEnvs = data_get($services, "$oneClickServiceName.envs", null);
|
||||||
|
if ($oneClickDotEnvs) {
|
||||||
|
$oneClickDotEnvs = str(base64_decode($oneClickDotEnvs))->split('/\r\n|\r|\n/')->filter(function ($value) {
|
||||||
|
return ! empty($value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if ($oneClickService) {
|
||||||
|
$service_payload = [
|
||||||
|
'name' => "$oneClickServiceName-".str()->random(10),
|
||||||
|
'docker_compose_raw' => base64_decode($oneClickService),
|
||||||
|
'environment_id' => $environment->id,
|
||||||
|
'service_type' => $oneClickServiceName,
|
||||||
|
'server_id' => $server->id,
|
||||||
|
'destination_id' => $destination->id,
|
||||||
|
'destination_type' => $destination->getMorphClass(),
|
||||||
|
];
|
||||||
|
if ($oneClickServiceName === 'cloudflared') {
|
||||||
|
data_set($service_payload, 'connect_to_docker_network', true);
|
||||||
|
}
|
||||||
|
$service = Service::create($service_payload);
|
||||||
|
$service->name = "$oneClickServiceName-".$service->uuid;
|
||||||
|
$service->save();
|
||||||
|
if ($oneClickDotEnvs?->count() > 0) {
|
||||||
|
$oneClickDotEnvs->each(function ($value) use ($service) {
|
||||||
|
$key = str()->before($value, '=');
|
||||||
|
$value = str(str()->after($value, '='));
|
||||||
|
$generatedValue = $value;
|
||||||
|
if ($value->contains('SERVICE_')) {
|
||||||
|
$command = $value->after('SERVICE_')->beforeLast('_');
|
||||||
|
$generatedValue = generateEnvValue($command->value(), $service);
|
||||||
|
}
|
||||||
|
EnvironmentVariable::create([
|
||||||
|
'key' => $key,
|
||||||
|
'value' => $generatedValue,
|
||||||
|
'service_id' => $service->id,
|
||||||
|
'is_build_time' => false,
|
||||||
|
'is_preview' => false,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$service->parse(isNew: true);
|
||||||
|
if ($instantDeploy) {
|
||||||
|
StartService::dispatch($service);
|
||||||
|
}
|
||||||
|
$domains = $service->applications()->get()->pluck('fqdn')->sort();
|
||||||
|
$domains = $domains->map(function ($domain) {
|
||||||
|
return str($domain)->beforeLast(':')->value();
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'uuid' => $service->uuid,
|
||||||
|
'domains' => $domains,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
|
} else {
|
||||||
|
return response()->json(['message' => 'Invalid service type.', 'valid_service_types' => $serviceKeys], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Invalid service type.'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Get',
|
||||||
|
description: 'Get service by UUID.',
|
||||||
|
path: '/services/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Services'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Service UUID', schema: new OA\Schema(type: 'string')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get a service by Uuid.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
ref: '#/components/schemas/Service'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function service_by_uuid(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
if (! $request->uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 404);
|
||||||
|
}
|
||||||
|
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||||
|
if (! $service) {
|
||||||
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($this->removeSensitiveData($service));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Delete(
|
||||||
|
summary: 'Delete',
|
||||||
|
description: 'Delete service by UUID.',
|
||||||
|
path: '/services/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Services'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Service UUID', schema: new OA\Schema(type: 'string')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Delete a service by Uuid',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string', 'example' => 'Service deletion request queued.'],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function delete_by_uuid(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
if (! $request->uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 404);
|
||||||
|
}
|
||||||
|
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||||
|
if (! $service) {
|
||||||
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
|
}
|
||||||
|
DeleteResourceJob::dispatch($service);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Service deletion request queued.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Start',
|
||||||
|
description: 'Start service. `Post` request is also accepted.',
|
||||||
|
path: '/services/{uuid}/start',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Services'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'uuid',
|
||||||
|
in: 'path',
|
||||||
|
description: 'UUID of the service.',
|
||||||
|
required: true,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'string',
|
||||||
|
format: 'uuid',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Start service.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string', 'example' => 'Service starting request queued.'],
|
||||||
|
])
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function action_deploy(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$uuid = $request->route('uuid');
|
||||||
|
if (! $uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 400);
|
||||||
|
}
|
||||||
|
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||||
|
if (! $service) {
|
||||||
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
|
}
|
||||||
|
if (str($service->status())->contains('running')) {
|
||||||
|
return response()->json(['message' => 'Service is already running.'], 400);
|
||||||
|
}
|
||||||
|
StartService::dispatch($service);
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
[
|
||||||
|
'message' => 'Service starting request queued.',
|
||||||
|
],
|
||||||
|
200
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Stop',
|
||||||
|
description: 'Stop service. `Post` request is also accepted.',
|
||||||
|
path: '/services/{uuid}/stop',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Services'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'uuid',
|
||||||
|
in: 'path',
|
||||||
|
description: 'UUID of the service.',
|
||||||
|
required: true,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'string',
|
||||||
|
format: 'uuid',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Stop service.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string', 'example' => 'Service stopping request queued.'],
|
||||||
|
])
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function action_stop(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$uuid = $request->route('uuid');
|
||||||
|
if (! $uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 400);
|
||||||
|
}
|
||||||
|
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||||
|
if (! $service) {
|
||||||
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
|
}
|
||||||
|
if (str($service->status())->contains('stopped') || str($service->status())->contains('exited')) {
|
||||||
|
return response()->json(['message' => 'Service is already stopped.'], 400);
|
||||||
|
}
|
||||||
|
StopService::dispatch($service);
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
[
|
||||||
|
'message' => 'Service stopping request queued.',
|
||||||
|
],
|
||||||
|
200
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Restart',
|
||||||
|
description: 'Restart service. `Post` request is also accepted.',
|
||||||
|
path: '/services/{uuid}/restart',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Services'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'uuid',
|
||||||
|
in: 'path',
|
||||||
|
description: 'UUID of the service.',
|
||||||
|
required: true,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'string',
|
||||||
|
format: 'uuid',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Restart service.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string', 'example' => 'Service restaring request queued.'],
|
||||||
|
])
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function action_restart(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$uuid = $request->route('uuid');
|
||||||
|
if (! $uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 400);
|
||||||
|
}
|
||||||
|
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||||
|
if (! $service) {
|
||||||
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
|
}
|
||||||
|
RestartService::dispatch($service);
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
[
|
||||||
|
'message' => 'Service restarting request queued.',
|
||||||
|
],
|
||||||
|
200
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -1,74 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class Team extends Controller
|
|
||||||
{
|
|
||||||
public function teams(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$teams = auth()->user()->teams;
|
|
||||||
|
|
||||||
return response()->json($teams);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function team_by_id(Request $request)
|
|
||||||
{
|
|
||||||
$id = $request->id;
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$teams = auth()->user()->teams;
|
|
||||||
$team = $teams->where('id', $id)->first();
|
|
||||||
if (is_null($team)) {
|
|
||||||
return response()->json(['error' => 'Team not found.', 'docs' => 'https://coolify.io/docs/api-reference/get-team-by-teamid'], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json($team);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function members_by_id(Request $request)
|
|
||||||
{
|
|
||||||
$id = $request->id;
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$teams = auth()->user()->teams;
|
|
||||||
$team = $teams->where('id', $id)->first();
|
|
||||||
if (is_null($team)) {
|
|
||||||
return response()->json(['error' => 'Team not found.', 'docs' => 'https://coolify.io/docs/api-reference/get-team-by-teamid-members'], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json($team->members);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function current_team(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$team = auth()->user()->currentTeam();
|
|
||||||
|
|
||||||
return response()->json($team);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function current_team_members(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$team = auth()->user()->currentTeam();
|
|
||||||
|
|
||||||
return response()->json($team->members);
|
|
||||||
}
|
|
||||||
}
|
|
270
app/Http/Controllers/Api/TeamController.php
Normal file
270
app/Http/Controllers/Api/TeamController.php
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
class TeamController extends Controller
|
||||||
|
{
|
||||||
|
private function removeSensitiveData($team)
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
$team->makeHidden([
|
||||||
|
'custom_server_limit',
|
||||||
|
'pivot',
|
||||||
|
]);
|
||||||
|
if ($token->can('view:sensitive')) {
|
||||||
|
return serializeApiResponse($team);
|
||||||
|
}
|
||||||
|
$team->makeHidden([
|
||||||
|
'smtp_username',
|
||||||
|
'smtp_password',
|
||||||
|
'resend_api_key',
|
||||||
|
'telegram_token',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return serializeApiResponse($team);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'List',
|
||||||
|
description: 'Get all teams.',
|
||||||
|
path: '/teams',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Teams'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'List of teams.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/Team')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function teams(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$teams = auth()->user()->teams->sortBy('id');
|
||||||
|
$teams = $teams->map(function ($team) {
|
||||||
|
return $this->removeSensitiveData($team);
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
$teams,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Get',
|
||||||
|
description: 'Get team by TeamId.',
|
||||||
|
path: '/teams/{id}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Teams'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Team ID', schema: new OA\Schema(type: 'integer')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'List of teams.',
|
||||||
|
content: new OA\JsonContent(ref: '#/components/schemas/Team')
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function team_by_id(Request $request)
|
||||||
|
{
|
||||||
|
$id = $request->id;
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$teams = auth()->user()->teams;
|
||||||
|
$team = $teams->where('id', $id)->first();
|
||||||
|
if (is_null($team)) {
|
||||||
|
return response()->json(['message' => 'Team not found.'], 404);
|
||||||
|
}
|
||||||
|
$team = $this->removeSensitiveData($team);
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
serializeApiResponse($team),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Members',
|
||||||
|
description: 'Get members by TeamId.',
|
||||||
|
path: '/teams/{id}/members',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Teams'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Team ID', schema: new OA\Schema(type: 'integer')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'List of members.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/User')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function members_by_id(Request $request)
|
||||||
|
{
|
||||||
|
$id = $request->id;
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$teams = auth()->user()->teams;
|
||||||
|
$team = $teams->where('id', $id)->first();
|
||||||
|
if (is_null($team)) {
|
||||||
|
return response()->json(['message' => 'Team not found.'], 404);
|
||||||
|
}
|
||||||
|
$members = $team->members;
|
||||||
|
$members->makeHidden([
|
||||||
|
'pivot',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
serializeApiResponse($members),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Authenticated Team',
|
||||||
|
description: 'Get currently authenticated team.',
|
||||||
|
path: '/teams/current',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Teams'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Current Team.',
|
||||||
|
content: new OA\JsonContent(ref: '#/components/schemas/Team')),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function current_team(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$team = auth()->user()->currentTeam();
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
$this->removeSensitiveData($team),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Authenticated Team Members',
|
||||||
|
description: 'Get currently authenticated team members.',
|
||||||
|
path: '/teams/current/members',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Teams'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Currently authenticated team members.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/User')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function current_team_members(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$team = auth()->user()->currentTeam();
|
||||||
|
$team->members->makeHidden([
|
||||||
|
'pivot',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
serializeApiResponse($team->members),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -81,8 +81,8 @@ public function link()
|
|||||||
$token = request()->get('token');
|
$token = request()->get('token');
|
||||||
if ($token) {
|
if ($token) {
|
||||||
$decrypted = Crypt::decryptString($token);
|
$decrypted = Crypt::decryptString($token);
|
||||||
$email = Str::of($decrypted)->before('@@@');
|
$email = str($decrypted)->before('@@@');
|
||||||
$password = Str::of($decrypted)->after('@@@');
|
$password = str($decrypted)->after('@@@');
|
||||||
$user = User::whereEmail($email)->first();
|
$user = User::whereEmail($email)->first();
|
||||||
if (! $user) {
|
if (! $user) {
|
||||||
return redirect()->route('login');
|
return redirect()->route('login');
|
||||||
|
@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||||
|
|
||||||
class OauthController extends Controller
|
class OauthController extends Controller
|
||||||
{
|
{
|
||||||
@ -20,6 +22,11 @@ public function callback(string $provider)
|
|||||||
$oauthUser = get_socialite_provider($provider)->user();
|
$oauthUser = get_socialite_provider($provider)->user();
|
||||||
$user = User::whereEmail($oauthUser->email)->first();
|
$user = User::whereEmail($oauthUser->email)->first();
|
||||||
if (! $user) {
|
if (! $user) {
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
if (! $settings->is_registration_enabled) {
|
||||||
|
abort(403, 'Registration is disabled');
|
||||||
|
}
|
||||||
|
|
||||||
$user = User::create([
|
$user = User::create([
|
||||||
'name' => $oauthUser->name,
|
'name' => $oauthUser->name,
|
||||||
'email' => $oauthUser->email,
|
'email' => $oauthUser->email,
|
||||||
@ -31,7 +38,9 @@ public function callback(string $provider)
|
|||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
|
|
||||||
return redirect()->route('login')->withErrors([__('auth.failed.callback')]);
|
$errorCode = $e instanceof HttpException ? 'auth.failed' : 'auth.failed.callback';
|
||||||
|
|
||||||
|
return redirect()->route('login')->withErrors([__($errorCode)]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,12 +130,23 @@ public function manual(Request $request)
|
|||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2(7);
|
||||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||||
if (! $found) {
|
if (! $found) {
|
||||||
ApplicationPreview::create([
|
if ($application->build_pack === 'dockercompose') {
|
||||||
'git_type' => 'bitbucket',
|
$pr_app = ApplicationPreview::create([
|
||||||
'application_id' => $application->id,
|
'git_type' => 'bitbucket',
|
||||||
'pull_request_id' => $pull_request_id,
|
'application_id' => $application->id,
|
||||||
'pull_request_html_url' => $pull_request_html_url,
|
'pull_request_id' => $pull_request_id,
|
||||||
]);
|
'pull_request_html_url' => $pull_request_html_url,
|
||||||
|
'docker_compose_domains' => $application->docker_compose_domains,
|
||||||
|
]);
|
||||||
|
$pr_app->generate_preview_fqdn_compose();
|
||||||
|
} else {
|
||||||
|
ApplicationPreview::create([
|
||||||
|
'git_type' => 'bitbucket',
|
||||||
|
'application_id' => $application->id,
|
||||||
|
'pull_request_id' => $pull_request_id,
|
||||||
|
'pull_request_html_url' => $pull_request_html_url,
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
|
@ -165,12 +165,24 @@ public function manual(Request $request)
|
|||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2(7);
|
||||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||||
if (! $found) {
|
if (! $found) {
|
||||||
ApplicationPreview::create([
|
if ($application->build_pack === 'dockercompose') {
|
||||||
'git_type' => 'gitea',
|
$pr_app = ApplicationPreview::create([
|
||||||
'application_id' => $application->id,
|
'git_type' => 'gitea',
|
||||||
'pull_request_id' => $pull_request_id,
|
'application_id' => $application->id,
|
||||||
'pull_request_html_url' => $pull_request_html_url,
|
'pull_request_id' => $pull_request_id,
|
||||||
]);
|
'pull_request_html_url' => $pull_request_html_url,
|
||||||
|
'docker_compose_domains' => $application->docker_compose_domains,
|
||||||
|
]);
|
||||||
|
$pr_app->generate_preview_fqdn_compose();
|
||||||
|
} else {
|
||||||
|
ApplicationPreview::create([
|
||||||
|
'git_type' => 'gitea',
|
||||||
|
'application_id' => $application->id,
|
||||||
|
'pull_request_id' => $pull_request_id,
|
||||||
|
'pull_request_html_url' => $pull_request_html_url,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
|
@ -170,12 +170,23 @@ public function manual(Request $request)
|
|||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2(7);
|
||||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||||
if (! $found) {
|
if (! $found) {
|
||||||
ApplicationPreview::create([
|
if ($application->build_pack === 'dockercompose') {
|
||||||
'git_type' => 'github',
|
$pr_app = ApplicationPreview::create([
|
||||||
'application_id' => $application->id,
|
'git_type' => 'github',
|
||||||
'pull_request_id' => $pull_request_id,
|
'application_id' => $application->id,
|
||||||
'pull_request_html_url' => $pull_request_html_url,
|
'pull_request_id' => $pull_request_id,
|
||||||
]);
|
'pull_request_html_url' => $pull_request_html_url,
|
||||||
|
'docker_compose_domains' => $application->docker_compose_domains,
|
||||||
|
]);
|
||||||
|
$pr_app->generate_preview_fqdn_compose();
|
||||||
|
} else {
|
||||||
|
ApplicationPreview::create([
|
||||||
|
'git_type' => 'github',
|
||||||
|
'application_id' => $application->id,
|
||||||
|
'pull_request_id' => $pull_request_id,
|
||||||
|
'pull_request_html_url' => $pull_request_html_url,
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
|
@ -180,12 +180,23 @@ public function manual(Request $request)
|
|||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2(7);
|
||||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||||
if (! $found) {
|
if (! $found) {
|
||||||
ApplicationPreview::create([
|
if ($application->build_pack === 'dockercompose') {
|
||||||
'git_type' => 'gitlab',
|
$pr_app = ApplicationPreview::create([
|
||||||
'application_id' => $application->id,
|
'git_type' => 'gitlab',
|
||||||
'pull_request_id' => $pull_request_id,
|
'application_id' => $application->id,
|
||||||
'pull_request_html_url' => $pull_request_html_url,
|
'pull_request_id' => $pull_request_id,
|
||||||
]);
|
'pull_request_html_url' => $pull_request_html_url,
|
||||||
|
'docker_compose_domains' => $application->docker_compose_domains,
|
||||||
|
]);
|
||||||
|
$pr_app->generate_preview_fqdn_compose();
|
||||||
|
} else {
|
||||||
|
ApplicationPreview::create([
|
||||||
|
'git_type' => 'gitlab',
|
||||||
|
'application_id' => $application->id,
|
||||||
|
'pull_request_id' => $pull_request_id,
|
||||||
|
'pull_request_html_url' => $pull_request_html_url,
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
|
@ -54,6 +54,34 @@ public function events(Request $request)
|
|||||||
$type = data_get($event, 'type');
|
$type = data_get($event, 'type');
|
||||||
$data = data_get($event, 'data.object');
|
$data = data_get($event, 'data.object');
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
|
case 'radar.early_fraud_warning.created':
|
||||||
|
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
|
||||||
|
$id = data_get($data, 'id');
|
||||||
|
$charge = data_get($data, 'charge');
|
||||||
|
if ($charge) {
|
||||||
|
$stripe->refunds->create(['charge' => $charge]);
|
||||||
|
}
|
||||||
|
$pi = data_get($data, 'payment_intent');
|
||||||
|
$piData = $stripe->paymentIntents->retrieve($pi, []);
|
||||||
|
$customerId = data_get($piData, 'customer');
|
||||||
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||||
|
if (! $subscription) {
|
||||||
|
Sleep::for(5)->seconds();
|
||||||
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||||
|
}
|
||||||
|
if (! $subscription) {
|
||||||
|
Sleep::for(5)->seconds();
|
||||||
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||||
|
}
|
||||||
|
if ($subscription) {
|
||||||
|
$subscriptionId = data_get($subscription, 'stripe_subscription_id');
|
||||||
|
$stripe->subscriptions->cancel($subscriptionId, []);
|
||||||
|
$subscription->update([
|
||||||
|
'stripe_invoice_paid' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
send_internal_notification("Early fraud warning created Refunded, subscription canceled. Charge: {$charge}, id: {$id}, pi: {$pi}");
|
||||||
|
break;
|
||||||
case 'checkout.session.completed':
|
case 'checkout.session.completed':
|
||||||
$clientReferenceId = data_get($data, 'client_reference_id');
|
$clientReferenceId = data_get($data, 'client_reference_id');
|
||||||
if (is_null($clientReferenceId)) {
|
if (is_null($clientReferenceId)) {
|
||||||
@ -72,14 +100,14 @@ public function events(Request $request)
|
|||||||
}
|
}
|
||||||
$subscription = Subscription::where('team_id', $teamId)->first();
|
$subscription = Subscription::where('team_id', $teamId)->first();
|
||||||
if ($subscription) {
|
if ($subscription) {
|
||||||
send_internal_notification('Old subscription activated for team: '.$teamId);
|
// send_internal_notification('Old subscription activated for team: '.$teamId);
|
||||||
$subscription->update([
|
$subscription->update([
|
||||||
'stripe_subscription_id' => $subscriptionId,
|
'stripe_subscription_id' => $subscriptionId,
|
||||||
'stripe_customer_id' => $customerId,
|
'stripe_customer_id' => $customerId,
|
||||||
'stripe_invoice_paid' => true,
|
'stripe_invoice_paid' => true,
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
send_internal_notification('New subscription for team: '.$teamId);
|
// send_internal_notification('New subscription for team: '.$teamId);
|
||||||
Subscription::create([
|
Subscription::create([
|
||||||
'team_id' => $teamId,
|
'team_id' => $teamId,
|
||||||
'stripe_subscription_id' => $subscriptionId,
|
'stripe_subscription_id' => $subscriptionId,
|
||||||
@ -92,7 +120,7 @@ public function events(Request $request)
|
|||||||
$customerId = data_get($data, 'customer');
|
$customerId = data_get($data, 'customer');
|
||||||
$planId = data_get($data, 'lines.data.0.plan.id');
|
$planId = data_get($data, 'lines.data.0.plan.id');
|
||||||
if (Str::contains($excludedPlans, $planId)) {
|
if (Str::contains($excludedPlans, $planId)) {
|
||||||
send_internal_notification('Subscription excluded.');
|
// send_internal_notification('Subscription excluded.');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||||
@ -108,33 +136,33 @@ public function events(Request $request)
|
|||||||
$customerId = data_get($data, 'customer');
|
$customerId = data_get($data, 'customer');
|
||||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||||
if (! $subscription) {
|
if (! $subscription) {
|
||||||
send_internal_notification('invoice.payment_failed failed but no subscription found in Coolify for customer: '.$customerId);
|
// send_internal_notification('invoice.payment_failed failed but no subscription found in Coolify for customer: '.$customerId);
|
||||||
|
|
||||||
return response('No subscription found in Coolify.');
|
return response('No subscription found in Coolify.');
|
||||||
}
|
}
|
||||||
$team = data_get($subscription, 'team');
|
$team = data_get($subscription, 'team');
|
||||||
if (! $team) {
|
if (! $team) {
|
||||||
send_internal_notification('invoice.payment_failed failed but no team found in Coolify for customer: '.$customerId);
|
// send_internal_notification('invoice.payment_failed failed but no team found in Coolify for customer: '.$customerId);
|
||||||
|
|
||||||
return response('No team found in Coolify.');
|
return response('No team found in Coolify.');
|
||||||
}
|
}
|
||||||
if (! $subscription->stripe_invoice_paid) {
|
if (! $subscription->stripe_invoice_paid) {
|
||||||
SubscriptionInvoiceFailedJob::dispatch($team);
|
SubscriptionInvoiceFailedJob::dispatch($team);
|
||||||
send_internal_notification('Invoice payment failed: '.$customerId);
|
// send_internal_notification('Invoice payment failed: '.$customerId);
|
||||||
} else {
|
} else {
|
||||||
send_internal_notification('Invoice payment failed but already paid: '.$customerId);
|
// send_internal_notification('Invoice payment failed but already paid: '.$customerId);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'payment_intent.payment_failed':
|
case 'payment_intent.payment_failed':
|
||||||
$customerId = data_get($data, 'customer');
|
$customerId = data_get($data, 'customer');
|
||||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||||
if (! $subscription) {
|
if (! $subscription) {
|
||||||
send_internal_notification('payment_intent.payment_failed, no subscription found in Coolify for customer: '.$customerId);
|
// send_internal_notification('payment_intent.payment_failed, no subscription found in Coolify for customer: '.$customerId);
|
||||||
|
|
||||||
return response('No subscription found in Coolify.');
|
return response('No subscription found in Coolify.');
|
||||||
}
|
}
|
||||||
if ($subscription->stripe_invoice_paid) {
|
if ($subscription->stripe_invoice_paid) {
|
||||||
send_internal_notification('payment_intent.payment_failed but invoice is active for customer: '.$customerId);
|
// send_internal_notification('payment_intent.payment_failed but invoice is active for customer: '.$customerId);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -146,7 +174,7 @@ public function events(Request $request)
|
|||||||
$subscriptionId = data_get($data, 'items.data.0.subscription');
|
$subscriptionId = data_get($data, 'items.data.0.subscription');
|
||||||
$planId = data_get($data, 'items.data.0.plan.id');
|
$planId = data_get($data, 'items.data.0.plan.id');
|
||||||
if (Str::contains($excludedPlans, $planId)) {
|
if (Str::contains($excludedPlans, $planId)) {
|
||||||
send_internal_notification('Subscription excluded.');
|
// send_internal_notification('Subscription excluded.');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||||
@ -156,11 +184,11 @@ public function events(Request $request)
|
|||||||
}
|
}
|
||||||
if (! $subscription) {
|
if (! $subscription) {
|
||||||
if ($status === 'incomplete_expired') {
|
if ($status === 'incomplete_expired') {
|
||||||
send_internal_notification('Subscription incomplete expired for customer: '.$customerId);
|
// send_internal_notification('Subscription incomplete expired for customer: '.$customerId);
|
||||||
|
|
||||||
return response('Subscription incomplete expired', 200);
|
return response('Subscription incomplete expired', 200);
|
||||||
}
|
}
|
||||||
send_internal_notification('No subscription found for: '.$customerId);
|
// send_internal_notification('No subscription found for: '.$customerId);
|
||||||
|
|
||||||
return response('No subscription found', 400);
|
return response('No subscription found', 400);
|
||||||
}
|
}
|
||||||
@ -194,7 +222,7 @@ public function events(Request $request)
|
|||||||
$subscription->update([
|
$subscription->update([
|
||||||
'stripe_invoice_paid' => false,
|
'stripe_invoice_paid' => false,
|
||||||
]);
|
]);
|
||||||
send_internal_notification('Subscription paused or incomplete for customer: '.$customerId);
|
// send_internal_notification('Subscription paused or incomplete for customer: '.$customerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trial ended but subscribed, reactive servers
|
// Trial ended but subscribed, reactive servers
|
||||||
@ -208,13 +236,13 @@ public function events(Request $request)
|
|||||||
if ($comment) {
|
if ($comment) {
|
||||||
$reason .= ' with comment: \''.$comment."'";
|
$reason .= ' with comment: \''.$comment."'";
|
||||||
}
|
}
|
||||||
send_internal_notification($reason);
|
// send_internal_notification($reason);
|
||||||
}
|
}
|
||||||
if ($alreadyCancelAtPeriodEnd !== $cancelAtPeriodEnd) {
|
if ($alreadyCancelAtPeriodEnd !== $cancelAtPeriodEnd) {
|
||||||
if ($cancelAtPeriodEnd) {
|
if ($cancelAtPeriodEnd) {
|
||||||
// send_internal_notification('Subscription cancelled at period end for team: ' . $subscription->team->id);
|
// send_internal_notification('Subscription cancelled at period end for team: ' . $subscription->team->id);
|
||||||
} else {
|
} else {
|
||||||
send_internal_notification('customer.subscription.updated for customer: '.$customerId);
|
// send_internal_notification('customer.subscription.updated for customer: '.$customerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -231,9 +259,9 @@ public function events(Request $request)
|
|||||||
'stripe_plan_id' => null,
|
'stripe_plan_id' => null,
|
||||||
'stripe_cancel_at_period_end' => false,
|
'stripe_cancel_at_period_end' => false,
|
||||||
'stripe_invoice_paid' => false,
|
'stripe_invoice_paid' => false,
|
||||||
'stripe_trial_already_ended' => true,
|
'stripe_trial_already_ended' => false,
|
||||||
]);
|
]);
|
||||||
send_internal_notification('customer.subscription.deleted for customer: '.$customerId);
|
// send_internal_notification('customer.subscription.deleted for customer: '.$customerId);
|
||||||
break;
|
break;
|
||||||
case 'customer.subscription.trial_will_end':
|
case 'customer.subscription.trial_will_end':
|
||||||
// Not used for now
|
// Not used for now
|
||||||
@ -258,7 +286,7 @@ public function events(Request $request)
|
|||||||
'stripe_invoice_paid' => false,
|
'stripe_invoice_paid' => false,
|
||||||
]);
|
]);
|
||||||
SubscriptionTrialEndedJob::dispatch($team);
|
SubscriptionTrialEndedJob::dispatch($team);
|
||||||
send_internal_notification('Subscription paused for customer: '.$customerId);
|
// send_internal_notification('Subscription paused for customer: '.$customerId);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// Unhandled event type
|
// Unhandled event type
|
||||||
|
@ -67,5 +67,7 @@ class Kernel extends HttpKernel
|
|||||||
'signed' => \App\Http\Middleware\ValidateSignature::class,
|
'signed' => \App\Http\Middleware\ValidateSignature::class,
|
||||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||||
|
'abilities' => \Laravel\Sanctum\Http\Middleware\CheckAbilities::class,
|
||||||
|
'ability' => \Laravel\Sanctum\Http\Middleware\CheckForAnyAbility::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
34
app/Http/Middleware/ApiAllowed.php
Normal file
34
app/Http/Middleware/ApiAllowed.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class ApiAllowed
|
||||||
|
{
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
ray()->clearAll();
|
||||||
|
if (isCloud()) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
if ($settings->is_api_enabled === false) {
|
||||||
|
return response()->json(['success' => true, 'message' => 'API is disabled.'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! isDev()) {
|
||||||
|
if ($settings->allowed_ips) {
|
||||||
|
$allowedIps = explode(',', $settings->allowed_ips);
|
||||||
|
if (! in_array($request->ip(), $allowedIps)) {
|
||||||
|
return response()->json(['success' => true, 'message' => 'You are not allowed to access the API.'], 403);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
28
app/Http/Middleware/IgnoreReadOnlyApiToken.php
Normal file
28
app/Http/Middleware/IgnoreReadOnlyApiToken.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class IgnoreReadOnlyApiToken
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
if ($token->can('*')) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
if ($token->can('read-only')) {
|
||||||
|
return response()->json(['message' => 'You are not allowed to perform this action.'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
25
app/Http/Middleware/OnlyRootApiToken.php
Normal file
25
app/Http/Middleware/OnlyRootApiToken.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class OnlyRootApiToken
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
if ($token->can('*')) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'You are not allowed to perform this action.'], 403);
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@
|
|||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
use App\Models\ApplicationPreview;
|
use App\Models\ApplicationPreview;
|
||||||
|
use App\Models\EnvironmentVariable;
|
||||||
use App\Models\GithubApp;
|
use App\Models\GithubApp;
|
||||||
use App\Models\GitlabApp;
|
use App\Models\GitlabApp;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
@ -126,7 +127,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
private string $dockerfile_location = '/Dockerfile';
|
private string $dockerfile_location = '/Dockerfile';
|
||||||
|
|
||||||
private string $docker_compose_location = '/docker-compose.yml';
|
private string $docker_compose_location = '/docker-compose.yaml';
|
||||||
|
|
||||||
private ?string $docker_compose_custom_start_command = null;
|
private ?string $docker_compose_custom_start_command = null;
|
||||||
|
|
||||||
@ -193,6 +194,9 @@ public function __construct(int $application_deployment_queue_id)
|
|||||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||||
|
|
||||||
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
|
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
|
||||||
|
if ($this->application->settings->custom_internal_name && ! $this->application->settings->is_consistent_container_name_enabled) {
|
||||||
|
$this->container_name = $this->application->settings->custom_internal_name;
|
||||||
|
}
|
||||||
ray('New container name: ', $this->container_name);
|
ray('New container name: ', $this->container_name);
|
||||||
|
|
||||||
savePrivateKeyToFs($this->server);
|
savePrivateKeyToFs($this->server);
|
||||||
@ -339,7 +343,7 @@ private function decide_what_to_do()
|
|||||||
private function post_deployment()
|
private function post_deployment()
|
||||||
{
|
{
|
||||||
if ($this->server->isProxyShouldRun()) {
|
if ($this->server->isProxyShouldRun()) {
|
||||||
GetContainersStatus::dispatch($this->server);
|
GetContainersStatus::dispatch($this->server)->onQueue('high');
|
||||||
// dispatch(new ContainerStatusJob($this->server));
|
// dispatch(new ContainerStatusJob($this->server));
|
||||||
}
|
}
|
||||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||||
@ -607,10 +611,10 @@ private function write_deployment_configurations()
|
|||||||
}
|
}
|
||||||
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
|
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
|
||||||
if ($this->pull_request_id === 0) {
|
if ($this->pull_request_id === 0) {
|
||||||
$composeFileName = "$this->configuration_dir/docker-compose.yml";
|
$composeFileName = "$this->configuration_dir/docker-compose.yaml";
|
||||||
} else {
|
} else {
|
||||||
$composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yml";
|
$composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yaml";
|
||||||
$this->docker_compose_location = "/docker-compose-pr-{$this->pull_request_id}.yml";
|
$this->docker_compose_location = "/docker-compose-pr-{$this->pull_request_id}.yaml";
|
||||||
}
|
}
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
@ -827,6 +831,9 @@ private function save_environment_variables()
|
|||||||
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||||
$envs->push("COOLIFY_BRANCH={$local_branch}");
|
$envs->push("COOLIFY_BRANCH={$local_branch}");
|
||||||
}
|
}
|
||||||
|
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
||||||
|
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
|
||||||
|
}
|
||||||
foreach ($sorted_environment_variables_preview as $env) {
|
foreach ($sorted_environment_variables_preview as $env) {
|
||||||
$real_value = $env->real_value;
|
$real_value = $env->real_value;
|
||||||
if ($env->version === '4.0.0-beta.239') {
|
if ($env->version === '4.0.0-beta.239') {
|
||||||
@ -868,6 +875,9 @@ private function save_environment_variables()
|
|||||||
if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||||
$envs->push("COOLIFY_BRANCH={$local_branch}");
|
$envs->push("COOLIFY_BRANCH={$local_branch}");
|
||||||
}
|
}
|
||||||
|
if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
||||||
|
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
|
||||||
|
}
|
||||||
foreach ($sorted_environment_variables as $env) {
|
foreach ($sorted_environment_variables as $env) {
|
||||||
$real_value = $env->real_value;
|
$real_value = $env->real_value;
|
||||||
if ($env->version === '4.0.0-beta.239') {
|
if ($env->version === '4.0.0-beta.239') {
|
||||||
@ -877,7 +887,6 @@ private function save_environment_variables()
|
|||||||
$real_value = '\''.$real_value.'\'';
|
$real_value = '\''.$real_value.'\'';
|
||||||
} else {
|
} else {
|
||||||
$real_value = escapeEnvVariables($env->real_value);
|
$real_value = escapeEnvVariables($env->real_value);
|
||||||
ray($real_value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$envs->push($env->key.'='.$real_value);
|
$envs->push($env->key.'='.$real_value);
|
||||||
@ -946,9 +955,8 @@ private function save_environment_variables()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function framework_based_notification()
|
private function laravel_finetunes()
|
||||||
{
|
{
|
||||||
// Laravel old env variables
|
|
||||||
if ($this->pull_request_id === 0) {
|
if ($this->pull_request_id === 0) {
|
||||||
$nixpacks_php_fallback_path = $this->application->environment_variables->where('key', 'NIXPACKS_PHP_FALLBACK_PATH')->first();
|
$nixpacks_php_fallback_path = $this->application->environment_variables->where('key', 'NIXPACKS_PHP_FALLBACK_PATH')->first();
|
||||||
$nixpacks_php_root_dir = $this->application->environment_variables->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first();
|
$nixpacks_php_root_dir = $this->application->environment_variables->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first();
|
||||||
@ -956,9 +964,24 @@ private function framework_based_notification()
|
|||||||
$nixpacks_php_fallback_path = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_FALLBACK_PATH')->first();
|
$nixpacks_php_fallback_path = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_FALLBACK_PATH')->first();
|
||||||
$nixpacks_php_root_dir = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first();
|
$nixpacks_php_root_dir = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first();
|
||||||
}
|
}
|
||||||
if ($nixpacks_php_fallback_path?->value === '/index.php' && $nixpacks_php_root_dir?->value === '/app/public' && $this->newVersionIsHealthy === false) {
|
if (! $nixpacks_php_fallback_path) {
|
||||||
$this->application_deployment_queue->addLogEntry('There was a change in how Laravel is deployed. Please update your environment variables to match the new deployment method. More details here: https://coolify.io/docs/resources/laravel', 'stderr');
|
$nixpacks_php_fallback_path = new EnvironmentVariable();
|
||||||
|
$nixpacks_php_fallback_path->key = 'NIXPACKS_PHP_FALLBACK_PATH';
|
||||||
|
$nixpacks_php_fallback_path->value = '/index.php';
|
||||||
|
$nixpacks_php_fallback_path->is_build_time = false;
|
||||||
|
$nixpacks_php_fallback_path->application_id = $this->application->id;
|
||||||
|
$nixpacks_php_fallback_path->save();
|
||||||
}
|
}
|
||||||
|
if (! $nixpacks_php_root_dir) {
|
||||||
|
$nixpacks_php_root_dir = new EnvironmentVariable();
|
||||||
|
$nixpacks_php_root_dir->key = 'NIXPACKS_PHP_ROOT_DIR';
|
||||||
|
$nixpacks_php_root_dir->value = '/app/public';
|
||||||
|
$nixpacks_php_root_dir->is_build_time = false;
|
||||||
|
$nixpacks_php_root_dir->application_id = $this->application->id;
|
||||||
|
$nixpacks_php_root_dir->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$nixpacks_php_fallback_path, $nixpacks_php_root_dir];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function rolling_update()
|
private function rolling_update()
|
||||||
@ -1005,7 +1028,6 @@ private function rolling_update()
|
|||||||
$this->application_deployment_queue->addLogEntry('Rolling update completed.');
|
$this->application_deployment_queue->addLogEntry('Rolling update completed.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->framework_based_notification();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function health_check()
|
private function health_check()
|
||||||
@ -1059,13 +1081,13 @@ private function health_check()
|
|||||||
$this->application_deployment_queue->addLogEntry("Healthcheck logs: {$health_check_logs} | Return code: {$health_check_return_code}");
|
$this->application_deployment_queue->addLogEntry("Healthcheck logs: {$health_check_logs} | Return code: {$health_check_return_code}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Str::of($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'healthy') {
|
if (str($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'healthy') {
|
||||||
$this->newVersionIsHealthy = true;
|
$this->newVersionIsHealthy = true;
|
||||||
$this->application->update(['status' => 'running']);
|
$this->application->update(['status' => 'running']);
|
||||||
$this->application_deployment_queue->addLogEntry('New container is healthy.');
|
$this->application_deployment_queue->addLogEntry('New container is healthy.');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (Str::of($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'unhealthy') {
|
if (str($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'unhealthy') {
|
||||||
$this->newVersionIsHealthy = false;
|
$this->newVersionIsHealthy = false;
|
||||||
$this->query_logs();
|
$this->query_logs();
|
||||||
break;
|
break;
|
||||||
@ -1077,7 +1099,7 @@ private function health_check()
|
|||||||
$sleeptime++;
|
$sleeptime++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Str::of($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'starting') {
|
if (str($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'starting') {
|
||||||
$this->query_logs();
|
$this->query_logs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1366,17 +1388,20 @@ private function generate_nixpacks_confs()
|
|||||||
throw new RuntimeException('Nixpacks failed to detect the application type. Please check the documentation of Nixpacks: https://nixpacks.com/docs/providers');
|
throw new RuntimeException('Nixpacks failed to detect the application type. Please check the documentation of Nixpacks: https://nixpacks.com/docs/providers');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->saved_outputs->get('nixpacks_plan')) {
|
if ($this->saved_outputs->get('nixpacks_plan')) {
|
||||||
$this->nixpacks_plan = $this->saved_outputs->get('nixpacks_plan');
|
$this->nixpacks_plan = $this->saved_outputs->get('nixpacks_plan');
|
||||||
if ($this->nixpacks_plan) {
|
if ($this->nixpacks_plan) {
|
||||||
$this->application_deployment_queue->addLogEntry("Found application type: {$this->nixpacks_type}.");
|
$this->application_deployment_queue->addLogEntry("Found application type: {$this->nixpacks_type}.");
|
||||||
$this->application_deployment_queue->addLogEntry("If you need further customization, please check the documentation of Nixpacks: https://nixpacks.com/docs/providers/{$this->nixpacks_type}");
|
$this->application_deployment_queue->addLogEntry("If you need further customization, please check the documentation of Nixpacks: https://nixpacks.com/docs/providers/{$this->nixpacks_type}");
|
||||||
$parsed = Toml::Parse($this->nixpacks_plan);
|
$parsed = Toml::Parse($this->nixpacks_plan);
|
||||||
|
|
||||||
// Do any modifications here
|
// Do any modifications here
|
||||||
$this->generate_env_variables();
|
$this->generate_env_variables();
|
||||||
$merged_envs = $this->env_args->merge(collect(data_get($parsed, 'variables', [])));
|
$merged_envs = $this->env_args->merge(collect(data_get($parsed, 'variables', [])));
|
||||||
$aptPkgs = data_get($parsed, 'phases.setup.aptPkgs', []);
|
$aptPkgs = data_get($parsed, 'phases.setup.aptPkgs', []);
|
||||||
if (count($aptPkgs) === 0) {
|
if (count($aptPkgs) === 0) {
|
||||||
|
$aptPkgs = ['curl', 'wget'];
|
||||||
data_set($parsed, 'phases.setup.aptPkgs', ['curl', 'wget']);
|
data_set($parsed, 'phases.setup.aptPkgs', ['curl', 'wget']);
|
||||||
} else {
|
} else {
|
||||||
if (! in_array('curl', $aptPkgs)) {
|
if (! in_array('curl', $aptPkgs)) {
|
||||||
@ -1388,6 +1413,12 @@ private function generate_nixpacks_confs()
|
|||||||
data_set($parsed, 'phases.setup.aptPkgs', $aptPkgs);
|
data_set($parsed, 'phases.setup.aptPkgs', $aptPkgs);
|
||||||
}
|
}
|
||||||
data_set($parsed, 'variables', $merged_envs->toArray());
|
data_set($parsed, 'variables', $merged_envs->toArray());
|
||||||
|
$is_laravel = data_get($parsed, 'variables.IS_LARAVEL', false);
|
||||||
|
if ($is_laravel) {
|
||||||
|
$variables = $this->laravel_finetunes();
|
||||||
|
data_set($parsed, 'variables.NIXPACKS_PHP_FALLBACK_PATH', $variables[0]->value);
|
||||||
|
data_set($parsed, 'variables.NIXPACKS_PHP_ROOT_DIR', $variables[1]->value);
|
||||||
|
}
|
||||||
$this->nixpacks_plan = json_encode($parsed, JSON_PRETTY_PRINT);
|
$this->nixpacks_plan = json_encode($parsed, JSON_PRETTY_PRINT);
|
||||||
$this->application_deployment_queue->addLogEntry("Final Nixpacks plan: {$this->nixpacks_plan}", hidden: true);
|
$this->application_deployment_queue->addLogEntry("Final Nixpacks plan: {$this->nixpacks_plan}", hidden: true);
|
||||||
}
|
}
|
||||||
@ -1509,7 +1540,7 @@ private function generate_compose_file()
|
|||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), 'hidden' => true, 'save' => 'dockerfile_from_repo', 'ignore_errors' => true,
|
executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), 'hidden' => true, 'save' => 'dockerfile_from_repo', 'ignore_errors' => true,
|
||||||
]);
|
]);
|
||||||
$dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile_from_repo'))->trim()->explode("\n"));
|
$dockerfile = collect(str($this->saved_outputs->get('dockerfile_from_repo'))->trim()->explode("\n"));
|
||||||
$this->application->parseHealthcheckFromDockerfile($dockerfile);
|
$this->application->parseHealthcheckFromDockerfile($dockerfile);
|
||||||
}
|
}
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
@ -1542,23 +1573,6 @@ private function generate_compose_file()
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
if (isset($this->application->settings->custom_internal_name)) {
|
|
||||||
$docker_compose['services'][$this->container_name]['networks'][$this->destination->network]['aliases'][] = $this->application->settings->custom_internal_name;
|
|
||||||
}
|
|
||||||
// if (str($this->saved_outputs->get('dotenv'))->isNotEmpty()) {
|
|
||||||
// if (data_get($docker_compose, "services.{$this->container_name}.env_file")) {
|
|
||||||
// $docker_compose['services'][$this->container_name]['env_file'][] = '.env';
|
|
||||||
// } else {
|
|
||||||
// $docker_compose['services'][$this->container_name]['env_file'] = ['.env'];
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if ($this->env_filename) {
|
|
||||||
// if (data_get($docker_compose, "services.{$this->container_name}.env_file")) {
|
|
||||||
// $docker_compose['services'][$this->container_name]['env_file'][] = $this->env_filename;
|
|
||||||
// } else {
|
|
||||||
// $docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename];
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
if (! is_null($this->env_filename)) {
|
if (! is_null($this->env_filename)) {
|
||||||
$docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename];
|
$docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename];
|
||||||
}
|
}
|
||||||
@ -1669,32 +1683,28 @@ private function generate_compose_file()
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$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,
|
|
||||||
// ];
|
|
||||||
// }
|
|
||||||
|
|
||||||
if ($this->pull_request_id === 0) {
|
if ($this->pull_request_id === 0) {
|
||||||
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
|
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
|
||||||
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
|
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
|
||||||
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
|
if (! $this->application->settings->custom_internal_name) {
|
||||||
if (count($custom_compose) > 0) {
|
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
|
||||||
$ipv4 = data_get($custom_compose, 'ip.0');
|
if (count($custom_compose) > 0) {
|
||||||
$ipv6 = data_get($custom_compose, 'ip6.0');
|
$ipv4 = data_get($custom_compose, 'ip.0');
|
||||||
data_forget($custom_compose, 'ip');
|
$ipv6 = data_get($custom_compose, 'ip6.0');
|
||||||
data_forget($custom_compose, 'ip6');
|
data_forget($custom_compose, 'ip');
|
||||||
if ($ipv4 || $ipv6) {
|
data_forget($custom_compose, 'ip6');
|
||||||
data_forget($docker_compose['services'][$this->application->uuid], 'networks');
|
if ($ipv4 || $ipv6) {
|
||||||
|
data_forget($docker_compose['services'][$this->application->uuid], 'networks');
|
||||||
|
}
|
||||||
|
if ($ipv4) {
|
||||||
|
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv4_address'] = $ipv4;
|
||||||
|
}
|
||||||
|
if ($ipv6) {
|
||||||
|
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv6_address'] = $ipv6;
|
||||||
|
}
|
||||||
|
$docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose);
|
||||||
}
|
}
|
||||||
if ($ipv4) {
|
|
||||||
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv4_address'] = $ipv4;
|
|
||||||
}
|
|
||||||
if ($ipv6) {
|
|
||||||
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv6_address'] = $ipv6;
|
|
||||||
}
|
|
||||||
$docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (count($custom_compose) > 0) {
|
if (count($custom_compose) > 0) {
|
||||||
@ -1718,7 +1728,7 @@ private function generate_compose_file()
|
|||||||
|
|
||||||
$this->docker_compose = Yaml::dump($docker_compose, 10);
|
$this->docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$this->docker_compose_base64 = base64_encode($this->docker_compose);
|
$this->docker_compose_base64 = base64_encode($this->docker_compose);
|
||||||
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}/docker-compose.yml > /dev/null"), 'hidden' => true]);
|
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}/docker-compose.yaml > /dev/null"), 'hidden' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generate_local_persistent_volumes()
|
private function generate_local_persistent_volumes()
|
||||||
@ -1841,13 +1851,25 @@ private function build_image()
|
|||||||
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), 'hidden' => true]);
|
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), 'hidden' => true]);
|
||||||
if ($this->force_rebuild) {
|
if ($this->force_rebuild) {
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir}"), 'hidden' => true,
|
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true,
|
||||||
]);
|
]);
|
||||||
|
$build_command = "docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->build_image_name} {$this->workdir}";
|
||||||
} else {
|
} else {
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->build_image_name} {$this->workdir}"), 'hidden' => true,
|
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true,
|
||||||
]);
|
]);
|
||||||
|
$build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->build_image_name} {$this->workdir}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$base64_build_command = base64_encode($build_command);
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true,
|
||||||
|
]
|
||||||
|
);
|
||||||
$this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm /artifacts/thegameplan.json'), 'hidden' => true]);
|
$this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm /artifacts/thegameplan.json'), 'hidden' => true]);
|
||||||
} else {
|
} else {
|
||||||
if ($this->force_rebuild) {
|
if ($this->force_rebuild) {
|
||||||
@ -1866,7 +1888,6 @@ private function build_image()
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$dockerfile = base64_encode("FROM {$this->application->static_image}
|
$dockerfile = base64_encode("FROM {$this->application->static_image}
|
||||||
WORKDIR /usr/share/nginx/html/
|
WORKDIR /usr/share/nginx/html/
|
||||||
LABEL coolify.deploymentId={$this->deployment_uuid}
|
LABEL coolify.deploymentId={$this->deployment_uuid}
|
||||||
@ -1929,13 +1950,24 @@ private function build_image()
|
|||||||
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), 'hidden' => true]);
|
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), 'hidden' => true]);
|
||||||
if ($this->force_rebuild) {
|
if ($this->force_rebuild) {
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->production_image_name} {$this->workdir}"), 'hidden' => true,
|
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->production_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true,
|
||||||
]);
|
]);
|
||||||
|
$build_command = "docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
|
||||||
} else {
|
} else {
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->production_image_name} {$this->workdir}"), 'hidden' => true,
|
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->production_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true,
|
||||||
]);
|
]);
|
||||||
|
$build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
|
||||||
}
|
}
|
||||||
|
$base64_build_command = base64_encode($build_command);
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true,
|
||||||
|
]
|
||||||
|
);
|
||||||
$this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm /artifacts/thegameplan.json'), 'hidden' => true]);
|
$this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm /artifacts/thegameplan.json'), 'hidden' => true]);
|
||||||
} else {
|
} else {
|
||||||
if ($this->force_rebuild) {
|
if ($this->force_rebuild) {
|
||||||
@ -2059,7 +2091,7 @@ private function add_build_env_variables_to_dockerfile()
|
|||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), 'hidden' => true, 'save' => 'dockerfile',
|
executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), 'hidden' => true, 'save' => 'dockerfile',
|
||||||
]);
|
]);
|
||||||
$dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
|
$dockerfile = collect(str($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
|
||||||
if ($this->pull_request_id === 0) {
|
if ($this->pull_request_id === 0) {
|
||||||
foreach ($this->application->build_environment_variables as $env) {
|
foreach ($this->application->build_environment_variables as $env) {
|
||||||
if (data_get($env, 'is_multiline') === true) {
|
if (data_get($env, 'is_multiline') === true) {
|
||||||
@ -2184,10 +2216,14 @@ public function failed(Throwable $exception): void
|
|||||||
ray($code);
|
ray($code);
|
||||||
if ($code !== 69420) {
|
if ($code !== 69420) {
|
||||||
// 69420 means failed to push the image to the registry, so we don't need to remove the new version as it is the currently running one
|
// 69420 means failed to push the image to the registry, so we don't need to remove the new version as it is the currently running one
|
||||||
$this->application_deployment_queue->addLogEntry('Deployment failed. Removing the new version of your application.', 'stderr');
|
if ($this->application->settings->is_consistent_container_name_enabled || isset($this->application->settings->custom_internal_name)) {
|
||||||
$this->execute_remote_command(
|
// do not remove already running container
|
||||||
["docker rm -f $this->container_name >/dev/null 2>&1", 'hidden' => true, 'ignore_errors' => true]
|
} else {
|
||||||
);
|
$this->application_deployment_queue->addLogEntry('Deployment failed. Removing the new version of your application.', 'stderr');
|
||||||
|
$this->execute_remote_command(
|
||||||
|
["docker rm -f $this->container_name >/dev/null 2>&1", 'hidden' => true, 'ignore_errors' => true]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,7 @@ public function __construct(
|
|||||||
public ApplicationPreview $preview,
|
public ApplicationPreview $preview,
|
||||||
public ProcessStatus $status,
|
public ProcessStatus $status,
|
||||||
public ?string $deployment_uuid = null
|
public ?string $deployment_uuid = null
|
||||||
) {
|
) {}
|
||||||
}
|
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
@ -19,9 +19,7 @@ class CheckLogDrainContainerJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public function __construct(public Server $server)
|
public function __construct(public Server $server) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function middleware(): array
|
public function middleware(): array
|
||||||
{
|
{
|
||||||
|
@ -14,9 +14,7 @@ class CheckResaleLicenseJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct() {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
|
@ -15,9 +15,7 @@ class CleanupHelperContainersJob implements ShouldBeEncrypted, ShouldBeUnique, S
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public function __construct(public Server $server)
|
public function __construct(public Server $server) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
|
@ -16,10 +16,7 @@ class CleanupInstanceStuffsJob implements ShouldBeEncrypted, ShouldBeUnique, Sho
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct() {}
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// public function uniqueId(): string
|
// public function uniqueId(): string
|
||||||
// {
|
// {
|
||||||
|
@ -23,9 +23,7 @@ public function backoff(): int
|
|||||||
return isDev() ? 1 : 3;
|
return isDev() ? 1 : 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(public Server $server)
|
public function __construct(public Server $server) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function middleware(): array
|
public function middleware(): array
|
||||||
{
|
{
|
||||||
|
@ -20,11 +20,10 @@ class CoolifyTask implements ShouldBeEncrypted, ShouldQueue
|
|||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public Activity $activity,
|
public Activity $activity,
|
||||||
public bool $ignore_errors = false,
|
public bool $ignore_errors,
|
||||||
public $call_event_on_finish = null,
|
public $call_event_on_finish,
|
||||||
public $call_event_data = null
|
public $call_event_data,
|
||||||
) {
|
) {}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the job.
|
* Execute the job.
|
||||||
|
@ -98,7 +98,7 @@ public function handle(): void
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$status = Str::of(data_get($this->database, 'status'));
|
$status = str(data_get($this->database, 'status'));
|
||||||
if (! $status->startsWith('running') && $this->database->id !== 0) {
|
if (! $status->startsWith('running') && $this->database->id !== 0) {
|
||||||
ray('database not running');
|
ray('database not running');
|
||||||
|
|
||||||
@ -236,7 +236,7 @@ public function handle(): void
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->backup_dir = backup_dir().'/databases/'.Str::of($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name;
|
$this->backup_dir = backup_dir().'/databases/'.str($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name;
|
||||||
|
|
||||||
if ($this->database->name === 'coolify-db') {
|
if ($this->database->name === 'coolify-db') {
|
||||||
$databasesToBackup = ['coolify'];
|
$databasesToBackup = ['coolify'];
|
||||||
@ -332,8 +332,7 @@ public function handle(): void
|
|||||||
private function backup_standalone_mongodb(string $databaseWithCollections): void
|
private function backup_standalone_mongodb(string $databaseWithCollections): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
ray($this->database->toArray());
|
$url = $this->database->internal_db_url;
|
||||||
$url = $this->database->get_db_url(useInternal: true);
|
|
||||||
if ($databaseWithCollections === 'all') {
|
if ($databaseWithCollections === 'all') {
|
||||||
$commands[] = 'mkdir -p '.$this->backup_dir;
|
$commands[] = 'mkdir -p '.$this->backup_dir;
|
||||||
if (str($this->database->image)->startsWith('mongo:4.0')) {
|
if (str($this->database->image)->startsWith('mongo:4.0')) {
|
||||||
|
@ -18,9 +18,7 @@ class DatabaseBackupStatusJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public $tries = 1;
|
public $tries = 1;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct() {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
@ -28,9 +28,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource, public bool $deleteConfigurations = false)
|
public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource, public bool $deleteConfigurations = false) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
@ -22,9 +22,7 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public ?int $usageBefore = null;
|
public ?int $usageBefore = null;
|
||||||
|
|
||||||
public function __construct(public Server $server)
|
public function __construct(public Server $server) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
@ -37,9 +35,9 @@ public function handle(): void
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if ($isInprogress) {
|
// if ($isInprogress) {
|
||||||
throw new RuntimeException('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping...');
|
// throw new RuntimeException('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping...');
|
||||||
}
|
// }
|
||||||
if (! $this->server->isFunctional()) {
|
if (! $this->server->isFunctional()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -62,7 +60,7 @@ public function handle(): void
|
|||||||
Log::info('No need to clean up '.$this->server->name);
|
Log::info('No need to clean up '.$this->server->name);
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
send_internal_notification('DockerCleanupJob failed with: '.$e->getMessage());
|
// send_internal_notification('DockerCleanupJob failed with: '.$e->getMessage());
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
|
@ -23,9 +23,7 @@ public function backoff(): int
|
|||||||
return isDev() ? 1 : 3;
|
return isDev() ? 1 : 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(public GithubApp $github_app)
|
public function __construct(public GithubApp $github_app) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function middleware(): array
|
public function middleware(): array
|
||||||
{
|
{
|
||||||
|
@ -19,9 +19,7 @@ class InstanceAutoUpdateJob implements ShouldBeEncrypted, ShouldBeUnique, Should
|
|||||||
|
|
||||||
public $tries = 1;
|
public $tries = 1;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct() {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
|
@ -19,9 +19,7 @@ class PullCoolifyImageJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public $timeout = 1000;
|
public $timeout = 1000;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct() {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
|
@ -27,9 +27,7 @@ public function uniqueId(): string
|
|||||||
return $this->server->uuid;
|
return $this->server->uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(public Server $server)
|
public function __construct(public Server $server) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
|
@ -28,9 +28,7 @@ public function uniqueId(): string
|
|||||||
return $this->server->uuid;
|
return $this->server->uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(public Server $server)
|
public function __construct(public Server $server) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
@ -52,7 +50,7 @@ public function handle(): void
|
|||||||
}
|
}
|
||||||
ray('Sentinel image is up to date');
|
ray('Sentinel image is up to date');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
send_internal_notification('PullSentinelImageJob failed with: '.$e->getMessage());
|
// send_internal_notification('PullSentinelImageJob failed with: '.$e->getMessage());
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,7 @@ class PullTemplatesFromCDN implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public $timeout = 10;
|
public $timeout = 10;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct() {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
|
@ -17,9 +17,7 @@ class PullVersionsFromCDN implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public $timeout = 10;
|
public $timeout = 10;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct() {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
|
@ -14,9 +14,7 @@ class SendConfirmationForWaitlistJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public function __construct(public string $email, public string $uuid)
|
public function __construct(public string $email, public string $uuid) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
@ -31,8 +31,7 @@ class SendMessageToDiscordJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
public string $text,
|
public string $text,
|
||||||
public string $webhookUrl
|
public string $webhookUrl
|
||||||
) {
|
) {}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the job.
|
* Execute the job.
|
||||||
|
@ -33,8 +33,7 @@ public function __construct(
|
|||||||
public string $token,
|
public string $token,
|
||||||
public string $chatId,
|
public string $chatId,
|
||||||
public ?string $topicId = null,
|
public ?string $topicId = null,
|
||||||
) {
|
) {}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the job.
|
* Execute the job.
|
||||||
|
@ -16,9 +16,7 @@ class ServerFilesFromServerJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public function __construct(public ServiceApplication|ServiceDatabase|Application $resource)
|
public function __construct(public ServiceApplication|ServiceDatabase|Application $resource) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
@ -24,9 +24,7 @@ public function backoff(): int
|
|||||||
return isDev() ? 1 : 3;
|
return isDev() ? 1 : 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(public Team $team)
|
public function __construct(public Team $team) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function middleware(): array
|
public function middleware(): array
|
||||||
{
|
{
|
||||||
|
@ -25,9 +25,7 @@ public function backoff(): int
|
|||||||
return isDev() ? 1 : 3;
|
return isDev() ? 1 : 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(public Server $server)
|
public function __construct(public Server $server) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function middleware(): array
|
public function middleware(): array
|
||||||
{
|
{
|
||||||
@ -48,12 +46,12 @@ public function handle()
|
|||||||
if ($this->server->isFunctional()) {
|
if ($this->server->isFunctional()) {
|
||||||
$this->cleanup(notify: false);
|
$this->cleanup(notify: false);
|
||||||
$this->remove_unnecessary_coolify_yaml();
|
$this->remove_unnecessary_coolify_yaml();
|
||||||
if (config('coolify.is_sentinel_enabled')) {
|
if ($this->server->isSentinelEnabled()) {
|
||||||
$this->server->checkSentinel();
|
$this->server->checkSentinel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
send_internal_notification('ServerStatusJob failed with: '.$e->getMessage());
|
// send_internal_notification('ServerStatusJob failed with: '.$e->getMessage());
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
|
|
||||||
return handleError($e);
|
return handleError($e);
|
||||||
|
@ -14,9 +14,7 @@ class ServerStorageSaveJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public function __construct(public LocalFileVolume $localFileVolume)
|
public function __construct(public LocalFileVolume $localFileVolume) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
@ -15,9 +15,7 @@ class SubscriptionInvoiceFailedJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public function __construct(protected Team $team)
|
public function __construct(protected Team $team) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
@ -17,8 +17,7 @@ class SubscriptionTrialEndedJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public Team $team
|
public Team $team
|
||||||
) {
|
) {}
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
|
@ -17,8 +17,7 @@ class SubscriptionTrialEndsSoonJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public Team $team
|
public Team $team
|
||||||
) {
|
) {}
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
|
@ -9,9 +9,7 @@
|
|||||||
|
|
||||||
class MaintenanceModeDisabledNotification
|
class MaintenanceModeDisabledNotification
|
||||||
{
|
{
|
||||||
public function __construct()
|
public function __construct() {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(EventsMaintenanceModeDisabled $event): void
|
public function handle(EventsMaintenanceModeDisabled $event): void
|
||||||
{
|
{
|
||||||
|
@ -9,9 +9,7 @@ class ProxyStartedNotification
|
|||||||
{
|
{
|
||||||
public Server $server;
|
public Server $server;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct() {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(ProxyStarted $event): void
|
public function handle(ProxyStarted $event): void
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
class Index extends Component
|
class Index extends Component
|
||||||
{
|
{
|
||||||
protected $listeners = ['serverInstalled' => 'validateServer'];
|
protected $listeners = ['refreshBoardingIndex' => 'validateServer'];
|
||||||
|
|
||||||
public string $currentState = 'welcome';
|
public string $currentState = 'welcome';
|
||||||
|
|
||||||
|
51
app/Livewire/MonacoEditor.php
Normal file
51
app/Livewire/MonacoEditor.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
//use Livewire\Component;
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
|
class MonacoEditor extends Component
|
||||||
|
{
|
||||||
|
protected $listeners = [
|
||||||
|
'configurationChanged' => '$refresh',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public ?string $id,
|
||||||
|
public ?string $name,
|
||||||
|
public ?string $type,
|
||||||
|
public ?string $monacoContent,
|
||||||
|
public ?string $value,
|
||||||
|
public ?string $label,
|
||||||
|
public ?string $placeholder,
|
||||||
|
public bool $required,
|
||||||
|
public bool $disabled,
|
||||||
|
public bool $readonly,
|
||||||
|
public bool $allowTab,
|
||||||
|
public bool $spellcheck,
|
||||||
|
public ?string $helper,
|
||||||
|
public bool $realtimeValidation,
|
||||||
|
public bool $allowToPeak,
|
||||||
|
public string $defaultClass,
|
||||||
|
public string $defaultClassInput,
|
||||||
|
public ?string $language
|
||||||
|
|
||||||
|
) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
if (is_null($this->id)) {
|
||||||
|
$this->id = new Cuid2(7);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_null($this->name)) {
|
||||||
|
$this->name = $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('components.forms.monaco-editor');
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user