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 {
ray('Running update on production server');
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"
], $this->server);
return;
}
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",
], $this->server);
}

View File

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

View File

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

View File

@ -1,5 +1,5 @@
<<<<<<< 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() {
if (this.full === 'full') {
localStorage.removeItem('pageWidth');
@ -336,216 +336,216 @@ class="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border text-
@endpersist
@endif
<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">
<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"/>
=======
@auth
<nav class="fixed h-full pt-28 scrollbar">
<a href="/" class="fixed top-0 z-50 mx-3 mt-3 bg-transparent cursor-pointer"><img class="transition rounded w-11 h-11" src="{{ asset('lasthour-transparent.png') }}"></a>
<ul class="flex flex-col h-full gap-4 menu flex-nowrap">
<li title="Dashboard">
<a class="hover:bg-transparent" href="/">
<svg xmlns="http://www.w3.org/2000/svg" class="{{ request()->is('/') ? 'text-warning icon' : 'icon' }}" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
<ul class="flex flex-col h-full gap-4 menu flex-nowrap">
<li title="Dashboard">
<a class="hover:bg-transparent" href="/">
<svg xmlns="http://www.w3.org/2000/svg" class="{{ request()->is('/') ? 'text-warning icon' : 'icon' }}" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
</a>
</li>
<li title="Servers">
<a class="hover:bg-transparent" href="/servers">
<svg xmlns="http://www.w3.org/2000/svg" class="{{ request()->is('server/*') || request()->is('servers') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M3 4m0 3a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3z" />
<path d="M15 20h-9a3 3 0 0 1 -3 -3v-2a3 3 0 0 1 3 -3h12" />
<path d="M7 8v.01" />
<path d="M7 16v.01" />
<path d="M20 15l-2 3h3l-2 3" />
</svg>
</a>
</li>
<li title="Projects">
<a class="hover:bg-transparent" href="/projects">
<svg xmlns="http://www.w3.org/2000/svg" class="{{ request()->is('project/*') || request()->is('projects') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 4l-8 4l8 4l8 -4l-8 -4" />
<path d="M4 12l8 4l8 -4" />
<path d="M4 16l8 4l8 -4" />
</svg>
</a>
</li>
<div class="inline-block text-left " x-data="{ open: false }">
<div>
<button x-on:click.prevent="open = !open" x-on:click.away="open = false" type="button" class="py-4 mx-4" id="menu-button" aria-expanded="true" aria-haspopup="true">
<svg class="icon" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M224 128a8 8 0 0 1-8 8h-80v80a8 8 0 0 1-16 0v-80H40a8 8 0 0 1 0-16h80V40a8 8 0 0 1 16 0v80h80a8 8 0 0 1 8 8" />
</svg>
</a>
</li>
<li title="Servers">
<a class="hover:bg-transparent" href="/servers">
<svg xmlns="http://www.w3.org/2000/svg" class="{{ request()->is('server/*') || request()->is('servers') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M3 4m0 3a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3z" />
<path d="M15 20h-9a3 3 0 0 1 -3 -3v-2a3 3 0 0 1 3 -3h12" />
<path d="M7 8v.01" />
<path d="M7 16v.01" />
<path d="M20 15l-2 3h3l-2 3" />
</svg>
</a>
</li>
<li title="Projects">
<a class="hover:bg-transparent" href="/projects">
<svg xmlns="http://www.w3.org/2000/svg" class="{{ request()->is('project/*') || request()->is('projects') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 4l-8 4l8 4l8 -4l-8 -4" />
<path d="M4 12l8 4l8 -4" />
<path d="M4 16l8 4l8 -4" />
</svg>
</a>
</li>
<div class="inline-block text-left " x-data="{ open: false }">
<div>
<button x-on:click.prevent="open = !open" x-on:click.away="open = false" type="button" class="py-4 mx-4" id="menu-button" aria-expanded="true" aria-haspopup="true">
<svg class="icon" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M224 128a8 8 0 0 1-8 8h-80v80a8 8 0 0 1-16 0v-80H40a8 8 0 0 1 0-16h80V40a8 8 0 0 1 16 0v80h80a8 8 0 0 1 8 8" />
</svg>
</button>
</div>
<div x-show="open" x-cloak class="absolute left-0 z-10 w-56 mx-4 mt-2 origin-top-right rounded shadow-lg bg-coolgray-100 ring-1 ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="menu-button" tabindex="-1">
<div class="py-1" role="none">
<li title="Tags" class="border-transparent hover:bg-coolgray-200 ">
<a class=" hover:bg-transparent hover:no-underline" href="{{ route('tags.index') }}">
<svg class="{{ request()->is('tags*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<path d="M3 8v4.172a2 2 0 0 0 .586 1.414l5.71 5.71a2.41 2.41 0 0 0 3.408 0l3.592-3.592a2.41 2.41 0 0 0 0-3.408l-5.71-5.71A2 2 0 0 0 9.172 6H5a2 2 0 0 0-2 2" />
<path d="m18 19l1.592-1.592a4.82 4.82 0 0 0 0-6.816L15 6m-8 4h-.01" />
</g>
</svg>
Tags
</a>
</li>
<li title="Command Center" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('command-center') }}">
<svg xmlns="http://www.w3.org/2000/svg" class="{{ request()->is('command-center*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M5 7l5 5l-5 5" />
<path d="M12 19l7 0" />
</svg>
Command Center
</a>
</li>
<li title="Source" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('source.all') }}">
<svg class="{{ request()->is('source*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="m6.793 1.207l.353.354l-.353-.354ZM1.207 6.793l-.353-.354l.353.354Zm0 1.414l.354-.353l-.354.353Zm5.586 5.586l-.354.353l.354-.353Zm1.414 0l-.353-.354l.353.354Zm5.586-5.586l.353.354l-.353-.354Zm0-1.414l-.354.353l.354-.353ZM8.207 1.207l.354-.353l-.354.353ZM6.44.854L.854 6.439l.707.707l5.585-5.585L6.44.854ZM.854 8.56l5.585 5.585l.707-.707l-5.585-5.585l-.707.707Zm7.707 5.585l5.585-5.585l-.707-.707l-5.585 5.585l.707.707Zm5.585-7.707L8.561.854l-.707.707l5.585 5.585l.707-.707Zm0 2.122a1.5 1.5 0 0 0 0-2.122l-.707.707a.5.5 0 0 1 0 .708l.707.707ZM6.44 14.146a1.5 1.5 0 0 0 2.122 0l-.707-.707a.5.5 0 0 1-.708 0l-.707.707ZM.854 6.44a1.5 1.5 0 0 0 0 2.122l.707-.707a.5.5 0 0 1 0-.708L.854 6.44Zm6.292-4.878a.5.5 0 0 1 .708 0L8.56.854a1.5 1.5 0 0 0-2.122 0l.707.707Zm-2 1.293l1 1l.708-.708l-1-1l-.708.708ZM7.5 5a.5.5 0 0 1-.5-.5H6A1.5 1.5 0 0 0 7.5 6V5Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 9 4.5H8ZM7.5 4a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 7.5 3v1Zm0-1A1.5 1.5 0 0 0 6 4.5h1a.5.5 0 0 1 .5-.5V3Zm.646 2.854l1.5 1.5l.707-.708l-1.5-1.5l-.707.708ZM10.5 8a.5.5 0 0 1-.5-.5H9A1.5 1.5 0 0 0 10.5 9V8Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 12 7.5h-1Zm-.5-.5a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 10.5 6v1Zm0-1A1.5 1.5 0 0 0 9 7.5h1a.5.5 0 0 1 .5-.5V6ZM7 5.5v4h1v-4H7Zm.5 5.5a.5.5 0 0 1-.5-.5H6A1.5 1.5 0 0 0 7.5 12v-1Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 9 10.5H8Zm-.5-.5a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 7.5 9v1Zm0-1A1.5 1.5 0 0 0 6 10.5h1a.5.5 0 0 1 .5-.5V9Z" />
</svg>
Sources
</a>
</li>
<li title="Security" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('security.private-key.index') }}">
<svg class="{{ request()->is('security*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m16.555 3.843l3.602 3.602a2.877 2.877 0 0 1 0 4.069l-2.643 2.643a2.877 2.877 0 0 1-4.069 0l-.301-.301l-6.558 6.558a2 2 0 0 1-1.239.578L5.172 21H4a1 1 0 0 1-.993-.883L3 20v-1.172a2 2 0 0 1 .467-1.284l.119-.13L4 17h2v-2h2v-2l2.144-2.144l-.301-.301a2.877 2.877 0 0 1 0-4.069l2.643-2.643a2.877 2.877 0 0 1 4.069 0zM15 9h.01" />
</svg>
Security
</a>
</li>
<li title="Profile" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('profile') }}">
<svg xmlns="http://www.w3.org/2000/svg" class="{{ request()->is('profile*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
<path d="M12 10m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0" />
<path d="M6.168 18.849a4 4 0 0 1 3.832 -2.849h4a4 4 0 0 1 3.834 2.855" />
</svg>
Profile
</a>
</li>
<li title="Teams" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('team.index') }}">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="{{ request()->is('team*') ? 'text-warning icon' : 'icon' }}" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10 13a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M8 21v-1a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v1" />
<path d="M15 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M17 10h2a2 2 0 0 1 2 2v1" />
<path d="M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M3 13v-1a2 2 0 0 1 2 -2h2" />
</svg>
Teams @if (isCloud())
/ Subscription
@endif
</a>
</li>
<li title="Notifications" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('notification.index') }}">
<svg class="{{ request()->is('notifications*') ? 'text-warning icon' : 'icon' }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 5a2 2 0 1 1 4 0a7 7 0 0 1 4 6v3a4 4 0 0 0 2 3H4a4 4 0 0 0 2-3v-3a7 7 0 0 1 4-6M9 17v1a3 3 0 0 0 6 0v-1" />
</svg>
Notifications
</a>
</li>
@if (isInstanceAdmin())
<li title="Settings" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="/settings">
<svg xmlns="http://www.w3.org/2000/svg" class="{{ request()->is('settings*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z" />
<path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" />
</svg>
Settings
</a>
</li>
@endif
<li title="Guided Tour" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('onboarding') }}">
<svg class="{{ request()->is('onboarding*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M224 128a8 8 0 0 1-8 8h-88a8 8 0 0 1 0-16h88a8 8 0 0 1 8 8m-96-56h88a8 8 0 0 0 0-16h-88a8 8 0 0 0 0 16m88 112h-88a8 8 0 0 0 0 16h88a8 8 0 0 0 0-16M82.34 42.34L56 68.69L45.66 58.34a8 8 0 0 0-11.32 11.32l16 16a8 8 0 0 0 11.32 0l32-32a8 8 0 0 0-11.32-11.32m0 64L56 132.69l-10.34-10.35a8 8 0 0 0-11.32 11.32l16 16a8 8 0 0 0 11.32 0l32-32a8 8 0 0 0-11.32-11.32m0 64L56 196.69l-10.34-10.35a8 8 0 0 0-11.32 11.32l16 16a8 8 0 0 0 11.32 0l32-32a8 8 0 0 0-11.32-11.32" />
</svg>
Guided Tour
</a>
</li>
</div>
</button>
</div>
<div x-show="open" x-cloak class="absolute left-0 z-10 w-56 mx-4 mt-2 origin-top-right rounded shadow-lg bg-coolgray-100 ring-1 ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="menu-button" tabindex="-1">
<div class="py-1" role="none">
<li title="Tags" class="border-transparent hover:bg-coolgray-200 ">
<a class=" hover:bg-transparent hover:no-underline" href="{{ route('tags.index') }}">
<svg class="{{ request()->is('tags*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<path d="M3 8v4.172a2 2 0 0 0 .586 1.414l5.71 5.71a2.41 2.41 0 0 0 3.408 0l3.592-3.592a2.41 2.41 0 0 0 0-3.408l-5.71-5.71A2 2 0 0 0 9.172 6H5a2 2 0 0 0-2 2" />
<path d="m18 19l1.592-1.592a4.82 4.82 0 0 0 0-6.816L15 6m-8 4h-.01" />
</g>
</svg>
Tags
</a>
</li>
<li title="Command Center" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('command-center') }}">
<svg xmlns="http://www.w3.org/2000/svg" class="{{ request()->is('command-center*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M5 7l5 5l-5 5" />
<path d="M12 19l7 0" />
</svg>
Command Center
</a>
</li>
<li title="Source" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('source.all') }}">
<svg class="{{ request()->is('source*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="m6.793 1.207l.353.354l-.353-.354ZM1.207 6.793l-.353-.354l.353.354Zm0 1.414l.354-.353l-.354.353Zm5.586 5.586l-.354.353l.354-.353Zm1.414 0l-.353-.354l.353.354Zm5.586-5.586l.353.354l-.353-.354Zm0-1.414l-.354.353l.354-.353ZM8.207 1.207l.354-.353l-.354.353ZM6.44.854L.854 6.439l.707.707l5.585-5.585L6.44.854ZM.854 8.56l5.585 5.585l.707-.707l-5.585-5.585l-.707.707Zm7.707 5.585l5.585-5.585l-.707-.707l-5.585 5.585l.707.707Zm5.585-7.707L8.561.854l-.707.707l5.585 5.585l.707-.707Zm0 2.122a1.5 1.5 0 0 0 0-2.122l-.707.707a.5.5 0 0 1 0 .708l.707.707ZM6.44 14.146a1.5 1.5 0 0 0 2.122 0l-.707-.707a.5.5 0 0 1-.708 0l-.707.707ZM.854 6.44a1.5 1.5 0 0 0 0 2.122l.707-.707a.5.5 0 0 1 0-.708L.854 6.44Zm6.292-4.878a.5.5 0 0 1 .708 0L8.56.854a1.5 1.5 0 0 0-2.122 0l.707.707Zm-2 1.293l1 1l.708-.708l-1-1l-.708.708ZM7.5 5a.5.5 0 0 1-.5-.5H6A1.5 1.5 0 0 0 7.5 6V5Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 9 4.5H8ZM7.5 4a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 7.5 3v1Zm0-1A1.5 1.5 0 0 0 6 4.5h1a.5.5 0 0 1 .5-.5V3Zm.646 2.854l1.5 1.5l.707-.708l-1.5-1.5l-.707.708ZM10.5 8a.5.5 0 0 1-.5-.5H9A1.5 1.5 0 0 0 10.5 9V8Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 12 7.5h-1Zm-.5-.5a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 10.5 6v1Zm0-1A1.5 1.5 0 0 0 9 7.5h1a.5.5 0 0 1 .5-.5V6ZM7 5.5v4h1v-4H7Zm.5 5.5a.5.5 0 0 1-.5-.5H6A1.5 1.5 0 0 0 7.5 12v-1Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 9 10.5H8Zm-.5-.5a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 7.5 9v1Zm0-1A1.5 1.5 0 0 0 6 10.5h1a.5.5 0 0 1 .5-.5V9Z" />
</svg>
Sources
</a>
</li>
<li title="Security" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('security.private-key.index') }}">
<svg class="{{ request()->is('security*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m16.555 3.843l3.602 3.602a2.877 2.877 0 0 1 0 4.069l-2.643 2.643a2.877 2.877 0 0 1-4.069 0l-.301-.301l-6.558 6.558a2 2 0 0 1-1.239.578L5.172 21H4a1 1 0 0 1-.993-.883L3 20v-1.172a2 2 0 0 1 .467-1.284l.119-.13L4 17h2v-2h2v-2l2.144-2.144l-.301-.301a2.877 2.877 0 0 1 0-4.069l2.643-2.643a2.877 2.877 0 0 1 4.069 0zM15 9h.01" />
</svg>
Security
</a>
</li>
<li title="Profile" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('profile') }}">
<svg xmlns="http://www.w3.org/2000/svg" class="{{ request()->is('profile*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
<path d="M12 10m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0" />
<path d="M6.168 18.849a4 4 0 0 1 3.832 -2.849h4a4 4 0 0 1 3.834 2.855" />
</svg>
Profile
</a>
</li>
<li title="Teams" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('team.index') }}">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="{{ request()->is('team*') ? 'text-warning icon' : 'icon' }}" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10 13a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M8 21v-1a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v1" />
<path d="M15 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M17 10h2a2 2 0 0 1 2 2v1" />
<path d="M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M3 13v-1a2 2 0 0 1 2 -2h2" />
</svg>
Teams @if (isCloud())
/ Subscription
@endif
</a>
</li>
<li title="Notifications" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('notification.index') }}">
<svg class="{{ request()->is('notifications*') ? 'text-warning icon' : 'icon' }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 5a2 2 0 1 1 4 0a7 7 0 0 1 4 6v3a4 4 0 0 0 2 3H4a4 4 0 0 0 2-3v-3a7 7 0 0 1 4-6M9 17v1a3 3 0 0 0 6 0v-1" />
</svg>
Notifications
</a>
</li>
@if (isInstanceAdmin())
<li title="Settings" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="/settings">
<svg xmlns="http://www.w3.org/2000/svg" class="{{ request()->is('settings*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z" />
<path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" />
</svg>
Settings
</a>
</li>
@endif
<li title="Guided Tour" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('onboarding') }}">
<svg class="{{ request()->is('onboarding*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M224 128a8 8 0 0 1-8 8h-88a8 8 0 0 1 0-16h88a8 8 0 0 1 8 8m-96-56h88a8 8 0 0 0 0-16h-88a8 8 0 0 0 0 16m88 112h-88a8 8 0 0 0 0 16h88a8 8 0 0 0 0-16M82.34 42.34L56 68.69L45.66 58.34a8 8 0 0 0-11.32 11.32l16 16a8 8 0 0 0 11.32 0l32-32a8 8 0 0 0-11.32-11.32m0 64L56 132.69l-10.34-10.35a8 8 0 0 0-11.32 11.32l16 16a8 8 0 0 0 11.32 0l32-32a8 8 0 0 0-11.32-11.32m0 64L56 196.69l-10.34-10.35a8 8 0 0 0-11.32 11.32l16 16a8 8 0 0 0 11.32 0l32-32a8 8 0 0 0-11.32-11.32" />
</svg>
Guided Tour
</a>
</li>
</div>
</div>
@if (isCloud() && isInstanceAdmin())
<li title="Admin">
<a class="hover:bg-transparent" href="/admin">
<svg class="text-pink-600 icon" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M177.62 159.6a52 52 0 0 1-34 34a12.2 12.2 0 0 1-3.6.55a12 12 0 0 1-3.6-23.45a28 28 0 0 0 18.32-18.32a12 12 0 0 1 22.9 7.2ZM220 144a92 92 0 0 1-184 0c0-28.81 11.27-58.18 33.48-87.28a12 12 0 0 1 17.9-1.33l19.69 19.11L127 19.89a12 12 0 0 1 18.94-5.12C168.2 33.25 220 82.85 220 144m-24 0c0-41.71-30.61-78.39-52.52-99.29l-20.21 55.4a12 12 0 0 1-19.63 4.5L80.71 82.36C67 103.38 60 124.06 60 144a68 68 0 0 0 136 0" />
</div>
@if (isCloud() && isInstanceAdmin())
<li title="Admin">
<a class="hover:bg-transparent" href="/admin">
<svg class="text-pink-600 icon" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M177.62 159.6a52 52 0 0 1-34 34a12.2 12.2 0 0 1-3.6.55a12 12 0 0 1-3.6-23.45a28 28 0 0 0 18.32-18.32a12 12 0 0 1 22.9 7.2ZM220 144a92 92 0 0 1-184 0c0-28.81 11.27-58.18 33.48-87.28a12 12 0 0 1 17.9-1.33l19.69 19.11L127 19.89a12 12 0 0 1 18.94-5.12C168.2 33.25 220 82.85 220 144m-24 0c0-41.71-30.61-78.39-52.52-99.29l-20.21 55.4a12 12 0 0 1-19.63 4.5L80.71 82.36C67 103.38 60 124.06 60 144a68 68 0 0 0 136 0" />
</svg>
</a>
</li>
@endif
<div class="flex-1"></div>
@if (isInstanceAdmin() && !isCloud())
@persist('upgrade')
<livewire:upgrade />
@endpersist
@endif
<li title="Get Involved">
<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">
<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>
</a>
</li>
<li title="Get help!">
<a href="https://lasthourhosting.org/contact.html" target="_blank">
<<<<<<< HEAD
<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">
<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)
=======
<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">
<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)
</svg>
</a>
</li>
@endif
<div class="flex-1"></div>
@if (isInstanceAdmin() && !isCloud())
@persist('upgrade')
<livewire:upgrade />
@endpersist
@endif
<li title="Get Involved">
<a class="justify-center hover:text-white hover:bg-transparent" href="https://githaven.org/Shiloh/lasthourcloud" target="_blank">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 16 16">
<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" />
</div>
</a>
</li>
<form action="/logout" method="POST" class="hover:bg-transparent">
<li title="Logout" class="mb-6 hover:transparent">
@csrf
<button type="submit" class="rounded-none hover:text-white hover:bg-transparent">
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2a9.985 9.985 0 0 1 8 4h-2.71a8 8 0 1 0 .001 12h2.71A9.985 9.985 0 0 1 12 22m7-6v-3h-8v-2h8V8l5 4z" />
</svg>
</a>
</li>
<li title="Get help!">
<a href="https://lasthourhosting.org/contact.html" target="_blank">
<<<<<<< HEAD
<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">
<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)
=======
<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">
<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)
</svg>
</div>
</a>
</button>
</li>
<<<<<<< HEAD
<form action="/logout" method="POST" class="hover:bg-transparent">
<li title="Logout" class="mb-6 hover:transparent">
@csrf
<button type="submit" class="rounded-none hover:text-white hover:bg-transparent">
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2a9.985 9.985 0 0 1 8 4h-2.71a8 8 0 1 0 .001 12h2.71A9.985 9.985 0 0 1 12 22m7-6v-3h-8v-2h8V8l5 4z" />
<path fill="currentColor"
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2a9.985 9.985 0 0 1 8 4h-2.71a8 8 0 1 0 .001 12h2.71A9.985 9.985 0 0 1 12 22m7-6v-3h-8v-2h8V8l5 4z" />
</svg>
</button>
>>>>>>> 35700ec24 (main: begin major rewrite for lasthour)
</li>
<<<<<<< HEAD
<form action="/logout" method="POST" class="hover:bg-transparent">
<li title="Logout" class="mb-6 hover:transparent">
@csrf
<button type="submit" class="rounded-none hover:text-white hover:bg-transparent">
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2a9.985 9.985 0 0 1 8 4h-2.71a8 8 0 1 0 .001 12h2.71A9.985 9.985 0 0 1 12 22m7-6v-3h-8v-2h8V8l5 4z" />
</svg>
</button>
>>>>>>> 35700ec24 (main: begin major rewrite for lasthour)
</li>
<li>
<a href="#"
class="flex p-2 text-sm font-semibold leading-6 text-gray-700 rounded-md hover:text-indigo-600 hover:bg-gray-50 group gap-x-3">
<span
class="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border text-[0.625rem] font-medium bg-white text-gray-400 border-gray-200 group-hover:border-indigo-600 group-hover:text-indigo-600">W</span>
<span class="truncate">Workcation</span>
</a>
</li>
</ul>
<li>
<a href="#"
class="flex p-2 text-sm font-semibold leading-6 text-gray-700 rounded-md hover:text-indigo-600 hover:bg-gray-50 group gap-x-3">
<span
class="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border text-[0.625rem] font-medium bg-white text-gray-400 border-gray-200 group-hover:border-indigo-600 group-hover:text-indigo-600">W</span>
<span class="truncate">Workcation</span>
</a>
</li>
</ul>
</li>
<li class="mt-auto -mx-6">
<a href="#"
@ -558,10 +558,10 @@ class="flex items-center px-6 py-3 text-sm font-semibold leading-6 text-gray-900
</a>
</li> --}}
</ul>
</nav>
=======
</form>
</nav>
=======
</form>
</ul>
</nav>
@endauth
>>>>>>> 04e370917 (main: fix for various bugs)
</nav>
@endauth
>>>>>>> 04e370917 (main: fix for various bugs)

