Compare commits

..

1 Commits
prod ... main

Author SHA1 Message Date
361e0a0430 Delete README.md 2024-07-25 21:19:08 +00:00
12 changed files with 1189 additions and 965 deletions

132
README.md
View File

@ -1,132 +0,0 @@
![Latest Release Version](https://img.shields.io/badge/dynamic/json?labelColor=grey&color=6366f1&label=Latest_released_version&url=https%3A%2F%2Fcdn.coollabs.io%2Fcoolify%2Fversions.json&query=coolify.v4.version&style=for-the-badge
)
[![Bounty Issues](https://img.shields.io/static/v1?labelColor=grey&color=6366f1&label=Algora&message=%F0%9F%92%8E+Bounty+issues&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties/new)
# About the Project
Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc.
It helps you manage your servers, applications, and databases on your own hardware; you only need an SSH connection. You can manage VPS, Bare Metal, Raspberry PIs, and anything else.
Imagine having the ease of a cloud but with your own servers. That is **Coolify**.
No vendor lock-in, which means that all the configurations for your applications/databases/etc are saved to your server. So, if you decide to stop using Coolify (oh nooo), you could still manage your running resources. You lose the automations and all the magic. 🪄️
For more information, take a look at our landing page at [coolify.io](https://coolify.io).
# Installation
```bash
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
```
You can find the installation script source [here](./scripts/install.sh).
# Support
Contact us at [coolify.io/docs/contact](https://coolify.io/docs/contact).
# Donations
To stay completely free and open-source, with no feature behind the paywall and evolve the project, we need your help. If you like Coolify, please consider donating to help us fund the project's future development.
[coolify.io/sponsorships](https://coolify.io/sponsorships)
Thank you so much!
Special thanks to our biggest sponsors!
<a href="https://cccareers.org/" target="_blank"><img src="./other/logos/ccc-logo.webp" alt="cccareers logo" width="200"/></a>
<a href="http://htznr.li/CoolifyXHetzner" target="_blank"><img src="./other/logos/hetzner.jpg" alt="hetzner logo" width="150"/></a>
<a href="https://logto.io/?ref=coolify" target="_blank"><img src="./other/logos/logto.webp" alt="logto logo" width="150"/></a>
<a href="https://bc.direct/?ref=coolify.io" target="_blank"><img src="./other/logos/bc.png" alt="bc direct logo" width="200"/></a>
<a href="https://www.quantcdn.io/?ref=coolify.io" target="_blank"><img src="./other/logos/quant.svg" alt="quantcdn logo" width="150"/></a>
<a href="https://arcjet.com/?ref=coolify.io" target="_blank"><img src="./other/logos/arcjet.svg" alt="arcjet logo" width="200"/></a>
<a href="https://supa.guide/?ref=coolify.io" target="_blank"><img src="./other/logos/supaguide.png" alt="supaguide logo" width="200"/></a>
<a href="https://tigrisdata.com/?ref=coolify.io" target="_blank"><img src="./other/logos/tigris.svg" alt="tigris logo" width="140"/></a>
<a href="https://fractalnetworks.co/?ref=coolify.io" target="_blank"><img src="./other/logos/fractal.svg" alt="fractal logo" width="180"/></a>
<a href="https://coolify.ad.vin/?ref=coolify.io" target="_blank"><img src="./other/logos/advin.png" alt="advin logo" width="250"/></a>
<a href="https://trieve.ai/?ref=coolify.io" target="_blank"><img src="./other/logos/trieve_bg.png" alt="trieve logo" width="180"/></a>
<a href="https://blacksmith.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/blacksmith.svg" alt="blacksmith logo" width="200"/></a>
<a href="https://latitude.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/latitude.svg" alt="latitude logo" width="200"/></a>
<a href="https://brand.dev/?ref=coolify.io" target="_blank"><img src="./other/logos/branddev.png" alt="branddev logo" width="200"/></a>
## Github Sponsors ($40+)
<a href="https://serpapi.com/?ref=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a>
<a href="https://typebot.io/?ref=coolify.io"><img src="https://pbs.twimg.com/profile_images/1509194008366657543/9I-C7uWT_400x400.jpg" width="60px" alt="typebot"/></a>
<a href="https://www.runpod.io/?ref=coolify.io">
<svg style="width:60px;height:60px;background:#fff;" xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 200 200"><g><path d="M74.5 51.1c-25.4 14.9-27 16-29.6 20.2-1.8 3-1.9 5.3-1.9 32.3 0 21.7.3 29.4 1.3 30.6 1.9 2.5 46.7 27.9 48.5 27.6 1.5-.3 1.7-3.1 2-27.7.2-21.9 0-27.8-1.1-29.5-.8-1.2-9.9-6.8-20.2-12.6-10.3-5.8-19.4-11.5-20.2-12.7-1.8-2.6-.9-5.9 1.8-7.4 1.6-.8 6.3 0 21.8 4C87.8 78.7 98 81 99.6 81c4.4 0 49.9-25.9 49.9-28.4 0-1.6-3.4-2.8-24-8.2-13.2-3.5-25.1-6.3-26.5-6.3-1.4.1-12.4 5.9-24.5 13z"></path><path d="m137.2 68.1-3.3 2.1 6.3 3.7c3.5 2 6.3 4.3 6.3 5.1 0 .9-8 6.1-19.4 12.6-10.6 6-20 11.9-20.7 12.9-1.2 1.6-1.4 7.2-1.2 29.4.3 24.8.5 27.6 2 27.9 1.8.3 46.6-25.1 48.6-27.6.9-1.2 1.2-8.8 1.2-30.2s-.3-29-1.2-30.2c-1.6-1.9-12.1-7.8-13.9-7.8-.8 0-2.9 1-4.7 2.1z"></path></g></svg></a>
<a href="https://lightspeed.run/?ref=coolify.io"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a>
<a href="https://www.flint.sh/en/home?ref=coolify.io"> <img src="https://github.com/Flint-company.png" width="60px" alt="FlintCompany"/></a>
<a href="https://americancloud.com/?ref=coolify.io"><img src="https://github.com/American-Cloud.png" width="60px" alt="American Cloud"/></a>
<a href="https://cryptojobslist.com/?ref=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a>
<a href="https://codext.link/coolify-io?ref=coolify.io"><img src="./other/logos/codext.jpg" width="60px" alt="Codext" /></a>
<a href="https://x.com/mrsmith9ja?ref=coolify.io"><img width="60px" alt="Thompson Edolo" src="https://github.com/verygreenboi.png"/></a>
<a href="https://www.uxwizz.com/?ref=coolify.io"><img width="60px" alt="UXWizz" src="https://github.com/UXWizz.png"/></a>
<a href="https://github.com/Flowko"><img src="https://barrad.me/_ipx/f_webp&s_300x300/younes.jpg" width="60px" alt="Younes Barrad" /></a>
<a href="https://github.com/automazeio"><img src="https://github.com/automazeio.png" width="60px" alt="Automaze" /></a>
<a href="https://github.com/corentinclichy"><img src="https://github.com/corentinclichy.png" width="60px" alt="Corentin Clichy" /></a>
<a href="https://github.com/Niki2k1"><img src="https://github.com/Niki2k1.png" width="60px" alt="Niklas Lausch" /></a>
<a href="https://github.com/pixelinfinito"><img src="https://github.com/pixelinfinito.png" width="60px" alt="Pixel Infinito" /></a>
<a href="https://github.com/whitesidest"><img src="https://avatars.githubusercontent.com/u/12365916?s=52&v=4" width="60px" alt="Tyler Whitesides" /></a>
<a href="https://github.com/aniftyco"><img src="https://github.com/aniftyco.png" width="60px" alt="NiftyCo" /></a>
<a href="https://github.com/iujlaki"><img src="https://github.com/iujlaki.png" width="60px" alt="Imre Ujlaki" /></a>
<a href="https://il.ly"><img src="https://github.com/Illyism.png" width="60px" alt="Ilias Ism" /></a>
<a href="https://github.com/urtho"><img src="https://github.com/urtho.png" width="60px" alt="Paweł Pierścionek" /></a>
<a href="https://github.com/monocursive"><img src="https://github.com/monocursive.png" width="60px" alt="Michael Mazurczak" /></a>
## Organizations
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/1/website"><img src="https://opencollective.com/coollabsio/organization/1/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/2/website"><img src="https://opencollective.com/coollabsio/organization/2/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/3/website"><img src="https://opencollective.com/coollabsio/organization/3/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/4/website"><img src="https://opencollective.com/coollabsio/organization/4/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/5/website"><img src="https://opencollective.com/coollabsio/organization/5/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/6/website"><img src="https://opencollective.com/coollabsio/organization/6/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/7/website"><img src="https://opencollective.com/coollabsio/organization/7/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
## Individuals
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
# Cloud
If you do not want to self-host Coolify, there is a paid cloud version available: [app.coolify.io](https://app.coolify.io)
For more information & pricing, take a look at our landing page [coolify.io](https://coolify.io).
## Why should I use the Cloud version?
The recommended way to use Coolify is to have one server for Coolify and one (or more) for the resources you are deploying. A server is around 4-5$/month.
By subscribing to the cloud version, you get the Coolify server for the same price, but with:
- High-availability
- Free email notifications
- Better support
- Less maintenance for you
# Recognitions
<p>
<a href="https://news.ycombinator.com/item?id=26624341">
<img
style="width: 250px; height: 54px;" width="250" height="54"
alt="Featured on Hacker News"
src="https://hackernews-badge.vercel.app/api?id=26624341"
/>
</a>
</p>
<a href="https://www.producthunt.com/posts/coolify?ref=badge-featured&utm_medium=badge&utm_souce=badge-coolify" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=338273&theme=light" alt="Coolify - An&#0032;open&#0045;source&#0032;&#0038;&#0032;self&#0045;hostable&#0032;Heroku&#0044;&#0032;Netlify&#0032;alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
<a href="https://trendshift.io/repositories/634" target="_blank"><img src="https://trendshift.io/api/badge/repositories/634" alt="coollabsio%2Fcoolify | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
# Repo Activity
![Alt](https://repobeats.axiom.co/api/embed/eab1c8066f9c59d0ad37b76c23ebb5ccac4278ae.svg "Repobeats analytics image")
# Star History
[![Star History Chart](https://api.star-history.com/svg?repos=coollabsio/coolify&type=Date)](https://star-history.com/#coollabsio/coolify&Date)

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/lasthourcloudprod/scripts/upgrade.sh -o /data/coolify/source/upgrade.sh", "curl -fsSL https://cdn.lasthourhosting.org/lasthourcloud/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.lasthourhosting.org/lasthourcloudprod/scripts/upgrade.sh -o /data/coolify/source/upgrade.sh", 'curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh',
"bash /data/coolify/source/upgrade.sh $this->latestVersion", "bash /data/coolify/source/upgrade.sh $this->latestVersion",
], $this->server); ], $this->server);
} }

View File

@ -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/lasthourcloudprod/versions.json'); $response = Http::get('https://cdn.lasthourhosting.org/lasthourcloud/versions.json');
$versions = $response->json(); $versions = $response->json();
return data_get($versions, 'sentinel.version'); return data_get($versions, 'sentinel.version');
@ -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:prod" image: "githaven.org/shiloh/lasthourcloud:latest"
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://githaven.org/Shiloh/lasthourcloud" target="_blank"> <a class="hover:bg-transparent" href="https://shilohcode.com/get-involved" target="_blank">
<svg xmlns="http://www.w3.org/2000/svg" class="icon hover:text-blue-500" viewBox="0 0 16 16"> <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://githaven.org/Shiloh/lasthourcloud" target="_blank"> <a class="justify-center hover:text-white hover:bg-transparent" href="https://shilohcode.com/get-involved" 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,143 +3,100 @@
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 <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 ">
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-3xl font-bold lg:text-5xl">Welcome to Last Hour Cloud</h1> <h1 class="text-5xl font-bold">Welcome to your Last Hour Cloud</h1>
<div class="py-6 text-center lg:text-xl">Let's help you get set up.</div> <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 "> <div class="flex justify-center ">
<x-forms.button class="justify-center w-64 box-boarding" <x-forms.button class="justify-center w-64 box" wire:click="$set('currentState','explanation')">Let's Start
wire:click="$set('currentState','explanation')">Get
Started
</x-forms.button> </x-forms.button>
</div> </div>
@elseif ($currentState === 'explanation') @endif
<x-boarding-step title="What is Last Hour Cloud?"> </div>
<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 application with Last Hour Cloud is an all-in-one application to automate tasks on your servers, deploy applications with Git
Git integrations, deploy databases and services, monitor these resources with notifications and alerts
integrations, deploy databases and services, monitor these resources with notifications and
alerts
without vendor lock-in without vendor lock-in
and <a href="https://lasthourhosting.org/cloud-apps.html" class="dark:text-white hover:underline">more</a>. and <a href="https://lasthourhosting.org/cloud.html" class="text-white hover:underline">much much more</a>.
<br><br> <br><br>
<span class="text-xl"> <span class="text-xl">
<x-highlighted text="Cloud apps for the last hour" /></span> <x-highlighted text="Self-hosting for the Last Hour Cloud." /></span>
</x-slot:question> </x-slot:question>
<x-slot:explanation> <x-slot:explanation>
<p> <p><x-highlighted text="Task automation:" /> You do not to manage your servers too much. This does
<x-highlighted text="Task automation:" /> You don't need to manage your servers anymore. it for you.</p>
Last Hour Cloud does <p><x-highlighted text="No vendor lock-in:" /> All configurations are stored on your server, so
it for you. everything works without this (except integrations and automations).</p>
</p> <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>
<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-boarding" wire:click="explanation">Next <x-forms.button class="justify-center w-64 box" wire:click="explanation">Next
</x-forms.button> </x-forms.button>
</x-slot:actions> </x-slot:actions>
</x-boarding-step> </x-boarding-step>
@elseif ($currentState === 'select-server-type') @endif
@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 to your Do you want to deploy your resources on your <x-highlighted text="Localhost" />
<x-highlighted text="Localhost" /> or on a <x-highlighted text="Remote Server" />?
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-boarding" wire:target="setServerType('localhost')" <x-forms.button class="justify-center w-64 box" wire:target="setServerType('localhost')" wire:click="setServerType('localhost')">Localhost
wire:click="setServerType('localhost')">Localhost
</x-forms.button> </x-forms.button>
<x-forms.button class="justify-center w-64 box-boarding " wire:target="setServerType('remote')" <x-forms.button class="justify-center w-64 box " wire:target="setServerType('remote')" wire:click="setServerType('remote')">Remote Server
wire:click="setServerType('remote')">Remote Server
</x-forms.button> </x-forms.button>
@if (!$serverReachable) @if (!$serverReachable)
<div class="mt-6 p-4 border border-error rounded-lg text-gray-800 dark:text-gray-200"> Localhost is not reachable with the following public key.
<h2 class="text-lg font-bold mb-2">Server is not reachable</h2> <br /> <br />
<p class="mb-4">Please check the connection details below and correct them if they are Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for user
incorrect.</p> 'root' or skip the guided tour and add a new private key manually to Last Hour Cloud and to the
server.
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4"> <br />
<x-forms.input placeholder="Default is 22" label="Port" id="remoteServerPort" Check the upstream <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh">documentation</a> for further help.
wire:model="remoteServerPort" :value="$remoteServerPort" /> <x-forms.input readonly id="serverPublicKey"></x-forms.input>
<div> <x-forms.button class="w-64 box" wire:target="setServerType('localhost')" wire:click="setServerType('localhost')">Check again
<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> <p>Localhost is the server where Last Hour Cloud is running on. It is not recommended to use one server
<x-highlighted text="Localhost" /> is the server where Last Hour Cloud is running on. It is not for everything.</p>
recommended to use one server <p>Remote Server is a server reachable through SSH. It can be hosted at home, or from any cloud
for everything. provider.</p>
</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>
@elseif ($currentState === 'private-key') @endif
</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 lg:w-64 box-boarding" wire:target="setPrivateKey('own')" <x-forms.button class="justify-center w-64 box" wire:target="setPrivateKey('own')" wire:click="setPrivateKey('own')">Yes
wire:click="setPrivateKey('own')">Yes
</x-forms.button> </x-forms.button>
<x-forms.button class="justify-center lg:w-64 box-boarding" wire:target="setPrivateKey('create')" <x-forms.button class="justify-center w-64 box" wire:target="setPrivateKey('create')" wire:click="setPrivateKey('create')">No (create one for me)
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 lg:pr-10"> <form wire:submit='selectExistingPrivateKey' class="flex flex-col w-full gap-4 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 }}">
@ -153,14 +110,17 @@ class="bg-red-200 dark:bg-red-900 px-1 rounded">~/.ssh/authorized_keys</code>
</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 let Last Hour Cloud to create one for you.</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 <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="dark:text-warning">~/.ssh/authorized_keys</code> file. <code class="text-warning">~/.ssh/authorized_keys</code> file.
</p> </p>
</x-slot:explanation> </x-slot:explanation>
</x-boarding-step> </x-boarding-step>
@elseif ($currentState === 'select-existing-server') @endif
</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?
@ -168,8 +128,9 @@ class="bg-red-200 dark:bg-red-900 px-1 rounded">~/.ssh/authorized_keys</code>
<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-boarding" wire:click="createNewServer">No <x-forms.button class="justify-center w-64 box" wire:click="createNewServer">No (create one
(create one for me) for
me)
</x-forms.button> </x-forms.button>
</div> </div>
<div> <div>
@ -186,75 +147,41 @@ class="bg-red-200 dark:bg-red-900 px-1 rounded">~/.ssh/authorized_keys</code>
</div> </div>
</div> </div>
@if (!$serverReachable) @if (!$serverReachable)
<div class="mt-6 p-4 bg-red-100 dark:bg-red-950 rounded-lg text-gray-800 dark:text-gray-200"> This server is not reachable with the following public key.
<h2 class="text-lg font-bold mb-2">Server is not reachable</h2> <br /> <br />
<p class="mb-4">Please check the connection details below and correct them if they are Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for user
incorrect.</p> 'root' or skip the boarding process and add a new private key manually to Last Hour Cloud and to the
server.
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4"> <x-forms.input readonly id="serverPublicKey"></x-forms.input>
<x-forms.input placeholder="Default is 22" label="Port" id="remoteServerPort" <x-forms.button class="w-64 box" wire:target="validateServer" wire:click="validateServer">Check
wire:model="remoteServerPort" :value="$remoteServerPort" /> 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">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 md:w-auto 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>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 to create one for you.</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 <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>
@elseif ($currentState === 'create-private-key') @endif
</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 lg:pr-10"> <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." <x-forms.input required placeholder="Choose a name for your Private Key. Could be anything." label="Name" id="privateKeyName" />
label="Name" id="privateKeyName" /> <x-forms.input placeholder="Description, so others will know more about this." label="Description" id="privateKeyDescription" />
<x-forms.input placeholder="Description, so others will know more about this." <x-forms.textarea required placeholder="-----BEGIN OPENSSH PRIVATE KEY-----" label="Private Key" id="privateKey" />
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 dark:text-warning">ACTION REQUIRED: Copy the 'Public Key' to your <span class="font-bold text-warning">ACTION REQUIRED: Copy the 'Public Key' to your server's
server's
~/.ssh/authorized_keys ~/.ssh/authorized_keys
file.</span> file.</span>
@endif @endif
@ -263,62 +190,50 @@ class="bg-red-200 dark:bg-red-900 px-1 rounded">~/.ssh/authorized_keys</code>
</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 to create one for you.</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 <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>
@elseif ($currentState === 'create-server') @endif
<x-boarding-step title="Create Server"> </div>
<div>
@if ($currentState === 'create-server')
<x-boarding-step title="Create a Server">
<x-slot:question> <x-slot:question>
Please let me know your server details. Please let us 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 lg:pr-10"> <form wire:submit='saveServer' class="flex flex-col w-full gap-4 pr-10">
<div class="flex flex-col gap-2 lg:flex-row"> <div class="flex gap-2">
<x-forms.input required placeholder="Choose a name for your Server. Could be anything." <x-forms.input required placeholder="Choose a name for your Server. It could be anything." label="Name" id="remoteServerName" />
label="Name" id="remoteServerName" wire:model="remoteServerName" /> <x-forms.input placeholder="Description, so others will know more about it." label="Description" id="remoteServerDescription" />
<x-forms.input placeholder="Description, so others will know more about this."
label="Description" id="remoteServerDescription"
wire:model="remoteServerDescription" />
</div> </div>
<div class="flex flex-col gap-2 lg:flex-row "> <div class="flex gap-2">
<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" />
wire:model="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>
<div x-data="{ showAdvanced: false }" class="flex flex-col gap-2"> <div class="w-64">
<button @click="showAdvanced = !showAdvanced" type="button" <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" />
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>
@elseif ($currentState === 'validate-server') @endif
</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>
I need to validate your server (connection, Docker Engine, etc) and configure if something is we need to validate your server (connection, Docker Engine, etc) and configure to see if something is
missing for me. Are you okay with this? missing. 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>
@ -326,39 +241,265 @@ class="text-left text-sm text-gray-600 dark:text-gray-300 hover:underline">
<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="w-full font-bold box-boarding lg:w-96" <x-forms.button @click="slideOverOpen=true" class="font-bold box w-96" wire:click.prevent='installServer' isHighlighted>
wire:click.prevent='installServer' isHighlighted> Send it!
Let's do it! </x-forms.button>
</x-slot:actions>
</x-boarding-step>
@endif
@if ($currentState === 'select-server-type')
<x-boarding-step title="Server">
<x-slot:question>
Do you want to deploy your resources on your <x-highlighted text="Localhost" />
or on a <x-highlighted text="Remote Server" />?
</x-slot:question>
<x-slot:actions>
<x-forms.button class="justify-center w-64 box" wire:target="setServerType('localhost')" wire:click="setServerType('localhost')">Localhost
</x-forms.button>
<x-forms.button class="justify-center w-64 box " wire:target="setServerType('remote')" wire:click="setServerType('remote')">Remote Server
</x-forms.button>
@if (!$serverReachable)
Localhost is not reachable with the following public key.
<br /> <br />
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for user
'root' or skip the guided tour and add a new private key manually to Last Hour Cloud and to the
server.
<br />
Check the upstream <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh">documentation</a> for further help.
<x-forms.input readonly id="serverPublicKey"></x-forms.input>
<x-forms.button class="w-64 box" wire:target="setServerType('localhost')" wire:click="setServerType('localhost')">Check again
</x-forms.button>
@endif
</x-slot:actions>
<x-slot:explanation>
<p>Servers are the main building blocks, as they will host your applications, databases,
services, called resources. Any CPU intensive process will use the server's CPU where you
are deploying your resources.</p>
<p>Localhost is the server where Last Hour Cloud is running on. It is not recommended to use one server
for everything.</p>
<p>Remote Server is a server reachable through SSH. It can be hosted at home, or from any cloud
provider.</p>
</x-slot:explanation>
</x-boarding-step>
@endif
</div>
<div>
@if ($currentState === 'private-key')
<x-boarding-step title="SSH Key">
<x-slot:question>
Do you have your own SSH Private Key?
</x-slot:question>
<x-slot:actions>
<x-forms.button class="justify-center w-64 box" wire:target="setPrivateKey('own')" wire:click="setPrivateKey('own')">Yes
</x-forms.button>
<x-forms.button class="justify-center w-64 box" wire:target="setPrivateKey('create')" wire:click="setPrivateKey('create')">No (create one for me)
</x-forms.button>
@if (count($privateKeys) > 0)
<form wire:submit='selectExistingPrivateKey' class="flex flex-col w-full gap-4 pr-10">
<x-forms.select label="Existing SSH Keys" id='selectedExistingPrivateKey'>
@foreach ($privateKeys as $privateKey)
<option wire:key="{{ $loop->index }}" value="{{ $privateKey->id }}">
{{ $privateKey->name }}
</option>
@endforeach
</x-forms.select>
<x-forms.button type="submit">Use this SSH Key</x-forms.button>
</form>
@endif
</x-slot:actions>
<x-slot:explanation>
<p>SSH Keys are used to connect to a remote server through a secure shell, called SSH.</p>
<p>You can use your own ssh private key, or you can allow Last Hour Cloud to create one for you.</p>
<p>In both ways, you need to add the public version of your ssh private key to the remote
server's
<code class="text-warning">~/.ssh/authorized_keys</code> file.
</p>
</x-slot:explanation>
</x-boarding-step>
@endif
</div>
<div>
@if ($currentState === 'select-existing-server')
<x-boarding-step title="Select a server">
<x-slot:question>
There are already servers available for your Team. Do you want to use one of them?
</x-slot:question>
<x-slot:actions>
<div class="flex flex-col gap-4">
<div>
<x-forms.button class="justify-center w-64 box" wire:click="createNewServer">No (create one
for
me)
</x-forms.button>
</div>
<div>
<form wire:submit='selectExistingServer' class="flex flex-col w-full gap-4 lg:w-96">
<x-forms.select label="Existing servers" class="w-96" id='selectedExistingServer'>
@foreach ($servers as $server)
<option wire:key="{{ $loop->index }}" value="{{ $server->id }}">
{{ $server->name }}
</option>
@endforeach
</x-forms.select>
<x-forms.button type="submit">Use this Server</x-forms.button>
</form>
</div>
</div>
@if (!$serverReachable)
This server is not reachable with the following public key.
<br /> <br />
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for user
'root' or skip the boarding process and add a new private key manually to Last Hour Cloud and to the
server.
<x-forms.input readonly id="serverPublicKey"></x-forms.input>
<x-forms.button class="w-64 box" wire:target="validateServer" wire:click="validateServer">Check
again
</x-forms.button>
@endif
</x-slot:actions>
<x-slot:explanation>
<p>Private Keys are used to connect to a remote server through a secure shell, called SSH.</p>
<p>You can use your own private key, or you can let Last Hour Cloud create one for you.</p>
<p>In both ways, you need to add the public version of your private key to the remote server's
<code>~/.ssh/authorized_keys</code> file.
</p>
</x-slot:explanation>
</x-boarding-step>
@endif
</div>
<div>
@if ($currentState === 'create-private-key')
<x-boarding-step title="Create Private Key">
<x-slot:question>
Please let me know your key details.
</x-slot:question>
<x-slot:actions>
<form wire:submit='savePrivateKey' class="flex flex-col w-full gap-4 pr-10">
<x-forms.input required placeholder="Choose a name for your Private Key. Could be anything." label="Name" id="privateKeyName" />
<x-forms.input placeholder="Description, so others will know more about this." label="Description" id="privateKeyDescription" />
<x-forms.textarea required placeholder="-----BEGIN OPENSSH PRIVATE KEY-----" label="Private Key" id="privateKey" />
@if ($privateKeyType === 'create')
<x-forms.textarea rows="7" readonly label="Public Key" id="publicKey" />
<span class="font-bold text-warning">ACTION REQUIRED: Copy the 'Public Key' to your server's
~/.ssh/authorized_keys
file.</span>
@endif
<x-forms.button type="submit">Save</x-forms.button>
</form>
</x-slot:actions>
<x-slot:explanation>
<p>Private Keys are used to connect to a remote server through a secure shell, called SSH.</p>
<p>You can use your own private key, or you can let Last Hour Cloud create one for you.</p>
<p>In both ways, you need to add the public version of your private key to the remote server's
<code>~/.ssh/authorized_keys</code> file.
</p>
</x-slot:explanation>
</x-boarding-step>
@endif
</div>
<div>
@if ($currentState === 'create-server')
<x-boarding-step title="Create a Server">
<x-slot:question>
Please let us know your server details.
</x-slot:question>
<x-slot:actions>
<form wire:submit='saveServer' class="flex flex-col w-full gap-4 pr-10">
<div class="flex gap-2">
<x-forms.input required placeholder="Choose a name for your Server. It could be anything." label="Name" id="remoteServerName" />
<x-forms.input placeholder="Description, so others will know more about it." label="Description" id="remoteServerDescription" />
</div>
<div class="flex gap-2">
<x-forms.input required placeholder="127.0.0.1" label="IP Address" id="remoteServerHost" />
<x-forms.input required placeholder="Port number of your server. Default is 22." label="Port" id="remoteServerPort" />
<x-forms.input required readonly placeholder="Username to connect to your server. Default is root." label="Username" id="remoteServerUser" />
</div>
<div class="w-64">
<x-forms.checkbox helper="If you are using Cloudflare Tunnels, enable this. It will proxy all ssh requests to your server through Cloudflare.<br><span class='text-warning'>Last Hour Cloud does not install/setup Cloudflare (cloudflared) on your server.</span>" id="isCloudflareTunnel" label="Cloudflare Tunnel" />
</div>
<x-forms.button type="submit">Continue</x-forms.button>
</form>
</x-slot:actions>
<x-slot:explanation>
<p>Username should be <x-highlighted text="root" /> for now. We are working on using
non-root users.</p>
</x-slot:explanation>
</x-boarding-step>
@endif
</div>
<div>
@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-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 to run optimal.<br><br>Minimum Docker Engine version is: 22<br><br>To manually install Docker
Docker Engine, check <a target="_blank" class="underline text-warning" href="https://docs.docker.com/engine/install/#server">this
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>
@elseif ($currentState === 'create-project') @endif
</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 I create a new one You already have some projects. Do you want to use one of them or should we create a new one for
for
you? you?
@else @else
Let's create an initial project for you. You can change all the details later on. We will create an initial project for you. You can change it 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-boarding" wire:click="createNewProject">Create new <x-forms.button class="justify-center w-64 box" wire:click="createNewProject">Create New Project</x-forms.button>
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='selectedProject'> <x-forms.select label="Existing projects" class="w-96" id='selectedExistingProject'>
@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 }}
@ -371,42 +512,32 @@ class="text-left text-sm text-gray-600 dark:text-gray-300 hover:underline">
</div> </div>
</x-slot:actions> </x-slot:actions>
<x-slot:explanation> <x-slot:explanation>
<p>Projects contain several resources combined into one virtual group. There are no <p>Projects put together several resources into one virtual group. There are no
limitations on the number of projects you can add.</p> limitations on the number of projects you can have here.</p>
<p>Each project should have at least one environment, this allows you to create a production & <p>Each project should have at least one environment. This helps 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>
@elseif ($currentState === 'create-resource')
<x-boarding-step title="Resources">
<x-slot:question>
Let's go to the new resource page, where you can create your first resource.
</x-slot:question>
<x-slot:actions>
<div class="items-center justify-center w-64 box-boarding" wire:click="showNewResource">Let's do
it!</div>
</x-slot:actions>
<x-slot:explanation>
<p>A resource could be an application, a database or a service (like WordPress).</p>
</x-slot:explanation>
</x-boarding-step>
@endif @endif
</div> </div>
<div class="flex flex-col justify-center gap-4 pt-4 lg:gap-2 lg:flex"> <div>
<div class="flex justify-center w-full gap-2"> @if ($currentState === 'create-resource')
<div class="cursor-pointer hover:underline dark:hover:text-white" wire:click='skipBoarding'>Skip <x-boarding-step title="Resources">
onboarding</div> <x-slot:question>
<div class="cursor-pointer hover:underline dark:hover:text-white" wire:click='restartBoarding'>Restart Next we will redirect you to the resource page, where you can create your first resource.
onboarding</div> </x-slot:question>
<x-slot:actions>
<div class="items-center justify-center w-64 box" wire:click="showNewResource">Let's do
it!</div>
</x-slot:actions>
<x-slot:explanation>
<p>A resource is an application, a database or a service (like WordPress).</p>
</x-slot:explanation>
</x-boarding-step>
@endif
</div> </div>
<x-modal-input title="How can we help?"> <div class="flex justify-center gap-2 pt-4">
<x-slot:content> <a wire:click='skipBoarding'>Skip Guided Tour</a>
<div class="w-full text-center cursor-pointer hover:underline dark:hover:text-white" <a wire:click='restartBoarding'>Restart Guided Tour</a>
title="Send us feedback or get help!">
Feedback
</div> </div>
</x-slot:content>
<livewire:help />
</x-modal-input>
</div> </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 Last Hour Cloud to $IMAGE..." </dev/tty read -p "Press Enter to update Coolify 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

