Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
361e0a0430 |
132
README.md
132
README.md
@ -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 open-source & self-hostable Heroku, Netlify 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)
|
|
@ -57,14 +57,14 @@ private function update()
|
|||||||
} else {
|
} else {
|
||||||
ray('Running update on production server');
|
ray('Running update on production server');
|
||||||
remote_process([
|
remote_process([
|
||||||
"curl -fsSL https://cdn.lasthourhosting.org/lasthourcloudprod/scripts/upgrade.sh -o /data/coolify/source/upgrade.sh",
|
"curl -fsSL https://cdn.lasthourhosting.org/lasthourcloud/scripts/upgrade.sh -o /data/coolify/source/upgrade.sh",
|
||||||
"bash /data/coolify/source/upgrade.sh $this->latestVersion"
|
"bash /data/coolify/source/upgrade.sh $this->latestVersion"
|
||||||
], $this->server);
|
], $this->server);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
remote_process([
|
remote_process([
|
||||||
"curl -fsSL https://cdn.lasthourhosting.org/lasthourcloudprod/scripts/upgrade.sh -o /data/coolify/source/upgrade.sh",
|
'curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh',
|
||||||
"bash /data/coolify/source/upgrade.sh $this->latestVersion",
|
"bash /data/coolify/source/upgrade.sh $this->latestVersion",
|
||||||
], $this->server);
|
], $this->server);
|
||||||
}
|
}
|
||||||
|
@ -163,7 +163,7 @@ function get_route_parameters(): array
|
|||||||
function get_latest_sentinel_version(): string
|
function get_latest_sentinel_version(): string
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$response = Http::get('https://cdn.lasthourhosting.org/lasthourcloudprod/versions.json');
|
$response = Http::get('https://cdn.lasthourhosting.org/lasthourcloud/versions.json');
|
||||||
$versions = $response->json();
|
$versions = $response->json();
|
||||||
|
|
||||||
return data_get($versions, 'sentinel.version');
|
return data_get($versions, 'sentinel.version');
|
||||||
@ -388,7 +388,7 @@ function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null
|
|||||||
Mail::send(
|
Mail::send(
|
||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
fn(Message $message) => $message
|
fn (Message $message) => $message
|
||||||
->to($email)
|
->to($email)
|
||||||
->replyTo($email)
|
->replyTo($email)
|
||||||
->cc($cc)
|
->cc($cc)
|
||||||
@ -399,7 +399,7 @@ function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null
|
|||||||
Mail::send(
|
Mail::send(
|
||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
fn(Message $message) => $message
|
fn (Message $message) => $message
|
||||||
->to($email)
|
->to($email)
|
||||||
->subject($mail->subject)
|
->subject($mail->subject)
|
||||||
->html((string) $mail->render())
|
->html((string) $mail->render())
|
||||||
@ -2331,15 +2331,15 @@ function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId =
|
|||||||
$applications = Application::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid']);
|
$applications = Application::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid']);
|
||||||
$serviceApplications = ServiceApplication::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid']);
|
$serviceApplications = ServiceApplication::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid']);
|
||||||
if ($uuid) {
|
if ($uuid) {
|
||||||
$applications = $applications->filter(fn($app) => $app->uuid !== $uuid);
|
$applications = $applications->filter(fn ($app) => $app->uuid !== $uuid);
|
||||||
$serviceApplications = $serviceApplications->filter(fn($app) => $app->uuid !== $uuid);
|
$serviceApplications = $serviceApplications->filter(fn ($app) => $app->uuid !== $uuid);
|
||||||
}
|
}
|
||||||
$domainFound = false;
|
$domainFound = false;
|
||||||
foreach ($applications as $app) {
|
foreach ($applications as $app) {
|
||||||
if (is_null($app->fqdn)) {
|
if (is_null($app->fqdn)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn($fqdn) => $fqdn !== '');
|
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
|
||||||
foreach ($list_of_domains as $domain) {
|
foreach ($list_of_domains as $domain) {
|
||||||
if (str($domain)->endsWith('/')) {
|
if (str($domain)->endsWith('/')) {
|
||||||
$domain = str($domain)->beforeLast('/');
|
$domain = str($domain)->beforeLast('/');
|
||||||
@ -2358,7 +2358,7 @@ function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId =
|
|||||||
if (str($app->fqdn)->isEmpty()) {
|
if (str($app->fqdn)->isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn($fqdn) => $fqdn !== '');
|
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
|
||||||
foreach ($list_of_domains as $domain) {
|
foreach ($list_of_domains as $domain) {
|
||||||
if (str($domain)->endsWith('/')) {
|
if (str($domain)->endsWith('/')) {
|
||||||
$domain = str($domain)->beforeLast('/');
|
$domain = str($domain)->beforeLast('/');
|
||||||
@ -2408,7 +2408,7 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
|
|||||||
});
|
});
|
||||||
$apps = Application::all();
|
$apps = Application::all();
|
||||||
foreach ($apps as $app) {
|
foreach ($apps as $app) {
|
||||||
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn($fqdn) => $fqdn !== '');
|
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
|
||||||
foreach ($list_of_domains as $domain) {
|
foreach ($list_of_domains as $domain) {
|
||||||
if (str($domain)->endsWith('/')) {
|
if (str($domain)->endsWith('/')) {
|
||||||
$domain = str($domain)->beforeLast('/');
|
$domain = str($domain)->beforeLast('/');
|
||||||
@ -2427,7 +2427,7 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
|
|||||||
}
|
}
|
||||||
$apps = ServiceApplication::all();
|
$apps = ServiceApplication::all();
|
||||||
foreach ($apps as $app) {
|
foreach ($apps as $app) {
|
||||||
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn($fqdn) => $fqdn !== '');
|
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
|
||||||
foreach ($list_of_domains as $domain) {
|
foreach ($list_of_domains as $domain) {
|
||||||
if (str($domain)->endsWith('/')) {
|
if (str($domain)->endsWith('/')) {
|
||||||
$domain = str($domain)->beforeLast('/');
|
$domain = str($domain)->beforeLast('/');
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
coolify:
|
coolify:
|
||||||
image: "githaven.org/shiloh/lasthourcloud:prod"
|
image: "githaven.org/shiloh/lasthourcloud:latest"
|
||||||
volumes:
|
volumes:
|
||||||
- type: bind
|
- type: bind
|
||||||
source: /data/coolify/source/.env
|
source: /data/coolify/source/.env
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<<<<<<< HEAD
|
<<<<<<< HEAD
|
||||||
<nav class="flex flex-col flex-1 bg-white border-r dark:border-coolgray-200 dark:bg-base" x-data="{
|
<nav class="flex flex-col flex-1 bg-white border-r dark:border-coolgray-200 dark:bg-base" x-data="{
|
||||||
switchWidth() {
|
switchWidth() {
|
||||||
if (this.full === 'full') {
|
if (this.full === 'full') {
|
||||||
localStorage.removeItem('pageWidth');
|
localStorage.removeItem('pageWidth');
|
||||||
@ -336,216 +336,216 @@ class="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border text-
|
|||||||
@endpersist
|
@endpersist
|
||||||
@endif
|
@endif
|
||||||
<li title="Get Involved">
|
<li title="Get Involved">
|
||||||
<a class="hover:bg-transparent" href="https://githaven.org/Shiloh/lasthourcloud" target="_blank">
|
<a class="hover:bg-transparent" href="https://shilohcode.com/get-involved" target="_blank">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon hover:text-blue-500" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon hover:text-blue-500" viewBox="0 0 16 16">
|
||||||
<path d="M13.5 1a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3M11 2.5a2.5 2.5 0 1 1 .603 1.628l-6.718 3.12a2.5 2.5 0 0 1 0 1.504l6.718 3.12a2.5 2.5 0 1 1-.488.876l-6.718-3.12a2.5 2.5 0 1 1 0-3.256l6.718-3.12A2.5 2.5 0 0 1 11 2.5m-8.5 4a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3m11 5.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3"/>
|
<path d="M13.5 1a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3M11 2.5a2.5 2.5 0 1 1 .603 1.628l-6.718 3.12a2.5 2.5 0 0 1 0 1.504l6.718 3.12a2.5 2.5 0 1 1-.488.876l-6.718-3.12a2.5 2.5 0 1 1 0-3.256l6.718-3.12A2.5 2.5 0 0 1 11 2.5m-8.5 4a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3m11 5.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3"/>
|
||||||
=======
|
=======
|
||||||
@auth
|
@auth
|
||||||
<nav class="fixed h-full pt-28 scrollbar">
|
<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>
|
<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">
|
<ul class="flex flex-col h-full gap-4 menu flex-nowrap">
|
||||||
<li title="Dashboard">
|
<li title="Dashboard">
|
||||||
<a class="hover:bg-transparent" href="/">
|
<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">
|
<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" />
|
<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>
|
</svg>
|
||||||
</a>
|
</button>
|
||||||
</li>
|
</div>
|
||||||
<li title="Servers">
|
<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">
|
||||||
<a class="hover:bg-transparent" href="/servers">
|
<div class="py-1" role="none">
|
||||||
<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">
|
<li title="Tags" class="border-transparent hover:bg-coolgray-200 ">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<a class=" hover:bg-transparent hover:no-underline" href="{{ route('tags.index') }}">
|
||||||
<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" />
|
<svg class="{{ request()->is('tags*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M15 20h-9a3 3 0 0 1 -3 -3v-2a3 3 0 0 1 3 -3h12" />
|
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||||
<path d="M7 8v.01" />
|
<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="M7 16v.01" />
|
<path d="m18 19l1.592-1.592a4.82 4.82 0 0 0 0-6.816L15 6m-8 4h-.01" />
|
||||||
<path d="M20 15l-2 3h3l-2 3" />
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
Tags
|
||||||
</li>
|
</a>
|
||||||
<li title="Projects">
|
</li>
|
||||||
<a class="hover:bg-transparent" href="/projects">
|
<li title="Command Center" class="hover:bg-coolgray-200">
|
||||||
<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">
|
<a class="hover:bg-transparent hover:no-underline" href="{{ route('command-center') }}">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<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 d="M12 4l-8 4l8 4l8 -4l-8 -4" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<path d="M4 12l8 4l8 -4" />
|
<path d="M5 7l5 5l-5 5" />
|
||||||
<path d="M4 16l8 4l8 -4" />
|
<path d="M12 19l7 0" />
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
Command Center
|
||||||
</li>
|
</a>
|
||||||
<div class="inline-block text-left " x-data="{ open: false }">
|
</li>
|
||||||
<div>
|
<li title="Source" class="hover:bg-coolgray-200">
|
||||||
<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">
|
<a class="hover:bg-transparent hover:no-underline" href="{{ route('source.all') }}">
|
||||||
<svg class="icon" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
<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="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" />
|
<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>
|
</svg>
|
||||||
</button>
|
Sources
|
||||||
</div>
|
</a>
|
||||||
<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">
|
</li>
|
||||||
<div class="py-1" role="none">
|
<li title="Security" class="hover:bg-coolgray-200">
|
||||||
<li title="Tags" class="border-transparent hover:bg-coolgray-200 ">
|
<a class="hover:bg-transparent hover:no-underline" href="{{ route('security.private-key.index') }}">
|
||||||
<a class=" hover:bg-transparent hover:no-underline" href="{{ route('tags.index') }}">
|
<svg class="{{ request()->is('security*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
<svg class="{{ request()->is('tags*') ? '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" />
|
||||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
</svg>
|
||||||
<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" />
|
Security
|
||||||
<path d="m18 19l1.592-1.592a4.82 4.82 0 0 0 0-6.816L15 6m-8 4h-.01" />
|
</a>
|
||||||
</g>
|
</li>
|
||||||
</svg>
|
<li title="Profile" class="hover:bg-coolgray-200">
|
||||||
Tags
|
<a class="hover:bg-transparent hover:no-underline" href="{{ route('profile') }}">
|
||||||
</a>
|
<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">
|
||||||
</li>
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<li title="Command Center" class="hover:bg-coolgray-200">
|
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
|
||||||
<a class="hover:bg-transparent hover:no-underline" href="{{ route('command-center') }}">
|
<path d="M12 10m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0" />
|
||||||
<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 d="M6.168 18.849a4 4 0 0 1 3.832 -2.849h4a4 4 0 0 1 3.834 2.855" />
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
</svg>
|
||||||
<path d="M5 7l5 5l-5 5" />
|
Profile
|
||||||
<path d="M12 19l7 0" />
|
</a>
|
||||||
</svg>
|
</li>
|
||||||
Command Center
|
<li title="Teams" class="hover:bg-coolgray-200">
|
||||||
</a>
|
<a class="hover:bg-transparent hover:no-underline" href="{{ route('team.index') }}">
|
||||||
</li>
|
<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">
|
||||||
<li title="Source" class="hover:bg-coolgray-200">
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<a class="hover:bg-transparent hover:no-underline" href="{{ route('source.all') }}">
|
<path d="M10 13a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
|
||||||
<svg class="{{ request()->is('source*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg">
|
<path d="M8 21v-1a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v1" />
|
||||||
<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" />
|
<path d="M15 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
|
||||||
</svg>
|
<path d="M17 10h2a2 2 0 0 1 2 2v1" />
|
||||||
Sources
|
<path d="M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
|
||||||
</a>
|
<path d="M3 13v-1a2 2 0 0 1 2 -2h2" />
|
||||||
</li>
|
</svg>
|
||||||
<li title="Security" class="hover:bg-coolgray-200">
|
Teams @if (isCloud())
|
||||||
<a class="hover:bg-transparent hover:no-underline" href="{{ route('security.private-key.index') }}">
|
/ Subscription
|
||||||
<svg class="{{ request()->is('security*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
@endif
|
||||||
<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" />
|
</a>
|
||||||
</svg>
|
</li>
|
||||||
Security
|
<li title="Notifications" class="hover:bg-coolgray-200">
|
||||||
</a>
|
<a class="hover:bg-transparent hover:no-underline" href="{{ route('notification.index') }}">
|
||||||
</li>
|
<svg class="{{ request()->is('notifications*') ? 'text-warning icon' : 'icon' }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
<li title="Profile" class="hover:bg-coolgray-200">
|
<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" />
|
||||||
<a class="hover:bg-transparent hover:no-underline" href="{{ route('profile') }}">
|
</svg>
|
||||||
<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">
|
Notifications
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
</a>
|
||||||
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
|
</li>
|
||||||
<path d="M12 10m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0" />
|
@if (isInstanceAdmin())
|
||||||
<path d="M6.168 18.849a4 4 0 0 1 3.832 -2.849h4a4 4 0 0 1 3.834 2.855" />
|
<li title="Settings" class="hover:bg-coolgray-200">
|
||||||
</svg>
|
<a class="hover:bg-transparent hover:no-underline" href="/settings">
|
||||||
Profile
|
<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">
|
||||||
</a>
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
</li>
|
<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" />
|
||||||
<li title="Teams" class="hover:bg-coolgray-200">
|
<path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" />
|
||||||
<a class="hover:bg-transparent hover:no-underline" href="{{ route('team.index') }}">
|
</svg>
|
||||||
<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">
|
Settings
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
</a>
|
||||||
<path d="M10 13a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
|
</li>
|
||||||
<path d="M8 21v-1a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v1" />
|
@endif
|
||||||
<path d="M15 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
|
<li title="Guided Tour" class="hover:bg-coolgray-200">
|
||||||
<path d="M17 10h2a2 2 0 0 1 2 2v1" />
|
<a class="hover:bg-transparent hover:no-underline" href="{{ route('onboarding') }}">
|
||||||
<path d="M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
|
<svg class="{{ request()->is('onboarding*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M3 13v-1a2 2 0 0 1 2 -2h2" />
|
<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>
|
</svg>
|
||||||
Teams @if (isCloud())
|
Guided Tour
|
||||||
/ Subscription
|
</a>
|
||||||
@endif
|
</li>
|
||||||
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (isCloud() && isInstanceAdmin())
|
</div>
|
||||||
<li title="Admin">
|
@if (isCloud() && isInstanceAdmin())
|
||||||
<a class="hover:bg-transparent" href="/admin">
|
<li title="Admin">
|
||||||
<svg class="text-pink-600 icon" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
<a class="hover:bg-transparent" href="/admin">
|
||||||
<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 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>
|
</svg>
|
||||||
</a>
|
</div>
|
||||||
</li>
|
</a>
|
||||||
@endif
|
</li>
|
||||||
<div class="flex-1"></div>
|
<form action="/logout" method="POST" class="hover:bg-transparent">
|
||||||
@if (isInstanceAdmin() && !isCloud())
|
<li title="Logout" class="mb-6 hover:transparent">
|
||||||
@persist('upgrade')
|
@csrf
|
||||||
<livewire:upgrade />
|
<button type="submit" class="rounded-none hover:text-white hover:bg-transparent">
|
||||||
@endpersist
|
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
@endif
|
<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" />
|
||||||
<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" />
|
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</button>
|
||||||
</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>
|
|
||||||
</li>
|
</li>
|
||||||
|
<<<<<<< HEAD
|
||||||
<form action="/logout" method="POST" class="hover:bg-transparent">
|
<form action="/logout" method="POST" class="hover:bg-transparent">
|
||||||
<li title="Logout" class="mb-6 hover:transparent">
|
<li title="Logout" class="mb-6 hover:transparent">
|
||||||
@csrf
|
@csrf
|
||||||
<button type="submit" class="rounded-none hover:text-white hover:bg-transparent">
|
<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">
|
<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>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
>>>>>>> 35700ec24 (main: begin major rewrite for lasthour)
|
||||||
</li>
|
</li>
|
||||||
<<<<<<< HEAD
|
<li>
|
||||||
<form action="/logout" method="POST" class="hover:bg-transparent">
|
<a href="#"
|
||||||
<li title="Logout" class="mb-6 hover:transparent">
|
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">
|
||||||
@csrf
|
<span
|
||||||
<button type="submit" class="rounded-none hover:text-white hover:bg-transparent">
|
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>
|
||||||
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
<span class="truncate">Workcation</span>
|
||||||
<path fill="currentColor"
|
</a>
|
||||||
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" />
|
</li>
|
||||||
</svg>
|
</ul>
|
||||||
</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>
|
</li>
|
||||||
<li class="mt-auto -mx-6">
|
<li class="mt-auto -mx-6">
|
||||||
<a href="#"
|
<a href="#"
|
||||||
@ -558,10 +558,10 @@ class="flex items-center px-6 py-3 text-sm font-semibold leading-6 text-gray-900
|
|||||||
</a>
|
</a>
|
||||||
</li> --}}
|
</li> --}}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
=======
|
=======
|
||||||
</form>
|
</form>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
@endauth
|
@endauth
|
||||||
>>>>>>> 04e370917 (main: fix for various bugs)
|
>>>>>>> 04e370917 (main: fix for various bugs)
|
||||||
|
@ -3,143 +3,100 @@
|
|||||||
Onboarding | Last Hour Cloud
|
Onboarding | Last Hour Cloud
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<section class="flex flex-col h-full lg:items-center lg:justify-center">
|
<section class="flex flex-col h-full lg:items-center lg:justify-center">
|
||||||
<div
|
<div class="flex flex-col items-center justify-center p-10 mx-2 mt-10 bg-white border rounded-lg shadow lg:p-20 dark:bg-transparent dark:border-none max-w-7xl ">
|
||||||
class="flex flex-col items-center justify-center p-10 mx-2 mt-10 bg-white border rounded-lg shadow lg:p-20 dark:bg-transparent dark:border-none max-w-7xl ">
|
|
||||||
@if ($currentState === 'welcome')
|
@if ($currentState === 'welcome')
|
||||||
<h1 class="text-3xl font-bold lg:text-5xl">Welcome to Last Hour Cloud</h1>
|
<h1 class="text-5xl font-bold">Welcome to your Last Hour Cloud</h1>
|
||||||
<div class="py-6 text-center lg:text-xl">Let's help you get set up.</div>
|
<p class="py-6 text-xl text-center">Let's help you to set up the basics and show you around.</p>
|
||||||
<div class="flex justify-center ">
|
<div class="flex justify-center ">
|
||||||
<x-forms.button class="justify-center w-64 box-boarding"
|
<x-forms.button class="justify-center w-64 box" wire:click="$set('currentState','explanation')">Let's Start
|
||||||
wire:click="$set('currentState','explanation')">Get
|
|
||||||
Started
|
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
</div>
|
</div>
|
||||||
@elseif ($currentState === 'explanation')
|
@endif
|
||||||
<x-boarding-step title="What is Last Hour Cloud?">
|
</div>
|
||||||
|
<h1 class="text-5xl font-bold">Welcome to your Last Hour Cloud</h1>
|
||||||
|
<p class="py-6 text-xl text-center">Let's help you to set up the basics and show you around.</p>
|
||||||
|
<div class="flex justify-center ">
|
||||||
|
<x-forms.button class="justify-center w-64 box" wire:click="$set('currentState','explanation')">Let's Start
|
||||||
|
</x-forms.button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if ($currentState === 'explanation')
|
||||||
|
<x-boarding-step title="What is this?">
|
||||||
<x-slot:question>
|
<x-slot:question>
|
||||||
Last Hour Cloud is an all-in-one application to automate tasks on your servers, deploy application with
|
Last Hour Cloud is an all-in-one application to automate tasks on your servers, deploy applications with Git
|
||||||
Git
|
integrations, deploy databases and services, monitor these resources with notifications and alerts
|
||||||
integrations, deploy databases and services, monitor these resources with notifications and
|
|
||||||
alerts
|
|
||||||
without vendor lock-in
|
without vendor lock-in
|
||||||
and <a href="https://lasthourhosting.org/cloud-apps.html" class="dark:text-white hover:underline">more</a>.
|
and <a href="https://lasthourhosting.org/cloud.html" class="text-white hover:underline">much much more</a>.
|
||||||
<br><br>
|
<br><br>
|
||||||
<span class="text-xl">
|
<span class="text-xl">
|
||||||
<x-highlighted text="Cloud apps for the last hour" /></span>
|
<x-highlighted text="Self-hosting for the Last Hour Cloud." /></span>
|
||||||
</x-slot:question>
|
</x-slot:question>
|
||||||
<x-slot:explanation>
|
<x-slot:explanation>
|
||||||
<p>
|
<p><x-highlighted text="Task automation:" /> You do not to manage your servers too much. This does
|
||||||
<x-highlighted text="Task automation:" /> You don't need to manage your servers anymore.
|
it for you.</p>
|
||||||
Last Hour Cloud does
|
<p><x-highlighted text="No vendor lock-in:" /> All configurations are stored on your server, so
|
||||||
it for you.
|
everything works without this (except integrations and automations).</p>
|
||||||
</p>
|
<p><x-highlighted text="Monitoring:" />You will get notified on your favourite platform (Discord,
|
||||||
<p>
|
Telegram, Email, etc.) when something goes wrong, or if an action is needed from your side.</p>
|
||||||
<x-highlighted text="No vendor lock-in:" /> All configurations are stored on your servers, so
|
|
||||||
everything works without a connection to Last Hour Cloud (except integrations and automations).
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<x-highlighted text="Monitoring:" />You can get notified on your favourite platforms
|
|
||||||
(Discord,
|
|
||||||
Telegram, Email, etc.) when something goes wrong, or an action is needed from your side.
|
|
||||||
</p>
|
|
||||||
</x-slot:explanation>
|
</x-slot:explanation>
|
||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
<x-forms.button class="justify-center w-64 box-boarding" wire:click="explanation">Next
|
<x-forms.button class="justify-center w-64 box" wire:click="explanation">Next
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
</x-slot:actions>
|
</x-slot:actions>
|
||||||
</x-boarding-step>
|
</x-boarding-step>
|
||||||
@elseif ($currentState === 'select-server-type')
|
@endif
|
||||||
|
@if ($currentState === 'select-server-type')
|
||||||
<x-boarding-step title="Server">
|
<x-boarding-step title="Server">
|
||||||
<x-slot:question>
|
<x-slot:question>
|
||||||
Do you want to deploy your resources to your
|
Do you want to deploy your resources on your <x-highlighted text="Localhost" />
|
||||||
<x-highlighted text="Localhost" />
|
or on a <x-highlighted text="Remote Server" />?
|
||||||
or to a
|
|
||||||
<x-highlighted text="Remote Server" />?
|
|
||||||
</x-slot:question>
|
</x-slot:question>
|
||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
<x-forms.button class="justify-center w-64 box-boarding" wire:target="setServerType('localhost')"
|
<x-forms.button class="justify-center w-64 box" wire:target="setServerType('localhost')" wire:click="setServerType('localhost')">Localhost
|
||||||
wire:click="setServerType('localhost')">Localhost
|
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
|
|
||||||
<x-forms.button class="justify-center w-64 box-boarding " wire:target="setServerType('remote')"
|
<x-forms.button class="justify-center w-64 box " wire:target="setServerType('remote')" wire:click="setServerType('remote')">Remote Server
|
||||||
wire:click="setServerType('remote')">Remote Server
|
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
|
|
||||||
@if (!$serverReachable)
|
@if (!$serverReachable)
|
||||||
<div class="mt-6 p-4 border border-error rounded-lg text-gray-800 dark:text-gray-200">
|
Localhost is not reachable with the following public key.
|
||||||
<h2 class="text-lg font-bold mb-2">Server is not reachable</h2>
|
<br /> <br />
|
||||||
<p class="mb-4">Please check the connection details below and correct them if they are
|
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for user
|
||||||
incorrect.</p>
|
'root' or skip the guided tour and add a new private key manually to Last Hour Cloud and to the
|
||||||
|
server.
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
<br />
|
||||||
<x-forms.input placeholder="Default is 22" label="Port" id="remoteServerPort"
|
Check the upstream <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh">documentation</a> for further help.
|
||||||
wire:model="remoteServerPort" :value="$remoteServerPort" />
|
<x-forms.input readonly id="serverPublicKey"></x-forms.input>
|
||||||
<div>
|
<x-forms.button class="w-64 box" wire:target="setServerType('localhost')" wire:click="setServerType('localhost')">Check again
|
||||||
<x-forms.input placeholder="Default is root" label="User" id="remoteServerUser"
|
</x-forms.button>
|
||||||
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>
|
|
||||||
@endif
|
@endif
|
||||||
</x-slot:actions>
|
</x-slot:actions>
|
||||||
<x-slot:explanation>
|
<x-slot:explanation>
|
||||||
<p>Servers are the main building blocks, as they will host your applications, databases,
|
<p>Servers are the main building blocks, as they will host your applications, databases,
|
||||||
services, called resources. Any CPU intensive process will use the server's CPU where you
|
services, called resources. Any CPU intensive process will use the server's CPU where you
|
||||||
are deploying your resources.</p>
|
are deploying your resources.</p>
|
||||||
<p>
|
<p>Localhost is the server where Last Hour Cloud is running on. It is not recommended to use one server
|
||||||
<x-highlighted text="Localhost" /> is the server where Last Hour Cloud is running on. It is not
|
for everything.</p>
|
||||||
recommended to use one server
|
<p>Remote Server is a server reachable through SSH. It can be hosted at home, or from any cloud
|
||||||
for everything.
|
provider.</p>
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<x-highlighted text="A remote server" /> is a server reachable through SSH. It can be hosted
|
|
||||||
at home, or from any cloud
|
|
||||||
provider.
|
|
||||||
</p>
|
|
||||||
</x-slot:explanation>
|
</x-slot:explanation>
|
||||||
</x-boarding-step>
|
</x-boarding-step>
|
||||||
@elseif ($currentState === 'private-key')
|
@endif
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if ($currentState === 'private-key')
|
||||||
<x-boarding-step title="SSH Key">
|
<x-boarding-step title="SSH Key">
|
||||||
<x-slot:question>
|
<x-slot:question>
|
||||||
Do you have your own SSH Private Key?
|
Do you have your own SSH Private Key?
|
||||||
</x-slot:question>
|
</x-slot:question>
|
||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
<x-forms.button class="justify-center lg:w-64 box-boarding" wire:target="setPrivateKey('own')"
|
<x-forms.button class="justify-center w-64 box" wire:target="setPrivateKey('own')" wire:click="setPrivateKey('own')">Yes
|
||||||
wire:click="setPrivateKey('own')">Yes
|
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
<x-forms.button class="justify-center lg:w-64 box-boarding" wire:target="setPrivateKey('create')"
|
<x-forms.button class="justify-center w-64 box" wire:target="setPrivateKey('create')" wire:click="setPrivateKey('create')">No (create one for me)
|
||||||
wire:click="setPrivateKey('create')">No (create one for me)
|
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
@if (count($privateKeys) > 0)
|
@if (count($privateKeys) > 0)
|
||||||
<form wire:submit='selectExistingPrivateKey' class="flex flex-col w-full gap-4 lg:pr-10">
|
<form wire:submit='selectExistingPrivateKey' class="flex flex-col w-full gap-4 pr-10">
|
||||||
<x-forms.select label="Existing SSH Keys" id='selectedExistingPrivateKey'>
|
<x-forms.select label="Existing SSH Keys" id='selectedExistingPrivateKey'>
|
||||||
@foreach ($privateKeys as $privateKey)
|
@foreach ($privateKeys as $privateKey)
|
||||||
<option wire:key="{{ $loop->index }}" value="{{ $privateKey->id }}">
|
<option wire:key="{{ $loop->index }}" value="{{ $privateKey->id }}">
|
||||||
@ -153,14 +110,17 @@ class="bg-red-200 dark:bg-red-900 px-1 rounded">~/.ssh/authorized_keys</code>
|
|||||||
</x-slot:actions>
|
</x-slot:actions>
|
||||||
<x-slot:explanation>
|
<x-slot:explanation>
|
||||||
<p>SSH Keys are used to connect to a remote server through a secure shell, called SSH.</p>
|
<p>SSH Keys are used to connect to a remote server through a secure shell, called SSH.</p>
|
||||||
<p>You can use your own ssh private key, or you can let Last Hour Cloud to create one for you.</p>
|
<p>You can use your own ssh private key, or you can allow Last Hour Cloud to create one for you.</p>
|
||||||
<p>In both ways, you need to add the public version of your ssh private key to the remote
|
<p>In both ways, you need to add the public version of your ssh private key to the remote
|
||||||
server's
|
server's
|
||||||
<code class="dark:text-warning">~/.ssh/authorized_keys</code> file.
|
<code class="text-warning">~/.ssh/authorized_keys</code> file.
|
||||||
</p>
|
</p>
|
||||||
</x-slot:explanation>
|
</x-slot:explanation>
|
||||||
</x-boarding-step>
|
</x-boarding-step>
|
||||||
@elseif ($currentState === 'select-existing-server')
|
@endif
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if ($currentState === 'select-existing-server')
|
||||||
<x-boarding-step title="Select a server">
|
<x-boarding-step title="Select a server">
|
||||||
<x-slot:question>
|
<x-slot:question>
|
||||||
There are already servers available for your Team. Do you want to use one of them?
|
There are already servers available for your Team. Do you want to use one of them?
|
||||||
@ -168,8 +128,9 @@ class="bg-red-200 dark:bg-red-900 px-1 rounded">~/.ssh/authorized_keys</code>
|
|||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<div>
|
<div>
|
||||||
<x-forms.button class="justify-center w-64 box-boarding" wire:click="createNewServer">No
|
<x-forms.button class="justify-center w-64 box" wire:click="createNewServer">No (create one
|
||||||
(create one for me)
|
for
|
||||||
|
me)
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@ -186,75 +147,41 @@ class="bg-red-200 dark:bg-red-900 px-1 rounded">~/.ssh/authorized_keys</code>
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (!$serverReachable)
|
@if (!$serverReachable)
|
||||||
<div class="mt-6 p-4 bg-red-100 dark:bg-red-950 rounded-lg text-gray-800 dark:text-gray-200">
|
This server is not reachable with the following public key.
|
||||||
<h2 class="text-lg font-bold mb-2">Server is not reachable</h2>
|
<br /> <br />
|
||||||
<p class="mb-4">Please check the connection details below and correct them if they are
|
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for user
|
||||||
incorrect.</p>
|
'root' or skip the boarding process and add a new private key manually to Last Hour Cloud and to the
|
||||||
|
server.
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
<x-forms.input readonly id="serverPublicKey"></x-forms.input>
|
||||||
<x-forms.input placeholder="Default is 22" label="Port" id="remoteServerPort"
|
<x-forms.button class="w-64 box" wire:target="validateServer" wire:click="validateServer">Check
|
||||||
wire:model="remoteServerPort" :value="$remoteServerPort" />
|
again
|
||||||
<div>
|
</x-forms.button>
|
||||||
<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>
|
|
||||||
@endif
|
@endif
|
||||||
</x-slot:actions>
|
</x-slot:actions>
|
||||||
<x-slot:explanation>
|
<x-slot:explanation>
|
||||||
<p>Private Keys are used to connect to a remote server through a secure shell, called SSH.</p>
|
<p>Private Keys are used to connect to a remote server through a secure shell, called SSH.</p>
|
||||||
<p>You can use your own private key, or you can let Last Hour Cloud to create one for you.</p>
|
<p>You can use your own private key, or you can let Last Hour Cloud create one for you.</p>
|
||||||
<p>In both ways, you need to add the public version of your private key to the remote server's
|
<p>In both ways, you need to add the public version of your private key to the remote server's
|
||||||
<code>~/.ssh/authorized_keys</code> file.
|
<code>~/.ssh/authorized_keys</code> file.
|
||||||
</p>
|
</p>
|
||||||
</x-slot:explanation>
|
</x-slot:explanation>
|
||||||
</x-boarding-step>
|
</x-boarding-step>
|
||||||
@elseif ($currentState === 'create-private-key')
|
@endif
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if ($currentState === 'create-private-key')
|
||||||
<x-boarding-step title="Create Private Key">
|
<x-boarding-step title="Create Private Key">
|
||||||
<x-slot:question>
|
<x-slot:question>
|
||||||
Please let me know your key details.
|
Please let me know your key details.
|
||||||
</x-slot:question>
|
</x-slot:question>
|
||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
<form wire:submit='savePrivateKey' class="flex flex-col w-full gap-4 lg:pr-10">
|
<form wire:submit='savePrivateKey' class="flex flex-col w-full gap-4 pr-10">
|
||||||
<x-forms.input required placeholder="Choose a name for your Private Key. Could be anything."
|
<x-forms.input required placeholder="Choose a name for your Private Key. Could be anything." label="Name" id="privateKeyName" />
|
||||||
label="Name" id="privateKeyName" />
|
<x-forms.input placeholder="Description, so others will know more about this." label="Description" id="privateKeyDescription" />
|
||||||
<x-forms.input placeholder="Description, so others will know more about this."
|
<x-forms.textarea required placeholder="-----BEGIN OPENSSH PRIVATE KEY-----" label="Private Key" id="privateKey" />
|
||||||
label="Description" id="privateKeyDescription" />
|
|
||||||
<x-forms.textarea required placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"
|
|
||||||
label="Private Key" id="privateKey" />
|
|
||||||
@if ($privateKeyType === 'create')
|
@if ($privateKeyType === 'create')
|
||||||
<x-forms.textarea rows="7" readonly label="Public Key" id="publicKey" />
|
<x-forms.textarea rows="7" readonly label="Public Key" id="publicKey" />
|
||||||
<span class="font-bold dark:text-warning">ACTION REQUIRED: Copy the 'Public Key' to your
|
<span class="font-bold text-warning">ACTION REQUIRED: Copy the 'Public Key' to your server's
|
||||||
server's
|
|
||||||
~/.ssh/authorized_keys
|
~/.ssh/authorized_keys
|
||||||
file.</span>
|
file.</span>
|
||||||
@endif
|
@endif
|
||||||
@ -263,62 +190,50 @@ class="bg-red-200 dark:bg-red-900 px-1 rounded">~/.ssh/authorized_keys</code>
|
|||||||
</x-slot:actions>
|
</x-slot:actions>
|
||||||
<x-slot:explanation>
|
<x-slot:explanation>
|
||||||
<p>Private Keys are used to connect to a remote server through a secure shell, called SSH.</p>
|
<p>Private Keys are used to connect to a remote server through a secure shell, called SSH.</p>
|
||||||
<p>You can use your own private key, or you can let Last Hour Cloud to create one for you.</p>
|
<p>You can use your own private key, or you can let Last Hour Cloud create one for you.</p>
|
||||||
<p>In both ways, you need to add the public version of your private key to the remote server's
|
<p>In both ways, you need to add the public version of your private key to the remote server's
|
||||||
<code>~/.ssh/authorized_keys</code> file.
|
<code>~/.ssh/authorized_keys</code> file.
|
||||||
</p>
|
</p>
|
||||||
</x-slot:explanation>
|
</x-slot:explanation>
|
||||||
</x-boarding-step>
|
</x-boarding-step>
|
||||||
@elseif ($currentState === 'create-server')
|
@endif
|
||||||
<x-boarding-step title="Create Server">
|
</div>
|
||||||
|
<div>
|
||||||
|
@if ($currentState === 'create-server')
|
||||||
|
<x-boarding-step title="Create a Server">
|
||||||
<x-slot:question>
|
<x-slot:question>
|
||||||
Please let me know your server details.
|
Please let us know your server details.
|
||||||
</x-slot:question>
|
</x-slot:question>
|
||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
<form wire:submit='saveServer' class="flex flex-col w-full gap-4 lg:pr-10">
|
<form wire:submit='saveServer' class="flex flex-col w-full gap-4 pr-10">
|
||||||
<div class="flex flex-col gap-2 lg:flex-row">
|
<div class="flex gap-2">
|
||||||
<x-forms.input required placeholder="Choose a name for your Server. Could be anything."
|
<x-forms.input required placeholder="Choose a name for your Server. It could be anything." label="Name" id="remoteServerName" />
|
||||||
label="Name" id="remoteServerName" wire:model="remoteServerName" />
|
<x-forms.input placeholder="Description, so others will know more about it." label="Description" id="remoteServerDescription" />
|
||||||
<x-forms.input placeholder="Description, so others will know more about this."
|
|
||||||
label="Description" id="remoteServerDescription"
|
|
||||||
wire:model="remoteServerDescription" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2 lg:flex-row ">
|
<div class="flex gap-2">
|
||||||
<x-forms.input required placeholder="127.0.0.1" label="IP Address" id="remoteServerHost"
|
<x-forms.input required placeholder="127.0.0.1" label="IP Address" id="remoteServerHost" />
|
||||||
wire:model="remoteServerHost" />
|
<x-forms.input required placeholder="Port number of your server. Default is 22." label="Port" id="remoteServerPort" />
|
||||||
|
<x-forms.input required readonly placeholder="Username to connect to your server. Default is root." label="Username" id="remoteServerUser" />
|
||||||
</div>
|
</div>
|
||||||
<div x-data="{ showAdvanced: false }" class="flex flex-col gap-2">
|
<div class="w-64">
|
||||||
<button @click="showAdvanced = !showAdvanced" type="button"
|
<x-forms.checkbox helper="If you are using Cloudflare Tunnels, enable this. It will proxy all ssh requests to your server through Cloudflare.<br><span class='text-warning'>Last Hour Cloud does not install/setup Cloudflare (cloudflared) on your server.</span>" id="isCloudflareTunnel" label="Cloudflare Tunnel" />
|
||||||
class="text-left text-sm text-gray-600 dark:text-gray-300 hover:underline">
|
|
||||||
Advanced Settings
|
|
||||||
</button>
|
|
||||||
<div x-show="showAdvanced" class="flex flex-col gap-2 lg:flex-row">
|
|
||||||
<x-forms.input placeholder="Port number of your server. Default is 22." label="Port"
|
|
||||||
id="remoteServerPort" wire:model="remoteServerPort" />
|
|
||||||
<div class="w-full">
|
|
||||||
<x-forms.input placeholder="Default is root." label="User"
|
|
||||||
id="remoteServerUser" wire:model="remoteServerUser" />
|
|
||||||
<div class="text-xs text-gray-600 dark:text-gray-300">Non-root user is
|
|
||||||
experimental: <a class="font-bold underline" target="_blank"
|
|
||||||
href="https://coolify.io/docs/knowledge-base/server/non-root-user">upstream docs</a>.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="lg:w-64">
|
|
||||||
<x-forms.checkbox
|
|
||||||
helper="If you are using Cloudflare Tunnels, enable this. It will proxy all ssh requests to your server through Cloudflare.<br><span class='dark:text-warning'>Coolify does not install/setup Cloudflare (cloudflared) on your server.</span>"
|
|
||||||
id="isCloudflareTunnel" label="Cloudflare Tunnel" wire:model="isCloudflareTunnel" />
|
|
||||||
</div>
|
</div>
|
||||||
<x-forms.button type="submit">Continue</x-forms.button>
|
<x-forms.button type="submit">Continue</x-forms.button>
|
||||||
</form>
|
</form>
|
||||||
</x-slot:actions>
|
</x-slot:actions>
|
||||||
|
<x-slot:explanation>
|
||||||
|
<p>Username should be <x-highlighted text="root" /> for now. We are working on using
|
||||||
|
non-root users.</p>
|
||||||
|
</x-slot:explanation>
|
||||||
</x-boarding-step>
|
</x-boarding-step>
|
||||||
@elseif ($currentState === 'validate-server')
|
@endif
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if ($currentState === 'validate-server')
|
||||||
<x-boarding-step title="Validate & Configure Server">
|
<x-boarding-step title="Validate & Configure Server">
|
||||||
<x-slot:question>
|
<x-slot:question>
|
||||||
I need to validate your server (connection, Docker Engine, etc) and configure if something is
|
we need to validate your server (connection, Docker Engine, etc) and configure to see if something is
|
||||||
missing for me. Are you okay with this?
|
missing. Are you okay with this?
|
||||||
</x-slot:question>
|
</x-slot:question>
|
||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
<x-slide-over closeWithX fullScreen>
|
<x-slide-over closeWithX fullScreen>
|
||||||
@ -326,39 +241,265 @@ class="text-left text-sm text-gray-600 dark:text-gray-300 hover:underline">
|
|||||||
<x-slot:content>
|
<x-slot:content>
|
||||||
<livewire:server.validate-and-install :server="$this->createdServer" />
|
<livewire:server.validate-and-install :server="$this->createdServer" />
|
||||||
</x-slot:content>
|
</x-slot:content>
|
||||||
<x-forms.button @click="slideOverOpen=true" class="w-full font-bold box-boarding lg:w-96"
|
<x-forms.button @click="slideOverOpen=true" class="font-bold box w-96" wire:click.prevent='installServer' isHighlighted>
|
||||||
wire:click.prevent='installServer' isHighlighted>
|
Send it!
|
||||||
Let's do it!
|
</x-forms.button>
|
||||||
|
</x-slot:actions>
|
||||||
|
</x-boarding-step>
|
||||||
|
@endif
|
||||||
|
@if ($currentState === 'select-server-type')
|
||||||
|
<x-boarding-step title="Server">
|
||||||
|
<x-slot:question>
|
||||||
|
Do you want to deploy your resources on your <x-highlighted text="Localhost" />
|
||||||
|
or on a <x-highlighted text="Remote Server" />?
|
||||||
|
</x-slot:question>
|
||||||
|
<x-slot:actions>
|
||||||
|
<x-forms.button class="justify-center w-64 box" wire:target="setServerType('localhost')" wire:click="setServerType('localhost')">Localhost
|
||||||
|
</x-forms.button>
|
||||||
|
|
||||||
|
<x-forms.button class="justify-center w-64 box " wire:target="setServerType('remote')" wire:click="setServerType('remote')">Remote Server
|
||||||
|
</x-forms.button>
|
||||||
|
@if (!$serverReachable)
|
||||||
|
Localhost is not reachable with the following public key.
|
||||||
|
<br /> <br />
|
||||||
|
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for user
|
||||||
|
'root' or skip the guided tour and add a new private key manually to Last Hour Cloud and to the
|
||||||
|
server.
|
||||||
|
<br />
|
||||||
|
Check the upstream <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh">documentation</a> for further help.
|
||||||
|
<x-forms.input readonly id="serverPublicKey"></x-forms.input>
|
||||||
|
<x-forms.button class="w-64 box" wire:target="setServerType('localhost')" wire:click="setServerType('localhost')">Check again
|
||||||
|
</x-forms.button>
|
||||||
|
@endif
|
||||||
|
</x-slot:actions>
|
||||||
|
<x-slot:explanation>
|
||||||
|
<p>Servers are the main building blocks, as they will host your applications, databases,
|
||||||
|
services, called resources. Any CPU intensive process will use the server's CPU where you
|
||||||
|
are deploying your resources.</p>
|
||||||
|
<p>Localhost is the server where Last Hour Cloud is running on. It is not recommended to use one server
|
||||||
|
for everything.</p>
|
||||||
|
<p>Remote Server is a server reachable through SSH. It can be hosted at home, or from any cloud
|
||||||
|
provider.</p>
|
||||||
|
</x-slot:explanation>
|
||||||
|
</x-boarding-step>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if ($currentState === 'private-key')
|
||||||
|
<x-boarding-step title="SSH Key">
|
||||||
|
<x-slot:question>
|
||||||
|
Do you have your own SSH Private Key?
|
||||||
|
</x-slot:question>
|
||||||
|
<x-slot:actions>
|
||||||
|
<x-forms.button class="justify-center w-64 box" wire:target="setPrivateKey('own')" wire:click="setPrivateKey('own')">Yes
|
||||||
|
</x-forms.button>
|
||||||
|
<x-forms.button class="justify-center w-64 box" wire:target="setPrivateKey('create')" wire:click="setPrivateKey('create')">No (create one for me)
|
||||||
|
</x-forms.button>
|
||||||
|
@if (count($privateKeys) > 0)
|
||||||
|
<form wire:submit='selectExistingPrivateKey' class="flex flex-col w-full gap-4 pr-10">
|
||||||
|
<x-forms.select label="Existing SSH Keys" id='selectedExistingPrivateKey'>
|
||||||
|
@foreach ($privateKeys as $privateKey)
|
||||||
|
<option wire:key="{{ $loop->index }}" value="{{ $privateKey->id }}">
|
||||||
|
{{ $privateKey->name }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</x-forms.select>
|
||||||
|
<x-forms.button type="submit">Use this SSH Key</x-forms.button>
|
||||||
|
</form>
|
||||||
|
@endif
|
||||||
|
</x-slot:actions>
|
||||||
|
<x-slot:explanation>
|
||||||
|
<p>SSH Keys are used to connect to a remote server through a secure shell, called SSH.</p>
|
||||||
|
<p>You can use your own ssh private key, or you can allow Last Hour Cloud to create one for you.</p>
|
||||||
|
<p>In both ways, you need to add the public version of your ssh private key to the remote
|
||||||
|
server's
|
||||||
|
<code class="text-warning">~/.ssh/authorized_keys</code> file.
|
||||||
|
</p>
|
||||||
|
</x-slot:explanation>
|
||||||
|
</x-boarding-step>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if ($currentState === 'select-existing-server')
|
||||||
|
<x-boarding-step title="Select a server">
|
||||||
|
<x-slot:question>
|
||||||
|
There are already servers available for your Team. Do you want to use one of them?
|
||||||
|
</x-slot:question>
|
||||||
|
<x-slot:actions>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div>
|
||||||
|
<x-forms.button class="justify-center w-64 box" wire:click="createNewServer">No (create one
|
||||||
|
for
|
||||||
|
me)
|
||||||
|
</x-forms.button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<form wire:submit='selectExistingServer' class="flex flex-col w-full gap-4 lg:w-96">
|
||||||
|
<x-forms.select label="Existing servers" class="w-96" id='selectedExistingServer'>
|
||||||
|
@foreach ($servers as $server)
|
||||||
|
<option wire:key="{{ $loop->index }}" value="{{ $server->id }}">
|
||||||
|
{{ $server->name }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</x-forms.select>
|
||||||
|
<x-forms.button type="submit">Use this Server</x-forms.button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (!$serverReachable)
|
||||||
|
This server is not reachable with the following public key.
|
||||||
|
<br /> <br />
|
||||||
|
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for user
|
||||||
|
'root' or skip the boarding process and add a new private key manually to Last Hour Cloud and to the
|
||||||
|
server.
|
||||||
|
<x-forms.input readonly id="serverPublicKey"></x-forms.input>
|
||||||
|
<x-forms.button class="w-64 box" wire:target="validateServer" wire:click="validateServer">Check
|
||||||
|
again
|
||||||
|
</x-forms.button>
|
||||||
|
@endif
|
||||||
|
</x-slot:actions>
|
||||||
|
<x-slot:explanation>
|
||||||
|
<p>Private Keys are used to connect to a remote server through a secure shell, called SSH.</p>
|
||||||
|
<p>You can use your own private key, or you can let Last Hour Cloud create one for you.</p>
|
||||||
|
<p>In both ways, you need to add the public version of your private key to the remote server's
|
||||||
|
<code>~/.ssh/authorized_keys</code> file.
|
||||||
|
</p>
|
||||||
|
</x-slot:explanation>
|
||||||
|
</x-boarding-step>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if ($currentState === 'create-private-key')
|
||||||
|
<x-boarding-step title="Create Private Key">
|
||||||
|
<x-slot:question>
|
||||||
|
Please let me know your key details.
|
||||||
|
</x-slot:question>
|
||||||
|
<x-slot:actions>
|
||||||
|
<form wire:submit='savePrivateKey' class="flex flex-col w-full gap-4 pr-10">
|
||||||
|
<x-forms.input required placeholder="Choose a name for your Private Key. Could be anything." label="Name" id="privateKeyName" />
|
||||||
|
<x-forms.input placeholder="Description, so others will know more about this." label="Description" id="privateKeyDescription" />
|
||||||
|
<x-forms.textarea required placeholder="-----BEGIN OPENSSH PRIVATE KEY-----" label="Private Key" id="privateKey" />
|
||||||
|
@if ($privateKeyType === 'create')
|
||||||
|
<x-forms.textarea rows="7" readonly label="Public Key" id="publicKey" />
|
||||||
|
<span class="font-bold text-warning">ACTION REQUIRED: Copy the 'Public Key' to your server's
|
||||||
|
~/.ssh/authorized_keys
|
||||||
|
file.</span>
|
||||||
|
@endif
|
||||||
|
<x-forms.button type="submit">Save</x-forms.button>
|
||||||
|
</form>
|
||||||
|
</x-slot:actions>
|
||||||
|
<x-slot:explanation>
|
||||||
|
<p>Private Keys are used to connect to a remote server through a secure shell, called SSH.</p>
|
||||||
|
<p>You can use your own private key, or you can let Last Hour Cloud create one for you.</p>
|
||||||
|
<p>In both ways, you need to add the public version of your private key to the remote server's
|
||||||
|
<code>~/.ssh/authorized_keys</code> file.
|
||||||
|
</p>
|
||||||
|
</x-slot:explanation>
|
||||||
|
</x-boarding-step>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if ($currentState === 'create-server')
|
||||||
|
<x-boarding-step title="Create a Server">
|
||||||
|
<x-slot:question>
|
||||||
|
Please let us know your server details.
|
||||||
|
</x-slot:question>
|
||||||
|
<x-slot:actions>
|
||||||
|
<form wire:submit='saveServer' class="flex flex-col w-full gap-4 pr-10">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<x-forms.input required placeholder="Choose a name for your Server. It could be anything." label="Name" id="remoteServerName" />
|
||||||
|
<x-forms.input placeholder="Description, so others will know more about it." label="Description" id="remoteServerDescription" />
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<x-forms.input required placeholder="127.0.0.1" label="IP Address" id="remoteServerHost" />
|
||||||
|
<x-forms.input required placeholder="Port number of your server. Default is 22." label="Port" id="remoteServerPort" />
|
||||||
|
<x-forms.input required readonly placeholder="Username to connect to your server. Default is root." label="Username" id="remoteServerUser" />
|
||||||
|
</div>
|
||||||
|
<div class="w-64">
|
||||||
|
<x-forms.checkbox helper="If you are using Cloudflare Tunnels, enable this. It will proxy all ssh requests to your server through Cloudflare.<br><span class='text-warning'>Last Hour Cloud does not install/setup Cloudflare (cloudflared) on your server.</span>" id="isCloudflareTunnel" label="Cloudflare Tunnel" />
|
||||||
|
</div>
|
||||||
|
<x-forms.button type="submit">Continue</x-forms.button>
|
||||||
|
</form>
|
||||||
|
</x-slot:actions>
|
||||||
|
<x-slot:explanation>
|
||||||
|
<p>Username should be <x-highlighted text="root" /> for now. We are working on using
|
||||||
|
non-root users.</p>
|
||||||
|
</x-slot:explanation>
|
||||||
|
</x-boarding-step>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if ($currentState === 'validate-server')
|
||||||
|
<x-boarding-step title="Validate & Configure Server">
|
||||||
|
<x-slot:question>
|
||||||
|
we need to validate your server (connection, Docker Engine, etc) and configure to see if something is
|
||||||
|
missing. Are you okay with this?
|
||||||
|
</x-slot:question>
|
||||||
|
<x-slot:actions>
|
||||||
|
<x-slide-over closeWithX fullScreen>
|
||||||
|
<x-slot:title>Validate & configure</x-slot:title>
|
||||||
|
<x-slot:content>
|
||||||
|
<livewire:server.validate-and-install :server="$this->createdServer" />
|
||||||
|
</x-slot:content>
|
||||||
|
<x-forms.button @click="slideOverOpen=true" class="font-bold box w-96" wire:click.prevent='installServer' isHighlighted>
|
||||||
|
Send it!
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
</x-slide-over>
|
</x-slide-over>
|
||||||
</x-slot:actions>
|
</x-slot:actions>
|
||||||
<x-slot:explanation>
|
<x-slot:explanation>
|
||||||
<p>This will install the latest Docker Engine on your server, configure a few things to be able
|
<p>This will install the latest Docker Engine on your server, configure a few things to be able
|
||||||
to run optimal.<br><br>Minimum Docker Engine version is: 22<br><br>To manually install
|
to run optimal.<br><br>Minimum Docker Engine version is: 22<br><br>To manually install Docker
|
||||||
Docker
|
Engine, check <a target="_blank" class="underline text-warning" href="https://docs.docker.com/engine/install/#server">this
|
||||||
Engine, check <a target="_blank" class="underline dark:text-warning"
|
|
||||||
href="https://docs.docker.com/engine/install/#server">this
|
|
||||||
documentation</a>.</p>
|
documentation</a>.</p>
|
||||||
</x-slot:explanation>
|
</x-slot:explanation>
|
||||||
</x-boarding-step>
|
</x-boarding-step>
|
||||||
@elseif ($currentState === 'create-project')
|
@endif
|
||||||
|
</div>
|
||||||
|
{{-- <div>
|
||||||
|
@if ($currentState === 'select-proxy')
|
||||||
|
<x-boarding-step title="Select a Proxy">
|
||||||
|
<x-slot:question>
|
||||||
|
If you would like to attach any kind of domain to your resources, you need a proxy.
|
||||||
|
</x-slot:question>
|
||||||
|
<x-slot:actions>
|
||||||
|
<x-forms.button wire:click="selectProxy" class="w-64 box">
|
||||||
|
Decide later
|
||||||
|
</x-forms.button>
|
||||||
|
<x-forms.button class="w-32 box" wire:click="selectProxy('{{ ProxyTypes::TRAEFIK_V2 }}')">
|
||||||
|
Traefik
|
||||||
|
v2
|
||||||
|
</x-forms.button>
|
||||||
|
<x-forms.button disabled class="w-32 box">
|
||||||
|
Nginx
|
||||||
|
</x-forms.button>
|
||||||
|
<x-forms.button disabled class="w-32 box">
|
||||||
|
Caddy
|
||||||
|
</x-forms.button>
|
||||||
|
</x-slot:actions>
|
||||||
|
<x-slot:explanation>
|
||||||
|
<p>This will install the latest Docker Engine on your server and configure a few items to be able
|
||||||
|
to run optimally.</p>
|
||||||
|
</x-slot:explanation>
|
||||||
|
</x-boarding-step>
|
||||||
|
@endif
|
||||||
|
</div> --}}
|
||||||
|
<div>
|
||||||
|
@if ($currentState === 'create-project')
|
||||||
<x-boarding-step title="Project">
|
<x-boarding-step title="Project">
|
||||||
<x-slot:question>
|
<x-slot:question>
|
||||||
@if (count($projects) > 0)
|
@if (count($projects) > 0)
|
||||||
You already have some projects. Do you want to use one of them or should I create a new one
|
You already have some projects. Do you want to use one of them or should we create a new one for
|
||||||
for
|
|
||||||
you?
|
you?
|
||||||
@else
|
@else
|
||||||
Let's create an initial project for you. You can change all the details later on.
|
We will create an initial project for you. You can change it later on.
|
||||||
@endif
|
@endif
|
||||||
</x-slot:question>
|
</x-slot:question>
|
||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
<x-forms.button class="justify-center w-64 box-boarding" wire:click="createNewProject">Create new
|
<x-forms.button class="justify-center w-64 box" wire:click="createNewProject">Create New Project</x-forms.button>
|
||||||
project!</x-forms.button>
|
|
||||||
<div>
|
<div>
|
||||||
@if (count($projects) > 0)
|
@if (count($projects) > 0)
|
||||||
<form wire:submit='selectExistingProject' class="flex flex-col w-full gap-4 lg:w-96">
|
<form wire:submit='selectExistingProject' class="flex flex-col w-full gap-4 lg:w-96">
|
||||||
<x-forms.select label="Existing projects" class="w-96" id='selectedProject'>
|
<x-forms.select label="Existing projects" class="w-96" id='selectedExistingProject'>
|
||||||
@foreach ($projects as $project)
|
@foreach ($projects as $project)
|
||||||
<option wire:key="{{ $loop->index }}" value="{{ $project->id }}">
|
<option wire:key="{{ $loop->index }}" value="{{ $project->id }}">
|
||||||
{{ $project->name }}
|
{{ $project->name }}
|
||||||
@ -371,42 +512,32 @@ class="text-left text-sm text-gray-600 dark:text-gray-300 hover:underline">
|
|||||||
</div>
|
</div>
|
||||||
</x-slot:actions>
|
</x-slot:actions>
|
||||||
<x-slot:explanation>
|
<x-slot:explanation>
|
||||||
<p>Projects contain several resources combined into one virtual group. There are no
|
<p>Projects put together several resources into one virtual group. There are no
|
||||||
limitations on the number of projects you can add.</p>
|
limitations on the number of projects you can have here.</p>
|
||||||
<p>Each project should have at least one environment, this allows you to create a production &
|
<p>Each project should have at least one environment. This helps you to create a production &
|
||||||
staging version of the same application, but grouped separately.</p>
|
staging version of the same application, but grouped separately.</p>
|
||||||
</x-slot:explanation>
|
</x-slot:explanation>
|
||||||
</x-boarding-step>
|
</x-boarding-step>
|
||||||
@elseif ($currentState === 'create-resource')
|
|
||||||
<x-boarding-step title="Resources">
|
|
||||||
<x-slot:question>
|
|
||||||
Let's go to the new resource page, where you can create your first resource.
|
|
||||||
</x-slot:question>
|
|
||||||
<x-slot:actions>
|
|
||||||
<div class="items-center justify-center w-64 box-boarding" wire:click="showNewResource">Let's do
|
|
||||||
it!</div>
|
|
||||||
</x-slot:actions>
|
|
||||||
<x-slot:explanation>
|
|
||||||
<p>A resource could be an application, a database or a service (like WordPress).</p>
|
|
||||||
</x-slot:explanation>
|
|
||||||
</x-boarding-step>
|
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col justify-center gap-4 pt-4 lg:gap-2 lg:flex">
|
<div>
|
||||||
<div class="flex justify-center w-full gap-2">
|
@if ($currentState === 'create-resource')
|
||||||
<div class="cursor-pointer hover:underline dark:hover:text-white" wire:click='skipBoarding'>Skip
|
<x-boarding-step title="Resources">
|
||||||
onboarding</div>
|
<x-slot:question>
|
||||||
<div class="cursor-pointer hover:underline dark:hover:text-white" wire:click='restartBoarding'>Restart
|
Next we will redirect you to the resource page, where you can create your first resource.
|
||||||
onboarding</div>
|
</x-slot:question>
|
||||||
</div>
|
<x-slot:actions>
|
||||||
<x-modal-input title="How can we help?">
|
<div class="items-center justify-center w-64 box" wire:click="showNewResource">Let's do
|
||||||
<x-slot:content>
|
it!</div>
|
||||||
<div class="w-full text-center cursor-pointer hover:underline dark:hover:text-white"
|
</x-slot:actions>
|
||||||
title="Send us feedback or get help!">
|
<x-slot:explanation>
|
||||||
Feedback
|
<p>A resource is an application, a database or a service (like WordPress).</p>
|
||||||
</div>
|
</x-slot:explanation>
|
||||||
</x-slot:content>
|
</x-boarding-step>
|
||||||
<livewire:help />
|
@endif
|
||||||
</x-modal-input>
|
|
||||||
</div>
|
</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>
|
@ -2,7 +2,7 @@ set -e
|
|||||||
export IMAGE=$1
|
export IMAGE=$1
|
||||||
docker system prune -af
|
docker system prune -af
|
||||||
docker compose pull
|
docker compose pull
|
||||||
read -p "Press Enter to update Last Hour Cloud to $IMAGE..." </dev/tty
|
read -p "Press Enter to update Coolify to $IMAGE..." </dev/tty
|
||||||
docker exec coolify sh -c "php artisan tinker --execute='isAnyDeploymentInprogress()'"
|
docker exec coolify sh -c "php artisan tinker --execute='isAnyDeploymentInprogress()'"
|
||||||
docker compose up --remove-orphans --force-recreate -d --wait
|
docker compose up --remove-orphans --force-recreate -d --wait
|
||||||
echo $IMAGE > last_version
|
echo $IMAGE > last_version
|
||||||
|
283
scripts/dev_install.sh
Normal file
283
scripts/dev_install.sh
Normal 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
41
scripts/dev_upgrade.sh
Normal 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
|
@ -1,36 +1,15 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
## Do not modify this file. You will lose the ability to install and auto-update!
|
|
||||||
|
|
||||||
set -e # Exit immediately if a command exits with a non-zero status
|
set -e # Exit immediately if a command exits with a non-zero status
|
||||||
## $1 could be empty, so we need to disable this check
|
## $1 could be empty, so we need to disable this check
|
||||||
#set -u # Treat unset variables as an error and exit
|
#set -u # Treat unset variables as an error and exit
|
||||||
set -o pipefail # Cause a pipeline to return the status of the last command that exited with a non-zero status
|
set -o pipefail # Cause a pipeline to return the status of the last command that exited with a non-zero status
|
||||||
CDN="https://cdn.lasthourhosting.org/lasthourcloudprod"
|
|
||||||
DATE=$(date +"%Y%m%d-%H%M%S")
|
|
||||||
|
|
||||||
VERSION="1.5"
|
VERSION="1.3.3"
|
||||||
DOCKER_VERSION="26.0"
|
DOCKER_VERSION="26.0"
|
||||||
|
|
||||||
mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,webhooks-during-maintenance,metrics,logs}
|
CDN="https://cdn.lasthourhosting.org/lasthourcloud"
|
||||||
mkdir -p /data/coolify/ssh/{keys,mux}
|
|
||||||
mkdir -p /data/coolify/proxy/dynamic
|
|
||||||
|
|
||||||
chown -R 9999:root /data/coolify
|
|
||||||
chmod -R 700 /data/coolify
|
|
||||||
|
|
||||||
INSTALLATION_LOG_WITH_DATE="/data/coolify/source/installation-${DATE}.log"
|
|
||||||
|
|
||||||
exec > >(tee -a $INSTALLATION_LOG_WITH_DATE) 2>&1
|
|
||||||
|
|
||||||
getAJoke() {
|
|
||||||
JOKES=$(curl -s --max-time 2 https://v2.jokeapi.dev/joke/Programming?format=txt&type=single&amount=1 || true)
|
|
||||||
if [ "$JOKES" != "" ]; then
|
|
||||||
echo -e " - Until then, here's a joke for you:\n"
|
|
||||||
echo -e "$JOKES\n"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
OS_TYPE=$(grep -w "ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"')
|
OS_TYPE=$(grep -w "ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"')
|
||||||
ENV_FILE="/data/coolify/source/.env"
|
|
||||||
|
|
||||||
# Check if the OS is manjaro, if so, change it to arch
|
# Check if the OS is manjaro, if so, change it to arch
|
||||||
if [ "$OS_TYPE" = "manjaro" ] || [ "$OS_TYPE" = "manjaro-arm" ]; then
|
if [ "$OS_TYPE" = "manjaro" ] || [ "$OS_TYPE" = "manjaro-arm" ]; then
|
||||||
@ -64,17 +43,7 @@ if [ "$OS_TYPE" = 'amzn' ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
LATEST_VERSION=$(curl --silent $CDN/versions.json | grep -i version | xargs | awk '{print $2}' | tr -d ',')
|
LATEST_VERSION=$(curl --silent $CDN/versions.json | grep -i version | xargs | awk '{print $2}' | tr -d ',')
|
||||||
LATEST_HELPER_VERSION=$(curl --silent $CDN/versions.json | grep -i version | xargs | awk '{print $6}' | tr -d ',')
|
DATE=$(date +"%Y%m%d-%H%M%S")
|
||||||
LATEST_REALTIME_VERSION=$(curl --silent $CDN/versions.json | grep -i version | xargs | awk '{print $8}' | tr -d ',')
|
|
||||||
|
|
||||||
if [ -z "$LATEST_HELPER_VERSION" ]; then
|
|
||||||
LATEST_HELPER_VERSION=latest
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$LATEST_REALTIME_VERSION" ]; then
|
|
||||||
LATEST_REALTIME_VERSION=latest
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
if [ $EUID != 0 ]; then
|
if [ $EUID != 0 ]; then
|
||||||
echo "Please run as root"
|
echo "Please run as root"
|
||||||
@ -82,9 +51,9 @@ if [ $EUID != 0 ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
case "$OS_TYPE" in
|
case "$OS_TYPE" in
|
||||||
arch | ubuntu | debian | raspbian | centos | fedora | rhel | ol | rocky | sles | opensuse-leap | opensuse-tumbleweed | almalinux | amzn | alpine) ;;
|
arch | ubuntu | debian | raspbian | centos | fedora | rhel | ol | rocky | sles | opensuse-leap | opensuse-tumbleweed | almalinux | amzn) ;;
|
||||||
*)
|
*)
|
||||||
echo "This script only supports Debian, Redhat, Arch Linux, Alpine Linux, or SLES based operating systems for now."
|
echo "This script only supports Debian, Redhat, Arch Linux, or SLES based operating systems for now."
|
||||||
exit
|
exit
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@ -96,41 +65,33 @@ if [ "$1" != "" ]; then
|
|||||||
LATEST_VERSION="${LATEST_VERSION#v}"
|
LATEST_VERSION="${LATEST_VERSION#v}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "\033[0;35m"
|
echo -e "-------------"
|
||||||
cat << "EOF"
|
echo -e "Welcome to Last Hour Cloud v4 installer!"
|
||||||
|
echo -e "This script will install everything for you."
|
||||||
|
<<<<<<< HEAD
|
||||||
|
<<<<<<< HEAD
|
||||||
|
echo -e "(Source code: https://github.com/coollabsio/coolify/blob/main/scripts/install.sh )\n"
|
||||||
|
=======
|
||||||
|
echo -e "(Source code: https://https://githaven.org/Shiloh/lasthourcloud/blob/main/scripts/install.sh)\n"
|
||||||
|
>>>>>>> 35700ec24 (main: begin major rewrite for lasthour)
|
||||||
|
=======
|
||||||
|
echo -e "(Source code: https://githaven.org/Shiloh/lasthourcloud/src/branch/main/scripts/install.sh)\n"
|
||||||
|
>>>>>>> 4e89beaf1 (main: fix CDN url)
|
||||||
|
echo -e "-------------"
|
||||||
|
|
||||||
██ ███████ ███████ ██ ██ ███████ ██ ███████ ██ ██ ██ ███ ██ ██████
|
echo "OS: $OS_TYPE $OS_VERSION"
|
||||||
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██
|
echo "Last Hour Cloud version: $LATEST_VERSION"
|
||||||
██ █████ ███████ ██ ██ ███████ ██ ███████ █████ ██ ██ ██ ██ ██ ███
|
|
||||||
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
echo -e "-------------"
|
||||||
█████ ███████ ███████ ██████ ███████ ██ ███████ ██ ██ ██ ██ ████ ██████
|
echo "Installing required packages..."
|
||||||
|
|
||||||
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). "
|
|
||||||
|
|
||||||
case "$OS_TYPE" in
|
case "$OS_TYPE" in
|
||||||
arch)
|
arch)
|
||||||
pacman -Sy --noconfirm --needed curl wget git jq >/dev/null || true
|
pacman -Sy --noconfirm --needed curl wget git jq >/dev/null || true
|
||||||
;;
|
;;
|
||||||
alpine)
|
|
||||||
sed -i '/^#.*\/community/s/^#//' /etc/apk/repositories
|
|
||||||
apk update >/dev/null
|
|
||||||
apk add curl wget git jq >/dev/null
|
|
||||||
;;
|
|
||||||
ubuntu | debian | raspbian)
|
ubuntu | debian | raspbian)
|
||||||
apt-get update -y >/dev/null
|
apt update -y >/dev/null
|
||||||
apt-get install -y curl wget git jq >/dev/null
|
apt install -y curl wget git jq >/dev/null
|
||||||
;;
|
;;
|
||||||
centos | fedora | rhel | ol | rocky | almalinux | amzn)
|
centos | fedora | rhel | ol | rocky | almalinux | amzn)
|
||||||
if [ "$OS_TYPE" = "amzn" ]; then
|
if [ "$OS_TYPE" = "amzn" ]; then
|
||||||
@ -139,10 +100,7 @@ centos | fedora | rhel | ol | rocky | almalinux | amzn)
|
|||||||
if ! command -v dnf >/dev/null; then
|
if ! command -v dnf >/dev/null; then
|
||||||
yum install -y dnf >/dev/null
|
yum install -y dnf >/dev/null
|
||||||
fi
|
fi
|
||||||
if ! command -v curl >/dev/null; then
|
dnf install -y curl wget git jq >/dev/null
|
||||||
dnf install -y curl >/dev/null
|
|
||||||
fi
|
|
||||||
dnf install -y wget git jq >/dev/null
|
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
sles | opensuse-leap | opensuse-tumbleweed)
|
sles | opensuse-leap | opensuse-tumbleweed)
|
||||||
@ -155,26 +113,24 @@ sles | opensuse-leap | opensuse-tumbleweed)
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
echo -e "2. Check OpenSSH server configuration. "
|
|
||||||
|
|
||||||
# Detect OpenSSH server
|
# Detect OpenSSH server
|
||||||
SSH_DETECTED=false
|
SSH_DETECTED=false
|
||||||
if [ -x "$(command -v systemctl)" ]; then
|
if [ -x "$(command -v systemctl)" ]; then
|
||||||
if systemctl status sshd >/dev/null 2>&1; then
|
if systemctl status sshd >/dev/null 2>&1; then
|
||||||
echo " - OpenSSH server is installed."
|
echo "OpenSSH server is installed."
|
||||||
SSH_DETECTED=true
|
SSH_DETECTED=true
|
||||||
elif systemctl status ssh >/dev/null 2>&1; then
|
fi
|
||||||
echo " - OpenSSH server is installed."
|
if systemctl status ssh >/dev/null 2>&1; then
|
||||||
|
echo "OpenSSH server is installed."
|
||||||
SSH_DETECTED=true
|
SSH_DETECTED=true
|
||||||
fi
|
fi
|
||||||
elif [ -x "$(command -v service)" ]; then
|
elif [ -x "$(command -v service)" ]; then
|
||||||
if service sshd status >/dev/null 2>&1; then
|
if service sshd status >/dev/null 2>&1; then
|
||||||
echo " - OpenSSH server is installed."
|
echo "OpenSSH server is installed."
|
||||||
SSH_DETECTED=true
|
SSH_DETECTED=true
|
||||||
elif service ssh status >/dev/null 2>&1; then
|
fi
|
||||||
echo " - OpenSSH server is installed."
|
if service ssh status >/dev/null 2>&1; then
|
||||||
|
echo "OpenSSH server is installed."
|
||||||
SSH_DETECTED=true
|
SSH_DETECTED=true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@ -186,91 +142,105 @@ if [ "$SSH_DETECTED" = "false" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Detect SSH PermitRootLogin
|
# Detect SSH PermitRootLogin
|
||||||
SSH_PERMIT_ROOT_LOGIN=$(sshd -T | grep -i "permitrootlogin" | awk '{print $2}') || true
|
SSH_PERMIT_ROOT_LOGIN=false
|
||||||
if [ "$SSH_PERMIT_ROOT_LOGIN" = "yes" ] || [ "$SSH_PERMIT_ROOT_LOGIN" = "without-password" ] || [ "$SSH_PERMIT_ROOT_LOGIN" = "prohibit-password" ]; then
|
SSH_PERMIT_ROOT_LOGIN_CONFIG=$(grep "^PermitRootLogin" /etc/ssh/sshd_config | awk '{print $2}') || SSH_PERMIT_ROOT_LOGIN_CONFIG="N/A (commented out or not found at all)"
|
||||||
echo " - SSH PermitRootLogin is enabled."
|
if [ "$SSH_PERMIT_ROOT_LOGIN_CONFIG" = "prohibit-password" ] || [ "$SSH_PERMIT_ROOT_LOGIN_CONFIG" = "yes" ] || [ "$SSH_PERMIT_ROOT_LOGIN_CONFIG" = "without-password" ]; then
|
||||||
else
|
echo "PermitRootLogin is enabled."
|
||||||
echo " - SSH PermitRootLogin is disabled."
|
SSH_PERMIT_ROOT_LOGIN=true
|
||||||
echo " If you have problems with SSH, please read this upstream documentation: https://coolify.io/docs/knowledge-base/server/openssh"
|
fi
|
||||||
|
|
||||||
|
if [ "$SSH_PERMIT_ROOT_LOGIN" != "true" ]; then
|
||||||
|
echo "###############################################################################"
|
||||||
|
echo "WARNING: PermitRootLogin is not enabled in /etc/ssh/sshd_config."
|
||||||
|
echo -e "It is set to $SSH_PERMIT_ROOT_LOGIN_CONFIG. Should be prohibit-password, yes or without-password.\n"
|
||||||
|
<<<<<<< HEAD
|
||||||
|
echo -e "Please make sure it is set, otherwise Coolify cannot connect to the host system. \n"
|
||||||
|
=======
|
||||||
|
echo -e "Please make sure it is set, otherwise Last Hour Cloud cannot connect to the host system. \n"
|
||||||
|
echo "(Currently we only support root user to login via SSH, this will be changed in the future.)"
|
||||||
|
>>>>>>> 35700ec24 (main: begin major rewrite for lasthour)
|
||||||
|
echo "###############################################################################"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Detect if docker is installed via snap
|
# Detect if docker is installed via snap
|
||||||
if [ -x "$(command -v snap)" ]; then
|
if [ -x "$(command -v snap)" ]; then
|
||||||
SNAP_DOCKER_INSTALLED=$(snap list docker >/dev/null 2>&1 && echo "true" || echo "false")
|
if snap list | grep -q docker; then
|
||||||
if [ "$SNAP_DOCKER_INSTALLED" = "true" ]; then
|
echo "Docker is installed via snap."
|
||||||
echo " - Docker is installed via snap."
|
echo "Please note that Coolify does not support Docker installed via snap."
|
||||||
echo " Please note that Last Hour Cloud does not support Docker installed via snap."
|
echo "Please remove Docker with snap (snap remove docker) and reexecute this script."
|
||||||
echo " Please remove Docker with snap (snap remove docker) and reexecute this script."
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "3. Check Docker Installation. "
|
|
||||||
if ! [ -x "$(command -v docker)" ]; then
|
if ! [ -x "$(command -v docker)" ]; then
|
||||||
echo " - Docker is not installed. Installing Docker. It may take a while."
|
# Almalinux
|
||||||
getAJoke
|
if [ "$OS_TYPE" == 'almalinux' ]; then
|
||||||
case "$OS_TYPE" in
|
dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
|
||||||
"almalinux")
|
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||||||
dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo >/dev/null 2>&1
|
if ! [ -x "$(command -v docker)" ]; then
|
||||||
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin >/dev/null 2>&1
|
echo "Docker could not be installed automatically. Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
|
||||||
if ! [ -x "$(command -v docker)" ]; then
|
exit 1
|
||||||
echo " - Docker could not be installed automatically. Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
|
fi
|
||||||
exit 1
|
systemctl start docker
|
||||||
fi
|
systemctl enable docker
|
||||||
systemctl start docker >/dev/null 2>&1
|
else
|
||||||
systemctl enable docker >/dev/null 2>&1
|
set +e
|
||||||
;;
|
if ! [ -x "$(command -v docker)" ]; then
|
||||||
"alpine")
|
echo "Docker is not installed. Installing Docker."
|
||||||
apk add docker docker-cli-compose >/dev/null 2>&1
|
# Arch Linux
|
||||||
rc-update add docker default >/dev/null 2>&1
|
if [ "$OS_TYPE" = "arch" ]; then
|
||||||
service docker start >/dev/null 2>&1
|
pacman -Sy docker docker-compose --noconfirm
|
||||||
if ! [ -x "$(command -v docker)" ]; then
|
systemctl enable docker.service
|
||||||
echo " - Failed to install Docker with apk. Try to install it manually."
|
if [ -x "$(command -v docker)" ]; then
|
||||||
echo " Please visit https://wiki.alpinelinux.org/wiki/Docker for more information."
|
echo "Docker installed successfully."
|
||||||
exit 1
|
else
|
||||||
fi
|
echo "Failed to install Docker with pacman. Try to install it manually."
|
||||||
;;
|
echo "Please visit https://wiki.archlinux.org/title/docker for more information."
|
||||||
"arch")
|
exit
|
||||||
pacman -Sy docker docker-compose --noconfirm >/dev/null 2>&1
|
fi
|
||||||
systemctl enable docker.service >/dev/null 2>&1
|
else
|
||||||
if ! [ -x "$(command -v docker)" ]; then
|
# Amazon Linux 2023
|
||||||
echo " - Failed to install Docker with pacman. Try to install it manually."
|
if [ "$OS_TYPE" = "amzn" ]; then
|
||||||
echo " Please visit https://wiki.archlinux.org/title/docker for more information."
|
dnf install docker -y
|
||||||
exit 1
|
DOCKER_CONFIG=${DOCKER_CONFIG:-/usr/local/lib/docker}
|
||||||
fi
|
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
|
||||||
"amzn")
|
chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose
|
||||||
dnf install docker -y >/dev/null 2>&1
|
systemctl start docker
|
||||||
DOCKER_CONFIG=${DOCKER_CONFIG:-/usr/local/lib/docker}
|
systemctl enable docker
|
||||||
mkdir -p $DOCKER_CONFIG/cli-plugins >/dev/null 2>&1
|
if [ -x "$(command -v docker)" ]; then
|
||||||
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
|
echo "Docker installed successfully."
|
||||||
chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose >/dev/null 2>&1
|
else
|
||||||
systemctl start docker >/dev/null 2>&1
|
echo "Failed to install Docker with pacman. Try to install it manually."
|
||||||
systemctl enable docker >/dev/null 2>&1
|
echo "Please visit https://wiki.archlinux.org/title/docker for more information."
|
||||||
if ! [ -x "$(command -v docker)" ]; then
|
exit
|
||||||
echo " - Failed to install Docker with dnf. Try to install it manually."
|
fi
|
||||||
echo " Please visit https://www.cyberciti.biz/faq/how-to-install-docker-on-amazon-linux-2/ for more information."
|
else
|
||||||
exit 1
|
# Automated Docker installation
|
||||||
fi
|
curl https://releases.rancher.com/install-docker/${DOCKER_VERSION}.sh | sh
|
||||||
;;
|
if [ -x "$(command -v docker)" ]; then
|
||||||
*)
|
echo "Docker installed successfully."
|
||||||
curl -s https://releases.rancher.com/install-docker/${DOCKER_VERSION}.sh | sh >/dev/null 2>&1
|
else
|
||||||
if ! [ -x "$(command -v docker)" ]; then
|
echo "Docker installation failed with Rancher script. Trying with official script."
|
||||||
curl -s https://get.docker.com | sh -s -- --version ${DOCKER_VERSION} >/dev/null 2>&1
|
curl https://get.docker.com | sh -s -- --version ${DOCKER_VERSION}
|
||||||
if ! [ -x "$(command -v docker)" ]; then
|
if [ -x "$(command -v docker)" ]; then
|
||||||
echo " - Docker installation failed."
|
echo "Docker installed successfully."
|
||||||
echo " Maybe your OS is not supported?"
|
else
|
||||||
echo " - Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
|
echo "Docker installation failed with official script."
|
||||||
exit 1
|
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
|
||||||
fi
|
fi
|
||||||
esac
|
fi
|
||||||
echo " - Docker installed successfully."
|
set -e
|
||||||
else
|
fi
|
||||||
echo " - Docker is installed."
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "4. Check Docker Configuration. "
|
echo -e "-------------"
|
||||||
|
echo -e "Check Docker Configuration..."
|
||||||
mkdir -p /etc/docker
|
mkdir -p /etc/docker
|
||||||
# shellcheck disable=SC2015
|
# shellcheck disable=SC2015
|
||||||
test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json /etc/docker/daemon.json.original-"$DATE" || cat >/etc/docker/daemon.json <<EOL
|
test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json /etc/docker/daemon.json.original-"$DATE" || cat >/etc/docker/daemon.json <<EOL
|
||||||
@ -298,87 +268,55 @@ if ! jq -s '.[0] * .[1]' /etc/docker/daemon.json /etc/docker/daemon.json.coolify
|
|||||||
fi
|
fi
|
||||||
mv "$TEMP_FILE" /etc/docker/daemon.json
|
mv "$TEMP_FILE" /etc/docker/daemon.json
|
||||||
|
|
||||||
restart_docker_service() {
|
|
||||||
# Check if systemctl is available
|
|
||||||
if command -v systemctl >/dev/null 2>&1; then
|
|
||||||
echo " - Using systemctl to restart Docker."
|
|
||||||
systemctl restart docker
|
|
||||||
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
echo " - Docker restarted successfully using systemctl."
|
|
||||||
else
|
|
||||||
echo " - Failed to restart Docker using systemctl."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if service command is available
|
|
||||||
elif command -v service >/dev/null 2>&1; then
|
|
||||||
echo " - Using service command to restart Docker."
|
|
||||||
service docker restart
|
|
||||||
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
echo " - Docker restarted successfully using service."
|
|
||||||
else
|
|
||||||
echo " - Failed to restart Docker using service."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If neither systemctl nor service is available
|
|
||||||
else
|
|
||||||
echo " - Neither systemctl nor service command is available on this system."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ -s /etc/docker/daemon.json.original-"$DATE" ]; then
|
if [ -s /etc/docker/daemon.json.original-"$DATE" ]; then
|
||||||
DIFF=$(diff <(jq --sort-keys . /etc/docker/daemon.json) <(jq --sort-keys . /etc/docker/daemon.json.original-"$DATE"))
|
DIFF=$(diff <(jq --sort-keys . /etc/docker/daemon.json) <(jq --sort-keys . /etc/docker/daemon.json.original-"$DATE"))
|
||||||
if [ "$DIFF" != "" ]; then
|
if [ "$DIFF" != "" ]; then
|
||||||
echo " - Docker configuration updated, restart docker daemon..."
|
echo "Docker configuration updated, restart docker daemon..."
|
||||||
restart_docker_service
|
systemctl restart docker
|
||||||
else
|
else
|
||||||
echo " - Docker configuration is up to date."
|
echo "Docker configuration is up to date."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo " - Docker configuration updated, restart docker daemon..."
|
echo "Docker configuration updated, restart docker daemon..."
|
||||||
restart_docker_service
|
systemctl restart docker
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "5. Download required files from CDN. "
|
echo -e "-------------"
|
||||||
|
|
||||||
|
mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,webhooks-during-maintenance,metrics,logs}
|
||||||
|
mkdir -p /data/coolify/ssh/{keys,mux}
|
||||||
|
mkdir -p /data/coolify/proxy/dynamic
|
||||||
|
|
||||||
|
echo "Downloading required files from CDN..."
|
||||||
curl -fsSL $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml
|
curl -fsSL $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml
|
||||||
curl -fsSL $CDN/docker-compose.prod.yml -o /data/coolify/source/docker-compose.prod.yml
|
curl -fsSL $CDN/docker-compose.prod.yml -o /data/coolify/source/docker-compose.prod.yml
|
||||||
curl -fsSL $CDN/.env.production -o /data/coolify/source/.env.production
|
curl -fsSL $CDN/.env.production -o /data/coolify/source/.env.production
|
||||||
curl -fsSL $CDN/upgrade.sh -o /data/coolify/source/upgrade.sh
|
curl -fsSL $CDN/scripts/upgrade.sh -o /data/coolify/source/upgrade.sh
|
||||||
|
|
||||||
echo -e "6. Make backup of .env to .env-$DATE"
|
# echo "Copying required files from Last Hour Cloud git repo..."
|
||||||
|
# cp /home/lasthour/lasthourcloud/docker-compose.yml /data/coolify/source/docker-compose.yml
|
||||||
|
# cp /home/lasthour/lasthourcloud/docker-compose.prod.yml /data/coolify/source/docker-compose.prod.yml
|
||||||
|
# cp /home/lasthour/lasthourcloud/.env.production /data/coolify/source/.env.production
|
||||||
|
# cp /home/lasthour/lasthourcloud/scripts/upgrade.sh /data/coolify/source/upgrade.sh
|
||||||
|
|
||||||
|
|
||||||
|
chown -R 9999:root /data/coolify
|
||||||
|
chmod -R 700 /data/coolify
|
||||||
|
|
||||||
# Copy .env.example if .env does not exist
|
# Copy .env.example if .env does not exist
|
||||||
if [ -f $ENV_FILE ]; then
|
if [ ! -f /data/coolify/source/.env ]; then
|
||||||
cp $ENV_FILE $ENV_FILE-$DATE
|
cp /data/coolify/source/.env.production /data/coolify/source/.env
|
||||||
else
|
sed -i "s|APP_ID=.*|APP_ID=$(openssl rand -hex 16)|g" /data/coolify/source/.env
|
||||||
echo " - File does not exist: $ENV_FILE"
|
sed -i "s|APP_KEY=.*|APP_KEY=base64:$(openssl rand -base64 32)|g" /data/coolify/source/.env
|
||||||
echo " - Copying .env.production to .env-$DATE"
|
sed -i "s|DB_PASSWORD=.*|DB_PASSWORD=$(openssl rand -base64 32)|g" /data/coolify/source/.env
|
||||||
cp /data/coolify/source/.env.production $ENV_FILE-$DATE
|
sed -i "s|REDIS_PASSWORD=.*|REDIS_PASSWORD=$(openssl rand -base64 32)|g" /data/coolify/source/.env
|
||||||
# Generate a secure APP_ID and APP_KEY
|
sed -i "s|PUSHER_APP_ID=.*|PUSHER_APP_ID=$(openssl rand -hex 32)|g" /data/coolify/source/.env
|
||||||
sed -i "s|^APP_ID=.*|APP_ID=$(openssl rand -hex 16)|" "$ENV_FILE-$DATE"
|
sed -i "s|PUSHER_APP_KEY=.*|PUSHER_APP_KEY=$(openssl rand -hex 32)|g" /data/coolify/source/.env
|
||||||
sed -i "s|^APP_KEY=.*|APP_KEY=base64:$(openssl rand -base64 32)|" "$ENV_FILE-$DATE"
|
sed -i "s|PUSHER_APP_SECRET=.*|PUSHER_APP_SECRET=$(openssl rand -hex 32)|g" /data/coolify/source/.env
|
||||||
|
|
||||||
# Generate a secure Postgres DB username and password
|
|
||||||
# Causes issues: database "random-user" does not exist
|
|
||||||
# sed -i "s|^DB_USERNAME=.*|DB_USERNAME=$(openssl rand -hex 16)|" "$ENV_FILE-$DATE"
|
|
||||||
sed -i "s|^DB_PASSWORD=.*|DB_PASSWORD=$(openssl rand -base64 32)|" "$ENV_FILE-$DATE"
|
|
||||||
|
|
||||||
# Generate a secure Redis password
|
|
||||||
sed -i "s|^REDIS_PASSWORD=.*|REDIS_PASSWORD=$(openssl rand -base64 32)|" "$ENV_FILE-$DATE"
|
|
||||||
|
|
||||||
# Generate secure Pusher credentials
|
|
||||||
sed -i "s|^PUSHER_APP_ID=.*|PUSHER_APP_ID=$(openssl rand -hex 32)|" "$ENV_FILE-$DATE"
|
|
||||||
sed -i "s|^PUSHER_APP_KEY=.*|PUSHER_APP_KEY=$(openssl rand -hex 32)|" "$ENV_FILE-$DATE"
|
|
||||||
sed -i "s|^PUSHER_APP_SECRET=.*|PUSHER_APP_SECRET=$(openssl rand -hex 32)|" "$ENV_FILE-$DATE"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Merge .env and .env.production. New values will be added to .env
|
# Merge .env and .env.production. New values will be added to .env
|
||||||
echo -e "7. Propagating .env with new values - if necessary."
|
sort -u -t '=' -k 1,1 /data/coolify/source/.env /data/coolify/source/.env.production | sed '/^$/d' >/data/coolify/source/.env.temp && mv /data/coolify/source/.env.temp /data/coolify/source/.env
|
||||||
awk -F '=' '!seen[$1]++' "$ENV_FILE-$DATE" /data/coolify/source/.env.production > $ENV_FILE
|
|
||||||
|
|
||||||
if [ "$AUTOUPDATE" = "false" ]; then
|
if [ "$AUTOUPDATE" = "false" ]; then
|
||||||
if ! grep -q "AUTOUPDATE=" /data/coolify/source/.env; then
|
if ! grep -q "AUTOUPDATE=" /data/coolify/source/.env; then
|
||||||
@ -387,122 +325,32 @@ if [ "$AUTOUPDATE" = "false" ]; then
|
|||||||
sed -i "s|AUTOUPDATE=.*|AUTOUPDATE=false|g" /data/coolify/source/.env
|
sed -i "s|AUTOUPDATE=.*|AUTOUPDATE=false|g" /data/coolify/source/.env
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
echo -e "8. Checking for SSH key for localhost access."
|
|
||||||
|
# Generate an ssh key (ed25519) at /data/coolify/ssh/keys/id.root@host.docker.internal
|
||||||
|
if [ ! -f /data/coolify/ssh/keys/id.root@host.docker.internal ]; then
|
||||||
|
ssh-keygen -t ed25519 -a 100 -f /data/coolify/ssh/keys/id.root@host.docker.internal -q -N "" -C root@coolify
|
||||||
|
chown 9999 /data/coolify/ssh/keys/id.root@host.docker.internal
|
||||||
|
fi
|
||||||
|
|
||||||
|
addSshKey() {
|
||||||
|
cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >>~/.ssh/authorized_keys
|
||||||
|
chmod 600 ~/.ssh/authorized_keys
|
||||||
|
}
|
||||||
|
|
||||||
if [ ! -f ~/.ssh/authorized_keys ]; then
|
if [ ! -f ~/.ssh/authorized_keys ]; then
|
||||||
mkdir -p ~/.ssh
|
mkdir -p ~/.ssh
|
||||||
chmod 700 ~/.ssh
|
chmod 700 ~/.ssh
|
||||||
touch ~/.ssh/authorized_keys
|
touch ~/.ssh/authorized_keys
|
||||||
chmod 600 ~/.ssh/authorized_keys
|
addSshKey
|
||||||
fi
|
fi
|
||||||
|
|
||||||
checkSshKeyInAuthorizedKeys() {
|
if ! grep -qw "root@coolify" ~/.ssh/authorized_keys; then
|
||||||
grep -qw "root@coolify" ~/.ssh/authorized_keys
|
addSshKey
|
||||||
return $?
|
fi
|
||||||
}
|
echo "Generated SSH access"
|
||||||
|
|
||||||
checkSshKeyInCoolifyData() {
|
echo "Begin upgrade.sh"
|
||||||
[ -s /data/coolify/ssh/keys/id.root@host.docker.internal ]
|
bash /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}"
|
||||||
return $?
|
|
||||||
}
|
|
||||||
|
|
||||||
generateAuthorizedKeys() {
|
echo -e "\nCongratulations! Your Last Hour Cloud instance is ready to use.\n"
|
||||||
sed -i "/root@coolify/d" ~/.ssh/authorized_keys
|
echo "Please visit http://$(curl -4s https://ifconfig.io):8000 to get started."
|
||||||
cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >> ~/.ssh/authorized_keys
|
|
||||||
rm -f /data/coolify/ssh/keys/id.root@host.docker.internal.pub
|
|
||||||
}
|
|
||||||
generateSshKey() {
|
|
||||||
echo " - Generating SSH key."
|
|
||||||
ssh-keygen -t ed25519 -a 100 -f /data/coolify/ssh/keys/id.root@host.docker.internal -q -N "" -C root@coolify
|
|
||||||
chown 9999 /data/coolify/ssh/keys/id.root@host.docker.internal
|
|
||||||
generateAuthorizedKeys
|
|
||||||
}
|
|
||||||
|
|
||||||
syncSshKeys() {
|
|
||||||
DB_RUNNING=$(docker inspect coolify-db --format '{{ .State.Status }}' 2>/dev/null)
|
|
||||||
# Check if SSH key exists in Coolify data but not in authorized_keys
|
|
||||||
if checkSshKeyInCoolifyData && ! checkSshKeyInAuthorizedKeys; then
|
|
||||||
# Add the existing Coolify SSH key to authorized_keys
|
|
||||||
cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >> ~/.ssh/authorized_keys
|
|
||||||
# Check if SSH key exists in authorized_keys but not in Coolify data
|
|
||||||
elif checkSshKeyInAuthorizedKeys && ! checkSshKeyInCoolifyData; then
|
|
||||||
# Ensure Coolify DB is running before proceeding
|
|
||||||
if [ "$DB_RUNNING" = "running" ]; then
|
|
||||||
# Retrieve DB user and SSH key from Coolify database
|
|
||||||
DB_USER=$(docker inspect coolify-db --format '{{ .Config.Env }}' | grep -oP 'POSTGRES_USER=\K[^ ]+')
|
|
||||||
DB_SSH_KEY=$(docker exec coolify-db psql -U $DB_USER -d coolify -t -c "SELECT \"private_key\" FROM \"private_keys\" WHERE id = 0 AND team_id = 0 LIMIT 1;" -A -t)
|
|
||||||
|
|
||||||
if [ -z "$DB_SSH_KEY" ]; then
|
|
||||||
# If no key found in DB, generate a new one
|
|
||||||
echo " - SSH key not found in database. Generating new key."
|
|
||||||
generateSshKey
|
|
||||||
else
|
|
||||||
# If key found in DB, save it and update authorized_keys
|
|
||||||
echo " - SSH key found in database. Saving to file."
|
|
||||||
echo "$DB_SSH_KEY" > /data/coolify/ssh/keys/id.root@host.docker.internal
|
|
||||||
chmod 600 /data/coolify/ssh/keys/id.root@host.docker.internal
|
|
||||||
chown 9999 /data/coolify/ssh/keys/id.root@host.docker.internal
|
|
||||||
|
|
||||||
# Generate public key from private key and update authorized_keys
|
|
||||||
ssh-keygen -y -f /data/coolify/ssh/keys/id.root@host.docker.internal -C root@coolify > /data/coolify/ssh/keys/id.root@host.docker.internal.pub
|
|
||||||
sed -i "/root@coolify/d" ~/.ssh/authorized_keys
|
|
||||||
cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >> ~/.ssh/authorized_keys
|
|
||||||
rm -f /data/coolify/ssh/keys/id.root@host.docker.internal.pub
|
|
||||||
chmod 600 ~/.ssh/authorized_keys
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
# If SSH key doesn't exist in either location
|
|
||||||
elif ! checkSshKeyInAuthorizedKeys && ! checkSshKeyInCoolifyData; then
|
|
||||||
# Ensure Coolify DB is running before proceeding
|
|
||||||
if [ "$DB_RUNNING" = "running" ]; then
|
|
||||||
# Retrieve DB user and SSH key from Coolify database
|
|
||||||
DB_USER=$(docker inspect coolify-db --format '{{ .Config.Env }}' | grep -oP 'POSTGRES_USER=\K[^ ]+')
|
|
||||||
DB_SSH_KEY=$(docker exec coolify-db psql -U $DB_USER -d coolify -t -c "SELECT \"private_key\" FROM \"private_keys\" WHERE id = 0 AND team_id = 0 LIMIT 1;" -A -t)
|
|
||||||
if [ -z "$DB_SSH_KEY" ]; then
|
|
||||||
# If no key found in DB, generate a new one
|
|
||||||
echo " - SSH key not found in database. Generating new key."
|
|
||||||
generateSshKey
|
|
||||||
else
|
|
||||||
# If key found in DB, save it and update authorized_keys
|
|
||||||
echo " - SSH key found in database. Saving to file."
|
|
||||||
echo "$DB_SSH_KEY" > /data/coolify/ssh/keys/id.root@host.docker.internal
|
|
||||||
chmod 600 /data/coolify/ssh/keys/id.root@host.docker.internal
|
|
||||||
ssh-keygen -y -f /data/coolify/ssh/keys/id.root@host.docker.internal -C root@coolify > /data/coolify/ssh/keys/id.root@host.docker.internal.pub
|
|
||||||
sed -i "/root@coolify/d" ~/.ssh/authorized_keys
|
|
||||||
cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >> ~/.ssh/authorized_keys
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
generateSshKey
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
syncSshKeys || true
|
|
||||||
|
|
||||||
chown -R 9999:root /data/coolify
|
|
||||||
chmod -R 700 /data/coolify
|
|
||||||
|
|
||||||
echo -e "9. Installing Coolify ($LATEST_VERSION)"
|
|
||||||
echo -e " - It could take a while based on your server's performance, network speed, stars, etc."
|
|
||||||
echo -e " - Please wait."
|
|
||||||
getAJoke
|
|
||||||
|
|
||||||
bash /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}" "${LATEST_HELPER_VERSION:-latest}" >/dev/null 2>&1
|
|
||||||
echo " - Last Hour Cloud installed successfully."
|
|
||||||
rm -f $ENV_FILE-$DATE
|
|
||||||
|
|
||||||
echo " - Waiting for 20 seconds for Last Hour Cloud (database migrations) to be ready."
|
|
||||||
getAJoke
|
|
||||||
|
|
||||||
sleep 20
|
|
||||||
echo -e "\033[0;35m
|
|
||||||
____ _ _ _ _ _
|
|
||||||
/ ___|___ _ __ __ _ _ __ __ _| |_ _ _| | __ _| |_(_) ___ _ __ ___| |
|
|
||||||
| | / _ \| '_ \ / _\` | '__/ _\` | __| | | | |/ _\` | __| |/ _ \| '_ \/ __| |
|
|
||||||
| |__| (_) | | | | (_| | | | (_| | |_| |_| | | (_| | |_| | (_) | | | \__ \_|
|
|
||||||
\____\___/|_| |_|\__, |_| \__,_|\__|\__,_|_|\__,_|\__|_|\___/|_| |_|___(_)
|
|
||||||
|___/
|
|
||||||
\033[0m"
|
|
||||||
echo -e "\nYour instance is ready to use."
|
|
||||||
echo -e "Please visit http://$(curl -4s https://ifconfig.io):8000 to get started.\n"
|
|
||||||
echo -e "WARNING: We recommend you to backup your /data/coolify/source/.env file to a safe location, outside of this server."
|
|
||||||
cp /data/coolify/source/.env /data/coolify/source/.env.backup
|
|
140
scripts/run
140
scripts/run
@ -1,56 +1,104 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
# Sync docker volumes between two servers
|
|
||||||
|
|
||||||
VERSION="1.0.0"
|
# Inspired on https://github.com/adriancooney/Taskfile
|
||||||
SOURCE=$1
|
#
|
||||||
DESTINATION=$2
|
# Install an alias, to be able to simply execute `run`
|
||||||
|
# echo 'alias run=./scripts/run' >> ~/.aliases
|
||||||
|
#
|
||||||
|
# Define Docker Compose command prefix...
|
||||||
set -e
|
set -e
|
||||||
if [ -z "$SOURCE" ]; then
|
|
||||||
echo "Source server is not specified."
|
if [ $? == 0 ]; then
|
||||||
exit 1
|
DOCKER_COMPOSE="docker compose"
|
||||||
fi
|
else
|
||||||
if [ -z "$DESTINATION" ]; then
|
DOCKER_COMPOSE="docker-compose"
|
||||||
echo "Destination server is not specified."
|
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
SOURCE_USER=$(echo $SOURCE | cut -d@ -f1)
|
function help {
|
||||||
SOURCE_SERVER=$(echo $SOURCE | cut -d: -f1 | cut -d@ -f2)
|
echo "$0 <task> <args>"
|
||||||
SOURCE_PORT=$(echo $SOURCE | cut -d: -f2 | cut -d/ -f1)
|
echo "Tasks:"
|
||||||
SOURCE_VOLUME_NAME=$(echo $SOURCE | cut -d/ -f2)
|
compgen -A function | cat -n
|
||||||
|
}
|
||||||
|
|
||||||
if ! [[ "$SOURCE_PORT" =~ ^[0-9]+$ ]]; then
|
# function sync:v3 {
|
||||||
echo "Invalid source port: $SOURCE_PORT"
|
# if [ -z "$1" ]; then
|
||||||
exit 1
|
# echo -e "Please provide a version.\n\nExample: run sync:v3 3.12.32"
|
||||||
fi
|
# exit 1
|
||||||
|
# fi
|
||||||
|
# skopeo copy --all docker://ghcr.io/coollabsio/coolify:$1 docker://coollabsio/coolify:$1
|
||||||
|
# }
|
||||||
|
function sync:bunny {
|
||||||
|
php artisan sync:bunny --env=secrets
|
||||||
|
}
|
||||||
|
|
||||||
DESTINATION_USER=$(echo $DESTINATION | cut -d@ -f1)
|
# function queue {
|
||||||
DESTINATION_SERVER=$(echo $DESTINATION | cut -d: -f1 | cut -d@ -f2)
|
# bash spin exec -u webuser coolify php artisan queue:listen
|
||||||
DESTINATION_PORT=$(echo $DESTINATION | cut -d: -f2 | cut -d/ -f1)
|
# }
|
||||||
DESTINATION_VOLUME_NAME=$(echo $DESTINATION | cut -d/ -f2)
|
|
||||||
|
|
||||||
if ! [[ "$DESTINATION_PORT" =~ ^[0-9]+$ ]]; then
|
# function horizon {
|
||||||
echo "Invalid destination port: $DESTINATION_PORT"
|
# bash spin exec -u webuser coolify php artisan horizon -vvv
|
||||||
exit 1
|
# }
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Generating backup file to ./$SOURCE_VOLUME_NAME.tgz"
|
# function schedule {
|
||||||
ssh -p $SOURCE_PORT $SOURCE_USER@$SOURCE_SERVER "docker run -v $SOURCE_VOLUME_NAME:/volume --rm --log-driver none loomchild/volume-backup backup -c pigz -v" >./$SOURCE_VOLUME_NAME.tgz
|
# bash spin exec -u webuser coolify php artisan schedule:work
|
||||||
echo ""
|
# }
|
||||||
if [ -f "./$SOURCE_VOLUME_NAME.tgz" ]; then
|
|
||||||
echo "Uploading backup file to $DESTINATION_SERVER:~/$DESTINATION_VOLUME_NAME.tgz"
|
|
||||||
scp -P $DESTINATION_PORT ./$SOURCE_VOLUME_NAME.tgz $DESTINATION_USER@$DESTINATION_SERVER:~/$DESTINATION_VOLUME_NAME.tgz
|
|
||||||
echo ""
|
|
||||||
echo "Restoring backup file on remote ($DESTINATION_SERVER:/~/$DESTINATION_VOLUME_NAME.tgz)"
|
|
||||||
ssh -p $DESTINATION_PORT $DESTINATION_USER@$DESTINATION_SERVER "docker run -i -v $DESTINATION_VOLUME_NAME:/volume --log-driver none --rm loomchild/volume-backup restore -c pigz -vf < ~/$DESTINATION_VOLUME_NAME.tgz"
|
|
||||||
echo ""
|
|
||||||
echo "Deleting backup file on remote ($DESTINATION_SERVER:/~/$DESTINATION_VOLUME_NAME.tgz)"
|
|
||||||
ssh -p $DESTINATION_PORT $DESTINATION_USER@$DESTINATION_SERVER "rm ~/$DESTINATION_VOLUME_NAME.tgz"
|
|
||||||
|
|
||||||
echo ""
|
# function schedule:run {
|
||||||
echo "Local file ./$SOURCE_VOLUME_NAME.tgz is not deleted."
|
# bash spin exec -u webuser coolify php artisan schedule:run
|
||||||
|
# }
|
||||||
|
|
||||||
echo ""
|
# function db {
|
||||||
echo "WARNING: If you are copying a database volume, you need to set the right users/passwords on the destination service's environment variables."
|
# bash spin exec -u webuser coolify php artisan db
|
||||||
echo "Why? Because we are copying the volume as-is, so the database credentials will bethe same as on the source volume."
|
# }
|
||||||
fi
|
# function db:seed {
|
||||||
|
# bash spin exec -u webuser coolify php artisan migrate --seed
|
||||||
|
# }
|
||||||
|
|
||||||
|
# function db:migrate {
|
||||||
|
# bash spin exec -u webuser coolify php artisan migrate --step
|
||||||
|
# }
|
||||||
|
|
||||||
|
function db:reset {
|
||||||
|
bash spin exec -u webuser coolify php artisan migrate:fresh --seed
|
||||||
|
}
|
||||||
|
function db:reset-prod {
|
||||||
|
bash spin exec -u webuser coolify php artisan migrate:fresh --force --seed --seeder=ProductionSeeder ||
|
||||||
|
php artisan migrate:fresh --force --seed --seeder=ProductionSeeder
|
||||||
|
}
|
||||||
|
|
||||||
|
function mfs {
|
||||||
|
db:reset
|
||||||
|
}
|
||||||
|
|
||||||
|
function coolify {
|
||||||
|
bash spin exec -u webuser coolify bash
|
||||||
|
}
|
||||||
|
|
||||||
|
function coolify:root {
|
||||||
|
bash spin exec coolify bash
|
||||||
|
}
|
||||||
|
function coolify:proxy {
|
||||||
|
docker exec -ti coolify-proxy sh
|
||||||
|
}
|
||||||
|
|
||||||
|
function redis {
|
||||||
|
docker exec -ti coolify-redis redis-cli
|
||||||
|
}
|
||||||
|
|
||||||
|
function vite {
|
||||||
|
bash spin exec vite bash
|
||||||
|
}
|
||||||
|
|
||||||
|
function tinker {
|
||||||
|
bash spin exec -u webuser coolify php artisan tinker
|
||||||
|
}
|
||||||
|
|
||||||
|
# function build:helper {
|
||||||
|
# act -W .github/workflows/coolify-helper.yml --secret-file .env.secrets
|
||||||
|
# }
|
||||||
|
function default {
|
||||||
|
help
|
||||||
|
}
|
||||||
|
|
||||||
|
TIMEFORMAT="Task completed in %3lR"
|
||||||
|
time "${@:-default}"
|
||||||
|
@ -1,17 +1,23 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
## Do not modify this file. You will lose the ability to autoupdate!
|
## Do not modify this file. You will lose the ability to autoupdate!
|
||||||
|
|
||||||
VERSION="1.1"
|
VERSION="1.0.5"
|
||||||
CDN="https://cdn.lasthourhosting.org/lasthourcloudprod"
|
CDN="https://cdn.lasthourhosting.org/lasthourcloud"
|
||||||
LATEST_IMAGE=${1:-latest}
|
|
||||||
LATEST_HELPER_VERSION=${2:-latest}
|
|
||||||
|
|
||||||
curl -fsSL $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml
|
curl -fsSL $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml
|
||||||
curl -fsSL $CDN/docker-compose.prod.yml -o /data/coolify/source/docker-compose.prod.yml
|
curl -fsSL $CDN/docker-compose.prod.yml -o /data/coolify/source/docker-compose.prod.yml
|
||||||
curl -fsSL $CDN/.env.production -o /data/coolify/source/.env.production
|
curl -fsSL $CDN/.env.production -o /data/coolify/source/.env.production
|
||||||
|
|
||||||
|
# echo "Copying required files from Last Hour Cloud git repo..."
|
||||||
|
# cp /home/lasthour/lasthourcloud/docker-compose.yml /data/coolify/source/docker-compose.yml
|
||||||
|
# cp /home/lasthour/lasthourcloud/docker-compose.prod.yml /data/coolify/source/docker-compose.prod.yml
|
||||||
|
# cp /home/lasthour/lasthourcloud/.env.production /data/coolify/source/.env.production
|
||||||
|
# cp /home/lasthour/lasthourcloud/scripts/upgrade.sh /data/coolify/source/upgrade.sh
|
||||||
|
|
||||||
|
|
||||||
# Merge .env and .env.production. New values will be added to .env
|
# Merge .env and .env.production. New values will be added to .env
|
||||||
awk -F '=' '!seen[$1]++' /data/coolify/source/.env /data/coolify/source/.env.production > /data/coolify/source/.env.tmp && mv /data/coolify/source/.env.tmp /data/coolify/source/.env
|
sort -u -t '=' -k 1,1 /data/coolify/source/.env /data/coolify/source/.env.production | sed '/^$/d' >/data/coolify/source/.env.temp && mv /data/coolify/source/.env.temp /data/coolify/source/.env
|
||||||
|
|
||||||
# Check if PUSHER_APP_ID or PUSHER_APP_KEY or PUSHER_APP_SECRET is empty in /data/coolify/source/.env
|
# Check if PUSHER_APP_ID or PUSHER_APP_KEY or PUSHER_APP_SECRET is empty in /data/coolify/source/.env
|
||||||
if grep -q "PUSHER_APP_ID=$" /data/coolify/source/.env; then
|
if grep -q "PUSHER_APP_ID=$" /data/coolify/source/.env; then
|
||||||
sed -i "s|PUSHER_APP_ID=.*|PUSHER_APP_ID=$(openssl rand -hex 32)|g" /data/coolify/source/.env
|
sed -i "s|PUSHER_APP_ID=.*|PUSHER_APP_ID=$(openssl rand -hex 32)|g" /data/coolify/source/.env
|
||||||
@ -25,14 +31,13 @@ if grep -q "PUSHER_APP_SECRET=$" /data/coolify/source/.env; then
|
|||||||
sed -i "s|PUSHER_APP_SECRET=.*|PUSHER_APP_SECRET=$(openssl rand -hex 32)|g" /data/coolify/source/.env
|
sed -i "s|PUSHER_APP_SECRET=.*|PUSHER_APP_SECRET=$(openssl rand -hex 32)|g" /data/coolify/source/.env
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Make sure Last Hour Cloud network exists
|
# Make sure coolify network exists
|
||||||
# It is created when starting Last Hour Cloud with docker compose
|
|
||||||
docker network create --attachable coolify 2>/dev/null
|
docker network create --attachable coolify 2>/dev/null
|
||||||
# docker network create --attachable --driver=overlay coolify-overlay 2>/dev/null
|
# docker network create --attachable --driver=overlay coolify-overlay 2>/dev/null
|
||||||
|
|
||||||
if [ -f /data/coolify/source/docker-compose.custom.yml ]; then
|
if [ -f /data/coolify/source/docker-compose.custom.yml ]; then
|
||||||
echo "docker-compose.custom.yml detected."
|
echo "docker-compose.custom.yml detected."
|
||||||
docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper:${LATEST_HELPER_VERSION:-latest} bash -c "LATEST_IMAGE=${1:-} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml -f /data/coolify/source/docker-compose.custom.yml up -d --remove-orphans --force-recreate --wait --wait-timeout 60"
|
docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper bash -c "LATEST_IMAGE=${1:-} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml -f /data/coolify/source/docker-compose.custom.yml up -d --remove-orphans --force-recreate"
|
||||||
else
|
else
|
||||||
docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper:${LATEST_HELPER_VERSION:-latest} bash -c "LATEST_IMAGE=${1:-} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml up -d --remove-orphans --force-recreate --wait --wait-timeout 60"
|
docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper bash -c "LATEST_IMAGE=${1:-} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml up -d --remove-orphans --force-recreate"
|
||||||
fi
|
fi
|
||||||
|
Loading…
Reference in New Issue
Block a user