View File

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

View File

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

View File

@ -1,56 +1,104 @@
#!/bin/bash
# Sync docker volumes between two servers
#!/usr/bin/env bash
VERSION="1.0.0"
SOURCE=$1
DESTINATION=$2
# Inspired on https://github.com/adriancooney/Taskfile
#
# Install an alias, to be able to simply execute `run`
# echo 'alias run=./scripts/run' >> ~/.aliases
#
# Define Docker Compose command prefix...
set -e
if [ -z "$SOURCE" ]; then
echo "Source server is not specified."
exit 1
fi
if [ -z "$DESTINATION" ]; then
echo "Destination server is not specified."
exit 1
if [ $? == 0 ]; then
DOCKER_COMPOSE="docker compose"
else
DOCKER_COMPOSE="docker-compose"
fi
SOURCE_USER=$(echo $SOURCE | cut -d@ -f1)
SOURCE_SERVER=$(echo $SOURCE | cut -d: -f1 | cut -d@ -f2)
SOURCE_PORT=$(echo $SOURCE | cut -d: -f2 | cut -d/ -f1)
SOURCE_VOLUME_NAME=$(echo $SOURCE | cut -d/ -f2)
function help {
echo "$0 <task> <args>"
echo "Tasks:"
compgen -A function | cat -n
}
if ! [[ "$SOURCE_PORT" =~ ^[0-9]+$ ]]; then
echo "Invalid source port: $SOURCE_PORT"
exit 1
fi
# function sync:v3 {
# if [ -z "$1" ]; then
# echo -e "Please provide a version.\n\nExample: run sync:v3 3.12.32"
# 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)
DESTINATION_SERVER=$(echo $DESTINATION | cut -d: -f1 | cut -d@ -f2)
DESTINATION_PORT=$(echo $DESTINATION | cut -d: -f2 | cut -d/ -f1)
DESTINATION_VOLUME_NAME=$(echo $DESTINATION | cut -d/ -f2)
# function queue {
# bash spin exec -u webuser coolify php artisan queue:listen
# }
if ! [[ "$DESTINATION_PORT" =~ ^[0-9]+$ ]]; then
echo "Invalid destination port: $DESTINATION_PORT"
exit 1
fi
# function horizon {
# bash spin exec -u webuser coolify php artisan horizon -vvv
# }
echo "Generating backup file to ./$SOURCE_VOLUME_NAME.tgz"
ssh -p $SOURCE_PORT $SOURCE_USER@$SOURCE_SERVER "docker run -v $SOURCE_VOLUME_NAME:/volume --rm --log-driver none loomchild/volume-backup backup -c pigz -v" >./$SOURCE_VOLUME_NAME.tgz
echo ""
if [ -f "./$SOURCE_VOLUME_NAME.tgz" ]; then
echo "Uploading backup file to $DESTINATION_SERVER:~/$DESTINATION_VOLUME_NAME.tgz"
scp -P $DESTINATION_PORT ./$SOURCE_VOLUME_NAME.tgz $DESTINATION_USER@$DESTINATION_SERVER:~/$DESTINATION_VOLUME_NAME.tgz
echo ""
echo "Restoring backup file on remote ($DESTINATION_SERVER:/~/$DESTINATION_VOLUME_NAME.tgz)"
ssh -p $DESTINATION_PORT $DESTINATION_USER@$DESTINATION_SERVER "docker run -i -v $DESTINATION_VOLUME_NAME:/volume --log-driver none --rm loomchild/volume-backup restore -c pigz -vf < ~/$DESTINATION_VOLUME_NAME.tgz"
echo ""
echo "Deleting backup file on remote ($DESTINATION_SERVER:/~/$DESTINATION_VOLUME_NAME.tgz)"
ssh -p $DESTINATION_PORT $DESTINATION_USER@$DESTINATION_SERVER "rm ~/$DESTINATION_VOLUME_NAME.tgz"
# function schedule {
# bash spin exec -u webuser coolify php artisan schedule:work
# }
echo ""
echo "Local file ./$SOURCE_VOLUME_NAME.tgz is not deleted."
# function schedule:run {
# bash spin exec -u webuser coolify php artisan schedule:run
# }
echo ""
echo "WARNING: If you are copying a database volume, you need to set the right users/passwords on the destination service's environment variables."
echo "Why? Because we are copying the volume as-is, so the database credentials will bethe same as on the source volume."
fi
# function db {
# bash spin exec -u webuser coolify php artisan db
# }
# 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
## Do not modify this file. You will lose the ability to autoupdate!
VERSION="1.1"
CDN="https://cdn.lasthourhosting.org/lasthourcloudprod"
LATEST_IMAGE=${1:-latest}
LATEST_HELPER_VERSION=${2:-latest}
VERSION="1.0.5"
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.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
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
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
@ -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
fi
# Make sure Last Hour Cloud network exists
# It is created when starting Last Hour Cloud with docker compose
# 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 -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
docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper:${LATEST_HELPER_VERSION:-latest} bash -c "LATEST_IMAGE=${1:-} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml up -d --remove-orphans --force-recreate --wait --wait-timeout 60"
fi
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