283
scripts/dev_install.sh Normal file
View File

@ -0,0 +1,283 @@
#!/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."

41
scripts/dev_upgrade.sh Normal file
View File

@ -0,0 +1,41 @@
#!/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,36 +1,15 @@
#!/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.5" VERSION="1.3.3"
DOCKER_VERSION="26.0" DOCKER_VERSION="26.0"
mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,webhooks-during-maintenance,metrics,logs} CDN="https://cdn.lasthourhosting.org/lasthourcloud"
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
@ -64,17 +43,7 @@ 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 ',')
LATEST_HELPER_VERSION=$(curl --silent $CDN/versions.json | grep -i version | xargs | awk '{print $6}' | tr -d ',') DATE=$(date +"%Y%m%d-%H%M%S")
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"
@ -82,9 +51,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 | alpine) ;; arch | ubuntu | debian | raspbian | centos | fedora | rhel | ol | rocky | sles | opensuse-leap | opensuse-tumbleweed | almalinux | amzn) ;;
*) *)
echo "This script only supports Debian, Redhat, Arch Linux, Alpine Linux, or SLES based operating systems for now." echo "This script only supports Debian, Redhat, Arch Linux, or SLES based operating systems for now."
exit exit
;; ;;
esac esac
@ -96,41 +65,33 @@ if [ "$1" != "" ]; then
LATEST_VERSION="${LATEST_VERSION#v}" LATEST_VERSION="${LATEST_VERSION#v}"
fi fi
echo -e "\033[0;35m" echo -e "-------------"
cat << "EOF" echo -e "Welcome to Last Hour Cloud v4 installer!"
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"
██ █████ ███████ ██ ██ ███████ ██ ███████ █████ ██ ██ ██ ██ ██ ███
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
█████ ███████ ███████ ██████ ███████ ██ ███████ ██ ██ ██ ██ ████ ██████
EOF echo -e "-------------"
echo -e "\033[0m" echo "Installing required packages..."
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-get update -y >/dev/null apt update -y >/dev/null
apt-get install -y curl wget git jq >/dev/null apt 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
@ -139,10 +100,7 @@ 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
if ! command -v curl >/dev/null; then dnf install -y curl wget git jq >/dev/null
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)
@ -155,26 +113,24 @@ 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
elif systemctl status ssh >/dev/null 2>&1; then fi
echo " - OpenSSH server is installed." if systemctl status ssh >/dev/null 2>&1; then
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
elif service ssh status >/dev/null 2>&1; then fi
echo " - OpenSSH server is installed." if service ssh status >/dev/null 2>&1; then
echo "OpenSSH server is installed."
SSH_DETECTED=true SSH_DETECTED=true
fi fi
fi fi
@ -186,91 +142,105 @@ if [ "$SSH_DETECTED" = "false" ]; then
fi fi
# Detect SSH PermitRootLogin # Detect SSH PermitRootLogin
SSH_PERMIT_ROOT_LOGIN=$(sshd -T | grep -i "permitrootlogin" | awk '{print $2}') || true SSH_PERMIT_ROOT_LOGIN=false
if [ "$SSH_PERMIT_ROOT_LOGIN" = "yes" ] || [ "$SSH_PERMIT_ROOT_LOGIN" = "without-password" ] || [ "$SSH_PERMIT_ROOT_LOGIN" = "prohibit-password" ]; then 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)"
echo " - SSH PermitRootLogin is enabled." if [ "$SSH_PERMIT_ROOT_LOGIN_CONFIG" = "prohibit-password" ] || [ "$SSH_PERMIT_ROOT_LOGIN_CONFIG" = "yes" ] || [ "$SSH_PERMIT_ROOT_LOGIN_CONFIG" = "without-password" ]; then
else echo "PermitRootLogin is enabled."
echo " - SSH PermitRootLogin is disabled." SSH_PERMIT_ROOT_LOGIN=true
echo " If you have problems with SSH, please read this upstream documentation: https://coolify.io/docs/knowledge-base/server/openssh" 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"
<<<<<<< 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
SNAP_DOCKER_INSTALLED=$(snap list docker >/dev/null 2>&1 && echo "true" || echo "false") if snap list | grep -q docker; then
if [ "$SNAP_DOCKER_INSTALLED" = "true" ]; then echo "Docker is installed via snap."
echo " - Docker is installed via snap." echo "Please note that Coolify does not support Docker installed via snap."
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."
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
echo " - Docker is not installed. Installing Docker. It may take a while." # Almalinux
getAJoke if [ "$OS_TYPE" == 'almalinux' ]; then
case "$OS_TYPE" in dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
"almalinux") dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
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 >/dev/null 2>&1 systemctl start docker
systemctl enable docker >/dev/null 2>&1 systemctl enable docker
;; else
"alpine") set +e
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 " - Failed to install Docker with apk. Try to install it manually." echo "Docker is not installed. Installing Docker."
echo " Please visit https://wiki.alpinelinux.org/wiki/Docker for more information." # Arch Linux
exit 1 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 fi
;; else
"arch") # Amazon Linux 2023
pacman -Sy docker docker-compose --noconfirm >/dev/null 2>&1 if [ "$OS_TYPE" = "amzn" ]; then
systemctl enable docker.service >/dev/null 2>&1 dnf install docker -y
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 >/dev/null 2>&1 mkdir -p $DOCKER_CONFIG/cli-plugins
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 curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o $DOCKER_CONFIG/cli-plugins/docker-compose
chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose >/dev/null 2>&1 chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose
systemctl start docker >/dev/null 2>&1 systemctl start docker
systemctl enable docker >/dev/null 2>&1 systemctl enable docker
if ! [ -x "$(command -v docker)" ]; then if [ -x "$(command -v docker)" ]; then
echo " - Failed to install Docker with dnf. Try to install it manually." echo "Docker installed successfully."
echo " Please visit https://www.cyberciti.biz/faq/how-to-install-docker-on-amazon-linux-2/ for more information." else
exit 1 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
*) # Automated Docker installation
curl -s https://releases.rancher.com/install-docker/${DOCKER_VERSION}.sh | sh >/dev/null 2>&1 curl https://releases.rancher.com/install-docker/${DOCKER_VERSION}.sh | sh
if ! [ -x "$(command -v docker)" ]; then if [ -x "$(command -v docker)" ]; then
curl -s https://get.docker.com | sh -s -- --version ${DOCKER_VERSION} >/dev/null 2>&1 echo "Docker installed successfully."
if ! [ -x "$(command -v docker)" ]; then else
echo " - Docker installation failed." echo "Docker installation failed with Rancher script. Trying with official script."
echo " Maybe your OS is not supported?" curl https://get.docker.com | sh -s -- --version ${DOCKER_VERSION}
echo " - Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue." 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 exit 1
fi fi
fi fi
esac fi
echo " - Docker installed successfully." fi
else fi
echo " - Docker is installed." set -e
fi
fi fi
echo -e "4. Check Docker Configuration. " echo -e "-------------"
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
@ -298,87 +268,55 @@ 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..."
restart_docker_service systemctl restart docker
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..."
restart_docker_service systemctl restart docker
fi fi
echo -e "5. Download required files from CDN. " echo -e "-------------"
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/upgrade.sh -o /data/coolify/source/upgrade.sh curl -fsSL $CDN/scripts/upgrade.sh -o /data/coolify/source/upgrade.sh
echo -e "6. Make backup of .env to .env-$DATE" # echo "Copying required files from Last Hour Cloud git repo..."
# cp /home/lasthour/lasthourcloud/docker-compose.yml /data/coolify/source/docker-compose.yml
# cp /home/lasthour/lasthourcloud/docker-compose.prod.yml /data/coolify/source/docker-compose.prod.yml
# cp /home/lasthour/lasthourcloud/.env.production /data/coolify/source/.env.production
# cp /home/lasthour/lasthourcloud/scripts/upgrade.sh /data/coolify/source/upgrade.sh
chown -R 9999:root /data/coolify
chmod -R 700 /data/coolify
# Copy .env.example if .env does not exist # Copy .env.example if .env does not exist
if [ -f $ENV_FILE ]; then if [ ! -f /data/coolify/source/.env ]; then
cp $ENV_FILE $ENV_FILE-$DATE cp /data/coolify/source/.env.production /data/coolify/source/.env
else sed -i "s|APP_ID=.*|APP_ID=$(openssl rand -hex 16)|g" /data/coolify/source/.env
echo " - File does not exist: $ENV_FILE" sed -i "s|APP_KEY=.*|APP_KEY=base64:$(openssl rand -base64 32)|g" /data/coolify/source/.env
echo " - Copying .env.production to .env-$DATE" sed -i "s|DB_PASSWORD=.*|DB_PASSWORD=$(openssl rand -base64 32)|g" /data/coolify/source/.env
cp /data/coolify/source/.env.production $ENV_FILE-$DATE sed -i "s|REDIS_PASSWORD=.*|REDIS_PASSWORD=$(openssl rand -base64 32)|g" /data/coolify/source/.env
# Generate a secure APP_ID and APP_KEY sed -i "s|PUSHER_APP_ID=.*|PUSHER_APP_ID=$(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_KEY=.*|PUSHER_APP_KEY=$(openssl rand -hex 32)|g" /data/coolify/source/.env
sed -i "s|^APP_KEY=.*|APP_KEY=base64:$(openssl rand -base64 32)|" "$ENV_FILE-$DATE" sed -i "s|PUSHER_APP_SECRET=.*|PUSHER_APP_SECRET=$(openssl rand -hex 32)|g" /data/coolify/source/.env
# 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
echo -e "7. Propagating .env with new values - if necessary." 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]++' "$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
@ -387,122 +325,32 @@ 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
chmod 600 ~/.ssh/authorized_keys addSshKey
fi fi
checkSshKeyInAuthorizedKeys() { if ! grep -qw "root@coolify" ~/.ssh/authorized_keys; then
grep -qw "root@coolify" ~/.ssh/authorized_keys addSshKey
return $? fi
} echo "Generated SSH access"
checkSshKeyInCoolifyData() { echo "Begin upgrade.sh"
[ -s /data/coolify/ssh/keys/id.root@host.docker.internal ] bash /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}"
return $?
}
generateAuthorizedKeys() { echo -e "\nCongratulations! Your Last Hour Cloud instance is ready to use.\n"
sed -i "/root@coolify/d" ~/.ssh/authorized_keys echo "Please visit http://$(curl -4s https://ifconfig.io):8000 to get started."
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,56 +1,104 @@
#!/bin/bash #!/usr/bin/env bash
# Sync docker volumes between two servers
VERSION="1.0.0" # Inspired on https://github.com/adriancooney/Taskfile
SOURCE=$1 #
DESTINATION=$2 # Install an alias, to be able to simply execute `run`
# echo 'alias run=./scripts/run' >> ~/.aliases
#
# Define Docker Compose command prefix...
set -e set -e
if [ -z "$SOURCE" ]; then
echo "Source server is not specified." if [ $? == 0 ]; then
exit 1 DOCKER_COMPOSE="docker compose"
fi else
if [ -z "$DESTINATION" ]; then DOCKER_COMPOSE="docker-compose"
echo "Destination server is not specified."
exit 1
fi fi
SOURCE_USER=$(echo $SOURCE | cut -d@ -f1) function help {
SOURCE_SERVER=$(echo $SOURCE | cut -d: -f1 | cut -d@ -f2) echo "$0 <task> <args>"
SOURCE_PORT=$(echo $SOURCE | cut -d: -f2 | cut -d/ -f1) echo "Tasks:"
SOURCE_VOLUME_NAME=$(echo $SOURCE | cut -d/ -f2) compgen -A function | cat -n
}
if ! [[ "$SOURCE_PORT" =~ ^[0-9]+$ ]]; then # function sync:v3 {
echo "Invalid source port: $SOURCE_PORT" # if [ -z "$1" ]; then
exit 1 # echo -e "Please provide a version.\n\nExample: run sync:v3 3.12.32"
fi # exit 1
# fi
# skopeo copy --all docker://ghcr.io/coollabsio/coolify:$1 docker://coollabsio/coolify:$1
# }
function sync:bunny {
php artisan sync:bunny --env=secrets
}
DESTINATION_USER=$(echo $DESTINATION | cut -d@ -f1) # function queue {
DESTINATION_SERVER=$(echo $DESTINATION | cut -d: -f1 | cut -d@ -f2) # bash spin exec -u webuser coolify php artisan queue:listen
DESTINATION_PORT=$(echo $DESTINATION | cut -d: -f2 | cut -d/ -f1) # }
DESTINATION_VOLUME_NAME=$(echo $DESTINATION | cut -d/ -f2)
if ! [[ "$DESTINATION_PORT" =~ ^[0-9]+$ ]]; then # function horizon {
echo "Invalid destination port: $DESTINATION_PORT" # bash spin exec -u webuser coolify php artisan horizon -vvv
exit 1 # }
fi
echo "Generating backup file to ./$SOURCE_VOLUME_NAME.tgz" # function schedule {
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 # bash spin exec -u webuser coolify php artisan schedule:work
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"
echo "" # function schedule:run {
echo "Local file ./$SOURCE_VOLUME_NAME.tgz is not deleted." # bash spin exec -u webuser coolify php artisan schedule:run
# }
echo "" # function 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." # bash spin exec -u webuser coolify php artisan db
echo "Why? Because we are copying the volume as-is, so the database credentials will bethe same as on the source volume." # }
fi # function db:seed {
# 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,17 +1,23 @@
#!/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.1" VERSION="1.0.5"
CDN="https://cdn.lasthourhosting.org/lasthourcloudprod" CDN="https://cdn.lasthourhosting.org/lasthourcloud"
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
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 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 # 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
@ -25,14 +31,13 @@ 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 Last Hour Cloud network exists # Make sure coolify 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:${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" 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"
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:${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" 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"
fi fi