main: begin major rewrite for lasthour

This commit is contained in:
Gary 2024-07-25 13:50:18 -07:00
parent e2bcfbadf8
commit 6a268ce65b
85 changed files with 2681 additions and 2800 deletions

View File

@ -12,7 +12,7 @@ tasks:
./vendor/bin/spin exec -u webuser coolify php artisan key:generate ./vendor/bin/spin exec -u webuser coolify php artisan key:generate
./vendor/bin/spin exec -u webuser coolify php artisan storage:link ./vendor/bin/spin exec -u webuser coolify php artisan storage:link
./vendor/bin/spin exec -u webuser coolify php artisan migrate:fresh --seed ./vendor/bin/spin exec -u webuser coolify php artisan migrate:fresh --seed
cat .coolify-logo cat .jesus-is-king
gp sync-done spin-is-ready gp sync-done spin-is-ready
- name: Install Node dependencies and run Vite - name: Install Node dependencies and run Vite

View File

@ -0,0 +1,28 @@
<?php
/**
* @label Send Email
* @description Send email to all users
*/
use App\Models\User;
use Illuminate\Support\Facades\Mail;
set_transanctional_email_settings();
$users = User::whereEmail('test@example.com');
foreach ($users as $user) {
Mail::send([], [], function ($message) use ($user) {
$message
->to($user->email)
->subject("Testing")
->text(
<<<EOF
Hello,
Welcome to Last Hour Cloud.
Here is your user id: $user->id
EOF
);
});
}

View File

@ -1,94 +0,0 @@
# Citizen Code of Conduct
## 1. Purpose
A primary goal of Coolify is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof).
This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior.
We invite all those who participate in Coolify to help us create safe and positive experiences for everyone.
## 2. Open [Source/Culture/Tech] Citizenship
A supplemental goal of this Code of Conduct is to increase open [source/culture/tech] citizenship by encouraging participants to recognize and strengthen the relationships between our actions and their effects on our community.
Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society.
If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all participants to contribute to the fullest extent, we want to know.
## 3. Expected Behavior
The following behaviors are expected and requested of all community members:
* Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community.
* Exercise consideration and respect in your speech and actions.
* Attempt collaboration before conflict.
* Refrain from demeaning, discriminatory, or harassing behavior and speech.
* Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential.
* Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations.
## 4. Unacceptable Behavior
The following behaviors are considered harassment and are unacceptable within our community:
* Violence, threats of violence or violent language directed against another person.
* Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language.
* Posting or displaying sexually explicit or violent material.
* Posting or threatening to post other people's personally identifying information ("doxing").
* Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability.
* Inappropriate photography or recording.
* Inappropriate physical contact. You should have someone's consent before touching them.
* Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances.
* Deliberate intimidation, stalking or following (online or in person).
* Advocating for, or encouraging, any of the above behavior.
* Sustained disruption of community events, including talks and presentations.
## 5. Weapons Policy
No weapons will be allowed at Coolify events, community spaces, or in other spaces covered by the scope of this Code of Conduct. Weapons include but are not limited to guns, explosives (including fireworks), and large knives such as those used for hunting or display, as well as any other item used for the purpose of causing injury or harm to others. Anyone seen in possession of one of these items will be asked to leave immediately, and will only be allowed to return without the weapon. Community members are further expected to comply with all state and local laws on this matter.
## 6. Consequences of Unacceptable Behavior
Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated.
Anyone asked to stop unacceptable behavior is expected to comply immediately.
If a community member engages in unacceptable behavior, the community organizers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event).
## 7. Reporting Guidelines
If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. hi@coollabs.io.
Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress.
## 8. Addressing Grievances
If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify coollabsio with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies.
## 9. Scope
We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code of Conduct in all community venues--online and in-person--as well as in all one-on-one communications pertaining to community business.
This code of conduct and its related procedures also applies to unacceptable behavior occurring outside the scope of community activities when such behavior has the potential to adversely affect the safety and well-being of community members.
## 10. Contact info
hi@coollabs.io
## 11. License and attribution
The Citizen Code of Conduct is distributed by [Stumptown Syndicate](http://stumptownsyndicate.org) under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/).
Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy).
_Revision 2.3. Posted 6 March 2017._
_Revision 2.2. Posted 4 February 2016._
_Revision 2.1. Posted 23 June 2014._
_Revision 2.0, adopted by the [Stumptown Syndicate](http://stumptownsyndicate.org) board on 10 January 2013. Posted 17 March 2013._

View File

@ -22,14 +22,14 @@ class UpdateCoolify
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();
ray('Running InstanceAutoUpdateJob'); ray('Running InstanceAutoUpdateJob');
$this->server = Server::find(0); $this->server = Server::find(0);
if (! $this->server) { if (!$this->server) {
return; return;
} }
CleanupDocker::dispatch($this->server, false)->onQueue('high'); CleanupDocker::dispatch($this->server, false)->onQueue('high');
$this->latestVersion = get_latest_version_of_coolify(); $this->latestVersion = get_latest_version_of_coolify();
$this->currentVersion = config('version'); $this->currentVersion = config('version');
if (! $manual_update) { if (!$manual_update) {
if (! $settings->is_auto_update_enabled) { if (!$settings->is_auto_update_enabled) {
return; return;
} }
if ($this->latestVersion === $this->currentVersion) { if ($this->latestVersion === $this->currentVersion) {
@ -50,7 +50,15 @@ class UpdateCoolify
if (isDev()) { if (isDev()) {
ray('Running in dev mode'); ray('Running in dev mode');
remote_process([ remote_process([
'sleep 10', "sleep 10"
], $this->server);
ray('Update done');
return;
} else {
ray('Running update on production server');
remote_process([
"curl -fsSL https://cdn.lasthourhosting.org/lasthourcloud/scripts/upgrade.sh -o /data/coolify/source/upgrade.sh",
"bash /data/coolify/source/upgrade.sh $this->latestVersion"
], $this->server); ], $this->server);
return; return;
@ -59,6 +67,5 @@ class UpdateCoolify
'curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh', 'curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh',
"bash /data/coolify/source/upgrade.sh $this->latestVersion", "bash /data/coolify/source/upgrade.sh $this->latestVersion",
], $this->server); ], $this->server);
} }
} }

View File

@ -80,7 +80,7 @@ class Init extends Command
try { try {
$database = StandalonePostgresql::withTrashed()->find(0); $database = StandalonePostgresql::withTrashed()->find(0);
if ($database && $database->trashed()) { if ($database && $database->trashed()) {
echo "Restoring coolify db backup\n"; echo "Restoring Last Hour Cloud db backup\n";
$database->restore(); $database->restore();
$scheduledBackup = ScheduledDatabaseBackup::find(0); $scheduledBackup = ScheduledDatabaseBackup::find(0);
if (! $scheduledBackup) { if (! $scheduledBackup) {
@ -96,7 +96,7 @@ class Init extends Command
} }
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
echo "Error in restoring coolify db backup: {$e->getMessage()}\n"; echo "Error in restoring Last Hour Cloud db backup: {$e->getMessage()}\n";
} }
} }
@ -122,7 +122,7 @@ class Init extends Command
return; return;
} }
try { try {
Http::get("https://undead.coolify.io/v4/alive?appId=$id&version=$version"); Http::get("");
echo "I am alive!\n"; echo "I am alive!\n";
} catch (\Throwable $e) { } catch (\Throwable $e) {
echo "Error in alive: {$e->getMessage()}\n"; echo "Error in alive: {$e->getMessage()}\n";

View File

@ -49,7 +49,7 @@ class NotifyDemo extends Command
<<<'HTML' <<<'HTML'
<div> <div>
<div class="title-box"> <div class="title-box">
Coolify Last Hour Cloud
</div> </div>
<p class="mt-1 ml-1 "> <p class="mt-1 ml-1 ">
Demo Notify <strong class="text-coolify">=></strong> Send a demo notification to a given channel. Demo Notify <strong class="text-coolify">=></strong> Send a demo notification to a given channel.
@ -72,7 +72,7 @@ class NotifyDemo extends Command
ask(<<<'HTML' ask(<<<'HTML'
<div class="mr-1"> <div class="mr-1">
In which manner you wish a <strong class="text-coolify">coolified</strong> notification? In which manner you wish a <strong class="text-coolify">Last Hour Cloud</strong> notification?
</div> </div>
HTML, ['email', 'slack', 'discord', 'telegram']); HTML, ['email', 'slack', 'discord', 'telegram']);
} }

View File

