commit
2af083b2e5
@ -39,7 +39,7 @@ public function __construct(CoolifyTaskArgs $remoteProcessArgs)
|
||||
|
||||
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);
|
||||
$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);
|
||||
dispatch($job);
|
||||
$this->activity->refresh();
|
||||
return $this->activity;
|
||||
|
@ -21,6 +21,8 @@ class RunRemoteProcess
|
||||
|
||||
public $call_event_on_finish = null;
|
||||
|
||||
public $call_event_data = null;
|
||||
|
||||
protected $time_start;
|
||||
|
||||
protected $current_time;
|
||||
@ -34,7 +36,7 @@ class RunRemoteProcess
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct(Activity $activity, bool $hide_from_output = false, bool $ignore_errors = false, $call_event_on_finish = 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) {
|
||||
@ -45,6 +47,7 @@ public function __construct(Activity $activity, bool $hide_from_output = false,
|
||||
$this->hide_from_output = $hide_from_output;
|
||||
$this->ignore_errors = $ignore_errors;
|
||||
$this->call_event_on_finish = $call_event_on_finish;
|
||||
$this->call_event_data = $call_event_data;
|
||||
}
|
||||
|
||||
public static function decodeOutput(?Activity $activity = null): string
|
||||
@ -111,9 +114,15 @@ public function __invoke(): ProcessResult
|
||||
}
|
||||
if ($this->call_event_on_finish) {
|
||||
try {
|
||||
event(resolve("App\\Events\\$this->call_event_on_finish", [
|
||||
'userId' => $this->activity->causer_id,
|
||||
]));
|
||||
if ($this->call_event_data) {
|
||||
event(resolve("App\\Events\\$this->call_event_on_finish", [
|
||||
"data" => $this->call_event_data,
|
||||
]));
|
||||
} else {
|
||||
event(resolve("App\\Events\\$this->call_event_on_finish", [
|
||||
'userId' => $this->activity->causer_id,
|
||||
]));
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
}
|
||||
|
@ -11,7 +11,12 @@ class CheckConfiguration
|
||||
use AsAction;
|
||||
public function handle(Server $server, bool $reset = false)
|
||||
{
|
||||
$proxy_path = get_proxy_path();
|
||||
$proxyType = $server->proxyType();
|
||||
if ($proxyType === 'NONE') {
|
||||
return 'OK';
|
||||
}
|
||||
$proxy_path = $server->proxyPath();
|
||||
|
||||
$proxy_configuration = instant_remote_process([
|
||||
"mkdir -p $proxy_path",
|
||||
"cat $proxy_path/docker-compose.yml",
|
||||
|
@ -10,6 +10,9 @@ class CheckProxy
|
||||
use AsAction;
|
||||
public function handle(Server $server, $fromUI = false)
|
||||
{
|
||||
if ($server->proxyType() === 'NONE') {
|
||||
return false;
|
||||
}
|
||||
if (!$server->isProxyShouldRun()) {
|
||||
if ($fromUI) {
|
||||
throw new \Exception("Proxy should not run. You selected the Custom Proxy.");
|
||||
|
@ -15,7 +15,7 @@ public function handle(Server $server, ?string $proxy_settings = null)
|
||||
if (is_null($proxy_settings)) {
|
||||
$proxy_settings = CheckConfiguration::run($server, true);
|
||||
}
|
||||
$proxy_path = get_proxy_path();
|
||||
$proxy_path = $server->proxyPath();
|
||||
$docker_compose_yml_base64 = base64_encode($proxy_settings);
|
||||
|
||||
$server->proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Actions\Proxy;
|
||||
|
||||
use App\Events\ProxyStatusChanged;
|
||||
use App\Events\ProxyStarted;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Str;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
@ -15,11 +15,11 @@ public function handle(Server $server, bool $async = true): string|Activity
|
||||
{
|
||||
try {
|
||||
$proxyType = $server->proxyType();
|
||||
if ($proxyType === 'NONE') {
|
||||
if (is_null($proxyType) || $proxyType === 'NONE') {
|
||||
return 'OK';
|
||||
}
|
||||
$commands = collect([]);
|
||||
$proxy_path = get_proxy_path();
|
||||
$proxy_path = $server->proxyPath();
|
||||
$configuration = CheckConfiguration::run($server);
|
||||
if (!$configuration) {
|
||||
throw new \Exception("Configuration is not synced");
|
||||
@ -37,8 +37,10 @@ public function handle(Server $server, bool $async = true): string|Activity
|
||||
"echo 'Proxy started successfully.'"
|
||||
]);
|
||||
} else {
|
||||
$caddfile = "import /dynamic/*.caddy";
|
||||
$commands = $commands->merge([
|
||||
"mkdir -p $proxy_path/dynamic && cd $proxy_path",
|
||||
"echo '$caddfile' > $proxy_path/dynamic/Caddyfile",
|
||||
"echo 'Creating required Docker Compose file.'",
|
||||
"echo 'Pulling docker image.'",
|
||||
'docker compose pull',
|
||||
@ -52,13 +54,14 @@ public function handle(Server $server, bool $async = true): string|Activity
|
||||
}
|
||||
|
||||
if ($async) {
|
||||
$activity = remote_process($commands, $server);
|
||||
$activity = remote_process($commands, $server, callEventOnFinish: 'ProxyStarted', callEventData: $server);
|
||||
return $activity;
|
||||
} else {
|
||||
instant_remote_process($commands, $server);
|
||||
$server->proxy->set('status', 'running');
|
||||
$server->proxy->set('type', $proxyType);
|
||||
$server->save();
|
||||
ProxyStarted::dispatch($server);
|
||||
return 'OK';
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
|
@ -12,9 +12,13 @@ class CleanupDatabase extends Command
|
||||
|
||||
public function handle()
|
||||
{
|
||||
echo "Running database cleanup...\n";
|
||||
if ($this->option('yes')) {
|
||||
echo "Running database cleanup...\n";
|
||||
} else {
|
||||
echo "Running database cleanup in dry-run mode...\n";
|
||||
}
|
||||
$keep_days = 60;
|
||||
|
||||
echo "Keep days: $keep_days\n";
|
||||
// Cleanup failed jobs table
|
||||
$failed_jobs = DB::table('failed_jobs')->where('failed_at', '<', now()->subDays(7));
|
||||
$count = $failed_jobs->count();
|
||||
@ -32,7 +36,7 @@ public function handle()
|
||||
}
|
||||
|
||||
// Cleanup activity_log table
|
||||
$activity_log = DB::table('activity_log')->where('created_at', '<', now()->subDays($keep_days));
|
||||
$activity_log = DB::table('activity_log')->where('created_at', '<', now()->subDays($keep_days))->orderBy('created_at', 'desc')->skip(10);
|
||||
$count = $activity_log->count();
|
||||
echo "Delete $count entries from activity_log.\n";
|
||||
if ($this->option('yes')) {
|
||||
@ -40,7 +44,7 @@ public function handle()
|
||||
}
|
||||
|
||||
// Cleanup application_deployment_queues table
|
||||
$application_deployment_queues = DB::table('application_deployment_queues')->where('created_at', '<', now()->subDays($keep_days));
|
||||
$application_deployment_queues = DB::table('application_deployment_queues')->where('created_at', '<', now()->subDays($keep_days))->orderBy('created_at', 'desc')->skip(10);
|
||||
$count = $application_deployment_queues->count();
|
||||
echo "Delete $count entries from application_deployment_queues.\n";
|
||||
if ($this->option('yes')) {
|
||||
|
@ -35,7 +35,8 @@ public function handle()
|
||||
$this->call('cleanup:queue');
|
||||
$this->call('cleanup:stucked-resources');
|
||||
try {
|
||||
setup_dynamic_configuration();
|
||||
$server = Server::find(0)->first();
|
||||
$server->setupDynamicProxyConfiguration();
|
||||
} catch (\Throwable $e) {
|
||||
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
|
||||
}
|
||||
|
@ -100,6 +100,12 @@ private function process_file($file)
|
||||
} else {
|
||||
$tags = null;
|
||||
}
|
||||
$port = collect(preg_grep('/^# port:/', explode("\n", $content)))->values();
|
||||
if ($port->count() > 0) {
|
||||
$port = str($port[0])->after('# port:')->trim()->value();
|
||||
} else {
|
||||
$port = null;
|
||||
}
|
||||
$json = Yaml::parse($content);
|
||||
$yaml = base64_encode(Yaml::dump($json, 10, 2));
|
||||
$payload = [
|
||||
@ -111,6 +117,9 @@ private function process_file($file)
|
||||
'logo' => $logo,
|
||||
'minversion' => $minversion,
|
||||
];
|
||||
if ($port) {
|
||||
$payload['port'] = $port;
|
||||
}
|
||||
if ($env_file) {
|
||||
$env_file_content = file_get_contents(base_path("templates/compose/$env_file"));
|
||||
$env_file_base64 = base64_encode($env_file_content);
|
||||
|
@ -21,6 +21,7 @@ public function __construct(
|
||||
public ?string $status = null ,
|
||||
public bool $ignore_errors = false,
|
||||
public $call_event_on_finish = null,
|
||||
public $call_event_data = null
|
||||
) {
|
||||
if(is_null($status)){
|
||||
$this->status = ProcessStatus::QUEUED->value;
|
||||
|
16
app/Events/ProxyStarted.php
Normal file
16
app/Events/ProxyStarted.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ProxyStarted
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
public function __construct(public $data)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -9,13 +9,33 @@
|
||||
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 Illuminate\Support\Collection;
|
||||
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();
|
||||
@ -27,7 +47,7 @@ public function deploy(Request $request)
|
||||
return response()->json(['error' => 'You can only use uuid or tag, not both.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
||||
}
|
||||
if (is_null($teamId)) {
|
||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||
return invalid_token();
|
||||
}
|
||||
if ($tags) {
|
||||
return $this->by_tags($tags, $teamId, $force);
|
||||
@ -44,16 +64,22 @@ private function by_uuids(string $uuid, int $teamId, bool $force = false)
|
||||
if (count($uuids) === 0) {
|
||||
return response()->json(['error' => 'No UUIDs provided.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
||||
}
|
||||
$message = collect([]);
|
||||
$deployments = collect();
|
||||
$payload = collect();
|
||||
foreach ($uuids as $uuid) {
|
||||
$resource = getResourceByUuid($uuid, $teamId);
|
||||
if ($resource) {
|
||||
$return_message = $this->deploy_resource($resource, $force);
|
||||
$message = $message->merge($return_message);
|
||||
['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 ($message->count() > 0) {
|
||||
return response()->json(['message' => $message->toArray()], 200);
|
||||
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/deploy-webhook'], 404);
|
||||
}
|
||||
@ -66,10 +92,12 @@ public function by_tags(string $tags, int $team_id, bool $force = false)
|
||||
return response()->json(['error' => 'No TAGs provided.', 'docs' => 'https://coolify.io/docs/api/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.");
|
||||
// $message->push("Tag {$tag} not found.");
|
||||
continue;
|
||||
}
|
||||
$applications = $found_tag->applications()->get();
|
||||
@ -79,83 +107,78 @@ public function by_tags(string $tags, int $team_id, bool $force = false)
|
||||
continue;
|
||||
}
|
||||
foreach ($applications as $resource) {
|
||||
$return_message = $this->deploy_resource($resource, $force);
|
||||
['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) {
|
||||
$return_message = $this->deploy_resource($resource, $force);
|
||||
['message' => $return_message] = $this->deploy_resource($resource, $force);
|
||||
$message = $message->merge($return_message);
|
||||
}
|
||||
}
|
||||
ray($message);
|
||||
if ($message->count() > 0) {
|
||||
return response()->json(['message' => $message->toArray()], 200);
|
||||
$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.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
||||
return response()->json(['error' => "No resources found with this tag.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
||||
}
|
||||
public function deploy_resource($resource, bool $force = false): Collection
|
||||
public function deploy_resource($resource, bool $force = false): array
|
||||
{
|
||||
$message = collect([]);
|
||||
$message = null;
|
||||
$deployment_uuid = null;
|
||||
if (gettype($resource) !== 'object') {
|
||||
return $message->push("Resource ($resource) not found.");
|
||||
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: new Cuid2(7),
|
||||
deployment_uuid: $deployment_uuid,
|
||||
force_rebuild: $force,
|
||||
);
|
||||
$message->push("Application {$resource->name} deployment queued.");
|
||||
$message = "Application {$resource->name} deployment queued.";
|
||||
} else if ($type === 'App\Models\StandalonePostgresql') {
|
||||
if (str($resource->status)->startsWith('running')) {
|
||||
$message->push("Database {$resource->name} already running.");
|
||||
}
|
||||
StartPostgresql::run($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message->push("Database {$resource->name} started.");
|
||||
$message = "Database {$resource->name} started.";
|
||||
} else if ($type === 'App\Models\StandaloneRedis') {
|
||||
if (str($resource->status)->startsWith('running')) {
|
||||
$message->push("Database {$resource->name} already running.");
|
||||
}
|
||||
StartRedis::run($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message->push("Database {$resource->name} started.");
|
||||
$message = "Database {$resource->name} started.";
|
||||
} else if ($type === 'App\Models\StandaloneMongodb') {
|
||||
if (str($resource->status)->startsWith('running')) {
|
||||
$message->push("Database {$resource->name} already running.");
|
||||
}
|
||||
StartMongodb::run($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message->push("Database {$resource->name} started.");
|
||||
$message = "Database {$resource->name} started.";
|
||||
} else if ($type === 'App\Models\StandaloneMysql') {
|
||||
if (str($resource->status)->startsWith('running')) {
|
||||
$message->push("Database {$resource->name} already running.");
|
||||
}
|
||||
StartMysql::run($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message->push("Database {$resource->name} started.");
|
||||
$message = "Database {$resource->name} started.";
|
||||
} else if ($type === 'App\Models\StandaloneMariadb') {
|
||||
if (str($resource->status)->startsWith('running')) {
|
||||
$message->push("Database {$resource->name} already running.");
|
||||
}
|
||||
StartMariadb::run($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message->push("Database {$resource->name} started.");
|
||||
$message = "Database {$resource->name} started.";
|
||||
} else if ($type === 'App\Models\Service') {
|
||||
StartService::run($resource);
|
||||
$message->push("Service {$resource->name} started. It could take a while, be patient.");
|
||||
$message = "Service {$resource->name} started. It could take a while, be patient.";
|
||||
}
|
||||
return $message;
|
||||
return ['message' => $message, 'deployment_uuid' => $deployment_uuid];
|
||||
}
|
||||
}
|
||||
|
104
app/Http/Controllers/Api/Domains.php
Normal file
104
app/Http/Controllers/Api/Domains.php
Normal file
@ -0,0 +1,104 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ public function projects(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||
return invalid_token();
|
||||
}
|
||||
$projects = ModelsProject::whereTeamId($teamId)->select('id', 'name', 'uuid')->get();
|
||||
return response()->json($projects);
|
||||
@ -21,7 +21,7 @@ public function project_by_uuid(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||
return invalid_token();
|
||||
}
|
||||
$project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first()->load(['environments']);
|
||||
return response()->json($project);
|
||||
@ -30,7 +30,7 @@ public function environment_details(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||
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']);
|
||||
|
38
app/Http/Controllers/Api/Resources.php
Normal file
38
app/Http/Controllers/Api/Resources.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Project;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class Resources extends Controller
|
||||
{
|
||||
public function resources(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$projects = Project::where('team_id', $teamId)->get();
|
||||
$resources = collect();
|
||||
$resources->push($projects->pluck('applications')->flatten());
|
||||
$resources->push($projects->pluck('services')->flatten());
|
||||
foreach (collect(DATABASE_TYPES) as $db) {
|
||||
$resources->push($projects->pluck(str($db)->plural(2))->flatten());
|
||||
}
|
||||
$resources = $resources->flatten();
|
||||
$resources = $resources->map(function ($resource) {
|
||||
$payload = $resource->toArray();
|
||||
if ($resource->getMorphClass() === 'App\Models\Service') {
|
||||
$payload['status'] = $resource->status();
|
||||
} else {
|
||||
$payload['status'] = $resource->status;
|
||||
}
|
||||
$payload['type'] = $resource->type();
|
||||
return $payload;
|
||||
});
|
||||
return response()->json($resources);
|
||||
}
|
||||
|
||||
}
|
@ -12,7 +12,7 @@ public function servers(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||
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;
|
||||
@ -26,7 +26,7 @@ public function server_by_uuid(Request $request)
|
||||
$with_resources = $request->query('resources');
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||
return invalid_token();
|
||||
}
|
||||
$server = ModelsServer::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
||||
if (is_null($server)) {
|
||||
|
65
app/Http/Controllers/Api/Team.php
Normal file
65
app/Http/Controllers/Api/Team.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?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/team-by-id"], 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/team-by-id-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);
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@ public function handle(Request $request, Closure $next): Response
|
||||
}
|
||||
if (!auth()->user() || !isCloud() || isInstanceAdmin()) {
|
||||
if (!isCloud() && showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
||||
return redirect()->route('boarding');
|
||||
return redirect()->route('onboarding');
|
||||
}
|
||||
return $next($request);
|
||||
}
|
||||
@ -43,7 +43,7 @@ public function handle(Request $request, Closure $next): Response
|
||||
if (Str::startsWith($request->path(), 'invitations')) {
|
||||
return $next($request);
|
||||
}
|
||||
return redirect()->route('boarding');
|
||||
return redirect()->route('onboarding');
|
||||
}
|
||||
if (auth()->user()->hasVerifiedEmail() && $request->path() === 'verify') {
|
||||
return redirect(RouteServiceProvider::HOME);
|
||||
|
@ -24,6 +24,7 @@
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Sleep;
|
||||
use Illuminate\Support\Str;
|
||||
use RuntimeException;
|
||||
use Spatie\Url\Url;
|
||||
@ -92,6 +93,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
private ?string $buildTarget = null;
|
||||
private Collection $saved_outputs;
|
||||
private ?string $full_healthcheck_url = null;
|
||||
private bool $custom_healthcheck_found = false;
|
||||
|
||||
private string $serverUser = 'root';
|
||||
private string $serverUserHomeDir = '/root';
|
||||
@ -239,6 +241,7 @@ public function handle(): void
|
||||
}
|
||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||
$this->application->isConfigurationChanged(false);
|
||||
$this->run_post_deployment_command();
|
||||
return;
|
||||
} else if ($this->pull_request_id !== 0) {
|
||||
$this->deploy_pull_request();
|
||||
@ -273,6 +276,7 @@ public function handle(): void
|
||||
ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::FINISHED);
|
||||
}
|
||||
}
|
||||
$this->run_post_deployment_command();
|
||||
$this->application->isConfigurationChanged(true);
|
||||
} catch (Exception $e) {
|
||||
if ($this->pull_request_id !== 0 && $this->application->is_github_based()) {
|
||||
@ -294,13 +298,13 @@ public function handle(): void
|
||||
"ignore_errors" => true,
|
||||
]
|
||||
);
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"docker image prune -f >/dev/null 2>&1",
|
||||
"hidden" => true,
|
||||
"ignore_errors" => true,
|
||||
]
|
||||
);
|
||||
// $this->execute_remote_command(
|
||||
// [
|
||||
// "docker image prune -f >/dev/null 2>&1",
|
||||
// "hidden" => true,
|
||||
// "ignore_errors" => true,
|
||||
// ]
|
||||
// );
|
||||
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
|
||||
}
|
||||
}
|
||||
@ -456,6 +460,7 @@ private function deploy_dockerfile_buildpack()
|
||||
$this->check_git_if_build_needed();
|
||||
$this->set_base_dir();
|
||||
$this->generate_image_names();
|
||||
$this->clone_repository();
|
||||
if (!$this->force_rebuild) {
|
||||
$this->check_image_locally_or_remotely();
|
||||
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
|
||||
@ -467,7 +472,6 @@ private function deploy_dockerfile_buildpack()
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->clone_repository();
|
||||
$this->cleanup_git();
|
||||
$this->generate_compose_file();
|
||||
$this->generate_build_env_variables();
|
||||
@ -775,7 +779,7 @@ private function health_check()
|
||||
if ($this->server->isSwarm()) {
|
||||
// Implement healthcheck for swarm
|
||||
} else {
|
||||
if ($this->application->isHealthcheckDisabled()) {
|
||||
if ($this->application->isHealthcheckDisabled() && $this->custom_healthcheck_found === false) {
|
||||
$this->newVersionIsHealthy = true;
|
||||
return;
|
||||
}
|
||||
@ -808,7 +812,7 @@ private function health_check()
|
||||
break;
|
||||
}
|
||||
$counter++;
|
||||
sleep($this->application->health_check_interval);
|
||||
Sleep::for($this->application->health_check_interval)->seconds();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -873,8 +877,8 @@ private function prepare_builder_image()
|
||||
[
|
||||
"command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->basedir}")
|
||||
],
|
||||
|
||||
);
|
||||
$this->run_pre_deployment_command();
|
||||
}
|
||||
private function deploy_to_additional_destinations()
|
||||
{
|
||||
@ -1077,7 +1081,10 @@ private function generate_env_variables()
|
||||
private function generate_compose_file()
|
||||
{
|
||||
$ports = $this->application->settings->is_static ? [80] : $this->application->ports_exposes_array;
|
||||
|
||||
$onlyPort = null;
|
||||
if (count($ports) > 0) {
|
||||
$onlyPort = $ports[0];
|
||||
}
|
||||
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||
$environment_variables = $this->generate_environment_variables($ports);
|
||||
@ -1088,6 +1095,25 @@ private function generate_compose_file()
|
||||
$labels = $labels->filter(function ($value, $key) {
|
||||
return !Str::startsWith($value, 'coolify.');
|
||||
});
|
||||
$found_caddy_labels = $labels->filter(function ($value, $key) {
|
||||
return Str::startsWith($value, 'caddy_');
|
||||
});
|
||||
if ($found_caddy_labels->count() === 0) {
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$domains = str(data_get($this->preview, 'fqdn'))->explode(',');
|
||||
} else {
|
||||
$domains = str(data_get($this->application, 'fqdn'))->explode(',');
|
||||
}
|
||||
$labels = $labels->merge(fqdnLabelsForCaddy(
|
||||
network: $this->application->destination->network,
|
||||
uuid: $this->application->uuid,
|
||||
domains: $domains,
|
||||
onlyPort: $onlyPort,
|
||||
is_force_https_enabled: $this->application->isForceHttpsEnabled(),
|
||||
is_gzip_enabled: $this->application->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $this->application->isStripprefixEnabled()
|
||||
));
|
||||
}
|
||||
$this->application->custom_labels = base64_encode($labels->implode("\n"));
|
||||
$this->application->save();
|
||||
} else {
|
||||
@ -1097,6 +1123,18 @@ private function generate_compose_file()
|
||||
$labels = collect(generateLabelsApplication($this->application, $this->preview));
|
||||
}
|
||||
$labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray();
|
||||
|
||||
// Check for custom HEALTHCHECK
|
||||
$this->custom_healthcheck_found = false;
|
||||
if ($this->application->build_pack === 'dockerfile' || $this->application->dockerfile) {
|
||||
$this->execute_remote_command([
|
||||
executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), "hidden" => true, "save" => 'dockerfile', "ignore_errors" => true
|
||||
]);
|
||||
$dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
|
||||
if (str($dockerfile)->contains('HEALTHCHECK')) {
|
||||
$this->custom_healthcheck_found = true;
|
||||
}
|
||||
}
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
'services' => [
|
||||
@ -1109,16 +1147,6 @@ private function generate_compose_file()
|
||||
'networks' => [
|
||||
$this->destination->network,
|
||||
],
|
||||
'healthcheck' => [
|
||||
'test' => [
|
||||
'CMD-SHELL',
|
||||
$this->generate_healthcheck_commands()
|
||||
],
|
||||
'interval' => $this->application->health_check_interval . 's',
|
||||
'timeout' => $this->application->health_check_timeout . 's',
|
||||
'retries' => $this->application->health_check_retries,
|
||||
'start_period' => $this->application->health_check_start_period . 's'
|
||||
],
|
||||
'mem_limit' => $this->application->limits_memory,
|
||||
'memswap_limit' => $this->application->limits_memory_swap,
|
||||
'mem_swappiness' => $this->application->limits_memory_swappiness,
|
||||
@ -1135,6 +1163,18 @@ private function generate_compose_file()
|
||||
]
|
||||
]
|
||||
];
|
||||
if (!$this->custom_healthcheck_found) {
|
||||
$docker_compose['services'][$this->container_name]['healthcheck'] = [
|
||||
'test' => [
|
||||
'CMD-SHELL',
|
||||
$this->generate_healthcheck_commands()
|
||||
],
|
||||
'interval' => $this->application->health_check_interval . 's',
|
||||
'timeout' => $this->application->health_check_timeout . 's',
|
||||
'retries' => $this->application->health_check_retries,
|
||||
'start_period' => $this->application->health_check_start_period . 's'
|
||||
];
|
||||
}
|
||||
if (!is_null($this->application->limits_cpuset)) {
|
||||
data_set($docker_compose, 'services.' . $this->container_name . '.cpuset', $this->application->limits_cpuset);
|
||||
}
|
||||
@ -1235,24 +1275,6 @@ private function generate_compose_file()
|
||||
|
||||
if ($this->pull_request_id === 0) {
|
||||
if ((bool)$this->application->settings->is_consistent_container_name_enabled) {
|
||||
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
|
||||
if (count($custom_compose) > 0) {
|
||||
$ipv4 = data_get($custom_compose, 'ip.0');
|
||||
$ipv6 = data_get($custom_compose, 'ip6.0');
|
||||
data_forget($custom_compose, 'ip');
|
||||
data_forget($custom_compose, 'ip6');
|
||||
if ($ipv4 || $ipv6) {
|
||||
data_forget($docker_compose['services'][$this->container_name], 'networks');
|
||||
}
|
||||
if ($ipv4) {
|
||||
$docker_compose['services'][$this->container_name]['networks'][$this->destination->network]['ipv4_address'] = $ipv4;
|
||||
}
|
||||
if ($ipv6) {
|
||||
$docker_compose['services'][$this->container_name]['networks'][$this->destination->network]['ipv6_address'] = $ipv6;
|
||||
}
|
||||
$docker_compose['services'][$this->container_name] = array_merge_recursive($docker_compose['services'][$this->container_name], $custom_compose);
|
||||
}
|
||||
} else {
|
||||
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
|
||||
data_forget($docker_compose, 'services.' . $this->container_name);
|
||||
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
|
||||
@ -1272,6 +1294,24 @@ private function generate_compose_file()
|
||||
}
|
||||
$docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose);
|
||||
}
|
||||
} else {
|
||||
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
|
||||
if (count($custom_compose) > 0) {
|
||||
$ipv4 = data_get($custom_compose, 'ip.0');
|
||||
$ipv6 = data_get($custom_compose, 'ip6.0');
|
||||
data_forget($custom_compose, 'ip');
|
||||
data_forget($custom_compose, 'ip6');
|
||||
if ($ipv4 || $ipv6) {
|
||||
data_forget($docker_compose['services'][$this->container_name], 'networks');
|
||||
}
|
||||
if ($ipv4) {
|
||||
$docker_compose['services'][$this->container_name]['networks'][$this->destination->network]['ipv4_address'] = $ipv4;
|
||||
}
|
||||
if ($ipv6) {
|
||||
$docker_compose['services'][$this->container_name]['networks'][$this->destination->network]['ipv6_address'] = $ipv6;
|
||||
}
|
||||
$docker_compose['services'][$this->container_name] = array_merge_recursive($docker_compose['services'][$this->container_name], $custom_compose);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1652,16 +1692,69 @@ private function add_build_env_variables_to_dockerfile()
|
||||
]);
|
||||
}
|
||||
|
||||
private function run_pre_deployment_command()
|
||||
{
|
||||
if (empty($this->application->pre_deployment_command)) {
|
||||
return;
|
||||
}
|
||||
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
|
||||
if ($containers->count() == 0) {
|
||||
return;
|
||||
}
|
||||
$this->application_deployment_queue->addLogEntry("Executing pre-deployment command (see debug log for output): {$this->application->pre_deployment_command}");
|
||||
|
||||
foreach ($containers as $container) {
|
||||
$containerName = data_get($container, 'Names');
|
||||
if ($containers->count() == 1 || str_starts_with($containerName, $this->application->pre_deployment_command_container . '-' . $this->application->uuid)) {
|
||||
$cmd = 'sh -c "' . str_replace('"', '\"', $this->application->pre_deployment_command) . '"';
|
||||
$exec = "docker exec {$containerName} {$cmd}";
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, $exec), 'hidden' => true
|
||||
],
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException('Pre-deployment command: Could not find a valid container. Is the container name correct?');
|
||||
}
|
||||
|
||||
private function run_post_deployment_command()
|
||||
{
|
||||
if (empty($this->application->post_deployment_command)) {
|
||||
return;
|
||||
}
|
||||
$this->application_deployment_queue->addLogEntry("Executing post-deployment command (see debug log for output): {$this->application->post_deployment_command}");
|
||||
|
||||
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
|
||||
foreach ($containers as $container) {
|
||||
$containerName = data_get($container, 'Names');
|
||||
if ($containers->count() == 1 || str_starts_with($containerName, $this->application->post_deployment_command_container . '-' . $this->application->uuid)) {
|
||||
$cmd = 'sh -c "' . str_replace('"', '\"', $this->application->post_deployment_command) . '"';
|
||||
$exec = "docker exec {$containerName} {$cmd}";
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, $exec), 'hidden' => true
|
||||
],
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException('Post-deployment command: Could not find a valid container. Is the container name correct?');
|
||||
}
|
||||
|
||||
private function next(string $status)
|
||||
{
|
||||
queue_next_deployment($this->application);
|
||||
// If the deployment is cancelled by the user, don't update the status
|
||||
if ($this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value && $this->application_deployment_queue->status !== ApplicationDeploymentStatus::FAILED->value) {
|
||||
if (
|
||||
$this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value && $this->application_deployment_queue->status !== ApplicationDeploymentStatus::FAILED->value
|
||||
) {
|
||||
$this->application_deployment_queue->update([
|
||||
'status' => $status,
|
||||
]);
|
||||
}
|
||||
if ($status === ApplicationDeploymentStatus::FAILED->value) {
|
||||
if ($this->application_deployment_queue->status === ApplicationDeploymentStatus::FAILED->value) {
|
||||
$this->application->environment->project->team?->notify(new DeploymentFailed($this->application, $this->deployment_uuid, $this->preview));
|
||||
return;
|
||||
}
|
||||
|
@ -21,7 +21,8 @@ class CoolifyTask implements ShouldQueue, ShouldBeEncrypted
|
||||
public function __construct(
|
||||
public Activity $activity,
|
||||
public bool $ignore_errors = false,
|
||||
public $call_event_on_finish = null
|
||||
public $call_event_on_finish = null,
|
||||
public $call_event_data = null
|
||||
) {
|
||||
}
|
||||
|
||||
@ -33,7 +34,8 @@ public function handle(): void
|
||||
$remote_process = resolve(RunRemoteProcess::class, [
|
||||
'activity' => $this->activity,
|
||||
'ignore_errors' => $this->ignore_errors,
|
||||
'call_event_on_finish' => $this->call_event_on_finish
|
||||
'call_event_on_finish' => $this->call_event_on_finish,
|
||||
'call_event_data' => $this->call_event_data
|
||||
]);
|
||||
|
||||
$remote_process();
|
||||
|
26
app/Jobs/ServerFilesFromServerJob.php
Normal file
26
app/Jobs/ServerFilesFromServerJob.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ServerFilesFromServerJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
|
||||
public function __construct(public ServiceApplication|ServiceDatabase $service)
|
||||
{
|
||||
}
|
||||
public function handle()
|
||||
{
|
||||
$this->service->getFilesFromServer(isInit: true);
|
||||
}
|
||||
}
|
26
app/Jobs/ServerStorageSaveJob.php
Normal file
26
app/Jobs/ServerStorageSaveJob.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\LocalFileVolume;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ServerStorageSaveJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
|
||||
public function __construct(public LocalFileVolume $localFileVolume)
|
||||
{
|
||||
}
|
||||
public function handle()
|
||||
{
|
||||
$this->localFileVolume->saveStorageOnServer();
|
||||
}
|
||||
|
||||
}
|
21
app/Listeners/ProxyStartedNotification.php
Normal file
21
app/Listeners/ProxyStartedNotification.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\ProxyStarted;
|
||||
use App\Models\Server;
|
||||
|
||||
class ProxyStartedNotification
|
||||
{
|
||||
public Server $server;
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function handle(ProxyStarted $event): void
|
||||
{
|
||||
$this->server = data_get($event, 'data');
|
||||
$this->server->setupDefault404Redirect();
|
||||
$this->server->setupDynamicProxyConfiguration();
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Livewire\Component;
|
||||
|
||||
class Index extends Component
|
||||
|
@ -73,7 +73,7 @@ public function explanation()
|
||||
|
||||
public function restartBoarding()
|
||||
{
|
||||
return redirect()->route('boarding');
|
||||
return redirect()->route('onboarding');
|
||||
}
|
||||
public function skipBoarding()
|
||||
{
|
||||
@ -126,6 +126,7 @@ public function selectExistingServer()
|
||||
}
|
||||
public function getProxyType()
|
||||
{
|
||||
// Set Default Proxy Type
|
||||
$this->selectProxy(ProxyTypes::TRAEFIK_V2->value);
|
||||
// $proxyTypeSet = $this->createdServer->proxy->type;
|
||||
// if (!$proxyTypeSet) {
|
||||
|
@ -17,10 +17,14 @@ public function testEvent()
|
||||
{
|
||||
$this->dispatch('success', 'Realtime events configured!');
|
||||
}
|
||||
public function disable()
|
||||
public function disableSponsorship()
|
||||
{
|
||||
auth()->user()->update(['is_notification_sponsorship_enabled' => false]);
|
||||
}
|
||||
public function disableNotifications()
|
||||
{
|
||||
auth()->user()->update(['is_notification_notifications_enabled' => false]);
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.layout-popups');
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Livewire\Profile;
|
||||
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Livewire\Attributes\Validate;
|
||||
use Livewire\Component;
|
||||
|
||||
@ -10,6 +11,13 @@ class Index extends Component
|
||||
public int $userId;
|
||||
public string $email;
|
||||
|
||||
#[Validate('required')]
|
||||
public string $current_password;
|
||||
#[Validate('required|min:8')]
|
||||
public string $new_password;
|
||||
#[Validate('required|min:8|same:new_password')]
|
||||
public string $new_password_confirmation;
|
||||
|
||||
#[Validate('required')]
|
||||
public string $name;
|
||||
public function mount()
|
||||
@ -19,7 +27,6 @@ public function mount()
|
||||
$this->email = auth()->user()->email;
|
||||
}
|
||||
public function submit()
|
||||
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
@ -27,7 +34,30 @@ public function submit()
|
||||
'name' => $this->name,
|
||||
]);
|
||||
|
||||
$this->dispatch('success', 'Profile updated');
|
||||
$this->dispatch('success', 'Profile updated.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function resetPassword()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
if (!Hash::check($this->current_password, auth()->user()->password)) {
|
||||
$this->dispatch('error', 'Current password is incorrect.');
|
||||
return;
|
||||
}
|
||||
if ($this->new_password !== $this->new_password_confirmation) {
|
||||
$this->dispatch('error', 'The two new passwords does not match.');
|
||||
return;
|
||||
}
|
||||
auth()->user()->update([
|
||||
'password' => Hash::make($this->new_password),
|
||||
]);
|
||||
$this->dispatch('success', 'Password updated.');
|
||||
$this->current_password = '';
|
||||
$this->new_password = '';
|
||||
$this->new_password_confirmation = '';
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
@ -66,6 +66,10 @@ class General extends Component
|
||||
'application.docker_compose_custom_build_command' => 'nullable',
|
||||
'application.custom_labels' => 'nullable',
|
||||
'application.custom_docker_run_options' => 'nullable',
|
||||
'application.pre_deployment_command' => 'nullable',
|
||||
'application.pre_deployment_command_container' => 'nullable',
|
||||
'application.post_deployment_command' => 'nullable',
|
||||
'application.post_deployment_command_container' => 'nullable',
|
||||
'application.settings.is_static' => 'boolean|required',
|
||||
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
|
||||
'application.settings.is_build_server_enabled' => 'boolean|required',
|
||||
@ -112,6 +116,10 @@ public function mount()
|
||||
} catch (\Throwable $e) {
|
||||
$this->dispatch('error', $e->getMessage());
|
||||
}
|
||||
if ($this->application->build_pack === 'dockercompose') {
|
||||
$this->application->fqdn = null;
|
||||
$this->application->settings->save();
|
||||
}
|
||||
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
|
||||
|
||||
$this->ports_exposes = $this->application->ports_exposes;
|
||||
@ -120,7 +128,7 @@ public function mount()
|
||||
}
|
||||
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
|
||||
$this->customLabels = $this->application->parseContainerLabels();
|
||||
if (!$this->customLabels && $this->application->destination->server->proxyType() === 'TRAEFIK_V2') {
|
||||
if (!$this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') {
|
||||
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
|
||||
$this->application->custom_labels = base64_encode($this->customLabels);
|
||||
$this->application->save();
|
||||
@ -163,7 +171,12 @@ public function generateDomain(string $serviceName)
|
||||
}
|
||||
return $domain;
|
||||
}
|
||||
|
||||
public function updatedApplicationBaseDirectory() {
|
||||
raY('asdf');
|
||||
if ($this->application->build_pack === 'dockercompose') {
|
||||
$this->loadComposeFile();
|
||||
}
|
||||
}
|
||||
public function updatedApplicationBuildPack()
|
||||
{
|
||||
if ($this->application->build_pack !== 'nixpacks') {
|
||||
@ -211,12 +224,11 @@ public function updatedApplicationFqdn()
|
||||
$this->application->fqdn = $this->application->fqdn->unique()->implode(',');
|
||||
$this->application->save();
|
||||
$this->resetDefaultLabels(false);
|
||||
// $this->dispatch('success', 'Labels reset to default!');
|
||||
}
|
||||
public function submit($showToaster = true)
|
||||
{
|
||||
try {
|
||||
if (!$this->customLabels && $this->application->destination->server->proxyType() === 'TRAEFIK_V2') {
|
||||
if (!$this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') {
|
||||
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
|
||||
$this->application->custom_labels = base64_encode($this->customLabels);
|
||||
$this->application->save();
|
||||
|
@ -10,7 +10,8 @@ class Execution extends Component
|
||||
public $backup;
|
||||
public $executions;
|
||||
public $s3s;
|
||||
public function mount() {
|
||||
public function mount()
|
||||
{
|
||||
$backup_uuid = request()->route('backup_uuid');
|
||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||
if (!$project) {
|
||||
@ -34,6 +35,11 @@ public function mount() {
|
||||
$this->executions = $executions;
|
||||
$this->s3s = currentTeam()->s3s;
|
||||
}
|
||||
public function cleanupFailed()
|
||||
{
|
||||
$this->backup->executions()->where('status', 'failed')->delete();
|
||||
$this->dispatch('refreshBackupExecutions');
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.database.backup.execution');
|
||||
|
@ -34,7 +34,7 @@ public function deleteBackup($exeuctionId)
|
||||
}
|
||||
$execution->delete();
|
||||
$this->dispatch('success', 'Backup deleted.');
|
||||
$this->dispatch('refreshBackupExecutions');
|
||||
$this->refreshBackupExecutions();
|
||||
}
|
||||
public function download($exeuctionId)
|
||||
{
|
||||
@ -65,6 +65,6 @@ public function download($exeuctionId)
|
||||
}
|
||||
public function refreshBackupExecutions(): void
|
||||
{
|
||||
$this->executions = data_get($this->backup, 'executions', []);
|
||||
$this->executions = $this->backup->executions()->get()->sortByDesc('created_at');
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ class DockerCompose extends Component
|
||||
public array $query;
|
||||
public function mount()
|
||||
{
|
||||
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->query = request()->query();
|
||||
if (isDev()) {
|
||||
@ -40,12 +39,17 @@ public function mount()
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
$server_id = $this->query['server_id'];
|
||||
try {
|
||||
$this->validate([
|
||||
'dockerComposeRaw' => 'required'
|
||||
]);
|
||||
$this->dockerComposeRaw = Yaml::dump(Yaml::parse($this->dockerComposeRaw), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
|
||||
$server_id = $this->query['server_id'];
|
||||
|
||||
$isValid = validateComposeFile($this->dockerComposeRaw, $server_id);
|
||||
if ($isValid !== 'OK') {
|
||||
return $this->dispatch('error', "Invalid docker-compose file.\n$isValid");
|
||||
}
|
||||
|
||||
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
||||
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
|
||||
@ -74,7 +78,6 @@ public function submit()
|
||||
'environment_name' => $environment->name,
|
||||
'project_uuid' => $project->uuid,
|
||||
]);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
@ -38,8 +38,12 @@ public function mount()
|
||||
}
|
||||
public function check_status()
|
||||
{
|
||||
dispatch_sync(new ContainerStatusJob($this->service->server));
|
||||
$this->dispatch('refresh')->self();
|
||||
$this->dispatch('serviceStatusChanged');
|
||||
try {
|
||||
dispatch_sync(new ContainerStatusJob($this->service->server));
|
||||
$this->dispatch('refresh')->self();
|
||||
$this->dispatch('serviceStatusChanged');
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,6 @@ public function mount()
|
||||
}
|
||||
public function saveCompose($raw)
|
||||
{
|
||||
|
||||
$this->service->docker_compose_raw = $raw;
|
||||
$this->submit();
|
||||
}
|
||||
@ -55,6 +54,10 @@ public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$isValid = validateComposeFile($this->service->docker_compose_raw, $this->service->server->id);
|
||||
if ($isValid !== 'OK') {
|
||||
throw new \Exception("Invalid docker-compose file.\n$isValid");
|
||||
}
|
||||
$this->service->save();
|
||||
$this->service->saveExtraFields($this->fields);
|
||||
$this->service->parse();
|
||||
|
@ -35,7 +35,7 @@ public function submit()
|
||||
if (!$this->resource->limits_memory_swap) {
|
||||
$this->resource->limits_memory_swap = "0";
|
||||
}
|
||||
if (!$this->resource->limits_memory_swappiness) {
|
||||
if (is_null($this->resource->limits_memory_swappiness)) {
|
||||
$this->resource->limits_memory_swappiness = "60";
|
||||
}
|
||||
if (!$this->resource->limits_memory_reservation) {
|
||||
@ -47,7 +47,7 @@ public function submit()
|
||||
if ($this->resource->limits_cpuset === "") {
|
||||
$this->resource->limits_cpuset = null;
|
||||
}
|
||||
if (!$this->resource->limits_cpu_shares) {
|
||||
if (is_null($this->resource->limits_cpu_shares)) {
|
||||
$this->resource->limits_cpu_shares = 1024;
|
||||
}
|
||||
$this->validate();
|
||||
|
@ -45,7 +45,7 @@ public function cloneTo($destination_id)
|
||||
'destination_id' => $new_destination->id,
|
||||
]);
|
||||
$new_resource->save();
|
||||
if ($new_resource->destination->server->proxyType() === 'TRAEFIK_V2') {
|
||||
if ($new_resource->destination->server->proxyType() !== 'NONE') {
|
||||
$customLabels = str(implode("|", generateLabelsApplication($new_resource)))->replace("|", "\n");
|
||||
$new_resource->custom_labels = base64_encode($customLabels);
|
||||
$new_resource->save();
|
||||
|
@ -89,6 +89,7 @@ public function submit()
|
||||
'team_id' => currentTeam()->id,
|
||||
'private_key_id' => $this->private_key_id,
|
||||
'proxy' => [
|
||||
// set default proxy type to traefik v2
|
||||
"type" => ProxyTypes::TRAEFIK_V2->value,
|
||||
"status" => ProxyStatus::EXITED->value,
|
||||
],
|
||||
|
@ -21,7 +21,7 @@ class Proxy extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->selectedProxy = data_get($this->server, 'proxy.type');
|
||||
$this->selectedProxy = $this->server->proxyType();
|
||||
$this->redirect_url = data_get($this->server, 'proxy.redirect_url');
|
||||
}
|
||||
|
||||
@ -54,8 +54,7 @@ public function submit()
|
||||
SaveConfiguration::run($this->server, $this->proxy_settings);
|
||||
$this->server->proxy->redirect_url = $this->redirect_url;
|
||||
$this->server->save();
|
||||
|
||||
setup_default_redirect_404(redirect_url: $this->server->proxy->redirect_url, server: $this->server);
|
||||
$this->server->setupDefault404Redirect();
|
||||
$this->dispatch('success', 'Proxy configuration saved.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
@ -66,6 +65,9 @@ public function reset_proxy_configuration()
|
||||
{
|
||||
try {
|
||||
$this->proxy_settings = CheckConfiguration::run($this->server, true);
|
||||
SaveConfiguration::run($this->server, $this->proxy_settings);
|
||||
$this->server->save();
|
||||
$this->dispatch('success', 'Proxy configuration saved.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
@ -14,9 +14,17 @@ class DynamicConfigurationNavbar extends Component
|
||||
public function delete(string $fileName)
|
||||
{
|
||||
$server = Server::ownedByCurrentTeam()->whereId($this->server_id)->first();
|
||||
$proxy_path = get_proxy_path();
|
||||
$proxy_path = $server->proxyPath();
|
||||
$proxy_type = $server->proxyType();
|
||||
$file = str_replace('|', '.', $fileName);
|
||||
if ($proxy_type === 'CADDY' && $file === "Caddyfile") {
|
||||
$this->dispatch('error', 'Cannot delete Caddyfile.');
|
||||
return;
|
||||
}
|
||||
instant_remote_process(["rm -f {$proxy_path}/dynamic/{$file}"], $server);
|
||||
if ($proxy_type === 'CADDY') {
|
||||
$server->reloadCaddy();
|
||||
}
|
||||
$this->dispatch('success', 'File deleted.');
|
||||
$this->dispatch('loadDynamicConfigurations');
|
||||
$this->dispatch('refresh');
|
||||
|
@ -11,26 +11,32 @@ class DynamicConfigurations extends Component
|
||||
public ?Server $server = null;
|
||||
public $parameters = [];
|
||||
public Collection $contents;
|
||||
protected $listeners = ['loadDynamicConfigurations', 'refresh' => '$refresh'];
|
||||
public function getListeners()
|
||||
{
|
||||
$teamId = auth()->user()->currentTeam()->id;
|
||||
return [
|
||||
"echo-private:team.{$teamId},ProxyStatusChanged" => 'loadDynamicConfigurations',
|
||||
'loadDynamicConfigurations',
|
||||
'refresh' => '$refresh'
|
||||
];
|
||||
}
|
||||
protected $rules = [
|
||||
'contents.*' => 'nullable|string',
|
||||
];
|
||||
public function loadDynamicConfigurations()
|
||||
{
|
||||
$proxy_path = get_proxy_path();
|
||||
$proxy_path = $this->server->proxyPath();
|
||||
$files = instant_remote_process(["mkdir -p $proxy_path/dynamic && ls -1 {$proxy_path}/dynamic"], $this->server);
|
||||
$files = collect(explode("\n", $files))->filter(fn ($file) => !empty($file));
|
||||
$files = $files->map(fn ($file) => trim($file));
|
||||
$files = $files->sort();
|
||||
if ($files->contains('coolify.yaml')) {
|
||||
$files = $files->filter(fn ($file) => $file !== 'coolify.yaml')->prepend('coolify.yaml');
|
||||
}
|
||||
$contents = collect([]);
|
||||
foreach ($files as $file) {
|
||||
$without_extension = str_replace('.', '|', $file);
|
||||
$contents[$without_extension] = instant_remote_process(["cat {$proxy_path}/dynamic/{$file}"], $this->server);
|
||||
}
|
||||
$this->contents = $contents;
|
||||
$this->dispatch('refresh');
|
||||
}
|
||||
public function mount()
|
||||
{
|
||||
|
@ -29,7 +29,6 @@ public function addDynamicConfiguration()
|
||||
'fileName' => 'required',
|
||||
'value' => 'required',
|
||||
]);
|
||||
|
||||
if (data_get($this->parameters, 'server_uuid')) {
|
||||
$this->server = Server::ownedByCurrentTeam()->whereUuid(data_get($this->parameters, 'server_uuid'))->first();
|
||||
}
|
||||
@ -39,14 +38,21 @@ public function addDynamicConfiguration()
|
||||
if (is_null($this->server)) {
|
||||
return redirect()->route('server.index');
|
||||
}
|
||||
if (!str($this->fileName)->endsWith('.yaml') && !str($this->fileName)->endsWith('.yml')) {
|
||||
$this->fileName = "{$this->fileName}.yaml";
|
||||
$proxy_type = $this->server->proxyType();
|
||||
if ($proxy_type === 'TRAEFIK_V2') {
|
||||
if (!str($this->fileName)->endsWith('.yaml') && !str($this->fileName)->endsWith('.yml')) {
|
||||
$this->fileName = "{$this->fileName}.yaml";
|
||||
}
|
||||
if ($this->fileName === 'coolify.yaml') {
|
||||
$this->dispatch('error', 'File name is reserved.');
|
||||
return;
|
||||
}
|
||||
} else if ($proxy_type === 'CADDY') {
|
||||
if (!str($this->fileName)->endsWith('.caddy')) {
|
||||
$this->fileName = "{$this->fileName}.caddy";
|
||||
}
|
||||
}
|
||||
if ($this->fileName === 'coolify.yaml') {
|
||||
$this->dispatch('error', 'File name is reserved.');
|
||||
return;
|
||||
}
|
||||
$proxy_path = get_proxy_path();
|
||||
$proxy_path = $this->server->proxyPath();
|
||||
$file = "{$proxy_path}/dynamic/{$this->fileName}";
|
||||
if ($this->newFile) {
|
||||
$exists = instant_remote_process(["test -f $file && echo 1 || echo 0"], $this->server);
|
||||
@ -55,11 +61,18 @@ public function addDynamicConfiguration()
|
||||
return;
|
||||
}
|
||||
}
|
||||
$yaml = Yaml::parse($this->value);
|
||||
$yaml = Yaml::dump($yaml, 10, 2);
|
||||
$this->value = $yaml;
|
||||
if ($proxy_type === 'TRAEFIK_V2') {
|
||||
$yaml = Yaml::parse($this->value);
|
||||
$yaml = Yaml::dump($yaml, 10, 2);
|
||||
$this->value = $yaml;
|
||||
}
|
||||
$base64_value = base64_encode($this->value);
|
||||
instant_remote_process(["echo '{$base64_value}' | base64 -d > {$file}"], $this->server);
|
||||
instant_remote_process([
|
||||
"echo '{$base64_value}' | base64 -d > {$file}",
|
||||
], $this->server);
|
||||
if ($proxy_type === 'CADDY') {
|
||||
$this->server->reloadCaddy();
|
||||
}
|
||||
$this->dispatch('loadDynamicConfigurations');
|
||||
$this->dispatch('dynamic-configuration-added');
|
||||
$this->dispatch('success', 'Dynamic configuration saved.');
|
||||
|
@ -2,12 +2,9 @@
|
||||
|
||||
namespace App\Livewire\Settings;
|
||||
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Models\InstanceSettings as ModelsInstanceSettings;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
use Spatie\Url\Url;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class Configuration extends Component
|
||||
{
|
||||
@ -78,13 +75,7 @@ public function submit()
|
||||
|
||||
$this->settings->save();
|
||||
$this->server = Server::findOrFail(0);
|
||||
$this->setup_instance_fqdn();
|
||||
$this->server->setupDynamicProxyConfiguration();
|
||||
$this->dispatch('success', 'Instance settings updated successfully!');
|
||||
}
|
||||
|
||||
private function setup_instance_fqdn()
|
||||
{
|
||||
setup_dynamic_configuration();
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,8 @@ class Change extends Component
|
||||
public string $name;
|
||||
public bool $is_system_wide;
|
||||
|
||||
public $applications;
|
||||
|
||||
protected $rules = [
|
||||
'github_app.name' => 'required|string',
|
||||
'github_app.organization' => 'nullable|string',
|
||||
@ -90,6 +92,7 @@ public function mount()
|
||||
if (!$this->github_app) {
|
||||
return redirect()->route('source.all');
|
||||
}
|
||||
$this->applications = $this->github_app->applications;
|
||||
$settings = InstanceSettings::get();
|
||||
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
|
||||
|
||||
@ -170,6 +173,11 @@ public function instantSave()
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
if ($this->github_app->applications->isNotEmpty()) {
|
||||
$this->dispatch('error', 'This source is being used by an application. Please delete all applications first.');
|
||||
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
|
||||
return;
|
||||
}
|
||||
$this->github_app->delete();
|
||||
return redirect()->route('source.all');
|
||||
} catch (\Throwable $e) {
|
||||
|
@ -13,7 +13,7 @@ protected static function booted()
|
||||
{
|
||||
static::created(function (LocalFileVolume $fileVolume) {
|
||||
$fileVolume->load(['service']);
|
||||
$fileVolume->saveStorageOnServer();
|
||||
dispatch(new \App\Jobs\ServerStorageSaveJob($fileVolume));
|
||||
});
|
||||
}
|
||||
public function service()
|
||||
|
@ -27,7 +27,8 @@ protected static function booted()
|
||||
$project->settings()->delete();
|
||||
});
|
||||
}
|
||||
public function environment_variables() {
|
||||
public function environment_variables()
|
||||
{
|
||||
return $this->hasMany(SharedEnvironmentVariable::class);
|
||||
}
|
||||
public function environments()
|
||||
@ -45,6 +46,10 @@ public function team()
|
||||
return $this->belongsTo(Team::class);
|
||||
}
|
||||
|
||||
public function services()
|
||||
{
|
||||
return $this->hasManyThrough(Service::class, Environment::class);
|
||||
}
|
||||
public function applications()
|
||||
{
|
||||
return $this->hasManyThrough(Application::class, Environment::class);
|
||||
@ -70,7 +75,8 @@ public function mariadbs()
|
||||
{
|
||||
return $this->hasManyThrough(StandaloneMariadb::class, Environment::class);
|
||||
}
|
||||
public function resource_count() {
|
||||
public function resource_count()
|
||||
{
|
||||
return $this->applications()->count() + $this->postgresqls()->count() + $this->redis()->count() + $this->mongodbs()->count() + $this->mysqls()->count() + $this->mariadbs()->count();
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Actions\Server\InstallDocker;
|
||||
use App\Enums\ProxyStatus;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Notifications\Server\Revived;
|
||||
use App\Notifications\Server\Unreachable;
|
||||
@ -15,6 +14,8 @@
|
||||
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Stringable;
|
||||
use Spatie\Url\Url;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class Server extends BaseModel
|
||||
{
|
||||
@ -118,18 +119,304 @@ public function addInitialNetwork()
|
||||
}
|
||||
}
|
||||
}
|
||||
public function setupDefault404Redirect()
|
||||
{
|
||||
$dynamic_conf_path = $this->proxyPath() . "/dynamic";
|
||||
$proxy_type = $this->proxyType();
|
||||
$redirect_url = $this->proxy->redirect_url;
|
||||
if ($proxy_type === 'TRAEFIK_V2') {
|
||||
$default_redirect_file = "$dynamic_conf_path/default_redirect_404.yaml";
|
||||
} else if ($proxy_type === 'CADDY') {
|
||||
$default_redirect_file = "$dynamic_conf_path/default_redirect_404.caddy";
|
||||
}
|
||||
if (empty($redirect_url)) {
|
||||
if ($proxy_type === 'CADDY') {
|
||||
$conf = ":80, :443 {
|
||||
respond 404
|
||||
}";
|
||||
$conf =
|
||||
"# This file is automatically generated by Coolify.\n" .
|
||||
"# Do not edit it manually (only if you know what are you doing).\n\n" .
|
||||
$conf;
|
||||
$base64 = base64_encode($conf);
|
||||
instant_remote_process([
|
||||
"mkdir -p $dynamic_conf_path",
|
||||
"echo '$base64' | base64 -d > $default_redirect_file",
|
||||
], $this);
|
||||
$this->reloadCaddy();
|
||||
return;
|
||||
}
|
||||
instant_remote_process([
|
||||
"mkdir -p $dynamic_conf_path",
|
||||
"rm -f $default_redirect_file",
|
||||
], $this);
|
||||
return;
|
||||
}
|
||||
if ($proxy_type === 'TRAEFIK_V2') {
|
||||
$dynamic_conf = [
|
||||
'http' =>
|
||||
[
|
||||
'routers' =>
|
||||
[
|
||||
'catchall' =>
|
||||
[
|
||||
'entryPoints' => [
|
||||
0 => 'http',
|
||||
1 => 'https',
|
||||
],
|
||||
'service' => 'noop',
|
||||
'rule' => "HostRegexp(`{catchall:.*}`)",
|
||||
'priority' => 1,
|
||||
'middlewares' => [
|
||||
0 => 'redirect-regexp@file',
|
||||
],
|
||||
],
|
||||
],
|
||||
'services' =>
|
||||
[
|
||||
'noop' =>
|
||||
[
|
||||
'loadBalancer' =>
|
||||
[
|
||||
'servers' =>
|
||||
[
|
||||
0 =>
|
||||
[
|
||||
'url' => '',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'middlewares' =>
|
||||
[
|
||||
'redirect-regexp' =>
|
||||
[
|
||||
'redirectRegex' =>
|
||||
[
|
||||
'regex' => '(.*)',
|
||||
'replacement' => $redirect_url,
|
||||
'permanent' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
$conf = Yaml::dump($dynamic_conf, 12, 2);
|
||||
$conf =
|
||||
"# This file is automatically generated by Coolify.\n" .
|
||||
"# Do not edit it manually (only if you know what are you doing).\n\n" .
|
||||
$conf;
|
||||
|
||||
$base64 = base64_encode($conf);
|
||||
} else if ($proxy_type === 'CADDY') {
|
||||
$conf = ":80, :443 {
|
||||
redir $redirect_url
|
||||
}";
|
||||
$conf =
|
||||
"# This file is automatically generated by Coolify.\n" .
|
||||
"# Do not edit it manually (only if you know what are you doing).\n\n" .
|
||||
$conf;
|
||||
$base64 = base64_encode($conf);
|
||||
}
|
||||
|
||||
|
||||
instant_remote_process([
|
||||
"mkdir -p $dynamic_conf_path",
|
||||
"echo '$base64' | base64 -d > $default_redirect_file",
|
||||
], $this);
|
||||
|
||||
if (config('app.env') == 'local') {
|
||||
ray($conf);
|
||||
}
|
||||
if ($proxy_type === 'CADDY') {
|
||||
$this->reloadCaddy();
|
||||
}
|
||||
}
|
||||
public function setupDynamicProxyConfiguration()
|
||||
{
|
||||
$settings = InstanceSettings::get();
|
||||
$dynamic_config_path = $this->proxyPath() . "/dynamic";
|
||||
if ($this->proxyType() === 'TRAEFIK_V2') {
|
||||
$file = "$dynamic_config_path/coolify.yaml";
|
||||
if (empty($settings->fqdn)) {
|
||||
instant_remote_process([
|
||||
"rm -f $file",
|
||||
], $this);
|
||||
} else {
|
||||
$url = Url::fromString($settings->fqdn);
|
||||
$host = $url->getHost();
|
||||
$schema = $url->getScheme();
|
||||
$traefik_dynamic_conf = [
|
||||
'http' =>
|
||||
[
|
||||
'middlewares' => [
|
||||
'redirect-to-https' => [
|
||||
'redirectscheme' => [
|
||||
'scheme' => 'https',
|
||||
],
|
||||
],
|
||||
'gzip' => [
|
||||
'compress' => true,
|
||||
],
|
||||
],
|
||||
'routers' =>
|
||||
[
|
||||
'coolify-http' =>
|
||||
[
|
||||
'middlewares' => [
|
||||
0 => 'gzip',
|
||||
],
|
||||
'entryPoints' => [
|
||||
0 => 'http',
|
||||
],
|
||||
'service' => 'coolify',
|
||||
'rule' => "Host(`{$host}`)",
|
||||
],
|
||||
'coolify-realtime-ws' =>
|
||||
[
|
||||
'entryPoints' => [
|
||||
0 => 'http',
|
||||
],
|
||||
'service' => 'coolify-realtime',
|
||||
'rule' => "Host(`{$host}`) && PathPrefix(`/app`)",
|
||||
],
|
||||
],
|
||||
'services' =>
|
||||
[
|
||||
'coolify' =>
|
||||
[
|
||||
'loadBalancer' =>
|
||||
[
|
||||
'servers' =>
|
||||
[
|
||||
0 =>
|
||||
[
|
||||
'url' => 'http://coolify:80',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'coolify-realtime' =>
|
||||
[
|
||||
'loadBalancer' =>
|
||||
[
|
||||
'servers' =>
|
||||
[
|
||||
0 =>
|
||||
[
|
||||
'url' => 'http://coolify-realtime:6001',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
if ($schema === 'https') {
|
||||
$traefik_dynamic_conf['http']['routers']['coolify-http']['middlewares'] = [
|
||||
0 => 'redirect-to-https',
|
||||
];
|
||||
|
||||
$traefik_dynamic_conf['http']['routers']['coolify-https'] = [
|
||||
'entryPoints' => [
|
||||
0 => 'https',
|
||||
],
|
||||
'service' => 'coolify',
|
||||
'rule' => "Host(`{$host}`)",
|
||||
'tls' => [
|
||||
'certresolver' => 'letsencrypt',
|
||||
],
|
||||
];
|
||||
$traefik_dynamic_conf['http']['routers']['coolify-realtime-wss'] = [
|
||||
'entryPoints' => [
|
||||
0 => 'https',
|
||||
],
|
||||
'service' => 'coolify-realtime',
|
||||
'rule' => "Host(`{$host}`) && PathPrefix(`/app`)",
|
||||
'tls' => [
|
||||
'certresolver' => 'letsencrypt',
|
||||
],
|
||||
];
|
||||
}
|
||||
$yaml = Yaml::dump($traefik_dynamic_conf, 12, 2);
|
||||
$yaml =
|
||||
"# This file is automatically generated by Coolify.\n" .
|
||||
"# Do not edit it manually (only if you know what are you doing).\n\n" .
|
||||
$yaml;
|
||||
|
||||
$base64 = base64_encode($yaml);
|
||||
instant_remote_process([
|
||||
"mkdir -p $dynamic_config_path",
|
||||
"echo '$base64' | base64 -d > $file",
|
||||
], $this);
|
||||
|
||||
if (config('app.env') == 'local') {
|
||||
// ray($yaml);
|
||||
}
|
||||
}
|
||||
} else if ($this->proxyType() === 'CADDY') {
|
||||
$file = "$dynamic_config_path/coolify.caddy";
|
||||
if (empty($settings->fqdn)) {
|
||||
instant_remote_process([
|
||||
"rm -f $file",
|
||||
], $this);
|
||||
$this->reloadCaddy();
|
||||
} else {
|
||||
$url = Url::fromString($settings->fqdn);
|
||||
$host = $url->getHost();
|
||||
$schema = $url->getScheme();
|
||||
$caddy_file = "
|
||||
$schema://$host {
|
||||
handle /app/* {
|
||||
reverse_proxy coolify-realtime:6001
|
||||
}
|
||||
reverse_proxy coolify:80
|
||||
}";
|
||||
$base64 = base64_encode($caddy_file);
|
||||
instant_remote_process([
|
||||
"echo '$base64' | base64 -d > $file",
|
||||
], $this);
|
||||
$this->reloadCaddy();
|
||||
}
|
||||
}
|
||||
}
|
||||
public function reloadCaddy()
|
||||
{
|
||||
return instant_remote_process([
|
||||
"docker exec coolify-proxy caddy reload --config /config/caddy/Caddyfile.autosave",
|
||||
], $this);
|
||||
}
|
||||
public function proxyPath()
|
||||
{
|
||||
$base_path = config('coolify.base_config_path');
|
||||
$proxyType = $this->proxyType();
|
||||
$proxy_path = "$base_path/proxy";
|
||||
// TODO: should use /traefik for already exisiting configurations?
|
||||
// Should move everything except /caddy and /nginx to /traefik
|
||||
// The code needs to be modified as well, so maybe it does not worth it
|
||||
if ($proxyType === ProxyTypes::TRAEFIK_V2->value) {
|
||||
$proxy_path = $proxy_path;
|
||||
} else if ($proxyType === ProxyTypes::CADDY->value) {
|
||||
$proxy_path = $proxy_path . '/caddy';
|
||||
} else if ($proxyType === ProxyTypes::NGINX->value) {
|
||||
$proxy_path = $proxy_path . '/nginx';
|
||||
}
|
||||
return $proxy_path;
|
||||
}
|
||||
public function proxyType()
|
||||
{
|
||||
$proxyType = $this->proxy->get('type');
|
||||
if ($proxyType === ProxyTypes::NONE->value) {
|
||||
return $proxyType;
|
||||
}
|
||||
if (is_null($proxyType)) {
|
||||
$this->proxy->type = ProxyTypes::TRAEFIK_V2->value;
|
||||
$this->proxy->status = ProxyStatus::EXITED->value;
|
||||
$this->save();
|
||||
}
|
||||
return $this->proxy->get('type');
|
||||
// $proxyType = $this->proxy->get('type');
|
||||
// if ($proxyType === ProxyTypes::NONE->value) {
|
||||
// return $proxyType;
|
||||
// }
|
||||
// if (is_null($proxyType)) {
|
||||
// $this->proxy->type = ProxyTypes::TRAEFIK_V2->value;
|
||||
// $this->proxy->status = ProxyStatus::EXITED->value;
|
||||
// $this->save();
|
||||
// }
|
||||
return data_get($this->proxy, 'type');
|
||||
}
|
||||
public function scopeWithProxy(): Builder
|
||||
{
|
||||
|
@ -102,6 +102,29 @@ public function extraFields()
|
||||
foreach ($applications as $application) {
|
||||
$image = str($application->image)->before(':')->value();
|
||||
switch ($image) {
|
||||
case str($image)?->contains('grafana'):
|
||||
$data = collect([]);
|
||||
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_GRAFANA')->first();
|
||||
$data = $data->merge([
|
||||
'Admin User' => [
|
||||
'key' => 'GF_SECURITY_ADMIN_USER',
|
||||
'value' => 'admin',
|
||||
'readonly' => true,
|
||||
'rules' => 'required',
|
||||
],
|
||||
]);
|
||||
if ($admin_password) {
|
||||
$data = $data->merge([
|
||||
'Admin Password' => [
|
||||
'key' => 'GF_SECURITY_ADMIN_PASSWORD',
|
||||
'value' => data_get($admin_password, 'value'),
|
||||
'rules' => 'required',
|
||||
'isPassword' => true,
|
||||
],
|
||||
]);
|
||||
}
|
||||
$fields->put('Grafana', $data);
|
||||
break;
|
||||
case str($image)?->contains('directus'):
|
||||
$data = collect([]);
|
||||
$admin_email = $this->environment_variables()->where('key', 'ADMIN_EMAIL')->first();
|
||||
|
@ -48,13 +48,15 @@ public function getRecepients($notification)
|
||||
}
|
||||
return explode(',', $recipients);
|
||||
}
|
||||
static public function serverLimitReached() {
|
||||
static public function serverLimitReached()
|
||||
{
|
||||
$serverLimit = Team::serverLimit();
|
||||
$team = currentTeam();
|
||||
$servers = $team->servers->count();
|
||||
return $servers >= $serverLimit;
|
||||
}
|
||||
public function serverOverflow() {
|
||||
public function serverOverflow()
|
||||
{
|
||||
if ($this->serverLimit() < $this->servers->count()) {
|
||||
return true;
|
||||
}
|
||||
@ -170,4 +172,17 @@ public function trialEndedButSubscribed()
|
||||
]);
|
||||
}
|
||||
}
|
||||
public function isAnyNotificationEnabled()
|
||||
{
|
||||
if (isCloud()) {
|
||||
return true;
|
||||
}
|
||||
if (!data_get(auth()->user(), 'is_notification_notifications_enabled')) {
|
||||
return true;
|
||||
}
|
||||
if ($this->smtp_enabled || $this->resend_enabled || $this->discord_enabled || $this->telegram_enabled || $this->use_instance_email_settings) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,8 @@ class User extends Authenticatable implements SendsEmail
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
'two_factor_recovery_codes',
|
||||
'two_factor_secret',
|
||||
];
|
||||
protected $casts = [
|
||||
'email_verified_at' => 'datetime',
|
||||
|
@ -2,8 +2,10 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Events\ProxyStarted;
|
||||
use App\Listeners\MaintenanceModeDisabledNotification;
|
||||
use App\Listeners\MaintenanceModeEnabledNotification;
|
||||
use App\Listeners\ProxyStartedNotification;
|
||||
use Illuminate\Foundation\Events\MaintenanceModeDisabled;
|
||||
use Illuminate\Foundation\Events\MaintenanceModeEnabled;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
@ -17,9 +19,9 @@ class EventServiceProvider extends ServiceProvider
|
||||
MaintenanceModeDisabled::class => [
|
||||
MaintenanceModeDisabledNotification::class,
|
||||
],
|
||||
// Registered::class => [
|
||||
// SendEmailVerificationNotification::class,
|
||||
// ],
|
||||
ProxyStarted::class => [
|
||||
ProxyStartedNotification::class,
|
||||
],
|
||||
];
|
||||
public function boot(): void
|
||||
{
|
||||
|
@ -5,3 +5,7 @@ function get_team_id_from_token()
|
||||
$token = auth()->user()->currentAccessToken();
|
||||
return data_get($token, 'team_id');
|
||||
}
|
||||
function invalid_token()
|
||||
{
|
||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\Server;
|
||||
@ -215,6 +216,45 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource,
|
||||
}
|
||||
return $payload;
|
||||
}
|
||||
function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null)
|
||||
{
|
||||
$labels = collect([]);
|
||||
if ($serviceLabels) {
|
||||
$labels->push("caddy_ingress_network={$uuid}");
|
||||
} else {
|
||||
$labels->push("caddy_ingress_network={$network}");
|
||||
}
|
||||
foreach ($domains as $loop => $domain) {
|
||||
$loop = $loop;
|
||||
$url = Url::fromString($domain);
|
||||
$host = $url->getHost();
|
||||
$path = $url->getPath();
|
||||
// $stripped_path = str($path)->replaceEnd('/', '');
|
||||
|
||||
$schema = $url->getScheme();
|
||||
$port = $url->getPort();
|
||||
if (is_null($port) && !is_null($onlyPort)) {
|
||||
$port = $onlyPort;
|
||||
}
|
||||
$labels->push("caddy_{$loop}={$schema}://{$host}");
|
||||
$labels->push("caddy_{$loop}.header=-Server");
|
||||
$labels->push("caddy_{$loop}.try_files={path} /index.html /index.php");
|
||||
|
||||
if ($port) {
|
||||
$labels->push("caddy_{$loop}.handle_path.{$loop}_reverse_proxy={{upstreams $port}}");
|
||||
} else {
|
||||
$labels->push("caddy_{$loop}.handle_path.{$loop}_reverse_proxy={{upstreams}}");
|
||||
}
|
||||
$labels->push("caddy_{$loop}.handle_path={$path}*");
|
||||
if ($is_gzip_enabled) {
|
||||
$labels->push("caddy_{$loop}.encode=zstd gzip");
|
||||
}
|
||||
if (isDev()) {
|
||||
// $labels->push("caddy_{$loop}.tls=internal");
|
||||
}
|
||||
}
|
||||
return $labels->sort();
|
||||
}
|
||||
function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null)
|
||||
{
|
||||
$labels = collect([]);
|
||||
@ -395,7 +435,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
||||
} else {
|
||||
$domains = Str::of(data_get($application, 'fqdn'))->explode(',');
|
||||
}
|
||||
// Add Traefik labels no matter which proxy is selected
|
||||
// Add Traefik labels
|
||||
$labels = $labels->merge(fqdnLabelsForTraefik(
|
||||
uuid: $appUuid,
|
||||
domains: $domains,
|
||||
@ -404,6 +444,16 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
||||
is_gzip_enabled: $application->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $application->isStripprefixEnabled()
|
||||
));
|
||||
// Add Caddy labels
|
||||
$labels = $labels->merge(fqdnLabelsForCaddy(
|
||||
network: $application->destination->network,
|
||||
uuid: $appUuid,
|
||||
domains: $domains,
|
||||
onlyPort: $onlyPort,
|
||||
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
||||
is_gzip_enabled: $application->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $application->isStripprefixEnabled()
|
||||
));
|
||||
}
|
||||
return $labels->all();
|
||||
}
|
||||
@ -506,3 +556,24 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
|
||||
}
|
||||
return $compose_options->toArray();
|
||||
}
|
||||
|
||||
function validateComposeFile(string $compose, int $server_id): string|Throwable {
|
||||
try {
|
||||
$uuid = Str::random(10);
|
||||
$server = Server::findOrFail($server_id);
|
||||
$base64_compose = base64_encode($compose);
|
||||
$output = instant_remote_process([
|
||||
"echo {$base64_compose} | base64 -d > /tmp/{$uuid}.yml",
|
||||
"docker compose -f /tmp/{$uuid}.yml config",
|
||||
], $server);
|
||||
ray($output);
|
||||
return 'OK';
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
return $e->getMessage();
|
||||
} finally {
|
||||
instant_remote_process([
|
||||
"rm /tmp/{$uuid}.yml",
|
||||
], $server);
|
||||
}
|
||||
}
|
||||
|
@ -7,12 +7,7 @@
|
||||
use Spatie\Url\Url;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
function get_proxy_path()
|
||||
{
|
||||
$base_path = config('coolify.base_config_path');
|
||||
$proxy_path = "$base_path/proxy";
|
||||
return $proxy_path;
|
||||
}
|
||||
|
||||
function connectProxyToNetworks(Server $server)
|
||||
{
|
||||
if ($server->isSwarm()) {
|
||||
@ -75,7 +70,9 @@ function connectProxyToNetworks(Server $server)
|
||||
}
|
||||
function generate_default_proxy_configuration(Server $server)
|
||||
{
|
||||
$proxy_path = get_proxy_path();
|
||||
$proxy_path = $server->proxyPath();
|
||||
$proxy_type = $server->proxyType();
|
||||
|
||||
if ($server->isSwarm()) {
|
||||
$networks = collect($server->swarmDockers)->map(function ($docker) {
|
||||
return $docker['network'];
|
||||
@ -98,287 +95,126 @@ function generate_default_proxy_configuration(Server $server)
|
||||
"external" => true,
|
||||
];
|
||||
});
|
||||
$labels = [
|
||||
"traefik.enable=true",
|
||||
"traefik.http.routers.traefik.entrypoints=http",
|
||||
"traefik.http.routers.traefik.service=api@internal",
|
||||
"traefik.http.services.traefik.loadbalancer.server.port=8080",
|
||||
"coolify.managed=true",
|
||||
];
|
||||
$config = [
|
||||
"version" => "3.8",
|
||||
"networks" => $array_of_networks->toArray(),
|
||||
"services" => [
|
||||
"traefik" => [
|
||||
"container_name" => "coolify-proxy",
|
||||
"image" => "traefik:v2.10",
|
||||
"restart" => RESTART_MODE,
|
||||
"extra_hosts" => [
|
||||
"host.docker.internal:host-gateway",
|
||||
if ($proxy_type === 'TRAEFIK_V2') {
|
||||
$labels = [
|
||||
"traefik.enable=true",
|
||||
"traefik.http.routers.traefik.entrypoints=http",
|
||||
"traefik.http.routers.traefik.service=api@internal",
|
||||
"traefik.http.services.traefik.loadbalancer.server.port=8080",
|
||||
"coolify.managed=true",
|
||||
];
|
||||
$config = [
|
||||
"version" => "3.8",
|
||||
"networks" => $array_of_networks->toArray(),
|
||||
"services" => [
|
||||
"traefik" => [
|
||||
"container_name" => "coolify-proxy",
|
||||
"image" => "traefik:v2.10",
|
||||
"restart" => RESTART_MODE,
|
||||
"extra_hosts" => [
|
||||
"host.docker.internal:host-gateway",
|
||||
],
|
||||
"networks" => $networks->toArray(),
|
||||
"ports" => [
|
||||
"80:80",
|
||||
"443:443",
|
||||
"8080:8080",
|
||||
],
|
||||
"healthcheck" => [
|
||||
"test" => "wget -qO- http://localhost:80/ping || exit 1",
|
||||
"interval" => "4s",
|
||||
"timeout" => "2s",
|
||||
"retries" => 5,
|
||||
],
|
||||
"volumes" => [
|
||||
"/var/run/docker.sock:/var/run/docker.sock:ro",
|
||||
"{$proxy_path}:/traefik",
|
||||
],
|
||||
"command" => [
|
||||
"--ping=true",
|
||||
"--ping.entrypoint=http",
|
||||
"--api.dashboard=true",
|
||||
"--api.insecure=false",
|
||||
"--entrypoints.http.address=:80",
|
||||
"--entrypoints.https.address=:443",
|
||||
"--entrypoints.http.http.encodequerysemicolons=true",
|
||||
"--entryPoints.http.http2.maxConcurrentStreams=50",
|
||||
"--entrypoints.https.http.encodequerysemicolons=true",
|
||||
"--entryPoints.https.http2.maxConcurrentStreams=50",
|
||||
"--providers.docker.exposedbydefault=false",
|
||||
"--providers.file.directory=/traefik/dynamic/",
|
||||
"--providers.file.watch=true",
|
||||
"--certificatesresolvers.letsencrypt.acme.httpchallenge=true",
|
||||
"--certificatesresolvers.letsencrypt.acme.storage=/traefik/acme.json",
|
||||
"--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=http",
|
||||
],
|
||||
"labels" => $labels,
|
||||
],
|
||||
"networks" => $networks->toArray(),
|
||||
"ports" => [
|
||||
"80:80",
|
||||
"443:443",
|
||||
"8080:8080",
|
||||
],
|
||||
"healthcheck" => [
|
||||
"test" => "wget -qO- http://localhost:80/ping || exit 1",
|
||||
"interval" => "4s",
|
||||
"timeout" => "2s",
|
||||
"retries" => 5,
|
||||
],
|
||||
"volumes" => [
|
||||
"/var/run/docker.sock:/var/run/docker.sock:ro",
|
||||
"{$proxy_path}:/traefik",
|
||||
],
|
||||
"command" => [
|
||||
"--ping=true",
|
||||
"--ping.entrypoint=http",
|
||||
"--api.dashboard=true",
|
||||
"--api.insecure=false",
|
||||
"--entrypoints.http.address=:80",
|
||||
"--entrypoints.https.address=:443",
|
||||
"--entrypoints.http.http.encodequerysemicolons=true",
|
||||
"--entryPoints.http.http2.maxConcurrentStreams=50",
|
||||
"--entrypoints.https.http.encodequerysemicolons=true",
|
||||
"--entryPoints.https.http2.maxConcurrentStreams=50",
|
||||
"--providers.docker.exposedbydefault=false",
|
||||
"--providers.file.directory=/traefik/dynamic/",
|
||||
"--providers.file.watch=true",
|
||||
"--certificatesresolvers.letsencrypt.acme.httpchallenge=true",
|
||||
"--certificatesresolvers.letsencrypt.acme.storage=/traefik/acme.json",
|
||||
"--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=http",
|
||||
],
|
||||
"labels" => $labels,
|
||||
],
|
||||
],
|
||||
];
|
||||
if (isDev()) {
|
||||
// $config['services']['traefik']['command'][] = "--log.level=debug";
|
||||
$config['services']['traefik']['command'][] = "--accesslog.filepath=/traefik/access.log";
|
||||
$config['services']['traefik']['command'][] = "--accesslog.bufferingsize=100";
|
||||
}
|
||||
if ($server->isSwarm()) {
|
||||
data_forget($config, 'services.traefik.container_name');
|
||||
data_forget($config, 'services.traefik.restart');
|
||||
data_forget($config, 'services.traefik.labels');
|
||||
];
|
||||
if (isDev()) {
|
||||
// $config['services']['traefik']['command'][] = "--log.level=debug";
|
||||
$config['services']['traefik']['command'][] = "--accesslog.filepath=/traefik/access.log";
|
||||
$config['services']['traefik']['command'][] = "--accesslog.bufferingsize=100";
|
||||
}
|
||||
if ($server->isSwarm()) {
|
||||
data_forget($config, 'services.traefik.container_name');
|
||||
data_forget($config, 'services.traefik.restart');
|
||||
data_forget($config, 'services.traefik.labels');
|
||||
|
||||
$config['services']['traefik']['command'][] = "--providers.docker.swarmMode=true";
|
||||
$config['services']['traefik']['deploy'] = [
|
||||
"labels" => $labels,
|
||||
"placement" => [
|
||||
"constraints" => [
|
||||
"node.role==manager",
|
||||
$config['services']['traefik']['command'][] = "--providers.docker.swarmMode=true";
|
||||
$config['services']['traefik']['deploy'] = [
|
||||
"labels" => $labels,
|
||||
"placement" => [
|
||||
"constraints" => [
|
||||
"node.role==manager",
|
||||
],
|
||||
],
|
||||
];
|
||||
} else {
|
||||
$config['services']['traefik']['command'][] = "--providers.docker=true";
|
||||
}
|
||||
} else if ($proxy_type === 'CADDY') {
|
||||
$config = [
|
||||
"version" => "3.8",
|
||||
"networks" => $array_of_networks->toArray(),
|
||||
"services" => [
|
||||
"caddy" => [
|
||||
"container_name" => "coolify-proxy",
|
||||
"image" => "lucaslorentz/caddy-docker-proxy:2.8-alpine",
|
||||
"restart" => RESTART_MODE,
|
||||
"extra_hosts" => [
|
||||
"host.docker.internal:host-gateway",
|
||||
],
|
||||
"environment" => [
|
||||
"CADDY_DOCKER_POLLING_INTERVAL=5s",
|
||||
"CADDY_DOCKER_CADDYFILE_PATH=/dynamic/Caddyfile",
|
||||
],
|
||||
"networks" => $networks->toArray(),
|
||||
"ports" => [
|
||||
"80:80",
|
||||
"443:443",
|
||||
],
|
||||
// "healthcheck" => [
|
||||
// "test" => "wget -qO- http://localhost:80|| exit 1",
|
||||
// "interval" => "4s",
|
||||
// "timeout" => "2s",
|
||||
// "retries" => 5,
|
||||
// ],
|
||||
"volumes" => [
|
||||
"/var/run/docker.sock:/var/run/docker.sock:ro",
|
||||
"{$proxy_path}/dynamic:/dynamic",
|
||||
"{$proxy_path}/config:/config",
|
||||
"{$proxy_path}/data:/data",
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
} else {
|
||||
$config['services']['traefik']['command'][] = "--providers.docker=true";
|
||||
return null;
|
||||
}
|
||||
|
||||
$config = Yaml::dump($config, 12, 2);
|
||||
SaveConfiguration::run($server, $config);
|
||||
return $config;
|
||||
}
|
||||
function setup_dynamic_configuration()
|
||||
{
|
||||
$dynamic_config_path = get_proxy_path() . "/dynamic";
|
||||
$settings = InstanceSettings::get();
|
||||
$server = Server::find(0);
|
||||
if ($server) {
|
||||
$file = "$dynamic_config_path/coolify.yaml";
|
||||
if (empty($settings->fqdn)) {
|
||||
instant_remote_process([
|
||||
"rm -f $file",
|
||||
], $server);
|
||||
} else {
|
||||
$url = Url::fromString($settings->fqdn);
|
||||
$host = $url->getHost();
|
||||
$schema = $url->getScheme();
|
||||
$traefik_dynamic_conf = [
|
||||
'http' =>
|
||||
[
|
||||
'middlewares' => [
|
||||
'redirect-to-https' => [
|
||||
'redirectscheme' => [
|
||||
'scheme' => 'https',
|
||||
],
|
||||
],
|
||||
'gzip' => [
|
||||
'compress' => true,
|
||||
],
|
||||
],
|
||||
'routers' =>
|
||||
[
|
||||
'coolify-http' =>
|
||||
[
|
||||
'middlewares' => [
|
||||
0 => 'gzip',
|
||||
],
|
||||
'entryPoints' => [
|
||||
0 => 'http',
|
||||
],
|
||||
'service' => 'coolify',
|
||||
'rule' => "Host(`{$host}`)",
|
||||
],
|
||||
'coolify-realtime-ws' =>
|
||||
[
|
||||
'entryPoints' => [
|
||||
0 => 'http',
|
||||
],
|
||||
'service' => 'coolify-realtime',
|
||||
'rule' => "Host(`{$host}`) && PathPrefix(`/app`)",
|
||||
],
|
||||
],
|
||||
'services' =>
|
||||
[
|
||||
'coolify' =>
|
||||
[
|
||||
'loadBalancer' =>
|
||||
[
|
||||
'servers' =>
|
||||
[
|
||||
0 =>
|
||||
[
|
||||
'url' => 'http://coolify:80',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'coolify-realtime' =>
|
||||
[
|
||||
'loadBalancer' =>
|
||||
[
|
||||
'servers' =>
|
||||
[
|
||||
0 =>
|
||||
[
|
||||
'url' => 'http://coolify-realtime:6001',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
if ($schema === 'https') {
|
||||
$traefik_dynamic_conf['http']['routers']['coolify-http']['middlewares'] = [
|
||||
0 => 'redirect-to-https',
|
||||
];
|
||||
|
||||
$traefik_dynamic_conf['http']['routers']['coolify-https'] = [
|
||||
'entryPoints' => [
|
||||
0 => 'https',
|
||||
],
|
||||
'service' => 'coolify',
|
||||
'rule' => "Host(`{$host}`)",
|
||||
'tls' => [
|
||||
'certresolver' => 'letsencrypt',
|
||||
],
|
||||
];
|
||||
$traefik_dynamic_conf['http']['routers']['coolify-realtime-wss'] = [
|
||||
'entryPoints' => [
|
||||
0 => 'https',
|
||||
],
|
||||
'service' => 'coolify-realtime',
|
||||
'rule' => "Host(`{$host}`) && PathPrefix(`/app`)",
|
||||
'tls' => [
|
||||
'certresolver' => 'letsencrypt',
|
||||
],
|
||||
];
|
||||
}
|
||||
$yaml = Yaml::dump($traefik_dynamic_conf, 12, 2);
|
||||
$yaml =
|
||||
"# This file is automatically generated by Coolify.\n" .
|
||||
"# Do not edit it manually (only if you know what are you doing).\n\n" .
|
||||
$yaml;
|
||||
|
||||
$base64 = base64_encode($yaml);
|
||||
instant_remote_process([
|
||||
"mkdir -p $dynamic_config_path",
|
||||
"echo '$base64' | base64 -d > $file",
|
||||
], $server);
|
||||
|
||||
if (config('app.env') == 'local') {
|
||||
// ray($yaml);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function setup_default_redirect_404(string|null $redirect_url, Server $server)
|
||||
{
|
||||
$traefik_dynamic_conf_path = get_proxy_path() . "/dynamic";
|
||||
$traefik_default_redirect_file = "$traefik_dynamic_conf_path/default_redirect_404.yaml";
|
||||
if (empty($redirect_url)) {
|
||||
instant_remote_process([
|
||||
"mkdir -p $traefik_dynamic_conf_path",
|
||||
"rm -f $traefik_default_redirect_file",
|
||||
], $server);
|
||||
} else {
|
||||
$traefik_dynamic_conf = [
|
||||
'http' =>
|
||||
[
|
||||
'routers' =>
|
||||
[
|
||||
'catchall' =>
|
||||
[
|
||||
'entryPoints' => [
|
||||
0 => 'http',
|
||||
1 => 'https',
|
||||
],
|
||||
'service' => 'noop',
|
||||
'rule' => "HostRegexp(`{catchall:.*}`)",
|
||||
'priority' => 1,
|
||||
'middlewares' => [
|
||||
0 => 'redirect-regexp@file',
|
||||
],
|
||||
],
|
||||
],
|
||||
'services' =>
|
||||
[
|
||||
'noop' =>
|
||||
[
|
||||
'loadBalancer' =>
|
||||
[
|
||||
'servers' =>
|
||||
[
|
||||
0 =>
|
||||
[
|
||||
'url' => '',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'middlewares' =>
|
||||
[
|
||||
'redirect-regexp' =>
|
||||
[
|
||||
'redirectRegex' =>
|
||||
[
|
||||
'regex' => '(.*)',
|
||||
'replacement' => $redirect_url,
|
||||
'permanent' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
$yaml = Yaml::dump($traefik_dynamic_conf, 12, 2);
|
||||
$yaml =
|
||||
"# This file is automatically generated by Coolify.\n" .
|
||||
"# Do not edit it manually (only if you know what are you doing).\n\n" .
|
||||
$yaml;
|
||||
|
||||
$base64 = base64_encode($yaml);
|
||||
instant_remote_process([
|
||||
"mkdir -p $traefik_dynamic_conf_path",
|
||||
"echo '$base64' | base64 -d > $traefik_default_redirect_file",
|
||||
], $server);
|
||||
|
||||
if (config('app.env') == 'local') {
|
||||
ray($yaml);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,8 @@ function remote_process(
|
||||
?string $type_uuid = null,
|
||||
?Model $model = null,
|
||||
bool $ignore_errors = false,
|
||||
$callEventOnFinish = null
|
||||
$callEventOnFinish = null,
|
||||
$callEventData = null
|
||||
): Activity {
|
||||
if (is_null($type)) {
|
||||
$type = ActivityTypes::INLINE->value;
|
||||
@ -50,6 +51,7 @@ function remote_process(
|
||||
model: $model,
|
||||
ignore_errors: $ignore_errors,
|
||||
call_event_on_finish: $callEventOnFinish,
|
||||
call_event_data: $callEventData,
|
||||
),
|
||||
])();
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase $oneS
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
function updateCompose($resource)
|
||||
function updateCompose(ServiceApplication|ServiceDatabase $resource)
|
||||
{
|
||||
try {
|
||||
$name = data_get($resource, 'name');
|
||||
@ -90,6 +90,9 @@ function updateCompose($resource)
|
||||
// Switch Image
|
||||
$image = data_get($resource, 'image');
|
||||
data_set($dockerCompose, "services.{$name}.image", $image);
|
||||
$dockerComposeRaw = Yaml::dump($dockerCompose, 10, 2);
|
||||
$resource->service->docker_compose_raw = $dockerComposeRaw;
|
||||
$resource->service->save();
|
||||
|
||||
if (!str($resource->fqdn)->contains(',')) {
|
||||
// Update FQDN
|
||||
@ -105,7 +108,6 @@ function updateCompose($resource)
|
||||
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
|
||||
$url = Url::fromString($resource->fqdn);
|
||||
$url = $url->getHost();
|
||||
ray($url);
|
||||
if ($generatedEnv) {
|
||||
$url = Str::of($resource->fqdn)->after('://');
|
||||
$generatedEnv->value = $url;
|
||||
@ -113,9 +115,6 @@ function updateCompose($resource)
|
||||
}
|
||||
}
|
||||
|
||||
$dockerComposeRaw = Yaml::dump($dockerCompose, 10, 2);
|
||||
$resource->service->docker_compose_raw = $dockerComposeRaw;
|
||||
$resource->service->save();
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Jobs\ServerFilesFromServerJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\EnvironmentVariable;
|
||||
@ -615,7 +616,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
} catch (\Exception $e) {
|
||||
throw new \Exception($e->getMessage());
|
||||
}
|
||||
|
||||
$allServices = getServiceTemplates();
|
||||
$topLevelVolumes = collect(data_get($yaml, 'volumes', []));
|
||||
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
|
||||
$dockerComposeVersion = data_get($yaml, 'version') ?? '3.8';
|
||||
@ -630,7 +631,22 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
}
|
||||
}
|
||||
$definedNetwork = collect([$resource->uuid]);
|
||||
$services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource) {
|
||||
$services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource, $allServices) {
|
||||
// Workarounds for beta users.
|
||||
if ($serviceName === 'registry') {
|
||||
$tempServiceName = "docker-registry";
|
||||
} else {
|
||||
$tempServiceName = $serviceName;
|
||||
}
|
||||
if (str(data_get($service, 'image'))->contains('glitchtip')) {
|
||||
$tempServiceName = 'glitchtip';
|
||||
}
|
||||
$serviceDefinition = data_get($allServices, $tempServiceName);
|
||||
$predefinedPort = data_get($serviceDefinition, 'port');
|
||||
if ($serviceName === 'plausible') {
|
||||
$predefinedPort = '8000';
|
||||
}
|
||||
// End of workarounds for beta users.
|
||||
$serviceVolumes = collect(data_get($service, 'volumes', []));
|
||||
$servicePorts = collect(data_get($service, 'ports', []));
|
||||
$serviceNetworks = collect(data_get($service, 'networks', []));
|
||||
@ -852,7 +868,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
]
|
||||
);
|
||||
}
|
||||
$savedService->getFilesFromServer(isInit: true);
|
||||
dispatch(new ServerFilesFromServerJob($savedService));
|
||||
return $volume;
|
||||
});
|
||||
data_set($service, 'volumes', $serviceVolumes->toArray());
|
||||
@ -905,10 +921,11 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
$fqdn = "$fqdn:$port";
|
||||
}
|
||||
if (substr_count($key->value(), '_') >= 2) {
|
||||
if (is_null($value)) {
|
||||
$value = Str::of('/');
|
||||
if ($value) {
|
||||
$path = $value->value();
|
||||
} else {
|
||||
$path = null;
|
||||
}
|
||||
$path = $value->value();
|
||||
if ($generatedServiceFQDNS->count() > 0) {
|
||||
$alreadyGenerated = $generatedServiceFQDNS->has($key->value());
|
||||
if ($alreadyGenerated) {
|
||||
@ -939,6 +956,25 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
'is_preview' => false,
|
||||
]);
|
||||
}
|
||||
// Caddy needs exact port in some cases.
|
||||
|
||||
if ($predefinedPort && !$key->endsWith("_{$predefinedPort}")) {
|
||||
if ($resource->server->proxyType() === 'CADDY') {
|
||||
$env = EnvironmentVariable::where([
|
||||
'key' => $key,
|
||||
'service_id' => $resource->id,
|
||||
])->first();
|
||||
if ($env) {
|
||||
$env_url = Url::fromString($savedService->fqdn);
|
||||
$env_port = $env_url->getPort();
|
||||
if ($env_port !== $predefinedPort) {
|
||||
$env_url = $env_url->withPort($predefinedPort);
|
||||
$savedService->fqdn = $env_url->__toString();
|
||||
$savedService->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// data_forget($service, "environment.$variableName");
|
||||
// $yaml = data_forget($yaml, "services.$serviceName.environment.$variableName");
|
||||
// if (count(data_get($yaml, 'services.' . $serviceName . '.environment')) === 0) {
|
||||
@ -987,6 +1023,22 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
$savedService->fqdn = $fqdn;
|
||||
$savedService->save();
|
||||
}
|
||||
// Caddy needs exact port in some cases.
|
||||
if ($predefinedPort && !$key->endsWith("_{$predefinedPort}") && $command?->value() === 'FQDN' && $resource->server->proxyType() === 'CADDY') {
|
||||
$env = EnvironmentVariable::where([
|
||||
'key' => $key,
|
||||
'service_id' => $resource->id,
|
||||
])->first();
|
||||
if ($env) {
|
||||
$env_url = Url::fromString($env->value);
|
||||
$env_port = $env_url->getPort();
|
||||
if ($env_port !== $predefinedPort) {
|
||||
$env_url = $env_url->withPort($predefinedPort);
|
||||
$savedService->fqdn = $env_url->__toString();
|
||||
$savedService->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$generatedValue = generateEnvValue($command, $resource);
|
||||
@ -1056,6 +1108,16 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
|
||||
service_name: $serviceName
|
||||
));
|
||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
|
||||
network: $resource->destination->network,
|
||||
uuid: $resource->uuid,
|
||||
domains: $fqdns,
|
||||
is_force_https_enabled: true,
|
||||
serviceLabels: $serviceLabels,
|
||||
is_gzip_enabled: $savedService->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
|
||||
service_name: $serviceName
|
||||
));
|
||||
}
|
||||
}
|
||||
if ($resource->server->isLogDrainEnabled() && $savedService->isLogDrainEnabled()) {
|
||||
@ -1354,10 +1416,11 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
$fqdn = "$fqdn:$port";
|
||||
}
|
||||
if (substr_count($key->value(), '_') >= 2) {
|
||||
if (is_null($value)) {
|
||||
$value = Str::of('/');
|
||||
if ($value) {
|
||||
$path = $value->value();
|
||||
} else {
|
||||
$path = null;
|
||||
}
|
||||
$path = $value->value();
|
||||
if ($generatedServiceFQDNS->count() > 0) {
|
||||
$alreadyGenerated = $generatedServiceFQDNS->has($key->value());
|
||||
if ($alreadyGenerated) {
|
||||
@ -1495,7 +1558,17 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
return $preview_fqdn;
|
||||
});
|
||||
}
|
||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($uuid, $fqdns, serviceLabels: $serviceLabels));
|
||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
|
||||
uuid: $uuid,
|
||||
domains: $fqdns,
|
||||
serviceLabels: $serviceLabels
|
||||
));
|
||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
|
||||
network: $resource->destination->network,
|
||||
uuid: $uuid,
|
||||
domains: $fqdns,
|
||||
serviceLabels: $serviceLabels
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -135,7 +135,7 @@ function allowedPathsForBoardingAccounts()
|
||||
{
|
||||
return [
|
||||
...allowedPathsForUnsubscribedAccounts(),
|
||||
'boarding',
|
||||
'onboarding',
|
||||
'livewire/update'
|
||||
];
|
||||
}
|
||||
|
@ -3,11 +3,11 @@
|
||||
return [
|
||||
|
||||
// @see https://docs.sentry.io/product/sentry-basics/dsn-explainer/
|
||||
'dsn' => 'https://1bbc8f762199a52aee39196adb3e8d1a@o1082494.ingest.sentry.io/4505347448045568',
|
||||
'dsn' => 'https://f0b0e6be13926d4ac68d68d51d38db8f@o1082494.ingest.us.sentry.io/4505347448045568',
|
||||
|
||||
// The release version of your application
|
||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||
'release' => '4.0.0-beta.236',
|
||||
'release' => '4.0.0-beta.237',
|
||||
// When left empty or `null` the Laravel environment will be used
|
||||
'environment' => config('app.env'),
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
<?php
|
||||
|
||||
return '4.0.0-beta.236';
|
||||
return '4.0.0-beta.237';
|
||||
|
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('applications', function (Blueprint $table) {
|
||||
$table->string('post_deployment_command')->nullable();
|
||||
$table->string('post_deployment_command_container')->nullable();
|
||||
$table->string('pre_deployment_command')->nullable();
|
||||
$table->string('pre_deployment_command_container')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('applications', function (Blueprint $table) {
|
||||
$table->dropColumn('post_deployment_command');
|
||||
$table->dropColumn('post_deployment_command_container');
|
||||
$table->dropColumn('pre_deployment_command');
|
||||
$table->dropColumn('pre_deployment_command_container');
|
||||
});
|
||||
}
|
||||
};
|
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->boolean('is_notification_notifications_enabled')->default(true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('is_notification_notifications_enabled');
|
||||
});
|
||||
}
|
||||
};
|
@ -15,12 +15,12 @@ class ApplicationSeeder extends Seeder
|
||||
public function run(): void
|
||||
{
|
||||
Application::create([
|
||||
'name' => 'coollabsio/coolify-examples:nodejs-fastify',
|
||||
'description' => 'NodeJS Fastify Example',
|
||||
'name' => 'NodeJS Fastify Example',
|
||||
'fqdn' => 'http://nodejs.127.0.0.1.sslip.io',
|
||||
'repository_project_id' => 603035348,
|
||||
'git_repository' => 'coollabsio/coolify-examples',
|
||||
'git_branch' => 'nodejs-fastify',
|
||||
'git_branch' => 'main',
|
||||
'base_directory' => '/nodejs',
|
||||
'build_pack' => 'nixpacks',
|
||||
'ports_exposes' => '3000',
|
||||
'environment_id' => 1,
|
||||
@ -30,12 +30,12 @@ public function run(): void
|
||||
'source_type' => GithubApp::class
|
||||
]);
|
||||
Application::create([
|
||||
'name' => 'coollabsio/coolify-examples:dockerfile',
|
||||
'description' => 'Dockerfile Example',
|
||||
'name' => 'Dockerfile Example',
|
||||
'fqdn' => 'http://dockerfile.127.0.0.1.sslip.io',
|
||||
'repository_project_id' => 603035348,
|
||||
'git_repository' => 'coollabsio/coolify-examples',
|
||||
'git_branch' => 'dockerfile',
|
||||
'git_branch' => 'main',
|
||||
'base_directory' => '/dockerfile',
|
||||
'build_pack' => 'dockerfile',
|
||||
'ports_exposes' => '80',
|
||||
'environment_id' => 1,
|
||||
@ -45,8 +45,7 @@ public function run(): void
|
||||
'source_type' => GithubApp::class
|
||||
]);
|
||||
Application::create([
|
||||
'name' => 'pure-dockerfile',
|
||||
'description' => 'Pure Dockerfile Example',
|
||||
'name' => 'Pure Dockerfile Example',
|
||||
'fqdn' => 'http://pure-dockerfile.127.0.0.1.sslip.io',
|
||||
'git_repository' => 'coollabsio/coolify',
|
||||
'git_branch' => 'main',
|
||||
|
@ -419,7 +419,7 @@ const magicActions = [{
|
||||
},
|
||||
{
|
||||
id: 24,
|
||||
name: 'Goto: Boarding process',
|
||||
name: 'Goto: Onboarding process',
|
||||
icon: 'goto',
|
||||
sequence: ['main', 'redirect']
|
||||
},
|
||||
@ -667,7 +667,7 @@ async function redirect() {
|
||||
targetUrl.pathname = `/team`
|
||||
break;
|
||||
case 24:
|
||||
targetUrl.pathname = `/boarding`
|
||||
targetUrl.pathname = `/onboarding`
|
||||
break;
|
||||
case 25:
|
||||
targetUrl.pathname = `/security/api-tokens`
|
||||
|
@ -150,7 +150,17 @@ class="{{ request()->is('subscription*') ? 'text-warning icon' : 'icon' }}"
|
||||
</a>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
<li title="Notifications" class="hover:bg-coolgray-200">
|
||||
<a class="hover:bg-transparent hover:no-underline" href="{{ route('notification.index') }}">
|
||||
<svg class="{{ request()->is('notifications*') ? 'text-warning icon' : 'icon' }}"
|
||||
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round"
|
||||
stroke-linejoin="round" stroke-width="2"
|
||||
d="M10 5a2 2 0 1 1 4 0a7 7 0 0 1 4 6v3a4 4 0 0 0 2 3H4a4 4 0 0 0 2-3v-3a7 7 0 0 1 4-6M9 17v1a3 3 0 0 0 6 0v-1" />
|
||||
</svg>
|
||||
Notifications
|
||||
</a>
|
||||
</li>
|
||||
@if (isInstanceAdmin())
|
||||
<li title="Settings" class="hover:bg-coolgray-200">
|
||||
<a class="hover:bg-transparent hover:no-underline" href="/settings">
|
||||
@ -167,14 +177,14 @@ class="{{ request()->is('settings*') ? 'text-warning icon' : 'icon' }}"
|
||||
</a>
|
||||
</li>
|
||||
@endif
|
||||
<li title="Boarding" class="hover:bg-coolgray-200">
|
||||
<a class="hover:bg-transparent hover:no-underline" href="{{ route('boarding') }}">
|
||||
<svg class="{{ request()->is('boarding*') ? 'text-warning icon' : 'icon' }}"
|
||||
<li title="Onboarding" class="hover:bg-coolgray-200">
|
||||
<a class="hover:bg-transparent hover:no-underline" href="{{ route('onboarding') }}">
|
||||
<svg class="{{ request()->is('onboarding*') ? 'text-warning icon' : 'icon' }}"
|
||||
viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="currentColor"
|
||||
d="M224 128a8 8 0 0 1-8 8h-88a8 8 0 0 1 0-16h88a8 8 0 0 1 8 8m-96-56h88a8 8 0 0 0 0-16h-88a8 8 0 0 0 0 16m88 112h-88a8 8 0 0 0 0 16h88a8 8 0 0 0 0-16M82.34 42.34L56 68.69L45.66 58.34a8 8 0 0 0-11.32 11.32l16 16a8 8 0 0 0 11.32 0l32-32a8 8 0 0 0-11.32-11.32m0 64L56 132.69l-10.34-10.35a8 8 0 0 0-11.32 11.32l16 16a8 8 0 0 0 11.32 0l32-32a8 8 0 0 0-11.32-11.32m0 64L56 196.69l-10.34-10.35a8 8 0 0 0-11.32 11.32l16 16a8 8 0 0 0 11.32 0l32-32a8 8 0 0 0-11.32-11.32" />
|
||||
</svg>
|
||||
Boarding
|
||||
Onboarding
|
||||
</a>
|
||||
</li>
|
||||
</div>
|
||||
|
25
resources/views/components/notifications/navbar.blade.php
Normal file
25
resources/views/components/notifications/navbar.blade.php
Normal file
@ -0,0 +1,25 @@
|
||||
<div class="pb-6">
|
||||
<div class="flex items-end gap-2">
|
||||
<h1>Team Notifications</h1>
|
||||
</div>
|
||||
<nav class="flex pt-2 pb-10">
|
||||
<ol class="inline-flex items-center">
|
||||
<li>
|
||||
<div class="flex items-center">
|
||||
<span>Currently active team: <span
|
||||
class="text-warning">{{ session('currentTeam.name') }}</span></span>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<nav class="navbar-main">
|
||||
<a class="{{ request()->routeIs('notification.index') ? 'text-white' : '' }}"
|
||||
href="{{ route('notification.index') }}">
|
||||
<button>General</button>
|
||||
</a>
|
||||
<div class="flex-1"></div>
|
||||
<div class="-mt-9">
|
||||
<livewire:switch-team />
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
@ -4,11 +4,13 @@
|
||||
href="{{ route('server.proxy', $parameters) }}">
|
||||
<button>Configuration</button>
|
||||
</a>
|
||||
@if (data_get($server, 'proxy.type') !== 'NONE')
|
||||
<a class="{{ request()->routeIs('server.proxy.dynamic-confs') ? 'text-white' : '' }}"
|
||||
href="{{ route('server.proxy.dynamic-confs', $parameters) }}">
|
||||
<button>Dynamic Configurations</button>
|
||||
</a>
|
||||
@if ($server->proxyType() !== 'NONE')
|
||||
{{-- @if ($server->proxyType() === 'TRAEFIK_V2') --}}
|
||||
<a class="{{ request()->routeIs('server.proxy.dynamic-confs') ? 'text-white' : '' }}"
|
||||
href="{{ route('server.proxy.dynamic-confs', $parameters) }}">
|
||||
<button>Dynamic Configurations</button>
|
||||
</a>
|
||||
{{-- @endif --}}
|
||||
<a class="{{ request()->routeIs('server.proxy.logs') ? 'text-white' : '' }}"
|
||||
href="{{ route('server.proxy.logs', $parameters) }}">
|
||||
<button>Logs</button>
|
||||
|
@ -8,6 +8,6 @@
|
||||
{{ str($status)->before(':')->headline() }}
|
||||
</div>
|
||||
@if (!str($status)->startsWith('Proxy') && !str($status)->contains('('))
|
||||
<div class="text-xs text-success">({{ str($status)->after(':') }})</div>
|
||||
<div class="text-xs {{ str($status)->contains('unhealthy') ? 'text-warning' : 'text-success' }}">({{ str($status)->after(':') }})</div>
|
||||
@endif
|
||||
</div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<div class="pb-6">
|
||||
<div class="flex items-end gap-2">
|
||||
<h1>Team</h1>
|
||||
<a href="/team/new"><x-forms.button>+ Add Team</x-forms.button></a>
|
||||
<a href="/team/new"><x-forms.button>+ Add Team</x-forms.button></a>
|
||||
</div>
|
||||
<nav class="flex pt-2 pb-10">
|
||||
<ol class="inline-flex items-center">
|
||||
@ -17,18 +17,15 @@ class="text-warning">{{ session('currentTeam.name') }}</span></span>
|
||||
<a class="{{ request()->routeIs('team.index') ? 'text-white' : '' }}" href="{{ route('team.index') }}">
|
||||
<button>General</button>
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('team.member.index') ? 'text-white' : '' }}" href="{{ route('team.member.index') }}">
|
||||
<a class="{{ request()->routeIs('team.member.index') ? 'text-white' : '' }}"
|
||||
href="{{ route('team.member.index') }}">
|
||||
<button>Members</button>
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('team.storage.index') ? 'text-white' : '' }}"
|
||||
href="{{ route('team.storage.index') }}">
|
||||
<button>S3 Storages</button>
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('team.notification.index') ? 'text-white' : '' }}"
|
||||
href="{{ route('team.notification.index') }}">
|
||||
<button>Notifications</button>
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('team.shared-variables.index') ? 'text-white' : '' }}"
|
||||
<a class="{{ request()->routeIs('team.shared-variables.index') ? 'text-white' : '' }}"
|
||||
href="{{ route('team.shared-variables.index') }}">
|
||||
<button>Shared Variables</button>
|
||||
</a>
|
||||
|
@ -27,6 +27,7 @@
|
||||
<ul x-data="{
|
||||
toasts: [],
|
||||
toastsHovered: false,
|
||||
timeout: null,
|
||||
expanded: false,
|
||||
layout: 'default',
|
||||
position: 'top-center',
|
||||
@ -286,14 +287,12 @@
|
||||
}
|
||||
stackToasts();
|
||||
$watch('toastsHovered', function(value) {
|
||||
|
||||
if (layout == 'default') {
|
||||
if (position.includes('bottom')) {
|
||||
resetBottom();
|
||||
} else {
|
||||
resetTop();
|
||||
}
|
||||
|
||||
if (value) {
|
||||
// calculate the new positions
|
||||
expanded = true;
|
||||
@ -319,13 +318,32 @@ class="fixed block w-full group z-[99] sm:max-w-xs"
|
||||
|
||||
<template x-for="(toast, index) in toasts" :key="toast.id">
|
||||
<li :id="toast.id" x-data="{
|
||||
toastHovered: false
|
||||
toastHovered: false,
|
||||
}" x-init="if (position.includes('bottom')) {
|
||||
$el.firstElementChild.classList.add('toast-bottom');
|
||||
$el.firstElementChild.classList.add('opacity-0', 'translate-y-full');
|
||||
} else {
|
||||
$el.firstElementChild.classList.add('opacity-0', '-translate-y-full');
|
||||
}
|
||||
$watch('toastsHovered', function(value) {
|
||||
if (value && this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
} else {
|
||||
this.timeout = setTimeout(function() {
|
||||
setTimeout(function() {
|
||||
$el.firstElementChild.classList.remove('opacity-100');
|
||||
$el.firstElementChild.classList.add('opacity-0');
|
||||
if (toasts.length == 1) {
|
||||
$el.firstElementChild.classList.remove('translate-y-0');
|
||||
$el.firstElementChild.classList.add('-translate-y-full');
|
||||
}
|
||||
setTimeout(function() {
|
||||
deleteToastWithId(toast.id)
|
||||
}, 300);
|
||||
}, 5);
|
||||
}, 2000)
|
||||
}
|
||||
});
|
||||
setTimeout(function() {
|
||||
|
||||
setTimeout(function() {
|
||||
@ -342,7 +360,7 @@ class="fixed block w-full group z-[99] sm:max-w-xs"
|
||||
}, 5);
|
||||
}, 50);
|
||||
|
||||
setTimeout(function() {
|
||||
this.timeout = setTimeout(function() {
|
||||
setTimeout(function() {
|
||||
$el.firstElementChild.classList.remove('opacity-100');
|
||||
$el.firstElementChild.classList.add('opacity-0');
|
||||
@ -390,12 +408,12 @@ class="relative flex flex-col items-start shadow-[0_5px_15px_-3px_rgb(0_0_0_/_0.
|
||||
d="M2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12ZM11.9996 7C12.5519 7 12.9996 7.44772 12.9996 8V12C12.9996 12.5523 12.5519 13 11.9996 13C11.4474 13 10.9996 12.5523 10.9996 12V8C10.9996 7.44772 11.4474 7 11.9996 7ZM12.001 14.99C11.4488 14.9892 11.0004 15.4363 10.9997 15.9886L10.9996 15.9986C10.9989 16.5509 11.446 16.9992 11.9982 17C12.5505 17.0008 12.9989 16.5537 12.9996 16.0014L12.9996 15.9914C13.0004 15.4391 12.5533 14.9908 12.001 14.99Z"
|
||||
fill="currentColor"></path>
|
||||
</svg>
|
||||
<p class="leading-2 text-neutral-200"
|
||||
x-html="toast.message">
|
||||
<p class="leading-2 text-neutral-200" x-html="toast.message">
|
||||
</p>
|
||||
</div>
|
||||
<p x-show="toast.description" :class="{ 'pl-5': toast.type!='default' }"
|
||||
class="mt-1.5 text-xs leading-2 opacity-90 whitespace-pre-wrap" x-html="toast.description"></p>
|
||||
class="mt-1.5 text-xs leading-2 opacity-90 whitespace-pre-wrap"
|
||||
x-html="toast.description"></p>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="toast.html">
|
||||
|
@ -3,7 +3,7 @@
|
||||
<div>
|
||||
@if ($currentState === 'welcome')
|
||||
<h1 class="text-5xl font-bold">Welcome to Coolify</h1>
|
||||
<p class="py-6 text-xl text-center">Let me help you to set the basics.</p>
|
||||
<p class="py-6 text-xl text-center">Let me help you set up the basics.</p>
|
||||
<div class="flex justify-center ">
|
||||
<x-forms.button class="justify-center w-64 box" wire:click="$set('currentState','explanation')">Get
|
||||
Started
|
||||
@ -24,12 +24,12 @@
|
||||
<x-highlighted text="Self-hosting with superpowers!" /></span>
|
||||
</x-slot:question>
|
||||
<x-slot:explanation>
|
||||
<p><x-highlighted text="Task automation:" /> You do not to manage your servers too much. Coolify do
|
||||
<p><x-highlighted text="Task automation:" /> You don't need to manage your servers anymore. Coolify does
|
||||
it for you.</p>
|
||||
<p><x-highlighted text="No vendor lock-in:" /> All configurations are stored on your server, so
|
||||
everything works without Coolify (except integrations and automations).</p>
|
||||
<p><x-highlighted text="Monitoring:" />You will get notified on your favourite platform (Discord,
|
||||
Telegram, Email, etc.) when something goes wrong, or an action needed from your side.</p>
|
||||
<p><x-highlighted text="No vendor lock-in:" /> All configurations are stored on your servers, so
|
||||
everything works without a connection to Coolify (except integrations and automations).</p>
|
||||
<p><x-highlighted text="Monitoring:" />You can get notified on your favourite platforms (Discord,
|
||||
Telegram, Email, etc.) when something goes wrong, or an action is needed from your side.</p>
|
||||
</x-slot:explanation>
|
||||
<x-slot:actions>
|
||||
<x-forms.button class="justify-center w-64 box" wire:click="explanation">Next
|
||||
@ -40,8 +40,8 @@
|
||||
@if ($currentState === 'select-server-type')
|
||||
<x-boarding-step title="Server">
|
||||
<x-slot:question>
|
||||
Do you want to deploy your resources on your <x-highlighted text="Localhost" />
|
||||
or on a <x-highlighted text="Remote Server" />?
|
||||
Do you want to deploy your resources to your <x-highlighted text="Localhost" />
|
||||
or to a <x-highlighted text="Remote Server" />?
|
||||
</x-slot:question>
|
||||
<x-slot:actions>
|
||||
<x-forms.button class="justify-center w-64 box" wire:target="setServerType('localhost')"
|
||||
@ -297,12 +297,12 @@
|
||||
You already have some projects. Do you want to use one of them or should I create a new one for
|
||||
you?
|
||||
@else
|
||||
I will create an initial project for you. You can change all the details later on.
|
||||
Let's create an initial project for you. You can change all the details later on.
|
||||
@endif
|
||||
</x-slot:question>
|
||||
<x-slot:actions>
|
||||
<x-forms.button class="justify-center w-64 box" wire:click="createNewProject">Let's create a new
|
||||
one!</x-forms.button>
|
||||
<x-forms.button class="justify-center w-64 box" wire:click="createNewProject">Create new
|
||||
project!</x-forms.button>
|
||||
<div>
|
||||
@if (count($projects) > 0)
|
||||
<form wire:submit='selectExistingProject' class="flex flex-col w-full gap-4 lg:w-96">
|
||||
@ -319,9 +319,9 @@
|
||||
</div>
|
||||
</x-slot:actions>
|
||||
<x-slot:explanation>
|
||||
<p>Projects are bound together several resources into one virtual group. There are no
|
||||
limitations on the number of projects you could have.</p>
|
||||
<p>Each project should have at least one environment. This helps you to create a production &
|
||||
<p>Projects contain several resources combined into one virtual group. There are no
|
||||
limitations on the number of projects you can add.</p>
|
||||
<p>Each project should have at least one environment, this allows you to create a production &
|
||||
staging version of the same application, but grouped separately.</p>
|
||||
</x-slot:explanation>
|
||||
</x-boarding-step>
|
||||
@ -331,7 +331,7 @@
|
||||
@if ($currentState === 'create-resource')
|
||||
<x-boarding-step title="Resources">
|
||||
<x-slot:question>
|
||||
I will redirect you to the new resource page, where you can create your first resource.
|
||||
Let's go to the new resource page, where you can create your first resource.
|
||||
</x-slot:question>
|
||||
<x-slot:actions>
|
||||
<div class="items-center justify-center w-64 box" wire:click="showNewResource">Let's do
|
||||
|
@ -17,7 +17,7 @@
|
||||
@endif
|
||||
@if ($projects->count() === 0 && $servers->count() === 0)
|
||||
No resources found. Add your first server & private key <a class="text-white underline"
|
||||
href="{{ route('server.create') }}">here</a> or go to the <a class="text-white underline" href="{{ route('boarding') }}">boarding page</a>.
|
||||
href="{{ route('server.create') }}">here</a> or go to the <a class="text-white underline" href="{{ route('onboarding') }}">onboarding page</a>.
|
||||
@endif
|
||||
@if ($projects->count() > 0)
|
||||
<h3 class="pb-4">Projects</h3>
|
||||
|
@ -5,9 +5,7 @@
|
||||
<div class="text-5xl font-bold tracking-tight text-center text-white">Coolify</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex items-center justify-center pb-4 text-center">
|
||||
Set your initial password
|
||||
</div>
|
||||
|
||||
<form class="flex flex-col gap-2" wire:submit='submit'>
|
||||
<x-forms.input id="email" type="email" placeholder="Email" readonly label="Email" />
|
||||
<x-forms.input id="password" type="password" placeholder="New Password" label="New Password" required />
|
||||
|
@ -7,7 +7,8 @@ class="underline text-warning">Please
|
||||
consider donating!</a>💜</span>
|
||||
<span>It enables us to keep creating features without paywalls, ensuring our work remains free and
|
||||
open.</span>
|
||||
<x-forms.button class="bg-coolgray-400" wire:click='disable'>Disable This Popup</x-forms.button>
|
||||
<x-forms.button class="bg-coolgray-400" wire:click='disableSponsorship'>Disable This
|
||||
Popup</x-forms.button>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@ -20,4 +21,16 @@ class="text-white underline">/subscription</a> to update your subscription or re
|
||||
</div>
|
||||
</x-banner>
|
||||
@endif
|
||||
@if (!currentTeam()->isAnyNotificationEnabled())
|
||||
<div class="toast">
|
||||
<div class="flex flex-col text-white rounded alert bg-coolgray-200">
|
||||
<span><span class="font-bold text-red-500">WARNING:</span> No notifications enabled.<br><br> It is highly recommended to enable at least
|
||||
one
|
||||
notification channel to receive important alerts.<br>Visit <a href="{{ route('notification.index') }}"
|
||||
class="text-white underline">/notification</a> to enable notifications.</span>
|
||||
<x-forms.button class="bg-coolgray-400" wire:click='disableNotifications'>Disable This
|
||||
Popup</x-forms.button>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
@ -6,13 +6,22 @@
|
||||
<h2>General</h2>
|
||||
<x-forms.button type="submit" label="Save">Save</x-forms.button>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="flex flex-col gap-2 lg:flex-row">
|
||||
<x-forms.input id="name" label="Name" required />
|
||||
<x-forms.input id="email" label="Email" readonly />
|
||||
</div>
|
||||
</form>
|
||||
<h2 class="py-4">Subscription</h2>
|
||||
<a href="{{ route('team.index') }}">Check in Team Settings</a>
|
||||
<form wire:submit='resetPassword' class="flex flex-col max-w-xl pt-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Reset Password</h2>
|
||||
<x-forms.button type="submit" label="Save">Reset</x-forms.button>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<x-forms.input id="current_password" label="Current Password" required type="password" />
|
||||
<x-forms.input id="new_password" label="New Password" required type="password" />
|
||||
<x-forms.input id="new_password_confirmation" label="New Password Again" required type="password" />
|
||||
</div>
|
||||
</form>
|
||||
<h2 class="py-4">Two-factor Authentication</h2>
|
||||
@if (session('status') == 'two-factor-authentication-enabled')
|
||||
<div class="mb-4 font-medium">
|
||||
|
@ -45,11 +45,11 @@
|
||||
</div>
|
||||
@endif
|
||||
@if ($application->build_pack === 'dockercompose')
|
||||
<div class="w-96">
|
||||
<x-forms.checkbox instantSave id="application.settings.is_raw_compose_deployment_enabled"
|
||||
label="Raw Compose Deployment"
|
||||
helper="WARNING: Advanced use cases only. Your docker compose file will be deployed as-is. Nothing is modified by Coolify. You need to configure the proxy parts. More info in the <a href='https://coolify.io/docs/docker/compose#raw-docker-compose-deployment'>documentation.</a>" />
|
||||
</div>
|
||||
<div class="w-96">
|
||||
<x-forms.checkbox instantSave id="application.settings.is_raw_compose_deployment_enabled"
|
||||
label="Raw Compose Deployment"
|
||||
helper="WARNING: Advanced use cases only. Your docker compose file will be deployed as-is. Nothing is modified by Coolify. You need to configure the proxy parts. More info in the <a href='https://coolify.io/docs/docker/compose#raw-docker-compose-deployment'>documentation.</a>" />
|
||||
</div>
|
||||
@if (count($parsedServices) > 0 && !$application->settings->is_raw_compose_deployment_enabled)
|
||||
@foreach (data_get($parsedServices, 'services') as $serviceName => $service)
|
||||
@if (!isDatabaseImage(data_get($service, 'image')))
|
||||
@ -210,10 +210,10 @@ class="underline" href="https://coolify.io/docs/docker/registry"
|
||||
id="application.custom_docker_run_options" label="Custom Docker Options" />
|
||||
@endif
|
||||
@else
|
||||
<x-forms.input
|
||||
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='text-white underline' href='https://coolify.io/docs/custom-docker-options'>docs.</a>"
|
||||
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
|
||||
id="application.custom_docker_run_options" label="Custom Docker Options" />
|
||||
<x-forms.input
|
||||
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='text-white underline' href='https://coolify.io/docs/custom-docker-options'>docs.</a>"
|
||||
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
|
||||
id="application.custom_docker_run_options" label="Custom Docker Options" />
|
||||
@endif
|
||||
@if ($application->build_pack === 'dockercompose')
|
||||
<x-forms.button wire:click="loadComposeFile">Reload Compose File</x-forms.button>
|
||||
@ -250,6 +250,21 @@ class="underline" href="https://coolify.io/docs/docker/registry"
|
||||
<x-forms.textarea label="Container Labels" rows="15" id="customLabels"></x-forms.textarea>
|
||||
<x-forms.button wire:click="resetDefaultLabels">Reset to Coolify Generated Labels</x-forms.button>
|
||||
@endif
|
||||
|
||||
<h3 class="pt-8">Pre/Post Deployment Commands</h3>
|
||||
<div class="flex flex-col gap-2 xl:flex-row">
|
||||
<x-forms.input id="application.pre_deployment_command" label="Pre-deployment Command"
|
||||
helper="An optional script or command to execute in the existing container before the deployment begins." />
|
||||
<x-forms.input id="application.pre_deployment_command_container" label="Container Name"
|
||||
helper="The name of the container to execute within. You can leave it blank if your application only has one container." />
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 xl:flex-row">
|
||||
<x-forms.input placeholder="php artisan migrate" id="application.post_deployment_command"
|
||||
label="Post-deployment Command"
|
||||
helper="An optional script or command to execute in the newly built container after the deployment completes." />
|
||||
<x-forms.input id="application.post_deployment_command_container" label="Container Name"
|
||||
helper="The name of the container to execute within. You can leave it blank if your application only has one container." />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -13,7 +13,10 @@
|
||||
</x-modal>
|
||||
<div class="pt-6">
|
||||
<livewire:project.database.backup-edit :backup="$backup" :s3s="$s3s" :status="data_get($database, 'status')" />
|
||||
<h3 class="py-4">Executions</h3>
|
||||
<div class="flex items-center gap-2">
|
||||
<h3 class="py-4">Executions</h3>
|
||||
<x-forms.button wire:click='cleanupFailed'>Cleanup Failed Backups</x-forms.button>
|
||||
</div>
|
||||
<livewire:project.database.backup-executions :backup="$backup" :executions="$executions" />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -21,8 +21,8 @@
|
||||
<div class='text-helper'>git@..</div>
|
||||
</div>
|
||||
<div class="flex gap-1">
|
||||
<div>Preselect branch (eg: static):</div>
|
||||
<div class='text-helper'>https://github.com/coollabsio/coolify-examples/tree/static</div>
|
||||
<div>Preselect branch (eg: main):</div>
|
||||
<div class='text-helper'>https://github.com/coollabsio/coolify-examples/tree/main</div>
|
||||
</div>
|
||||
<div>
|
||||
For example application deployments, checkout <a class="text-white underline"
|
||||
|
@ -289,10 +289,10 @@ class="w-full text-white rounded input input-sm bg-coolgray-200 disabled:bg-cool
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
@if ($isDatabase)
|
||||
{{-- @if ($isDatabase)
|
||||
<div class="text-center">Swarm clusters are excluded from this type of resource at the moment. It will
|
||||
be activated soon. Stay tuned.</div>
|
||||
@endif
|
||||
@endif --}}
|
||||
@endif
|
||||
@if ($current_step === 'destinations')
|
||||
<ul class="pb-10 steps">
|
||||
|
@ -16,7 +16,7 @@
|
||||
</div>
|
||||
<div class="w-96">
|
||||
<x-forms.checkbox instantSave id="service.connect_to_docker_network" label="Connect To Predefined Network"
|
||||
helper="By default, you do not reach the Coolify defined networks.<br>Starting a docker compose based resource will have an internal network. <br>If you connect to a Coolify defined network, you maybe need to use different internal DNS names to connect to a resource.<br><br>For more information, check <a class='text-white underline' href='https://coolify.io/docs/docker/compose#connect-to-predefined-networks'>this</a>." />
|
||||
helper="By default, you do not reach the Coolify defined networks.<br>Starting a docker compose based resource will have an internal network. <br>If you connect to a Coolify defined network, you maybe need to use different internal DNS names to connect to a resource.<br><br>For more information, check <a class='text-white underline' target='_blank' href='https://coolify.io/docs/docker/compose#connect-to-predefined-networks'>this</a>." />
|
||||
</div>
|
||||
@if ($fields)
|
||||
<div>
|
||||
|
@ -17,7 +17,7 @@ class="font-normal text-white normal-case border-none rounded btn btn-primary bt
|
||||
</div>
|
||||
<div>Environment variables (secrets) for this resource.</div>
|
||||
@if ($resource->type() === 'service')
|
||||
<div>If you cannot find a variable here, or need a new one, define it in the Docker Compose file.</div>
|
||||
<div>Hardcoded variables are not shown here.</div>
|
||||
@endif
|
||||
</div>
|
||||
@if ($view === 'normal')
|
||||
|
@ -1,16 +1,29 @@
|
||||
<div>
|
||||
@if (data_get($server, 'proxy.type'))
|
||||
@if ($server->proxyType())
|
||||
<div x-init="$wire.loadProxyConfiguration">
|
||||
@if ($selectedProxy === 'TRAEFIK_V2')
|
||||
@if ($selectedProxy !== 'NONE')
|
||||
<form wire:submit='submit'>
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Configuration</h2>
|
||||
<x-forms.button type="submit">Save</x-forms.button>
|
||||
@if ($server->proxy->status === 'exited')
|
||||
<x-forms.button wire:click.prevent="change_proxy">Switch Proxy</x-forms.button>
|
||||
@else
|
||||
<x-forms.button disabled wire:click.prevent="change_proxy">Switch Proxy</x-forms.button>
|
||||
@endif
|
||||
<x-forms.button type="submit">Save</x-forms.button>
|
||||
|
||||
</div>
|
||||
<div class="pt-3 pb-4 ">Traefik v2</div>
|
||||
<div class="pb-4 "> <svg class="inline-flex w-6 h-6 mr-2 text-warning" viewBox="0 0 256 256"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="currentColor"
|
||||
d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16" />
|
||||
</svg>Before switching proxies, please read <a class="text-white underline"
|
||||
href="https://coolify.io/docs/server/switching-proxies">this</a>.</div>
|
||||
@if ($server->proxyType() === 'TRAEFIK_V2')
|
||||
<div class="pb-4">Traefik v2</div>
|
||||
@elseif ($server->proxyType() === 'CADDY')
|
||||
<div class="pb-4 ">Caddy</div>
|
||||
@endif
|
||||
@if (
|
||||
$server->proxy->last_applied_settings &&
|
||||
$server->proxy->last_saved_settings !== $server->proxy->last_applied_settings)
|
||||
@ -26,7 +39,7 @@
|
||||
<div wire:loading.remove wire:target="loadProxyConfiguration">
|
||||
@if ($proxy_settings)
|
||||
<div class="flex flex-col gap-2 pt-4">
|
||||
<x-forms.textarea label="Configuration file: traefik.conf" name="proxy_settings"
|
||||
<x-forms.textarea label="Configuration file" name="proxy_settings"
|
||||
wire:model="proxy_settings" rows="30" />
|
||||
<x-forms.button wire:click.prevent="reset_proxy_configuration">
|
||||
Reset configuration to default
|
||||
@ -40,7 +53,7 @@
|
||||
<h2>Configuration</h2>
|
||||
<x-forms.button wire:click.prevent="change_proxy">Switch Proxy</x-forms.button>
|
||||
</div>
|
||||
<div class="pt-3 pb-4">Custom (None) Proxy Selected</div>
|
||||
<div class="pt-2 pb-4">Custom (None) Proxy Selected</div>
|
||||
@else
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Configuration</h2>
|
||||
@ -57,14 +70,13 @@
|
||||
</x-forms.button>
|
||||
<x-forms.button class="box" wire:click="select_proxy('TRAEFIK_V2')">
|
||||
Traefik
|
||||
v2
|
||||
</x-forms.button>
|
||||
<x-forms.button class="box" wire:click="select_proxy('CADDY')">
|
||||
Caddy (experimental)
|
||||
</x-forms.button>
|
||||
<x-forms.button disabled class="box">
|
||||
Nginx
|
||||
</x-forms.button>
|
||||
<x-forms.button disabled class="box">
|
||||
Caddy
|
||||
</x-forms.button>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
@ -16,10 +16,10 @@
|
||||
</p>
|
||||
</x-slot:modalBody>
|
||||
</x-modal>
|
||||
@if ($server->isFunctional() && data_get($server, 'proxy.type') !== 'NONE')
|
||||
@if ($server->isFunctional() && $server->proxyType() !== 'NONE')
|
||||
@if (data_get($server, 'proxy.status') === 'running')
|
||||
<div class="flex gap-4">
|
||||
@if ($currentRoute === 'server.proxy' && $traefikDashboardAvailable)
|
||||
@if ($currentRoute === 'server.proxy' && $traefikDashboardAvailable && $server->proxyType() === 'TRAEFIK_V2')
|
||||
<button>
|
||||
<a target="_blank" href="http://{{ $serverIp }}:8080">
|
||||
Traefik Dashboard
|
||||
|
@ -19,7 +19,7 @@ class="font-normal text-white normal-case border-none rounded btn btn-primary bt
|
||||
Add</button>
|
||||
</x-slide-over>
|
||||
</div>
|
||||
<div class='pb-4'>You can add dynamic Traefik configurations here.</div>
|
||||
<div class='pb-4'>You can add dynamic proxy configurations here.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div wire:loading wire:target="loadDynamicConfigurations">
|
||||
@ -29,12 +29,12 @@ class="font-normal text-white normal-case border-none rounded btn btn-primary bt
|
||||
@if ($contents?->isNotEmpty())
|
||||
@foreach ($contents as $fileName => $value)
|
||||
<div class="flex flex-col gap-2 py-2">
|
||||
@if (str_replace('|', '.', $fileName) === 'coolify.yaml')
|
||||
@if (str_replace('|', '.', $fileName) === 'coolify.yaml' || str_replace('|', '.', $fileName) === 'Caddyfile' || str_replace('|', '.', $fileName) === 'coolify.caddy' || str_replace('|', '.', $fileName) === 'default_redirect_404.caddy')
|
||||
<div>
|
||||
<h3 class="text-white">File: {{ str_replace('|', '.', $fileName) }}</h3>
|
||||
</div>
|
||||
<x-forms.textarea disabled name="proxy_settings"
|
||||
wire:model="contents.{{ $fileName }}" rows="10" />
|
||||
wire:model="contents.{{ $fileName }}" rows="5" />
|
||||
@else
|
||||
<livewire:server.proxy.dynamic-configuration-navbar :server_id="$server->id"
|
||||
:fileName="$fileName" :value="$value" :newFile="false"
|
||||
|
@ -1,5 +1,5 @@
|
||||
<form wire:submit.prevent="addDynamicConfiguration" class="flex flex-col gap-4">
|
||||
<x-forms.input id="fileName" label="Filename (.yaml or .yml)" required />
|
||||
<x-forms.input id="fileName" label="Filename" required />
|
||||
<x-forms.textarea id="value" label="Configuration" required rows="20" />
|
||||
<x-forms.button type="submit" @click="slideOverOpen=false">Save</x-forms.button>
|
||||
</form>
|
||||
|
@ -45,7 +45,8 @@
|
||||
{{ data_get($resource, 'environment.name') }}
|
||||
</td>
|
||||
<td class="px-5 py-4 text-sm whitespace-nowrap"><a class=""
|
||||
href="{{ $resource->link() }}">{{ $resource->name }} <x-internal-link/></a>
|
||||
href="{{ $resource->link() }}">{{ $resource->name }}
|
||||
<x-internal-link /></a>
|
||||
</td>
|
||||
<td class="px-5 py-4 text-sm whitespace-nowrap">
|
||||
{{ str($resource->type())->headline() }}</td>
|
||||
@ -138,6 +139,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
@ -69,7 +69,7 @@
|
||||
<x-forms.input id="github_app.webhook_secret" label="Webhook Secret" type="password" />
|
||||
</div>
|
||||
<div class="flex items-end gap-2 ">
|
||||
<h3 class="pt-4">Permissions</h3>
|
||||
<h2 class="pt-4">Permissions</h2>
|
||||
<x-forms.button wire:click.prevent="checkPermissions">Refetch</x-forms.button>
|
||||
<a href="{{ get_permissions_path($github_app) }}">
|
||||
<x-forms.button>
|
||||
@ -93,6 +93,57 @@
|
||||
</div>
|
||||
@endif
|
||||
</form>
|
||||
<div class="w-full pt-10">
|
||||
<div class="h-full">
|
||||
<div class="flex flex-col">
|
||||
<div class="flex gap-2">
|
||||
<h2>Resources</h2>
|
||||
</div>
|
||||
<div class="pb-4 title">Here you can find all resources that are used by this source.</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-col">
|
||||
<div class="overflow-x-auto">
|
||||
<div class="inline-block min-w-full">
|
||||
<div class="overflow-hidden">
|
||||
<table class="min-w-full divide-y divide-coolgray-400">
|
||||
<thead>
|
||||
<tr class="text-neutral-500">
|
||||
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Project
|
||||
</th>
|
||||
<th class="px-5 py-3 text-xs font-medium text-left uppercase">
|
||||
Environment</th>
|
||||
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Name</th>
|
||||
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Type</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-coolgray-400">
|
||||
@forelse ($applications->sortBy('name',SORT_NATURAL) as $resource)
|
||||
<tr class="text-white bg-coolblack hover:bg-coolgray-100">
|
||||
<td class="px-5 py-4 text-sm whitespace-nowrap">
|
||||
{{ data_get($resource->project(), 'name') }}
|
||||
</td>
|
||||
<td class="px-5 py-4 text-sm whitespace-nowrap">
|
||||
{{ data_get($resource, 'environment.name') }}
|
||||
</td>
|
||||
<td class="px-5 py-4 text-sm whitespace-nowrap"><a class=""
|
||||
href="{{ $resource->link() }}">{{ $resource->name }}
|
||||
<x-internal-link /></a>
|
||||
</td>
|
||||
<td class="px-5 py-4 text-sm whitespace-nowrap">
|
||||
{{ str($resource->type())->headline() }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="flex items-center gap-2 pb-4">
|
||||
<h1>GitHub App</h1>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<div>
|
||||
<x-team.navbar />
|
||||
<x-notifications.navbar />
|
||||
<h2 class="pb-4">Notifications</h2>
|
||||
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'email' }" class="flex h-full">
|
||||
<div class="flex flex-col gap-4 min-w-fit">
|
||||
|
@ -1,8 +1,10 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\Api\Deploy;
|
||||
use App\Http\Controllers\Api\Project;
|
||||
use App\Http\Controllers\Api\Domains;
|
||||
use App\Http\Controllers\Api\Resources;
|
||||
use App\Http\Controllers\Api\Server;
|
||||
use App\Http\Controllers\Api\Team;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
@ -29,11 +31,24 @@
|
||||
return response(config('version'));
|
||||
});
|
||||
Route::get('/deploy', [Deploy::class, 'deploy']);
|
||||
Route::get('/deployments', [Deploy::class, 'deployments']);
|
||||
|
||||
Route::get('/servers', [Server::class, 'servers']);
|
||||
Route::get('/server/{uuid}', [Server::class, 'server_by_uuid']);
|
||||
Route::get('/projects', [Project::class, 'projects']);
|
||||
Route::get('/project/{uuid}', [Project::class, 'project_by_uuid']);
|
||||
Route::get('/project/{uuid}/{environment_name}', [Project::class, 'environment_details']);
|
||||
|
||||
Route::get('/resources', [Resources::class, 'resources']);
|
||||
Route::get('/domains', [Domains::class, 'domains']);
|
||||
|
||||
Route::get('/teams', [Team::class, 'teams']);
|
||||
Route::get('/team/current', [Team::class, 'current_team']);
|
||||
Route::get('/team/current/members', [Team::class, 'current_team_members']);
|
||||
Route::get('/team/{id}', [Team::class, 'team_by_id']);
|
||||
Route::get('/team/{id}/members', [Team::class, 'members_by_id']);
|
||||
|
||||
|
||||
//Route::get('/projects', [Project::class, 'projects']);
|
||||
//Route::get('/project/{uuid}', [Project::class, 'project_by_uuid']);
|
||||
//Route::get('/project/{uuid}/{environment_name}', [Project::class, 'environment_details']);
|
||||
});
|
||||
|
||||
Route::get('/{any}', function () {
|
||||
|
@ -85,7 +85,7 @@
|
||||
Route::get('/admin', AdminIndex::class)->name('admin.index');
|
||||
|
||||
Route::post('/forgot-password', [Controller::class, 'forgot_password'])->name('password.forgot');
|
||||
Route::get('/api/v1/test/realtime', [Controller::class, 'realtime_test'])->middleware('auth');
|
||||
Route::get('/realtime', [Controller::class, 'realtime_test'])->middleware('auth');
|
||||
Route::get('/waitlist', WaitlistIndex::class)->name('waitlist.index');
|
||||
Route::get('/verify', [Controller::class, 'verify'])->middleware('auth')->name('verify.email');
|
||||
Route::get('/email/verify/{id}/{hash}', [Controller::class, 'email_verify'])->middleware(['auth'])->name('verify.verify');
|
||||
@ -108,7 +108,7 @@
|
||||
});
|
||||
|
||||
Route::get('/', Dashboard::class)->name('dashboard');
|
||||
Route::get('/boarding', BoardingIndex::class)->name('boarding');
|
||||
Route::get('/onboarding', BoardingIndex::class)->name('onboarding');
|
||||
|
||||
Route::get('/subscription', SubscriptionShow::class)->name('subscription.show');
|
||||
Route::get('/subscription/new', SubscriptionIndex::class)->name('subscription.index');
|
||||
@ -121,11 +121,13 @@
|
||||
Route::get('/', TagsIndex::class)->name('tags.index');
|
||||
Route::get('/{tag_name}', TagsShow::class)->name('tags.show');
|
||||
});
|
||||
Route::prefix('notifications')->group(function () {
|
||||
Route::get('/', TeamNotificationIndex::class)->name('notification.index');
|
||||
});
|
||||
Route::prefix('team')->group(function () {
|
||||
Route::get('/', TeamIndex::class)->name('team.index');
|
||||
Route::get('/new', TeamCreate::class)->name('team.create');
|
||||
Route::get('/members', TeamMemberIndex::class)->name('team.member.index');
|
||||
Route::get('/notifications', TeamNotificationIndex::class)->name('team.notification.index');
|
||||
Route::get('/shared-variables', TeamSharedVariablesIndex::class)->name('team.shared-variables.index');
|
||||
Route::get('/storages', TeamStorageIndex::class)->name('team.storage.index');
|
||||
Route::get('/storages/new', TeamStorageCreate::class)->name('team.storage.create');
|
||||
|
@ -6,7 +6,7 @@ set -e # Exit immediately if a command exits with a non-zero status
|
||||
#set -u # Treat unset variables as an error and exit
|
||||
set -o pipefail # Cause a pipeline to return the status of the last command that exited with a non-zero status
|
||||
|
||||
VERSION="1.2.2"
|
||||
VERSION="1.2.3"
|
||||
DOCKER_VERSION="24.0"
|
||||
|
||||
CDN="https://cdn.coollabs.io/coolify"
|
||||
@ -122,6 +122,16 @@ if [ "$SSH_PERMIT_ROOT_LOGIN" != "true" ]; then
|
||||
echo "###############################################################################"
|
||||
fi
|
||||
|
||||
# Detect if docker is installed via snap
|
||||
if [ -x "$(command -v snap)" ]; then
|
||||
if snap list | grep -q docker; then
|
||||
echo "Docker is installed via snap."
|
||||
echo "Please note that Coolify does not support Docker installed via snap."
|
||||
echo "Please remove Docker with snap (snap remove docker) and reexecute this script."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! [ -x "$(command -v docker)" ]; then
|
||||
if [ "$OS_TYPE" == 'almalinux' ]; then
|
||||
dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
|
||||
|
@ -2,6 +2,7 @@
|
||||
# slogan: Website change detection monitor and notifications.
|
||||
# tags: web, alert, monitor
|
||||
# logo: svgs/changedetection.png
|
||||
# port: 5000
|
||||
|
||||
services:
|
||||
changedetection:
|
||||
@ -9,7 +10,7 @@ services:
|
||||
volumes:
|
||||
- changedetection-data:/datastore
|
||||
environment:
|
||||
- SERVICE_FQDN_CHANGEDETECTION
|
||||
- SERVICE_FQDN_CHANGEDETECTION_5000
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- BASE_URL=$SERVICE_FQDN_CHANGEDETECTION
|
||||
|
@ -2,12 +2,13 @@
|
||||
# slogan: Code-Server is a web-based code editor that enables remote coding and collaboration from any device, anywhere.
|
||||
# tags: code, editor, remote, collaboration
|
||||
# logo: svgs/code-server.svg
|
||||
# port: 8443
|
||||
|
||||
services:
|
||||
code-server:
|
||||
image: lscr.io/linuxserver/code-server:latest
|
||||
environment:
|
||||
- SERVICE_FQDN_CODESERVER
|
||||
- SERVICE_FQDN_CODESERVER_8443
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=Europe/Madrid
|
||||
|
@ -1,12 +1,13 @@
|
||||
# documentation: https://github.com/phntxx/dashboard?tab=readme-ov-file#dashboard
|
||||
# slogan: A dashboard, inspired by SUI.
|
||||
# tags: dashboard, web, search, bookmarks
|
||||
# port: 8080
|
||||
|
||||
services:
|
||||
dashboard:
|
||||
image: phntxx/dashboard:latest
|
||||
environment:
|
||||
- SERVICE_FQDN_DASHBOARD
|
||||
- SERVICE_FQDN_DASHBOARD_8080
|
||||
volumes:
|
||||
- dashboard-data:/app/data
|
||||
healthcheck:
|
||||
|
@ -2,6 +2,7 @@
|
||||
# slogan: Directus wraps databases with a dynamic API, and provides an intuitive app for managing its content.
|
||||
# tags: directus, cms, database, sql
|
||||
# logo: svgs/directus.svg
|
||||
# port: 8055
|
||||
|
||||
services:
|
||||
directus:
|
||||
@ -10,7 +11,7 @@ services:
|
||||
- directus-uploads:/directus/uploads
|
||||
- directus-extensions:/directus/extensions
|
||||
environment:
|
||||
- SERVICE_FQDN_DIRECTUS
|
||||
- SERVICE_FQDN_DIRECTUS_8055
|
||||
- KEY=$SERVICE_BASE64_64_KEY
|
||||
- SECRET=$SERVICE_BASE64_64_SECRET
|
||||
- ADMIN_EMAIL=${ADMIN_EMAIL:-admin@example.com}
|
||||
|
@ -2,15 +2,17 @@
|
||||
# slogan: Directus wraps databases with a dynamic API, and provides an intuitive app for managing its content.
|
||||
# tags: directus, cms, database, sql
|
||||
# logo: svgs/directus.svg
|
||||
# port: 8055
|
||||
|
||||
services:
|
||||
directus:
|
||||
image: directus/directus:10.7
|
||||
image: directus/directus:10
|
||||
volumes:
|
||||
- directus-database:/directus/database
|
||||
- directus-uploads:/directus/uploads
|
||||
- directus-database:/directus/database
|
||||
- directus-extensions:/directus/extensions
|
||||
environment:
|
||||
- SERVICE_FQDN_DIRECTUS
|
||||
- SERVICE_FQDN_DIRECTUS_8055
|
||||
- KEY=$SERVICE_BASE64_64_KEY
|
||||
- SECRET=$SERVICE_BASE64_64_SECRET
|
||||
- ADMIN_EMAIL=${ADMIN_EMAIL:-admin@example.com}
|
||||
|
@ -2,12 +2,13 @@
|
||||
# slogan: The Docker Registry is lets you distribute Docker images.
|
||||
# tags: registry,images,docker
|
||||
# logo: svgs/docker-registry.png
|
||||
# port: 5000
|
||||
|
||||
services:
|
||||
registry:
|
||||
image: registry:2
|
||||
environment:
|
||||
- SERVICE_FQDN_REGISTRY
|
||||
- SERVICE_FQDN_REGISTRY_5000
|
||||
- REGISTRY_AUTH=htpasswd
|
||||
- REGISTRY_AUTH_HTPASSWD_REALM=Registry
|
||||
- REGISTRY_AUTH_HTPASSWD_PATH=/auth/registry.password
|
||||
|
@ -2,12 +2,13 @@
|
||||
# slogan: Duplicati is a backup solution, allowing you to make scheduled backups with encryption.
|
||||
# tags: backup, encryption
|
||||
# logo: svgs/duplicati.webp
|
||||
# port: 8200
|
||||
|
||||
services:
|
||||
duplicati:
|
||||
image: lscr.io/linuxserver/duplicati:latest
|
||||
environment:
|
||||
- SERVICE_FQDN_DUPLICATI
|
||||
- SERVICE_FQDN_DUPLICATI_8200
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=Europe/Madrid
|
||||
|
@ -2,12 +2,13 @@
|
||||
# slogan: A media server software that allows you to organize, stream, and access your multimedia content effortlessly.
|
||||
# tags: media, server, movies, tv, music
|
||||
# logo: svgs/emby.png
|
||||
# port: 8096
|
||||
|
||||
services:
|
||||
emby:
|
||||
image: lscr.io/linuxserver/emby:latest
|
||||
environment:
|
||||
- SERVICE_FQDN_EMBY
|
||||
- SERVICE_FQDN_EMBY_8096
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=Europe/Madrid
|
||||
|
@ -1,12 +1,13 @@
|
||||
# documentation: https://github.com/mregni/EmbyStat
|
||||
# slogan: EmnyStat is a web analytics tool, designed to provide insight into website traffic and user behavior.
|
||||
# tags: media, server, movies, tv, music
|
||||
# port: 6555
|
||||
|
||||
services:
|
||||
embystat:
|
||||
image: lscr.io/linuxserver/embystat:latest
|
||||
environment:
|
||||
- SERVICE_FQDN_EMBYSTAT
|
||||
- SERVICE_FQDN_EMBYSTAT_6555
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=Europe/Madrid
|
||||
|
@ -2,12 +2,13 @@
|
||||
# slogan: Fider is a feedback platform for collecting and managing user feedback.
|
||||
# tags: feedback, user-feedback
|
||||
# logo: svgs/fider.svg
|
||||
# port: 3000
|
||||
|
||||
services:
|
||||
fider:
|
||||
image: getfider/fider:stable
|
||||
environment:
|
||||
BASE_URL: $SERVICE_FQDN_FIDER
|
||||
BASE_URL: $SERVICE_FQDN_FIDER_3000
|
||||
DATABASE_URL: postgres://$SERVICE_USER_MYSQL:$SERVICE_PASSWORD_MYSQL@database:5432/fider?sslmode=disable
|
||||
JWT_SECRET: $SERVICE_PASSWORD_64_FIDER
|
||||
EMAIL_NOREPLY: ${EMAIL_NOREPLY:-noreply@example.com}
|
||||
|
@ -2,12 +2,13 @@
|
||||
# slogan: A personal finances manager that can help you save money.
|
||||
# tags: finance, money, personal, manager
|
||||
# logo: svgs/firefly.svg
|
||||
# port: 8080
|
||||
|
||||
services:
|
||||
firefly:
|
||||
image: fireflyiii/core:latest
|
||||
environment:
|
||||
- SERVICE_FQDN_FIREFLY
|
||||
- SERVICE_FQDN_FIREFLY_8080
|
||||
- APP_KEY=$SERVICE_BASE64_APPKEY
|
||||
- DB_HOST=mysql
|
||||
- DB_PORT=3306
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user