Compare commits

...

5 Commits
main ... prod

Author SHA1 Message Date
Gary
fe0f5321be update index.blade.php
update install.sh
update upgrade.sh
update cloud_upgrade.sh
update run
2024-09-16 09:11:52 -07:00
Gary
172589a805 fix index.blade.php error syntax 2024-09-13 17:51:48 -07:00
Gary
8e9b3efeb9 change docker image to prod 2024-09-13 17:11:00 -07:00
Gary
0ab9001dbf change to lasthourcloudprod url for updates and install 2024-09-13 17:06:48 -07:00
Gary
f28e52e7b2 remove localhost from prod
replace broken get-involved link
2024-09-13 16:49:13 -07:00
11 changed files with 825 additions and 1181 deletions

View File

@ -57,14 +57,14 @@ private function update()
} else { } else {
ray('Running update on production server'); ray('Running update on production server');
remote_process([ remote_process([
"curl -fsSL https://cdn.lasthourhosting.org/lasthourcloud/scripts/upgrade.sh -o /data/coolify/source/upgrade.sh", "curl -fsSL https://cdn.lasthourhosting.org/lasthourcloudprod/scripts/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);
return; return;
} }
remote_process([ remote_process([
'curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh', "curl -fsSL https://cdn.lasthourhosting.org/lasthourcloudprod/scripts/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

@ -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.lasthourhosting.org/lasthourcloud/versions.json'); $response = Http::get('https://cdn.lasthourhosting.org/lasthourcloudprod/versions.json');
$versions = $response->json(); $versions = $response->json();
return data_get($versions, 'sentinel.version'); return data_get($versions, 'sentinel.version');
@ -388,7 +388,7 @@ function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null
Mail::send( Mail::send(
[], [],
[], [],
fn (Message $message) => $message fn(Message $message) => $message
->to($email) ->to($email)
->replyTo($email) ->replyTo($email)
->cc($cc) ->cc($cc)
@ -399,7 +399,7 @@ function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null
Mail::send( Mail::send(
[], [],
[], [],
fn (Message $message) => $message fn(Message $message) => $message
->to($email) ->to($email)
->subject($mail->subject) ->subject($mail->subject)
->html((string) $mail->render()) ->html((string) $mail->render())
@ -2331,15 +2331,15 @@ function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId =
$applications = Application::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid']); $applications = Application::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid']);
$serviceApplications = ServiceApplication::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid']); $serviceApplications = ServiceApplication::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid']);
if ($uuid) { if ($uuid) {
$applications = $applications->filter(fn ($app) => $app->uuid !== $uuid); $applications = $applications->filter(fn($app) => $app->uuid !== $uuid);
$serviceApplications = $serviceApplications->filter(fn ($app) => $app->uuid !== $uuid); $serviceApplications = $serviceApplications->filter(fn($app) => $app->uuid !== $uuid);
} }
$domainFound = false; $domainFound = false;
foreach ($applications as $app) { foreach ($applications as $app) {
if (is_null($app->fqdn)) { if (is_null($app->fqdn)) {
continue; continue;
} }
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn($fqdn) => $fqdn !== '');
foreach ($list_of_domains as $domain) { foreach ($list_of_domains as $domain) {
if (str($domain)->endsWith('/')) { if (str($domain)->endsWith('/')) {
$domain = str($domain)->beforeLast('/'); $domain = str($domain)->beforeLast('/');
@ -2358,7 +2358,7 @@ function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId =
if (str($app->fqdn)->isEmpty()) { if (str($app->fqdn)->isEmpty()) {
continue; continue;
} }
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn($fqdn) => $fqdn !== '');
foreach ($list_of_domains as $domain) { foreach ($list_of_domains as $domain) {
if (str($domain)->endsWith('/')) { if (str($domain)->endsWith('/')) {
$domain = str($domain)->beforeLast('/'); $domain = str($domain)->beforeLast('/');
@ -2408,7 +2408,7 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
}); });
$apps = Application::all(); $apps = Application::all();
foreach ($apps as $app) { foreach ($apps as $app) {
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn($fqdn) => $fqdn !== '');
foreach ($list_of_domains as $domain) { foreach ($list_of_domains as $domain) {
if (str($domain)->endsWith('/')) { if (str($domain)->endsWith('/')) {
$domain = str($domain)->beforeLast('/'); $domain = str($domain)->beforeLast('/');
@ -2427,7 +2427,7 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
} }
$apps = ServiceApplication::all(); $apps = ServiceApplication::all();
foreach ($apps as $app) { foreach ($apps as $app) {
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn($fqdn) => $fqdn !== '');
foreach ($list_of_domains as $domain) { foreach ($list_of_domains as $domain) {
if (str($domain)->endsWith('/')) { if (str($domain)->endsWith('/')) {
$domain = str($domain)->beforeLast('/'); $domain = str($domain)->beforeLast('/');

View File

@ -1,6 +1,6 @@
services: services:
coolify: coolify:
image: "githaven.org/shiloh/lasthourcloud:latest" image: "githaven.org/shiloh/lasthourcloud:prod"
volumes: volumes:
- type: bind - type: bind
source: /data/coolify/source/.env source: /data/coolify/source/.env

View File

@ -1,5 +1,5 @@
<<<<<<< HEAD <<<<<<< HEAD
<nav class="flex flex-col flex-1 bg-white border-r dark:border-coolgray-200 dark:bg-base" x-data="{ <nav class="flex flex-col flex-1 bg-white border-r dark:border-coolgray-200 dark:bg-base" x-data="{
switchWidth() { switchWidth() {
if (this.full === 'full') { if (this.full === 'full') {
localStorage.removeItem('pageWidth'); localStorage.removeItem('pageWidth');
@ -336,7 +336,7 @@ class="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border text-
@endpersist @endpersist
@endif @endif
<li title="Get Involved"> <li title="Get Involved">
<a class="hover:bg-transparent" href="https://shilohcode.com/get-involved" target="_blank"> <a class="hover:bg-transparent" href="https://githaven.org/Shiloh/lasthourcloud" target="_blank">
<svg xmlns="http://www.w3.org/2000/svg" class="icon hover:text-blue-500" viewBox="0 0 16 16"> <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"/> <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"/>
======= =======
@ -494,7 +494,7 @@ class="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border text-
@endpersist @endpersist
@endif @endif
<li title="Get Involved"> <li title="Get Involved">
<a class="justify-center hover:text-white hover:bg-transparent" href="https://shilohcode.com/get-involved" target="_blank"> <a class="justify-center hover:text-white hover:bg-transparent" href="https://githaven.org/Shiloh/lasthourcloud" target="_blank">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 16 16">
<path fill="currentColor" 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" /> <path fill="currentColor" 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> </svg>
@ -502,16 +502,16 @@ class="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border text-
</li> </li>
<li title="Get help!"> <li title="Get help!">
<a href="https://lasthourhosting.org/contact.html" target="_blank"> <a href="https://lasthourhosting.org/contact.html" target="_blank">
<<<<<<< HEAD <<<<<<< HEAD
<div class="justify-center hover:text-white hover:bg-transparent"> <div class="justify-center hover:text-white hover:bg-transparent">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" 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" /> <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" />
>>>>>>> 04e370917 (main: fix for various bugs) >>>>>>> 04e370917 (main: fix for various bugs)
======= =======
<div class="justify-center items-center hover:text-white hover:bg-transparent"> <div class="justify-center items-center hover:text-white hover:bg-transparent">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="currentColor" 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" /> <path fill="currentColor" 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" />
>>>>>>> c870cd8ce (fix: UI and copy issues) >>>>>>> c870cd8ce (fix: UI and copy issues)
</svg> </svg>
</div> </div>
</a> </a>
@ -525,7 +525,7 @@ class="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border text-
</svg> </svg>
</button> </button>
</li> </li>
<<<<<<< HEAD <<<<<<< HEAD
<form action="/logout" method="POST" class="hover:bg-transparent"> <form action="/logout" method="POST" class="hover:bg-transparent">
<li title="Logout" class="mb-6 hover:transparent"> <li title="Logout" class="mb-6 hover:transparent">
@csrf @csrf
@ -535,7 +535,7 @@ class="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border text-
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>
</button> </button>
>>>>>>> 35700ec24 (main: begin major rewrite for lasthour) >>>>>>> 35700ec24 (main: begin major rewrite for lasthour)
</li> </li>
<li> <li>
<a href="#" <a href="#"
@ -558,10 +558,10 @@ class="flex items-center px-6 py-3 text-sm font-semibold leading-6 text-gray-900
</a> </a>
</li> --}} </li> --}}
</ul> </ul>
</nav> </nav>
======= =======
</form> </form>
</ul> </ul>
</nav> </nav>
@endauth @endauth
>>>>>>> 04e370917 (main: fix for various bugs) >>>>>>> 04e370917 (main: fix for various bugs)

View File

@ -3,100 +3,143 @@
Onboarding | Last Hour Cloud 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 ">
@if ($currentState === 'welcome') @if ($currentState === 'welcome')
<h1 class="text-5xl font-bold">Welcome to your Last Hour Cloud</h1> <h1 class="text-3xl font-bold lg:text-5xl">Welcome to Last Hour Cloud</h1>
<p class="py-6 text-xl text-center">Let's help you to set up the basics and show you around.</p> <div class="py-6 text-center lg:text-xl">Let's help you get set up.</div>
<div class="flex justify-center "> <div class="flex justify-center ">
<x-forms.button class="justify-center w-64 box" wire:click="$set('currentState','explanation')">Let's Start <x-forms.button class="justify-center w-64 box-boarding"
wire:click="$set('currentState','explanation')">Get
Started
</x-forms.button> </x-forms.button>
</div> </div>
@endif @elseif ($currentState === 'explanation')
</div> <x-boarding-step title="What is Last Hour Cloud?">
<h1 class="text-5xl font-bold">Welcome to your Last Hour Cloud</h1>
<p class="py-6 text-xl text-center">Let's help you to set up the basics and show you around.</p>
<div class="flex justify-center ">
<x-forms.button class="justify-center w-64 box" wire:click="$set('currentState','explanation')">Let's Start
</x-forms.button>
</div>
@endif
</div>
<div>
@if ($currentState === 'explanation')
<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 applications with Git Last Hour Cloud is an all-in-one application to automate tasks on your servers, deploy application with
integrations, deploy databases and services, monitor these resources with notifications and alerts Git
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-apps.html" class="dark:text-white hover:underline">more</a>.
<br><br> <br><br>
<span class="text-xl"> <span class="text-xl">
<x-highlighted text="Self-hosting for the Last Hour Cloud." /></span> <x-highlighted text="Cloud apps for the last hour" /></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>
it for you.</p> <x-highlighted text="Task automation:" /> You don't need to manage your servers anymore.
<p><x-highlighted text="No vendor lock-in:" /> All configurations are stored on your server, so Last Hour Cloud does
everything works without this (except integrations and automations).</p> it for you.
<p><x-highlighted text="Monitoring:" />You will get notified on your favourite platform (Discord, </p>
Telegram, Email, etc.) when something goes wrong, or if an action is needed from your side.</p> <p>
<x-highlighted text="No vendor lock-in:" /> All configurations are stored on your servers, so
everything works without a connection to Last Hour Cloud (except integrations and automations).
</p>
<p>
<x-highlighted text="Monitoring:" />You can get notified on your favourite platforms
(Discord,
Telegram, Email, etc.) when something goes wrong, or an action is needed from your side.
</p>
</x-slot:explanation> </x-slot:explanation>
<x-slot:actions> <x-slot:actions>
<x-forms.button class="justify-center w-64 box" wire:click="explanation">Next <x-forms.button class="justify-center w-64 box-boarding" wire:click="explanation">Next
</x-forms.button> </x-forms.button>
</x-slot:actions> </x-slot:actions>
</x-boarding-step> </x-boarding-step>
@endif @elseif ($currentState === 'select-server-type')
@if ($currentState === 'select-server-type')
<x-boarding-step title="Server"> <x-boarding-step title="Server">
<x-slot:question> <x-slot:question>
Do you want to deploy your resources on your <x-highlighted text="Localhost" /> Do you want to deploy your resources to your
or on a <x-highlighted text="Remote Server" />? <x-highlighted text="Localhost" />
or to a
<x-highlighted text="Remote Server" />?
</x-slot:question> </x-slot:question>
<x-slot:actions> <x-slot:actions>
<x-forms.button class="justify-center w-64 box" wire:target="setServerType('localhost')" wire:click="setServerType('localhost')">Localhost <x-forms.button class="justify-center w-64 box-boarding" wire:target="setServerType('localhost')"
wire:click="setServerType('localhost')">Localhost
</x-forms.button> </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 class="justify-center w-64 box-boarding " wire:target="setServerType('remote')"
wire:click="setServerType('remote')">Remote Server
</x-forms.button> </x-forms.button>
@if (!$serverReachable) @if (!$serverReachable)
Localhost is not reachable with the following public key. <div class="mt-6 p-4 border border-error rounded-lg text-gray-800 dark:text-gray-200">
<br /> <br /> <h2 class="text-lg font-bold mb-2">Server is not reachable</h2>
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for user <p class="mb-4">Please check the connection details below and correct them if they are
'root' or skip the guided tour and add a new private key manually to Last Hour Cloud and to the incorrect.</p>
server.
<br /> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
Check the upstream <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh">documentation</a> for further help. <x-forms.input placeholder="Default is 22" label="Port" id="remoteServerPort"
<x-forms.input readonly id="serverPublicKey"></x-forms.input> wire:model="remoteServerPort" :value="$remoteServerPort" />
<x-forms.button class="w-64 box" wire:target="setServerType('localhost')" wire:click="setServerType('localhost')">Check again <div>
<x-forms.input placeholder="Default is root" label="User" id="remoteServerUser"
wire:model="remoteServerUser" :value="$remoteServerUser" />
<p class="text-xs mt-1">
Non-root user is experimental:
<a class="font-bold underline" target="_blank"
href="https://coolify.io/docs/knowledge-base/server/non-root-user">See upstream docs</a>
</p>
</div>
</div>
<div class="mb-4">
<p class="mb-2">If the connection details are correct, please ensure:</p>
<ul class="list-disc list-inside">
<li>The correct public key is in your <code
class="bg-red-200 dark:bg-red-900 px-1 rounded">~/.ssh/authorized_keys</code>
file for the specified user</li>
<li>Or skip the boarding process and manually add a new private key to Last Hour Cloud and
the server</li>
</ul>
</div>
<p class="mb-4">
For more help, check this <a target="_blank" class="underline font-semibold"
href="https://coolify.io/docs/knowledge-base/server/openssh">upstream documentation</a>.
</p>
<x-forms.input readonly id="serverPublicKey" class="mb-4"
label="Current Public Key"></x-forms.input>
<x-forms.button class="w-full box-boarding" wire:click="saveAndValidateServer">
Check Again
</x-forms.button> </x-forms.button>
</div>
@endif @endif
</x-slot:actions> </x-slot:actions>
<x-slot:explanation> <x-slot:explanation>
<p>Servers are the main building blocks, as they will host your applications, databases, <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 services, called resources. Any CPU intensive process will use the server's CPU where you
are deploying your resources.</p> 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 <p>
for everything.</p> <x-highlighted text="Localhost" /> is the server where Last Hour Cloud is running on. It is not
<p>Remote Server is a server reachable through SSH. It can be hosted at home, or from any cloud recommended to use one server
provider.</p> for everything.
</p>
<p>
<x-highlighted text="A remote server" /> is a server reachable through SSH. It can be hosted
at home, or from any cloud
provider.
</p>
</x-slot:explanation> </x-slot:explanation>
</x-boarding-step> </x-boarding-step>
@endif @elseif ($currentState === 'private-key')
</div>
<div>
@if ($currentState === 'private-key')
<x-boarding-step title="SSH Key"> <x-boarding-step title="SSH Key">
<x-slot:question> <x-slot:question>
Do you have your own SSH Private Key? Do you have your own SSH Private Key?
</x-slot:question> </x-slot:question>
<x-slot:actions> <x-slot:actions>
<x-forms.button class="justify-center w-64 box" wire:target="setPrivateKey('own')" wire:click="setPrivateKey('own')">Yes <x-forms.button class="justify-center lg:w-64 box-boarding" wire:target="setPrivateKey('own')"
wire:click="setPrivateKey('own')">Yes
</x-forms.button> </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 class="justify-center lg:w-64 box-boarding" wire:target="setPrivateKey('create')"
wire:click="setPrivateKey('create')">No (create one for me)
</x-forms.button> </x-forms.button>
@if (count($privateKeys) > 0) @if (count($privateKeys) > 0)
<form wire:submit='selectExistingPrivateKey' class="flex flex-col w-full gap-4 pr-10"> <form wire:submit='selectExistingPrivateKey' class="flex flex-col w-full gap-4 lg:pr-10">
<x-forms.select label="Existing SSH Keys" id='selectedExistingPrivateKey'> <x-forms.select label="Existing SSH Keys" id='selectedExistingPrivateKey'>
@foreach ($privateKeys as $privateKey) @foreach ($privateKeys as $privateKey)
<option wire:key="{{ $loop->index }}" value="{{ $privateKey->id }}"> <option wire:key="{{ $loop->index }}" value="{{ $privateKey->id }}">
@ -110,17 +153,14 @@
</x-slot:actions> </x-slot:actions>
<x-slot:explanation> <x-slot:explanation>
<p>SSH Keys are used to connect to a remote server through a secure shell, called SSH.</p> <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>You can use your own ssh private key, or you can let 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 <p>In both ways, you need to add the public version of your ssh private key to the remote
server's server's
<code class="text-warning">~/.ssh/authorized_keys</code> file. <code class="dark:text-warning">~/.ssh/authorized_keys</code> file.
</p> </p>
</x-slot:explanation> </x-slot:explanation>
</x-boarding-step> </x-boarding-step>
@endif @elseif ($currentState === 'select-existing-server')
</div>
<div>
@if ($currentState === 'select-existing-server')
<x-boarding-step title="Select a server"> <x-boarding-step title="Select a server">
<x-slot:question> <x-slot:question>
There are already servers available for your Team. Do you want to use one of them? There are already servers available for your Team. Do you want to use one of them?
@ -128,9 +168,8 @@
<x-slot:actions> <x-slot:actions>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div> <div>
<x-forms.button class="justify-center w-64 box" wire:click="createNewServer">No (create one <x-forms.button class="justify-center w-64 box-boarding" wire:click="createNewServer">No
for (create one for me)
me)
</x-forms.button> </x-forms.button>
</div> </div>
<div> <div>
@ -147,241 +186,75 @@
</div> </div>
</div> </div>
@if (!$serverReachable) @if (!$serverReachable)
This server is not reachable with the following public key. <div class="mt-6 p-4 bg-red-100 dark:bg-red-950 rounded-lg text-gray-800 dark:text-gray-200">
<br /> <br /> <h2 class="text-lg font-bold mb-2">Server is not reachable</h2>
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for user <p class="mb-4">Please check the connection details below and correct them if they are
'root' or skip the boarding process and add a new private key manually to Last Hour Cloud and to the incorrect.</p>
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>
@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 <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
</x-forms.button> <x-forms.input placeholder="Default is 22" label="Port" id="remoteServerPort"
@if (!$serverReachable) wire:model="remoteServerPort" :value="$remoteServerPort" />
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> <div>
@if ($currentState === 'private-key') <x-forms.input placeholder="Default is root" label="User" id="remoteServerUser"
<x-boarding-step title="SSH Key"> wire:model="remoteServerUser" :value="$remoteServerUser" />
<x-slot:question> <p class="text-xs mt-1">
Do you have your own SSH Private Key? Non-root user is experimental:
</x-slot:question> <a class="font-bold underline" target="_blank"
<x-slot:actions> href="https://coolify.io/docs/knowledge-base/server/non-root-user">upstream docs</a>
<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> </p>
</x-slot:explanation>
</x-boarding-step>
@endif
</div> </div>
<div> </div>
@if ($currentState === 'select-existing-server')
<x-boarding-step title="Select a server"> <div class="mb-4">
<x-slot:question> <p class="mb-2">If the connection details are correct, please ensure:</p>
There are already servers available for your Team. Do you want to use one of them? <ul class="list-disc list-inside">
</x-slot:question> <li>The correct public key is in your <code
<x-slot:actions> class="bg-red-200 dark:bg-red-900 px-1 rounded">~/.ssh/authorized_keys</code>
<div class="flex flex-col gap-4"> file for the specified user</li>
<div> <li>Or skip the boarding process and manually add a new private key to Last Hour Cloud and
<x-forms.button class="justify-center w-64 box" wire:click="createNewServer">No (create one the server</li>
for </ul>
me) </div>
<p class="mb-4">
For more help, check this <a target="_blank" class="underline font-semibold"
href="https://coolify.io/docs/knowledge-base/server/openssh">upstream documentation</a>.
</p>
<x-forms.input readonly id="serverPublicKey" class="mb-4"
label="Current Public Key"></x-forms.input>
<x-forms.button class="w-full md:w-auto box-boarding" wire:click="saveAndValidateServer">
Check again
</x-forms.button> </x-forms.button>
</div> </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 @endif
</x-slot:actions> </x-slot:actions>
<x-slot:explanation> <x-slot:explanation>
<p>Private Keys are used to connect to a remote server through a secure shell, called SSH.</p> <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>You can use your own private key, or you can let Last Hour Cloud to 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 <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. <code>~/.ssh/authorized_keys</code> file.
</p> </p>
</x-slot:explanation> </x-slot:explanation>
</x-boarding-step> </x-boarding-step>
@endif @elseif ($currentState === 'create-private-key')
</div>
<div>
@if ($currentState === 'create-private-key')
<x-boarding-step title="Create Private Key"> <x-boarding-step title="Create Private Key">
<x-slot:question> <x-slot:question>
Please let me know your key details. Please let me know your key details.
</x-slot:question> </x-slot:question>
<x-slot:actions> <x-slot:actions>
<form wire:submit='savePrivateKey' class="flex flex-col w-full gap-4 pr-10"> <form wire:submit='savePrivateKey' class="flex flex-col w-full gap-4 lg:pr-10">
<x-forms.input required placeholder="Choose a name for your Private Key. Could be anything." label="Name" id="privateKeyName" /> <x-forms.input required placeholder="Choose a name for your Private Key. Could be anything."
<x-forms.input placeholder="Description, so others will know more about this." label="Description" id="privateKeyDescription" /> label="Name" id="privateKeyName" />
<x-forms.textarea required placeholder="-----BEGIN OPENSSH PRIVATE KEY-----" label="Private Key" id="privateKey" /> <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') @if ($privateKeyType === 'create')
<x-forms.textarea rows="7" readonly label="Public Key" id="publicKey" /> <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 <span class="font-bold dark:text-warning">ACTION REQUIRED: Copy the 'Public Key' to your
server's
~/.ssh/authorized_keys ~/.ssh/authorized_keys
file.</span> file.</span>
@endif @endif
@ -390,50 +263,62 @@
</x-slot:actions> </x-slot:actions>
<x-slot:explanation> <x-slot:explanation>
<p>Private Keys are used to connect to a remote server through a secure shell, called SSH.</p> <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>You can use your own private key, or you can let Last Hour Cloud to 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 <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. <code>~/.ssh/authorized_keys</code> file.
</p> </p>
</x-slot:explanation> </x-slot:explanation>
</x-boarding-step> </x-boarding-step>
@endif @elseif ($currentState === 'create-server')
</div> <x-boarding-step title="Create Server">
<div>
@if ($currentState === 'create-server')
<x-boarding-step title="Create a Server">
<x-slot:question> <x-slot:question>
Please let us know your server details. Please let me know your server details.
</x-slot:question> </x-slot:question>
<x-slot:actions> <x-slot:actions>
<form wire:submit='saveServer' class="flex flex-col w-full gap-4 pr-10"> <form wire:submit='saveServer' class="flex flex-col w-full gap-4 lg:pr-10">
<div class="flex gap-2"> <div class="flex flex-col gap-2 lg:flex-row">
<x-forms.input required placeholder="Choose a name for your Server. It could be anything." label="Name" id="remoteServerName" /> <x-forms.input required placeholder="Choose a name for your Server. Could be anything."
<x-forms.input placeholder="Description, so others will know more about it." label="Description" id="remoteServerDescription" /> label="Name" id="remoteServerName" wire:model="remoteServerName" />
<x-forms.input placeholder="Description, so others will know more about this."
label="Description" id="remoteServerDescription"
wire:model="remoteServerDescription" />
</div> </div>
<div class="flex gap-2"> <div class="flex flex-col gap-2 lg:flex-row ">
<x-forms.input required placeholder="127.0.0.1" label="IP Address" id="remoteServerHost" /> <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" /> wire:model="remoteServerHost" />
<x-forms.input required readonly placeholder="Username to connect to your server. Default is root." label="Username" id="remoteServerUser" />
</div> </div>
<div class="w-64"> <div x-data="{ showAdvanced: false }" class="flex flex-col gap-2">
<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" /> <button @click="showAdvanced = !showAdvanced" type="button"
class="text-left text-sm text-gray-600 dark:text-gray-300 hover:underline">
Advanced Settings
</button>
<div x-show="showAdvanced" class="flex flex-col gap-2 lg:flex-row">
<x-forms.input placeholder="Port number of your server. Default is 22." label="Port"
id="remoteServerPort" wire:model="remoteServerPort" />
<div class="w-full">
<x-forms.input placeholder="Default is root." label="User"
id="remoteServerUser" wire:model="remoteServerUser" />
<div class="text-xs text-gray-600 dark:text-gray-300">Non-root user is
experimental: <a class="font-bold underline" target="_blank"
href="https://coolify.io/docs/knowledge-base/server/non-root-user">upstream docs</a>.
</div>
</div>
</div>
</div>
<div class="lg: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='dark:text-warning'>Coolify does not install/setup Cloudflare (cloudflared) on your server.</span>"
id="isCloudflareTunnel" label="Cloudflare Tunnel" wire:model="isCloudflareTunnel" />
</div> </div>
<x-forms.button type="submit">Continue</x-forms.button> <x-forms.button type="submit">Continue</x-forms.button>
</form> </form>
</x-slot:actions> </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> </x-boarding-step>
@endif @elseif ($currentState === 'validate-server')
</div>
<div>
@if ($currentState === 'validate-server')
<x-boarding-step title="Validate & Configure Server"> <x-boarding-step title="Validate & Configure Server">
<x-slot:question> <x-slot:question>
we need to validate your server (connection, Docker Engine, etc) and configure to see if something is I need to validate your server (connection, Docker Engine, etc) and configure if something is
missing. Are you okay with this? missing for me. Are you okay with this?
</x-slot:question> </x-slot:question>
<x-slot:actions> <x-slot:actions>
<x-slide-over closeWithX fullScreen> <x-slide-over closeWithX fullScreen>
@ -441,65 +326,39 @@
<x-slot:content> <x-slot:content>
<livewire:server.validate-and-install :server="$this->createdServer" /> <livewire:server.validate-and-install :server="$this->createdServer" />
</x-slot:content> </x-slot:content>
<x-forms.button @click="slideOverOpen=true" class="font-bold box w-96" wire:click.prevent='installServer' isHighlighted> <x-forms.button @click="slideOverOpen=true" class="w-full font-bold box-boarding lg:w-96"
Send it! wire:click.prevent='installServer' isHighlighted>
Let's do it!
</x-forms.button> </x-forms.button>
</x-slide-over> </x-slide-over>
</x-slot:actions> </x-slot:actions>
<x-slot:explanation> <x-slot:explanation>
<p>This will install the latest Docker Engine on your server, configure a few things to be able <p>This will install the latest Docker Engine on your server, configure a few things to be able
to run optimal.<br><br>Minimum Docker Engine version is: 22<br><br>To manually install Docker to run optimal.<br><br>Minimum Docker Engine version is: 22<br><br>To manually install
Engine, check <a target="_blank" class="underline text-warning" href="https://docs.docker.com/engine/install/#server">this Docker
Engine, check <a target="_blank" class="underline dark:text-warning"
href="https://docs.docker.com/engine/install/#server">this
documentation</a>.</p> documentation</a>.</p>
</x-slot:explanation> </x-slot:explanation>
</x-boarding-step> </x-boarding-step>
@endif @elseif ($currentState === 'create-project')
</div>
{{-- <div>
@if ($currentState === 'select-proxy')
<x-boarding-step title="Select a Proxy">
<x-slot:question>
If you would like to attach any kind of domain to your resources, you need a proxy.
</x-slot:question>
<x-slot:actions>
<x-forms.button wire:click="selectProxy" class="w-64 box">
Decide later
</x-forms.button>
<x-forms.button class="w-32 box" wire:click="selectProxy('{{ ProxyTypes::TRAEFIK_V2 }}')">
Traefik
v2
</x-forms.button>
<x-forms.button disabled class="w-32 box">
Nginx
</x-forms.button>
<x-forms.button disabled class="w-32 box">
Caddy
</x-forms.button>
</x-slot:actions>
<x-slot:explanation>
<p>This will install the latest Docker Engine on your server and configure a few items to be able
to run optimally.</p>
</x-slot:explanation>
</x-boarding-step>
@endif
</div> --}}
<div>
@if ($currentState === 'create-project')
<x-boarding-step title="Project"> <x-boarding-step title="Project">
<x-slot:question> <x-slot:question>
@if (count($projects) > 0) @if (count($projects) > 0)
You already have some projects. Do you want to use one of them or should we create a new one for You already have some projects. Do you want to use one of them or should I create a new one
for
you? you?
@else @else
We will create an initial project for you. You can change it later on. Let's create an initial project for you. You can change all the details later on.
@endif @endif
</x-slot:question> </x-slot:question>
<x-slot:actions> <x-slot:actions>
<x-forms.button class="justify-center w-64 box" wire:click="createNewProject">Create New Project</x-forms.button> <x-forms.button class="justify-center w-64 box-boarding" wire:click="createNewProject">Create new
project!</x-forms.button>
<div> <div>
@if (count($projects) > 0) @if (count($projects) > 0)
<form wire:submit='selectExistingProject' class="flex flex-col w-full gap-4 lg:w-96"> <form wire:submit='selectExistingProject' class="flex flex-col w-full gap-4 lg:w-96">
<x-forms.select label="Existing projects" class="w-96" id='selectedExistingProject'> <x-forms.select label="Existing projects" class="w-96" id='selectedProject'>
@foreach ($projects as $project) @foreach ($projects as $project)
<option wire:key="{{ $loop->index }}" value="{{ $project->id }}"> <option wire:key="{{ $loop->index }}" value="{{ $project->id }}">
{{ $project->name }} {{ $project->name }}
@ -512,32 +371,42 @@
</div> </div>
</x-slot:actions> </x-slot:actions>
<x-slot:explanation> <x-slot:explanation>
<p>Projects put together several resources into one virtual group. There are no <p>Projects contain several resources combined into one virtual group. There are no
limitations on the number of projects you can have here.</p> limitations on the number of projects you can add.</p>
<p>Each project should have at least one environment. This helps you to create a production & <p>Each project should have at least one environment, this allows you to create a production &
staging version of the same application, but grouped separately.</p> staging version of the same application, but grouped separately.</p>
</x-slot:explanation> </x-slot:explanation>
</x-boarding-step> </x-boarding-step>
@endif @elseif ($currentState === 'create-resource')
</div>
<div>
@if ($currentState === 'create-resource')
<x-boarding-step title="Resources"> <x-boarding-step title="Resources">
<x-slot:question> <x-slot:question>
Next we will redirect you to the resource page, where you can create your first resource. Let's go to the new resource page, where you can create your first resource.
</x-slot:question> </x-slot:question>
<x-slot:actions> <x-slot:actions>
<div class="items-center justify-center w-64 box" wire:click="showNewResource">Let's do <div class="items-center justify-center w-64 box-boarding" wire:click="showNewResource">Let's do
it!</div> it!</div>
</x-slot:actions> </x-slot:actions>
<x-slot:explanation> <x-slot:explanation>
<p>A resource is an application, a database or a service (like WordPress).</p> <p>A resource could be an application, a database or a service (like WordPress).</p>
</x-slot:explanation> </x-slot:explanation>
</x-boarding-step> </x-boarding-step>
@endif @endif
</div> </div>
<div class="flex justify-center gap-2 pt-4"> <div class="flex flex-col justify-center gap-4 pt-4 lg:gap-2 lg:flex">
<a wire:click='skipBoarding'>Skip Guided Tour</a> <div class="flex justify-center w-full gap-2">
<a wire:click='restartBoarding'>Restart Guided Tour</a> <div class="cursor-pointer hover:underline dark:hover:text-white" wire:click='skipBoarding'>Skip
onboarding</div>
<div class="cursor-pointer hover:underline dark:hover:text-white" wire:click='restartBoarding'>Restart
onboarding</div>
</div> </div>
<x-modal-input title="How can we help?">
<x-slot:content>
<div class="w-full text-center cursor-pointer hover:underline dark:hover:text-white"
title="Send us feedback or get help!">
Feedback
</div> </div>
</x-slot:content>
<livewire:help />
</x-modal-input>
</div>
</section>

View File

@ -2,7 +2,7 @@ set -e
export IMAGE=$1 export IMAGE=$1
docker system prune -af docker system prune -af
docker compose pull docker compose pull
read -p "Press Enter to update Coolify to $IMAGE..." </dev/tty read -p "Press Enter to update Last Hour Cloud to $IMAGE..." </dev/tty
docker exec coolify sh -c "php artisan tinker --execute='isAnyDeploymentInprogress()'" docker exec coolify sh -c "php artisan tinker --execute='isAnyDeploymentInprogress()'"
docker compose up --remove-orphans --force-recreate -d --wait docker compose up --remove-orphans --force-recreate -d --wait
echo $IMAGE > last_version echo $IMAGE > last_version

View File

@ -1,283 +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.2"
DOCKER_VERSION="24.0"
CDN="https://cdn.lasthourhosting.org/lasthourcloud"
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=$(wget -q -O - $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 | almalinux) ;;
*)
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 Last Hour Cloud v4 development installer!"
echo -e "This script will install everything for you."
echo -e "(Source code: https://githaven.org/Shiloh/lasthourcloud/src/branch/main/scripts/dev_install.sh)\n"
echo -e "-------------"
echo "OS: $OS_TYPE $OS_VERSION"
echo "Last Hour Cloud 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 | almalinux)
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 Last Hour Cloud 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 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.)"
echo "###############################################################################"
fi
if ! [ -x "$(command -v docker)" ]; then
if [ "$OS_TYPE" == 'almalinux' ]; then
dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
if ! [ -x "$(command -v docker)" ]; then
echo "Docker could not be installed automatically. Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
exit 1
fi
systemctl start docker
systemctl enable docker
else
set +e
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
set -e
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 "-------------"
mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,webhooks-during-maintenance}
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/scripts/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.dev.yml /data/coolify/source/docker-compose.dev.yml
cp /home/lasthour/lasthourcloud/.env.production /data/coolify/source/.env.production
cp /home/lasthour/lasthourcloud/scripts/dev_upgrade.sh /data/coolify/source/dev_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 dev_upgrade.sh"
bash /data/coolify/source/dev_upgrade.sh "${LATEST_VERSION:-latest}"
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."

View File

@ -1,41 +0,0 @@
#!/bin/bash
## Do not modify this file. You will lose the ability to autoupdate!
VERSION="1.0.4"
CDN="https://cdn.lasthourhosting.org/lasthourcloud"
# 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
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.dev.yml /data/coolify/source/docker-compose.dev.yml
cp /home/lasthour/lasthourcloud/.env.production /data/coolify/source/.env.production
# 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
# Check if PUSHER_APP_ID or PUSHER_APP_KEY or PUSHER_APP_SECRET is empty in /data/coolify/source/.env
if grep -q "PUSHER_APP_ID=$" /data/coolify/source/.env; then
sed -i "s|PUSHER_APP_ID=.*|PUSHER_APP_ID=$(openssl rand -hex 32)|g" /data/coolify/source/.env
fi
if grep -q "PUSHER_APP_KEY=$" /data/coolify/source/.env; then
sed -i "s|PUSHER_APP_KEY=.*|PUSHER_APP_KEY=$(openssl rand -hex 32)|g" /data/coolify/source/.env
fi
if grep -q "PUSHER_APP_SECRET=$" /data/coolify/source/.env; then
sed -i "s|PUSHER_APP_SECRET=.*|PUSHER_APP_SECRET=$(openssl rand -hex 32)|g" /data/coolify/source/.env
fi
# Make sure coolify network exists
docker network create --attachable coolify 2>/dev/null
# docker network create --attachable --driver=overlay coolify-overlay 2>/dev/null
if [ -f /data/coolify/source/docker-compose.custom.yml ]; then
echo "docker-compose.custom.yml detected."
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.dev.yml -f /data/coolify/source/docker-compose.custom.yml up -d --pull always --remove-orphans --force-recreate"
else
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.dev.yml up -d --pull always --remove-orphans --force-recreate"
fi

View File

@ -1,15 +1,36 @@
#!/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
#set -u # Treat unset variables as an error and exit #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 set -o pipefail # Cause a pipeline to return the status of the last command that exited with a non-zero status
CDN="https://cdn.lasthourhosting.org/lasthourcloudprod"
DATE=$(date +"%Y%m%d-%H%M%S")
VERSION="1.3.3" VERSION="1.5"
DOCKER_VERSION="26.0" DOCKER_VERSION="26.0"
CDN="https://cdn.lasthourhosting.org/lasthourcloud" mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,webhooks-during-maintenance,metrics,logs}
mkdir -p /data/coolify/ssh/{keys,mux}
mkdir -p /data/coolify/proxy/dynamic
chown -R 9999:root /data/coolify
chmod -R 700 /data/coolify
INSTALLATION_LOG_WITH_DATE="/data/coolify/source/installation-${DATE}.log"
exec > >(tee -a $INSTALLATION_LOG_WITH_DATE) 2>&1
getAJoke() {
JOKES=$(curl -s --max-time 2 https://v2.jokeapi.dev/joke/Programming?format=txt&type=single&amount=1 || true)
if [ "$JOKES" != "" ]; then
echo -e " - Until then, here's a joke for you:\n"
echo -e "$JOKES\n"
fi
}
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 '"')
ENV_FILE="/data/coolify/source/.env"
# Check if the OS is manjaro, if so, change it to arch # Check if the OS is manjaro, if so, change it to arch
if [ "$OS_TYPE" = "manjaro" ] || [ "$OS_TYPE" = "manjaro-arm" ]; then if [ "$OS_TYPE" = "manjaro" ] || [ "$OS_TYPE" = "manjaro-arm" ]; then
@ -43,7 +64,17 @@ if [ "$OS_TYPE" = 'amzn' ]; then
fi fi
LATEST_VERSION=$(curl --silent $CDN/versions.json | grep -i version | xargs | awk '{print $2}' | tr -d ',') LATEST_VERSION=$(curl --silent $CDN/versions.json | grep -i version | xargs | awk '{print $2}' | tr -d ',')
DATE=$(date +"%Y%m%d-%H%M%S") LATEST_HELPER_VERSION=$(curl --silent $CDN/versions.json | grep -i version | xargs | awk '{print $6}' | tr -d ',')
LATEST_REALTIME_VERSION=$(curl --silent $CDN/versions.json | grep -i version | xargs | awk '{print $8}' | tr -d ',')
if [ -z "$LATEST_HELPER_VERSION" ]; then
LATEST_HELPER_VERSION=latest
fi
if [ -z "$LATEST_REALTIME_VERSION" ]; then
LATEST_REALTIME_VERSION=latest
fi
if [ $EUID != 0 ]; then if [ $EUID != 0 ]; then
echo "Please run as root" echo "Please run as root"
@ -51,9 +82,9 @@ if [ $EUID != 0 ]; then
fi fi
case "$OS_TYPE" in case "$OS_TYPE" in
arch | ubuntu | debian | raspbian | centos | fedora | rhel | ol | rocky | sles | opensuse-leap | opensuse-tumbleweed | almalinux | amzn) ;; arch | ubuntu | debian | raspbian | centos | fedora | rhel | ol | rocky | sles | opensuse-leap | opensuse-tumbleweed | almalinux | amzn | alpine) ;;
*) *)
echo "This script only supports Debian, Redhat, Arch Linux, or SLES based operating systems for now." echo "This script only supports Debian, Redhat, Arch Linux, Alpine Linux, or SLES based operating systems for now."
exit exit
;; ;;
esac esac
@ -65,33 +96,41 @@ if [ "$1" != "" ]; then
LATEST_VERSION="${LATEST_VERSION#v}" LATEST_VERSION="${LATEST_VERSION#v}"
fi fi
echo -e "-------------" echo -e "\033[0;35m"
echo -e "Welcome to Last Hour Cloud v4 installer!" cat << "EOF"
echo -e "This script will install everything for you."
<<<<<<< HEAD
<<<<<<< HEAD
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 "(Source code: https://githaven.org/Shiloh/lasthourcloud/src/branch/main/scripts/install.sh)\n"
>>>>>>> 4e89beaf1 (main: fix CDN url)
echo -e "-------------"
echo "OS: $OS_TYPE $OS_VERSION" ██ ███████ ███████ ██ ██ ███████ ██ ███████ ██ ██ ██ ███ ██ ██████
echo "Last Hour Cloud version: $LATEST_VERSION" ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██
██ █████ ███████ ██ ██ ███████ ██ ███████ █████ ██ ██ ██ ██ ██ ███
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
█████ ███████ ███████ ██████ ███████ ██ ███████ ██ ██ ██ ██ ████ ██████
echo -e "-------------" EOF
echo "Installing required packages..." echo -e "\033[0m"
echo -e "Welcome to Last Hour Cloud Installer!"
echo -e "This script will install everything for you. Sit back and relax."
echo -e "Source code: https://githaven.org/Shiloh/lasthourcloud/src/branch/prod/scripts/install.sh\n"
echo -e "---------------------------------------------"
echo "| Operating System | $OS_TYPE $OS_VERSION"
echo "| Docker | $DOCKER_VERSION"
echo "| Last Hour Cloud | $LATEST_VERSION"
echo "| Helper | $LATEST_HELPER_VERSION"
echo "| Realtime | $LATEST_REALTIME_VERSION"
echo -e "---------------------------------------------\n"
echo -e "1. Installing required packages (curl, wget, git, jq). "
case "$OS_TYPE" in case "$OS_TYPE" in
arch) arch)
pacman -Sy --noconfirm --needed curl wget git jq >/dev/null || true pacman -Sy --noconfirm --needed curl wget git jq >/dev/null || true
;; ;;
alpine)
sed -i '/^#.*\/community/s/^#//' /etc/apk/repositories
apk update >/dev/null
apk add curl wget git jq >/dev/null
;;
ubuntu | debian | raspbian) ubuntu | debian | raspbian)
apt update -y >/dev/null apt-get update -y >/dev/null
apt install -y curl wget git jq >/dev/null apt-get install -y curl wget git jq >/dev/null
;; ;;
centos | fedora | rhel | ol | rocky | almalinux | amzn) centos | fedora | rhel | ol | rocky | almalinux | amzn)
if [ "$OS_TYPE" = "amzn" ]; then if [ "$OS_TYPE" = "amzn" ]; then
@ -100,7 +139,10 @@ centos | fedora | rhel | ol | rocky | almalinux | amzn)
if ! command -v dnf >/dev/null; then if ! command -v dnf >/dev/null; then
yum install -y dnf >/dev/null yum install -y dnf >/dev/null
fi fi
dnf install -y curl wget git jq >/dev/null if ! command -v curl >/dev/null; then
dnf install -y curl >/dev/null
fi
dnf install -y wget git jq >/dev/null
fi fi
;; ;;
sles | opensuse-leap | opensuse-tumbleweed) sles | opensuse-leap | opensuse-tumbleweed)
@ -113,24 +155,26 @@ sles | opensuse-leap | opensuse-tumbleweed)
;; ;;
esac esac
echo -e "2. Check OpenSSH server configuration. "
# Detect OpenSSH server # Detect OpenSSH server
SSH_DETECTED=false SSH_DETECTED=false
if [ -x "$(command -v systemctl)" ]; then if [ -x "$(command -v systemctl)" ]; then
if systemctl status sshd >/dev/null 2>&1; then if systemctl status sshd >/dev/null 2>&1; then
echo "OpenSSH server is installed." echo " - OpenSSH server is installed."
SSH_DETECTED=true SSH_DETECTED=true
fi elif systemctl status ssh >/dev/null 2>&1; then
if systemctl status ssh >/dev/null 2>&1; then echo " - OpenSSH server is installed."
echo "OpenSSH server is installed."
SSH_DETECTED=true SSH_DETECTED=true
fi fi
elif [ -x "$(command -v service)" ]; then elif [ -x "$(command -v service)" ]; then
if service sshd status >/dev/null 2>&1; then if service sshd status >/dev/null 2>&1; then
echo "OpenSSH server is installed." echo " - OpenSSH server is installed."
SSH_DETECTED=true SSH_DETECTED=true
fi elif service ssh status >/dev/null 2>&1; then
if service ssh status >/dev/null 2>&1; then echo " - OpenSSH server is installed."
echo "OpenSSH server is installed."
SSH_DETECTED=true SSH_DETECTED=true
fi fi
fi fi
@ -142,105 +186,91 @@ if [ "$SSH_DETECTED" = "false" ]; then
fi fi
# Detect SSH PermitRootLogin # Detect SSH PermitRootLogin
SSH_PERMIT_ROOT_LOGIN=false SSH_PERMIT_ROOT_LOGIN=$(sshd -T | grep -i "permitrootlogin" | awk '{print $2}') || true
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" = "yes" ] || [ "$SSH_PERMIT_ROOT_LOGIN" = "without-password" ] || [ "$SSH_PERMIT_ROOT_LOGIN" = "prohibit-password" ]; then
if [ "$SSH_PERMIT_ROOT_LOGIN_CONFIG" = "prohibit-password" ] || [ "$SSH_PERMIT_ROOT_LOGIN_CONFIG" = "yes" ] || [ "$SSH_PERMIT_ROOT_LOGIN_CONFIG" = "without-password" ]; then echo " - SSH PermitRootLogin is enabled."
echo "PermitRootLogin is enabled." else
SSH_PERMIT_ROOT_LOGIN=true echo " - SSH PermitRootLogin is disabled."
fi echo " If you have problems with SSH, please read this upstream documentation: https://coolify.io/docs/knowledge-base/server/openssh"
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"
<<<<<<< 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 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 "###############################################################################"
fi fi
# Detect if docker is installed via snap # Detect if docker is installed via snap
if [ -x "$(command -v snap)" ]; then if [ -x "$(command -v snap)" ]; then
if snap list | grep -q docker; then SNAP_DOCKER_INSTALLED=$(snap list docker >/dev/null 2>&1 && echo "true" || echo "false")
echo "Docker is installed via snap." if [ "$SNAP_DOCKER_INSTALLED" = "true" ]; then
echo "Please note that Coolify does not support Docker installed via snap." echo " - Docker is installed via snap."
echo "Please remove Docker with snap (snap remove docker) and reexecute this script." echo " Please note that Last Hour Cloud does not support Docker installed via snap."
echo " Please remove Docker with snap (snap remove docker) and reexecute this script."
exit 1 exit 1
fi fi
fi fi
echo -e "3. Check Docker Installation. "
if ! [ -x "$(command -v docker)" ]; then if ! [ -x "$(command -v docker)" ]; then
# Almalinux echo " - Docker is not installed. Installing Docker. It may take a while."
if [ "$OS_TYPE" == 'almalinux' ]; then getAJoke
dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo case "$OS_TYPE" in
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin "almalinux")
dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo >/dev/null 2>&1
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin >/dev/null 2>&1
if ! [ -x "$(command -v docker)" ]; then if ! [ -x "$(command -v docker)" ]; then
echo "Docker could not be installed automatically. Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue." echo " - Docker could not be installed automatically. Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
exit 1 exit 1
fi fi
systemctl start docker systemctl start docker >/dev/null 2>&1
systemctl enable docker systemctl enable docker >/dev/null 2>&1
else ;;
set +e "alpine")
apk add docker docker-cli-compose >/dev/null 2>&1
rc-update add docker default >/dev/null 2>&1
service docker start >/dev/null 2>&1
if ! [ -x "$(command -v docker)" ]; then if ! [ -x "$(command -v docker)" ]; then
echo "Docker is not installed. Installing Docker." echo " - Failed to install Docker with apk. Try to install it manually."
# Arch Linux echo " Please visit https://wiki.alpinelinux.org/wiki/Docker for more information."
if [ "$OS_TYPE" = "arch" ]; then exit 1
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 fi
else ;;
# Amazon Linux 2023 "arch")
if [ "$OS_TYPE" = "amzn" ]; then pacman -Sy docker docker-compose --noconfirm >/dev/null 2>&1
dnf install docker -y systemctl enable docker.service >/dev/null 2>&1
if ! [ -x "$(command -v docker)" ]; then
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 1
fi
;;
"amzn")
dnf install docker -y >/dev/null 2>&1
DOCKER_CONFIG=${DOCKER_CONFIG:-/usr/local/lib/docker} DOCKER_CONFIG=${DOCKER_CONFIG:-/usr/local/lib/docker}
mkdir -p $DOCKER_CONFIG/cli-plugins mkdir -p $DOCKER_CONFIG/cli-plugins >/dev/null 2>&1
curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o $DOCKER_CONFIG/cli-plugins/docker-compose curl -sL https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o $DOCKER_CONFIG/cli-plugins/docker-compose >/dev/null 2>&1
chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose >/dev/null 2>&1
systemctl start docker systemctl start docker >/dev/null 2>&1
systemctl enable docker systemctl enable docker >/dev/null 2>&1
if [ -x "$(command -v docker)" ]; then if ! [ -x "$(command -v docker)" ]; then
echo "Docker installed successfully." echo " - Failed to install Docker with dnf. Try to install it manually."
else echo " Please visit https://www.cyberciti.biz/faq/how-to-install-docker-on-amazon-linux-2/ for more information."
echo "Failed to install Docker with pacman. Try to install it manually." exit 1
echo "Please visit https://wiki.archlinux.org/title/docker for more information."
exit
fi fi
else ;;
# Automated Docker installation *)
curl https://releases.rancher.com/install-docker/${DOCKER_VERSION}.sh | sh curl -s https://releases.rancher.com/install-docker/${DOCKER_VERSION}.sh | sh >/dev/null 2>&1
if [ -x "$(command -v docker)" ]; then if ! [ -x "$(command -v docker)" ]; then
echo "Docker installed successfully." curl -s https://get.docker.com | sh -s -- --version ${DOCKER_VERSION} >/dev/null 2>&1
else if ! [ -x "$(command -v docker)" ]; then
echo "Docker installation failed with Rancher script. Trying with official script." echo " - Docker installation failed."
curl https://get.docker.com | sh -s -- --version ${DOCKER_VERSION} echo " Maybe your OS is not supported?"
if [ -x "$(command -v docker)" ]; then echo " - Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
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 exit 1
fi fi
fi fi
fi esac
fi echo " - Docker installed successfully."
fi else
set -e echo " - Docker is installed."
fi
fi fi
echo -e "-------------" echo -e "4. Check Docker Configuration. "
echo -e "Check Docker Configuration..."
mkdir -p /etc/docker mkdir -p /etc/docker
# shellcheck disable=SC2015 # 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 test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json /etc/docker/daemon.json.original-"$DATE" || cat >/etc/docker/daemon.json <<EOL
@ -268,55 +298,87 @@ if ! jq -s '.[0] * .[1]' /etc/docker/daemon.json /etc/docker/daemon.json.coolify
fi fi
mv "$TEMP_FILE" /etc/docker/daemon.json mv "$TEMP_FILE" /etc/docker/daemon.json
restart_docker_service() {
# Check if systemctl is available
if command -v systemctl >/dev/null 2>&1; then
echo " - Using systemctl to restart Docker."
systemctl restart docker
if [ $? -eq 0 ]; then
echo " - Docker restarted successfully using systemctl."
else
echo " - Failed to restart Docker using systemctl."
return 1
fi
# Check if service command is available
elif command -v service >/dev/null 2>&1; then
echo " - Using service command to restart Docker."
service docker restart
if [ $? -eq 0 ]; then
echo " - Docker restarted successfully using service."
else
echo " - Failed to restart Docker using service."
return 1
fi
# If neither systemctl nor service is available
else
echo " - Neither systemctl nor service command is available on this system."
return 1
fi
}
if [ -s /etc/docker/daemon.json.original-"$DATE" ]; then 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")) DIFF=$(diff <(jq --sort-keys . /etc/docker/daemon.json) <(jq --sort-keys . /etc/docker/daemon.json.original-"$DATE"))
if [ "$DIFF" != "" ]; then if [ "$DIFF" != "" ]; then
echo "Docker configuration updated, restart docker daemon..." echo " - Docker configuration updated, restart docker daemon..."
systemctl restart docker restart_docker_service
else else
echo "Docker configuration is up to date." echo " - Docker configuration is up to date."
fi fi
else else
echo "Docker configuration updated, restart docker daemon..." echo " - Docker configuration updated, restart docker daemon..."
systemctl restart docker restart_docker_service
fi fi
echo -e "-------------" echo -e "5. Download required files from CDN. "
mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,webhooks-during-maintenance,metrics,logs}
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.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/scripts/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..." echo -e "6. Make backup of .env to .env-$DATE"
# 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 $ENV_FILE ]; then
cp /data/coolify/source/.env.production /data/coolify/source/.env cp $ENV_FILE $ENV_FILE-$DATE
sed -i "s|APP_ID=.*|APP_ID=$(openssl rand -hex 16)|g" /data/coolify/source/.env else
sed -i "s|APP_KEY=.*|APP_KEY=base64:$(openssl rand -base64 32)|g" /data/coolify/source/.env echo " - File does not exist: $ENV_FILE"
sed -i "s|DB_PASSWORD=.*|DB_PASSWORD=$(openssl rand -base64 32)|g" /data/coolify/source/.env echo " - Copying .env.production to .env-$DATE"
sed -i "s|REDIS_PASSWORD=.*|REDIS_PASSWORD=$(openssl rand -base64 32)|g" /data/coolify/source/.env cp /data/coolify/source/.env.production $ENV_FILE-$DATE
sed -i "s|PUSHER_APP_ID=.*|PUSHER_APP_ID=$(openssl rand -hex 32)|g" /data/coolify/source/.env # Generate a secure APP_ID and APP_KEY
sed -i "s|PUSHER_APP_KEY=.*|PUSHER_APP_KEY=$(openssl rand -hex 32)|g" /data/coolify/source/.env sed -i "s|^APP_ID=.*|APP_ID=$(openssl rand -hex 16)|" "$ENV_FILE-$DATE"
sed -i "s|PUSHER_APP_SECRET=.*|PUSHER_APP_SECRET=$(openssl rand -hex 32)|g" /data/coolify/source/.env sed -i "s|^APP_KEY=.*|APP_KEY=base64:$(openssl rand -base64 32)|" "$ENV_FILE-$DATE"
# Generate a secure Postgres DB username and password
# Causes issues: database "random-user" does not exist
# sed -i "s|^DB_USERNAME=.*|DB_USERNAME=$(openssl rand -hex 16)|" "$ENV_FILE-$DATE"
sed -i "s|^DB_PASSWORD=.*|DB_PASSWORD=$(openssl rand -base64 32)|" "$ENV_FILE-$DATE"
# Generate a secure Redis password
sed -i "s|^REDIS_PASSWORD=.*|REDIS_PASSWORD=$(openssl rand -base64 32)|" "$ENV_FILE-$DATE"
# Generate secure Pusher credentials
sed -i "s|^PUSHER_APP_ID=.*|PUSHER_APP_ID=$(openssl rand -hex 32)|" "$ENV_FILE-$DATE"
sed -i "s|^PUSHER_APP_KEY=.*|PUSHER_APP_KEY=$(openssl rand -hex 32)|" "$ENV_FILE-$DATE"
sed -i "s|^PUSHER_APP_SECRET=.*|PUSHER_APP_SECRET=$(openssl rand -hex 32)|" "$ENV_FILE-$DATE"
fi fi
# Merge .env and .env.production. New values will be added to .env # 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 echo -e "7. Propagating .env with new values - if necessary."
awk -F '=' '!seen[$1]++' "$ENV_FILE-$DATE" /data/coolify/source/.env.production > $ENV_FILE
if [ "$AUTOUPDATE" = "false" ]; then if [ "$AUTOUPDATE" = "false" ]; then
if ! grep -q "AUTOUPDATE=" /data/coolify/source/.env; then if ! grep -q "AUTOUPDATE=" /data/coolify/source/.env; then
@ -325,32 +387,122 @@ if [ "$AUTOUPDATE" = "false" ]; then
sed -i "s|AUTOUPDATE=.*|AUTOUPDATE=false|g" /data/coolify/source/.env sed -i "s|AUTOUPDATE=.*|AUTOUPDATE=false|g" /data/coolify/source/.env
fi fi
fi fi
echo -e "8. Checking for SSH key for localhost access."
# 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 if [ ! -f ~/.ssh/authorized_keys ]; then
mkdir -p ~/.ssh mkdir -p ~/.ssh
chmod 700 ~/.ssh chmod 700 ~/.ssh
touch ~/.ssh/authorized_keys touch ~/.ssh/authorized_keys
addSshKey chmod 600 ~/.ssh/authorized_keys
fi fi
if ! grep -qw "root@coolify" ~/.ssh/authorized_keys; then checkSshKeyInAuthorizedKeys() {
addSshKey grep -qw "root@coolify" ~/.ssh/authorized_keys
fi return $?
echo "Generated SSH access" }
echo "Begin upgrade.sh" checkSshKeyInCoolifyData() {
bash /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}" [ -s /data/coolify/ssh/keys/id.root@host.docker.internal ]
return $?
}
echo -e "\nCongratulations! Your Last Hour Cloud instance is ready to use.\n" generateAuthorizedKeys() {
echo "Please visit http://$(curl -4s https://ifconfig.io):8000 to get started." sed -i "/root@coolify/d" ~/.ssh/authorized_keys
cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >> ~/.ssh/authorized_keys
rm -f /data/coolify/ssh/keys/id.root@host.docker.internal.pub
}
generateSshKey() {
echo " - Generating SSH key."
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
generateAuthorizedKeys
}
syncSshKeys() {
DB_RUNNING=$(docker inspect coolify-db --format '{{ .State.Status }}' 2>/dev/null)
# Check if SSH key exists in Coolify data but not in authorized_keys
if checkSshKeyInCoolifyData && ! checkSshKeyInAuthorizedKeys; then
# Add the existing Coolify SSH key to authorized_keys
cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >> ~/.ssh/authorized_keys
# Check if SSH key exists in authorized_keys but not in Coolify data
elif checkSshKeyInAuthorizedKeys && ! checkSshKeyInCoolifyData; then
# Ensure Coolify DB is running before proceeding
if [ "$DB_RUNNING" = "running" ]; then
# Retrieve DB user and SSH key from Coolify database
DB_USER=$(docker inspect coolify-db --format '{{ .Config.Env }}' | grep -oP 'POSTGRES_USER=\K[^ ]+')
DB_SSH_KEY=$(docker exec coolify-db psql -U $DB_USER -d coolify -t -c "SELECT \"private_key\" FROM \"private_keys\" WHERE id = 0 AND team_id = 0 LIMIT 1;" -A -t)
if [ -z "$DB_SSH_KEY" ]; then
# If no key found in DB, generate a new one
echo " - SSH key not found in database. Generating new key."
generateSshKey
else
# If key found in DB, save it and update authorized_keys
echo " - SSH key found in database. Saving to file."
echo "$DB_SSH_KEY" > /data/coolify/ssh/keys/id.root@host.docker.internal
chmod 600 /data/coolify/ssh/keys/id.root@host.docker.internal
chown 9999 /data/coolify/ssh/keys/id.root@host.docker.internal
# Generate public key from private key and update authorized_keys
ssh-keygen -y -f /data/coolify/ssh/keys/id.root@host.docker.internal -C root@coolify > /data/coolify/ssh/keys/id.root@host.docker.internal.pub
sed -i "/root@coolify/d" ~/.ssh/authorized_keys
cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >> ~/.ssh/authorized_keys
rm -f /data/coolify/ssh/keys/id.root@host.docker.internal.pub
chmod 600 ~/.ssh/authorized_keys
fi
fi
# If SSH key doesn't exist in either location
elif ! checkSshKeyInAuthorizedKeys && ! checkSshKeyInCoolifyData; then
# Ensure Coolify DB is running before proceeding
if [ "$DB_RUNNING" = "running" ]; then
# Retrieve DB user and SSH key from Coolify database
DB_USER=$(docker inspect coolify-db --format '{{ .Config.Env }}' | grep -oP 'POSTGRES_USER=\K[^ ]+')
DB_SSH_KEY=$(docker exec coolify-db psql -U $DB_USER -d coolify -t -c "SELECT \"private_key\" FROM \"private_keys\" WHERE id = 0 AND team_id = 0 LIMIT 1;" -A -t)
if [ -z "$DB_SSH_KEY" ]; then
# If no key found in DB, generate a new one
echo " - SSH key not found in database. Generating new key."
generateSshKey
else
# If key found in DB, save it and update authorized_keys
echo " - SSH key found in database. Saving to file."
echo "$DB_SSH_KEY" > /data/coolify/ssh/keys/id.root@host.docker.internal
chmod 600 /data/coolify/ssh/keys/id.root@host.docker.internal
ssh-keygen -y -f /data/coolify/ssh/keys/id.root@host.docker.internal -C root@coolify > /data/coolify/ssh/keys/id.root@host.docker.internal.pub
sed -i "/root@coolify/d" ~/.ssh/authorized_keys
cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >> ~/.ssh/authorized_keys
fi
else
generateSshKey
fi
fi
}
syncSshKeys || true
chown -R 9999:root /data/coolify
chmod -R 700 /data/coolify
echo -e "9. Installing Coolify ($LATEST_VERSION)"
echo -e " - It could take a while based on your server's performance, network speed, stars, etc."
echo -e " - Please wait."
getAJoke
bash /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}" "${LATEST_HELPER_VERSION:-latest}" >/dev/null 2>&1
echo " - Last Hour Cloud installed successfully."
rm -f $ENV_FILE-$DATE
echo " - Waiting for 20 seconds for Last Hour Cloud (database migrations) to be ready."
getAJoke
sleep 20
echo -e "\033[0;35m
____ _ _ _ _ _
/ ___|___ _ __ __ _ _ __ __ _| |_ _ _| | __ _| |_(_) ___ _ __ ___| |
| | / _ \| '_ \ / _\` | '__/ _\` | __| | | | |/ _\` | __| |/ _ \| '_ \/ __| |
| |__| (_) | | | | (_| | | | (_| | |_| |_| | | (_| | |_| | (_) | | | \__ \_|
\____\___/|_| |_|\__, |_| \__,_|\__|\__,_|_|\__,_|\__|_|\___/|_| |_|___(_)
|___/
\033[0m"
echo -e "\nYour instance is ready to use."
echo -e "Please visit http://$(curl -4s https://ifconfig.io):8000 to get started.\n"
echo -e "WARNING: We recommend you to backup your /data/coolify/source/.env file to a safe location, outside of this server."
cp /data/coolify/source/.env /data/coolify/source/.env.backup

View File

@ -1,104 +1,56 @@
#!/usr/bin/env bash #!/bin/bash
# Sync docker volumes between two servers
# Inspired on https://github.com/adriancooney/Taskfile VERSION="1.0.0"
# SOURCE=$1
# Install an alias, to be able to simply execute `run` DESTINATION=$2
# echo 'alias run=./scripts/run' >> ~/.aliases
#
# Define Docker Compose command prefix...
set -e set -e
if [ -z "$SOURCE" ]; then
if [ $? == 0 ]; then echo "Source server is not specified."
DOCKER_COMPOSE="docker compose" exit 1
else fi
DOCKER_COMPOSE="docker-compose" if [ -z "$DESTINATION" ]; then
echo "Destination server is not specified."
exit 1
fi fi
function help { SOURCE_USER=$(echo $SOURCE | cut -d@ -f1)
echo "$0 <task> <args>" SOURCE_SERVER=$(echo $SOURCE | cut -d: -f1 | cut -d@ -f2)
echo "Tasks:" SOURCE_PORT=$(echo $SOURCE | cut -d: -f2 | cut -d/ -f1)
compgen -A function | cat -n SOURCE_VOLUME_NAME=$(echo $SOURCE | cut -d/ -f2)
}
# function sync:v3 { if ! [[ "$SOURCE_PORT" =~ ^[0-9]+$ ]]; then
# if [ -z "$1" ]; then echo "Invalid source port: $SOURCE_PORT"
# echo -e "Please provide a version.\n\nExample: run sync:v3 3.12.32" exit 1
# exit 1 fi
# fi
# skopeo copy --all docker://ghcr.io/coollabsio/coolify:$1 docker://coollabsio/coolify:$1
# }
function sync:bunny {
php artisan sync:bunny --env=secrets
}
# function queue { DESTINATION_USER=$(echo $DESTINATION | cut -d@ -f1)
# bash spin exec -u webuser coolify php artisan queue:listen DESTINATION_SERVER=$(echo $DESTINATION | cut -d: -f1 | cut -d@ -f2)
# } DESTINATION_PORT=$(echo $DESTINATION | cut -d: -f2 | cut -d/ -f1)
DESTINATION_VOLUME_NAME=$(echo $DESTINATION | cut -d/ -f2)
# function horizon { if ! [[ "$DESTINATION_PORT" =~ ^[0-9]+$ ]]; then
# bash spin exec -u webuser coolify php artisan horizon -vvv echo "Invalid destination port: $DESTINATION_PORT"
# } exit 1
fi
# function schedule { echo "Generating backup file to ./$SOURCE_VOLUME_NAME.tgz"
# bash spin exec -u webuser coolify php artisan schedule:work ssh -p $SOURCE_PORT $SOURCE_USER@$SOURCE_SERVER "docker run -v $SOURCE_VOLUME_NAME:/volume --rm --log-driver none loomchild/volume-backup backup -c pigz -v" >./$SOURCE_VOLUME_NAME.tgz
# } echo ""
if [ -f "./$SOURCE_VOLUME_NAME.tgz" ]; then
echo "Uploading backup file to $DESTINATION_SERVER:~/$DESTINATION_VOLUME_NAME.tgz"
scp -P $DESTINATION_PORT ./$SOURCE_VOLUME_NAME.tgz $DESTINATION_USER@$DESTINATION_SERVER:~/$DESTINATION_VOLUME_NAME.tgz
echo ""
echo "Restoring backup file on remote ($DESTINATION_SERVER:/~/$DESTINATION_VOLUME_NAME.tgz)"
ssh -p $DESTINATION_PORT $DESTINATION_USER@$DESTINATION_SERVER "docker run -i -v $DESTINATION_VOLUME_NAME:/volume --log-driver none --rm loomchild/volume-backup restore -c pigz -vf < ~/$DESTINATION_VOLUME_NAME.tgz"
echo ""
echo "Deleting backup file on remote ($DESTINATION_SERVER:/~/$DESTINATION_VOLUME_NAME.tgz)"
ssh -p $DESTINATION_PORT $DESTINATION_USER@$DESTINATION_SERVER "rm ~/$DESTINATION_VOLUME_NAME.tgz"
# function schedule:run { echo ""
# bash spin exec -u webuser coolify php artisan schedule:run echo "Local file ./$SOURCE_VOLUME_NAME.tgz is not deleted."
# }
# function db { echo ""
# bash spin exec -u webuser coolify php artisan db echo "WARNING: If you are copying a database volume, you need to set the right users/passwords on the destination service's environment variables."
# } echo "Why? Because we are copying the volume as-is, so the database credentials will bethe same as on the source volume."
# function db:seed { fi
# bash spin exec -u webuser coolify php artisan migrate --seed
# }
# function db:migrate {
# bash spin exec -u webuser coolify php artisan migrate --step
# }
function db:reset {
bash spin exec -u webuser coolify php artisan migrate:fresh --seed
}
function db:reset-prod {
bash spin exec -u webuser coolify php artisan migrate:fresh --force --seed --seeder=ProductionSeeder ||
php artisan migrate:fresh --force --seed --seeder=ProductionSeeder
}
function mfs {
db:reset
}
function coolify {
bash spin exec -u webuser coolify bash
}
function coolify:root {
bash spin exec coolify bash
}
function coolify:proxy {
docker exec -ti coolify-proxy sh
}
function redis {
docker exec -ti coolify-redis redis-cli
}
function vite {
bash spin exec vite bash
}
function tinker {
bash spin exec -u webuser coolify php artisan tinker
}
# function build:helper {
# act -W .github/workflows/coolify-helper.yml --secret-file .env.secrets
# }
function default {
help
}
TIMEFORMAT="Task completed in %3lR"
time "${@:-default}"

View File

@ -1,23 +1,17 @@
#!/bin/bash #!/bin/bash
## 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.1"
CDN="https://cdn.lasthourhosting.org/lasthourcloud" CDN="https://cdn.lasthourhosting.org/lasthourcloudprod"
LATEST_IMAGE=${1:-latest}
LATEST_HELPER_VERSION=${2:-latest}
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 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
# Merge .env and .env.production. New values will be added to .env # 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 awk -F '=' '!seen[$1]++' /data/coolify/source/.env /data/coolify/source/.env.production > /data/coolify/source/.env.tmp && mv /data/coolify/source/.env.tmp /data/coolify/source/.env
# Check if PUSHER_APP_ID or PUSHER_APP_KEY or PUSHER_APP_SECRET is empty in /data/coolify/source/.env # Check if PUSHER_APP_ID or PUSHER_APP_KEY or PUSHER_APP_SECRET is empty in /data/coolify/source/.env
if grep -q "PUSHER_APP_ID=$" /data/coolify/source/.env; then if grep -q "PUSHER_APP_ID=$" /data/coolify/source/.env; then
sed -i "s|PUSHER_APP_ID=.*|PUSHER_APP_ID=$(openssl rand -hex 32)|g" /data/coolify/source/.env sed -i "s|PUSHER_APP_ID=.*|PUSHER_APP_ID=$(openssl rand -hex 32)|g" /data/coolify/source/.env
@ -31,13 +25,14 @@ if grep -q "PUSHER_APP_SECRET=$" /data/coolify/source/.env; then
sed -i "s|PUSHER_APP_SECRET=.*|PUSHER_APP_SECRET=$(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 fi
# Make sure coolify network exists # Make sure Last Hour Cloud network exists
# It is created when starting Last Hour Cloud with docker compose
docker network create --attachable coolify 2>/dev/null docker network create --attachable coolify 2>/dev/null
# docker network create --attachable --driver=overlay coolify-overlay 2>/dev/null # docker network create --attachable --driver=overlay coolify-overlay 2>/dev/null
if [ -f /data/coolify/source/docker-compose.custom.yml ]; then if [ -f /data/coolify/source/docker-compose.custom.yml ]; then
echo "docker-compose.custom.yml detected." echo "docker-compose.custom.yml detected."
docker run -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 -f /data/coolify/source/docker-compose.custom.yml up -d --remove-orphans --force-recreate" docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper:${LATEST_HELPER_VERSION:-latest} 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 -f /data/coolify/source/docker-compose.custom.yml up -d --remove-orphans --force-recreate --wait --wait-timeout 60"
else else
docker run -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 --remove-orphans --force-recreate" docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper:${LATEST_HELPER_VERSION:-latest} 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 --remove-orphans --force-recreate --wait --wait-timeout 60"
fi fi