@ -0,0 +1,184 @@
<?php
namespace App\Http\Controllers\Api;
use App\Actions\Database\StartMariadb;
use App\Actions\Database\StartMongodb;
use App\Actions\Database\StartMysql;
use App\Actions\Database\StartPostgresql;
use App\Actions\Database\StartRedis;
use App\Actions\Service\StartService;
use App\Http\Controllers\Controller;
use App\Models\ApplicationDeploymentQueue;
use App\Models\Server;
use App\Models\Tag;
use Illuminate\Http\Request;
use Visus\Cuid2\Cuid2;
class Deploy extends Controller
{
public function deployments(Request $request)
{
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return invalid_token();
}
$servers = Server::whereTeamId($teamId)->get();
$deployments_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn("server_id", $servers->pluck("id"))->get([
"id",
"application_id",
"application_name",
"deployment_url",
"pull_request_id",
"server_name",
"server_id",
"status"
])->sortBy('id')->toArray();
return response()->json($deployments_per_server, 200);
}
public function deploy(Request $request)
{
$teamId = get_team_id_from_token();
$uuids = $request->query->get('uuid');
$tags = $request->query->get('tag');
$force = $request->query->get('force') ?? false;
if ($uuids && $tags) {
return response()->json(['error' => 'You can only use uuid or tag, not both.', 'upstream docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
}
if (is_null($teamId)) {
return invalid_token();
}
if ($tags) {
return $this->by_tags($tags, $teamId, $force);
} else if ($uuids) {
return $this->by_uuids($uuids, $teamId, $force);
}
return response()->json(['error' => 'You must provide uuid or tag.', 'upstream docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
}
private function by_uuids(string $uuid, int $teamId, bool $force = false)
{
$uuids = explode(',', $uuid);
$uuids = collect(array_filter($uuids));
if (count($uuids) === 0) {
return response()->json(['error' => 'No UUIDs provided.', 'upstream docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
}
$deployments = collect();
$payload = collect();
foreach ($uuids as $uuid) {
$resource = getResourceByUuid($uuid, $teamId);
if ($resource) {
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
if ($deployment_uuid) {
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
} else {
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid]);
}
}
}
if ($deployments->count() > 0) {
$payload->put('deployments', $deployments->toArray());
return response()->json($payload->toArray(), 200);
}
return response()->json(['error' => "No resources found.", 'upstream docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
}
public function by_tags(string $tags, int $team_id, bool $force = false)
{
$tags = explode(',', $tags);
$tags = collect(array_filter($tags));
if (count($tags) === 0) {
return response()->json(['error' => 'No TAGs provided.', 'upstream 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.");
continue;
}
$applications = $found_tag->applications()->get();
$services = $found_tag->services()->get();
if ($applications->count() === 0 && $services->count() === 0) {
$message->push("No resources found for tag {$tag}.");
continue;
}
foreach ($applications as $resource) {
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
if ($deployment_uuid) {
$deployments->push(['resource_uuid' => $resource->uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
}
$message = $message->merge($return_message);
}
foreach ($services as $resource) {
['message' => $return_message] = $this->deploy_resource($resource, $force);
$message = $message->merge($return_message);
}
}
ray($message);
if ($message->count() > 0) {
$payload->put('message', $message->toArray());
if ($deployments->count() > 0) {
$payload->put('details', $deployments->toArray());
}
return response()->json($payload->toArray(), 200);
}
return response()->json(['error' => "No resources found with this tag.", 'upstream docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
}
public function deploy_resource($resource, bool $force = false): array
{
$message = null;
$deployment_uuid = null;
if (gettype($resource) !== 'object') {
return ['message' => "Resource ($resource) not found.", 'deployment_uuid' => $deployment_uuid];
}
$type = $resource?->getMorphClass();
if ($type === 'App\Models\Application') {
$deployment_uuid = new Cuid2(7);
queue_application_deployment(
application: $resource,
deployment_uuid: $deployment_uuid,
force_rebuild: $force,
);
$message = "Application {$resource->name} deployment queued.";
} else if ($type === 'App\Models\StandalonePostgresql') {
StartPostgresql::run($resource);
$resource->update([
'started_at' => now(),
]);
$message = "Database {$resource->name} started.";
} else if ($type === 'App\Models\StandaloneRedis') {
StartRedis::run($resource);
$resource->update([
'started_at' => now(),
]);
$message = "Database {$resource->name} started.";
} else if ($type === 'App\Models\StandaloneMongodb') {
StartMongodb::run($resource);
$resource->update([
'started_at' => now(),
]);
$message = "Database {$resource->name} started.";
} else if ($type === 'App\Models\StandaloneMysql') {
StartMysql::run($resource);
$resource->update([
'started_at' => now(),
]);
$message = "Database {$resource->name} started.";
} else if ($type === 'App\Models\StandaloneMariadb') {
StartMariadb::run($resource);
$resource->update([
'started_at' => now(),
]);
$message = "Database {$resource->name} started.";
} else if ($type === 'App\Models\Service') {
StartService::run($resource);
$message = "Service {$resource->name} started. It could take a while, be patient.";
}
return ['message' => $message, 'deployment_uuid' => $deployment_uuid];
}
}

View File

@ -81,8 +81,8 @@ hwAAAAtzc2gtZWQyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevA
AAAECBQw4jg1WRT2IGHMncCiZhURCts2s24HoDS0thHnnRKVuGmoeGq/pojrsyP1pszcNV AAAECBQw4jg1WRT2IGHMncCiZhURCts2s24HoDS0thHnnRKVuGmoeGq/pojrsyP1pszcNV
uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
-----END OPENSSH PRIVATE KEY-----'; -----END OPENSSH PRIVATE KEY-----';
$this->privateKeyDescription = 'Created by Coolify'; $this->privateKeyDescription = 'Created by Last Hour Cloud';
$this->remoteServerDescription = 'Created by Coolify'; $this->remoteServerDescription = 'Created by Last Hour Cloud';
$this->remoteServerHost = 'coolify-testing-host'; $this->remoteServerHost = 'coolify-testing-host';
} }
// if ($this->currentState === 'create-project') { // if ($this->currentState === 'create-project') {
@ -352,7 +352,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
private function createNewPrivateKey() private function createNewPrivateKey()
{ {
$this->privateKeyName = generate_random_name(); $this->privateKeyName = generate_random_name();
$this->privateKeyDescription = 'Created by Coolify'; $this->privateKeyDescription = 'Created by Last Hour Cloud';
['private' => $this->privateKey, 'public' => $this->publicKey] = generateSSHKey(); ['private' => $this->privateKey, 'public' => $this->publicKey] = generateSSHKey();
} }

View File

@ -60,7 +60,7 @@ class Help extends Component
} else { } else {
send_user_an_email($mail, auth()->user()?->email, 'support@lasthourhosting.org'); send_user_an_email($mail, auth()->user()?->email, 'support@lasthourhosting.org');
} }
$this->dispatch('success', 'Feedback sent.', 'Thank you! We will get in touch with you as soon as possible.'); $this->dispatch('success', 'Help request sent.', 'Thank you! We will get in touch with you as soon as possible.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@ -147,13 +147,13 @@ class General extends Component
$this->ports_exposes = $this->application->ports_exposes; $this->ports_exposes = $this->application->ports_exposes;
$this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled; $this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled;
$this->customLabels = $this->application->parseContainerLabels(); $this->customLabels = $this->application->parseContainerLabels();
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && ! $this->application->settings->is_container_label_readonly_enabled) { if (!$this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && !$this->application->settings->is_container_label_readonly_enabled) {
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n"); $this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
$this->application->custom_labels = base64_encode($this->customLabels); $this->application->custom_labels = base64_encode($this->customLabels);
$this->application->save(); $this->application->save();
} }
$this->initialDockerComposeLocation = $this->application->docker_compose_location; $this->initialDockerComposeLocation = $this->application->docker_compose_location;
if ($this->application->build_pack === 'dockercompose' && ! $this->application->docker_compose_raw) { if ($this->application->build_pack === 'dockercompose' && !$this->application->docker_compose_raw) {
$this->initLoadingCompose = true; $this->initLoadingCompose = true;
$this->dispatch('info', 'Loading docker compose file.'); $this->dispatch('info', 'Loading docker compose file.');
} }
@ -314,7 +314,7 @@ class General extends Component
$domains = str($this->application->fqdn)->trim()->explode(','); $domains = str($this->application->fqdn)->trim()->explode(',');
if ($this->application->additional_servers->count() === 0) { if ($this->application->additional_servers->count() === 0) {
foreach ($domains as $domain) { foreach ($domains as $domain) {
if (! validate_dns_entry($domain, $this->application->destination->server)) { if (!validate_dns_entry($domain, $this->application->destination->server)) {
$showToaster && $this->dispatch('error', 'Validating DNS failed.', "Make sure you have added the DNS records correctly.<br><br>$domain->{$this->application->destination->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help."); $showToaster && $this->dispatch('error', 'Validating DNS failed.', "Make sure you have added the DNS records correctly.<br><br>$domain->{$this->application->destination->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
} }
} }
@ -357,7 +357,7 @@ class General extends Component
$this->checkFqdns(); $this->checkFqdns();
$this->application->save(); $this->application->save();
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && ! $this->application->settings->is_container_label_readonly_enabled) { if (!$this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && !$this->application->settings->is_container_label_readonly_enabled) {
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n"); $this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
$this->application->custom_labels = base64_encode($this->customLabels); $this->application->custom_labels = base64_encode($this->customLabels);
$this->application->save(); $this->application->save();
@ -379,13 +379,12 @@ class General extends Component
'application.docker_registry_image_name' => 'required', 'application.docker_registry_image_name' => 'required',
]); ]);
} }
if (data_get($this->application, 'custom_docker_run_options')) { if (data_get($this->application, 'custom_docker_run_options')) {
$this->application->custom_docker_run_options = str($this->application->custom_docker_run_options)->trim(); $this->application->custom_docker_run_options = str($this->application->custom_docker_run_options)->trim();
} }
if (data_get($this->application, 'dockerfile')) { if (data_get($this->application, 'dockerfile')) {
$port = get_port_from_dockerfile($this->application->dockerfile); $port = get_port_from_dockerfile($this->application->dockerfile);
if ($port && ! $this->application->ports_exposes) { if ($port && !$this->application->ports_exposes) {
$this->application->ports_exposes = $port; $this->application->ports_exposes = $port;
} }
} }
@ -401,7 +400,7 @@ class General extends Component
foreach ($this->parsedServiceDomains as $serviceName => $service) { foreach ($this->parsedServiceDomains as $serviceName => $service) {
$domain = data_get($service, 'domain'); $domain = data_get($service, 'domain');
if ($domain) { if ($domain) {
if (! validate_dns_entry($domain, $this->application->destination->server)) { if (!validate_dns_entry($domain, $this->application->destination->server)) {
$showToaster && $this->dispatch('error', 'Validating DNS failed.', "Make sure you have added the DNS records correctly.<br><br>$domain->{$this->application->destination->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help."); $showToaster && $this->dispatch('error', 'Validating DNS failed.', "Make sure you have added the DNS records correctly.<br><br>$domain->{$this->application->destination->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
} }
check_domain_usage(resource: $this->application); check_domain_usage(resource: $this->application);

View File

@ -123,8 +123,12 @@ class Heading extends Component
public function restart() public function restart()
{ {
if ($this->application->additional_servers->count() > 0 && str($this->application->docker_registry_image_name)->isEmpty()) { if ($this->application->additional_servers->count() > 0 && str($this->application->docker_registry_image_name)->isEmpty()) {
<<<<<<< HEAD
$this->dispatch('error', 'Failed to deploy', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/multiple-servers">documentation</a>'); $this->dispatch('error', 'Failed to deploy', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/multiple-servers">documentation</a>');
=======
$this->dispatch('error', 'Failed to deploy', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here in upstream docs: <a target="_blank" class="underline" href="https://coolify.io/docs/server/multiple-servers">documentation</a>');
>>>>>>> 35700ec24 (main: begin major rewrite for lasthour)
return; return;
} }
$this->setDeploymentUuid(); $this->setDeploymentUuid();

View File

@ -36,7 +36,7 @@ class Create extends Component
try { try {
$this->rateLimit(10); $this->rateLimit(10);
$this->name = generate_random_name(); $this->name = generate_random_name();
$this->description = 'Created by Coolify'; $this->description = 'Created by Last Hour Cloud';
['private' => $this->value, 'public' => $this->publicKey] = generateSSHKey(); ['private' => $this->value, 'public' => $this->publicKey] = generateSSHKey();
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);

View File

@ -116,7 +116,6 @@ class Form extends Component
} }
if ($this->server->settings->isDirty('is_server_api_enabled') && $this->server->settings->is_server_api_enabled === true) { if ($this->server->settings->isDirty('is_server_api_enabled') && $this->server->settings->is_server_api_enabled === true) {
ray('Starting sentinel'); ray('Starting sentinel');
} }
} else { } else {
ray('Sentinel is not enabled'); ray('Sentinel is not enabled');
@ -156,7 +155,7 @@ class Form extends Component
$this->server->settings->save(); $this->server->settings->save();
$this->dispatch('proxyStatusUpdated'); $this->dispatch('proxyStatusUpdated');
} else { } else {
$this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br>Error: '.$error); $this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br>Error: ' . $error);
return; return;
} }
@ -172,7 +171,7 @@ class Form extends Component
public function submit() public function submit()
{ {
if (isCloud() && ! isDev()) { if (isCloud() && !isDev()) {
$this->validate(); $this->validate();
$this->validate([ $this->validate([
'server.ip' => 'required', 'server.ip' => 'required',

View File

@ -53,7 +53,7 @@ class ValidateAndInstall extends Component
$this->proxy_started = null; $this->proxy_started = null;
$this->error = null; $this->error = null;
$this->number_of_tries = $data; $this->number_of_tries = $data;
if (! $this->ask) { if (!$this->ask) {
$this->dispatch('validateConnection'); $this->dispatch('validateConnection');
} }
} }
@ -86,8 +86,8 @@ class ValidateAndInstall extends Component
public function validateConnection() public function validateConnection()
{ {
['uptime' => $this->uptime, 'error' => $error] = $this->server->validateConnection(); ['uptime' => $this->uptime, 'error' => $error] = $this->server->validateConnection();
if (! $this->uptime) { if (!$this->uptime) {
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br>Check this <a target="_blank" class="text-black underline dark:text-white" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br><div class="text-error">Error: '.$error.'</div>'; $this->error = 'Server is not reachable. Please validate your configuration and connection.<br>Check this <a target="_blank" class="text-black underline dark:text-white" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br><div class="text-error">Error: ' . $error . '</div>';
$this->server->update([ $this->server->update([
'validation_logs' => $this->error, 'validation_logs' => $this->error,
]); ]);
@ -100,7 +100,7 @@ class ValidateAndInstall extends Component
public function validateOS() public function validateOS()
{ {
$this->supported_os_type = $this->server->validateOS(); $this->supported_os_type = $this->server->validateOS();
if (! $this->supported_os_type) { if (!$this->supported_os_type) {
$this->error = 'Server OS type is not supported. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.'; $this->error = 'Server OS type is not supported. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
$this->server->update([ $this->server->update([
'validation_logs' => $this->error, 'validation_logs' => $this->error,
@ -115,7 +115,7 @@ class ValidateAndInstall extends Component
{ {
$this->docker_installed = $this->server->validateDockerEngine(); $this->docker_installed = $this->server->validateDockerEngine();
$this->docker_compose_installed = $this->server->validateDockerCompose(); $this->docker_compose_installed = $this->server->validateDockerCompose();
if (! $this->docker_installed || ! $this->docker_compose_installed) { if (!$this->docker_installed || !$this->docker_compose_installed) {
if ($this->install) { if ($this->install) {
if ($this->number_of_tries == $this->max_tries) { if ($this->number_of_tries == $this->max_tries) {
$this->error = 'Docker Engine could not be installed. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.'; $this->error = 'Docker Engine could not be installed. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';

View File

@ -51,7 +51,7 @@ class S3Storage extends BaseModel
$this->is_usable = false; $this->is_usable = false;
if ($this->unusable_email_sent === false && is_transactional_emails_active()) { if ($this->unusable_email_sent === false && is_transactional_emails_active()) {
$mail = new MailMessage(); $mail = new MailMessage();
$mail->subject('Last Hour: S3 Storage Connection Error'); $mail->subject('Last Hour Cloud: S3 Storage Connection Error');
$mail->view('emails.s3-connection-error', ['name' => $this->name, 'reason' => $e->getMessage(), 'url' => route('team.storage.show', ['storage_uuid' => $this->uuid])]); $mail->view('emails.s3-connection-error', ['name' => $this->name, 'reason' => $e->getMessage(), 'url' => route('team.storage.show', ['storage_uuid' => $this->uuid])]);
$users = collect([]); $users = collect([]);
$members = $this->team->members()->get(); $members = $this->team->members()->get();

View File

@ -132,7 +132,7 @@ class User extends Authenticatable implements SendsEmail
$mail->view('emails.email-verification', [ $mail->view('emails.email-verification', [
'url' => $url, 'url' => $url,
]); ]);
$mail->subject('Last Hour: Verify your email.'); $mail->subject('Last Hour Cloud: Verify your email.');
send_user_an_email($mail, $this->email); send_user_an_email($mail, $this->email);
} }

View File

@ -57,10 +57,10 @@ class DeploymentFailed extends Notification implements ShouldQueue
$pull_request_id = data_get($this->preview, 'pull_request_id', 0); $pull_request_id = data_get($this->preview, 'pull_request_id', 0);
$fqdn = $this->fqdn; $fqdn = $this->fqdn;
if ($pull_request_id === 0) { if ($pull_request_id === 0) {
$mail->subject('Last Hour: Deployment failed of ' . $this->application_name . '.'); $mail->subject('Last Hour Cloud: Deployment failed of ' . $this->application_name . '.');
} else { } else {
$fqdn = $this->preview->fqdn; $fqdn = $this->preview->fqdn;
$mail->subject('Last Hour: Deployment failed of pull request #' . $this->preview->pull_request_id . ' of ' . $this->application_name . '.'); $mail->subject('Last Hour Cloud: Deployment failed of pull request #' . $this->preview->pull_request_id . ' of ' . $this->application_name . '.');
} }
$mail->view('emails.application-deployment-failed', [ $mail->view('emails.application-deployment-failed', [
'name' => $this->application_name, 'name' => $this->application_name,
@ -75,10 +75,10 @@ class DeploymentFailed extends Notification implements ShouldQueue
public function toDiscord(): string public function toDiscord(): string
{ {
if ($this->preview) { if ($this->preview) {
$message = 'Last Hour: Pull request #' . $this->preview->pull_request_id . ' of **' . $this->application_name . '** (' . $this->preview->fqdn . ') deployment failed: '; $message = 'Last Hour Cloud: Pull request #' . $this->preview->pull_request_id . ' of **' . $this->application_name . '** (' . $this->preview->fqdn . ') deployment failed: ';
$message .= '[View Deployment Logs](' . $this->deployment_url . ')'; $message .= '[View Deployment Logs](' . $this->deployment_url . ')';
} else { } else {
$message = 'Last Hour: Deployment failed of **' . $this->application_name . '** (' . $this->fqdn . '): '; $message = 'Last Hour Cloud: Deployment failed of **' . $this->application_name . '** (' . $this->fqdn . '): ';
$message .= '[View Deployment Logs](' . $this->deployment_url . ')'; $message .= '[View Deployment Logs](' . $this->deployment_url . ')';
} }
@ -88,9 +88,9 @@ class DeploymentFailed extends Notification implements ShouldQueue
public function toTelegram(): array public function toTelegram(): array
{ {
if ($this->preview) { if ($this->preview) {
$message = 'Last Hour: Pull request #' . $this->preview->pull_request_id . ' of **' . $this->application_name . '** (' . $this->preview->fqdn . ') deployment failed: '; $message = 'Last Hour Cloud: Pull request #' . $this->preview->pull_request_id . ' of **' . $this->application_name . '** (' . $this->preview->fqdn . ') deployment failed: ';
} else { } else {
$message = 'Last Hour: Deployment failed of **' . $this->application_name . '** (' . $this->fqdn . '): '; $message = 'Last Hour Cloud: Deployment failed of **' . $this->application_name . '** (' . $this->fqdn . '): ';
} }
$buttons[] = [ $buttons[] = [
'text' => 'Deployment logs', 'text' => 'Deployment logs',

View File

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

View File

@ -45,7 +45,7 @@ class StatusChanged extends Notification implements ShouldQueue
{ {
$mail = new MailMessage; $mail = new MailMessage;
$fqdn = $this->fqdn; $fqdn = $this->fqdn;
$mail->subject("Last Hour: {$this->resource_name} has been stopped"); $mail->subject("Last Hour Cloud: {$this->resource_name} has been stopped");
$mail->view('emails.application-status-changes', [ $mail->view('emails.application-status-changes', [
'name' => $this->resource_name, 'name' => $this->resource_name,
'fqdn' => $fqdn, 'fqdn' => $fqdn,
@ -57,21 +57,21 @@ class StatusChanged extends Notification implements ShouldQueue
public function toDiscord(): string public function toDiscord(): string
{ {
$message = 'Last Hour: ' . $this->resource_name . ' has been stopped. $message = 'Last Hour Cloud: ' . $this->resource_name . ' has been stopped.
'; ';
$message .= '[Open Application in Last Hour](' . $this->resource_url . ')'; $message .= '[Open Application in Last Hour Cloud](' . $this->resource_url . ')';
return $message; return $message;
} }
public function toTelegram(): array public function toTelegram(): array
{ {
$message = 'Last Hour: ' . $this->resource_name . ' has been stopped.'; $message = 'Last Hour Cloud: ' . $this->resource_name . ' has been stopped.';
return [ return [
'message' => $message, 'message' => $message,
'buttons' => [ 'buttons' => [
[ [
"text" => "Open Application in Last Hour", "text" => "Open Application in Last Hour Cloud",
"url" => $this->resource_url "url" => $this->resource_url
] ]
], ],

View File

@ -26,7 +26,7 @@ class ContainerRestarted extends Notification implements ShouldQueue
public function toMail(): MailMessage public function toMail(): MailMessage
{ {
$mail = new MailMessage(); $mail = new MailMessage();
$mail->subject("Last Hour: A resource ({$this->name}) has been restarted automatically on {$this->server->name}"); $mail->subject("Last Hour Cloud: A resource ({$this->name}) has been restarted automatically on {$this->server->name}");
$mail->view('emails.container-restarted', [ $mail->view('emails.container-restarted', [
'containerName' => $this->name, 'containerName' => $this->name,
'serverName' => $this->server->name, 'serverName' => $this->server->name,
@ -38,13 +38,13 @@ class ContainerRestarted extends Notification implements ShouldQueue
public function toDiscord(): string public function toDiscord(): string
{ {
$message = "Last Hour: A resource ({$this->name}) has been restarted automatically on {$this->server->name}"; $message = "Last Hour Cloud: A resource ({$this->name}) has been restarted automatically on {$this->server->name}";
return $message; return $message;
} }
public function toTelegram(): array public function toTelegram(): array
{ {
$message = "Last Hour: A resource ({$this->name}) has been restarted automatically on {$this->server->name}"; $message = "Last Hour Cloud: A resource ({$this->name}) has been restarted automatically on {$this->server->name}";
$payload = [ $payload = [
'message' => $message, 'message' => $message,
]; ];
@ -52,7 +52,7 @@ class ContainerRestarted extends Notification implements ShouldQueue
$payload['buttons'] = [ $payload['buttons'] = [
[ [
[ [
"text" => "Check Proxy in Last Hour", "text" => "Check Proxy in Last Hour Cloud",
"url" => $this->url "url" => $this->url
] ]
] ]

View File

@ -26,7 +26,7 @@ class ContainerStopped extends Notification implements ShouldQueue
public function toMail(): MailMessage public function toMail(): MailMessage
{ {
$mail = new MailMessage(); $mail = new MailMessage();
$mail->subject("Last Hour: A resource has been stopped unexpectedly on {$this->server->name}"); $mail->subject("Last Hour Cloud: A resource has been stopped unexpectedly on {$this->server->name}");
$mail->view('emails.container-stopped', [ $mail->view('emails.container-stopped', [
'containerName' => $this->name, 'containerName' => $this->name,
'serverName' => $this->server->name, 'serverName' => $this->server->name,
@ -38,13 +38,13 @@ class ContainerStopped extends Notification implements ShouldQueue
public function toDiscord(): string public function toDiscord(): string
{ {
$message = "Last Hour: A resource ($this->name) has been stopped unexpectedly on {$this->server->name}"; $message = "Last Hour Cloud: A resource ($this->name) has been stopped unexpectedly on {$this->server->name}";
return $message; return $message;
} }
public function toTelegram(): array public function toTelegram(): array
{ {
$message = "Last Hour: A resource ($this->name) has been stopped unexpectedly on {$this->server->name}"; $message = "Last Hour Cloud: A resource ($this->name) has been stopped unexpectedly on {$this->server->name}";
$payload = [ $payload = [
'message' => $message, 'message' => $message,
]; ];
@ -52,7 +52,7 @@ class ContainerStopped extends Notification implements ShouldQueue
$payload['buttons'] = [ $payload['buttons'] = [
[ [
[ [
"text" => "Open Application in Last Hour", "text" => "Open Application in Last Hour Cloud",
"url" => $this->url "url" => $this->url
] ]
] ]

View File

@ -34,7 +34,7 @@ class BackupFailed extends Notification implements ShouldQueue
public function toMail(): MailMessage public function toMail(): MailMessage
{ {
$mail = new MailMessage(); $mail = new MailMessage();
$mail->subject("Last Hour: [ACTION REQUIRED] Backup FAILED for {$this->database->name}"); $mail->subject("Last Hour Cloud: [ACTION REQUIRED] Backup FAILED for {$this->database->name}");
$mail->view('emails.backup-failed', [ $mail->view('emails.backup-failed', [
'name' => $this->name, 'name' => $this->name,
'database_name' => $this->database_name, 'database_name' => $this->database_name,
@ -47,12 +47,12 @@ class BackupFailed extends Notification implements ShouldQueue
public function toDiscord(): string public function toDiscord(): string
{ {
return "Last Hour: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was FAILED.\n\nReason:\n{$this->output}"; return "Last Hour Cloud: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was FAILED.\n\nReason:\n{$this->output}";
} }
public function toTelegram(): array public function toTelegram(): array
{ {
$message = "Last Hour: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was FAILED.\n\nReason:\n{$this->output}"; $message = "Last Hour Cloud: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was FAILED.\n\nReason:\n{$this->output}";
return [ return [
'message' => $message, 'message' => $message,

View File

@ -34,7 +34,7 @@ class BackupSuccess extends Notification implements ShouldQueue
public function toMail(): MailMessage public function toMail(): MailMessage
{ {
$mail = new MailMessage(); $mail = new MailMessage();
$mail->subject("Last Hour: Backup successfully done for {$this->database->name}"); $mail->subject("Last Hour Cloud: Backup successfully done for {$this->database->name}");
$mail->view('emails.backup-success', [ $mail->view('emails.backup-success', [
'name' => $this->name, 'name' => $this->name,
'database_name' => $this->database_name, 'database_name' => $this->database_name,
@ -46,12 +46,12 @@ class BackupSuccess extends Notification implements ShouldQueue
public function toDiscord(): string public function toDiscord(): string
{ {
return "Last Hour: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was successful."; return "Last Hour Cloud: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was successful.";
} }
public function toTelegram(): array public function toTelegram(): array
{ {
$message = "Last Hour: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was successful."; $message = "Last Hour Cloud: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was successful.";
ray($message); ray($message);
return [ return [

View File

@ -44,7 +44,7 @@ class HighDiskUsage extends Notification implements ShouldQueue
public function toMail(): MailMessage public function toMail(): MailMessage
{ {
$mail = new MailMessage(); $mail = new MailMessage();
$mail->subject("Last Hour: Server ({$this->server->name}) high disk usage detected!"); $mail->subject("Last Hour Cloud: Server ({$this->server->name}) high disk usage detected!");
$mail->view('emails.high-disk-usage', [ $mail->view('emails.high-disk-usage', [
'name' => $this->server->name, 'name' => $this->server->name,
'disk_usage' => $this->disk_usage, 'disk_usage' => $this->disk_usage,
@ -56,7 +56,7 @@ class HighDiskUsage extends Notification implements ShouldQueue
public function toDiscord(): string public function toDiscord(): string
{ {
$message = "Last Hour: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->cleanup_after_percentage}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup."; $message = "Last Hour Cloud: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->cleanup_after_percentage}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.";
return $message; return $message;
} }
@ -64,7 +64,7 @@ class HighDiskUsage extends Notification implements ShouldQueue
public function toTelegram(): array public function toTelegram(): array
{ {
return [ return [
'message' => "Last Hour: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->cleanup_after_percentage}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.", 'message' => "Last Hou Cloud: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->cleanup_after_percentage}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.",
]; ];
} }
} }

View File

@ -51,7 +51,7 @@ class Revived extends Notification implements ShouldQueue
public function toMail(): MailMessage public function toMail(): MailMessage
{ {
$mail = new MailMessage(); $mail = new MailMessage();
$mail->subject("Last Hour: Server ({$this->server->name}) revived."); $mail->subject("Last Hour Cloud: Server ({$this->server->name}) revived.");
$mail->view('emails.server-revived', [ $mail->view('emails.server-revived', [
'name' => $this->server->name, 'name' => $this->server->name,
]); ]);
@ -61,14 +61,14 @@ class Revived extends Notification implements ShouldQueue
public function toDiscord(): string public function toDiscord(): string
{ {
$message = "Last Hour: Server '{$this->server->name}' revived. All automations & integrations are turned on again!"; $message = "Last Hour Cloud: Server '{$this->server->name}' revived. All automations & integrations are turned on again!";
return $message; return $message;
} }
public function toTelegram(): array public function toTelegram(): array
{ {
return [ return [
"message" => "Last Hour: Server '{$this->server->name}' revived. All automations & integrations are turned on again!" "message" => "Last Hour Cloud: Server '{$this->server->name}' revived. All automations & integrations are turned on again!"
]; ];
} }
} }

View File

@ -44,7 +44,7 @@ class Unreachable extends Notification implements ShouldQueue
public function toMail(): MailMessage public function toMail(): MailMessage
{ {
$mail = new MailMessage(); $mail = new MailMessage();
$mail->subject("Last Hour: Your server ({$this->server->name}) is unreachable."); $mail->subject("Last Hour Cloud: Your server ({$this->server->name}) is unreachable.");
$mail->view('emails.server-lost-connection', [ $mail->view('emails.server-lost-connection', [
'name' => $this->server->name, 'name' => $this->server->name,
]); ]);
@ -54,14 +54,14 @@ class Unreachable extends Notification implements ShouldQueue
public function toDiscord(): string public function toDiscord(): string
{ {
$message = "Last Hour: Your server '{$this->server->name}' is unreachable. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server and turn on all automations & integrations."; $message = "Last Hour Cloud: Your server '{$this->server->name}' is unreachable. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server and turn on all automations & integrations.";
return $message; return $message;
} }
public function toTelegram(): array public function toTelegram(): array
{ {
return [ return [
"message" => "Last Hour: Your server '{$this->server->name}' is unreachable. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server and turn on all automations & integrations." "message" => "Last Hour Cloud: Your server '{$this->server->name}' is unreachable. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server and turn on all automations & integrations."
]; ];
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -62,27 +62,27 @@ function base_configuration_dir(): string
} }
function application_configuration_dir(): string function application_configuration_dir(): string
{ {
return base_configuration_dir().'/applications'; return base_configuration_dir() . '/applications';
} }
function service_configuration_dir(): string function service_configuration_dir(): string
{ {
return base_configuration_dir().'/services'; return base_configuration_dir() . '/services';
} }
function database_configuration_dir(): string function database_configuration_dir(): string
{ {
return base_configuration_dir().'/databases'; return base_configuration_dir() . '/databases';
} }
function database_proxy_dir($uuid): string function database_proxy_dir($uuid): string
{ {
return base_configuration_dir()."/databases/$uuid/proxy"; return base_configuration_dir() . "/databases/$uuid/proxy";
} }
function backup_dir(): string function backup_dir(): string
{ {
return base_configuration_dir().'/backups'; return base_configuration_dir() . '/backups';
} }
function metrics_dir(): string function metrics_dir(): string
{ {
return base_configuration_dir().'/metrics'; return base_configuration_dir() . '/metrics';
} }
function generate_readme_file(string $name, string $updated_at): string function generate_readme_file(string $name, string $updated_at): string
@ -110,15 +110,15 @@ function showBoarding(): bool
} }
function refreshSession(?Team $team = null): void function refreshSession(?Team $team = null): void
{ {
if (! $team) { if (!$team) {
if (auth()->user()?->currentTeam()) { if (auth()->user()?->currentTeam()) {
$team = Team::find(auth()->user()->currentTeam()->id); $team = Team::find(auth()->user()->currentTeam()->id);
} else { } else {
$team = User::find(auth()->user()->id)->teams->first(); $team = User::find(auth()->user()->id)->teams->first();
} }
} }
Cache::forget('team:'.auth()->user()->id); Cache::forget('team:' . auth()->user()->id);
Cache::remember('team:'.auth()->user()->id, 3600, function () use ($team) { Cache::remember('team:' . auth()->user()->id, 3600, function () use ($team) {
return $team; return $team;
}); });
session(['currentTeam' => $team]); session(['currentTeam' => $team]);
@ -147,7 +147,7 @@ function handleError(?Throwable $error = null, ?Livewire\Component $livewire = n
$message = null; $message = null;
} }
if ($customErrorMessage) { if ($customErrorMessage) {
$message = $customErrorMessage.' '.$message; $message = $customErrorMessage . ' ' . $message;
} }
if (isset($livewire)) { if (isset($livewire)) {
@ -163,7 +163,7 @@ function get_route_parameters(): array
function get_latest_sentinel_version(): string function get_latest_sentinel_version(): string
{ {
try { try {
$response = Http::get('https://cdn.coollabs.io/sentinel/versions.json'); $response = Http::get('https://cdn.lasthourhosting.org/lasthourcloud/versions.json');
$versions = $response->json(); $versions = $response->json();
return data_get($versions, 'sentinel.version'); return data_get($versions, 'sentinel.version');
@ -227,7 +227,7 @@ function generateSSHKey(string $type = 'rsa')
function formatPrivateKey(string $privateKey) function formatPrivateKey(string $privateKey)
{ {
$privateKey = trim($privateKey); $privateKey = trim($privateKey);
if (! str_ends_with($privateKey, "\n")) { if (!str_ends_with($privateKey, "\n")) {
$privateKey .= "\n"; $privateKey .= "\n";
} }
@ -249,7 +249,7 @@ function is_transactional_emails_active(): bool
function set_transanctional_email_settings(?InstanceSettings $settings = null): ?string function set_transanctional_email_settings(?InstanceSettings $settings = null): ?string
{ {
if (! $settings) { if (!$settings) {
$settings = \App\Models\InstanceSettings::get(); $settings = \App\Models\InstanceSettings::get();
} }
config()->set('mail.from.address', data_get($settings, 'smtp_from_address')); config()->set('mail.from.address', data_get($settings, 'smtp_from_address'));
@ -353,7 +353,7 @@ function isDev(): bool
function isCloud(): bool function isCloud(): bool
{ {
return ! config('coolify.self_hosted'); return !config('coolify.self_hosted');
} }
function validate_cron_expression($expression_to_validate): bool function validate_cron_expression($expression_to_validate): bool
@ -381,7 +381,7 @@ function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null
{ {
$settings = \App\Models\InstanceSettings::get(); $settings = \App\Models\InstanceSettings::get();
$type = set_transanctional_email_settings($settings); $type = set_transanctional_email_settings($settings);
if (! $type) { if (!$type) {
throw new Exception('No email settings found.'); throw new Exception('No email settings found.');
} }
if ($cc) { if ($cc) {
@ -530,7 +530,7 @@ function getResourceByUuid(string $uuid, ?int $teamId = null)
return null; return null;
} }
$resource = queryResourcesByUuid($uuid); $resource = queryResourcesByUuid($uuid);
if (! is_null($resource) && $resource->environment->project->team_id === $teamId) { if (!is_null($resource) && $resource->environment->project->team_id === $teamId) {
return $resource; return $resource;
} }
@ -622,30 +622,30 @@ function queryResourcesByUuid(string $uuid)
function generatTagDeployWebhook($tag_name) function generatTagDeployWebhook($tag_name)
{ {
$baseUrl = base_url(); $baseUrl = base_url();
$api = Url::fromString($baseUrl).'/api/v1'; $api = Url::fromString($baseUrl) . '/api/v1';
$endpoint = "/deploy?tag=$tag_name"; $endpoint = "/deploy?tag=$tag_name";
$url = $api.$endpoint; $url = $api . $endpoint;
return $url; return $url;
} }
function generateDeployWebhook($resource) function generateDeployWebhook($resource)
{ {
$baseUrl = base_url(); $baseUrl = base_url();
$api = Url::fromString($baseUrl).'/api/v1'; $api = Url::fromString($baseUrl) . '/api/v1';
$endpoint = '/deploy'; $endpoint = '/deploy';
$uuid = data_get($resource, 'uuid'); $uuid = data_get($resource, 'uuid');
$url = $api.$endpoint."?uuid=$uuid&force=false"; $url = $api . $endpoint . "?uuid=$uuid&force=false";
return $url; return $url;
} }
function generateGitManualWebhook($resource, $type) function generateGitManualWebhook($resource, $type)
{ {
if ($resource->source_id !== 0 && ! is_null($resource->source_id)) { if ($resource->source_id !== 0 && !is_null($resource->source_id)) {
return null; return null;
} }
if ($resource->getMorphClass() === 'App\Models\Application') { if ($resource->getMorphClass() === 'App\Models\Application') {
$baseUrl = base_url(); $baseUrl = base_url();
$api = Url::fromString($baseUrl)."/webhooks/source/$type/events/manual"; $api = Url::fromString($baseUrl) . "/webhooks/source/$type/events/manual";
return $api; return $api;
} }
@ -674,7 +674,7 @@ function getTopLevelNetworks(Service|Application $resource)
$hasHostNetworkMode = data_get($service, 'network_mode') === 'host' ? true : false; $hasHostNetworkMode = data_get($service, 'network_mode') === 'host' ? true : false;
// Only add 'networks' key if 'network_mode' is not 'host' // Only add 'networks' key if 'network_mode' is not 'host'
if (! $hasHostNetworkMode) { if (!$hasHostNetworkMode) {
// Collect/create/update networks // Collect/create/update networks
if ($serviceNetworks->count() > 0) { if ($serviceNetworks->count() > 0) {
foreach ($serviceNetworks as $networkName => $networkDetails) { foreach ($serviceNetworks as $networkName => $networkDetails) {
@ -688,7 +688,7 @@ function getTopLevelNetworks(Service|Application $resource)
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) { $networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
return $value == $networkName || $key == $networkName; return $value == $networkName || $key == $networkName;
}); });
if (! $networkExists) { if (!$networkExists) {
$topLevelNetworks->put($networkDetails, null); $topLevelNetworks->put($networkDetails, null);
} }
} }
@ -697,7 +697,7 @@ function getTopLevelNetworks(Service|Application $resource)
$definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) { $definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) {
return $value == $definedNetwork; return $value == $definedNetwork;
}); });
if (! $definedNetworkExists) { if (!$definedNetworkExists) {
foreach ($definedNetwork as $network) { foreach ($definedNetwork as $network) {
$topLevelNetworks->put($network, [ $topLevelNetworks->put($network, [
'name' => $network, 'name' => $network,
@ -738,7 +738,7 @@ function getTopLevelNetworks(Service|Application $resource)
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) { $networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
return $value == $networkName || $key == $networkName; return $value == $networkName || $key == $networkName;
}); });
if (! $networkExists) { if (!$networkExists) {
$topLevelNetworks->put($networkDetails, null); $topLevelNetworks->put($networkDetails, null);
} }
} }
@ -746,7 +746,7 @@ function getTopLevelNetworks(Service|Application $resource)
$definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) { $definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) {
return $value == $definedNetwork; return $value == $definedNetwork;
}); });
if (! $definedNetworkExists) { if (!$definedNetworkExists) {
foreach ($definedNetwork as $network) { foreach ($definedNetwork as $network) {
$topLevelNetworks->put($network, [ $topLevelNetworks->put($network, [
'name' => $network, 'name' => $network,
@ -825,7 +825,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
if ($serviceLabels->count() > 0) { if ($serviceLabels->count() > 0) {
$removedLabels = collect([]); $removedLabels = collect([]);
$serviceLabels = $serviceLabels->filter(function ($serviceLabel, $serviceLabelName) use ($removedLabels) { $serviceLabels = $serviceLabels->filter(function ($serviceLabel, $serviceLabelName) use ($removedLabels) {
if (! str($serviceLabel)->contains('=')) { if (!str($serviceLabel)->contains('=')) {
$removedLabels->put($serviceLabelName, $serviceLabel); $removedLabels->put($serviceLabelName, $serviceLabel);
return false; return false;
@ -907,7 +907,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) { $networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
return $value == $networkName || $key == $networkName; return $value == $networkName || $key == $networkName;
}); });
if (! $networkExists) { if (!$networkExists) {
$topLevelNetworks->put($networkDetails, null); $topLevelNetworks->put($networkDetails, null);
} }
} }
@ -931,12 +931,12 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$savedService->ports = $collectedPorts->implode(','); $savedService->ports = $collectedPorts->implode(',');
$savedService->save(); $savedService->save();
if (! $hasHostNetworkMode) { if (!$hasHostNetworkMode) {
// Add Coolify specific networks // Add Coolify specific networks
$definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) { $definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) {
return $value == $definedNetwork; return $value == $definedNetwork;
}); });
if (! $definedNetworkExists) { if (!$definedNetworkExists) {
foreach ($definedNetwork as $network) { foreach ($definedNetwork as $network) {
$topLevelNetworks->put($network, [ $topLevelNetworks->put($network, [
'name' => $network, 'name' => $network,
@ -1128,9 +1128,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$fqdn = "$fqdn$path"; $fqdn = "$fqdn$path";
} }
if (! $isDatabase) { if (!$isDatabase) {
if ($savedService->fqdn) { if ($savedService->fqdn) {
data_set($savedService, 'fqdn', $savedService->fqdn.','.$fqdn); data_set($savedService, 'fqdn', $savedService->fqdn . ',' . $fqdn);
} else { } else {
data_set($savedService, 'fqdn', $fqdn); data_set($savedService, 'fqdn', $fqdn);
} }
@ -1145,7 +1145,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
]); ]);
} }
// Caddy needs exact port in some cases. // Caddy needs exact port in some cases.
if ($predefinedPort && ! $key->endsWith("_{$predefinedPort}")) { if ($predefinedPort && !$key->endsWith("_{$predefinedPort}")) {
$fqdns_exploded = str($savedService->fqdn)->explode(','); $fqdns_exploded = str($savedService->fqdn)->explode(',');
if ($fqdns_exploded->count() > 1) { if ($fqdns_exploded->count() > 1) {
continue; continue;
@ -1185,12 +1185,12 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
'service_id' => $resource->id, 'service_id' => $resource->id,
])->first(); ])->first();
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value); ['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
if (! is_null($command)) { if (!is_null($command)) {
if ($command?->value() === 'FQDN' || $command?->value() === 'URL') { if ($command?->value() === 'FQDN' || $command?->value() === 'URL') {
if (Str::lower($forService) === $serviceName) { if (Str::lower($forService) === $serviceName) {
$fqdn = generateFqdn($resource->server, $containerName); $fqdn = generateFqdn($resource->server, $containerName);
} else { } else {
$fqdn = generateFqdn($resource->server, Str::lower($forService).'-'.$resource->uuid); $fqdn = generateFqdn($resource->server, Str::lower($forService) . '-' . $resource->uuid);
} }
if ($port) { if ($port) {
$fqdn = "$fqdn:$port"; $fqdn = "$fqdn:$port";
@ -1220,13 +1220,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
'is_preview' => false, 'is_preview' => false,
]); ]);
} }
if (! $isDatabase) { if (!$isDatabase) {
if ($command->value() === 'FQDN' && is_null($savedService->fqdn) && ! $foundEnv) { if ($command->value() === 'FQDN' && is_null($savedService->fqdn) && !$foundEnv) {
$savedService->fqdn = $fqdn; $savedService->fqdn = $fqdn;
$savedService->save(); $savedService->save();
} }
// Caddy needs exact port in some cases. // Caddy needs exact port in some cases.
if ($predefinedPort && ! $key->endsWith("_{$predefinedPort}") && $command?->value() === 'FQDN' && $resource->server->proxyType() === 'CADDY') { if ($predefinedPort && !$key->endsWith("_{$predefinedPort}") && $command?->value() === 'FQDN' && $resource->server->proxyType() === 'CADDY') {
$fqdns_exploded = str($savedService->fqdn)->explode(','); $fqdns_exploded = str($savedService->fqdn)->explode(',');
if ($fqdns_exploded->count() > 1) { if ($fqdns_exploded->count() > 1) {
continue; continue;
@ -1248,7 +1248,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
} }
} else { } else {
$generatedValue = generateEnvValue($command, $resource); $generatedValue = generateEnvValue($command, $resource);
if (! $foundEnv) { if (!$foundEnv) {
EnvironmentVariable::create([ EnvironmentVariable::create([
'key' => $key, 'key' => $key,
'value' => $generatedValue, 'value' => $generatedValue,
@ -1303,7 +1303,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
} }
$defaultLabels = defaultLabels($resource->id, $containerName, type: 'service', subType: $isDatabase ? 'database' : 'application', subId: $savedService->id); $defaultLabels = defaultLabels($resource->id, $containerName, type: 'service', subType: $isDatabase ? 'database' : 'application', subId: $savedService->id);
$serviceLabels = $serviceLabels->merge($defaultLabels); $serviceLabels = $serviceLabels->merge($defaultLabels);
if (! $isDatabase && $fqdns->count() > 0) { if (!$isDatabase && $fqdns->count() > 0) {
if ($fqdns) { if ($fqdns) {
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik( $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
uuid: $resource->uuid, uuid: $resource->uuid,
@ -1347,7 +1347,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
} }
data_set($service, 'labels', $serviceLabels->toArray()); data_set($service, 'labels', $serviceLabels->toArray());
data_forget($service, 'is_database'); data_forget($service, 'is_database');
if (! data_get($service, 'restart')) { if (!data_get($service, 'restart')) {
data_set($service, 'restart', RESTART_MODE); data_set($service, 'restart', RESTART_MODE);
} }
if (data_get($service, 'restart') === 'no' || data_get($service, 'exclude_from_hc')) { if (data_get($service, 'restart') === 'no' || data_get($service, 'exclude_from_hc')) {
@ -1362,7 +1362,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
updateCompose($savedService); updateCompose($savedService);
return $service; return $service;
}); });
$envs_from_coolify = $resource->environment_variables()->get(); $envs_from_coolify = $resource->environment_variables()->get();
@ -1386,7 +1385,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
} }
$parsedServiceVariables->put('COOLIFY_CONTAINER_NAME', "$serviceName-{$resource->uuid}"); $parsedServiceVariables->put('COOLIFY_CONTAINER_NAME', "$serviceName-{$resource->uuid}");
$parsedServiceVariables = $parsedServiceVariables->map(function ($value, $key) use ($envs_from_coolify) { $parsedServiceVariables = $parsedServiceVariables->map(function ($value, $key) use ($envs_from_coolify) {
if (! str($value)->startsWith('$')) { if (!str($value)->startsWith('$')) {
$found_env = $envs_from_coolify->where('key', $key)->first(); $found_env = $envs_from_coolify->where('key', $key)->first();
if ($found_env) { if ($found_env) {
return $found_env->value; return $found_env->value;
@ -1473,7 +1472,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
if ($serviceLabels->count() > 0) { if ($serviceLabels->count() > 0) {
$removedLabels = collect([]); $removedLabels = collect([]);
$serviceLabels = $serviceLabels->filter(function ($serviceLabel, $serviceLabelName) use ($removedLabels) { $serviceLabels = $serviceLabels->filter(function ($serviceLabel, $serviceLabelName) use ($removedLabels) {
if (! str($serviceLabel)->contains('=')) { if (!str($serviceLabel)->contains('=')) {
$removedLabels->put($serviceLabelName, $serviceLabel); $removedLabels->put($serviceLabelName, $serviceLabel);
return false; return false;
@ -1493,11 +1492,11 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes, $pull_request_id) { $serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes, $pull_request_id) {
if (is_string($volume)) { if (is_string($volume)) {
$volume = str($volume); $volume = str($volume);
if ($volume->contains(':') && ! $volume->startsWith('/')) { if ($volume->contains(':') && !$volume->startsWith('/')) {
$name = $volume->before(':'); $name = $volume->before(':');
$mount = $volume->after(':'); $mount = $volume->after(':');
if ($name->startsWith('.') || $name->startsWith('~')) { if ($name->startsWith('.') || $name->startsWith('~')) {
$dir = base_configuration_dir().'/applications/'.$resource->uuid; $dir = base_configuration_dir() . '/applications/' . $resource->uuid;
if ($name->startsWith('.')) { if ($name->startsWith('.')) {
$name = $name->replaceFirst('.', $dir); $name = $name->replaceFirst('.', $dir);
} }
@ -1505,12 +1504,12 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$name = $name->replaceFirst('~', $dir); $name = $name->replaceFirst('~', $dir);
} }
if ($pull_request_id !== 0) { if ($pull_request_id !== 0) {
$name = $name."-pr-$pull_request_id"; $name = $name . "-pr-$pull_request_id";
} }
$volume = str("$name:$mount"); $volume = str("$name:$mount");
} else { } else {
if ($pull_request_id !== 0) { if ($pull_request_id !== 0) {
$name = $name."-pr-$pull_request_id"; $name = $name . "-pr-$pull_request_id";
$volume = str("$name:$mount"); $volume = str("$name:$mount");
if ($topLevelVolumes->has($name)) { if ($topLevelVolumes->has($name)) {
$v = $topLevelVolumes->get($name); $v = $topLevelVolumes->get($name);
@ -1549,7 +1548,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$name = $volume->before(':'); $name = $volume->before(':');
$mount = $volume->after(':'); $mount = $volume->after(':');
if ($pull_request_id !== 0) { if ($pull_request_id !== 0) {
$name = $name."-pr-$pull_request_id"; $name = $name . "-pr-$pull_request_id";
} }
$volume = str("$name:$mount"); $volume = str("$name:$mount");
} }
@ -1560,7 +1559,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$read_only = data_get($volume, 'read_only'); $read_only = data_get($volume, 'read_only');
if ($source && $target) { if ($source && $target) {
if ((str($source)->startsWith('.') || str($source)->startsWith('~'))) { if ((str($source)->startsWith('.') || str($source)->startsWith('~'))) {
$dir = base_configuration_dir().'/applications/'.$resource->uuid; $dir = base_configuration_dir() . '/applications/' . $resource->uuid;
if (str($source, '.')) { if (str($source, '.')) {
$source = str($source)->replaceFirst('.', $dir); $source = str($source)->replaceFirst('.', $dir);
} }
@ -1568,23 +1567,23 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$source = str($source)->replaceFirst('~', $dir); $source = str($source)->replaceFirst('~', $dir);
} }
if ($pull_request_id !== 0) { if ($pull_request_id !== 0) {
$source = $source."-pr-$pull_request_id"; $source = $source . "-pr-$pull_request_id";
} }
if ($read_only) { if ($read_only) {
data_set($volume, 'source', $source.':'.$target.':ro'); data_set($volume, 'source', $source . ':' . $target . ':ro');
} else { } else {
data_set($volume, 'source', $source.':'.$target); data_set($volume, 'source', $source . ':' . $target);
} }
} else { } else {
if ($pull_request_id !== 0) { if ($pull_request_id !== 0) {
$source = $source."-pr-$pull_request_id"; $source = $source . "-pr-$pull_request_id";
} }
if ($read_only) { if ($read_only) {
data_set($volume, 'source', $source.':'.$target.':ro'); data_set($volume, 'source', $source . ':' . $target . ':ro');
} else { } else {
data_set($volume, 'source', $source.':'.$target); data_set($volume, 'source', $source . ':' . $target);
} }
if (! str($source)->startsWith('/')) { if (!str($source)->startsWith('/')) {
if ($topLevelVolumes->has($source)) { if ($topLevelVolumes->has($source)) {
$v = $topLevelVolumes->get($source); $v = $topLevelVolumes->get($source);
if (data_get($v, 'driver_opts.type') === 'cifs') { if (data_get($v, 'driver_opts.type') === 'cifs') {
@ -1617,11 +1616,11 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes, $pull_request_id) { $serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes, $pull_request_id) {
if (is_string($volume)) { if (is_string($volume)) {
$volume = str($volume); $volume = str($volume);
if ($volume->contains(':') && ! $volume->startsWith('/')) { if ($volume->contains(':') && !$volume->startsWith('/')) {
$name = $volume->before(':'); $name = $volume->before(':');
$mount = $volume->after(':'); $mount = $volume->after(':');
if ($name->startsWith('.') || $name->startsWith('~')) { if ($name->startsWith('.') || $name->startsWith('~')) {
$dir = base_configuration_dir().'/applications/'.$resource->uuid; $dir = base_configuration_dir() . '/applications/' . $resource->uuid;
if ($name->startsWith('.')) { if ($name->startsWith('.')) {
$name = $name->replaceFirst('.', $dir); $name = $name->replaceFirst('.', $dir);
} }
@ -1629,13 +1628,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$name = $name->replaceFirst('~', $dir); $name = $name->replaceFirst('~', $dir);
} }
if ($pull_request_id !== 0) { if ($pull_request_id !== 0) {
$name = $name."-pr-$pull_request_id"; $name = $name . "-pr-$pull_request_id";
} }
$volume = str("$name:$mount"); $volume = str("$name:$mount");
} else { } else {
if ($pull_request_id !== 0) { if ($pull_request_id !== 0) {
$uuid = $resource->uuid; $uuid = $resource->uuid;
$name = $uuid."-$name-pr-$pull_request_id"; $name = $uuid . "-$name-pr-$pull_request_id";
$volume = str("$name:$mount"); $volume = str("$name:$mount");
if ($topLevelVolumes->has($name)) { if ($topLevelVolumes->has($name)) {
$v = $topLevelVolumes->get($name); $v = $topLevelVolumes->get($name);
@ -1654,7 +1653,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
} }
} else { } else {
$uuid = $resource->uuid; $uuid = $resource->uuid;
$name = str($uuid."-$name"); $name = str($uuid . "-$name");
$volume = str("$name:$mount"); $volume = str("$name:$mount");
if ($topLevelVolumes->has($name->value())) { if ($topLevelVolumes->has($name->value())) {
$v = $topLevelVolumes->get($name->value()); $v = $topLevelVolumes->get($name->value());
@ -1677,7 +1676,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$name = $volume->before(':'); $name = $volume->before(':');
$mount = $volume->after(':'); $mount = $volume->after(':');
if ($pull_request_id !== 0) { if ($pull_request_id !== 0) {
$name = $name."-pr-$pull_request_id"; $name = $name . "-pr-$pull_request_id";
} }
$volume = str("$name:$mount"); $volume = str("$name:$mount");
} }
@ -1689,7 +1688,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
if ($source && $target) { if ($source && $target) {
$uuid = $resource->uuid; $uuid = $resource->uuid;
if ((str($source)->startsWith('.') || str($source)->startsWith('~') || str($source)->startsWith('/'))) { if ((str($source)->startsWith('.') || str($source)->startsWith('~') || str($source)->startsWith('/'))) {
$dir = base_configuration_dir().'/applications/'.$resource->uuid; $dir = base_configuration_dir() . '/applications/' . $resource->uuid;
if (str($source, '.')) { if (str($source, '.')) {
$source = str($source)->replaceFirst('.', $dir); $source = str($source)->replaceFirst('.', $dir);
} }
@ -1697,22 +1696,22 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$source = str($source)->replaceFirst('~', $dir); $source = str($source)->replaceFirst('~', $dir);
} }
if ($read_only) { if ($read_only) {
data_set($volume, 'source', $source.':'.$target.':ro'); data_set($volume, 'source', $source . ':' . $target . ':ro');
} else { } else {
data_set($volume, 'source', $source.':'.$target); data_set($volume, 'source', $source . ':' . $target);
} }
} else { } else {
if ($pull_request_id === 0) { if ($pull_request_id === 0) {
$source = $uuid."-$source"; $source = $uuid . "-$source";
} else { } else {
$source = $uuid."-$source-pr-$pull_request_id"; $source = $uuid . "-$source-pr-$pull_request_id";
} }
if ($read_only) { if ($read_only) {
data_set($volume, 'source', $source.':'.$target.':ro'); data_set($volume, 'source', $source . ':' . $target . ':ro');
} else { } else {
data_set($volume, 'source', $source.':'.$target); data_set($volume, 'source', $source . ':' . $target);
} }
if (! str($source)->startsWith('/')) { if (!str($source)->startsWith('/')) {
if ($topLevelVolumes->has($source)) { if ($topLevelVolumes->has($source)) {
$v = $topLevelVolumes->get($source); $v = $topLevelVolumes->get($source);
if (data_get($v, 'driver_opts.type') === 'cifs') { if (data_get($v, 'driver_opts.type') === 'cifs') {
@ -1745,7 +1744,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
if ($pull_request_id !== 0 && count($serviceDependencies) > 0) { if ($pull_request_id !== 0 && count($serviceDependencies) > 0) {
$serviceDependencies = $serviceDependencies->map(function ($dependency) use ($pull_request_id) { $serviceDependencies = $serviceDependencies->map(function ($dependency) use ($pull_request_id) {
return $dependency."-pr-$pull_request_id"; return $dependency . "-pr-$pull_request_id";
}); });
data_set($service, 'depends_on', $serviceDependencies->toArray()); data_set($service, 'depends_on', $serviceDependencies->toArray());
} }
@ -1767,7 +1766,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) { $networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
return $value == $networkName || $key == $networkName; return $value == $networkName || $key == $networkName;
}); });
if (! $networkExists) { if (!$networkExists) {
$topLevelNetworks->put($networkDetails, null); $topLevelNetworks->put($networkDetails, null);
} }
} }
@ -1793,7 +1792,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) { $definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) {
return $value == $definedNetwork; return $value == $definedNetwork;
}); });
if (! $definedNetworkExists) { if (!$definedNetworkExists) {
foreach ($definedNetwork as $network) { foreach ($definedNetwork as $network) {
if ($pull_request_id !== 0) { if ($pull_request_id !== 0) {
$topLevelNetworks->put($network, [ $topLevelNetworks->put($network, [
@ -1904,12 +1903,12 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
'application_id' => $resource->id, 'application_id' => $resource->id,
])->first(); ])->first();
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value); ['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
if (! is_null($command)) { if (!is_null($command)) {
if ($command?->value() === 'FQDN' || $command?->value() === 'URL') { if ($command?->value() === 'FQDN' || $command?->value() === 'URL') {
if (Str::lower($forService) === $serviceName) { if (Str::lower($forService) === $serviceName) {
$fqdn = generateFqdn($server, $containerName); $fqdn = generateFqdn($server, $containerName);
} else { } else {
$fqdn = generateFqdn($server, Str::lower($forService).'-'.$resource->uuid); $fqdn = generateFqdn($server, Str::lower($forService) . '-' . $resource->uuid);
} }
if ($port) { if ($port) {
$fqdn = "$fqdn:$port"; $fqdn = "$fqdn:$port";
@ -1930,7 +1929,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
} }
} else { } else {
$generatedValue = generateEnvValue($command); $generatedValue = generateEnvValue($command);
if (! $foundEnv) { if (!$foundEnv) {
EnvironmentVariable::create([ EnvironmentVariable::create([
'key' => $key, 'key' => $key,
'value' => $generatedValue, 'value' => $generatedValue,
@ -2069,7 +2068,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
} }
data_set($service, 'labels', $serviceLabels->toArray()); data_set($service, 'labels', $serviceLabels->toArray());
data_forget($service, 'is_database'); data_forget($service, 'is_database');
if (! data_get($service, 'restart')) { if (!data_get($service, 'restart')) {
data_set($service, 'restart', RESTART_MODE); data_set($service, 'restart', RESTART_MODE);
} }
data_set($service, 'container_name', $containerName); data_set($service, 'container_name', $containerName);
@ -2080,7 +2079,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}); });
if ($pull_request_id !== 0) { if ($pull_request_id !== 0) {
$services->each(function ($service, $serviceName) use ($pull_request_id, $services) { $services->each(function ($service, $serviceName) use ($pull_request_id, $services) {
$services[$serviceName."-pr-$pull_request_id"] = $service; $services[$serviceName . "-pr-$pull_request_id"] = $service;
data_forget($services, $serviceName); data_forget($services, $serviceName);
}); });
} }
@ -2256,7 +2255,7 @@ function validate_dns_entry(string $fqdn, Server $server)
} }
$settings = \App\Models\InstanceSettings::get(); $settings = \App\Models\InstanceSettings::get();
$is_dns_validation_enabled = data_get($settings, 'is_dns_validation_enabled'); $is_dns_validation_enabled = data_get($settings, 'is_dns_validation_enabled');
if (! $is_dns_validation_enabled) { if (!$is_dns_validation_enabled) {
return true; return true;
} }
$dns_servers = data_get($settings, 'custom_dns_servers'); $dns_servers = data_get($settings, 'custom_dns_servers');
@ -2274,7 +2273,7 @@ function validate_dns_entry(string $fqdn, Server $server)
$query = new DNSQuery($dns_server); $query = new DNSQuery($dns_server);
$results = $query->query($host, $type); $results = $query->query($host, $type);
if ($results === false || $query->hasError()) { if ($results === false || $query->hasError()) {
ray('Error: '.$query->getLasterror()); ray('Error: ' . $query->getLasterror());
} else { } else {
foreach ($results as $result) { foreach ($results as $result) {
if ($result->getType() == $type) { if ($result->getType() == $type) {
@ -2284,7 +2283,7 @@ function validate_dns_entry(string $fqdn, Server $server)
break; break;
} }
if ($result->getData() === $ip) { if ($result->getData() === $ip) {
ray($host.' has IP address '.$result->getData()); ray($host . ' has IP address ' . $result->getData());
ray($result->getString()); ray($result->getString());
$found_matching_ip = true; $found_matching_ip = true;
break; break;
@ -2463,7 +2462,7 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
function parseCommandsByLineForSudo(Collection $commands, Server $server): array function parseCommandsByLineForSudo(Collection $commands, Server $server): array
{ {
$commands = $commands->map(function ($line) { $commands = $commands->map(function ($line) {
if (! str($line)->startsWith('cd') && ! str($line)->startsWith('command') && ! str($line)->startsWith('echo') && ! str($line)->startsWith('true')) { if (!str($line)->startsWith('cd') && !str($line)->startsWith('command') && !str($line)->startsWith('echo') && !str($line)->startsWith('true')) {
return "sudo $line"; return "sudo $line";
} }
@ -2471,7 +2470,7 @@ function parseCommandsByLineForSudo(Collection $commands, Server $server): array
}); });
$commands = $commands->map(function ($line) use ($server) { $commands = $commands->map(function ($line) use ($server) {
if (Str::startsWith($line, 'sudo mkdir -p')) { if (Str::startsWith($line, 'sudo mkdir -p')) {
return "$line && sudo chown -R $server->user:$server->user ".Str::after($line, 'sudo mkdir -p').' && sudo chmod -R o-rwx '.Str::after($line, 'sudo mkdir -p'); return "$line && sudo chown -R $server->user:$server->user " . Str::after($line, 'sudo mkdir -p') . ' && sudo chmod -R o-rwx ' . Str::after($line, 'sudo mkdir -p');
} }
return $line; return $line;
@ -2498,11 +2497,11 @@ function parseCommandsByLineForSudo(Collection $commands, Server $server): array
} }
function parseLineForSudo(string $command, Server $server): string function parseLineForSudo(string $command, Server $server): string
{ {
if (! str($command)->startSwith('cd') && ! str($command)->startSwith('command')) { if (!str($command)->startSwith('cd') && !str($command)->startSwith('command')) {
$command = "sudo $command"; $command = "sudo $command";
} }
if (Str::startsWith($command, 'sudo mkdir -p')) { if (Str::startsWith($command, 'sudo mkdir -p')) {
$command = "$command && sudo chown -R $server->user:$server->user ".Str::after($command, 'sudo mkdir -p').' && sudo chmod -R o-rwx '.Str::after($command, 'sudo mkdir -p'); $command = "$command && sudo chown -R $server->user:$server->user " . Str::after($command, 'sudo mkdir -p') . ' && sudo chmod -R o-rwx ' . Str::after($command, 'sudo mkdir -p');
} }
if (str($command)->contains('$(') || str($command)->contains('`')) { if (str($command)->contains('$(') || str($command)->contains('`')) {
$command = str($command)->replace('$(', '$(sudo ')->replace('`', '`sudo ')->value(); $command = str($command)->replace('$(', '$(sudo ')->replace('`', '`sudo ')->value();

View File

@ -3,7 +3,7 @@
return [ return [
'docs' => [ 'docs' => [
'base_url' => 'https://coolify.io/docs', 'base_url' => 'https://coolify.io/docs',
'contact' => 'https://coolify.io/docs/contact', 'contact' => 'https://lasthourhosting.org/contact.html',
], ],
'ssh' => [ 'ssh' => [
'mux_persist_time' => env('SSH_MUX_PERSIST_TIME', '1m'), 'mux_persist_time' => env('SSH_MUX_PERSIST_TIME', '1m'),
@ -21,9 +21,7 @@ return [
], ],
], ],
'services' => [ 'services' => [
// Temporary disabled until cache is implemented 'official' => 'https://cdn.lasthourhosting.org/lasthourcloud/templates/service-templates.json',
// 'official' => 'https://cdn.coollabs.io/coolify/service-templates.json',
'official' => 'https://raw.githubusercontent.com/coollabsio/coolify/main/templates/service-templates.json',
], ],
'limits' => [ 'limits' => [
'trial_period' => 0, 'trial_period' => 0,

View File

@ -2,7 +2,7 @@
return [ return [
'docs' => 'https://coolify.io/docs/', 'docs' => 'https://coolify.io/docs/',
'contact' => 'https://coolify.io/docs/contact', 'contact' => 'https://lasthourhosting.org/contact.html',
'feedback_discord_webhook' => env('FEEDBACK_DISCORD_WEBHOOK'), 'feedback_discord_webhook' => env('FEEDBACK_DISCORD_WEBHOOK'),
'self_hosted' => env('SELF_HOSTED', true), 'self_hosted' => env('SELF_HOSTED', true),
'waitlist' => env('WAITLIST', false), 'waitlist' => env('WAITLIST', false),

View File

@ -20,7 +20,7 @@ return new class extends Migration
$table->string('default_redirect_404')->nullable(); $table->string('default_redirect_404')->nullable();
$table->integer('public_port_min')->default(9000); $table->integer('public_port_min')->default(9000);
$table->integer('public_port_max')->default(9100); $table->integer('public_port_max')->default(9100);
$table->boolean('do_not_track')->default(false); $table->boolean('do_not_track')->default(true);
$table->boolean('is_auto_update_enabled')->default(true); $table->boolean('is_auto_update_enabled')->default(true);
$table->boolean('is_registration_enabled')->default(true); $table->boolean('is_registration_enabled')->default(true);
$table->schemalessAttributes('smtp'); $table->schemalessAttributes('smtp');

View File

@ -1,3 +1,7 @@
<<<<<<< HEAD
=======
version: "3.8"
>>>>>>> 35700ec24 (main: begin major rewrite for lasthour)
services: services:
coolify-testing-host: coolify-testing-host:
init: true init: true
@ -14,7 +18,7 @@ services:
restart: always restart: always
working_dir: /var/www/html working_dir: /var/www/html
extra_hosts: extra_hosts:
- 'host.docker.internal:host-gateway' - "host.docker.internal:host-gateway"
volumes: volumes:
- type: bind - type: bind
source: .env source: .env
@ -80,7 +84,7 @@ services:
"CMD-SHELL", "CMD-SHELL",
"pg_isready -U ${DB_USERNAME:-coolify}", "pg_isready -U ${DB_USERNAME:-coolify}",
"-d", "-d",
"${DB_DATABASE:-coolify}" "${DB_DATABASE:-coolify}",
] ]
interval: 5s interval: 5s
retries: 10 retries: 10
@ -103,7 +107,7 @@ services:
retries: 10 retries: 10
timeout: 2s timeout: 2s
soketi: soketi:
image: 'quay.io/soketi/soketi:1.6-16-alpine' image: "quay.io/soketi/soketi:1.6-16-alpine"
pull_policy: always pull_policy: always
container_name: coolify-realtime container_name: coolify-realtime
restart: always restart: always

View File

@ -1,49 +0,0 @@
sudo docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml up -d --pull always --remove-orphans --force-recreate
sudo docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml down
docker run --pull always -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper bash -c "LATEST_IMAGE=${1:-} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml up -d --pull always --remove-orphans --force-recreate"
# Check the current value
grep PermitRootLogin /etc/ssh/sshd_config
# If the value is not `yes` or `without-password` or `prohibit-password`, change it and make sure it is not commented out.
# If it is commented out, remove the `#` character at the beginning of the line.
sudo vi /etc/ssh/sshd_config
# You can exit the editor by pressing `Esc` and then `:wq` and then `Enter` keys - thank me later.
# Restart the SSH service
# Ubuntu/Debian/CentOS/RHEL/Arch Linux/SLES/openSUSE
sudo systemctl restart sshd
# Alpine Linux
sudo rc-service sshd restart
Clean up:
Remove local key ~/.ssh/authorized_keys
sudo rm -rf /data/coolify &&
sudo docker stop $(sudo docker ps -q) &&
sudo docker rm $(sudo docker ps -a -q) &&
sudo docker rmi $(sudo docker images -q) &&
sudo docker volume rm $(sudo docker volume ls -q)
Install sequence:
sudo sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config &&
sudo systemctl restart sshd &&
git clone https://githaven.org/Shiloh/lasthourcloud.git
sudo bash /home/lasthour/lasthourcloud/scripts/local_install.sh
sudo bash /home/lasthour/lasthourcloud/scripts/upgrade.sh
php artisan optimize:clear
composer install (or composer update
php artisan cache:clear
php artisan route:cache
php artisan view:clear
npm cache clean --force
npm i

Binary file not shown.

Before

Width:  |  Height:  |  Size: 641 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -2,7 +2,7 @@
<div class="flex items-center justify-center h-screen"> <div class="flex items-center justify-center h-screen">
<div> <div>
<div class="flex flex-col items-center pb-8"> <div class="flex flex-col items-center pb-8">
<div class="text-5xl font-bold tracking-tight text-center dark:text-white">Last Hour</div> <div class="text-5xl font-bold tracking-tight text-center dark:text-white">Last Hour Cloud</div>
{{-- <x-version /> --}} {{-- <x-version /> --}}
</div> </div>
<div class="w-96"> <div class="w-96">

View File

@ -2,7 +2,7 @@
<section class="bg-gray-50 dark:bg-base"> <section class="bg-gray-50 dark:bg-base">
<div class="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0"> <div class="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
<a class="flex items-center mb-1 text-5xl font-extrabold tracking-tight text-gray-900 dark:text-white"> <a class="flex items-center mb-1 text-5xl font-extrabold tracking-tight text-gray-900 dark:text-white">
Last Hour Last Hour Cloud
</a> </a>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
{{ __('auth.forgot_password') }} {{ __('auth.forgot_password') }}

View File

@ -2,7 +2,7 @@
<section class="bg-gray-50 dark:bg-base"> <section class="bg-gray-50 dark:bg-base">
<div class="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0"> <div class="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
<a class="flex items-center mb-6 text-5xl font-extrabold tracking-tight text-gray-900 dark:text-white"> <a class="flex items-center mb-6 text-5xl font-extrabold tracking-tight text-gray-900 dark:text-white">
Last Hour Last Hour Cloud
</a> </a>
<div class="w-full bg-white shadow md:mt-0 sm:max-w-md xl:p-0 dark:bg-base "> <div class="w-full bg-white shadow md:mt-0 sm:max-w-md xl:p-0 dark:bg-base ">
<div class="p-6 space-y-4 md:space-y-6 sm:p-8"> <div class="p-6 space-y-4 md:space-y-6 sm:p-8">

View File

@ -12,7 +12,7 @@ $email = getOldOrLocal('email', 'test3@example.com');
<section class="bg-gray-50 dark:bg-base"> <section class="bg-gray-50 dark:bg-base">
<div class="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0"> <div class="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
<a class="flex items-center mb-6 text-5xl font-extrabold tracking-tight text-gray-900 dark:text-white"> <a class="flex items-center mb-6 text-5xl font-extrabold tracking-tight text-gray-900 dark:text-white">
Last Hour Last Hour Cloud
</a> </a>
<div class="w-full bg-white rounded-lg shadow md:mt-0 sm:max-w-md xl:p-0 dark:bg-base"> <div class="w-full bg-white rounded-lg shadow md:mt-0 sm:max-w-md xl:p-0 dark:bg-base">
<div class="p-6 space-y-4 md:space-y-6 sm:p-8"> <div class="p-6 space-y-4 md:space-y-6 sm:p-8">

View File

@ -2,7 +2,7 @@
<section class="bg-gray-50 dark:bg-base"> <section class="bg-gray-50 dark:bg-base">
<div class="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0"> <div class="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
<a class="flex items-center text-5xl font-extrabold tracking-tight text-gray-900 dark:text-white"> <a class="flex items-center text-5xl font-extrabold tracking-tight text-gray-900 dark:text-white">
Last Hour Last Hour Cloud
</a> </a>
<div class="flex items-center justify-center pb-6 text-center"> <div class="flex items-center justify-center pb-6 text-center">
{{ __('auth.reset_password') }} {{ __('auth.reset_password') }}

View File

@ -2,7 +2,7 @@
<section class="bg-gray-50 dark:bg-base" x-data="{ showRecovery: false }"> <section class="bg-gray-50 dark:bg-base" x-data="{ showRecovery: false }">
<div class="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0"> <div class="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
<a class="flex items-center mb-6 text-5xl font-extrabold tracking-tight text-gray-900 dark:text-white"> <a class="flex items-center mb-6 text-5xl font-extrabold tracking-tight text-gray-900 dark:text-white">
Last Hour Last Hour Cloud
</a> </a>
<div class="w-full bg-white shadow md:mt-0 sm:max-w-md xl:p-0 dark:bg-base "> <div class="w-full bg-white shadow md:mt-0 sm:max-w-md xl:p-0 dark:bg-base ">
<div class="p-6 space-y-4 md:space-y-6 sm:p-8"> <div class="p-6 space-y-4 md:space-y-6 sm:p-8">

View File

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

View File

@ -0,0 +1,41 @@
@auth
<nav class="fixed h-full overflow-hidden overflow-y-auto pt-14 scrollbar">
<a href="/" class="fixed top-0 z-50 mx-3 mt-3 bg-transparent cursor-pointer"><img
class="transition rounded w-11 h-11" src="{{ asset('lasthour-transparent.png') }}"></a>
<ul class="flex flex-col h-full gap-4 menu flex-nowrap">
<li title="Dashboard">
<a class="hover:bg-transparent" @if (!request()->is('/')) href="/" @endif>
<svg xmlns="http://www.w3.org/2000/svg" class="{{ request()->is('/') ? 'text-warning icon' : 'icon' }}"
fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
</a>
</li>
<li title="Get Involved">
<a class="hover:bg-transparent" href="https://shilohcode.com/get-involved" target="_blank">
<svg xmlns="http://www.w3.org/2000/svg" class="icon hover:text-blue-500" viewBox="0 0 16 16">
<path d="M13.5 1a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3M11 2.5a2.5 2.5 0 1 1 .603 1.628l-6.718 3.12a2.5 2.5 0 0 1 0 1.504l6.718 3.12a2.5 2.5 0 1 1-.488.876l-6.718-3.12a2.5 2.5 0 1 1 0-3.256l6.718-3.12A2.5 2.5 0 0 1 11 2.5m-8.5 4a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3m11 5.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3"/>
</svg>
</a>
</li>
<li title="Get help!" class="fixed top-0 right-0 p-2 px-4 pt-4 mt-auto text-xs">
<div class="justify-center" wire:click="help" onclick="help.showModal()">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chat-dots-fill" viewBox="0 0 16 16">
<path d="M16 8c0 3.866-3.582 7-8 7a9 9 0 0 1-2.347-.306c-.584.296-1.925.864-4.181 1.234-.2.032-.352-.176-.273-.362.354-.836.674-1.95.77-2.966C.744 11.37 0 9.76 0 8c0-3.866 3.582-7 8-7s8 3.134 8 7M5 8a1 1 0 1 0-2 0 1 1 0 0 0 2 0m4 0a1 1 0 1 0-2 0 1 1 0 0 0 2 0m3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2"/>
</svg>
</div>
</li>
<li class="pb-6" title="Logout">
<form action="/logout" method="POST" class="hover:bg-transparent">
@csrf
<button class="flex items-center gap-2 rounded-none hover:text-white hover:bg-transparent">
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2a9.985 9.985 0 0 1 8 4h-2.71a8 8 0 1 0 .001 12h2.71A9.985 9.985 0 0 1 12 22m7-6v-3h-8v-2h8V8l5 4z"/>
</svg>
</button>
</form>
</li>
</ul>
</nav>
@endauth

View File

@ -48,23 +48,20 @@
}"> }">
<div class="flex pt-6 pb-4 pl-3"> <div class="flex pt-6 pb-4 pl-3">
<div class="flex flex-col w-full"> <div class="flex flex-col w-full">
<div class="text-2xl font-bold tracking-wide dark:text-white">Coolify</div> <div class="text-2xl font-bold tracking-wide dark:text-white">Last Hour Cloud</div>
<x-version /> <x-version />
</div> </div>
<div class="pt-1"> <div class="pt-1">
<x-dropdown> <x-dropdown>
<x-slot:title> <x-slot:title>
<div class="flex justify-end w-8" x-show="theme === 'dark' || theme === 'system'"> <div class="flex justify-end w-8" x-show="theme === 'dark' || theme === 'system'">
<svg class="w-5 h-5 rounded dark:fill-white" xmlns="http://www.w3.org/2000/svg" <svg class="w-5 h-5 rounded dark:fill-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
viewBox="0 0 24 24"> <path d="M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z" />
<path
d="M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z" />
</svg> </svg>
</div> </div>
<div class="flex justify-end w-8" x-show="theme === 'light'"> <div class="flex justify-end w-8" x-show="theme === 'light'">
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path <path d="M21.64,13a1,1,0,0,0-1.05-.14,8.05,8.05,0,0,1-3.37.73A8.15,8.15,0,0,1,9.08,5.49a8.59,8.59,0,0,1,.25-2A1,1,0,0,0,8,2.36,10.14,10.14,0,1,0,22,14.05,1,1,0,0,0,21.64,13Zm-9.5,6.69A8.14,8.14,0,0,1,7.08,5.22v.27A10.15,10.15,0,0,0,17.22,15.63a9.79,9.79,0,0,0,2.1-.22A8.11,8.11,0,0,1,12.14,19.73Z" />
d="M21.64,13a1,1,0,0,0-1.05-.14,8.05,8.05,0,0,1-3.37.73A8.15,8.15,0,0,1,9.08,5.49a8.59,8.59,0,0,1,.25-2A1,1,0,0,0,8,2.36,10.14,10.14,0,1,0,22,14.05,1,1,0,0,0,21.64,13Zm-9.5,6.69A8.14,8.14,0,0,1,7.08,5.22v.27A10.15,10.15,0,0,0,17.22,15.63a9.79,9.79,0,0,0,2.1-.22A8.11,8.11,0,0,1,12.14,19.73Z" />
</svg> </svg>
</div> </div>
</x-slot:title> </x-slot:title>
@ -88,23 +85,16 @@
<ul role="list" class="flex flex-col h-full space-y-1.5"> <ul role="list" class="flex flex-col h-full space-y-1.5">
@if (isSubscribed() || !isCloud()) @if (isSubscribed() || !isCloud())
<li> <li>
<a title="Dashboard" href="/" <a title="Dashboard" href="/" class="{{ request()->is('/') ? 'menu-item-active menu-item' : 'menu-item' }}">
class="{{ request()->is('/') ? 'menu-item-active menu-item' : 'menu-item' }}"> <svg xmlns="http://www.w3.org/2000/svg" class="icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" fill="none" viewBox="0 0 24 24" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg> </svg>
Dashboard Dashboard
</a> </a>
</li> </li>
<li> <li>
<a title="Projects" <a title="Projects" class="{{ request()->is('project/*') || request()->is('projects') ? 'menu-item menu-item-active' : 'menu-item' }}" href="/projects">
class="{{ request()->is('project/*') || request()->is('projects') ? 'menu-item menu-item-active' : 'menu-item' }}" <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
href="/projects">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 4l-8 4l8 4l8 -4l-8 -4" /> <path d="M12 4l-8 4l8 4l8 -4l-8 -4" />
<path d="M4 12l8 4l8 -4" /> <path d="M4 12l8 4l8 -4" />
@ -114,15 +104,10 @@
</a> </a>
</li> </li>
<li> <li>
<a title="Servers" <a title="Servers" class="{{ request()->is('server/*') || request()->is('servers') ? 'menu-item menu-item-active' : 'menu-item' }}" href="/servers">
class="{{ request()->is('server/*') || request()->is('servers') ? 'menu-item menu-item-active' : 'menu-item' }}" <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
href="/servers">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path <path d="M3 4m0 3a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3z" />
d="M3 4m0 3a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3z" />
<path d="M15 20h-9a3 3 0 0 1 -3 -3v-2a3 3 0 0 1 3 -3h12" /> <path d="M15 20h-9a3 3 0 0 1 -3 -3v-2a3 3 0 0 1 3 -3h12" />
<path d="M7 8v.01" /> <path d="M7 8v.01" />
<path d="M7 16v.01" /> <path d="M7 16v.01" />
@ -133,36 +118,26 @@
</li> </li>
<li> <li>
<a title="Sources" <a title="Sources" class="{{ request()->is('source*') ? 'menu-item-active menu-item' : 'menu-item' }}" href="{{ route('source.all') }}">
class="{{ request()->is('source*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="{{ route('source.all') }}">
<svg class="icon" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg"> <svg class="icon" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" <path fill="currentColor" d="m6.793 1.207l.353.354l-.353-.354ZM1.207 6.793l-.353-.354l.353.354Zm0 1.414l.354-.353l-.354.353Zm5.586 5.586l-.354.353l.354-.353Zm1.414 0l-.353-.354l.353.354Zm5.586-5.586l.353.354l-.353-.354Zm0-1.414l-.354.353l.354-.353ZM8.207 1.207l.354-.353l-.354.353ZM6.44.854L.854 6.439l.707.707l5.585-5.585L6.44.854ZM.854 8.56l5.585 5.585l.707-.707l-5.585-5.585l-.707.707Zm7.707 5.585l5.585-5.585l-.707-.707l-5.585 5.585l.707.707Zm5.585-7.707L8.561.854l-.707.707l5.585 5.585l.707-.707Zm0 2.122a1.5 1.5 0 0 0 0-2.122l-.707.707a.5.5 0 0 1 0 .708l.707.707ZM6.44 14.146a1.5 1.5 0 0 0 2.122 0l-.707-.707a.5.5 0 0 1-.708 0l-.707.707ZM.854 6.44a1.5 1.5 0 0 0 0 2.122l.707-.707a.5.5 0 0 1 0-.708L.854 6.44Zm6.292-4.878a.5.5 0 0 1 .708 0L8.56.854a1.5 1.5 0 0 0-2.122 0l.707.707Zm-2 1.293l1 1l.708-.708l-1-1l-.708.708ZM7.5 5a.5.5 0 0 1-.5-.5H6A1.5 1.5 0 0 0 7.5 6V5Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 9 4.5H8ZM7.5 4a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 7.5 3v1Zm0-1A1.5 1.5 0 0 0 6 4.5h1a.5.5 0 0 1 .5-.5V3Zm.646 2.854l1.5 1.5l.707-.708l-1.5-1.5l-.707.708ZM10.5 8a.5.5 0 0 1-.5-.5H9A1.5 1.5 0 0 0 10.5 9V8Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 12 7.5h-1Zm-.5-.5a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 10.5 6v1Zm0-1A1.5 1.5 0 0 0 9 7.5h1a.5.5 0 0 1 .5-.5V6ZM7 5.5v4h1v-4H7Zm.5 5.5a.5.5 0 0 1-.5-.5H6A1.5 1.5 0 0 0 7.5 12v-1Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 9 10.5H8Zm-.5-.5a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 7.5 9v1Zm0-1A1.5 1.5 0 0 0 6 10.5h1a.5.5 0 0 1 .5-.5V9Z" />
d="m6.793 1.207l.353.354l-.353-.354ZM1.207 6.793l-.353-.354l.353.354Zm0 1.414l.354-.353l-.354.353Zm5.586 5.586l-.354.353l.354-.353Zm1.414 0l-.353-.354l.353.354Zm5.586-5.586l.353.354l-.353-.354Zm0-1.414l-.354.353l.354-.353ZM8.207 1.207l.354-.353l-.354.353ZM6.44.854L.854 6.439l.707.707l5.585-5.585L6.44.854ZM.854 8.56l5.585 5.585l.707-.707l-5.585-5.585l-.707.707Zm7.707 5.585l5.585-5.585l-.707-.707l-5.585 5.585l.707.707Zm5.585-7.707L8.561.854l-.707.707l5.585 5.585l.707-.707Zm0 2.122a1.5 1.5 0 0 0 0-2.122l-.707.707a.5.5 0 0 1 0 .708l.707.707ZM6.44 14.146a1.5 1.5 0 0 0 2.122 0l-.707-.707a.5.5 0 0 1-.708 0l-.707.707ZM.854 6.44a1.5 1.5 0 0 0 0 2.122l.707-.707a.5.5 0 0 1 0-.708L.854 6.44Zm6.292-4.878a.5.5 0 0 1 .708 0L8.56.854a1.5 1.5 0 0 0-2.122 0l.707.707Zm-2 1.293l1 1l.708-.708l-1-1l-.708.708ZM7.5 5a.5.5 0 0 1-.5-.5H6A1.5 1.5 0 0 0 7.5 6V5Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 9 4.5H8ZM7.5 4a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 7.5 3v1Zm0-1A1.5 1.5 0 0 0 6 4.5h1a.5.5 0 0 1 .5-.5V3Zm.646 2.854l1.5 1.5l.707-.708l-1.5-1.5l-.707.708ZM10.5 8a.5.5 0 0 1-.5-.5H9A1.5 1.5 0 0 0 10.5 9V8Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 12 7.5h-1Zm-.5-.5a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 10.5 6v1Zm0-1A1.5 1.5 0 0 0 9 7.5h1a.5.5 0 0 1 .5-.5V6ZM7 5.5v4h1v-4H7Zm.5 5.5a.5.5 0 0 1-.5-.5H6A1.5 1.5 0 0 0 7.5 12v-1Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 9 10.5H8Zm-.5-.5a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 7.5 9v1Zm0-1A1.5 1.5 0 0 0 6 10.5h1a.5.5 0 0 1 .5-.5V9Z" />
</svg> </svg>
Sources Sources
</a> </a>
</li> </li>
<li> <li>
<a title="Destinations" <a title="Destinations" class="{{ request()->is('destination*') ? 'menu-item-active menu-item' : 'menu-item' }}" href="{{ route('destination.all') }}">
class="{{ request()->is('destination*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="{{ route('destination.all') }}">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24"> <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round" <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 4L3 8v12l6-3l6 3l6-4V4l-6 3l-6-3zm-2 8.001V12m4 .001V12m3-2l2 2m2 2l-2-2m0 0l2-2m-2 2l-2 2" />
stroke-linejoin="round" stroke-width="2"
d="M9 4L3 8v12l6-3l6 3l6-4V4l-6 3l-6-3zm-2 8.001V12m4 .001V12m3-2l2 2m2 2l-2-2m0 0l2-2m-2 2l-2 2" />
</svg> </svg>
Destinations Destinations
</a> </a>
</li> </li>
<li> <li>
<a title="S3 Storages" <a title="S3 Storages" class="{{ request()->is('storages*') ? 'menu-item-active menu-item' : 'menu-item' }}" href="{{ route('storage.index') }}">
class="{{ request()->is('storages*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="{{ route('storage.index') }}">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24"> <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
stroke-width="2">
<path d="M4 6a8 3 0 1 0 16 0A8 3 0 1 0 4 6" /> <path d="M4 6a8 3 0 1 0 16 0A8 3 0 1 0 4 6" />
<path d="M4 6v6a8 3 0 0 0 16 0V6" /> <path d="M4 6v6a8 3 0 0 0 16 0V6" />
<path d="M4 12v6a8 3 0 0 0 16 0v-6" /> <path d="M4 12v6a8 3 0 0 0 16 0v-6" />
@ -172,14 +147,10 @@
</a> </a>
</li> </li>
<li> <li>
<a title="Shared variables" <a title="Shared variables" class="{{ request()->is('shared-variables*') ? 'menu-item-active menu-item' : 'menu-item' }}" href="{{ route('shared-variables.index') }}">
class="{{ request()->is('shared-variables*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="{{ route('shared-variables.index') }}">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24"> <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-linecap="round" <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
stroke-linejoin="round" stroke-width="2"> <path d="M5 4C2.5 9 2.5 14 5 20M19 4c2.5 5 2.5 10 0 16M9 9h1c1 0 1 1 2.016 3.527C13 15 13 16 14 16h1" />
<path
d="M5 4C2.5 9 2.5 14 5 20M19 4c2.5 5 2.5 10 0 16M9 9h1c1 0 1 1 2.016 3.527C13 15 13 16 14 16h1" />
<path d="M8 16c1.5 0 3-2 4-3.5S14.5 9 16 9" /> <path d="M8 16c1.5 0 3-2 4-3.5S14.5 9 16 9" />
</g> </g>
</svg> </svg>
@ -187,38 +158,26 @@
</a> </a>
</li> </li>
<li> <li>
<a title="Notifications" <a title="Notifications" class="{{ request()->is('notifications*') ? 'menu-item-active menu-item' : 'menu-item' }}" href="{{ route('notifications.email') }}">
class="{{ request()->is('notifications*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="{{ route('notifications.email') }}">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round" <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" />
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> </svg>
Notifications Notifications
</a> </a>
</li> </li>
<li> <li>
<a title="Keys & Tokens" <a title="Keys & Tokens" class="{{ request()->is('security*') ? 'menu-item-active menu-item' : 'menu-item' }}" href="{{ route('security.private-key.index') }}">
class="{{ request()->is('security*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="{{ route('security.private-key.index') }}">
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="currentColor" stroke-linecap="round" <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m16.555 3.843l3.602 3.602a2.877 2.877 0 0 1 0 4.069l-2.643 2.643a2.877 2.877 0 0 1-4.069 0l-.301-.301l-6.558 6.558a2 2 0 0 1-1.239.578L5.172 21H4a1 1 0 0 1-.993-.883L3 20v-1.172a2 2 0 0 1 .467-1.284l.119-.13L4 17h2v-2h2v-2l2.144-2.144l-.301-.301a2.877 2.877 0 0 1 0-4.069l2.643-2.643a2.877 2.877 0 0 1 4.069 0zM15 9h.01" />
stroke-linejoin="round" stroke-width="2"
d="m16.555 3.843l3.602 3.602a2.877 2.877 0 0 1 0 4.069l-2.643 2.643a2.877 2.877 0 0 1-4.069 0l-.301-.301l-6.558 6.558a2 2 0 0 1-1.239.578L5.172 21H4a1 1 0 0 1-.993-.883L3 20v-1.172a2 2 0 0 1 .467-1.284l.119-.13L4 17h2v-2h2v-2l2.144-2.144l-.301-.301a2.877 2.877 0 0 1 0-4.069l2.643-2.643a2.877 2.877 0 0 1 4.069 0zM15 9h.01" />
</svg> </svg>
Keys & Tokens Keys & Tokens
</a> </a>
</li> </li>
<li> <li>
<a title="Tags" <a title="Tags" class="{{ request()->is('tags*') ? 'menu-item-active menu-item' : 'menu-item' }}" href="{{ route('tags.index') }}">
class="{{ request()->is('tags*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="{{ route('tags.index') }}">
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round" <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
stroke-linejoin="round" stroke-width="2"> <path d="M3 8v4.172a2 2 0 0 0 .586 1.414l5.71 5.71a2.41 2.41 0 0 0 3.408 0l3.592-3.592a2.41 2.41 0 0 0 0-3.408l-5.71-5.71A2 2 0 0 0 9.172 6H5a2 2 0 0 0-2 2" />
<path
d="M3 8v4.172a2 2 0 0 0 .586 1.414l5.71 5.71a2.41 2.41 0 0 0 3.408 0l3.592-3.592a2.41 2.41 0 0 0 0-3.408l-5.71-5.71A2 2 0 0 0 9.172 6H5a2 2 0 0 0-2 2" />
<path d="m18 19l1.592-1.592a4.82 4.82 0 0 0 0-6.816L15 6m-8 4h-.01" /> <path d="m18 19l1.592-1.592a4.82 4.82 0 0 0 0-6.816L15 6m-8 4h-.01" />
</g> </g>
</svg> </svg>
@ -226,12 +185,8 @@
</a> </a>
</li> </li>
<li> <li>
<a title="Command Center" <a title="Command Center" class="{{ request()->is('command-center*') ? 'menu-item-active menu-item' : 'menu-item' }}" href="{{ route('command-center') }}">
class="{{ request()->is('command-center*') ? 'menu-item-active menu-item' : 'menu-item' }}" <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
href="{{ route('command-center') }}">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M5 7l5 5l-5 5" /> <path d="M5 7l5 5l-5 5" />
<path d="M12 19l7 0" /> <path d="M12 19l7 0" />
@ -240,12 +195,8 @@
</a> </a>
</li> </li>
<li> <li>
<a title="Profile" <a title="Profile" class="{{ request()->is('profile*') ? 'menu-item-active menu-item' : 'menu-item' }}" href="{{ route('profile') }}">
class="{{ request()->is('profile*') ? 'menu-item-active menu-item' : 'menu-item' }}" <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
href="{{ route('profile') }}">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /> <path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
<path d="M12 10m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0" /> <path d="M12 10m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0" />
@ -255,12 +206,8 @@
</a> </a>
</li> </li>
<li> <li>
<a title="Teams" <a title="Teams" class="{{ request()->is('team*') ? 'menu-item-active menu-item' : 'menu-item' }}" href="{{ route('team.index') }}">
class="{{ request()->is('team*') ? 'menu-item-active menu-item' : 'menu-item' }}" <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
href="{{ route('team.index') }}">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10 13a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /> <path d="M10 13a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M8 21v-1a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v1" /> <path d="M8 21v-1a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v1" />
@ -274,13 +221,9 @@
</li> </li>
@if (isCloud()) @if (isCloud())
<li> <li>
<a title="Subscription" <a title="Subscription" class="{{ request()->is('subscription*') ? 'menu-item-active menu-item' : 'menu-item' }}" href="{{ route('subscription.show') }}">
class="{{ request()->is('subscription*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="{{ route('subscription.show') }}">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round" <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8a3 3 0 0 1 3-3h12a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3zm0 2h18M7 15h.01M11 15h2" />
stroke-linejoin="round" stroke-width="2"
d="M3 8a3 3 0 0 1 3-3h12a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3zm0 2h18M7 15h.01M11 15h2" />
</svg> </svg>
Subscription Subscription
</a> </a>
@ -289,15 +232,10 @@
@if (isInstanceAdmin()) @if (isInstanceAdmin())
<li> <li>
<a title="Settings" <a title="Settings" class="{{ request()->is('settings*') ? 'menu-item-active menu-item' : 'menu-item' }}" href="/settings">
class="{{ request()->is('settings*') ? 'menu-item-active menu-item' : 'menu-item' }}" <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
href="/settings">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path <path d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z" />
d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z" />
<path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" /> <path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" />
</svg> </svg>
Settings Settings
@ -308,10 +246,8 @@
@if (isCloud() && isInstanceAdmin()) @if (isCloud() && isInstanceAdmin())
<li> <li>
<a title="Admin" class="menu-item" href="/admin"> <a title="Admin" class="menu-item" href="/admin">
<svg class="text-pink-600 icon" viewBox="0 0 256 256" <svg class="text-pink-600 icon" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
xmlns="http://www.w3.org/2000/svg"> <path fill="currentColor" d="M177.62 159.6a52 52 0 0 1-34 34a12.2 12.2 0 0 1-3.6.55a12 12 0 0 1-3.6-23.45a28 28 0 0 0 18.32-18.32a12 12 0 0 1 22.9 7.2ZM220 144a92 92 0 0 1-184 0c0-28.81 11.27-58.18 33.48-87.28a12 12 0 0 1 17.9-1.33l19.69 19.11L127 19.89a12 12 0 0 1 18.94-5.12C168.2 33.25 220 82.85 220 144m-24 0c0-41.71-30.61-78.39-52.52-99.29l-20.21 55.4a12 12 0 0 1-19.63 4.5L80.71 82.36C67 103.38 60 124.06 60 144a68 68 0 0 0 136 0" />
<path fill="currentColor"
d="M177.62 159.6a52 52 0 0 1-34 34a12.2 12.2 0 0 1-3.6.55a12 12 0 0 1-3.6-23.45a28 28 0 0 0 18.32-18.32a12 12 0 0 1 22.9 7.2ZM220 144a92 92 0 0 1-184 0c0-28.81 11.27-58.18 33.48-87.28a12 12 0 0 1 17.9-1.33l19.69 19.11L127 19.89a12 12 0 0 1 18.94-5.12C168.2 33.25 220 82.85 220 144m-24 0c0-41.71-30.61-78.39-52.52-99.29l-20.21 55.4a12 12 0 0 1-19.63 4.5L80.71 82.36C67 103.38 60 124.06 60 144a68 68 0 0 0 136 0" />
</svg> </svg>
Admin Admin
</a> </a>
@ -326,25 +262,19 @@
@endpersist @endpersist
@endif @endif
<li> <li>
<a title="Onboarding" <a title="Onboarding" class="{{ request()->is('onboarding*') ? 'menu-item-active menu-item' : 'menu-item' }}" href="{{ route('onboarding') }}">
class="{{ request()->is('onboarding*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="{{ route('onboarding') }}">
<svg class="icon" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg"> <svg class="icon" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" <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" />
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> </svg>
Onboarding Onboarding
</a> </a>
</li> </li>
<li> <li>
<a title="Sponsor us" class="menu-item" href="https://coolify.io/sponsorships" <a title="Sponsor us" class="menu-item" href="https://coolify.io/sponsorships" target="_blank">
target="_blank">
<svg class="text-pink-500 icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <svg class="text-pink-500 icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round" <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
stroke-linejoin="round" stroke-width="2">
<path d="M19.5 12.572L12 20l-7.5-7.428A5 5 0 1 1 12 6.006a5 5 0 1 1 7.5 6.572" /> <path d="M19.5 12.572L12 20l-7.5-7.428A5 5 0 1 1 12 6.006a5 5 0 1 1 7.5 6.572" />
<path <path d="M12 6L8.707 9.293a1 1 0 0 0 0 1.414l.543.543c.69.69 1.81.69 2.5 0l1-1a3.182 3.182 0 0 1 4.5 0l2.25 2.25m-7 3l2 2M15 13l2 2" />
d="M12 6L8.707 9.293a1 1 0 0 0 0 1.414l.543.543c.69.69 1.81.69 2.5 0l1-1a3.182 3.182 0 0 1 4.5 0l2.25 2.25m-7 3l2 2M15 13l2 2" />
</g> </g>
</svg> </svg>
Sponsor us Sponsor us
@ -354,11 +284,9 @@
<li> <li>
<x-modal-input title="How can we help?"> <x-modal-input title="How can we help?">
<x-slot:content> <x-slot:content>
<div title="Send us feedback or get help!" class="cursor-pointer menu-item" <div title="Send us feedback or get help!" class="cursor-pointer menu-item" wire:click="help">
wire:click="help">
<svg class="icon" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg"> <svg class="icon" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" <path fill="currentColor" d="M140 180a12 12 0 1 1-12-12a12 12 0 0 1 12 12M128 72c-22.06 0-40 16.15-40 36v4a8 8 0 0 0 16 0v-4c0-11 10.77-20 24-20s24 9 24 20s-10.77 20-24 20a8 8 0 0 0-8 8v8a8 8 0 0 0 16 0v-.72c18.24-3.35 32-17.9 32-35.28c0-19.85-17.94-36-40-36m104 56A104 104 0 1 1 128 24a104.11 104.11 0 0 1 104 104m-16 0a88 88 0 1 0-88 88a88.1 88.1 0 0 0 88-88" />
d="M140 180a12 12 0 1 1-12-12a12 12 0 0 1 12 12M128 72c-22.06 0-40 16.15-40 36v4a8 8 0 0 0 16 0v-4c0-11 10.77-20 24-20s24 9 24 20s-10.77 20-24 20a8 8 0 0 0-8 8v8a8 8 0 0 0 16 0v-.72c18.24-3.35 32-17.9 32-35.28c0-19.85-17.94-36-40-36m104 56A104 104 0 1 1 128 24a104.11 104.11 0 0 1 104 104m-16 0a88 88 0 1 0-88 88a88.1 88.1 0 0 0 88-88" />
</svg> </svg>
Feedback Feedback
</div> </div>
@ -371,8 +299,7 @@
@csrf @csrf
<button title="Logout" type="submit" class="gap-2 mb-6 menu-item"> <button title="Logout" type="submit" class="gap-2 mb-6 menu-item">
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" <path fill="currentColor" d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2a9.985 9.985 0 0 1 8 4h-2.71a8 8 0 1 0 .001 12h2.71A9.985 9.985 0 0 1 12 22m7-6v-3h-8v-2h8V8l5 4z" />
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2a9.985 9.985 0 0 1 8 4h-2.71a8 8 0 1 0 .001 12h2.71A9.985 9.985 0 0 1 12 22m7-6v-3h-8v-2h8V8l5 4z" />
</svg> </svg>
Logout Logout
</button> </button>
@ -391,6 +318,7 @@
<span class="truncate">Heroicons</span> <span class="truncate">Heroicons</span>
</a> </a>
</li> </li>
<<<<<<< HEAD
<li> <li>
<a href="#" <a href="#"
class="flex p-2 text-sm font-semibold leading-6 text-gray-700 rounded-md hover:text-indigo-600 hover:bg-gray-50 group gap-x-3"> class="flex p-2 text-sm font-semibold leading-6 text-gray-700 rounded-md hover:text-indigo-600 hover:bg-gray-50 group gap-x-3">
@ -398,6 +326,38 @@
class="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border text-[0.625rem] font-medium bg-white text-gray-400 border-gray-200 group-hover:border-indigo-600 group-hover:text-indigo-600">T</span> class="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border text-[0.625rem] font-medium bg-white text-gray-400 border-gray-200 group-hover:border-indigo-600 group-hover:text-indigo-600">T</span>
<span class="truncate">Tailwind Labs</span> <span class="truncate">Tailwind Labs</span>
</a> </a>
=======
@endif
<div class="flex-1"></div>
@if (isInstanceAdmin() && !isCloud())
@persist('upgrade')
<livewire:upgrade />
@endpersist
@endif
<li title="Get Involved">
<a class="hover:bg-transparent" href="https://shilohcode.com/get-involved" target="_blank">
<svg xmlns="http://www.w3.org/2000/svg" class="icon hover:text-blue-500" viewBox="0 0 16 16">
<path d="M13.5 1a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3M11 2.5a2.5 2.5 0 1 1 .603 1.628l-6.718 3.12a2.5 2.5 0 0 1 0 1.504l6.718 3.12a2.5 2.5 0 1 1-.488.876l-6.718-3.12a2.5 2.5 0 1 1 0-3.256l6.718-3.12A2.5 2.5 0 0 1 11 2.5m-8.5 4a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3m11 5.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3"/>
</svg>
</a>
</li>
<li title="Get help!" class="fixed top-0 right-0 p-2 px-4 pt-4 mt-auto text-xs">
<div class="justify-center" wire:click="help" onclick="help.showModal()">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chat-dots-fill" viewBox="0 0 16 16">
<path d="M16 8c0 3.866-3.582 7-8 7a9 9 0 0 1-2.347-.306c-.584.296-1.925.864-4.181 1.234-.2.032-.352-.176-.273-.362.354-.836.674-1.95.77-2.966C.744 11.37 0 9.76 0 8c0-3.866 3.582-7 8-7s8 3.134 8 7M5 8a1 1 0 1 0-2 0 1 1 0 0 0 2 0m4 0a1 1 0 1 0-2 0 1 1 0 0 0 2 0m3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2"/>
</svg>
</div>
</li>
<form action="/logout" method="POST" class="hover:bg-transparent">
<li title="Logout" class="mb-6 hover:transparent">
@csrf
<button type="submit" class="rounded-none hover:text-white hover:bg-transparent">
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2a9.985 9.985 0 0 1 8 4h-2.71a8 8 0 1 0 .001 12h2.71A9.985 9.985 0 0 1 12 22m7-6v-3h-8v-2h8V8l5 4z" />
</svg>
</button>
>>>>>>> 35700ec24 (main: begin major rewrite for lasthour)
</li> </li>
<li> <li>
<a href="#" <a href="#"

View File

@ -329,7 +329,7 @@
</div> </div>
<div class="mt-1 text-base leading-7 text-gray-300"> <div class="mt-1 text-base leading-7 text-gray-300">
You own your own data. All configurations saved on your own servers, so if You own your own data. All configurations saved on your own servers, so if
you decide to stop using Coolify, you can still continue to manage your you decide to stop using Last Hour Cloud, you can still continue to manage your
deployed resources. deployed resources.
</div> </div>
</div> </div>
@ -351,7 +351,7 @@
<div class="text-2xl font-semibold dark:text-white">Monitoring</div> <div class="text-2xl font-semibold dark:text-white">Monitoring</div>
</div> </div>
<div class="mt-1 text-base leading-7 text-gray-300"> <div class="mt-1 text-base leading-7 text-gray-300">
Coolify will automatically monitor your configured servers and deployed Last Hour Cloud will automatically monitor your configured servers and deployed
resources. Notifies you if something goes wrong on your favourite resources. Notifies you if something goes wrong on your favourite
channels, like Discord, Telegram, via Email and more... channels, like Discord, Telegram, via Email and more...
</div> </div>

View File

@ -1,15 +1,13 @@
<div class="pb-5"> <div class="pb-5">
<h1>Settings</h1> <h1>Settings</h1>
<div class="subtitle">Instance wide settings for Coolify.</div> <div class="subtitle">Instance wide settings for Last Hour Cloud.</div>
<div class="navbar-main"> <div class="navbar-main">
<nav class="flex items-center gap-6 min-h-10 whitespace-nowrap"> <nav class="flex items-center gap-6 min-h-10 whitespace-nowrap">
<a class="{{ request()->routeIs('settings.index') ? 'dark:text-white' : '' }}" <a class="{{ request()->routeIs('settings.index') ? 'dark:text-white' : '' }}" href="{{ route('settings.index') }}">
href="{{ route('settings.index') }}">
<button>Configuration</button> <button>Configuration</button>
</a> </a>
@if (isCloud()) @if (isCloud())
<a class="{{ request()->routeIs('settings.license') ? 'dark:text-white' : '' }}" <a class="{{ request()->routeIs('settings.license') ? 'dark:text-white' : '' }}" href="{{ route('settings.license') }}">
href="{{ route('settings.license') }}">
<button>Resale License</button> <button>Resale License</button>
</a> </a>
@endif @endif

View File

@ -1,2 +1 @@
<a {{ $attributes->merge(['class' => 'text-xs cursor-pointer opacity-90 hover:opacity-100 dark:hover:text-white hover:text-black']) }} <a {{ $attributes->merge(['class' => 'text-xs cursor-pointer opacity-60 hover:opacity-100 hover:text-white z-50']) }} href="https://githaven.org/Shiloh/lasthourcloud/releases/tag/v{{ config('version') }}">v{{ config('version') }}</a>
href="https://github.com/coollabsio/coolify/releases/tag/v{{ config('version') }}">v{{ config('version') }}</a>

View File

@ -2,8 +2,8 @@
A resource ({{ $containerName }}) has been restarted automatically on {{ $serverName }}, because it was stopped unexpectedly. A resource ({{ $containerName }}) has been restarted automatically on {{ $serverName }}, because it was stopped unexpectedly.
@if ($containerName === 'coolify-proxy') @if ($containerName === 'coolify-proxy')
Coolify Proxy should run on your server as you have FQDNs set up in one of your resources. Last Hour Cloud Proxy should run on your server as you have FQDNs set up in one of your resources.
If you don't want to use Coolify Proxy, please remove FQDN from your resources or set Proxy type to Custom(None). If you don't want to use Last Hour Cloud Proxy, please remove FQDN from your resources or set Proxy type to Custom(None).
@endif @endif
</x-emails.layout> </x-emails.layout>

View File

@ -1,7 +1,7 @@
<x-emails.layout> <x-emails.layout>
Your server ({{ $name }}) has high disk usage ({{ $disk_usage }}% used). Threshold is {{ $threshold }}%. Your server ({{ $name }}) has high disk usage ({{ $disk_usage }}% used). Threshold is {{ $threshold }}%.
Please cleanup your disk to prevent data-loss. Here are some [tips](https://coolify.io/docs/knowledge-base/server/automated-cleanup). Please cleanup your disk to prevent data-loss. Here are some [tips](https://coolify.io/docs/knowledge-base/server/automated-cleanup).
(You can change the threshold in the Server Settings menu.) (You can change the threshold in the Server Settings menu.)
</x-emails.layout> </x-emails.layout>

View File

@ -1,5 +1,5 @@
<x-emails.layout> <x-emails.layout>
Coolify cannot connect to your server ({{ $name }}). Please check your server and make sure it is running. Last Hour Cloud cannot connect to your server ({{ $name }}). Please check your server and make sure it is running.
All automations & integrations are turned off! All automations & integrations are turned off!

View File

@ -19,11 +19,11 @@
} }
} }
@endphp @endphp
<title>{{ $name }}{{ $title ?? 'Coolify' }}</title> <title>{{ $name }}{{ $title ?? 'Last Hour Cloud' }}</title>
@env('local') @env('local')
<link rel="icon" href="{{ asset('favicon-dev.png') }}" type="image/x-icon" /> <link rel="icon" href="{{ asset('favicon-dev.png') }}" type="image/x-icon" />
@else @else
<link rel="icon" href="{{ asset('coolify-transparent.png') }}" type="image/x-icon" /> <link rel="icon" href="{{ asset('lasthour-transparent.png') }}" type="image/x-icon" />
@endenv @endenv
<meta name="csrf-token" content="{{ csrf_token() }}"> <meta name="csrf-token" content="{{ csrf_token() }}">
@vite(['resources/js/app.js', 'resources/css/app.css']) @vite(['resources/js/app.js', 'resources/css/app.css'])
@ -43,7 +43,7 @@
</head> </head>
@section('body') @section('body')
<body> <body>
<x-toast /> <x-toast />
<script data-navigate-once> <script data-navigate-once>
if (!('theme' in localStorage)) { if (!('theme' in localStorage)) {
@ -123,6 +123,55 @@
} }
} }
<<
<< << < HEAD
===
=== =
function revive() {
if (checkHealthInterval) return true;
console.log('Checking server\'s health...')
checkHealthInterval = setInterval(() => {
fetch('/api/health')
.then(response => {
if (response.ok) {
window.toast('Last Hour Cloud is back online. Reloading...', {
type: 'success',
})
if (checkHealthInterval) clearInterval(checkHealthInterval);
setTimeout(() => {
window.location.reload();
}, 5000)
} else {
console.log('Waiting for server to come back from dead...');
}
})
}, 2000);
}
function upgrade() {
if (checkIfIamDeadInterval) return true;
console.log('Update initiated.')
checkIfIamDeadInterval = setInterval(() => {
fetch('/api/health')
.then(response => {
if (response.ok) {
console.log('It\'s alive. Waiting for server to be dead...');
} else {
window.toast('Update done, restarting Last Hour Cloud!', {
type: 'success',
})
console.log('It\'s dead. Reviving... Standby... Bzz... Bzz...')
if (checkIfIamDeadInterval) clearInterval(checkIfIamDeadInterval);
revive();
}
})
}, 2000);
}
>>>
>>> > 35700 ec24(main: begin major rewrite
for lasthour)
function copyToClipboard(text) { function copyToClipboard(text) {
navigator?.clipboard?.writeText(text) && window.Livewire.dispatch('success', 'Copied to clipboard.'); navigator?.clipboard?.writeText(text) && window.Livewire.dispatch('success', 'Copied to clipboard.');
} }
@ -219,7 +268,7 @@
}) })
}); });
</script> </script>
</body> </body>
@show @show
</html> </html>

View File

@ -1,6 +1,6 @@
@php use App\Enums\ProxyTypes; @endphp @php use App\Enums\ProxyTypes; @endphp
<x-slot:title> <x-slot:title>
Onboarding | Coolify Onboarding | Last Hour Cloud
</x-slot> </x-slot>
<section class="flex flex-col h-full lg:items-center lg:justify-center"> <section class="flex flex-col h-full lg:items-center lg:justify-center">
<div class="flex flex-col items-center justify-center p-10 mx-2 mt-10 bg-white border rounded-lg shadow lg:p-20 dark:bg-transparent dark:border-none max-w-7xl "> <div class="flex flex-col items-center justify-center p-10 mx-2 mt-10 bg-white border rounded-lg shadow lg:p-20 dark:bg-transparent dark:border-none max-w-7xl ">
@ -13,17 +13,19 @@
</div> </div>
@endif @endif
</div> </div>
@endif
</div>
<div> <div>
@if ($currentState === 'explanation') @if ($currentState === 'explanation')
<x-boarding-step title="What is this?"> <x-boarding-step title="What is this?">
<x-slot:question> <x-slot:question>
Last Hour Cloud is an all-in-one application to automate tasks on your servers, deploy application with Git Last Hour Cloud is an all-in-one application to automate tasks on your servers, deploy applications with Git
integrations, deploy databases and services, monitor these resources with notifications and alerts integrations, deploy databases and services, monitor these resources with notifications and alerts
without vendor lock-in without vendor lock-in
and <a href="https://lasthourhosting.org/cloud.html" class="text-white hover:underline">much much more</a>. and <a href="https://lasthourhosting.org/cloud.html" class="text-white hover:underline">much much more</a>.
<br><br> <br><br>
<span class="text-xl"> <span class="text-xl">
<x-highlighted text="Self-hosting for the last hour." /></span> <x-highlighted text="Self-hosting for the Last Hour Cloud." /></span>
</x-slot:question> </x-slot:question>
<x-slot:explanation> <x-slot:explanation>
<p><x-highlighted text="Task automation:" /> You do not to manage your servers too much. This does <p><x-highlighted text="Task automation:" /> You do not to manage your servers too much. This does
@ -220,6 +222,206 @@
</x-boarding-step> </x-boarding-step>
@endif @endif
</div> </div>
<div>
@if ($currentState === 'validate-server')
<x-boarding-step title="Validate & Configure Server">
<x-slot:question>
we need to validate your server (connection, Docker Engine, etc) and configure to see if something is
missing. Are you okay with this?
</x-slot:question>
<x-slot:actions>
<x-slide-over closeWithX fullScreen>
<x-slot:title>Validate & configure</x-slot:title>
<x-slot:content>
<livewire:server.validate-and-install :server="$this->createdServer" />
</x-slot:content>
<x-forms.button @click="slideOverOpen=true" class="font-bold box w-96" wire:click.prevent='installServer' isHighlighted>
Send it!
</x-forms.button>
</x-slot:actions>
</x-boarding-step>
@endif
@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" />?
</x-slot:question>
<x-slot:actions>
<x-forms.button class="justify-center w-64 box" wire:target="setServerType('localhost')" wire:click="setServerType('localhost')">Localhost
</x-forms.button>
<x-forms.button class="justify-center w-64 box " wire:target="setServerType('remote')" wire:click="setServerType('remote')">Remote Server
</x-forms.button>
@if (!$serverReachable)
Localhost is not reachable with the following public key.
<br /> <br />
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for user
'root' or skip the guided tour and add a new private key manually to Last Hour Cloud and to the
server.
<br />
Check the upstream <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh">documentation</a> for further help.
<x-forms.input readonly id="serverPublicKey"></x-forms.input>
<x-forms.button class="w-64 box" wire:target="setServerType('localhost')" wire:click="setServerType('localhost')">Check again
</x-forms.button>
@endif
</x-slot:actions>
<x-slot:explanation>
<p>Servers are the main building blocks, as they will host your applications, databases,
services, called resources. Any CPU intensive process will use the server's CPU where you
are deploying your resources.</p>
<p>Localhost is the server where Last Hour Cloud is running on. It is not recommended to use one server
for everything.</p>
<p>Remote Server is a server reachable through SSH. It can be hosted at home, or from any cloud
provider.</p>
</x-slot:explanation>
</x-boarding-step>
@endif
</div>
<div>
@if ($currentState === 'private-key')
<x-boarding-step title="SSH Key">
<x-slot:question>
Do you have your own SSH Private Key?
</x-slot:question>
<x-slot:actions>
<x-forms.button class="justify-center w-64 box" wire:target="setPrivateKey('own')" wire:click="setPrivateKey('own')">Yes
</x-forms.button>
<x-forms.button class="justify-center w-64 box" wire:target="setPrivateKey('create')" wire:click="setPrivateKey('create')">No (create one for me)
</x-forms.button>
@if (count($privateKeys) > 0)
<form wire:submit='selectExistingPrivateKey' class="flex flex-col w-full gap-4 pr-10">
<x-forms.select label="Existing SSH Keys" id='selectedExistingPrivateKey'>
@foreach ($privateKeys as $privateKey)
<option wire:key="{{ $loop->index }}" value="{{ $privateKey->id }}">
{{ $privateKey->name }}
</option>
@endforeach
</x-forms.select>
<x-forms.button type="submit">Use this SSH Key</x-forms.button>
</form>
@endif
</x-slot:actions>
<x-slot:explanation>
<p>SSH Keys are used to connect to a remote server through a secure shell, called SSH.</p>
<p>You can use your own ssh private key, or you can allow Last Hour Cloud to create one for you.</p>
<p>In both ways, you need to add the public version of your ssh private key to the remote
server's
<code class="text-warning">~/.ssh/authorized_keys</code> file.
</p>
</x-slot:explanation>
</x-boarding-step>
@endif
</div>
<div>
@if ($currentState === 'select-existing-server')
<x-boarding-step title="Select a server">
<x-slot:question>
There are already servers available for your Team. Do you want to use one of them?
</x-slot:question>
<x-slot:actions>
<div class="flex flex-col gap-4">
<div>
<x-forms.button class="justify-center w-64 box" wire:click="createNewServer">No (create one
for
me)
</x-forms.button>
</div>
<div>
<form wire:submit='selectExistingServer' class="flex flex-col w-full gap-4 lg:w-96">
<x-forms.select label="Existing servers" class="w-96" id='selectedExistingServer'>
@foreach ($servers as $server)
<option wire:key="{{ $loop->index }}" value="{{ $server->id }}">
{{ $server->name }}
</option>
@endforeach
</x-forms.select>
<x-forms.button type="submit">Use this Server</x-forms.button>
</form>
</div>
</div>
@if (!$serverReachable)
This server is not reachable with the following public key.
<br /> <br />
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for user
'root' or skip the boarding process and add a new private key manually to Last Hour Cloud and to the
server.
<x-forms.input readonly id="serverPublicKey"></x-forms.input>
<x-forms.button class="w-64 box" wire:target="validateServer" wire:click="validateServer">Check
again
</x-forms.button>
@endif
</x-slot:actions>
<x-slot:explanation>
<p>Private Keys are used to connect to a remote server through a secure shell, called SSH.</p>
<p>You can use your own private key, or you can let Last Hour Cloud create one for you.</p>
<p>In both ways, you need to add the public version of your private key to the remote server's
<code>~/.ssh/authorized_keys</code> file.
</p>
</x-slot:explanation>
</x-boarding-step>
@endif
</div>
<div>
@if ($currentState === 'create-private-key')
<x-boarding-step title="Create Private Key">
<x-slot:question>
Please let me know your key details.
</x-slot:question>
<x-slot:actions>
<form wire:submit='savePrivateKey' class="flex flex-col w-full gap-4 pr-10">
<x-forms.input required placeholder="Choose a name for your Private Key. Could be anything." label="Name" id="privateKeyName" />
<x-forms.input placeholder="Description, so others will know more about this." label="Description" id="privateKeyDescription" />
<x-forms.textarea required placeholder="-----BEGIN OPENSSH PRIVATE KEY-----" label="Private Key" id="privateKey" />
@if ($privateKeyType === 'create')
<x-forms.textarea rows="7" readonly label="Public Key" id="publicKey" />
<span class="font-bold text-warning">ACTION REQUIRED: Copy the 'Public Key' to your server's
~/.ssh/authorized_keys
file.</span>
@endif
<x-forms.button type="submit">Save</x-forms.button>
</form>
</x-slot:actions>
<x-slot:explanation>
<p>Private Keys are used to connect to a remote server through a secure shell, called SSH.</p>
<p>You can use your own private key, or you can let Last Hour Cloud create one for you.</p>
<p>In both ways, you need to add the public version of your private key to the remote server's
<code>~/.ssh/authorized_keys</code> file.
</p>
</x-slot:explanation>
</x-boarding-step>
@endif
</div>
<div>
@if ($currentState === 'create-server')
<x-boarding-step title="Create a Server">
<x-slot:question>
Please let us know your server details.
</x-slot:question>
<x-slot:actions>
<form wire:submit='saveServer' class="flex flex-col w-full gap-4 pr-10">
<div class="flex gap-2">
<x-forms.input required placeholder="Choose a name for your Server. It could be anything." label="Name" id="remoteServerName" />
<x-forms.input placeholder="Description, so others will know more about it." label="Description" id="remoteServerDescription" />
</div>
<div class="flex gap-2">
<x-forms.input required placeholder="127.0.0.1" label="IP Address" id="remoteServerHost" />
<x-forms.input required placeholder="Port number of your server. Default is 22." label="Port" id="remoteServerPort" />
<x-forms.input required readonly placeholder="Username to connect to your server. Default is root." label="Username" id="remoteServerUser" />
</div>
<div class="w-64">
<x-forms.checkbox helper="If you are using Cloudflare Tunnels, enable this. It will proxy all ssh requests to your server through Cloudflare.<br><span class='text-warning'>Last Hour Cloud does not install/setup Cloudflare (cloudflared) on your server.</span>" id="isCloudflareTunnel" label="Cloudflare Tunnel" />
</div>
<x-forms.button type="submit">Continue</x-forms.button>
</form>
</x-slot:actions>
<x-slot:explanation>
<p>Username should be <x-highlighted text="root" /> for now. We are working on using
non-root users.</p>
</x-slot:explanation>
</x-boarding-step>
@endif
</div>
<div> <div>
@if ($currentState === 'validate-server') @if ($currentState === 'validate-server')
<x-boarding-step title="Validate & Configure Server"> <x-boarding-step title="Validate & Configure Server">

View File

@ -1,6 +1,6 @@
<div> <div>
<x-slot:title> <x-slot:title>
Dashboard | Coolify Dashboard | Last Hour Cloud
</x-slot> </x-slot>
@if (session('error')) @if (session('error'))
<span x-data x-init="$wire.emit('error', '{{ session('error') }}')" /> <span x-data x-init="$wire.emit('error', '{{ session('error') }}')" />
@ -9,10 +9,8 @@
<div class="subtitle">Your self-hosted infrastructure.</div> <div class="subtitle">Your self-hosted infrastructure.</div>
@if (request()->query->get('success')) @if (request()->query->get('success'))
<div class="items-center justify-center mb-10 font-bold rounded alert alert-success"> <div class="items-center justify-center mb-10 font-bold rounded alert alert-success">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none" <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none" viewBox="0 0 24 24">
viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg> </svg>
Your subscription has been activated! Welcome onboard! <br>It could take a few seconds before your Your subscription has been activated! Welcome onboard! <br>It could take a few seconds before your
subscription is activated.<br> Please be patient. subscription is activated.<br> Please be patient.
@ -22,22 +20,20 @@
@if ($projects->count() > 0) @if ($projects->count() > 0)
<div class="grid grid-cols-1 gap-2 xl:grid-cols-2"> <div class="grid grid-cols-1 gap-2 xl:grid-cols-2">
@foreach ($projects as $project) @foreach ($projects as $project)
<div class="gap-2 border border-transparent cursor-pointer box group" <div class="gap-2 border border-transparent cursor-pointer box group" onclick="gotoProject('{{ $project->uuid }}','{{ $project->default_environment() }}')">
onclick="gotoProject('{{ $project->uuid }}','{{ $project->default_environment() }}')">
<div class="flex flex-1 mx-6"> <div class="flex flex-1 mx-6">
<div class="flex flex-col justify-center flex-1"> <div class="flex flex-col justify-center flex-1">
<div class="box-title">{{ $project->name }}</div> <div class="box-title">{{ $project->name }}</div>
<div class="box-description"> <div class="box-description">
{{ $project->description }}</div> {{ $project->description }}
</div>
</div> </div>
<div class="flex items-center justify-center gap-2 text-xs font-bold "> <div class="flex items-center justify-center gap-2 text-xs font-bold ">
<a class="hover:underline" <a class="hover:underline" href="{{ route('project.resource.create', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
href="{{ route('project.resource.create', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
<span class="p-2 font-bold">+ <span class="p-2 font-bold">+
Add Resource</span> Add Resource</span>
</a> </a>
<a class="hover:underline" <a class="hover:underline" href="{{ route('project.edit', ['project_uuid' => data_get($project, 'uuid')]) }}">
href="{{ route('project.edit', ['project_uuid' => data_get($project, 'uuid')]) }}">
Settings Settings
</a> </a>
</div> </div>
@ -61,10 +57,7 @@
@if ($servers->count() > 0) @if ($servers->count() > 0)
<div class="grid grid-cols-1 gap-2 xl:grid-cols-2"> <div class="grid grid-cols-1 gap-2 xl:grid-cols-2">
@foreach ($servers as $server) @foreach ($servers as $server)
<a href="{{ route('server.show', ['server_uuid' => data_get($server, 'uuid')]) }}" <a href="{{ route('server.show', ['server_uuid' => data_get($server, 'uuid')]) }}" @class([ 'gap-2 border cursor-pointer box group' , 'border-transparent'=> $server->settings->is_reachable,
@class([
'gap-2 border cursor-pointer box group',
'border-transparent' => $server->settings->is_reachable,
'border-red-500' => !$server->settings->is_reachable, 'border-red-500' => !$server->settings->is_reachable,
])> ])>
<div class="flex flex-col justify-center mx-6"> <div class="flex flex-col justify-center mx-6">
@ -72,7 +65,8 @@
{{ $server->name }} {{ $server->name }}
</div> </div>
<div class="box-description"> <div class="box-description">
{{ $server->description }}</div> {{ $server->description }}
</div>
<div class="flex gap-1 text-xs text-error"> <div class="flex gap-1 text-xs text-error">
@if (!$server->settings->is_reachable) @if (!$server->settings->is_reachable)
Not reachable Not reachable
@ -81,7 +75,7 @@
& &
@endif @endif
@if (!$server->settings->is_usable) @if (!$server->settings->is_usable)
Not usable by Coolify Not usable by Last Hour Cloud
@endif @endif
</div> </div>
</div> </div>
@ -93,8 +87,7 @@
@if ($private_keys->count() === 0) @if ($private_keys->count() === 0)
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<div class='font-bold dark:text-warning'>No private keys found.</div> <div class='font-bold dark:text-warning'>No private keys found.</div>
<div class="flex items-center gap-1">Before you can add your server, first <x-modal-input <div class="flex items-center gap-1">Before you can add your server, first <x-modal-input buttonTitle="add" title="New Private Key">
buttonTitle="add" title="New Private Key">
<livewire:security.private-key.create from="server" /> <livewire:security.private-key.create from="server" />
</x-modal-input> a private key </x-modal-input> a private key
or or
@ -131,9 +124,7 @@
<h4 class="py-4">{{ $server_name }}</h4> <h4 class="py-4">{{ $server_name }}</h4>
<div class="grid grid-cols-1 gap-2 lg:grid-cols-3"> <div class="grid grid-cols-1 gap-2 lg:grid-cols-3">
@foreach ($deployments as $deployment) @foreach ($deployments as $deployment)
<a href="{{ data_get($deployment, 'deployment_url') }}" @class([ <a href="{{ data_get($deployment, 'deployment_url') }}" @class([ 'gap-2 cursor-pointer box group border-l-2 border-dotted' , 'dark:border-coolgray-300'=> data_get($deployment, 'status') === 'queued',
'gap-2 cursor-pointer box group border-l-2 border-dotted',
'dark:border-coolgray-300' => data_get($deployment, 'status') === 'queued',
'border-yellow-500' => data_get($deployment, 'status') === 'in_progress', 'border-yellow-500' => data_get($deployment, 'status') === 'in_progress',
])> ])>
<div class="flex flex-col justify-center mx-6"> <div class="flex flex-col justify-center mx-6">

View File

@ -1,7 +1,7 @@
<section class="bg-gray-50 dark:bg-base"> <section class="bg-gray-50 dark:bg-base">
<div class="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0"> <div class="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
<a class="flex items-center mb-6 text-5xl font-extrabold tracking-tight text-gray-900 dark:text-white"> <a class="flex items-center mb-6 text-5xl font-extrabold tracking-tight text-gray-900 dark:text-white">
Last Hour Last Hour Cloud
</a> </a>
<div class="w-full bg-white shadow md:mt-0 sm:max-w-md xl:p-0 dark:bg-base "> <div class="w-full bg-white shadow md:mt-0 sm:max-w-md xl:p-0 dark:bg-base ">
<div class="p-6 space-y-4 md:space-y-6 sm:p-8"> <div class="p-6 space-y-4 md:space-y-6 sm:p-8">

View File

@ -1,9 +1,8 @@
<div class="flex flex-col w-full gap-2"> <div class="flex flex-col w-full gap-2">
<div>Your feedback helps us to improve Coolify. Thank you! 💜</div> <div>Give us feedback or ask a question and our team will get back to you within 24 hours.</div>
<form wire:submit="submit" class="flex flex-col gap-4 pt-4"> <form wire:submit="submit" class="flex flex-col gap-4 pt-4">
<x-forms.input id="subject" label="Subject" placeholder="Summary of your problem."></x-forms.input> <x-forms.input id="subject" label="Subject" placeholder="Summary of your problem."></x-forms.input>
<x-forms.textarea rows="10" id="description" label="Description" class="font-sans" spellcheck <x-forms.textarea rows="10" id="description" label="Description" class="font-sans" spellcheck placeholder="Please provide as much information as possible."></x-forms.textarea>
placeholder="Please provide as much information as possible."></x-forms.textarea>
<div></div> <div></div>
<x-forms.button class="w-full mt-4" type="submit" @click="modalOpen=false">Send</x-forms.button> <x-forms.button class="w-full mt-4" type="submit" @click="modalOpen=false">Send</x-forms.button>
</form> </form>

View File

@ -18,7 +18,7 @@
if (checkNumber > 5) { if (checkNumber > 5) {
this.popups.realtime = true; this.popups.realtime = true;
console.error( console.error(
'Coolify could not connect to its real-time service. This will cause unusual problems on the UI if not fixed! Please check the related documentation (https://coolify.io/docs/knowledge-base/cloudflare/tunnels) or get help on Discord (https://coollabs.io/discord).)' 'Last Hour Cloud could not connect to its real-time service. This will cause unusual problems on the UI if not fixed! Please check the related documentation (https://coolify.io/docs/knowledge-base/cloudflare/tunnels) or get help on Discord (https://coollabs.io/discord).)'
); );
clearInterval(checkPusherInterval); clearInterval(checkPusherInterval);
} }
@ -35,16 +35,14 @@
<span class="font-bold text-left text-red-500">WARNING: </span>Realtime Error?! <span class="font-bold text-left text-red-500">WARNING: </span>Realtime Error?!
</x-slot:title> </x-slot:title>
<x-slot:description> <x-slot:description>
<span>Coolify could not connect to its real-time service.<br>This will cause unusual problems on the <span>Last Hour Cloud could not connect to its real-time service.<br>This will cause unusual problems on the
UI UI
if if
not fixed! <br><br> not fixed! <br><br>
Please ensure that you have opened the Please ensure that you have opened the
<a class="underline" href='https://coolify.io/docs/knowledge-base/server/firewall' <a class="underline" href='https://coolify.io/docs/knowledge-base/server/firewall' target='_blank'>required ports</a>,
target='_blank'>required ports</a>,
check the check the
related <a class="underline" href='https://coolify.io/docs/knowledge-base/cloudflare/tunnels' related <a class="underline" href='https://coolify.io/docs/knowledge-base/cloudflare/tunnels' target='_blank'>documentation</a> or get
target='_blank'>documentation</a> or get
help on <a class="underline" href='https://coollabs.io/discord' target='_blank'>Discord</a>. help on <a class="underline" href='https://coollabs.io/discord' target='_blank'>Discord</a>.
</span> </span>
</x-slot:description> </x-slot:description>
@ -58,20 +56,15 @@
<span x-show="popups.sponsorship"> <span x-show="popups.sponsorship">
<x-popup> <x-popup>
<x-slot:title> <x-slot:title>
Love Coolify as we do? Like Last Hour Cloud?
</x-slot:title> </x-slot:title>
<x-slot:icon> <x-slot:icon>
<img src="https://cdn-icons-png.flaticon.com/512/8236/8236748.png" <img src="https://cdn-icons-png.flaticon.com/512/8236/8236748.png" class="w-8 h-8 sm:w-12 sm:h-12 lg:w-16 lg:h-16">
class="w-8 h-8 sm:w-12 sm:h-12 lg:w-16 lg:h-16">
</x-slot:icon> </x-slot:icon>
<x-slot:description> <x-slot:description>
<span>Please <span>Please
consider donating on <a href="https://github.com/sponsors/coollabsio" consider sharing about us! <a href="https://githaven.org/Shiloh/lasthourcloud/" class="text-xs underline dark:text-white">GitHaven</a> or <a href="https://lasthourhosting.org" class="text-xs underline dark:text-white">Last Hour Hosting</a>.<br><br></span>
class="text-xs underline dark:text-white">GitHub</a> or <a <span>Your support enables us to keep creating services that support sharing the Gospel of Jesus Christ. 1 John 2:18</span>
href="https://opencollective.com/coollabsio"
class="text-xs underline dark:text-white">OpenCollective</a>.<br><br></span>
<span>It enables us to keep creating features without paywalls, ensuring our work remains free and
open.</span>
</x-slot:description> </x-slot:description>
<x-slot:button-text @click="disableSponsorship()"> <x-slot:button-text @click="disableSponsorship()">
Disable This Popup Disable This Popup
@ -82,8 +75,7 @@
<x-banner :closable=false> <x-banner :closable=false>
<div><span class="font-bold text-red-500">WARNING:</span> The number of active servers exceeds the limit <div><span class="font-bold text-red-500">WARNING:</span> The number of active servers exceeds the limit
covered by your payment. If not resolved, some of your servers <span class="font-bold text-red-500">will covered by your payment. If not resolved, some of your servers <span class="font-bold text-red-500">will
be deactivated</span>. Visit <a href="{{ route('subscription.show') }}" be deactivated</span>. Visit <a href="{{ route('subscription.show') }}" class="underline dark:text-white">/subscription</a> to update your subscription or remove some
class="underline dark:text-white">/subscription</a> to update your subscription or remove some
servers. servers.
</div> </div>
</x-banner> </x-banner>
@ -95,18 +87,15 @@
No notifications enabled. No notifications enabled.
</x-slot:title> </x-slot:title>
<x-slot:icon> <x-slot:icon>
<svg xmlns="http://www.w3.org/2000/svg" class="text-red-500 stroke-current w-14 h-14 shrink-0" <svg xmlns="http://www.w3.org/2000/svg" class="text-red-500 stroke-current w-14 h-14 shrink-0" fill="none" viewBox="0 0 24 24">
fill="none" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg> </svg>
</x-slot:icon> </x-slot:icon>
<x-slot:description> <x-slot:description>
It is It is
highly recommended to enable at least highly recommended to enable at least
one one
notification channel to receive important alerts.<br>Visit <a notification channel to receive important alerts.<br>Visit <a href="{{ route('notifications.email') }}" class="underline dark:text-white">/notification</a> to
href="{{ route('notifications.email') }}" class="underline dark:text-white">/notification</a> to
enable notifications.</span> enable notifications.</span>
</x-slot:description> </x-slot:description>
<x-slot:button-text @click="disableNotification()"> <x-slot:button-text @click="disableNotification()">

View File

@ -5,6 +5,14 @@
<x-forms.button type="submit"> <x-forms.button type="submit">
Save Save
</x-forms.button> </x-forms.button>
<<<<<<< HEAD=======@if ($isConfigurationChanged && !is_null($application->config_hash) && !$application->isExited())
<div title="Configuration not applied to the running application. You need to redeploy.">
<svg class="w-6 h-6 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>
</div>
@endif
>>>>>>> 35700ec24 (main: begin major rewrite for lasthour)
</div> </div>
<div>General configuration for your application.</div> <div>General configuration for your application.</div>
<div class="flex flex-col gap-2 py-4"> <div class="flex flex-col gap-2 py-4">
@ -16,8 +24,7 @@
@if (!$application->dockerfile && $application->build_pack !== 'dockerimage') @if (!$application->dockerfile && $application->build_pack !== 'dockerimage')
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<div class="flex gap-2"> <div class="flex gap-2">
<x-forms.select x-bind:disabled="initLoadingCompose" wire:model.live="application.build_pack" <x-forms.select x-bind:disabled="initLoadingCompose" wire:model.live="application.build_pack" label="Build Pack" required>
label="Build Pack" required>
<option value="nixpacks">Nixpacks</option> <option value="nixpacks">Nixpacks</option>
<option value="static">Static</option> <option value="static">Static</option>
<option value="dockerfile">Dockerfile</option> <option value="dockerfile">Dockerfile</option>
@ -40,10 +47,7 @@
@foreach (data_get($parsedServices, 'services') as $serviceName => $service) @foreach (data_get($parsedServices, 'services') as $serviceName => $service)
@if (!isDatabaseImage(data_get($service, 'image'))) @if (!isDatabaseImage(data_get($service, 'image')))
<div class="flex items-end gap-2"> <div class="flex items-end gap-2">
<x-forms.input <x-forms.input helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. " label="Domains for {{ str($serviceName)->headline() }}" id="parsedServiceDomains.{{ $serviceName }}.domain"></x-forms.input>
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. "
label="Domains for {{ str($serviceName)->headline() }}"
id="parsedServiceDomains.{{ $serviceName }}.domain"></x-forms.input>
<x-forms.button wire:click="generateDomain('{{ $serviceName }}')">Generate <x-forms.button wire:click="generateDomain('{{ $serviceName }}')">Generate
Domain</x-forms.button> Domain</x-forms.button>
</div> </div>
@ -56,14 +60,12 @@
@endif @endif
@if ($application->build_pack !== 'dockercompose') @if ($application->build_pack !== 'dockercompose')
<div class="flex items-end gap-2"> <div class="flex items-end gap-2">
<x-forms.input placeholder="https://coolify.io" id="application.fqdn" label="Domains" <x-forms.input placeholder="https://lasthourhosting.org" id="application.fqdn" label="Domains" helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.lasthourhosting.org,https://cloud.lasthourhosting.org/dashboard<br>- http://app.lasthourhosting.org/api/v3<br>- http://app.lasthourhosting.org:3000 -> app.lasthourhosting.org will point to port 3000 inside the container. " />
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. " />
<x-forms.button wire:click="getWildcardDomain">Generate Domain <x-forms.button wire:click="getWildcardDomain">Generate Domain
</x-forms.button> </x-forms.button>
</div> </div>
<div class="flex items-end gap-2"> <div class="flex items-end gap-2">
<x-forms.select label="Direction" id="application.redirect" required <x-forms.select label="Direction" id="application.redirect" required helper="You must need to add www and non-www as an A DNS record.">
helper="You must need to add www and non-www as an A DNS record.">
<option value="both">Allow www & non-www.</option> <option value="both">Allow www & non-www.</option>
<option value="www">Redirect to www.</option> <option value="www">Redirect to www.</option>
<option value="non-www">Redirect to non-www.</option> <option value="non-www">Redirect to non-www.</option>
@ -81,15 +83,52 @@
<div class="flex items-center gap-2 pt-8"> <div class="flex items-center gap-2 pt-8">
<h3>Docker Registry</h3> <h3>Docker Registry</h3>
@if ($application->build_pack !== 'dockerimage' && !$application->destination->server->isSwarm()) @if ($application->build_pack !== 'dockerimage' && !$application->destination->server->isSwarm())
<x-helper <x-helper helper="Push the built image to a docker registry. More info <a class='underline' href='https://coolify.io/docs/knowledge-base/docker/registry' target='_blank'>here</a>." />
helper="Push the built image to a docker registry. More info <a class='underline' href='https://coolify.io/docs/knowledge-base/docker/registry' target='_blank'>here</a>." />
@endif @endif
</div> </div>
@if ($application->destination->server->isSwarm()) @if ($application->destination->server->isSwarm())
@if ($application->build_pack !== 'dockerimage') @if ($application->build_pack !== 'dockerimage')
<div>Docker Swarm requires the image to be available in a registry. More info <a <div>Docker Swarm requires the image to be available in a registry. More info <a class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry" target="_blank">here</a>.</div>
class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry" @endif
target="_blank">here</a>.</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>
@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')))
<div class="flex items-end gap-2">
<x-forms.input helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io, https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. " label="Domains for {{ str($serviceName)->headline() }}" id="parsedServiceDomains.{{ $serviceName }}.domain"></x-forms.input>
@if (!data_get($parsedServiceDomains, "$serviceName.domain"))
<x-forms.button wire:click="generateDomain('{{ $serviceName }}')">Generate
Domain</x-forms.button>
@endif
</div>
@endif
@endforeach
@endif
@endif
</div>
@endif
@if ($application->build_pack !== 'dockercompose')
<div class="flex items-end gap-2">
<x-forms.input placeholder="https://lasthourhosting.org" id="application.fqdn" label="Domains" helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.lasthourhosting.org, https://cloud.lasthourhosting.org/dashboard<br>- http://app.lasthourhosting.org/api/v3<br>- http://app.lasthourhosting.org:3000 -> app.lasthourhosting.org will point to port 3000 inside the container. " />
<x-forms.button wire:click="getWildcardDomain">Generate Domain
</x-forms.button>
</div>
@endif
@if ($application->build_pack !== 'dockercompose')
<div class="flex items-center gap-2 pt-8">
<h3>Docker Registry</h3>
@if ($application->build_pack !== 'dockerimage' && !$application->destination->server->isSwarm())
<x-helper helper='Push the built image to a docker registry. More info <a class="underline"
href="https://coolify.io/docs/docker/registry" target="_blank">here</a>' />
@endif
</div>
@if ($application->destination->server->isSwarm())
@if ($application->build_pack !== 'dockerimage')
<div>Docker Swarm requires the image to be available in a registry. More info in upstream docs<a class="underline" href="https://coolify.io/docs/docker/registry" target="_blank">here</a>.</div>
@endif @endif
@endif @endif
<div class="flex flex-col gap-2 xl:flex-row"> <div class="flex flex-col gap-2 xl:flex-row">
@ -106,20 +145,11 @@
$application->destination->server->isSwarm() || $application->destination->server->isSwarm() ||
$application->additional_servers->count() > 0 || $application->additional_servers->count() > 0 ||
$application->settings->is_build_server_enabled) $application->settings->is_build_server_enabled)
<x-forms.input id="application.docker_registry_image_name" required label="Docker Image" <x-forms.input id="application.docker_registry_image_name" required label="Docker Image" placeholder="Required!" />
placeholder="Required!" /> <x-forms.input id="application.docker_registry_image_tag" helper="If set, it will tag the built image with this tag too. <br><br>Example: If you set it to 'latest', it will push the image with the commit sha tag + with the latest tag." placeholder="Empty means latest will be used." label="Docker Image Tag" />
<x-forms.input id="application.docker_registry_image_tag"
helper="If set, it will tag the built image with this tag too. <br><br>Example: If you set it to 'latest', it will push the image with the commit sha tag + with the latest tag."
placeholder="Empty means latest will be used." label="Docker Image Tag" />
@else @else
<x-forms.input id="application.docker_registry_image_name" <x-forms.input id="application.docker_registry_image_name" helper="Empty means it won't push the image to a docker registry." placeholder="Empty means it won't push the image to a docker registry." label="Docker Image" />
helper="Empty means it won't push the image to a docker registry." <x-forms.input id="application.docker_registry_image_tag" placeholder="Empty means only push commit sha tag." helper="If set, it will tag the built image with this tag too. <br><br>Example: If you set it to 'latest', it will push the image with the commit sha tag + with the latest tag." label="Docker Image Tag" />
placeholder="Empty means it won't push the image to a docker registry."
label="Docker Image" />
<x-forms.input id="application.docker_registry_image_tag"
placeholder="Empty means only push commit sha tag."
helper="If set, it will tag the built image with this tag too. <br><br>Example: If you set it to 'latest', it will push the image with the commit sha tag + with the latest tag."
label="Docker Image Tag" />
@endif @endif
@endif @endif
</div> </div>
@ -127,25 +157,18 @@
<div class="py-4 border-b dark:border-coolgray-200"> <div class="py-4 border-b dark:border-coolgray-200">
<h3>Build</h3> <h3>Build</h3>
@if ($application->build_pack === 'dockerimage') @if ($application->build_pack === 'dockerimage')
<x-forms.input <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='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>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" />
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='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>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" />
@else @else
@if ($application->could_set_build_commands()) @if ($application->could_set_build_commands())
@if ($application->build_pack === 'nixpacks') @if ($application->build_pack === 'nixpacks')
<div class="flex flex-col gap-2 xl:flex-row"> <div class="flex flex-col gap-2 xl:flex-row">
<x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml" <x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml" id="application.install_command" label="Install Command" />
id="application.install_command" label="Install Command" /> <x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml" id="application.build_command" label="Build Command" />
<x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml" <x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml" id="application.start_command" label="Start Command" />
id="application.build_command" label="Build Command" />
<x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml"
id="application.start_command" label="Start Command" />
</div> </div>
<div class="pt-1 text-xs">Nixpacks will detect the required configuration <div class="pt-1 text-xs">Nixpacks will detect the required configuration
automatically. automatically.
<a class="underline" <a class="underline" href="https://coolify.io/docs/resources/applications/index">Framework
href="https://coolify.io/docs/resources/applications/index">Framework
Specific Docs</a> Specific Docs</a>
</div> </div>
@endif @endif
@ -155,19 +178,11 @@
@if ($application->build_pack === 'dockercompose') @if ($application->build_pack === 'dockercompose')
<div class="flex flex-col gap-2" x-init="$wire.dispatch('loadCompose', true)"> <div class="flex flex-col gap-2" x-init="$wire.dispatch('loadCompose', true)">
<div class="flex gap-2"> <div class="flex gap-2">
<x-forms.input x-bind:disabled="initLoadingCompose" placeholder="/" <x-forms.input x-bind:disabled="initLoadingCompose" placeholder="/" id="application.base_directory" label="Base Directory" helper="Directory to use as root. Useful for monorepos." />
id="application.base_directory" label="Base Directory" <x-forms.input x-bind:disabled="initLoadingCompose" placeholder="/docker-compose.yaml" id="application.docker_compose_location" label="Docker Compose Location" helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }}</span>" />
helper="Directory to use as root. Useful for monorepos." />
<x-forms.input x-bind:disabled="initLoadingCompose"
placeholder="/docker-compose.yaml" id="application.docker_compose_location"
label="Docker Compose Location"
helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }}</span>" />
</div> </div>
<div class="w-96"> <div class="w-96">
<x-forms.checkbox instantSave <x-forms.checkbox instantSave id="application.settings.is_preserve_repository_enabled" label="Preserve Repository During Deployment" helper="Git repository (based on the base directory settings) will be copied to the deployment directory." />
id="application.settings.is_preserve_repository_enabled"
label="Preserve Repository During Deployment"
helper="Git repository (based on the base directory settings) will be copied to the deployment directory." />
</div> </div>
<div class="pt-4">The following commands are for advanced use cases. <div class="pt-4">The following commands are for advanced use cases.
Only Only
@ -175,70 +190,44 @@
know what are know what are
you doing.</div> you doing.</div>
<div class="flex gap-2"> <div class="flex gap-2">
<x-forms.input placeholder="docker compose build" <x-forms.input placeholder="docker compose build" x-bind:disabled="initLoadingCompose" id="application.docker_compose_custom_build_command" helper="If you use this, you need to specify paths relatively and should use the same compose file in the custom command, otherwise the automatically configured labels / etc won't work.<br><br>So in your case, use: <span class='dark:text-warning'>docker compose -f .{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }} build</span>" label="Custom Build Command" />
x-bind:disabled="initLoadingCompose" <x-forms.input placeholder="docker compose up -d" x-bind:disabled="initLoadingCompose" id="application.docker_compose_custom_start_command" helper="If you use this, you need to specify paths relatively and should use the same compose file in the custom command, otherwise the automatically configured labels / etc won't work.<br><br>So in your case, use: <span class='dark:text-warning'>docker compose -f .{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }} up -d</span>" label="Custom Start Command" />
id="application.docker_compose_custom_build_command"
helper="If you use this, you need to specify paths relatively and should use the same compose file in the custom command, otherwise the automatically configured labels / etc won't work.<br><br>So in your case, use: <span class='dark:text-warning'>docker compose -f .{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }} build</span>"
label="Custom Build Command" />
<x-forms.input placeholder="docker compose up -d"
x-bind:disabled="initLoadingCompose"
id="application.docker_compose_custom_start_command"
helper="If you use this, you need to specify paths relatively and should use the same compose file in the custom command, otherwise the automatically configured labels / etc won't work.<br><br>So in your case, use: <span class='dark:text-warning'>docker compose -f .{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }} up -d</span>"
label="Custom Start Command" />
</div> </div>
</div> </div>
@else @else
<div class="flex flex-col gap-2 xl:flex-row"> <div class="flex flex-col gap-2 xl:flex-row">
<x-forms.input placeholder="/" id="application.base_directory" label="Base Directory" <x-forms.input placeholder="/" id="application.base_directory" label="Base Directory" helper="Directory to use as root. Useful for monorepos." />
helper="Directory to use as root. Useful for monorepos." />
@if ($application->build_pack === 'dockerfile' && !$application->dockerfile) @if ($application->build_pack === 'dockerfile' && !$application->dockerfile)
<x-forms.input placeholder="/Dockerfile" id="application.dockerfile_location" <x-forms.input placeholder="/Dockerfile" id="application.dockerfile_location" label="Dockerfile Location" helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($application->base_directory . $application->dockerfile_location, '/') }}</span>" />
label="Dockerfile Location"
helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($application->base_directory . $application->dockerfile_location, '/') }}</span>" />
@endif @endif
@if ($application->build_pack === 'dockerfile') @if ($application->build_pack === 'dockerfile')
<x-forms.input id="application.dockerfile_target_build" <x-forms.input id="application.dockerfile_target_build" label="Docker Build Stage Target" helper="Useful if you have multi-staged dockerfile." />
label="Docker Build Stage Target"
helper="Useful if you have multi-staged dockerfile." />
@endif @endif
@if ($application->could_set_build_commands()) @if ($application->could_set_build_commands())
@if ($application->settings->is_static) @if ($application->settings->is_static)
<x-forms.input placeholder="/dist" id="application.publish_directory" <x-forms.input placeholder="/dist" id="application.publish_directory" label="Publish Directory" required />
label="Publish Directory" required />
@else @else
<x-forms.input placeholder="/" id="application.publish_directory" <x-forms.input placeholder="/" id="application.publish_directory" label="Publish Directory" />
label="Publish Directory" />
@endif @endif
@endif @endif
</div> </div>
@if ($this->application->is_github_based() && !$this->application->is_public_repository()) @if ($this->application->is_github_based() && !$this->application->is_public_repository())
<div class="pb-4"> <div class="pb-4">
<x-forms.textarea <x-forms.textarea helper="Gitignore-style rules to filter Git based webhook deployments." placeholder="src/pages/**" id="application.watch_paths" label="Watch Paths" />
helper="Gitignore-style rules to filter Git based webhook deployments."
placeholder="src/pages/**" id="application.watch_paths"
label="Watch Paths" />
</div> </div>
@endif @endif
<x-forms.input <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='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>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" />
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='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>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" />
@if ($application->build_pack !== 'dockercompose') @if ($application->build_pack !== 'dockercompose')
<div class="pt-2 w-96"> <div class="pt-2 w-96">
<x-forms.checkbox <x-forms.checkbox helper="Use a build server to build your application. You can configure your build server in the Server settings. This is experimental. For more info, check the <a href='https://coolify.io/docs/knowledge-base/server/build-server' class='underline' target='_blank'>documentation</a>." instantSave id="application.settings.is_build_server_enabled" label="Use a Build Server? (experimental)" />
helper="Use a build server to build your application. You can configure your build server in the Server settings. This is experimental. For more info, check the <a href='https://coolify.io/docs/knowledge-base/server/build-server' class='underline' target='_blank'>documentation</a>."
instantSave id="application.settings.is_build_server_enabled"
label="Use a Build Server? (experimental)" />
</div> </div>
@endif @endif
@if ($application->could_set_build_commands()) @if ($application->could_set_build_commands())
<div class="w-96"> <div class="w-96">
<x-forms.checkbox instantSave id="application.settings.is_static" <x-forms.checkbox instantSave id="application.settings.is_static" label="Is it a static site?" helper="If your application is a static site or the final build assets should be served as a static site, enable this." />
label="Is it a static site?"
helper="If your application is a static site or the final build assets should be served as a static site, enable this." />
</div> </div>
@endif @endif
@endif @endif
@ -246,30 +235,19 @@
@endif @endif
</div> </div>
@if ($application->build_pack === 'dockercompose') @if ($application->build_pack === 'dockercompose')
<x-forms.button wire:target='initLoadingCompose' <x-forms.button wire:target='initLoadingCompose' x-on:click="$wire.dispatch('loadCompose', false)">Reload Compose File</x-forms.button>
x-on:click="$wire.dispatch('loadCompose', false)">Reload Compose File</x-forms.button>
@if ($application->settings->is_raw_compose_deployment_enabled) @if ($application->settings->is_raw_compose_deployment_enabled)
<x-forms.textarea rows="10" readonly id="application.docker_compose_raw" <x-forms.textarea rows="10" readonly id="application.docker_compose_raw" label="Docker Compose Content (applicationId: {{ $application->id }})" helper="You need to modify the docker compose file." monacoEditorLanguage="yaml" useMonacoEditor />
label="Docker Compose Content (applicationId: {{ $application->id }})"
helper="You need to modify the docker compose file." monacoEditorLanguage="yaml"
useMonacoEditor />
@else @else
<x-forms.textarea rows="10" readonly id="application.docker_compose" <x-forms.textarea rows="10" readonly id="application.docker_compose" label="Docker Compose Content" helper="You need to modify the docker compose file." monacoEditorLanguage="yaml" useMonacoEditor />
label="Docker Compose Content" helper="You need to modify the docker compose file."
monacoEditorLanguage="yaml" useMonacoEditor />
@endif @endif
<div class="w-96"> <div class="w-96">
<x-forms.checkbox label="Escape special characters in labels?" <x-forms.checkbox label="Escape special characters in labels?" helper="By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$.<br><br>If you want to use env variables inside the labels, turn this off." id="application.settings.is_container_label_escape_enabled" instantSave></x-forms.checkbox>
helper="By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$.<br><br>If you want to use env variables inside the labels, turn this off." <x-forms.checkbox label="Readonly labels" helper="If you know what are you doing, you can enable this to edit the labels directly. Coolify won't update labels automatically. <br><br>Be careful, it could break the proxy configuration after you restart the container." id="application.settings.is_container_label_readonly_enabled" instantSave></x-forms.checkbox>
id="application.settings.is_container_label_escape_enabled" instantSave></x-forms.checkbox>
<x-forms.checkbox label="Readonly labels"
helper="If you know what are you doing, you can enable this to edit the labels directly. Coolify won't update labels automatically. <br><br>Be careful, it could break the proxy configuration after you restart the container."
id="application.settings.is_container_label_readonly_enabled" instantSave></x-forms.checkbox>
</div> </div>
@endif @endif
@if ($application->dockerfile) @if ($application->dockerfile)
<x-forms.textarea label="Dockerfile" id="application.dockerfile" monacoEditorLanguage="dockerfile" <x-forms.textarea label="Dockerfile" id="application.dockerfile" monacoEditorLanguage="dockerfile" useMonacoEditor rows="6"> </x-forms.textarea>
useMonacoEditor rows="6"> </x-forms.textarea>
@endif @endif
@if ($application->build_pack !== 'dockercompose') @if ($application->build_pack !== 'dockercompose')
<h3 class="pt-8">Network</h3> <h3 class="pt-8">Network</h3>
@ -277,29 +255,20 @@
@if ($application->settings->is_static || $application->build_pack === 'static') @if ($application->settings->is_static || $application->build_pack === 'static')
<x-forms.input id="application.ports_exposes" label="Ports Exposes" readonly /> <x-forms.input id="application.ports_exposes" label="Ports Exposes" readonly />
@else @else
<x-forms.input placeholder="3000,3001" id="application.ports_exposes" label="Ports Exposes" <x-forms.input placeholder="3000,3001" id="application.ports_exposes" label="Ports Exposes" required helper="A comma separated list of ports your application uses. The first port will be used as default healthcheck port if nothing defined in the Healthcheck menu. Be sure to set this correctly." />
required
helper="A comma separated list of ports your application uses. The first port will be used as default healthcheck port if nothing defined in the Healthcheck menu. Be sure to set this correctly." />
@endif @endif
@if (!$application->destination->server->isSwarm()) @if (!$application->destination->server->isSwarm())
<x-forms.input placeholder="3000:3000" id="application.ports_mappings" label="Ports Mappings" <x-forms.input placeholder="3000:3000" id="application.ports_mappings" label="Ports Mappings" helper="A comma separated list of ports you would like to map to the host system. Useful when you do not want to use domains.<br><br><span class='inline-block font-bold dark:text-warning'>Example:</span><br>3000:3000,3002:3002<br><br>Rolling update is not supported if you have a port mapped to the host." />
helper="A comma separated list of ports you would like to map to the host system. Useful when you do not want to use domains.<br><br><span class='inline-block font-bold dark:text-warning'>Example:</span><br>3000:3000,3002:3002<br><br>Rolling update is not supported if you have a port mapped to the host." />
@endif @endif
</div> </div>
<x-forms.textarea label="Container Labels" rows="15" id="customLabels" <x-forms.textarea label="Container Labels" rows="15" id="customLabels" monacoEditorLanguage="ini" useMonacoEditor></x-forms.textarea>
monacoEditorLanguage="ini" useMonacoEditor></x-forms.textarea>
<div class="w-96"> <div class="w-96">
<x-forms.checkbox label="Escape special characters in labels?" <x-forms.checkbox label="Escape special characters in labels?" helper="By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$.<br><br>If you want to use env variables inside the labels, turn this off." id="application.settings.is_container_label_escape_enabled" instantSave></x-forms.checkbox>
helper="By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$.<br><br>If you want to use env variables inside the labels, turn this off." <x-forms.checkbox label="Readonly labels" helper="If you know what are you doing, you can enable this to edit the labels directly. Last Hour Cloud won't update labels automatically. <br><br>Be careful, it could break the proxy configuration after you restart the container." id="application.settings.is_container_label_readonly_enabled" instantSave></x-forms.checkbox>
id="application.settings.is_container_label_escape_enabled" instantSave></x-forms.checkbox>
<x-forms.checkbox label="Readonly labels"
helper="If you know what are you doing, you can enable this to edit the labels directly. Coolify won't update labels automatically. <br><br>Be careful, it could break the proxy configuration after you restart the container."
id="application.settings.is_container_label_readonly_enabled" instantSave></x-forms.checkbox>
</div> </div>
<x-modal-confirmation buttonFullWidth action="resetDefaultLabels" <x-modal-confirmation buttonFullWidth action="resetDefaultLabels" buttonTitle="Reset to Last Hour Cloud Generated Labels">
buttonTitle="Reset to Coolify Generated Labels"> Are you sure you want to reset the labels to Last Hour Cloud generated labels? <br>It could break the proxy
Are you sure you want to reset the labels to Coolify generated labels? <br>It could break the proxy
configuration after you restart the container. configuration after you restart the container.
</x-modal-confirmation> </x-modal-confirmation>
@ -307,33 +276,25 @@
<h3 class="pt-8">Pre/Post Deployment Commands</h3> <h3 class="pt-8">Pre/Post Deployment Commands</h3>
<div class="flex flex-col gap-2 xl:flex-row"> <div class="flex flex-col gap-2 xl:flex-row">
<x-forms.input x-bind:disabled="initLoadingCompose" placeholder="php artisan migrate" <x-forms.input x-bind:disabled="initLoadingCompose" placeholder="php artisan migrate" id="application.pre_deployment_command" label="Pre-deployment " helper="An optional script or command to execute in the existing container before the deployment begins.<br>It is always executed with 'sh -c', so you do not need add it manually." />
id="application.pre_deployment_command" label="Pre-deployment "
helper="An optional script or command to execute in the existing container before the deployment begins.<br>It is always executed with 'sh -c', so you do not need add it manually." />
@if ($application->build_pack === 'dockercompose') @if ($application->build_pack === 'dockercompose')
<x-forms.input x-bind:disabled="initLoadingCompose" <x-forms.input x-bind:disabled="initLoadingCompose" 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." />
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." />
@endif @endif
</div> </div>
<div class="flex flex-col gap-2 xl:flex-row"> <div class="flex flex-col gap-2 xl:flex-row">
<x-forms.input x-bind:disabled="initLoadingCompose" placeholder="php artisan migrate" <x-forms.input x-bind:disabled="initLoadingCompose" placeholder="php artisan migrate" id="application.post_deployment_command" label="Post-deployment " helper="An optional script or command to execute in the newly built container after the deployment completes.<br>It is always executed with 'sh -c', so you do not need add it manually." />
id="application.post_deployment_command" label="Post-deployment "
helper="An optional script or command to execute in the newly built container after the deployment completes.<br>It is always executed with 'sh -c', so you do not need add it manually." />
@if ($application->build_pack === 'dockercompose') @if ($application->build_pack === 'dockercompose')
<x-forms.input x-bind:disabled="initLoadingCompose" <x-forms.input x-bind:disabled="initLoadingCompose" 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." />
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." />
@endif @endif
</div> </div>
</div> </div>
</form> </form>
@script @script
<script> <script>
$wire.$on('loadCompose', (isInit = true) => { $wire.$on('loadCompose', (isInit = true) => {
$wire.initLoadingCompose = true; $wire.initLoadingCompose = true;
$wire.loadComposeFile(isInit); $wire.loadComposeFile(isInit);
}); });
</script> </script>
@endscript @endscript
</div> </div>

View File

@ -12,30 +12,22 @@
<ol class="flex flex-wrap items-center gap-y-1"> <ol class="flex flex-wrap items-center gap-y-1">
<li class="inline-flex items-center"> <li class="inline-flex items-center">
<div class="flex items-center"> <div class="flex items-center">
<a class="text-xs truncate lg:text-sm" <a class="text-xs truncate lg:text-sm" href="{{ route('project.show', ['project_uuid' => data_get($parameters, 'project_uuid')]) }}">
href="{{ route('project.show', ['project_uuid' => data_get($parameters, 'project_uuid')]) }}">
{{ $project->name }}</a> {{ $project->name }}</a>
<svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor" <svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
<path fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"></path>
</svg> </svg>
</div> </div>
</li> </li>
<li> <li>
<div class="flex items-center"> <div class="flex items-center">
<a class="text-xs truncate lg:text-sm" <a class="text-xs truncate lg:text-sm" href="{{ route('project.resource.index', ['environment_name' => data_get($parameters, 'environment_name'), 'project_uuid' => data_get($parameters, 'project_uuid')]) }}">{{ data_get($parameters, 'environment_name') }}</a>
href="{{ route('project.resource.index', ['environment_name' => data_get($parameters, 'environment_name'), 'project_uuid' => data_get($parameters, 'project_uuid')]) }}">{{ data_get($parameters, 'environment_name') }}</a>
</div> </div>
</li> </li>
<li> <li>
<div class="flex items-center"> <div class="flex items-center">
<svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor" <svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
<path fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"></path>
</svg> </svg>
Edit Edit
</div> </div>

View File

@ -6,9 +6,7 @@
<h2>Docker Compose</h2> <h2>Docker Compose</h2>
<x-forms.button type="submit">Save</x-forms.button> <x-forms.button type="submit">Save</x-forms.button>
</div> </div>
<x-forms.textarea useMonacoEditor monacoEditorLanguage="yaml" label="Docker Compose file" rows="20" <x-forms.textarea useMonacoEditor monacoEditorLanguage="yaml" label="Docker Compose file" rows="20" id="dockerComposeRaw" placeholder='services:
id="dockerComposeRaw"
placeholder='services:
ghost: ghost:
documentation: https://ghost.org/docs/config documentation: https://ghost.org/docs/config
image: ghost:5 image: ghost:5

File diff suppressed because one or more lines are too long

View File

@ -16,37 +16,26 @@
</div> </div>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<div class="flex gap-2"> <div class="flex gap-2">
<x-forms.input label="Name" id="application.human_name" <x-forms.input label="Name" id="application.human_name" placeholder="Human readable name"></x-forms.input>
placeholder="Human readable name"></x-forms.input>
<x-forms.input label="Description" id="application.description"></x-forms.input> <x-forms.input label="Description" id="application.description"></x-forms.input>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
@if (!$application->serviceType()?->contains(str($application->image)->before(':'))) @if (!$application->serviceType()?->contains(str($application->image)->before(':')))
@if ($application->required_fqdn) @if ($application->required_fqdn)
<x-forms.input required placeholder="https://app.coolify.io" label="Domains" <x-forms.input required placeholder="https://app.lasthourhosting.org" label="Domains" id="application.fqdn" helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.lasthourhosting.org,https://cloud.lasthourhosting.org/dashboard<br>- http://app.lasthourhosting.org/api/v3<br>- http://app.lasthourhosting.org:3000 -> app.lasthourhosting.org will point to port 3000 inside the container. "></x-forms.input>
id="application.fqdn"
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. "></x-forms.input>
@else @else
<x-forms.input placeholder="https://app.coolify.io" label="Domains" id="application.fqdn" <x-forms.input placeholder="https://app.lasthourhosting.org" label="Domains" id="application.fqdn" helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.lasthourhosting.org,https://cloud.lasthourhosting.org/dashboard<br>- http://app.lasthourhosting.org/api/v3<br>- http://app.lasthourhosting.org:3000 -> app.lasthourhosting.org will point to port 3000 inside the container. "></x-forms.input>
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. "></x-forms.input>
@endif @endif
@endif @endif
<x-forms.input required <x-forms.input required helper="You can change the image you would like to deploy.<br><br><span class='dark:text-warning'>WARNING. You could corrupt your data. Only do it if you know what you are doing.</span>" label="Image" id="application.image"></x-forms.input>
helper="You can change the image you would like to deploy.<br><br><span class='dark:text-warning'>WARNING. You could corrupt your data. Only do it if you know what you are doing.</span>"
label="Image" id="application.image"></x-forms.input>
</div> </div>
</div> </div>
<h3 class="pt-2">Advanced</h3> <h3 class="pt-2">Advanced</h3>
<div class="w-96"> <div class="w-96">
<x-forms.checkbox instantSave id="application.is_gzip_enabled" label="Enable gzip compression" <x-forms.checkbox instantSave id="application.is_gzip_enabled" label="Enable gzip compression" helper="You can disable gzip compression if you want. Some services are compressing data by default. In this case, you do not need this." />
helper="You can disable gzip compression if you want. Some services are compressing data by default. In this case, you do not need this." /> <x-forms.checkbox instantSave id="application.is_stripprefix_enabled" label="Strip Prefixes" helper="Strip Prefix is used to remove prefixes from paths. Like /api/ to /api." />
<x-forms.checkbox instantSave id="application.is_stripprefix_enabled" label="Strip Prefixes" <x-forms.checkbox instantSave label="Exclude from service status" helper="If you do not need to monitor this resource, enable. Useful if this service is optional." id="application.exclude_from_status"></x-forms.checkbox>
helper="Strip Prefix is used to remove prefixes from paths. Like /api/ to /api." /> <x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings." instantSave="instantSaveAdvanced" id="application.is_log_drain_enabled" label="Drain Logs" />
<x-forms.checkbox instantSave label="Exclude from service status"
helper="If you do not need to monitor this resource, enable. Useful if this service is optional."
id="application.exclude_from_status"></x-forms.checkbox>
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
instantSave="instantSaveAdvanced" id="application.is_log_drain_enabled" label="Drain Logs" />
</div> </div>
</form> </form>
</div> </div>

View File

@ -14,8 +14,7 @@
<x-forms.input id="service.description" label="Description" /> <x-forms.input id="service.description" label="Description" />
</div> </div>
<div class="w-96"> <div class="w-96">
<x-forms.checkbox instantSave id="service.connect_to_docker_network" label="Connect To Predefined Network" <x-forms.checkbox instantSave id="service.connect_to_docker_network" label="Connect To Predefined Network" helper="By default, you do not reach the Last Hour Cloud 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='underline dark:text-white' target='_blank' href='https://coolify.io/docs/knowledge-base/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='underline dark:text-white' target='_blank' href='https://coolify.io/docs/knowledge-base/docker/compose#connect-to-predefined-networks'>this</a>." />
</div> </div>
@if ($fields->count() > 0) @if ($fields->count() > 0)
<div> <div>
@ -23,12 +22,8 @@
</div> </div>
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
@foreach ($fields as $serviceName => $field) @foreach ($fields as $serviceName => $field)
<div class="flex items-center gap-2"><span <div class="flex items-center gap-2"><span class="font-bold">{{ data_get($field, 'serviceName') }}</span>{{ data_get($field, 'name') }}<x-helper helper="Variable name: {{ $serviceName }}" /></div>
class="font-bold">{{ data_get($field, 'serviceName') }}</span>{{ data_get($field, 'name') }}<x-helper <x-forms.input type="{{ data_get($field, 'isPassword') ? 'password' : 'text' }}" required="{{ str(data_get($field, 'rules'))?->contains('required') }}" id="fields.{{ $serviceName }}.value"></x-forms.input>
helper="Variable name: {{ $serviceName }}" /></div>
<x-forms.input type="{{ data_get($field, 'isPassword') ? 'password' : 'text' }}"
required="{{ str(data_get($field, 'rules'))?->contains('required') }}"
id="fields.{{ $serviceName }}.value"></x-forms.input>
@endforeach @endforeach
</div> </div>
@endif @endif

View File

@ -1,13 +1,10 @@
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<h2>Webhooks</h2> <h2>Webhooks</h2>
<x-helper <x-helper helper="For more details goto our <a class='underline dark:text-white' href='https://coolify.io/docs/api-reference/deploy-webhook' target='_blank'>docs</a>." />
helper="For more details goto our <a class='underline dark:text-white' href='https://coolify.io/docs/api-reference/deploy-webhook' target='_blank'>docs</a>." />
</div> </div>
<div> <div>
<x-forms.input readonly <x-forms.input readonly helper="See details in our <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/api-reference/deploy-webhook'>documentation</a>." label="Deploy Webhook (auth required)" id="deploywebhook"></x-forms.input>
helper="See details in our <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/api-reference/deploy-webhook'>documentation</a>."
label="Deploy Webhook (auth required)" id="deploywebhook"></x-forms.input>
</div> </div>
@if ($resource->type() === 'application') @if ($resource->type() === 'application')
<div> <div>
@ -15,11 +12,8 @@
@if ($githubManualWebhook && $gitlabManualWebhook) @if ($githubManualWebhook && $gitlabManualWebhook)
<form wire:submit='submit' class="flex flex-col gap-2"> <form wire:submit='submit' class="flex flex-col gap-2">
<div class="flex items-end gap-2"> <div class="flex items-end gap-2">
<x-forms.input helper="Content Type in GitHub configuration could be json or form-urlencoded." <x-forms.input helper="Content Type in GitHub configuration could be json or form-urlencoded." readonly label="GitHub" id="githubManualWebhook"></x-forms.input>
readonly label="GitHub" id="githubManualWebhook"></x-forms.input> <x-forms.input type="password" helper="Need to set a secret to be able to use this webhook. It should match with the secret in GitHub." label="GitHub Webhook Secret" id="githubManualWebhookSecret"></x-forms.input>
<x-forms.input type="password"
helper="Need to set a secret to be able to use this webhook. It should match with the secret in GitHub."
label="GitHub Webhook Secret" id="githubManualWebhookSecret"></x-forms.input>
</div> </div>
<a target="_blank" class="flex hover:no-underline" href="{{ $resource?->gitWebhook }}"> <a target="_blank" class="flex hover:no-underline" href="{{ $resource?->gitWebhook }}">
@ -29,21 +23,15 @@
</a> </a>
<div class="flex gap-2"> <div class="flex gap-2">
<x-forms.input readonly label="GitLab" id="gitlabManualWebhook"></x-forms.input> <x-forms.input readonly label="GitLab" id="gitlabManualWebhook"></x-forms.input>
<x-forms.input type="password" <x-forms.input type="password" helper="Need to set a secret to be able to use this webhook. It should match with the secret in GitLab." label="GitLab Webhook Secret" id="gitlabManualWebhookSecret"></x-forms.input>
helper="Need to set a secret to be able to use this webhook. It should match with the secret in GitLab."
label="GitLab Webhook Secret" id="gitlabManualWebhookSecret"></x-forms.input>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
<x-forms.input readonly label="Bitbucket" id="bitbucketManualWebhook"></x-forms.input> <x-forms.input readonly label="Bitbucket" id="bitbucketManualWebhook"></x-forms.input>
<x-forms.input type="password" <x-forms.input type="password" helper="Need to set a secret to be able to use this webhook. It should match with the secret in Bitbucket." label="Bitbucket Webhook Secret" id="bitbucketManualWebhookSecret"></x-forms.input>
helper="Need to set a secret to be able to use this webhook. It should match with the secret in Bitbucket."
label="Bitbucket Webhook Secret" id="bitbucketManualWebhookSecret"></x-forms.input>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
<x-forms.input readonly label="Gitea" id="giteaManualWebhook"></x-forms.input> <x-forms.input readonly label="Gitea" id="giteaManualWebhook"></x-forms.input>
<x-forms.input type="password" <x-forms.input type="password" helper="Need to set a secret to be able to use this webhook. It should match with the secret in Gitea." label="Gitea Webhook Secret" id="giteaManualWebhookSecret"></x-forms.input>
helper="Need to set a secret to be able to use this webhook. It should match with the secret in Gitea."
label="Gitea Webhook Secret" id="giteaManualWebhookSecret"></x-forms.input>
</div> </div>
<x-forms.button type="submit">Save</x-forms.button> <x-forms.button type="submit">Save</x-forms.button>
</form> </form>

View File

@ -0,0 +1,48 @@
<div x-data="{ showNotification: @entangle('showNotification') }">
@if ($checkConnection)
@script
<script>
let checkPusherInterval = null;
let checkNumber = 0;
checkPusherInterval = setInterval(() => {
if (window.Echo) {
if (window.Echo.connector.pusher.connection.state !== 'connected') {
checkNumber++;
if (checkNumber > 4) {
@if ($isNotificationEnabled)
$wire.showNotification = true;
@endif
console.error(
'Last Hour Cloud could not connect to the new realtime service introduced in beta.154. This will cause unusual problems on the UI if not fixed! Please check the related upstream documentation (https://coolify.io/docs/cloudflare/tunnels) or get help on Discord (https://coollabs.io/discord).)'
);
clearInterval(checkPusherInterval);
}
} else {
console.log('Last Hour Cloud Realtime Service is connected!');
clearInterval(checkPusherInterval);
}
} else {
@if ($isNotificationEnabled)
$wire.showNotification = true;
@endif
console.error(
'Last Hour Cloud could not connect to the new realtime service introduced in beta.154. This will cause unusual problems on the UI if not fixed! Please check the related upstream documentation (https://coolify.io/docs/cloudflare/tunnels) or get help on Discord (https://coollabs.io/discord).)'
);
clearInterval(checkPusherInterval);
}
}, 1000);
</script>
@endscript
<div class="toast z-[9999]" x-cloak x-show="showNotification">
<div class="flex flex-col text-white border border-red-500 border-dashed rounded alert bg-coolgray-200">
<span><span class="font-bold text-left text-red-500">WARNING: </span>Last Hour Cloud could not connect to the new
realtime service introduced in beta.154. <br>This will cause unusual problems on the UI if not
fixed!<br><br>Please check the
related upstream <a href='https://coolify.io/docs/cloudflare/tunnels' target='_blank'>documentation</a> or get
help on <a href='https://coollabs.io/discord' target='_blank'>Discord</a>.</span>
<x-forms.button class="bg-coolgray-400" wire:click='disable'>Acknowledge the problem and disable this
popup</x-forms.button>
</div>
</div>
@endif
</div>

View File

@ -1,9 +1,9 @@
<div> <div>
@if ($server->id !== 0) @if ($server->id !== 0)
<h2 class="pt-4">Danger Zone</h2> <h2 class="pt-4">Danger Zone</h2>
<div class="">Woah. I hope you know what are you doing.</div> <div class="">Wait a minute, We hope you know what are you doing.</div>
<h4 class="pt-4">Delete Server</h4> <h4 class="pt-4">Delete Server</h4>
<div class="pb-4">This will remove this server from Coolify. Beware! There is no coming <div class="pb-4">This will remove this server from Last Hour Cloud. Beware! There is no coming
back! back!
</div> </div>
@if ($server->definedResources()->count() > 0) @if ($server->definedResources()->count() > 0)

View File

@ -4,7 +4,7 @@
<h2>General</h2> <h2>General</h2>
@if ($server->id === 0) @if ($server->id === 0)
<x-modal-confirmation buttonTitle="Save" title="Change Localhost" action="submit"> <x-modal-confirmation buttonTitle="Save" title="Change Localhost" action="submit">
You could lose a lot of functionalities if you change the server details of the server where Coolify You could lose a lot of functionalities if you change the server details of the server where Last Hour Cloud
is is
running on.<br>Please think again. running on.<br>Please think again.
</x-modal-confirmation> </x-modal-confirmation>
@ -34,9 +34,7 @@
<x-slot:content> <x-slot:content>
<livewire:server.validate-and-install :server="$server" /> <livewire:server.validate-and-install :server="$server" />
</x-slot:content> </x-slot:content>
<x-forms.button @click="slideOverOpen=true" <x-forms.button @click="slideOverOpen=true" class="w-full mt-8 mb-4 font-bold box-without-bg bg-coollabs hover:bg-coollabs-100" wire:click.prevent='validateServer' isHighlighted>
class="w-full mt-8 mb-4 font-bold box-without-bg bg-coollabs hover:bg-coollabs-100"
wire:click.prevent='validateServer' isHighlighted>
Validate Server & Install Docker Engine Validate Server & Install Docker Engine
</x-forms.button> </x-forms.button>
</x-slide-over> </x-slide-over>
@ -48,8 +46,7 @@
@endif @endif
@endif @endif
@if ((!$server->settings->is_reachable || !$server->settings->is_usable) && $server->id === 0) @if ((!$server->settings->is_reachable || !$server->settings->is_usable) && $server->id === 0)
<x-forms.button class="mt-8 mb-4 font-bold box-without-bg bg-coollabs hover:bg-coollabs-100" <x-forms.button class="mt-8 mb-4 font-bold box-without-bg bg-coollabs hover:bg-coollabs-100" wire:click.prevent='checkLocalhostConnection' isHighlighted>
wire:click.prevent='checkLocalhostConnection' isHighlighted>
Validate Server Validate Server
</x-forms.button> </x-forms.button>
@endif @endif
@ -62,14 +59,12 @@
<x-forms.input id="server.name" label="Name" required /> <x-forms.input id="server.name" label="Name" required />
<x-forms.input id="server.description" label="Description" /> <x-forms.input id="server.description" label="Description" />
@if (!$server->settings->is_swarm_worker && !$server->settings->is_build_server) @if (!$server->settings->is_swarm_worker && !$server->settings->is_build_server)
<x-forms.input placeholder="https://example.com" id="wildcard_domain" label="Wildcard Domain" <x-forms.input placeholder="https://example.com" id="wildcard_domain" label="Wildcard Domain" helper='A wildcard domain allows you to receive a randomly generated domain for your new applications. <br><br>For instance, if you set "https://example.com" as your wildcard domain, your applications will receive domains like "https://randomId.example.com".' />
helper='A wildcard domain allows you to receive a randomly generated domain for your new applications. <br><br>For instance, if you set "https://example.com" as your wildcard domain, your applications will receive domains like "https://randomId.example.com".' />
@endif @endif
</div> </div>
<div class="flex flex-col w-full gap-2 lg:flex-row"> <div class="flex flex-col w-full gap-2 lg:flex-row">
<x-forms.input id="server.ip" label="IP Address/Domain" <x-forms.input id="server.ip" label="IP Address/Domain" helper="An IP Address (127.0.0.1) or domain (example.com)." required />
helper="An IP Address (127.0.0.1) or domain (example.com)." required />
<div class="flex gap-2"> <div class="flex gap-2">
<x-forms.input id="server.user" label="User" required /> <x-forms.input id="server.user" label="User" required />
<x-forms.input type="number" id="server.port" label="Port" required /> <x-forms.input type="number" id="server.port" label="Port" required />
@ -78,13 +73,11 @@
<div class="w-64"> <div class="w-64">
@if ($server->isFunctional()) @if ($server->isFunctional())
@if (!$server->isLocalhost()) @if (!$server->isLocalhost())
<x-forms.checkbox instantSave id="server.settings.is_build_server" <x-forms.checkbox instantSave id="server.settings.is_build_server" label="Use it as a build server?" />
label="Use it as a build server?" />
<div class="flex items-center gap-1 pt-6"> <div class="flex items-center gap-1 pt-6">
<h3 class="">Cloudflare Tunnels <h3 class="">Cloudflare Tunnels
</h3> </h3>
<x-helper class="inline-flex" <x-helper class="inline-flex" helper="If you are using Cloudflare Tunnels, enable this. It will proxy all SSH requests to your server through Cloudflare.<br><span class='dark:text-warning'>Last Hour Cloud does not install or set up Cloudflare (cloudflared) on your server.</span>" />
helper="If you are using Cloudflare Tunnels, enable this. It will proxy all SSH requests to your server through Cloudflare.<br><span class='dark:text-warning'>Coolify does not install or set up Cloudflare (cloudflared) on your server.</span>" />
</div> </div>
@if ($server->settings->is_cloudflare_tunnel) @if ($server->settings->is_cloudflare_tunnel)
<x-forms.checkbox instantSave id="server.settings.is_cloudflare_tunnel" label="Enabled" /> <x-forms.checkbox instantSave id="server.settings.is_cloudflare_tunnel" label="Enabled" />
@ -95,29 +88,18 @@
@endif @endif
@if (!$server->isBuildServer()) @if (!$server->isBuildServer())
<h3 class="pt-6">Swarm <span class="text-xs text-neutral-500">(experimental)</span></h3> <h3 class="pt-6">Swarm <span class="text-xs text-neutral-500">(experimental)</span></h3>
<div class="pb-4">Read the docs <a class='underline dark:text-white' <div class="pb-4">Read the docs <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>.
href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>.
</div> </div>
@if ($server->settings->is_swarm_worker) @if ($server->settings->is_swarm_worker)
<x-forms.checkbox disabled instantSave type="checkbox" <x-forms.checkbox disabled instantSave type="checkbox" id="server.settings.is_swarm_manager" helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>." label="Is it a Swarm Manager?" />
id="server.settings.is_swarm_manager"
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Manager?" />
@else @else
<x-forms.checkbox instantSave type="checkbox" id="server.settings.is_swarm_manager" <x-forms.checkbox instantSave type="checkbox" id="server.settings.is_swarm_manager" helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>." label="Is it a Swarm Manager?" />
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Manager?" />
@endif @endif
@if ($server->settings->is_swarm_manager) @if ($server->settings->is_swarm_manager)
<x-forms.checkbox disabled instantSave type="checkbox" <x-forms.checkbox disabled instantSave type="checkbox" id="server.settings.is_swarm_worker" helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>." label="Is it a Swarm Worker?" />
id="server.settings.is_swarm_worker"
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Worker?" />
@else @else
<x-forms.checkbox instantSave type="checkbox" id="server.settings.is_swarm_worker" <x-forms.checkbox instantSave type="checkbox" id="server.settings.is_swarm_worker" helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>." label="Is it a Swarm Worker?" />
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Worker?" />
@endif @endif
@endif @endif
@endif @endif
@ -125,8 +107,7 @@
<div class="flex items-center gap-1 pt-6"> <div class="flex items-center gap-1 pt-6">
<h3 class="">Cloudflare Tunnels <h3 class="">Cloudflare Tunnels
</h3> </h3>
<x-helper class="inline-flex" <x-helper class="inline-flex" helper="If you are using Cloudflare Tunnels, enable this. It will proxy all SSH requests to your server through Cloudflare.<br><span class='dark:text-warning'>Coolify does not install or set up Cloudflare (cloudflared) on your server.</span>" />
helper="If you are using Cloudflare Tunnels, enable this. It will proxy all SSH requests to your server through Cloudflare.<br><span class='dark:text-warning'>Coolify does not install or set up Cloudflare (cloudflared) on your server.</span>" />
</div> </div>
@if ($server->settings->is_cloudflare_tunnel) @if ($server->settings->is_cloudflare_tunnel)
<x-forms.checkbox instantSave id="server.settings.is_cloudflare_tunnel" label="Enabled" /> <x-forms.checkbox instantSave id="server.settings.is_cloudflare_tunnel" label="Enabled" />
@ -146,27 +127,18 @@
<div class="flex flex-col flex-wrap gap-2 sm:flex-nowrap"> <div class="flex flex-col flex-wrap gap-2 sm:flex-nowrap">
@if ($server->settings->is_force_cleanup_enabled) @if ($server->settings->is_force_cleanup_enabled)
<div class="w-64"> <div class="w-64">
<x-forms.checkbox <x-forms.checkbox helper="This will cleanup build caches / unused images / etc every 10 minutes." instantSave id="server.settings.is_force_cleanup_enabled" label="Force Cleanup Docker Engine" />
helper="This will cleanup build caches / unused images / etc every 10 minutes."
instantSave id="server.settings.is_force_cleanup_enabled"
label="Force Cleanup Docker Engine" />
</div> </div>
@else @else
<x-forms.input id="cleanup_after_percentage" label="Disk cleanup threshold (%)" required <x-forms.input id="cleanup_after_percentage" label="Disk cleanup threshold (%)" required helper="The disk cleanup task will run when the disk usage exceeds this threshold." />
helper="The disk cleanup task will run when the disk usage exceeds this threshold." />
<div class="w-64"> <div class="w-64">
<x-forms.checkbox <x-forms.checkbox helper="This will cleanup build caches / unused images / etc every 10 minutes." instantSave id="server.settings.is_force_cleanup_enabled" label="Force Cleanup Docker Engine" />
helper="This will cleanup build caches / unused images / etc every 10 minutes."
instantSave id="server.settings.is_force_cleanup_enabled"
label="Force Cleanup Docker Engine" />
</div> </div>
@endif @endif
</div> </div>
<div class="flex flex-wrap gap-2 sm:flex-nowrap"> <div class="flex flex-wrap gap-2 sm:flex-nowrap">
<x-forms.input id="server.settings.concurrent_builds" label="Number of concurrent builds" required <x-forms.input id="server.settings.concurrent_builds" label="Number of concurrent builds" required helper="You can specify the number of simultaneous build processes/deployments that should run concurrently." />
helper="You can specify the number of simultaneous build processes/deployments that should run concurrently." /> <x-forms.input id="server.settings.dynamic_timeout" label="Deployment timeout (seconds)" required helper="You can define the maximum duration for a deployment to run before timing it out." />
<x-forms.input id="server.settings.dynamic_timeout" label="Deployment timeout (seconds)" required
helper="You can define the maximum duration for a deployment to run before timing it out." />
</div> </div>
</div> </div>
<div class="flex items-center gap-2 pt-4 pb-2"> <div class="flex items-center gap-2 pt-4 pb-2">

View File

@ -35,7 +35,7 @@
& &
@endif @endif
@if (!$server->settings->is_usable) @if (!$server->settings->is_usable)
<span>Not usable by Coolify</span> <span>Not usable by Last Hour Cloud</span>
@endif @endif
@if ($server->settings->force_disabled) @if ($server->settings->force_disabled)
<span>Disabled by the system</span> <span>Disabled by the system</span>

View File

@ -8,14 +8,11 @@
<x-forms.input id="description" label="Description" /> <x-forms.input id="description" label="Description" />
</div> </div>
<div class="flex gap-2 flex-wrap sm:flex-nowrap"> <div class="flex gap-2 flex-wrap sm:flex-nowrap">
<x-forms.input id="ip" label="IP Address/Domain" required <x-forms.input id="ip" label="IP Address/Domain" required helper="An IP Address (127.0.0.1) or domain (example.com)." />
helper="An IP Address (127.0.0.1) or domain (example.com)." />
<x-forms.input type="number" id="port" label="Port" required /> <x-forms.input type="number" id="port" label="Port" required />
</div> </div>
<x-forms.input id="user" label="User" required /> <x-forms.input id="user" label="User" required />
<div class="text-xs dark:text-warning text-coollabs ">Non-root user is experimental: <a <div class="text-xs dark:text-warning text-coollabs ">Non-root user is experimental: <a class="font-bold underline" target="_blank" href="https://coolify.io/docs/knowledge-base/server/non-root-user">docs</a>.</div>
class="font-bold underline" target="_blank"
href="https://coolify.io/docs/knowledge-base/server/non-root-user">docs</a>.</div>
<x-forms.select label="Private Key" id="private_key_id"> <x-forms.select label="Private Key" id="private_key_id">
<option disabled>Select a private key</option> <option disabled>Select a private key</option>
@foreach ($private_keys as $key) @foreach ($private_keys as $key)
@ -31,25 +28,16 @@
</div> </div>
<div class=""> <div class="">
<h3 class="pt-6">Swarm <span class="text-xs text-neutral-500">(experimental)</span></h3> <h3 class="pt-6">Swarm <span class="text-xs text-neutral-500">(experimental)</span></h3>
<div class="pb-4">Read the docs <a class='dark:text-white' <div class="pb-4">Read the docs <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>.</div>
href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>.</div>
@if ($is_swarm_worker || $is_build_server) @if ($is_swarm_worker || $is_build_server)
<x-forms.checkbox disabled instantSave type="checkbox" id="is_swarm_manager" <x-forms.checkbox disabled instantSave type="checkbox" id="is_swarm_manager" helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>." label="Is it a Swarm Manager?" />
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Manager?" />
@else @else
<x-forms.checkbox type="checkbox" instantSave id="is_swarm_manager" <x-forms.checkbox type="checkbox" instantSave id="is_swarm_manager" helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>." label="Is it a Swarm Manager?" />
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Manager?" />
@endif @endif
@if ($is_swarm_manager || $is_build_server) @if ($is_swarm_manager || $is_build_server)
<x-forms.checkbox disabled instantSave type="checkbox" id="is_swarm_worker" <x-forms.checkbox disabled instantSave type="checkbox" id="is_swarm_worker" helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>." label="Is it a Swarm Worker?" />
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Worker?" />
@else @else
<x-forms.checkbox type="checkbox" instantSave id="is_swarm_worker" <x-forms.checkbox type="checkbox" instantSave id="is_swarm_worker" helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>." label="Is it a Swarm Worker?" />
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Worker?" />
@endif @endif
@if ($is_swarm_worker && count($swarm_managers) > 0) @if ($is_swarm_worker && count($swarm_managers) > 0)
<div class="py-4"> <div class="py-4">

View File

@ -32,7 +32,7 @@
configurations. configurations.
</div> </div>
@endif @endif
<x-forms.input placeholder="https://app.coolify.io" id="redirect_url" label="Default Redirect 404" <x-forms.input placeholder="https://lasthourhosting.org" id="redirect_url" label="Default Redirect 404"
helper="All urls that has no service available will be redirected to this domain." /> helper="All urls that has no service available will be redirected to this domain." />
<div wire:loading wire:target="loadProxyConfiguration" class="pt-4"> <div wire:loading wire:target="loadProxyConfiguration" class="pt-4">
<x-loading text="Loading proxy configuration..." /> <x-loading text="Loading proxy configuration..." />

View File

@ -5,10 +5,8 @@
<x-server.navbar :server="$server" :parameters="$parameters" /> <x-server.navbar :server="$server" :parameters="$parameters" />
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'managed' }" class="flex flex-col h-full gap-8 md:flex-row"> <div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'managed' }" class="flex flex-col h-full gap-8 md:flex-row">
<div class="flex flex-row gap-4 md:flex-col"> <div class="flex flex-row gap-4 md:flex-col">
<a :class="activeTab === 'managed' && 'dark:text-white'" <a :class="activeTab === 'managed' && 'dark:text-white'" @click.prevent="activeTab = 'managed'; window.location.hash = 'managed'" href="#">Managed</a>
@click.prevent="activeTab = 'managed'; window.location.hash = 'managed'" href="#">Managed</a> <a :class="activeTab === 'unmanaged' && 'dark:text-white'" @click.prevent="activeTab = 'unmanaged'; window.location.hash = 'unmanaged'" href="#">Unmanaged</a>
<a :class="activeTab === 'unmanaged' && 'dark:text-white'"
@click.prevent="activeTab = 'unmanaged'; window.location.hash = 'unmanaged'" href="#">Unmanaged</a>
</div> </div>
<div class="w-full"> <div class="w-full">
<div x-cloak x-show="activeTab === 'managed'" class="h-full"> <div x-cloak x-show="activeTab === 'managed'" class="h-full">
@ -17,7 +15,7 @@
<h2>Resources</h2> <h2>Resources</h2>
<x-forms.button wire:click="refreshStatus">Refresh</x-forms.button> <x-forms.button wire:click="refreshStatus">Refresh</x-forms.button>
</div> </div>
<div class="subtitle">Here you can find all resources that are managed by Coolify.</div> <div class="subtitle">Here you can find all resources that are managed by Last Hour Cloud.</div>
</div> </div>
@if ($server->definedResources()->count() > 0) @if ($server->definedResources()->count() > 0)
<div class="flex flex-col"> <div class="flex flex-col">
@ -54,16 +52,15 @@
{{ data_get($resource, 'environment.name') }} {{ data_get($resource, 'environment.name') }}
</td> </td>
<td class="px-5 py-4 text-sm whitespace-nowrap hover:underline"> <td class="px-5 py-4 text-sm whitespace-nowrap hover:underline">
<a class="" <a class="" href="{{ $resource->link() }}">{{ $resource->name }}
href="{{ $resource->link() }}">{{ $resource->name }}
<x-internal-link /></a> <x-internal-link /></a>
</td> </td>
<td class="px-5 py-4 text-sm whitespace-nowrap"> <td class="px-5 py-4 text-sm whitespace-nowrap">
{{ str($resource->type())->headline() }}</td> {{ str($resource->type())->headline() }}
</td>
<td class="px-5 py-4 text-sm font-medium whitespace-nowrap"> <td class="px-5 py-4 text-sm font-medium whitespace-nowrap">
@if ($resource->type() === 'service') @if ($resource->type() === 'service')
<x-status.services :service="$resource" <x-status.services :service="$resource" :showRefreshButton="false" />
:showRefreshButton="false" />
@else @else
<x-status.index :resource="$resource" :showRefreshButton="false" /> <x-status.index :resource="$resource" :showRefreshButton="false" />
@endif @endif
@ -127,20 +124,12 @@
</td> </td>
<td class="flex gap-2 px-5 py-4 text-sm whitespace-nowrap"> <td class="flex gap-2 px-5 py-4 text-sm whitespace-nowrap">
@if (data_get($resource, 'State') === 'running') @if (data_get($resource, 'State') === 'running')
<x-forms.button <x-forms.button wire:click="restartUnmanaged('{{ data_get($resource, 'ID') }}')" wire:key="{{ data_get($resource, 'ID') }}">Restart</x-forms.button>
wire:click="restartUnmanaged('{{ data_get($resource, 'ID') }}')" <x-forms.button isError wire:click="stopUnmanaged('{{ data_get($resource, 'ID') }}')" wire:key="{{ data_get($resource, 'ID') }}">Stop</x-forms.button>
wire:key="{{ data_get($resource, 'ID') }}">Restart</x-forms.button>
<x-forms.button isError
wire:click="stopUnmanaged('{{ data_get($resource, 'ID') }}')"
wire:key="{{ data_get($resource, 'ID') }}">Stop</x-forms.button>
@elseif (data_get($resource, 'State') === 'exited') @elseif (data_get($resource, 'State') === 'exited')
<x-forms.button <x-forms.button wire:click="startUnmanaged('{{ data_get($resource, 'ID') }}')" wire:key="{{ data_get($resource, 'ID') }}">Start</x-forms.button>
wire:click="startUnmanaged('{{ data_get($resource, 'ID') }}')"
wire:key="{{ data_get($resource, 'ID') }}">Start</x-forms.button>
@elseif (data_get($resource, 'State') === 'restarting') @elseif (data_get($resource, 'State') === 'restarting')
<x-forms.button <x-forms.button wire:click="stopUnmanaged('{{ data_get($resource, 'ID') }}')" wire:key="{{ data_get($resource, 'ID') }}">Stop</x-forms.button>
wire:click="stopUnmanaged('{{ data_get($resource, 'ID') }}')"
wire:key="{{ data_get($resource, 'ID') }}">Stop</x-forms.button>
@endif @endif
</td> </td>
</tr> </tr>

View File

@ -8,7 +8,7 @@
</x-forms.button> </x-forms.button>
@endif @endif
</div> </div>
<div class="pb-4">Backup your Coolify instance settings</div> <div class="pb-4">Backup your Last Hour Cloud instance settings</div>
<div> <div>
@if (isset($database)) @if (isset($database))
<div class="flex flex-col gap-3 pb-4"> <div class="flex flex-col gap-3 pb-4">
@ -24,8 +24,8 @@
</div> </div>
<livewire:project.database.backup-edit :backup="$backup" :s3s="$s3s" :status="data_get($database, 'status')" /> <livewire:project.database.backup-edit :backup="$backup" :s3s="$s3s" :status="data_get($database, 'status')" />
@else @else
To configure automatic backup for your Coolify instance, you first need to add a database resource To configure automatic backup for your Last Hour Cloud instance, you first need to add a database resource
into Coolify. into Last Hour Cloud.
<x-forms.button class="mt-2" wire:click="add_coolify_database">Add Database</x-forms.button> <x-forms.button class="mt-2" wire:click="add_coolify_database">Add Database</x-forms.button>
@endif @endif
</div> </div>

View File

@ -6,15 +6,13 @@
Save Save
</x-forms.button> </x-forms.button>
</div> </div>
<div>General configuration for your Coolify instance.</div> <div>General configuration for your Last Hour Cloud instance.</div>
<div class="flex flex-col gap-2 pt-4"> <div class="flex flex-col gap-2 pt-4">
<div class="flex flex-wrap items-end gap-2"> <div class="flex flex-wrap items-end gap-2">
<x-forms.input id="settings.fqdn" label="Instance's Domain" placeholder="https://coolify.io" /> <x-forms.input id="settings.fqdn" label="Instance's Domain" placeholder="https://lasthourhosting.org" />
<x-forms.input id="settings.instance_name" label="Instance's Name" placeholder="Coolify" /> <x-forms.input id="settings.instance_name" label="Instance's Name" placeholder="Last Hour Cloud" />
<x-forms.input id="settings.custom_dns_servers" label="DNS Servers" <x-forms.input id="settings.custom_dns_servers" label="DNS Servers" helper="DNS servers for validation FQDNs againts. A comma separated list of DNS servers." placeholder="9.9.9.9,149.112.112.112" />
helper="DNS servers for validation FQDNs againts. A comma separated list of DNS servers."
placeholder="1.1.1.1,8.8.8.8" />
<div class="md:w-96"> <div class="md:w-96">
<x-forms.checkbox instantSave id="is_dns_validation_enabled" label="Validate DNS settings?" /> <x-forms.checkbox instantSave id="is_dns_validation_enabled" label="Validate DNS settings?" />
</div> </div>
@ -30,18 +28,15 @@
<div class="md:w-96"> <div class="md:w-96">
<x-forms.checkbox instantSave id="is_api_enabled" label="Enabled" /> <x-forms.checkbox instantSave id="is_api_enabled" label="Enabled" />
</div> </div>
<x-forms.input id="settings.allowed_ips" label="Allowed IPs" <x-forms.input id="settings.allowed_ips" label="Allowed IPs" helper="Allowed IP lists for the API. A comma separated list of IPs. Empty means you allow from everywhere." placeholder="1.1.1.1,8.8.8.8" />
helper="Allowed IP lists for the API. A comma separated list of IPs. Empty means you allow from everywhere."
placeholder="1.1.1.1,8.8.8.8" />
</form> </form>
<h2 class="pt-6">Advanced</h2> <h2 class="pt-6">Advanced</h2>
<div class="text-right md:w-96"> <div class="text-right md:w-96">
@if (!is_null(env('AUTOUPDATE', null))) @if (!is_null(env('AUTOUPDATE', null)))
<x-forms.checkbox instantSave helper="AUTOUPDATE is set in .env file, you need to modify it there." disabled <x-forms.checkbox instantSave helper="AUTOUPDATE is set in .env file, you need to modify it there." disabled id="is_auto_update_enabled" label="Auto Update Last Hour Cloud" />
id="is_auto_update_enabled" label="Auto Update Coolify" />
@else @else
<x-forms.checkbox instantSave id="is_auto_update_enabled" label="Auto Update Coolify" /> <x-forms.checkbox instantSave id="is_auto_update_enabled" label="Auto Update Last Hour Cloud" />
@endif @endif
<x-forms.checkbox instantSave id="is_registration_enabled" label="Registration Allowed" /> <x-forms.checkbox instantSave id="is_registration_enabled" label="Registration Allowed" />
<x-forms.checkbox instantSave id="do_not_track" label="Do Not Track" /> <x-forms.checkbox instantSave id="do_not_track" label="Do Not Track" />

View File

@ -27,28 +27,40 @@
<div class="subtitle">Your Private GitHub App for private repositories.</div> <div class="subtitle">Your Private GitHub App for private repositories.</div>
@if (!data_get($github_app, 'installation_id')) @if (!data_get($github_app, 'installation_id'))
<div class="mb-10 rounded alert-error"> <div class="mb-10 rounded alert-error">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none" <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none" viewBox="0 0 24 24">
viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg> </svg>
<span>You must complete this step before you can use this source!</span> <span>You must complete this step before you can use this source!</span>
</div> </div>
<a class="items-center justify-center box" href="{{ get_installation_path($github_app) }}"> <a class="items-center justify-center box" href="{{ get_installation_path($github_app) }}">
Install Repositories on GitHub Install Repositories on GitHub
</a> </a>
@else @endif
<div class="flex flex-col gap-2"> <x-new-modal isErrorButton buttonTitle="Delete">
This source will be deleted. It is not reversible. <br>Please think again.
</x-new-modal>
</div>
</div>
<div class="subtitle">Your Private GitHub App for private repositories.</div>
@if (!data_get($github_app, 'installation_id'))
<div class="mb-10 rounded alert alert-warning">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<span>You must complete this step before you can use this source!</span>
</div>
<a class="items-center justify-center box" href="{{ get_installation_path($github_app) }}">
Install Repositories on GitHub
</a>
@else
<div class="flex flex-col gap-2">
<div class="flex gap-2"> <div class="flex gap-2">
<x-forms.input id="github_app.name" label="App Name" disabled /> <x-forms.input id="github_app.name" label="App Name" disabled />
<x-forms.input id="github_app.organization" label="Organization" disabled <x-forms.input id="github_app.organization" label="Organization" disabled placeholder="If empty, personal user will be used" />
placeholder="If empty, personal user will be used" />
</div> </div>
@if (!isCloud()) @if (!isCloud())
<div class="w-48"> <div class="w-48">
<x-forms.checkbox label="System Wide?" <x-forms.checkbox label="System Wide?" helper="If checked, this GitHub App will be available for everyone in this Last Hour Cloud instance." instantSave id="github_app.is_system_wide" />
helper="If checked, this GitHub App will be available for everyone in this Coolify instance."
instantSave id="github_app.is_system_wide" />
</div> </div>
@endif @endif
<div class="flex gap-2"> <div class="flex gap-2">
@ -66,8 +78,7 @@
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
<x-forms.input type="number" id="github_app.app_id" label="App Id" disabled /> <x-forms.input type="number" id="github_app.app_id" label="App Id" disabled />
<x-forms.input type="number" id="github_app.installation_id" label="Installation Id" <x-forms.input type="number" id="github_app.installation_id" label="Installation Id" disabled />
disabled />
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
<x-forms.input id="github_app.client_id" label="Client Id" type="password" disabled /> <x-forms.input id="github_app.client_id" label="Client Id" type="password" disabled />
@ -85,17 +96,21 @@
</a> </a>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
<x-forms.input id="github_app.contents" helper="read - mandatory." label="Content" readonly <x-forms.input id="github_app.contents" helper="read - mandatory." label="Content" readonly placeholder="N/A" />
placeholder="N/A" /> <x-forms.input id="github_app.metadata" helper="read - mandatory." label="Metadata" readonly placeholder="N/A" />
<x-forms.input id="github_app.metadata" helper="read - mandatory." label="Metadata" readonly
placeholder="N/A" />
{{-- <x-forms.input id="github_app.administration" {{-- <x-forms.input id="github_app.administration"
helper="read:write access needed to setup servers as GitHub Runner." label="Administration" helper="read:write access needed to setup servers as GitHub Runner." label="Administration"
readonly placeholder="N/A" /> --}} readonly placeholder="N/A" /> --}}
<x-forms.input id="github_app.pull_requests" <x-forms.input id="github_app.pull_requests" helper="write access needed to use deployment status update in previews." label="Pull Request" readonly placeholder="N/A" />
helper="write access needed to use deployment status update in previews."
label="Pull Request" readonly placeholder="N/A" />
</div> </div>
</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>
@endif @endif
</form> </form>
@ -136,13 +151,12 @@
<td class="px-5 py-4 text-sm whitespace-nowrap"> <td class="px-5 py-4 text-sm whitespace-nowrap">
{{ data_get($resource, 'environment.name') }} {{ data_get($resource, 'environment.name') }}
</td> </td>
<td class="px-5 py-4 text-sm whitespace-nowrap"><a <td class="px-5 py-4 text-sm whitespace-nowrap"><a class="" href="{{ $resource->link() }}">{{ $resource->name }}
class=""
href="{{ $resource->link() }}">{{ $resource->name }}
<x-internal-link /></a> <x-internal-link /></a>
</td> </td>
<td class="px-5 py-4 text-sm whitespace-nowrap"> <td class="px-5 py-4 text-sm whitespace-nowrap">
{{ str($resource->type())->headline() }}</td> {{ str($resource->type())->headline() }}
</td>
</tr> </tr>
@empty @empty
@endforelse @endforelse
@ -166,27 +180,16 @@
</div> </div>
</div> </div>
<div class="mb-10 rounded alert-error"> <div class="mb-10 rounded alert-error">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none" <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none" viewBox="0 0 24 24">
viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg> </svg>
<span>You must complete this step before you can use this source!</span> <span>You must complete this step before you can use this source!</span>
</div> </div>
<div class="flex flex-col">
<div class="flex gap-2">
<h2>Register a GitHub App</h2>
<x-forms.button class="bg-coollabs hover:bg-coollabs-100"
x-on:click.prevent="createGithubApp('{{ $webhook_endpoint }}','{{ $preview_deployment_permissions }}',{{ $administration }})">
Register Now
</x-forms.button>
</div>
<div>You need to register a GitHub App before using this source.</div> <div>You need to register a GitHub App before using this source.</div>
<div class="py-10"> <div class="py-10">
@if (!isCloud() || isDev()) @if (!isCloud() || isDev())
<div class="flex items-end gap-2"> <div class="flex items-end gap-2">
<x-forms.select wire:model.live='webhook_endpoint' label="Webhook Endpoint" <x-forms.select wire:model.live='webhook_endpoint' label="Webhook Endpoint" helper="All Git webhooks will be sent to this endpoint. <br><br>If you would like to use domain instead of IP address, set your Last Hour Cloud instance's FQDN in the Settings menu.">
helper="All Git webhooks will be sent to this endpoint. <br><br>If you would like to use domain instead of IP address, set your Coolify instance's FQDN in the Settings menu.">
@if ($ipv4) @if ($ipv4)
<option value="{{ $ipv4 }}">Use {{ $ipv4 }}</option> <option value="{{ $ipv4 }}">Use {{ $ipv4 }}</option>
@endif @endif
@ -203,15 +206,12 @@
</div> </div>
@endif @endif
<div class="flex flex-col gap-2 pt-4 w-96"> <div class="flex flex-col gap-2 pt-4 w-96">
<x-forms.checkbox disabled instantSave id="default_permissions" label="Mandatory" <x-forms.checkbox disabled instantSave id="default_permissions" label="Mandatory" helper="Contents: read<br>Metadata: read<br>Email: read" />
helper="Contents: read<br>Metadata: read<br>Email: read" /> <x-forms.checkbox instantSave id="preview_deployment_permissions" label="Preview Deployments " helper="Necessary for updating pull requests with useful comments (deployment status, links, etc.)<br><br>Pull Request: read & write" />
<x-forms.checkbox instantSave id="preview_deployment_permissions" label="Preview Deployments "
helper="Necessary for updating pull requests with useful comments (deployment status, links, etc.)<br><br>Pull Request: read & write" />
{{-- <x-forms.checkbox instantSave id="administration" label="Administration (for Github Runners)" {{-- <x-forms.checkbox instantSave id="administration" label="Administration (for Github Runners)"
helper="Necessary for adding Github Runners to repositories.<br><br>Administration: read & write" /> --}} helper="Necessary for adding Github Runners to repositories.<br><br>Administration: read & write" /> --}}
</div> </div>
</div> </div>
</div>
<script> <script>
function createGithubApp(webhook_endpoint, preview_deployment_permissions, administration) { function createGithubApp(webhook_endpoint, preview_deployment_permissions, administration) {
const { const {
@ -273,6 +273,48 @@
document.getElementsByTagName('body')[0].appendChild(form); document.getElementsByTagName('body')[0].appendChild(form);
form.submit(); form.submit();
} }
const webhookBaseUrl = `${baseUrl}/webhooks`;
const path = organization ? `organizations/${organization}/settings/apps/new` : 'settings/apps/new';
const default_permissions = {
contents: 'read',
metadata: 'read',
emails: 'read',
administration: 'read'
};
if (preview_deployment_permissions) {
default_permissions.pull_requests = 'write';
}
if (administration) {
default_permissions.administration = 'write';
}
const data = {
name,
url: baseUrl,
hook_attributes: {
url: `${webhookBaseUrl}/source/github/events`,
active: true,
},
redirect_url: `${webhookBaseUrl}/source/github/redirect`,
callback_urls: [`${baseUrl}/login/github/app`],
public: false,
request_oauth_on_install: false,
setup_url: `${webhookBaseUrl}/source/github/install?source=${uuid}`,
setup_on_update: true,
default_permissions,
default_events: ['pull_request', 'push']
};
const form = document.createElement('form');
form.setAttribute('method', 'post');
form.setAttribute('action', `${html_url}/${path}?state=${uuid}`);
const input = document.createElement('input');
input.setAttribute('id', 'manifest');
input.setAttribute('name', 'manifest');
input.setAttribute('type', 'hidden');
input.setAttribute('value', JSON.stringify(data));
form.appendChild(input);
document.getElementsByTagName('body')[0].appendChild(form);
form.submit();
}
</script> </script>
@endif @endif
</div> </div>

View File

@ -0,0 +1,27 @@
<div>
<x-team.navbar />
<div class="flex gap-2">
<h2>Shared Variables</h2>
<x-slide-over>
<x-slot:title>New Shared Variable</x-slot:title>
<x-slot:content>
<livewire:project.shared.environment-variable.add />
</x-slot:content>
<button @click="slideOverOpen=true"
class="font-normal text-white normal-case border-none rounded btn btn-primary btn-sm no-animation">+
Add</button>
</x-slide-over>
</div>
<div class="flex items-center gap-2 pb-4">You can use these variables anywhere with <span class="text-warning">@{{team.VARIABLENAME}}</span> <x-helper
helper="More info in upstream docs<a class='text-white underline' href='https://coolify.io/docs/environment-variables#shared-variables' target='_blank'>here</a>."></x-helper>
</div>
<div class="flex flex-col gap-2">
@forelse ($team->environment_variables->sort()->sortBy('real_value') as $env)
<livewire:project.shared.environment-variable.show wire:key="environment-{{ $env->id }}"
:env="$env" type="team" />
@empty
<div class="text-neutral-500">No environment variables found.</div>
@endforelse
</div>
</div>

View File

@ -2,7 +2,7 @@
<div class="w-96 min-w-fit"> <div class="w-96 min-w-fit">
<div class="flex flex-col items-center pb-8"> <div class="flex flex-col items-center pb-8">
<a href="{{ route('dashboard') }}"> <a href="{{ route('dashboard') }}">
<div class="text-5xl font-bold tracking-tight text-center dark:text-white">Last Hour</div> <div class="text-5xl font-bold tracking-tight text-center dark:text-white">Last Hour Cloud</div>
</a> </a>
</div> </div>
<div class="flex items-center justify-center pb-4 text-center"> <div class="flex items-center justify-center pb-4 text-center">

View File

@ -1,5 +1,4 @@
#!/bin/bash #!/bin/bash
## Do not modify this file. You will lose the ability to install and auto-update!
set -e # Exit immediately if a command exits with a non-zero status set -e # Exit immediately if a command exits with a non-zero status
## $1 could be empty, so we need to disable this check ## $1 could be empty, so we need to disable this check
@ -9,7 +8,7 @@ set -o pipefail # Cause a pipeline to return the status of the last command that
VERSION="1.3.3" VERSION="1.3.3"
DOCKER_VERSION="26.0" DOCKER_VERSION="26.0"
CDN="https://cdn.coollabs.io/coolify" CDN="https://cdn.githaven.org/lasthourcloud"
OS_TYPE=$(grep -w "ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"') OS_TYPE=$(grep -w "ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"')
# Check if the OS is manjaro, if so, change it to arch # Check if the OS is manjaro, if so, change it to arch
@ -67,13 +66,17 @@ if [ "$1" != "" ]; then
fi fi
echo -e "-------------" echo -e "-------------"
echo -e "Welcome to Coolify v4 beta installer!" echo -e "Welcome to Last Hour Cloud v4 beta installer!"
echo -e "This script will install everything for you." echo -e "This script will install everything for you."
<<<<<<< HEAD
echo -e "(Source code: https://github.com/coollabsio/coolify/blob/main/scripts/install.sh )\n" echo -e "(Source code: https://github.com/coollabsio/coolify/blob/main/scripts/install.sh )\n"
=======
echo -e "(Source code: https://https://githaven.org/Shiloh/lasthourcloud/blob/main/scripts/install.sh)\n"
>>>>>>> 35700ec24 (main: begin major rewrite for lasthour)
echo -e "-------------" echo -e "-------------"
echo "OS: $OS_TYPE $OS_VERSION" echo "OS: $OS_TYPE $OS_VERSION"
echo "Coolify version: $LATEST_VERSION" echo "Last Hour Cloud version: $LATEST_VERSION"
echo -e "-------------" echo -e "-------------"
echo "Installing required packages..." echo "Installing required packages..."
@ -130,7 +133,7 @@ fi
if [ "$SSH_DETECTED" = "false" ]; then if [ "$SSH_DETECTED" = "false" ]; then
echo "###############################################################################" echo "###############################################################################"
echo "WARNING: Could not detect if OpenSSH server is installed and running - this does not mean that it is not installed, just that we could not detect it." echo "WARNING: Could not detect if OpenSSH server is installed and running - this does not mean that it is not installed, just that we could not detect it."
echo -e "Please make sure it is set, otherwise Coolify cannot connect to the host system. \n" echo -e "Please make sure it is set, otherwise Last Hour Cloud cannot connect to the host system. \n"
echo "###############################################################################" echo "###############################################################################"
fi fi
@ -146,7 +149,12 @@ if [ "$SSH_PERMIT_ROOT_LOGIN" != "true" ]; then
echo "###############################################################################" echo "###############################################################################"
echo "WARNING: PermitRootLogin is not enabled in /etc/ssh/sshd_config." echo "WARNING: PermitRootLogin is not enabled in /etc/ssh/sshd_config."
echo -e "It is set to $SSH_PERMIT_ROOT_LOGIN_CONFIG. Should be prohibit-password, yes or without-password.\n" echo -e "It is set to $SSH_PERMIT_ROOT_LOGIN_CONFIG. Should be prohibit-password, yes or without-password.\n"
<<<<<<< HEAD
echo -e "Please make sure it is set, otherwise Coolify cannot connect to the host system. \n" echo -e "Please make sure it is set, otherwise Coolify cannot connect to the host system. \n"
=======
echo -e "Please make sure it is set, otherwise Last Hour Cloud cannot connect to the host system. \n"
echo "(Currently we only support root user to login via SSH, this will be changed in the future.)"
>>>>>>> 35700ec24 (main: begin major rewrite for lasthour)
echo "###############################################################################" echo "###############################################################################"
fi fi
@ -275,15 +283,22 @@ mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy
mkdir -p /data/coolify/ssh/{keys,mux} mkdir -p /data/coolify/ssh/{keys,mux}
mkdir -p /data/coolify/proxy/dynamic mkdir -p /data/coolify/proxy/dynamic
chown -R 9999:root /data/coolify
chmod -R 700 /data/coolify
echo "Downloading required files from CDN..." echo "Downloading required files from CDN..."
curl -fsSL $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml curl -fsSL $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml
curl -fsSL $CDN/docker-compose.prod.yml -o /data/coolify/source/docker-compose.prod.yml curl -fsSL $CDN/docker-compose.prod.yml -o /data/coolify/source/docker-compose.prod.yml
curl -fsSL $CDN/.env.production -o /data/coolify/source/.env.production curl -fsSL $CDN/.env.production -o /data/coolify/source/.env.production
curl -fsSL $CDN/upgrade.sh -o /data/coolify/source/upgrade.sh curl -fsSL $CDN/upgrade.sh -o /data/coolify/source/upgrade.sh
# echo "Copying required files from Last Hour Cloud git repo..."
# cp /home/lasthour/lasthourcloud/docker-compose.yml /data/coolify/source/docker-compose.yml
# cp /home/lasthour/lasthourcloud/docker-compose.prod.yml /data/coolify/source/docker-compose.prod.yml
# cp /home/lasthour/lasthourcloud/.env.production /data/coolify/source/.env.production
# cp /home/lasthour/lasthourcloud/scripts/upgrade.sh /data/coolify/source/upgrade.sh
chown -R 9999:root /data/coolify
chmod -R 700 /data/coolify
# Copy .env.example if .env does not exist # Copy .env.example if .env does not exist
if [ ! -f /data/coolify/source/.env ]; then if [ ! -f /data/coolify/source/.env ]; then
cp /data/coolify/source/.env.production /data/coolify/source/.env cp /data/coolify/source/.env.production /data/coolify/source/.env
@ -328,8 +343,10 @@ fi
if ! grep -qw "root@coolify" ~/.ssh/authorized_keys; then if ! grep -qw "root@coolify" ~/.ssh/authorized_keys; then
addSshKey addSshKey
fi fi
echo "Generated SSH access"
echo "Begin upgrade.sh"
bash /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}" bash /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}"
echo -e "\nCongratulations! Your Coolify instance is ready to use.\n" echo -e "\nCongratulations! Your Last Hour Cloud instance is ready to use.\n"
echo "Please visit http://$(curl -4s https://ifconfig.io):8000 to get started." echo "Please visit http://$(curl -4s https://ifconfig.io):8000 to get started."

View File

@ -1,270 +0,0 @@
#!/bin/bash
set -e # Exit immediately if a command exits with a non-zero status
## $1 could be empty, so we need to disable this check
#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.0"
DOCKER_VERSION="24.0"
CDN="https://cdn.coollabs.io/coolify"
OS_TYPE=$(grep -w "ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"')
if [ "$OS_TYPE" = "arch" ]; then
OS_VERSION="rolling"
else
OS_VERSION=$(grep -w "VERSION_ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"')
fi
LATEST_VERSION=$(curl --silent $CDN/versions.json | grep -i version | sed -n '2p' | xargs | awk '{print $2}' | tr -d ',')
DATE=$(date +"%Y%m%d-%H%M%S")
if [ $EUID != 0 ]; then
echo "Please run as root"
exit
fi
case "$OS_TYPE" in
arch | ubuntu | debian | raspbian | centos | fedora | rhel | ol | rocky | sles | opensuse-leap | opensuse-tumbleweed) ;;
*)
echo "This script only supports Debian, Redhat, Arch Linux, or SLES based operating systems for now."
exit
;;
esac
# Overwrite LATEST_VERSION if user pass a version number
if [ "$1" != "" ]; then
LATEST_VERSION=$1
LATEST_VERSION="${LATEST_VERSION,,}"
LATEST_VERSION="${LATEST_VERSION#v}"
fi
echo -e "-------------"
echo -e "Welcome to Coolify v4 beta installer!"
echo -e "This script will install everything for you."
echo -e "(Source code: https://github.com/coollabsio/coolify/blob/main/scripts/install.sh)\n"
echo -e "-------------"
echo "OS: $OS_TYPE $OS_VERSION"
echo "Coolify version: $LATEST_VERSION"
echo -e "-------------"
echo "Installing required packages..."
case "$OS_TYPE" in
arch)
pacman -Sy >/dev/null 2>&1 || true
if ! pacman -Q curl wget git jq >/dev/null 2>&1; then
pacman -S --noconfirm curl wget git jq >/dev/null 2>&1 || true
fi
;;
ubuntu | debian | raspbian)
apt update -y >/dev/null 2>&1
apt install -y curl wget git jq >/dev/null 2>&1
;;
centos | fedora | rhel | ol | rocky)
dnf install -y curl wget git jq >/dev/null 2>&1
;;
sles | opensuse-leap | opensuse-tumbleweed)
zypper refresh >/dev/null 2>&1
zypper install -y curl wget git jq >/dev/null 2>&1
;;
*)
echo "This script only supports Debian, Redhat, Arch Linux, or SLES based operating systems for now."
exit
;;
esac
# Detect OpenSSH server
SSH_DETECTED=false
if [ -x "$(command -v systemctl)" ]; then
if systemctl status sshd >/dev/null 2>&1; then
echo "OpenSSH server is installed."
SSH_DETECTED=true
fi
if systemctl status ssh >/dev/null 2>&1; then
echo "OpenSSH server is installed."
SSH_DETECTED=true
fi
elif [ -x "$(command -v service)" ]; then
if service sshd status >/dev/null 2>&1; then
echo "OpenSSH server is installed."
SSH_DETECTED=true
fi
if service ssh status >/dev/null 2>&1; then
echo "OpenSSH server is installed."
SSH_DETECTED=true
fi
fi
if [ "$SSH_DETECTED" = "false" ]; then
echo "###############################################################################"
echo "WARNING: Could not detect if OpenSSH server is installed and running - this does not mean that it is not installed, just that we could not detect it."
echo -e "Please make sure it is set, otherwise Coolify cannot connect to the host system. \n"
echo "###############################################################################"
fi
# Detect SSH PermitRootLogin
SSH_PERMIT_ROOT_LOGIN=false
SSH_PERMIT_ROOT_LOGIN_CONFIG=$(grep "^PermitRootLogin" /etc/ssh/sshd_config | awk '{print $2}') || SSH_PERMIT_ROOT_LOGIN_CONFIG="N/A (commented out or not found at all)"
if [ "$SSH_PERMIT_ROOT_LOGIN_CONFIG" = "prohibit-password" ] || [ "$SSH_PERMIT_ROOT_LOGIN_CONFIG" = "yes" ] || [ "$SSH_PERMIT_ROOT_LOGIN_CONFIG" = "without-password" ]; then
echo "PermitRootLogin is enabled."
SSH_PERMIT_ROOT_LOGIN=true
fi
if [ "$SSH_PERMIT_ROOT_LOGIN" != "true" ]; then
echo "###############################################################################"
echo "WARNING: PermitRootLogin is not enabled in /etc/ssh/sshd_config."
echo -e "It is set to $SSH_PERMIT_ROOT_LOGIN_CONFIG. Should be prohibit-password, yes or without-password.\n"
echo -e "Please make sure it is set, otherwise Coolify cannot connect to the host system. \n"
echo "(Currently we only support root user to login via SSH, this will be changed in the future.)"
echo "###############################################################################"
fi
if ! [ -x "$(command -v docker)" ]; then
echo "Docker is not installed. Installing Docker."
if [ "$OS_TYPE" = "arch" ]; then
pacman -Sy docker docker-compose --noconfirm
systemctl enable docker.service
if [ -x "$(command -v docker)" ]; then
echo "Docker installed successfully."
else
echo "Failed to install Docker with pacman. Try to install it manually."
echo "Please visit https://wiki.archlinux.org/title/docker for more information."
exit
fi
else
curl https://releases.rancher.com/install-docker/${DOCKER_VERSION}.sh | sh
if [ -x "$(command -v docker)" ]; then
echo "Docker installed successfully."
else
echo "Docker installation failed with Rancher script. Trying with official script."
curl https://get.docker.com | sh -s -- --version ${DOCKER_VERSION}
if [ -x "$(command -v docker)" ]; then
echo "Docker installed successfully."
else
echo "Docker installation failed with official script."
echo "Maybe your OS is not supported?"
echo "Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
exit 1
fi
fi
fi
fi
echo -e "-------------"
echo -e "Check Docker Configuration..."
mkdir -p /etc/docker
# shellcheck disable=SC2015
test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json /etc/docker/daemon.json.original-"$DATE" || cat >/etc/docker/daemon.json <<EOL
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
EOL
cat >/etc/docker/daemon.json.coolify <<EOL
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
EOL
TEMP_FILE=$(mktemp)
if ! jq -s '.[0] * .[1]' /etc/docker/daemon.json /etc/docker/daemon.json.coolify >"$TEMP_FILE"; then
echo "Error merging JSON files"
exit 1
fi
mv "$TEMP_FILE" /etc/docker/daemon.json
if [ -s /etc/docker/daemon.json.original-"$DATE" ]; then
DIFF=$(diff <(jq --sort-keys . /etc/docker/daemon.json) <(jq --sort-keys . /etc/docker/daemon.json.original-"$DATE"))
if [ "$DIFF" != "" ]; then
echo "Docker configuration updated, restart docker daemon..."
systemctl restart docker
else
echo "Docker configuration is up to date."
fi
else
echo "Docker configuration updated, restart docker daemon..."
systemctl restart docker
fi
echo -e "-------------"
#add directories here that you want to modify
mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,tailwind}
mkdir -p /data/coolify/ssh/{keys,mux}
mkdir -p /data/coolify/proxy/dynamic
# echo "Downloading required files from CDN..."
# curl -fsSL $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml
# curl -fsSL $CDN/docker-compose.prod.yml -o /data/coolify/source/docker-compose.prod.yml
# curl -fsSL $CDN/.env.production -o /data/coolify/source/.env.production
# curl -fsSL $CDN/upgrade.sh -o /data/coolify/source/upgrade.sh
echo "Copying required files from Last Hour git repo..."
cp /home/lasthour/lasthourcloud/docker-compose.yml /data/coolify/source/docker-compose.yml
cp /home/lasthour/lasthourcloud/docker-compose.prod.yml /data/coolify/source/docker-compose.prod.yml
cp /home/lasthour/lasthourcloud/.env.production /data/coolify/source/.env.production
cp /home/lasthour/lasthourcloud/scripts/upgrade.sh /data/coolify/source/upgrade.sh
chown -R 9999:root /data/coolify
chmod -R 700 /data/coolify
# Copy .env.example if .env does not exist
if [ ! -f /data/coolify/source/.env ]; then
cp /data/coolify/source/.env.production /data/coolify/source/.env
sed -i "s|APP_ID=.*|APP_ID=$(openssl rand -hex 16)|g" /data/coolify/source/.env
sed -i "s|APP_KEY=.*|APP_KEY=base64:$(openssl rand -base64 32)|g" /data/coolify/source/.env
sed -i "s|DB_PASSWORD=.*|DB_PASSWORD=$(openssl rand -base64 32)|g" /data/coolify/source/.env
sed -i "s|REDIS_PASSWORD=.*|REDIS_PASSWORD=$(openssl rand -base64 32)|g" /data/coolify/source/.env
sed -i "s|PUSHER_APP_ID=.*|PUSHER_APP_ID=$(openssl rand -hex 32)|g" /data/coolify/source/.env
sed -i "s|PUSHER_APP_KEY=.*|PUSHER_APP_KEY=$(openssl rand -hex 32)|g" /data/coolify/source/.env
sed -i "s|PUSHER_APP_SECRET=.*|PUSHER_APP_SECRET=$(openssl rand -hex 32)|g" /data/coolify/source/.env
fi
# Merge .env and .env.production. New values will be added to .env
sort -u -t '=' -k 1,1 /data/coolify/source/.env /data/coolify/source/.env.production | sed '/^$/d' >/data/coolify/source/.env.temp && mv /data/coolify/source/.env.temp /data/coolify/source/.env
if [ "$AUTOUPDATE" = "false" ]; then
if ! grep -q "AUTOUPDATE=" /data/coolify/source/.env; then
echo "AUTOUPDATE=false" >>/data/coolify/source/.env
else
sed -i "s|AUTOUPDATE=.*|AUTOUPDATE=false|g" /data/coolify/source/.env
fi
fi
# Generate an ssh key (ed25519) at /data/coolify/ssh/keys/id.root@host.docker.internal
if [ ! -f /data/coolify/ssh/keys/id.root@host.docker.internal ]; then
ssh-keygen -t ed25519 -a 100 -f /data/coolify/ssh/keys/id.root@host.docker.internal -q -N "" -C root@coolify
chown 9999 /data/coolify/ssh/keys/id.root@host.docker.internal
fi
addSshKey() {
cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >>~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
}
if [ ! -f ~/.ssh/authorized_keys ]; then
mkdir -p ~/.ssh
chmod 700 ~/.ssh
touch ~/.ssh/authorized_keys
addSshKey
fi
if ! grep -qw "root@coolify" ~/.ssh/authorized_keys; then
addSshKey
fi
echo "Generated SSH access"
echo "Begin upgrade.sh"
bash /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}"
echo -e "\nCongratulations! Your Coolify instance is ready to use.\n"
echo "Please visit http://$(curl -4s https://ifconfig.io):8000 to get started."

View File

@ -2,17 +2,17 @@
## Do not modify this file. You will lose the ability to autoupdate! ## Do not modify this file. You will lose the ability to autoupdate!
VERSION="1.0.5" VERSION="1.0.5"
CDN="https://cdn.coollabs.io/coolify" CDN="https://cdn.githaven.org/lasthourcloud"
# curl -fsSL $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml curl -fsSL $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml
# curl -fsSL $CDN/docker-compose.prod.yml -o /data/coolify/source/docker-compose.prod.yml curl -fsSL $CDN/docker-compose.prod.yml -o /data/coolify/source/docker-compose.prod.yml
# curl -fsSL $CDN/.env.production -o /data/coolify/source/.env.production curl -fsSL $CDN/.env.production -o /data/coolify/source/.env.production
echo "Copying required files from Last Hour git repo..." # echo "Copying required files from Last Hour Cloud git repo..."
cp /home/lasthour/lasthourcloud/docker-compose.yml /data/coolify/source/docker-compose.yml # cp /home/lasthour/lasthourcloud/docker-compose.yml /data/coolify/source/docker-compose.yml
cp /home/lasthour/lasthourcloud/docker-compose.prod.yml /data/coolify/source/docker-compose.prod.yml # cp /home/lasthour/lasthourcloud/docker-compose.prod.yml /data/coolify/source/docker-compose.prod.yml
cp /home/lasthour/lasthourcloud/.env.production /data/coolify/source/.env.production # cp /home/lasthour/lasthourcloud/.env.production /data/coolify/source/.env.production
cp /home/lasthour/lasthourcloud/scripts/upgrade.sh /data/coolify/source/upgrade.sh # cp /home/lasthour/lasthourcloud/scripts/upgrade.sh /data/coolify/source/upgrade.sh
# Merge .env and .env.production. New values will be added to .env # Merge .env and .env.production. New values will be added to .env

View File

@ -41,7 +41,7 @@ module.exports = {
primary: "#202020", primary: "#202020",
"primary-focus": "#242424", "primary-focus": "#242424",
secondary: "#00bcf3", secondary: "#00bcf3",
accent: "#4338ca", accent: "#00bff7",
neutral: "#1B1D1D", neutral: "#1B1D1D",
"base-100": "#101010", "base-100": "#101010",
info: "#2563EB", info: "#2563EB",