mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-30 12:33:45 +00:00
Compare commits
56 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d826d9fb4 | ||
|
|
0af221f9fc | ||
|
|
9066c9bf90 | ||
|
|
77e3208f00 | ||
|
|
db5ecf07bd | ||
|
|
7f28aa6985 | ||
|
|
9d53e04ce9 | ||
|
|
3db9a1dd6e | ||
|
|
38f9761b67 | ||
|
|
7117ecf634 | ||
|
|
522e20f10a | ||
|
|
ebbce2396c | ||
|
|
f9a2ff6d90 | ||
|
|
df1b9e7319 | ||
|
|
e7c0c26b32 | ||
|
|
0dbb8b4420 | ||
|
|
a4b44bacc1 | ||
|
|
1338e68b8c | ||
|
|
f6f4cdde24 | ||
|
|
3daadf13c6 | ||
|
|
cbd5fab2e7 | ||
|
|
48ccb508f9 | ||
|
|
67edce0612 | ||
|
|
e8a41d7e6e | ||
|
|
be1dad03bd | ||
|
|
31db1db636 | ||
|
|
dca332a688 | ||
|
|
35f19ed53f | ||
|
|
a5c45ffe90 | ||
|
|
b6c3a65d2d | ||
|
|
220c8211fd | ||
|
|
ffbd04df29 | ||
|
|
73c59be865 | ||
|
|
3966abaf80 | ||
|
|
759517316a | ||
|
|
3e3024d47e | ||
|
|
517cb77637 | ||
|
|
14bd89a991 | ||
|
|
304de29924 | ||
|
|
ab3055150f | ||
|
|
6b9c7aa9c5 | ||
|
|
040f47b59c | ||
|
|
eac7834083 | ||
|
|
135a298080 | ||
|
|
0065a86371 | ||
|
|
2a842a2f50 | ||
|
|
231c02e00e | ||
|
|
4de4587ea6 | ||
|
|
8675e1d13f | ||
|
|
6ceacc68cc | ||
|
|
4aacf134b7 | ||
|
|
0605772715 | ||
|
|
3fa53556f4 | ||
|
|
76510b8971 | ||
|
|
66162966b9 | ||
|
|
71e1571c39 |
@@ -6,6 +6,7 @@
|
|||||||
USERID=
|
USERID=
|
||||||
GROUPID=
|
GROUPID=
|
||||||
############################################################################################################
|
############################################################################################################
|
||||||
|
APP_NAME=Coolify-localhost
|
||||||
APP_ID=development
|
APP_ID=development
|
||||||
APP_ENV=local
|
APP_ENV=local
|
||||||
APP_KEY=
|
APP_KEY=
|
||||||
|
|||||||
84
.github/workflows/coolify-helper-next.yml
vendored
Normal file
84
.github/workflows/coolify-helper-next.yml
vendored
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
name: Coolify Helper Image Development (v4)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "next" ]
|
||||||
|
paths:
|
||||||
|
- .github/workflows/coolify-helper.yml
|
||||||
|
- docker/coolify-helper/Dockerfile
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: "coollabsio/coolify-helper"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
amd64:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build image and push to registry
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
no-cache: true
|
||||||
|
context: .
|
||||||
|
file: docker/coolify-helper/Dockerfile
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
||||||
|
aarch64:
|
||||||
|
runs-on: [ self-hosted, arm64 ]
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build image and push to registry
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
no-cache: true
|
||||||
|
context: .
|
||||||
|
file: docker/coolify-helper/Dockerfile
|
||||||
|
platforms: linux/aarch64
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64
|
||||||
|
merge-manifest:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
needs: [ amd64, aarch64 ]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Create & publish manifest
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
||||||
|
- uses: sarisia/actions-status-discord@v1
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||||
8
.github/workflows/coolify-helper.yml
vendored
8
.github/workflows/coolify-helper.yml
vendored
@@ -2,7 +2,7 @@ name: Coolify Helper Image (v4)
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "main", "next" ]
|
branches: [ "main" ]
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/coolify-helper.yml
|
- .github/workflows/coolify-helper.yml
|
||||||
- docker/coolify-helper/Dockerfile
|
- docker/coolify-helper/Dockerfile
|
||||||
@@ -55,7 +55,7 @@ jobs:
|
|||||||
file: docker/coolify-helper/Dockerfile
|
file: docker/coolify-helper/Dockerfile
|
||||||
platforms: linux/aarch64
|
platforms: linux/aarch64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:aarch64
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64
|
||||||
merge-manifest:
|
merge-manifest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
@@ -78,3 +78,7 @@ jobs:
|
|||||||
- name: Create & publish manifest
|
- name: Create & publish manifest
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
- uses: sarisia/actions-status-discord@v1
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}
|
||||||
|
|||||||
5
.github/workflows/development-build.yml
vendored
5
.github/workflows/development-build.yml
vendored
@@ -3,6 +3,9 @@ name: Development Build (v4)
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ["next"]
|
branches: ["next"]
|
||||||
|
paths-ignore:
|
||||||
|
- .github/workflows/coolify-helper.yml
|
||||||
|
- docker/coolify-helper/Dockerfile
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
@@ -73,4 +76,4 @@ jobs:
|
|||||||
- uses: sarisia/actions-status-discord@v1
|
- uses: sarisia/actions-status-discord@v1
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -31,3 +31,4 @@ _ide_helper.php
|
|||||||
_ide_helper_models.php
|
_ide_helper_models.php
|
||||||
.rnd
|
.rnd
|
||||||
/.ssh
|
/.ssh
|
||||||
|
scripts/load-test/*
|
||||||
|
|||||||
52
README.md
52
README.md
@@ -1,20 +1,30 @@
|
|||||||
# Coolify v4 Beta
|
# About the Project
|
||||||
|
|
||||||
An open-source & self-hostable Heroku / Netlify alternative.
|
Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc.
|
||||||
|
|
||||||
|
It helps you to manage your servers, applications, databases on your own hardware, all you need is SSH connection. You can manage VPS, Bare Metal, Raspberry PI's anything.
|
||||||
|
|
||||||
|
Image if you could have the ease of a cloud but with your own servers. That is **Coolify**.
|
||||||
|
|
||||||
|
No vendor lock-in, which means that all the configuration 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 just lose the automations and all the magic. 🪄️
|
||||||
|
|
||||||
|
For more information, take a look at our landing page [here](https://coolify.io).
|
||||||
|
|
||||||
|
> If you are looking for previous (v3) version, it is [here](https://github.com/coollabsio/coolify/tree/v3).
|
||||||
|
|
||||||
|
# Cloud
|
||||||
|
|
||||||
|
If you do not want to self-host Coolify, there is a paid cloud version available: https://app.coolify.io
|
||||||
|
|
||||||
|
You can easily attach your own servers, get all the automations, free email notifications, etc.
|
||||||
|
|
||||||
|
For more information & pricing, take a look at our landing page [here](https://coolify.io).
|
||||||
|
|
||||||
# Beta
|
# Beta
|
||||||
|
|
||||||
You are checking the next-gen of Coolify, aka v4. Hi 👋
|
The latest version (v4) is still in beta. That does not mean it is unstable. All the features that are available are stable enough be usable in real-life.
|
||||||
|
|
||||||
It is still in beta, lots of improvements will come every day. Things could break, but we are working hard to make it stable as soon as possible. If you find any bugs, please report them.
|
There are hundreds of people using it for managing their client's applications, freelancers, hobbyists, businesses.
|
||||||
|
|
||||||
Automatic updates are available, so you will receive the latest version as soon as it is released.
|
|
||||||
|
|
||||||
If you are looking for v3, check out the [v3 branch](https://github.com/coollabsio/coolify/tree/v3).
|
|
||||||
|
|
||||||
## What's new?
|
|
||||||
|
|
||||||
Well, the whole tech stack changed, core is different, so yeah, a lot (documentation incoming).
|
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
@@ -26,13 +36,19 @@ You can find the installation script [here](./scripts/install.sh).
|
|||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
- Twitter: [@heyandras](https://twitter.com/heyandras)
|
Contact us [here](https://docs.coollabs.io/contact).
|
||||||
- Mastodon: [@andrasbacsai@fosstodon.org](https://fosstodon.org/@andrasbacsai)
|
|
||||||
- Email: [andras@coollabs.io](mailto:andras@coollabs.io)
|
|
||||||
- Discord: [Invitation](https://coollabs.io/discord)
|
|
||||||
- Telegram: [@andrasbacsai](https://t.me/andrasbacsai)
|
|
||||||
|
|
||||||
---
|
## Recognitions
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<a href="https://www.producthunt.com/posts/coolify?utm_source=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>
|
||||||
|
|
||||||
## 💰 Financial Contributors
|
## 💰 Financial Contributors
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class StartPostgresql
|
|||||||
],
|
],
|
||||||
'networks' => [
|
'networks' => [
|
||||||
$this->database->destination->network => [
|
$this->database->destination->network => [
|
||||||
'external' => false,
|
'external' => true,
|
||||||
'name' => $this->database->destination->network,
|
'name' => $this->database->destination->network,
|
||||||
'attachable' => true,
|
'attachable' => true,
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -10,8 +10,14 @@ class InstallDocker
|
|||||||
{
|
{
|
||||||
public function __invoke(Server $server, Team $team)
|
public function __invoke(Server $server, Team $team)
|
||||||
{
|
{
|
||||||
$dockerVersion = '23.0';
|
$dockerVersion = '24.0';
|
||||||
$config = base64_encode('{ "live-restore": true }');
|
$config = base64_encode('{
|
||||||
|
"log-driver": "json-file",
|
||||||
|
"log-opts": {
|
||||||
|
"max-size": "10m",
|
||||||
|
"max-file": "3"
|
||||||
|
}
|
||||||
|
}');
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
$activity = remote_process([
|
$activity = remote_process([
|
||||||
"echo ####### Installing Prerequisites...",
|
"echo ####### Installing Prerequisites...",
|
||||||
|
|||||||
184
app/Console/Commands/TestEmail.php
Normal file
184
app/Console/Commands/TestEmail.php
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\ApplicationPreview;
|
||||||
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\TeamInvitation;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Notifications\Application\DeploymentFailed;
|
||||||
|
use App\Notifications\Application\DeploymentSuccess;
|
||||||
|
use App\Notifications\Application\StatusChanged;
|
||||||
|
use App\Notifications\Database\BackupFailed;
|
||||||
|
use App\Notifications\Database\BackupSuccess;
|
||||||
|
use App\Notifications\Test;
|
||||||
|
use App\Notifications\TransactionalEmails\InvitationLink;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Mail\Message;
|
||||||
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Mail;
|
||||||
|
use Str;
|
||||||
|
|
||||||
|
use function Laravel\Prompts\select;
|
||||||
|
|
||||||
|
class TestEmail extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'email:test';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Send a test email to the admin';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
private ?MailMessage $mail = null;
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$email = select(
|
||||||
|
'Which Email should be sent?',
|
||||||
|
options: [
|
||||||
|
'emails-test' => 'Test',
|
||||||
|
'application-deployment-success' => 'Application - Deployment Success',
|
||||||
|
'application-deployment-failed' => 'Application - Deployment Failed',
|
||||||
|
'application-status-changed' => 'Application - Status Changed',
|
||||||
|
'backup-success' => 'Database - Backup Success',
|
||||||
|
'backup-failed' => 'Database - Backup Failed',
|
||||||
|
'invitation-link' => 'Invitation Link',
|
||||||
|
'waitlist-invitation-link' => 'Waitlist Invitation Link',
|
||||||
|
'waitlist-confirmation' => 'Waitlist Confirmation',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$type = set_transanctional_email_settings();
|
||||||
|
if (!$type) {
|
||||||
|
throw new Exception('No email settings found.');
|
||||||
|
}
|
||||||
|
$this->mail = new MailMessage();
|
||||||
|
$this->mail->subject("Test Email");
|
||||||
|
switch ($email) {
|
||||||
|
case 'emails-test':
|
||||||
|
$this->mail = (new Test())->toMail();
|
||||||
|
break;
|
||||||
|
case 'application-deployment-success':
|
||||||
|
$application = Application::all()->first();
|
||||||
|
$this->mail = (new DeploymentSuccess($application, 'test'))->toMail();
|
||||||
|
$this->sendEmail();
|
||||||
|
break;
|
||||||
|
case 'application-deployment-failed':
|
||||||
|
$application = Application::all()->first();
|
||||||
|
$preview = ApplicationPreview::all()->first();
|
||||||
|
if (!$preview) {
|
||||||
|
$preview = ApplicationPreview::create([
|
||||||
|
'application_id' => $application->id,
|
||||||
|
'pull_request_id' => 1,
|
||||||
|
'pull_request_html_url' => 'http://example.com',
|
||||||
|
'fqdn' => $application->fqdn,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$this->mail = (new DeploymentFailed($application, 'test'))->toMail();
|
||||||
|
$this->sendEmail();
|
||||||
|
$this->mail = (new DeploymentFailed($application, 'test', $preview))->toMail();
|
||||||
|
$this->sendEmail();
|
||||||
|
break;
|
||||||
|
case 'application-status-changed':
|
||||||
|
$application = Application::all()->first();
|
||||||
|
$this->mail = (new StatusChanged($application))->toMail();
|
||||||
|
$this->sendEmail();
|
||||||
|
break;
|
||||||
|
case 'backup-failed':
|
||||||
|
$backup = ScheduledDatabaseBackup::all()->first();
|
||||||
|
$db = StandalonePostgresql::all()->first();
|
||||||
|
if (!$backup) {
|
||||||
|
$backup = ScheduledDatabaseBackup::create([
|
||||||
|
'enabled' => true,
|
||||||
|
'frequency' => 'daily',
|
||||||
|
'save_s3' => false,
|
||||||
|
'database_id' => $db->id,
|
||||||
|
'database_type' => $db->getMorphClass(),
|
||||||
|
'team_id' => 0,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$output = 'Because of an error, the backup of the database ' . $db->name . ' failed.';
|
||||||
|
$this->mail = (new BackupFailed($backup, $db, $output))->toMail();
|
||||||
|
$this->sendEmail();
|
||||||
|
break;
|
||||||
|
case 'backup-success':
|
||||||
|
$backup = ScheduledDatabaseBackup::all()->first();
|
||||||
|
$db = StandalonePostgresql::all()->first();
|
||||||
|
if (!$backup) {
|
||||||
|
$backup = ScheduledDatabaseBackup::create([
|
||||||
|
'enabled' => true,
|
||||||
|
'frequency' => 'daily',
|
||||||
|
'save_s3' => false,
|
||||||
|
'database_id' => $db->id,
|
||||||
|
'database_type' => $db->getMorphClass(),
|
||||||
|
'team_id' => 0,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$this->mail = (new BackupSuccess($backup, $db))->toMail();
|
||||||
|
$this->sendEmail();
|
||||||
|
break;
|
||||||
|
case 'invitation-link':
|
||||||
|
$user = User::all()->first();
|
||||||
|
$invitation = TeamInvitation::whereEmail($user->email)->first();
|
||||||
|
if (!$invitation) {
|
||||||
|
$invitation = TeamInvitation::create([
|
||||||
|
'uuid' => Str::uuid(),
|
||||||
|
'email' => $user->email,
|
||||||
|
'team_id' => 1,
|
||||||
|
'link' => 'http://example.com',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$this->mail = (new InvitationLink($user))->toMail();
|
||||||
|
$this->sendEmail();
|
||||||
|
break;
|
||||||
|
case 'waitlist-invitation-link':
|
||||||
|
$this->mail = new MailMessage();
|
||||||
|
$this->mail->view('emails.waitlist-invitation', [
|
||||||
|
'email' => 'test2@example.com',
|
||||||
|
'password' => "supersecretpassword",
|
||||||
|
]);
|
||||||
|
$this->mail->subject('Congratulations! You are invited to join Coolify Cloud.');
|
||||||
|
$this->sendEmail();
|
||||||
|
break;
|
||||||
|
case 'waitlist-confirmation':
|
||||||
|
$this->mail = new MailMessage();
|
||||||
|
$this->mail->view(
|
||||||
|
'emails.waitlist-confirmation',
|
||||||
|
[
|
||||||
|
'confirmation_url' => 'http://example.com',
|
||||||
|
'cancel_url' => 'http://example.com',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$this->mail->subject('You are on the waitlist!');
|
||||||
|
$this->sendEmail();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function sendEmail()
|
||||||
|
{
|
||||||
|
Mail::send(
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
fn (Message $message) => $message
|
||||||
|
->from(
|
||||||
|
'internal@example.com',
|
||||||
|
'Test Email',
|
||||||
|
)
|
||||||
|
->to('test@example.com')
|
||||||
|
->subject($this->mail->subject)
|
||||||
|
->html((string)$this->mail->render())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,20 +6,20 @@ use App\Models\User;
|
|||||||
use App\Models\Waitlist;
|
use App\Models\Waitlist;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Support\Facades\Crypt;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class InviteFromWaitlist extends Command
|
class WaitlistInvite extends Command
|
||||||
{
|
{
|
||||||
public Waitlist|null $next_patient = null;
|
public Waitlist|User|null $next_patient = null;
|
||||||
public User|null $new_user = null;
|
|
||||||
public string|null $password = null;
|
public string|null $password = null;
|
||||||
/**
|
/**
|
||||||
* The name and signature of the console command.
|
* The name and signature of the console command.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'app:invite-from-waitlist {email?}';
|
protected $signature = 'waitlist:invite {email?} {--only-email}';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
@@ -34,7 +34,16 @@ class InviteFromWaitlist extends Command
|
|||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
if ($this->argument('email')) {
|
if ($this->argument('email')) {
|
||||||
$this->next_patient = Waitlist::where('email', $this->argument('email'))->first();
|
if ($this->option('only-email')) {
|
||||||
|
$this->next_patient = User::whereEmail($this->argument('email'))->first();
|
||||||
|
$this->password = Str::password();
|
||||||
|
$this->next_patient->update([
|
||||||
|
'password' => Hash::make($this->password),
|
||||||
|
'force_password_reset' => true,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$this->next_patient = Waitlist::where('email', $this->argument('email'))->first();
|
||||||
|
}
|
||||||
if (!$this->next_patient) {
|
if (!$this->next_patient) {
|
||||||
$this->error("{$this->argument('email')} not found in the waitlist.");
|
$this->error("{$this->argument('email')} not found in the waitlist.");
|
||||||
return;
|
return;
|
||||||
@@ -43,6 +52,10 @@ class InviteFromWaitlist extends Command
|
|||||||
$this->next_patient = Waitlist::orderBy('created_at', 'asc')->where('verified', true)->first();
|
$this->next_patient = Waitlist::orderBy('created_at', 'asc')->where('verified', true)->first();
|
||||||
}
|
}
|
||||||
if ($this->next_patient) {
|
if ($this->next_patient) {
|
||||||
|
if ($this->option('only-email')) {
|
||||||
|
$this->send_email();
|
||||||
|
return;
|
||||||
|
}
|
||||||
$this->register_user();
|
$this->register_user();
|
||||||
$this->remove_from_waitlist();
|
$this->remove_from_waitlist();
|
||||||
$this->send_email();
|
$this->send_email();
|
||||||
@@ -55,7 +68,7 @@ class InviteFromWaitlist extends Command
|
|||||||
$already_registered = User::whereEmail($this->next_patient->email)->first();
|
$already_registered = User::whereEmail($this->next_patient->email)->first();
|
||||||
if (!$already_registered) {
|
if (!$already_registered) {
|
||||||
$this->password = Str::password();
|
$this->password = Str::password();
|
||||||
$this->new_user = User::create([
|
User::create([
|
||||||
'name' => Str::of($this->next_patient->email)->before('@'),
|
'name' => Str::of($this->next_patient->email)->before('@'),
|
||||||
'email' => $this->next_patient->email,
|
'email' => $this->next_patient->email,
|
||||||
'password' => Hash::make($this->password),
|
'password' => Hash::make($this->password),
|
||||||
@@ -73,10 +86,14 @@ class InviteFromWaitlist extends Command
|
|||||||
}
|
}
|
||||||
private function send_email()
|
private function send_email()
|
||||||
{
|
{
|
||||||
|
ray($this->next_patient->email, $this->password);
|
||||||
|
$token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password");
|
||||||
|
$loginLink = route('auth.link', ['token' => $token]);
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage();
|
||||||
$mail->view('emails.waitlist-invitation', [
|
$mail->view('emails.waitlist-invitation', [
|
||||||
'email' => $this->next_patient->email,
|
'email' => $this->next_patient->email,
|
||||||
'password' => $this->password,
|
'password' => $this->password,
|
||||||
|
'loginLink' => $loginLink,
|
||||||
]);
|
]);
|
||||||
$mail->subject('Congratulations! You are invited to join Coolify Cloud.');
|
$mail->subject('Congratulations! You are invited to join Coolify Cloud.');
|
||||||
send_user_an_email($mail, $this->next_patient->email);
|
send_user_an_email($mail, $this->next_patient->email);
|
||||||
@@ -2,14 +2,19 @@
|
|||||||
|
|
||||||
namespace App\Console;
|
namespace App\Console;
|
||||||
|
|
||||||
|
use App\Jobs\ApplicationContainerStatusJob;
|
||||||
use App\Jobs\CheckResaleLicenseJob;
|
use App\Jobs\CheckResaleLicenseJob;
|
||||||
use App\Jobs\CleanupInstanceStuffsJob;
|
use App\Jobs\CleanupInstanceStuffsJob;
|
||||||
use App\Jobs\DatabaseBackupJob;
|
use App\Jobs\DatabaseBackupJob;
|
||||||
|
use App\Jobs\DatabaseContainerStatusJob;
|
||||||
use App\Jobs\DockerCleanupJob;
|
use App\Jobs\DockerCleanupJob;
|
||||||
use App\Jobs\InstanceAutoUpdateJob;
|
use App\Jobs\InstanceAutoUpdateJob;
|
||||||
use App\Jobs\ProxyCheckJob;
|
use App\Jobs\ProxyCheckJob;
|
||||||
use App\Jobs\ResourceStatusJob;
|
use App\Jobs\ResourceStatusJob;
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
use Illuminate\Console\Scheduling\Schedule;
|
use Illuminate\Console\Scheduling\Schedule;
|
||||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||||
|
|
||||||
@@ -17,28 +22,46 @@ class Kernel extends ConsoleKernel
|
|||||||
{
|
{
|
||||||
protected function schedule(Schedule $schedule): void
|
protected function schedule(Schedule $schedule): void
|
||||||
{
|
{
|
||||||
// $schedule->call(fn() => $this->check_scheduled_backups($schedule))->everyTenSeconds();
|
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
$schedule->command('horizon:snapshot')->everyMinute();
|
$schedule->command('horizon:snapshot')->everyMinute();
|
||||||
$schedule->job(new ResourceStatusJob)->everyMinute();
|
// $schedule->job(new ResourceStatusJob)->everyMinute();
|
||||||
$schedule->job(new ProxyCheckJob)->everyFiveMinutes();
|
$schedule->job(new ProxyCheckJob)->everyFiveMinutes();
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute();
|
||||||
|
|
||||||
// $schedule->job(new CheckResaleLicenseJob)->hourly();
|
// $schedule->job(new CheckResaleLicenseJob)->hourly();
|
||||||
$schedule->job(new DockerCleanupJob)->everyOddHour();
|
$schedule->job(new DockerCleanupJob)->everyOddHour();
|
||||||
// $schedule->job(new InstanceAutoUpdateJob(true))->everyMinute();
|
|
||||||
} else {
|
} else {
|
||||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyTenMinutes()->onOneServer();
|
||||||
$schedule->job(new ResourceStatusJob)->everyMinute()->onOneServer();
|
// $schedule->job(new ResourceStatusJob)->everyMinute()->onOneServer();
|
||||||
$schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
$schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
||||||
$schedule->job(new ProxyCheckJob)->everyFiveMinutes()->onOneServer();
|
$schedule->job(new ProxyCheckJob)->everyFiveMinutes()->onOneServer();
|
||||||
$schedule->job(new DockerCleanupJob)->everyTenMinutes()->onOneServer();
|
$schedule->job(new DockerCleanupJob)->everyTenMinutes()->onOneServer();
|
||||||
$schedule->job(new InstanceAutoUpdateJob)->everyTenMinutes();
|
|
||||||
}
|
}
|
||||||
|
$this->instance_auto_update($schedule);
|
||||||
$this->check_scheduled_backups($schedule);
|
$this->check_scheduled_backups($schedule);
|
||||||
|
$this->check_resources($schedule);
|
||||||
}
|
}
|
||||||
|
private function check_resources($schedule)
|
||||||
|
{
|
||||||
|
$applications = Application::all();
|
||||||
|
foreach ($applications as $application) {
|
||||||
|
$schedule->job(new ApplicationContainerStatusJob($application))->everyMinute()->onOneServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
$postgresqls = StandalonePostgresql::all();
|
||||||
|
foreach ($postgresqls as $postgresql) {
|
||||||
|
$schedule->job(new DatabaseContainerStatusJob($postgresql))->everyMinute()->onOneServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function instance_auto_update($schedule){
|
||||||
|
if (isDev()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
if ($settings->is_auto_update_enabled) {
|
||||||
|
$schedule->job(new InstanceAutoUpdateJob)->everyTenMinutes()->onOneServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
private function check_scheduled_backups($schedule)
|
private function check_scheduled_backups($schedule)
|
||||||
{
|
{
|
||||||
ray('check_scheduled_backups');
|
ray('check_scheduled_backups');
|
||||||
@@ -57,7 +80,7 @@ class Kernel extends ConsoleKernel
|
|||||||
}
|
}
|
||||||
$schedule->job(new DatabaseBackupJob(
|
$schedule->job(new DatabaseBackupJob(
|
||||||
backup: $scheduled_backup
|
backup: $scheduled_backup
|
||||||
))->cron($scheduled_backup->frequency);
|
))->cron($scheduled_backup->frequency)->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Enums;
|
|||||||
|
|
||||||
enum ProxyTypes: string
|
enum ProxyTypes: string
|
||||||
{
|
{
|
||||||
|
case NONE = 'NONE';
|
||||||
case TRAEFIK_V2 = 'TRAEFIK_V2';
|
case TRAEFIK_V2 = 'TRAEFIK_V2';
|
||||||
case NGINX = 'NGINX';
|
case NGINX = 'NGINX';
|
||||||
case CADDY = 'CADDY';
|
case CADDY = 'CADDY';
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Exceptions;
|
|||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||||
use Sentry\Laravel\Integration;
|
use Sentry\Laravel\Integration;
|
||||||
|
use Sentry\State\Scope;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
class Handler extends ExceptionHandler
|
class Handler extends ExceptionHandler
|
||||||
@@ -48,6 +49,11 @@ class Handler extends ExceptionHandler
|
|||||||
if ($this->settings->do_not_track || isDev()) {
|
if ($this->settings->do_not_track || isDev()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
app('sentry')->configureScope(
|
||||||
|
function (Scope $scope){
|
||||||
|
$scope->setUser(['id'=> config('sentry.server_name')]);
|
||||||
|
}
|
||||||
|
);
|
||||||
Integration::captureUnhandledException($e);
|
Integration::captureUnhandledException($e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,15 +8,41 @@ use App\Models\S3Storage;
|
|||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\TeamInvitation;
|
use App\Models\TeamInvitation;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use Auth;
|
||||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||||
use Illuminate\Routing\Controller as BaseController;
|
use Illuminate\Routing\Controller as BaseController;
|
||||||
|
use Illuminate\Support\Facades\Crypt;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
use Str;
|
||||||
|
|
||||||
|
|
||||||
class Controller extends BaseController
|
class Controller extends BaseController
|
||||||
{
|
{
|
||||||
use AuthorizesRequests, ValidatesRequests;
|
use AuthorizesRequests, ValidatesRequests;
|
||||||
|
|
||||||
|
public function link()
|
||||||
|
{
|
||||||
|
$token = request()->get('token');
|
||||||
|
if ($token) {
|
||||||
|
$decrypted = Crypt::decryptString($token);
|
||||||
|
$email = Str::of($decrypted)->before('@@@');
|
||||||
|
$password = Str::of($decrypted)->after('@@@');
|
||||||
|
$user = User::whereEmail($email)->first();
|
||||||
|
if (!$user) {
|
||||||
|
return redirect()->route('login');
|
||||||
|
}
|
||||||
|
if (Hash::check($password, $user->password)) {
|
||||||
|
Auth::login($user);
|
||||||
|
$team = $user->teams()->first();
|
||||||
|
session(['currentTeam' => $team]);
|
||||||
|
return redirect()->route('dashboard');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return redirect()->route('login')->with('error', 'Invalid credentials.');
|
||||||
|
}
|
||||||
public function subscription()
|
public function subscription()
|
||||||
{
|
{
|
||||||
if (!isCloud()) {
|
if (!isCloud()) {
|
||||||
@@ -37,10 +63,12 @@ class Controller extends BaseController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function force_passoword_reset() {
|
public function force_passoword_reset()
|
||||||
|
{
|
||||||
return view('auth.force-password-reset');
|
return view('auth.force-password-reset');
|
||||||
}
|
}
|
||||||
public function boarding() {
|
public function boarding()
|
||||||
|
{
|
||||||
if (currentTeam()->boarding || isDev()) {
|
if (currentTeam()->boarding || isDev()) {
|
||||||
return view('boarding');
|
return view('boarding');
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -60,9 +60,6 @@ class ProjectController extends Controller
|
|||||||
'environment_name' => $environment->name,
|
'environment_name' => $environment->name,
|
||||||
'database_uuid' => $standalone_postgresql->uuid,
|
'database_uuid' => $standalone_postgresql->uuid,
|
||||||
]);
|
]);
|
||||||
}
|
|
||||||
if ($server) {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
return view('project.new', [
|
return view('project.new', [
|
||||||
'type' => $type
|
'type' => $type
|
||||||
|
|||||||
@@ -25,6 +25,15 @@ class Dashboard extends Component
|
|||||||
}
|
}
|
||||||
$this->projects = $projects->count();
|
$this->projects = $projects->count();
|
||||||
}
|
}
|
||||||
|
// public function getIptables()
|
||||||
|
// {
|
||||||
|
// $servers = Server::ownedByCurrentTeam()->get();
|
||||||
|
// foreach ($servers as $server) {
|
||||||
|
// checkRequiredCommands($server);
|
||||||
|
// $iptables = instant_remote_process(['docker run --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c "iptables -L -n | jc --iptables"'], $server);
|
||||||
|
// ray($iptables);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.dashboard');
|
return view('livewire.dashboard');
|
||||||
|
|||||||
@@ -18,22 +18,26 @@ class ForcePasswordReset extends Component
|
|||||||
'password' => 'required|min:8',
|
'password' => 'required|min:8',
|
||||||
'password_confirmation' => 'required|same:password',
|
'password_confirmation' => 'required|same:password',
|
||||||
];
|
];
|
||||||
public function mount() {
|
public function mount()
|
||||||
|
{
|
||||||
$this->email = auth()->user()->email;
|
$this->email = auth()->user()->email;
|
||||||
}
|
}
|
||||||
public function submit() {
|
public function submit()
|
||||||
|
{
|
||||||
try {
|
try {
|
||||||
$this->rateLimit(10);
|
$this->rateLimit(10);
|
||||||
$this->validate();
|
$this->validate();
|
||||||
|
$firstLogin = auth()->user()->created_at == auth()->user()->updated_at;
|
||||||
auth()->user()->forceFill([
|
auth()->user()->forceFill([
|
||||||
'password' => Hash::make($this->password),
|
'password' => Hash::make($this->password),
|
||||||
'force_password_reset' => false,
|
'force_password_reset' => false,
|
||||||
])->save();
|
])->save();
|
||||||
auth()->logout();
|
if ($firstLogin) {
|
||||||
return redirect()->route('login')->with('status', 'Your initial password has been set.');
|
send_internal_notification('First login for ' . auth()->user()->email);
|
||||||
} catch(\Exception $e) {
|
}
|
||||||
return general_error_handler(err:$e, that:$this);
|
return redirect()->route('dashboard');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return general_error_handler(err: $e, that: $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
54
app/Http/Livewire/Help.php
Normal file
54
app/Http/Livewire/Help.php
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire;
|
||||||
|
|
||||||
|
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
||||||
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Route;
|
||||||
|
|
||||||
|
class Help extends Component
|
||||||
|
{
|
||||||
|
use WithRateLimiting;
|
||||||
|
public string $description;
|
||||||
|
public string $subject;
|
||||||
|
public ?string $path = null;
|
||||||
|
protected $rules = [
|
||||||
|
'description' => 'required|min:10',
|
||||||
|
'subject' => 'required|min:3'
|
||||||
|
];
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->path = Route::current()->uri();
|
||||||
|
if (isDev()) {
|
||||||
|
$this->description = "I'm having trouble with {$this->path}";
|
||||||
|
$this->subject = "Help with {$this->path}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function submit()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->rateLimit(1, 60);
|
||||||
|
$this->validate();
|
||||||
|
$subscriptionType = auth()->user()?->subscription?->type() ?? 'unknown';
|
||||||
|
$debug = "Route: {$this->path}";
|
||||||
|
$mail = new MailMessage();
|
||||||
|
$mail->view(
|
||||||
|
'emails.help',
|
||||||
|
[
|
||||||
|
'description' => $this->description,
|
||||||
|
'debug' => $debug
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$mail->subject("[HELP - {$subscriptionType}]: {$this->subject}");
|
||||||
|
send_user_an_email($mail, 'hi@coollabs.io');
|
||||||
|
$this->emit('success', 'Your message has been sent successfully. We will get in touch with you as soon as possible.');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return general_error_handler($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.help')->layout('layouts.app');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,26 +8,30 @@ use Livewire\Component;
|
|||||||
|
|
||||||
class DiscordSettings extends Component
|
class DiscordSettings extends Component
|
||||||
{
|
{
|
||||||
public Team $model;
|
public Team $team;
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'model.discord_enabled' => 'nullable|boolean',
|
'team.discord_enabled' => 'nullable|boolean',
|
||||||
'model.discord_webhook_url' => 'required|url',
|
'team.discord_webhook_url' => 'required|url',
|
||||||
'model.discord_notifications_test' => 'nullable|boolean',
|
'team.discord_notifications_test' => 'nullable|boolean',
|
||||||
'model.discord_notifications_deployments' => 'nullable|boolean',
|
'team.discord_notifications_deployments' => 'nullable|boolean',
|
||||||
'model.discord_notifications_status_changes' => 'nullable|boolean',
|
'team.discord_notifications_status_changes' => 'nullable|boolean',
|
||||||
'model.discord_notifications_database_backups' => 'nullable|boolean',
|
'team.discord_notifications_database_backups' => 'nullable|boolean',
|
||||||
];
|
];
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
'model.discord_webhook_url' => 'Discord Webhook',
|
'team.discord_webhook_url' => 'Discord Webhook',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->team = auth()->user()->currentTeam();
|
||||||
|
}
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->submit();
|
$this->submit();
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
$this->model->discord_enabled = false;
|
$this->team->discord_enabled = false;
|
||||||
$this->validate();
|
$this->validate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,8 +45,8 @@ class DiscordSettings extends Component
|
|||||||
|
|
||||||
public function saveModel()
|
public function saveModel()
|
||||||
{
|
{
|
||||||
$this->model->save();
|
$this->team->save();
|
||||||
if (is_a($this->model, Team::class)) {
|
if (is_a($this->team, Team::class)) {
|
||||||
refreshSession();
|
refreshSession();
|
||||||
}
|
}
|
||||||
$this->emit('success', 'Settings saved.');
|
$this->emit('success', 'Settings saved.');
|
||||||
@@ -50,7 +54,7 @@ class DiscordSettings extends Component
|
|||||||
|
|
||||||
public function sendTestNotification()
|
public function sendTestNotification()
|
||||||
{
|
{
|
||||||
$this->model->notify(new Test);
|
$this->team->notify(new Test());
|
||||||
$this->emit('success', 'Test notification sent.');
|
$this->emit('success', 'Test notification sent.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ class EmailSettings extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
$this->team = auth()->user()->currentTeam();
|
||||||
['sharedEmailEnabled' => $this->sharedEmailEnabled] = $this->team->limits;
|
['sharedEmailEnabled' => $this->sharedEmailEnabled] = $this->team->limits;
|
||||||
$this->emails = auth()->user()->email;
|
$this->emails = auth()->user()->email;
|
||||||
}
|
}
|
||||||
@@ -106,7 +107,14 @@ class EmailSettings extends Component
|
|||||||
return general_error_handler($e, $this);
|
return general_error_handler($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public function saveModel()
|
||||||
|
{
|
||||||
|
$this->team->save();
|
||||||
|
if (is_a($this->team, Team::class)) {
|
||||||
|
refreshSession();
|
||||||
|
}
|
||||||
|
$this->emit('success', 'Settings saved.');
|
||||||
|
}
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|||||||
62
app/Http/Livewire/Notifications/TelegramSettings.php
Normal file
62
app/Http/Livewire/Notifications/TelegramSettings.php
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Notifications;
|
||||||
|
|
||||||
|
use App\Models\Team;
|
||||||
|
use App\Notifications\Test;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class TelegramSettings extends Component
|
||||||
|
{
|
||||||
|
public Team $team;
|
||||||
|
protected $rules = [
|
||||||
|
'team.telegram_enabled' => 'nullable|boolean',
|
||||||
|
'team.telegram_token' => 'required|string',
|
||||||
|
'team.telegram_chat_id' => 'required|string',
|
||||||
|
'team.telegram_notifications_test' => 'nullable|boolean',
|
||||||
|
'team.telegram_notifications_deployments' => 'nullable|boolean',
|
||||||
|
'team.telegram_notifications_status_changes' => 'nullable|boolean',
|
||||||
|
'team.telegram_notifications_database_backups' => 'nullable|boolean',
|
||||||
|
];
|
||||||
|
protected $validationAttributes = [
|
||||||
|
'team.telegram_token' => 'Token',
|
||||||
|
'team.telegram_chat_id' => 'Chat ID',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->team = auth()->user()->currentTeam();
|
||||||
|
}
|
||||||
|
public function instantSave()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->submit();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
ray($e->getMessage());
|
||||||
|
$this->team->telegram_enabled = false;
|
||||||
|
$this->validate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function submit()
|
||||||
|
{
|
||||||
|
$this->resetErrorBag();
|
||||||
|
$this->validate();
|
||||||
|
$this->saveModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveModel()
|
||||||
|
{
|
||||||
|
$this->team->save();
|
||||||
|
if (is_a($this->team, Team::class)) {
|
||||||
|
refreshSession();
|
||||||
|
}
|
||||||
|
$this->emit('success', 'Settings saved.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sendTestNotification()
|
||||||
|
{
|
||||||
|
$this->team->notify(new Test());
|
||||||
|
$this->emit('success', 'Test notification sent.');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,7 +27,7 @@ class Change extends Component
|
|||||||
if ($this->private_key->isEmpty()) {
|
if ($this->private_key->isEmpty()) {
|
||||||
$this->private_key->delete();
|
$this->private_key->delete();
|
||||||
currentTeam()->privateKeys = PrivateKey::where('team_id', currentTeam()->id)->get();
|
currentTeam()->privateKeys = PrivateKey::where('team_id', currentTeam()->id)->get();
|
||||||
return redirect()->route('private-key.all');
|
return redirect()->route('security.private-key.index');
|
||||||
}
|
}
|
||||||
$this->emit('error', 'This private key is in use and cannot be deleted. Please delete all servers, applications, and GitHub/GitLab apps that use this private key before deleting it.');
|
$this->emit('error', 'This private key is in use and cannot be deleted. Please delete all servers, applications, and GitHub/GitLab apps that use this private key before deleting it.');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|||||||
@@ -42,8 +42,13 @@ class Heading extends Component
|
|||||||
["docker rm -f {$this->database->uuid}"],
|
["docker rm -f {$this->database->uuid}"],
|
||||||
$this->database->destination->server
|
$this->database->destination->server
|
||||||
);
|
);
|
||||||
|
if ($this->database->is_public) {
|
||||||
|
stopPostgresProxy($this->database);
|
||||||
|
$this->database->is_public = false;
|
||||||
|
}
|
||||||
$this->database->status = 'stopped';
|
$this->database->status = 'stopped';
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
|
$this->emit('refresh');
|
||||||
// $this->database->environment->project->team->notify(new StatusChanged($this->database));
|
// $this->database->environment->project->team->notify(new StatusChanged($this->database));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class General extends Component
|
|||||||
public StandalonePostgresql $database;
|
public StandalonePostgresql $database;
|
||||||
public string $new_filename;
|
public string $new_filename;
|
||||||
public string $new_content;
|
public string $new_content;
|
||||||
|
public string $db_url;
|
||||||
|
|
||||||
protected $listeners = ['refresh', 'save_init_script', 'delete_init_script'];
|
protected $listeners = ['refresh', 'save_init_script', 'delete_init_script'];
|
||||||
|
|
||||||
@@ -26,6 +27,8 @@ class General extends Component
|
|||||||
'database.init_scripts' => 'nullable',
|
'database.init_scripts' => 'nullable',
|
||||||
'database.image' => 'required',
|
'database.image' => 'required',
|
||||||
'database.ports_mappings' => 'nullable',
|
'database.ports_mappings' => 'nullable',
|
||||||
|
'database.is_public' => 'nullable|boolean',
|
||||||
|
'database.public_port' => 'nullable|integer',
|
||||||
];
|
];
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
'database.name' => 'Name',
|
'database.name' => 'Name',
|
||||||
@@ -38,8 +41,43 @@ class General extends Component
|
|||||||
'database.init_scripts' => 'Init Scripts',
|
'database.init_scripts' => 'Init Scripts',
|
||||||
'database.image' => 'Image',
|
'database.image' => 'Image',
|
||||||
'database.ports_mappings' => 'Port Mapping',
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
|
'database.is_public' => 'Is Public',
|
||||||
|
'database.public_port' => 'Public Port',
|
||||||
];
|
];
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->getDbUrl();
|
||||||
|
}
|
||||||
|
public function getDbUrl() {
|
||||||
|
if ($this->database->is_public) {
|
||||||
|
$this->db_url = "postgres://{$this->database->postgres_user}:{$this->database->postgres_password}@{$this->database->destination->server->ip}:{$this->database->public_port}/{$this->database->postgres_db}";
|
||||||
|
} else {
|
||||||
|
$this->db_url = "postgres://{$this->database->postgres_user}:{$this->database->postgres_password}@{$this->database->uuid}:5432/{$this->database->postgres_db}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function instantSave()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($this->database->is_public && !$this->database->public_port) {
|
||||||
|
$this->emit('error', 'Public port is required.');
|
||||||
|
$this->database->is_public = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($this->database->is_public) {
|
||||||
|
startPostgresProxy($this->database);
|
||||||
|
$this->emit('success', 'Database is now publicly accessible.');
|
||||||
|
} else {
|
||||||
|
stopPostgresProxy($this->database);
|
||||||
|
$this->emit('success', 'Database is no longer publicly accessible.');
|
||||||
|
}
|
||||||
|
$this->getDbUrl();
|
||||||
|
$this->database->save();
|
||||||
|
} catch(Exception $e) {
|
||||||
|
$this->database->is_public = !$this->database->is_public;
|
||||||
|
return general_error_handler(err: $e, that: $this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
public function save_init_script($script)
|
public function save_init_script($script)
|
||||||
{
|
{
|
||||||
$this->database->init_scripts = filter($this->database->init_scripts, fn ($s) => $s['filename'] !== $script['filename']);
|
$this->database->init_scripts = filter($this->database->init_scripts, fn ($s) => $s['filename'] !== $script['filename']);
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class PublicGitRepository extends Component
|
|||||||
'publish_directory' => 'publish directory',
|
'publish_directory' => 'publish directory',
|
||||||
];
|
];
|
||||||
private object $repository_url_parsed;
|
private object $repository_url_parsed;
|
||||||
private GithubApp|GitlabApp $git_source;
|
private GithubApp|GitlabApp|null $git_source = null;
|
||||||
private string $git_host;
|
private string $git_host;
|
||||||
private string $git_repository;
|
private string $git_repository;
|
||||||
|
|
||||||
@@ -67,18 +67,17 @@ class PublicGitRepository extends Component
|
|||||||
|
|
||||||
public function load_branch()
|
public function load_branch()
|
||||||
{
|
{
|
||||||
$this->branch_found = false;
|
try {
|
||||||
|
$this->branch_found = false;
|
||||||
$this->validate([
|
$this->validate([
|
||||||
'repository_url' => 'required|url'
|
'repository_url' => 'required|url'
|
||||||
]);
|
]);
|
||||||
$this->get_git_source();
|
$this->get_git_source();
|
||||||
try {
|
$this->get_branch();
|
||||||
$this->get_branch();
|
$this->selected_branch = $this->git_branch;
|
||||||
$this->selected_branch = $this->git_branch;
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return general_error_handler(err: $e, that: $this);
|
return general_error_handler(err: $e, that: $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->branch_found && $this->git_branch == 'main') {
|
if (!$this->branch_found && $this->git_branch == 'main') {
|
||||||
try {
|
try {
|
||||||
$this->git_branch = 'master';
|
$this->git_branch = 'master';
|
||||||
@@ -103,6 +102,9 @@ class PublicGitRepository extends Component
|
|||||||
} elseif ($this->git_host == 'bitbucket.org') {
|
} elseif ($this->git_host == 'bitbucket.org') {
|
||||||
// Not supported yet
|
// Not supported yet
|
||||||
}
|
}
|
||||||
|
if (is_null($this->git_source)) {
|
||||||
|
throw new \Exception('Git source not found. What?!');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function get_branch()
|
private function get_branch()
|
||||||
|
|||||||
@@ -22,34 +22,52 @@ class Select extends Component
|
|||||||
public Collection|array $swarmDockers = [];
|
public Collection|array $swarmDockers = [];
|
||||||
public array $parameters;
|
public array $parameters;
|
||||||
|
|
||||||
|
public ?string $existingPostgresqlUrl = null;
|
||||||
|
|
||||||
protected $queryString = [
|
protected $queryString = [
|
||||||
'server',
|
'server',
|
||||||
];
|
];
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->parameters = get_route_parameters();
|
$this->parameters = get_route_parameters();
|
||||||
|
if (isDev()) {
|
||||||
|
$this->existingPostgresqlUrl = 'postgres://coolify:password@coolify-db:5432';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_type(string $type)
|
// public function addExistingPostgresql()
|
||||||
|
// {
|
||||||
|
// try {
|
||||||
|
// instantCommand("psql {$this->existingPostgresqlUrl} -c 'SELECT 1'");
|
||||||
|
// $this->emit('success', 'Successfully connected to the database.');
|
||||||
|
// } catch (\Exception $e) {
|
||||||
|
// return general_error_handler($e, $this);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
public function setType(string $type)
|
||||||
{
|
{
|
||||||
$this->type = $type;
|
$this->type = $type;
|
||||||
|
if ($type === "existing-postgresql") {
|
||||||
|
$this->current_step = $type;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (count($this->servers) === 1) {
|
if (count($this->servers) === 1) {
|
||||||
$server = $this->servers->first();
|
$server = $this->servers->first();
|
||||||
$this->set_server($server);
|
$this->setServer($server);
|
||||||
if (count($server->destinations()) === 1) {
|
if (count($server->destinations()) === 1) {
|
||||||
$this->set_destination($server->destinations()->first()->uuid);
|
$this->setDestination($server->destinations()->first()->uuid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!is_null($this->server)) {
|
if (!is_null($this->server)) {
|
||||||
$foundServer = $this->servers->where('id', $this->server)->first();
|
$foundServer = $this->servers->where('id', $this->server)->first();
|
||||||
if ($foundServer) {
|
if ($foundServer) {
|
||||||
return $this->set_server($foundServer);
|
return $this->setServer($foundServer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->current_step = 'servers';
|
$this->current_step = 'servers';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_server(Server $server)
|
public function setServer(Server $server)
|
||||||
{
|
{
|
||||||
$this->server_id = $server->id;
|
$this->server_id = $server->id;
|
||||||
$this->standaloneDockers = $server->standaloneDockers;
|
$this->standaloneDockers = $server->standaloneDockers;
|
||||||
@@ -57,7 +75,7 @@ class Select extends Component
|
|||||||
$this->current_step = 'destinations';
|
$this->current_step = 'destinations';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_destination(string $destination_uuid)
|
public function setDestination(string $destination_uuid)
|
||||||
{
|
{
|
||||||
$this->destination_uuid = $destination_uuid;
|
$this->destination_uuid = $destination_uuid;
|
||||||
redirect()->route('project.resources.new', [
|
redirect()->route('project.resources.new', [
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Http\Livewire\Server\New;
|
namespace App\Http\Livewire\Server\New;
|
||||||
|
|
||||||
|
use App\Enums\ProxyStatus;
|
||||||
|
use App\Enums\ProxyTypes;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
@@ -67,6 +69,11 @@ class ByIp extends Component
|
|||||||
'port' => $this->port,
|
'port' => $this->port,
|
||||||
'team_id' => currentTeam()->id,
|
'team_id' => currentTeam()->id,
|
||||||
'private_key_id' => $this->private_key_id,
|
'private_key_id' => $this->private_key_id,
|
||||||
|
'proxy' => [
|
||||||
|
"type" => ProxyTypes::TRAEFIK_V2->value,
|
||||||
|
"status" => ProxyStatus::EXITED->value,
|
||||||
|
]
|
||||||
|
|
||||||
]);
|
]);
|
||||||
$server->settings->is_part_of_swarm = $this->is_part_of_swarm;
|
$server->settings->is_part_of_swarm = $this->is_part_of_swarm;
|
||||||
$server->settings->save();
|
$server->settings->save();
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class Proxy extends Component
|
|||||||
{
|
{
|
||||||
public Server $server;
|
public Server $server;
|
||||||
|
|
||||||
public ProxyTypes $selectedProxy = ProxyTypes::TRAEFIK_V2;
|
public ?string $selectedProxy = null;
|
||||||
public $proxy_settings = null;
|
public $proxy_settings = null;
|
||||||
public string|null $redirect_url = null;
|
public string|null $redirect_url = null;
|
||||||
|
|
||||||
@@ -20,6 +20,7 @@ class Proxy extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
$this->selectedProxy = $this->server->proxy->type;
|
||||||
$this->redirect_url = $this->server->proxy->redirect_url;
|
$this->redirect_url = $this->server->proxy->redirect_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,11 +36,12 @@ class Proxy extends Component
|
|||||||
$this->emit('proxyStatusUpdated');
|
$this->emit('proxyStatusUpdated');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function select_proxy(ProxyTypes $proxy_type)
|
public function select_proxy($proxy_type)
|
||||||
{
|
{
|
||||||
$this->server->proxy->type = $proxy_type;
|
$this->server->proxy->type = $proxy_type;
|
||||||
$this->server->proxy->status = 'exited';
|
$this->server->proxy->status = 'exited';
|
||||||
$this->server->save();
|
$this->server->save();
|
||||||
|
$this->selectedProxy = $this->server->proxy->type;
|
||||||
$this->emit('proxyStatusUpdated');
|
$this->emit('proxyStatusUpdated');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,16 +32,19 @@ class Create extends Component
|
|||||||
"custom_port" => 'required|int',
|
"custom_port" => 'required|int',
|
||||||
"is_system_wide" => 'required|bool',
|
"is_system_wide" => 'required|bool',
|
||||||
]);
|
]);
|
||||||
$github_app = GithubApp::create([
|
$payload = [
|
||||||
'name' => $this->name,
|
'name' => $this->name,
|
||||||
'organization' => $this->organization,
|
'organization' => $this->organization,
|
||||||
'api_url' => $this->api_url,
|
'api_url' => $this->api_url,
|
||||||
'html_url' => $this->html_url,
|
'html_url' => $this->html_url,
|
||||||
'custom_user' => $this->custom_user,
|
'custom_user' => $this->custom_user,
|
||||||
'custom_port' => $this->custom_port,
|
'custom_port' => $this->custom_port,
|
||||||
'is_system_wide' => $this->is_system_wide,
|
|
||||||
'team_id' => currentTeam()->id,
|
'team_id' => currentTeam()->id,
|
||||||
]);
|
];
|
||||||
|
if (isCloud()) {
|
||||||
|
$payload['is_system_wide'] = $this->is_system_wide;
|
||||||
|
}
|
||||||
|
$github_app = GithubApp::create($payload);
|
||||||
if (session('from')) {
|
if (session('from')) {
|
||||||
session(['from' => session('from') + ['source_id' => $github_app->id]]);
|
session(['from' => session('from') + ['source_id' => $github_app->id]]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ class PricingPlans extends Component
|
|||||||
'tax_id_collection' => [
|
'tax_id_collection' => [
|
||||||
'enabled' => true,
|
'enabled' => true,
|
||||||
],
|
],
|
||||||
|
'automatic_tax' => [
|
||||||
|
'enabled' => true,
|
||||||
|
],
|
||||||
'mode' => 'subscription',
|
'mode' => 'subscription',
|
||||||
'success_url' => route('dashboard', ['success' => true]),
|
'success_url' => route('dashboard', ['success' => true]),
|
||||||
'cancel_url' => route('subscription.index', ['cancelled' => true]),
|
'cancel_url' => route('subscription.index', ['cancelled' => true]),
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ class CheckForcePasswordReset
|
|||||||
public function handle(Request $request, Closure $next): Response
|
public function handle(Request $request, Closure $next): Response
|
||||||
{
|
{
|
||||||
if (auth()->user()) {
|
if (auth()->user()) {
|
||||||
|
if ($request->path() === 'auth/link') {
|
||||||
|
auth()->logout();
|
||||||
|
request()->session()->invalidate();
|
||||||
|
request()->session()->regenerateToken();
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
$force_password_reset = auth()->user()->force_password_reset;
|
$force_password_reset = auth()->user()->force_password_reset;
|
||||||
if ($force_password_reset) {
|
if ($force_password_reset) {
|
||||||
if ($request->routeIs('auth.force-password-reset') || $request->path() === 'livewire/message/force-password-reset') {
|
if ($request->routeIs('auth.force-password-reset') || $request->path() === 'livewire/message/force-password-reset') {
|
||||||
|
|||||||
@@ -66,12 +66,7 @@ class ApplicationDeploymentJob implements ShouldQueue
|
|||||||
private $log_model;
|
private $log_model;
|
||||||
private Collection $saved_outputs;
|
private Collection $saved_outputs;
|
||||||
|
|
||||||
public function middleware(): array
|
public $tries = 1;
|
||||||
{
|
|
||||||
return [
|
|
||||||
(new WithoutOverlapping("dockerimagejobs"))->shared(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
public function __construct(int $application_deployment_queue_id)
|
public function __construct(int $application_deployment_queue_id)
|
||||||
{
|
{
|
||||||
ray()->clearScreen();
|
ray()->clearScreen();
|
||||||
@@ -181,8 +176,8 @@ class ApplicationDeploymentJob implements ShouldQueue
|
|||||||
$this->execute_in_builder("echo '$dockerfile_base64' | base64 -d > $this->workdir/Dockerfile")
|
$this->execute_in_builder("echo '$dockerfile_base64' | base64 -d > $this->workdir/Dockerfile")
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
$this->build_image_name = "{$this->application->git_repository}:build";
|
$this->build_image_name = Str::lower("{$this->application->git_repository}:build");
|
||||||
$this->production_image_name = "{$this->application->uuid}:latest";
|
$this->production_image_name = Str::lower("{$this->application->uuid}:latest");
|
||||||
ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
|
ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
|
||||||
$this->generate_compose_file();
|
$this->generate_compose_file();
|
||||||
$this->generate_build_env_variables();
|
$this->generate_build_env_variables();
|
||||||
@@ -206,8 +201,8 @@ class ApplicationDeploymentJob implements ShouldQueue
|
|||||||
$tag = $tag->substr(0, 128);
|
$tag = $tag->substr(0, 128);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->build_image_name = "{$this->application->git_repository}:{$tag}-build";
|
$this->build_image_name = Str::lower("{$this->application->git_repository}:{$tag}-build");
|
||||||
$this->production_image_name = "{$this->application->uuid}:{$tag}";
|
$this->production_image_name = Str::lower("{$this->application->uuid}:{$tag}");
|
||||||
ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
|
ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
|
||||||
|
|
||||||
if (!$this->force_rebuild) {
|
if (!$this->force_rebuild) {
|
||||||
@@ -242,7 +237,7 @@ class ApplicationDeploymentJob implements ShouldQueue
|
|||||||
}
|
}
|
||||||
private function health_check()
|
private function health_check()
|
||||||
{
|
{
|
||||||
ray('New container name: ',$this->container_name);
|
ray('New container name: ', $this->container_name);
|
||||||
if ($this->container_name) {
|
if ($this->container_name) {
|
||||||
$counter = 0;
|
$counter = 0;
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
@@ -264,7 +259,7 @@ class ApplicationDeploymentJob implements ShouldQueue
|
|||||||
);
|
);
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"echo 'New application version health check status: {$this->saved_outputs->get('health_check')}'"
|
"echo 'New version health check status: {$this->saved_outputs->get('health_check')}'"
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
if (Str::of($this->saved_outputs->get('health_check'))->contains('healthy')) {
|
if (Str::of($this->saved_outputs->get('health_check'))->contains('healthy')) {
|
||||||
@@ -282,8 +277,8 @@ class ApplicationDeploymentJob implements ShouldQueue
|
|||||||
}
|
}
|
||||||
private function deploy_pull_request()
|
private function deploy_pull_request()
|
||||||
{
|
{
|
||||||
$this->build_image_name = "{$this->application->uuid}:pr-{$this->pull_request_id}-build";
|
$this->build_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}-build");
|
||||||
$this->production_image_name = "{$this->application->uuid}:pr-{$this->pull_request_id}";
|
$this->production_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}");
|
||||||
ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
|
ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
"echo 'Starting pull request (#{$this->pull_request_id}) deployment of {$this->application->git_repository}:{$this->application->git_branch}.'",
|
"echo 'Starting pull request (#{$this->pull_request_id}) deployment of {$this->application->git_repository}:{$this->application->git_branch}.'",
|
||||||
@@ -304,12 +299,19 @@ class ApplicationDeploymentJob implements ShouldQueue
|
|||||||
|
|
||||||
private function prepare_builder_image()
|
private function prepare_builder_image()
|
||||||
{
|
{
|
||||||
|
$pull = "--pull=always";
|
||||||
|
if (isDev()) {
|
||||||
|
$pull = "--pull=never";
|
||||||
|
}
|
||||||
|
$helperImage = config('coolify.helper_image');
|
||||||
|
$runCommand = "docker run {$pull} -d --network {$this->destination->network} -v /:/host --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
|
||||||
|
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"echo -n 'Pulling latest version of the builder image (ghcr.io/coollabsio/coolify-helper).'",
|
"echo -n 'Pulling helper image from $helperImage.'",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"docker run --pull=always -d --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock ghcr.io/coollabsio/coolify-helper",
|
$runCommand,
|
||||||
"hidden" => true,
|
"hidden" => true,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@@ -494,7 +496,7 @@ class ApplicationDeploymentJob implements ShouldQueue
|
|||||||
],
|
],
|
||||||
'networks' => [
|
'networks' => [
|
||||||
$this->destination->network => [
|
$this->destination->network => [
|
||||||
'external' => false,
|
'external' => true,
|
||||||
'name' => $this->destination->network,
|
'name' => $this->destination->network,
|
||||||
'attachable' => true,
|
'attachable' => true,
|
||||||
]
|
]
|
||||||
@@ -654,12 +656,12 @@ class ApplicationDeploymentJob implements ShouldQueue
|
|||||||
private function build_image()
|
private function build_image()
|
||||||
{
|
{
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
"echo -n 'Building docker image.'",
|
"echo -n 'Building docker image for your application.'",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($this->application->settings->is_static) {
|
if ($this->application->settings->is_static) {
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
$this->execute_in_builder("docker build -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true
|
$this->execute_in_builder("docker build --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$dockerfile = base64_encode("FROM {$this->application->static_image}
|
$dockerfile = base64_encode("FROM {$this->application->static_image}
|
||||||
@@ -692,12 +694,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
$this->execute_in_builder("echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf")
|
$this->execute_in_builder("echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf")
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
$this->execute_in_builder("docker build -f {$this->workdir}/Dockerfile-prod {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
$this->execute_in_builder("docker build --network host -f {$this->workdir}/Dockerfile-prod {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
$this->execute_in_builder("docker build -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
$this->execute_in_builder("docker build --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -706,7 +708,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
{
|
{
|
||||||
if ($this->currently_running_container_name) {
|
if ($this->currently_running_container_name) {
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
["echo -n 'Removing old application version.'"],
|
["echo -n 'Removing old version of your application.'"],
|
||||||
[$this->execute_in_builder("docker rm -f $this->currently_running_container_name >/dev/null 2>&1"), "hidden" => true],
|
[$this->execute_in_builder("docker rm -f $this->currently_running_container_name >/dev/null 2>&1"), "hidden" => true],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class DatabaseBackupJob implements ShouldQueue
|
|||||||
$ip = Str::slug($this->server->ip);
|
$ip = Str::slug($this->server->ip);
|
||||||
$this->backup_dir = backup_dir() . "/coolify" . "/coolify-db-$ip";
|
$this->backup_dir = backup_dir() . "/coolify" . "/coolify-db-$ip";
|
||||||
}
|
}
|
||||||
$this->backup_file = "/dumpall-" . Carbon::now()->timestamp . ".sql";
|
$this->backup_file = "/pg_dump-" . Carbon::now()->timestamp . ".dump";
|
||||||
$this->backup_location = $this->backup_dir . $this->backup_file;
|
$this->backup_location = $this->backup_dir . $this->backup_file;
|
||||||
|
|
||||||
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
||||||
@@ -107,7 +107,7 @@ class DatabaseBackupJob implements ShouldQueue
|
|||||||
try {
|
try {
|
||||||
ray($this->backup_dir);
|
ray($this->backup_dir);
|
||||||
$commands[] = "mkdir -p " . $this->backup_dir;
|
$commands[] = "mkdir -p " . $this->backup_dir;
|
||||||
$commands[] = "docker exec $this->container_name pg_dumpall -U {$this->database->postgres_user} > $this->backup_location";
|
$commands[] = "docker exec $this->container_name pg_dump -Fc -U {$this->database->postgres_user} > $this->backup_location";
|
||||||
|
|
||||||
$this->backup_output = instant_remote_process($commands, $this->server);
|
$this->backup_output = instant_remote_process($commands, $this->server);
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
@@ -9,7 +10,6 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
|||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
|
|
||||||
class DockerCleanupJob implements ShouldQueue
|
class DockerCleanupJob implements ShouldQueue
|
||||||
{
|
{
|
||||||
@@ -30,6 +30,11 @@ class DockerCleanupJob implements ShouldQueue
|
|||||||
}
|
}
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
|
$queue = ApplicationDeploymentQueue::where('status', '==', 'in_progress')->get();
|
||||||
|
if ($queue->count() > 0) {
|
||||||
|
ray('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping')->color('orange');
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
ray()->showQueries()->color('orange');
|
ray()->showQueries()->color('orange');
|
||||||
$servers = Server::all();
|
$servers = Server::all();
|
||||||
|
|||||||
72
app/Jobs/SendMessageToTelegramJob.php
Normal file
72
app/Jobs/SendMessageToTelegramJob.php
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Str;
|
||||||
|
|
||||||
|
class SendMessageToTelegramJob implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of times the job may be attempted.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $tries = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of unhandled exceptions to allow before failing.
|
||||||
|
*/
|
||||||
|
public int $maxExceptions = 3;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public string $text,
|
||||||
|
public array $buttons,
|
||||||
|
public string $token,
|
||||||
|
public string $chatId,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$url = 'https://api.telegram.org/bot' . $this->token . '/sendMessage';
|
||||||
|
$inlineButtons = [];
|
||||||
|
if (!empty($this->buttons)) {
|
||||||
|
foreach ($this->buttons as $button) {
|
||||||
|
$buttonUrl = data_get($button, 'url');
|
||||||
|
if ($buttonUrl && Str::contains($buttonUrl, 'http://localhost')) {
|
||||||
|
$buttonUrl = str_replace('http://localhost', config('app.url'), $buttonUrl);
|
||||||
|
}
|
||||||
|
$inlineButtons[] = [
|
||||||
|
'text' => $button['text'],
|
||||||
|
'url' => $buttonUrl,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$payload = [
|
||||||
|
'parse_mode' => 'markdown',
|
||||||
|
'reply_markup' => json_encode([
|
||||||
|
'inline_keyboard' => [
|
||||||
|
[...$inlineButtons],
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
'chat_id' => $this->chatId,
|
||||||
|
'text' => $this->text,
|
||||||
|
];
|
||||||
|
ray($payload);
|
||||||
|
$response = Http::post($url, $payload);
|
||||||
|
if ($response->failed()) {
|
||||||
|
throw new \Exception('Telegram notification failed with ' . $response->status() . ' status code.' . $response->body());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,15 +4,7 @@ namespace App\Models;
|
|||||||
|
|
||||||
class ApplicationPreview extends BaseModel
|
class ApplicationPreview extends BaseModel
|
||||||
{
|
{
|
||||||
protected $fillable = [
|
protected $guarded = [];
|
||||||
'uuid',
|
|
||||||
'pull_request_id',
|
|
||||||
'pull_request_html_url',
|
|
||||||
'pull_request_issue_comment_id',
|
|
||||||
'fqdn',
|
|
||||||
'status',
|
|
||||||
'application_id',
|
|
||||||
];
|
|
||||||
|
|
||||||
static function findPreviewByApplicationAndPullId(int $application_id, int $pull_request_id)
|
static function findPreviewByApplicationAndPullId(int $application_id, int $pull_request_id)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,6 +24,14 @@ class Team extends Model implements SendsDiscord, SendsEmail
|
|||||||
return data_get($this, 'discord_webhook_url', null);
|
return data_get($this, 'discord_webhook_url', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function routeNotificationForTelegram()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"token" => data_get($this, 'telegram_token', null),
|
||||||
|
"chat_id" => data_get($this, 'telegram_chat_id', null)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public function getRecepients($notification)
|
public function getRecepients($notification)
|
||||||
{
|
{
|
||||||
$recipients = data_get($notification, 'emails', null);
|
$recipients = data_get($notification, 'emails', null);
|
||||||
|
|||||||
@@ -18,15 +18,15 @@ class DeploymentFailed extends Notification implements ShouldQueue
|
|||||||
|
|
||||||
public Application $application;
|
public Application $application;
|
||||||
public string $deployment_uuid;
|
public string $deployment_uuid;
|
||||||
public ApplicationPreview|null $preview;
|
public ?ApplicationPreview $preview = null;
|
||||||
|
|
||||||
public string $application_name;
|
public string $application_name;
|
||||||
public string|null $deployment_url = null;
|
public ?string $deployment_url = null;
|
||||||
public string $project_uuid;
|
public string $project_uuid;
|
||||||
public string $environment_name;
|
public string $environment_name;
|
||||||
public string|null $fqdn;
|
public ?string $fqdn = null;
|
||||||
|
|
||||||
public function __construct(Application $application, string $deployment_uuid, ApplicationPreview|null $preview)
|
public function __construct(Application $application, string $deployment_uuid, ?ApplicationPreview $preview = null)
|
||||||
{
|
{
|
||||||
$this->application = $application;
|
$this->application = $application;
|
||||||
$this->deployment_uuid = $deployment_uuid;
|
$this->deployment_uuid = $deployment_uuid;
|
||||||
@@ -43,19 +43,7 @@ class DeploymentFailed extends Notification implements ShouldQueue
|
|||||||
|
|
||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
{
|
{
|
||||||
$channels = [];
|
return setNotificationChannels($notifiable, 'deployments');
|
||||||
$isEmailEnabled = isEmailEnabled($notifiable);
|
|
||||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
|
||||||
$isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_deployments');
|
|
||||||
$isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_deployments');
|
|
||||||
|
|
||||||
if ($isEmailEnabled && $isSubscribedToEmailEvent) {
|
|
||||||
$channels[] = EmailChannel::class;
|
|
||||||
}
|
|
||||||
if ($isDiscordEnabled && $isSubscribedToDiscordEvent) {
|
|
||||||
$channels[] = DiscordChannel::class;
|
|
||||||
}
|
|
||||||
return $channels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toMail(): MailMessage
|
public function toMail(): MailMessage
|
||||||
@@ -67,9 +55,8 @@ class DeploymentFailed extends Notification implements ShouldQueue
|
|||||||
$mail->subject('❌ Deployment failed of ' . $this->application_name . '.');
|
$mail->subject('❌ Deployment failed of ' . $this->application_name . '.');
|
||||||
} else {
|
} else {
|
||||||
$fqdn = $this->preview->fqdn;
|
$fqdn = $this->preview->fqdn;
|
||||||
$mail->subject('❌ Pull request #' . $this->preview->pull_request_id . ' of ' . $this->application_name . ' deployment failed.');
|
$mail->subject('❌ Deployment failed of pull request #' . $this->preview->pull_request_id . ' of ' . $this->application_name . '.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$mail->view('emails.application-deployment-failed', [
|
$mail->view('emails.application-deployment-failed', [
|
||||||
'name' => $this->application_name,
|
'name' => $this->application_name,
|
||||||
'fqdn' => $fqdn,
|
'fqdn' => $fqdn,
|
||||||
@@ -90,4 +77,19 @@ class DeploymentFailed extends Notification implements ShouldQueue
|
|||||||
}
|
}
|
||||||
return $message;
|
return $message;
|
||||||
}
|
}
|
||||||
|
public function toTelegram(): array
|
||||||
|
{
|
||||||
|
if ($this->preview) {
|
||||||
|
$message = '❌ Pull request #' . $this->preview->pull_request_id . ' of **' . $this->application_name . '** (' . $this->preview->fqdn . ') deployment failed: ';
|
||||||
|
} else {
|
||||||
|
$message = '❌ Deployment failed of **' . $this->application_name . '** (' . $this->fqdn . '): ';
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
"message" => $message,
|
||||||
|
"buttons" => [
|
||||||
|
"text" => "View Deployment Logs",
|
||||||
|
"url" => $this->deployment_url
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,19 +43,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue
|
|||||||
|
|
||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
{
|
{
|
||||||
$channels = [];
|
return setNotificationChannels($notifiable, 'deployments');
|
||||||
$isEmailEnabled = data_get($notifiable, 'smtp_enabled');
|
|
||||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
|
||||||
$isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_deployments');
|
|
||||||
$isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_deployments');
|
|
||||||
|
|
||||||
if ($isEmailEnabled && $isSubscribedToEmailEvent) {
|
|
||||||
$channels[] = EmailChannel::class;
|
|
||||||
}
|
|
||||||
if ($isDiscordEnabled && $isSubscribedToDiscordEvent) {
|
|
||||||
$channels[] = DiscordChannel::class;
|
|
||||||
}
|
|
||||||
return $channels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toMail(): MailMessage
|
public function toMail(): MailMessage
|
||||||
@@ -99,4 +87,34 @@ class DeploymentSuccess extends Notification implements ShouldQueue
|
|||||||
}
|
}
|
||||||
return $message;
|
return $message;
|
||||||
}
|
}
|
||||||
|
public function toTelegram(): array
|
||||||
|
{
|
||||||
|
if ($this->preview) {
|
||||||
|
$message = '✅ New PR' . $this->preview->pull_request_id . ' version successfully deployed of ' . $this->application_name . '';
|
||||||
|
if ($this->preview->fqdn) {
|
||||||
|
$buttons[] = [
|
||||||
|
"text" => "Open Application",
|
||||||
|
"url" => $this->preview->fqdn
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$message = '✅ New version successfully deployed of ' . $this->application_name . '';
|
||||||
|
if ($this->fqdn) {
|
||||||
|
$buttons[] = [
|
||||||
|
"text" => "Open Application",
|
||||||
|
"url" => $this->fqdn
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$buttons[] = [
|
||||||
|
"text" => "Deployment logs",
|
||||||
|
"url" => $this->deployment_url
|
||||||
|
];
|
||||||
|
return [
|
||||||
|
"message" => $message,
|
||||||
|
"buttons" => [
|
||||||
|
...$buttons
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,19 +37,7 @@ class StatusChanged extends Notification implements ShouldQueue
|
|||||||
|
|
||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
{
|
{
|
||||||
$channels = [];
|
return setNotificationChannels($notifiable, 'status_changes');
|
||||||
$isEmailEnabled = data_get($notifiable, 'smtp_enabled');
|
|
||||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
|
||||||
$isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_status_changes');
|
|
||||||
$isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_status_changes');
|
|
||||||
|
|
||||||
if ($isEmailEnabled && $isSubscribedToEmailEvent) {
|
|
||||||
$channels[] = EmailChannel::class;
|
|
||||||
}
|
|
||||||
if ($isDiscordEnabled && $isSubscribedToDiscordEvent) {
|
|
||||||
$channels[] = DiscordChannel::class;
|
|
||||||
}
|
|
||||||
return $channels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toMail(): MailMessage
|
public function toMail(): MailMessage
|
||||||
@@ -70,7 +58,20 @@ class StatusChanged extends Notification implements ShouldQueue
|
|||||||
$message = '⛔ ' . $this->application_name . ' has been stopped.
|
$message = '⛔ ' . $this->application_name . ' has been stopped.
|
||||||
|
|
||||||
';
|
';
|
||||||
$message .= '[Application URL](' . $this->application_url . ')';
|
$message .= '[Open Application in Coolify](' . $this->application_url . ')';
|
||||||
return $message;
|
return $message;
|
||||||
}
|
}
|
||||||
|
public function toTelegram(): array
|
||||||
|
{
|
||||||
|
$message = '⛔ ' . $this->application_name . ' has been stopped.';
|
||||||
|
return [
|
||||||
|
"message" => $message,
|
||||||
|
"buttons" => [
|
||||||
|
[
|
||||||
|
"text" => "Open Application in Coolify",
|
||||||
|
"url" => $this->application_url
|
||||||
|
]
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ use Illuminate\Support\Facades\Mail;
|
|||||||
|
|
||||||
class EmailChannel
|
class EmailChannel
|
||||||
{
|
{
|
||||||
private bool $isResend = false;
|
|
||||||
public function send(SendsEmail $notifiable, Notification $notification): void
|
public function send(SendsEmail $notifiable, Notification $notification): void
|
||||||
{
|
{
|
||||||
$this->bootConfigs($notifiable);
|
$this->bootConfigs($notifiable);
|
||||||
@@ -20,35 +19,14 @@ class EmailChannel
|
|||||||
}
|
}
|
||||||
|
|
||||||
$mailMessage = $notification->toMail($notifiable);
|
$mailMessage = $notification->toMail($notifiable);
|
||||||
if ($this->isResend) {
|
Mail::send(
|
||||||
foreach ($recepients as $receipient) {
|
[],
|
||||||
Mail::send(
|
[],
|
||||||
[],
|
fn (Message $message) => $message
|
||||||
[],
|
->to($recepients)
|
||||||
fn (Message $message) => $message
|
->subject($mailMessage->subject)
|
||||||
->from(
|
->html((string)$mailMessage->render())
|
||||||
data_get($notifiable, 'smtp_from_address'),
|
);
|
||||||
data_get($notifiable, 'smtp_from_name'),
|
|
||||||
)
|
|
||||||
->to($receipient)
|
|
||||||
->subject($mailMessage->subject)
|
|
||||||
->html((string)$mailMessage->render())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Mail::send(
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
fn (Message $message) => $message
|
|
||||||
->from(
|
|
||||||
data_get($notifiable, 'smtp_from_address'),
|
|
||||||
data_get($notifiable, 'smtp_from_name'),
|
|
||||||
)
|
|
||||||
->bcc($recepients)
|
|
||||||
->subject($mailMessage->subject)
|
|
||||||
->html((string)$mailMessage->render())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function bootConfigs($notifiable): void
|
private function bootConfigs($notifiable): void
|
||||||
@@ -58,13 +36,11 @@ class EmailChannel
|
|||||||
if (!$type) {
|
if (!$type) {
|
||||||
throw new Exception('No email settings found.');
|
throw new Exception('No email settings found.');
|
||||||
}
|
}
|
||||||
if ($type === 'resend') {
|
|
||||||
$this->isResend = true;
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
config()->set('mail.from.address', data_get($notifiable, 'smtp_from_address'));
|
||||||
|
config()->set('mail.from.name', data_get($notifiable, 'smtp_from_name'));
|
||||||
if (data_get($notifiable, 'resend_enabled')) {
|
if (data_get($notifiable, 'resend_enabled')) {
|
||||||
$this->isResend = true;
|
|
||||||
config()->set('mail.default', 'resend');
|
config()->set('mail.default', 'resend');
|
||||||
config()->set('resend.api_key', data_get($notifiable, 'resend_api_key'));
|
config()->set('resend.api_key', data_get($notifiable, 'resend_api_key'));
|
||||||
}
|
}
|
||||||
|
|||||||
9
app/Notifications/Channels/SendsTelegram.php
Normal file
9
app/Notifications/Channels/SendsTelegram.php
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications\Channels;
|
||||||
|
|
||||||
|
interface SendsTelegram
|
||||||
|
{
|
||||||
|
public function routeNotificationForTelegram();
|
||||||
|
|
||||||
|
}
|
||||||
25
app/Notifications/Channels/TelegramChannel.php
Normal file
25
app/Notifications/Channels/TelegramChannel.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications\Channels;
|
||||||
|
|
||||||
|
use App\Jobs\SendMessageToTelegramJob;
|
||||||
|
|
||||||
|
class TelegramChannel
|
||||||
|
{
|
||||||
|
public function send($notifiable, $notification): void
|
||||||
|
{
|
||||||
|
$data = $notification->toTelegram($notifiable);
|
||||||
|
$telegramData = $notifiable->routeNotificationForTelegram();
|
||||||
|
|
||||||
|
$message = data_get($data, 'message');
|
||||||
|
$buttons = data_get($data, 'buttons', []);
|
||||||
|
ray($message, $buttons);
|
||||||
|
$telegramToken = data_get($telegramData, 'token');
|
||||||
|
$chatId = data_get($telegramData, 'chat_id');
|
||||||
|
|
||||||
|
if (!$telegramToken || !$chatId || !$message) {
|
||||||
|
throw new \Exception('Telegram token, chat id and message are required');
|
||||||
|
}
|
||||||
|
dispatch(new SendMessageToTelegramJob($message, $buttons, $telegramToken, $chatId));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,6 @@ use Log;
|
|||||||
|
|
||||||
class TransactionalEmailChannel
|
class TransactionalEmailChannel
|
||||||
{
|
{
|
||||||
private bool $isResend = false;
|
|
||||||
public function send(User $notifiable, Notification $notification): void
|
public function send(User $notifiable, Notification $notification): void
|
||||||
{
|
{
|
||||||
$settings = InstanceSettings::get();
|
$settings = InstanceSettings::get();
|
||||||
@@ -26,33 +25,14 @@ class TransactionalEmailChannel
|
|||||||
}
|
}
|
||||||
$this->bootConfigs();
|
$this->bootConfigs();
|
||||||
$mailMessage = $notification->toMail($notifiable);
|
$mailMessage = $notification->toMail($notifiable);
|
||||||
if ($this->isResend) {
|
Mail::send(
|
||||||
Mail::send(
|
[],
|
||||||
[],
|
[],
|
||||||
[],
|
fn (Message $message) => $message
|
||||||
fn (Message $message) => $message
|
->to($email)
|
||||||
->from(
|
->subject($mailMessage->subject)
|
||||||
data_get($settings, 'smtp_from_address'),
|
->html((string)$mailMessage->render())
|
||||||
data_get($settings, 'smtp_from_name'),
|
);
|
||||||
)
|
|
||||||
->to($email)
|
|
||||||
->subject($mailMessage->subject)
|
|
||||||
->html((string)$mailMessage->render())
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
Mail::send(
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
fn (Message $message) => $message
|
|
||||||
->from(
|
|
||||||
data_get($settings, 'smtp_from_address'),
|
|
||||||
data_get($settings, 'smtp_from_name'),
|
|
||||||
)
|
|
||||||
->bcc($email)
|
|
||||||
->subject($mailMessage->subject)
|
|
||||||
->html((string)$mailMessage->render())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function bootConfigs(): void
|
private function bootConfigs(): void
|
||||||
@@ -61,8 +41,5 @@ class TransactionalEmailChannel
|
|||||||
if (!$type) {
|
if (!$type) {
|
||||||
throw new Exception('No email settings found.');
|
throw new Exception('No email settings found.');
|
||||||
}
|
}
|
||||||
if ($type === 'resend') {
|
|
||||||
$this->isResend = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,42 +14,41 @@ class BackupFailed extends Notification implements ShouldQueue
|
|||||||
{
|
{
|
||||||
use Queueable;
|
use Queueable;
|
||||||
|
|
||||||
public string $message = 'Backup FAILED';
|
public string $name;
|
||||||
|
public string $frequency;
|
||||||
|
|
||||||
public function __construct(ScheduledDatabaseBackup $backup, public $database, public $output)
|
public function __construct(ScheduledDatabaseBackup $backup, public $database, public $output)
|
||||||
{
|
{
|
||||||
$this->message = "❌ Database backup for {$database->name} with frequency of $backup->frequency was FAILED.\n\nReason: $output";
|
$this->name = $database->name;
|
||||||
|
$this->frequency = $backup->frequency;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
{
|
{
|
||||||
$channels = [];
|
return setNotificationChannels($notifiable, 'database_backups');
|
||||||
$isEmailEnabled = isEmailEnabled($notifiable);
|
|
||||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
|
||||||
$isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_database_backups');
|
|
||||||
$isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_database_backups');
|
|
||||||
|
|
||||||
if ($isEmailEnabled && $isSubscribedToEmailEvent) {
|
|
||||||
$channels[] = EmailChannel::class;
|
|
||||||
}
|
|
||||||
if ($isDiscordEnabled && $isSubscribedToDiscordEvent) {
|
|
||||||
$channels[] = DiscordChannel::class;
|
|
||||||
}
|
|
||||||
ray($channels);
|
|
||||||
return $channels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toMail(): MailMessage
|
public function toMail(): MailMessage
|
||||||
{
|
{
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage();
|
||||||
$mail->subject("❌ Backup FAILED for {$this->database->name}");
|
$mail->subject("❌ [ACTION REQUIRED] Backup FAILED for {$this->database->name}");
|
||||||
$mail->line($this->message);
|
$mail->view('emails.backup-failed', [
|
||||||
|
'name' => $this->name,
|
||||||
|
'frequency' => $this->frequency,
|
||||||
|
'output' => $this->output,
|
||||||
|
]);
|
||||||
return $mail;
|
return $mail;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toDiscord(): string
|
public function toDiscord(): string
|
||||||
{
|
{
|
||||||
return $this->message;
|
return "❌ Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}";
|
||||||
|
}
|
||||||
|
public function toTelegram(): array
|
||||||
|
{
|
||||||
|
$message = "❌ Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}";
|
||||||
|
return [
|
||||||
|
"message" => $message,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,41 +14,40 @@ class BackupSuccess extends Notification implements ShouldQueue
|
|||||||
{
|
{
|
||||||
use Queueable;
|
use Queueable;
|
||||||
|
|
||||||
public string $message = 'Backup Success';
|
public string $name;
|
||||||
|
public string $frequency;
|
||||||
|
|
||||||
public function __construct(ScheduledDatabaseBackup $backup, public $database)
|
public function __construct(ScheduledDatabaseBackup $backup, public $database)
|
||||||
{
|
{
|
||||||
$this->message = "✅ Database backup for {$database->name} with frequency of $backup->frequency was successful.";
|
$this->name = $database->name;
|
||||||
|
$this->frequency = $backup->frequency;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
{
|
{
|
||||||
$channels = [];
|
return setNotificationChannels($notifiable, 'database_backups');
|
||||||
$isEmailEnabled = isEmailEnabled($notifiable);
|
|
||||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
|
||||||
$isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_database_backups');
|
|
||||||
$isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_database_backups');
|
|
||||||
|
|
||||||
if ($isEmailEnabled && $isSubscribedToEmailEvent) {
|
|
||||||
$channels[] = EmailChannel::class;
|
|
||||||
}
|
|
||||||
if ($isDiscordEnabled && $isSubscribedToDiscordEvent) {
|
|
||||||
$channels[] = DiscordChannel::class;
|
|
||||||
}
|
|
||||||
return $channels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toMail(): MailMessage
|
public function toMail(): MailMessage
|
||||||
{
|
{
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage();
|
||||||
$mail->subject("✅ Backup success for {$this->database->name}");
|
$mail->subject("✅ Backup successfully done for {$this->database->name}");
|
||||||
$mail->line($this->message);
|
$mail->view('emails.backup-success', [
|
||||||
|
'name' => $this->name,
|
||||||
|
'frequency' => $this->frequency,
|
||||||
|
]);
|
||||||
return $mail;
|
return $mail;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toDiscord(): string
|
public function toDiscord(): string
|
||||||
{
|
{
|
||||||
return $this->message;
|
return "✅ Database backup for {$this->name} with frequency of {$this->frequency} was successful.";
|
||||||
|
}
|
||||||
|
public function toTelegram(): array
|
||||||
|
{
|
||||||
|
$message = "✅ Database backup for {$this->name} with frequency of {$this->frequency} was successful.";
|
||||||
|
return [
|
||||||
|
"message" => $message,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Notifications\Internal;
|
namespace App\Notifications\Internal;
|
||||||
|
|
||||||
use App\Notifications\Channels\DiscordChannel;
|
use App\Notifications\Channels\DiscordChannel;
|
||||||
|
use App\Notifications\Channels\TelegramChannel;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Notifications\Notification;
|
use Illuminate\Notifications\Notification;
|
||||||
@@ -12,16 +13,22 @@ class GeneralNotification extends Notification implements ShouldQueue
|
|||||||
use Queueable;
|
use Queueable;
|
||||||
|
|
||||||
public function __construct(public string $message)
|
public function __construct(public string $message)
|
||||||
{}
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
{
|
{
|
||||||
$channels[] = DiscordChannel::class;
|
return [TelegramChannel::class, DiscordChannel::class];
|
||||||
return $channels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toDiscord(): string
|
public function toDiscord(): string
|
||||||
{
|
{
|
||||||
return $this->message;
|
return $this->message;
|
||||||
}
|
}
|
||||||
|
public function toTelegram(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"message" => $this->message,
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,19 +22,7 @@ class NotReachable extends Notification implements ShouldQueue
|
|||||||
|
|
||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
{
|
{
|
||||||
$channels = [];
|
return setNotificationChannels($notifiable, 'status_changes');
|
||||||
$isEmailEnabled = isEmailEnabled($notifiable);
|
|
||||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
|
||||||
$isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_status_changes');
|
|
||||||
$isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_status_changes');
|
|
||||||
|
|
||||||
// if ($isEmailEnabled && $isSubscribedToEmailEvent) {
|
|
||||||
// $channels[] = EmailChannel::class;
|
|
||||||
// }
|
|
||||||
if ($isDiscordEnabled && $isSubscribedToDiscordEvent) {
|
|
||||||
$channels[] = DiscordChannel::class;
|
|
||||||
}
|
|
||||||
return $channels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toMail(): MailMessage
|
public function toMail(): MailMessage
|
||||||
@@ -55,4 +43,10 @@ class NotReachable extends Notification implements ShouldQueue
|
|||||||
$message = '⛔ Server \'' . $this->server->name . '\' is unreachable (could be a temporary issue). If you receive this more than twice in a row, please check your server.';
|
$message = '⛔ Server \'' . $this->server->name . '\' is unreachable (could be a temporary issue). If you receive this more than twice in a row, please check your server.';
|
||||||
return $message;
|
return $message;
|
||||||
}
|
}
|
||||||
|
public function toTelegram(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"message" => '⛔ Server \'' . $this->server->name . '\' is unreachable (could be a temporary issue). If you receive this more than twice in a row, please check your server.'
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Notifications;
|
|||||||
|
|
||||||
use App\Notifications\Channels\DiscordChannel;
|
use App\Notifications\Channels\DiscordChannel;
|
||||||
use App\Notifications\Channels\EmailChannel;
|
use App\Notifications\Channels\EmailChannel;
|
||||||
|
use App\Notifications\Channels\TelegramChannel;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
@@ -19,24 +20,13 @@ class Test extends Notification implements ShouldQueue
|
|||||||
|
|
||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
{
|
{
|
||||||
$channels = [];
|
return setNotificationChannels($notifiable, 'test');
|
||||||
$isEmailEnabled = isEmailEnabled($notifiable);
|
|
||||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
|
||||||
|
|
||||||
if ($isDiscordEnabled && empty($this->emails)) {
|
|
||||||
$channels[] = DiscordChannel::class;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($isEmailEnabled && !empty($this->emails)) {
|
|
||||||
$channels[] = EmailChannel::class;
|
|
||||||
}
|
|
||||||
return $channels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toMail(): MailMessage
|
public function toMail(): MailMessage
|
||||||
{
|
{
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage();
|
||||||
$mail->subject("Coolify Test Notification");
|
$mail->subject("Test Email");
|
||||||
$mail->view('emails.test');
|
$mail->view('emails.test');
|
||||||
return $mail;
|
return $mail;
|
||||||
}
|
}
|
||||||
@@ -48,4 +38,16 @@ class Test extends Notification implements ShouldQueue
|
|||||||
$message .= '[Go to your dashboard](' . base_url() . ')';
|
$message .= '[Go to your dashboard](' . base_url() . ')';
|
||||||
return $message;
|
return $message;
|
||||||
}
|
}
|
||||||
|
public function toTelegram(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"message" => 'This is a test Telegram notification from Coolify.',
|
||||||
|
"buttons" => [
|
||||||
|
[
|
||||||
|
"text" => "Go to your dashboard",
|
||||||
|
"url" => base_url()
|
||||||
|
]
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,16 +20,19 @@ class InvitationLink extends Notification implements ShouldQueue
|
|||||||
return [TransactionalEmailChannel::class];
|
return [TransactionalEmailChannel::class];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toMail(User $user): MailMessage
|
public function __construct(public User $user)
|
||||||
{
|
{
|
||||||
$invitation = TeamInvitation::whereEmail($user->email)->first();
|
}
|
||||||
|
public function toMail(): MailMessage
|
||||||
|
{
|
||||||
|
$invitation = TeamInvitation::whereEmail($this->user->email)->first();
|
||||||
$invitation_team = Team::find($invitation->team->id);
|
$invitation_team = Team::find($invitation->team->id);
|
||||||
|
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage();
|
||||||
$mail->subject('Invitation for ' . $invitation_team->name);
|
$mail->subject('Invitation for ' . $invitation_team->name);
|
||||||
$mail->view('emails.invitation-link', [
|
$mail->view('emails.invitation-link', [
|
||||||
'team' => $invitation_team->name,
|
'team' => $invitation_team->name,
|
||||||
'email' => $user->email,
|
'email' => $this->user->email,
|
||||||
'invitation_link' => $invitation->link,
|
'invitation_link' => $invitation->link,
|
||||||
]);
|
]);
|
||||||
return $mail;
|
return $mail;
|
||||||
|
|||||||
@@ -50,10 +50,6 @@ class ResetPassword extends Notification
|
|||||||
protected function buildMailMessage($url)
|
protected function buildMailMessage($url)
|
||||||
{
|
{
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage();
|
||||||
$mail->from(
|
|
||||||
data_get($this->settings, 'smtp_from_address'),
|
|
||||||
data_get($this->settings, 'smtp_from_name'),
|
|
||||||
);
|
|
||||||
$mail->subject('Reset Password');
|
$mail->subject('Reset Password');
|
||||||
$mail->view('emails.reset-password', ['url' => $url, 'count' => config('auth.passwords.' . config('auth.defaults.passwords') . '.expire')]);
|
$mail->view('emails.reset-password', ['url' => $url, 'count' => config('auth.passwords.' . config('auth.defaults.passwords') . '.expire')]);
|
||||||
return $mail;
|
return $mail;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class Test extends Notification implements ShouldQueue
|
|||||||
public function toMail(): MailMessage
|
public function toMail(): MailMessage
|
||||||
{
|
{
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage();
|
||||||
$mail->subject('Test Notification');
|
$mail->subject('Test Email');
|
||||||
$mail->view('emails.test');
|
$mail->view('emails.test');
|
||||||
return $mail;
|
return $mail;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ function create_standalone_postgresql($environment_id, $destination_uuid): Stand
|
|||||||
}
|
}
|
||||||
return StandalonePostgresql::create([
|
return StandalonePostgresql::create([
|
||||||
'name' => generate_database_name('postgresql'),
|
'name' => generate_database_name('postgresql'),
|
||||||
'postgres_password' => \Illuminate\Support\Str::password(),
|
'postgres_password' => \Illuminate\Support\Str::password(symbols: false),
|
||||||
'environment_id' => $environment_id,
|
'environment_id' => $environment_id,
|
||||||
'destination_id' => $destination->id,
|
'destination_id' => $destination->id,
|
||||||
'destination_type' => $destination->getMorphClass(),
|
'destination_type' => $destination->getMorphClass(),
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ function getContainerStatus(Server $server, string $container_id, bool $all_data
|
|||||||
|
|
||||||
function generateApplicationContainerName(string $uuid, int $pull_request_id = 0)
|
function generateApplicationContainerName(string $uuid, int $pull_request_id = 0)
|
||||||
{
|
{
|
||||||
$now = now()->format('YmdHis');
|
$now = now()->format('Hisu');
|
||||||
if ($pull_request_id !== 0 && $pull_request_id !== null) {
|
if ($pull_request_id !== 0 && $pull_request_id !== null) {
|
||||||
return $uuid . '-pr-' . $pull_request_id . '-' . $now;
|
return $uuid . '-pr-' . $pull_request_id . '-' . $now;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
function get_proxy_path()
|
function get_proxy_path()
|
||||||
@@ -166,3 +167,75 @@ function setup_default_redirect_404(string|null $redirect_url, Server $server)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function startPostgresProxy(StandalonePostgresql $database)
|
||||||
|
{
|
||||||
|
$containerName = "{$database->uuid}-proxy";
|
||||||
|
$configuration_dir = database_proxy_dir($database->uuid);
|
||||||
|
$nginxconf = <<<EOF
|
||||||
|
user nginx;
|
||||||
|
worker_processes auto;
|
||||||
|
|
||||||
|
error_log /var/log/nginx/error.log;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
stream {
|
||||||
|
server {
|
||||||
|
listen $database->public_port;
|
||||||
|
proxy_pass $database->uuid:5432;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF;
|
||||||
|
$docker_compose = [
|
||||||
|
'version' => '3.8',
|
||||||
|
'services' => [
|
||||||
|
$containerName => [
|
||||||
|
'image' => "nginx:stable-alpine",
|
||||||
|
'container_name' => $containerName,
|
||||||
|
'restart' => RESTART_MODE,
|
||||||
|
'volumes' => [
|
||||||
|
"$configuration_dir/nginx.conf:/etc/nginx/nginx.conf:ro",
|
||||||
|
],
|
||||||
|
'ports' => [
|
||||||
|
"$database->public_port:$database->public_port",
|
||||||
|
],
|
||||||
|
'networks' => [
|
||||||
|
$database->destination->network,
|
||||||
|
],
|
||||||
|
'healthcheck' => [
|
||||||
|
'test' => [
|
||||||
|
'CMD-SHELL',
|
||||||
|
'stat /etc/nginx/nginx.conf || exit 1',
|
||||||
|
],
|
||||||
|
'interval' => '5s',
|
||||||
|
'timeout' => '5s',
|
||||||
|
'retries' => 3,
|
||||||
|
'start_period' => '1s'
|
||||||
|
],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'networks' => [
|
||||||
|
$database->destination->network => [
|
||||||
|
'external' => true,
|
||||||
|
'name' => $database->destination->network,
|
||||||
|
'attachable' => true,
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
$dockercompose_base64 = base64_encode(Yaml::dump($docker_compose, 4, 2));
|
||||||
|
$nginxconf_base64 = base64_encode($nginxconf);
|
||||||
|
instant_remote_process([
|
||||||
|
"mkdir -p $configuration_dir",
|
||||||
|
"echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf",
|
||||||
|
"echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml",
|
||||||
|
"docker compose --project-directory {$configuration_dir} up -d >/dev/null",
|
||||||
|
|
||||||
|
|
||||||
|
], $database->destination->server);
|
||||||
|
}
|
||||||
|
function stopPostgresProxy(StandalonePostgresql $database)
|
||||||
|
{
|
||||||
|
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $database->destination->server);
|
||||||
|
}
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ function generate_ssh_command(string $private_key_location, string $server_ip, s
|
|||||||
if ($isMux && config('coolify.mux_enabled')) {
|
if ($isMux && config('coolify.mux_enabled')) {
|
||||||
$ssh_command .= '-o ControlMaster=auto -o ControlPersist=1m -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r ';
|
$ssh_command .= '-o ControlMaster=auto -o ControlPersist=1m -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r ';
|
||||||
}
|
}
|
||||||
|
$command = "PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin && $command";
|
||||||
$ssh_command .= "-i {$private_key_location} "
|
$ssh_command .= "-i {$private_key_location} "
|
||||||
. '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null '
|
. '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null '
|
||||||
. '-o PasswordAuthentication=no '
|
. '-o PasswordAuthentication=no '
|
||||||
@@ -92,7 +93,18 @@ function generate_ssh_command(string $private_key_location, string $server_ip, s
|
|||||||
|
|
||||||
return $ssh_command;
|
return $ssh_command;
|
||||||
}
|
}
|
||||||
|
function instantCommand(string $command, $throwError = true) {
|
||||||
|
$process = Process::run($command);
|
||||||
|
$output = trim($process->output());
|
||||||
|
$exitCode = $process->exitCode();
|
||||||
|
if ($exitCode !== 0) {
|
||||||
|
if (!$throwError) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
throw new \RuntimeException($process->errorOutput(), $exitCode);
|
||||||
|
}
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
function instant_remote_process(array $command, Server $server, $throwError = true, $repeat = 1)
|
function instant_remote_process(array $command, Server $server, $throwError = true, $repeat = 1)
|
||||||
{
|
{
|
||||||
$command_string = implode("\n", $command);
|
$command_string = implode("\n", $command);
|
||||||
@@ -216,3 +228,29 @@ function check_server_connection(Server $server)
|
|||||||
$server->save();
|
$server->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkRequiredCommands(Server $server)
|
||||||
|
{
|
||||||
|
$commands = collect(["jq", "jc"]);
|
||||||
|
foreach ($commands as $command) {
|
||||||
|
$commandFound = instant_remote_process(["docker run --rm --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c 'command -v {$command}'"], $server, false);
|
||||||
|
if ($commandFound) {
|
||||||
|
ray($command . ' found');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
instant_remote_process(["docker run --rm --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c 'apt update && apt install -y {$command}'"], $server);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
ray('could not install ' . $command);
|
||||||
|
ray($e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$commandFound = instant_remote_process(["docker run --rm --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c 'command -v {$command}'"], $server, false);
|
||||||
|
if ($commandFound) {
|
||||||
|
ray($command . ' found');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ray('could not install ' . $command);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,12 +2,14 @@
|
|||||||
|
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
|
use App\Notifications\Channels\DiscordChannel;
|
||||||
|
use App\Notifications\Channels\EmailChannel;
|
||||||
|
use App\Notifications\Channels\TelegramChannel;
|
||||||
use App\Notifications\Internal\GeneralNotification;
|
use App\Notifications\Internal\GeneralNotification;
|
||||||
use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
|
use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
|
||||||
use Illuminate\Database\QueryException;
|
use Illuminate\Database\QueryException;
|
||||||
use Illuminate\Mail\Message;
|
use Illuminate\Mail\Message;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Illuminate\Support\Facades\Mail;
|
use Illuminate\Support\Facades\Mail;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
@@ -26,6 +28,10 @@ function database_configuration_dir(): string
|
|||||||
{
|
{
|
||||||
return '/data/coolify/databases';
|
return '/data/coolify/databases';
|
||||||
}
|
}
|
||||||
|
function database_proxy_dir($uuid): string
|
||||||
|
{
|
||||||
|
return "/data/coolify/databases/$uuid/proxy";
|
||||||
|
}
|
||||||
|
|
||||||
function backup_dir(): string
|
function backup_dir(): string
|
||||||
{
|
{
|
||||||
@@ -149,6 +155,8 @@ function set_transanctional_email_settings(InstanceSettings | null $settings = n
|
|||||||
if (!$settings) {
|
if (!$settings) {
|
||||||
$settings = InstanceSettings::get();
|
$settings = InstanceSettings::get();
|
||||||
}
|
}
|
||||||
|
config()->set('mail.from.address', data_get($settings, 'smtp_from_address'));
|
||||||
|
config()->set('mail.from.name', data_get($settings, 'smtp_from_name'));
|
||||||
if (data_get($settings, 'resend_enabled')) {
|
if (data_get($settings, 'resend_enabled')) {
|
||||||
config()->set('mail.default', 'resend');
|
config()->set('mail.default', 'resend');
|
||||||
config()->set('resend.api_key', data_get($settings, 'resend_api_key'));
|
config()->set('resend.api_key', data_get($settings, 'resend_api_key'));
|
||||||
@@ -241,9 +249,9 @@ function validate_cron_expression($expression_to_validate): bool
|
|||||||
function send_internal_notification(string $message): void
|
function send_internal_notification(string $message): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$baseUrl = base_url(false);
|
$baseUrl = config('app.name');
|
||||||
$team = Team::find(0);
|
$team = Team::find(0);
|
||||||
$team->notify(new GeneralNotification("👀 Internal notifications from {$baseUrl}: " . $message));
|
$team->notify(new GeneralNotification("👀 {$baseUrl}: " . $message));
|
||||||
} catch (\Throwable $th) {
|
} catch (\Throwable $th) {
|
||||||
ray($th->getMessage());
|
ray($th->getMessage());
|
||||||
}
|
}
|
||||||
@@ -259,17 +267,32 @@ function send_user_an_email(MailMessage $mail, string $email): void
|
|||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
fn (Message $message) => $message
|
fn (Message $message) => $message
|
||||||
->from(
|
|
||||||
data_get($settings, 'smtp_from_address'),
|
|
||||||
data_get($settings, 'smtp_from_name')
|
|
||||||
)
|
|
||||||
->to($email)
|
->to($email)
|
||||||
->subject($mail->subject)
|
->subject($mail->subject)
|
||||||
->html((string) $mail->render())
|
->html((string) $mail->render())
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
function isEmailEnabled($notifiable)
|
function isEmailEnabled($notifiable)
|
||||||
{
|
{
|
||||||
return data_get($notifiable, 'smtp_enabled') || data_get($notifiable, 'resend_enabled') || data_get($notifiable, 'use_instance_email_settings');
|
return data_get($notifiable, 'smtp_enabled') || data_get($notifiable, 'resend_enabled') || data_get($notifiable, 'use_instance_email_settings');
|
||||||
}
|
}
|
||||||
|
function setNotificationChannels($notifiable, $event)
|
||||||
|
{
|
||||||
|
$channels = [];
|
||||||
|
$isEmailEnabled = isEmailEnabled($notifiable);
|
||||||
|
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
||||||
|
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
|
||||||
|
$isSubscribedToDiscordEvent = data_get($notifiable, "discord_notifications_$event");
|
||||||
|
$isSubscribedToTelegramEvent = data_get($notifiable, "telegram_notifications_$event");
|
||||||
|
|
||||||
|
if ($isDiscordEnabled && $isSubscribedToDiscordEvent) {
|
||||||
|
$channels[] = DiscordChannel::class;
|
||||||
|
}
|
||||||
|
if ($isEmailEnabled) {
|
||||||
|
$channels[] = EmailChannel::class;
|
||||||
|
}
|
||||||
|
if ($isTelegramEnabled && $isSubscribedToTelegramEvent) {
|
||||||
|
$channels[] = TelegramChannel::class;
|
||||||
|
}
|
||||||
|
return $channels;
|
||||||
|
}
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ function getEndDate()
|
|||||||
|
|
||||||
function isSubscriptionActive()
|
function isSubscriptionActive()
|
||||||
{
|
{
|
||||||
|
if (!isCloud()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
$team = currentTeam();
|
$team = currentTeam();
|
||||||
if (!$team) {
|
if (!$team) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"laravel/fortify": "^v1.16.0",
|
"laravel/fortify": "^v1.16.0",
|
||||||
"laravel/framework": "^v10.7.1",
|
"laravel/framework": "^v10.7.1",
|
||||||
"laravel/horizon": "^5.15",
|
"laravel/horizon": "^5.15",
|
||||||
|
"laravel/prompts": "^0.1.6",
|
||||||
"laravel/sanctum": "^v3.2.1",
|
"laravel/sanctum": "^v3.2.1",
|
||||||
"laravel/tinker": "^v2.8.1",
|
"laravel/tinker": "^v2.8.1",
|
||||||
"laravel/ui": "^4.2",
|
"laravel/ui": "^4.2",
|
||||||
|
|||||||
2
composer.lock
generated
2
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "dbb08df7a80c46ce2b9b9fa397ed71c1",
|
"content-hash": "0603276b60e77cd859fabacdaaf31550",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "aws/aws-crt-php",
|
"name": "aws/aws-crt-php",
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'name' => env('APP_NAME', 'Laravel'),
|
'name' => env('APP_NAME', 'Coolify'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ return [
|
|||||||
'users' => [
|
'users' => [
|
||||||
'provider' => 'users',
|
'provider' => 'users',
|
||||||
'table' => 'password_reset_tokens',
|
'table' => 'password_reset_tokens',
|
||||||
'expire' => 60,
|
'expire' => 10,
|
||||||
'throttle' => 60,
|
'throttle' => 60,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -7,4 +7,5 @@ return [
|
|||||||
'mux_enabled' => env('MUX_ENABLED', true),
|
'mux_enabled' => env('MUX_ENABLED', true),
|
||||||
'dev_webhook' => env('SERVEO_URL'),
|
'dev_webhook' => env('SERVEO_URL'),
|
||||||
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
|
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
|
||||||
|
'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper:latest'),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ return [
|
|||||||
|
|
||||||
// The release version of your application
|
// The release version of your application
|
||||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||||
'release' => '4.0.0-beta.22',
|
'release' => '4.0.0-beta.26',
|
||||||
'server_name' => env('APP_ID', 'coolify'),
|
'server_name' => env('APP_ID', 'coolify'),
|
||||||
// When left empty or `null` the Laravel environment will be used
|
// When left empty or `null` the Laravel environment will be used
|
||||||
'environment' => config('app.env'),
|
'environment' => config('app.env'),
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return '4.0.0-beta.22';
|
return '4.0.0-beta.26';
|
||||||
|
|||||||
32
database/migrations/2023_08_22_071054_add_stripe_reasons.php
Normal file
32
database/migrations/2023_08_22_071054_add_stripe_reasons.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('subscriptions', function (Blueprint $table) {
|
||||||
|
$table->string('stripe_feedback')->nullable()->after('stripe_cancel_at_period_end');
|
||||||
|
$table->string('stripe_comment')->nullable()->after('stripe_feedback');
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('subscriptions', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('stripe_feedback');
|
||||||
|
$table->dropColumn('stripe_comment');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('teams', function (Blueprint $table) {
|
||||||
|
$table->boolean('telegram_enabled')->default(false);
|
||||||
|
$table->text('telegram_token')->nullable();
|
||||||
|
$table->text('telegram_chat_id')->nullable();
|
||||||
|
$table->boolean('telegram_notifications_test')->default(true);
|
||||||
|
$table->boolean('telegram_notifications_deployments')->default(true);
|
||||||
|
$table->boolean('telegram_notifications_status_changes')->default(true);
|
||||||
|
$table->boolean('telegram_notifications_database_backups')->default(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('teams', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('telegram_enabled');
|
||||||
|
$table->dropColumn('telegram_token');
|
||||||
|
$table->dropColumn('telegram_chat_id');
|
||||||
|
$table->dropColumn('telegram_notifications_test');
|
||||||
|
$table->dropColumn('telegram_notifications_deployments');
|
||||||
|
$table->dropColumn('telegram_notifications_status_changes');
|
||||||
|
$table->dropColumn('telegram_notifications_database_backups');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -45,7 +45,7 @@ class ProductionSeeder extends Seeder
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config('app.name') !== 'coolify-cloud') {
|
if (config('app.name') !== 'Coolify Cloud') {
|
||||||
// Save SSH Keys for the Coolify Host
|
// Save SSH Keys for the Coolify Host
|
||||||
$coolify_key_name = "id.root@host.docker.internal";
|
$coolify_key_name = "id.root@host.docker.internal";
|
||||||
$coolify_key = Storage::disk('ssh-keys')->get("{$coolify_key_name}");
|
$coolify_key = Storage::disk('ssh-keys')->get("{$coolify_key_name}");
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ x-testing-host: &testing-host-base
|
|||||||
context: ./docker/testing-host
|
context: ./docker/testing-host
|
||||||
networks:
|
networks:
|
||||||
- coolify
|
- coolify
|
||||||
|
init: true
|
||||||
|
|
||||||
services:
|
services:
|
||||||
coolify:
|
coolify:
|
||||||
@@ -53,6 +54,7 @@ services:
|
|||||||
<<: *testing-host-base
|
<<: *testing-host-base
|
||||||
container_name: coolify-testing-host
|
container_name: coolify-testing-host
|
||||||
volumes:
|
volumes:
|
||||||
|
- /:/host
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
- /data/coolify/:/data/coolify
|
- /data/coolify/:/data/coolify
|
||||||
mailpit:
|
mailpit:
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ FROM alpine:3.17
|
|||||||
|
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
# https://download.docker.com/linux/static/stable/
|
# https://download.docker.com/linux/static/stable/
|
||||||
ARG DOCKER_VERSION=23.0.6
|
ARG DOCKER_VERSION=24.0.5
|
||||||
# https://github.com/docker/compose/releases
|
# https://github.com/docker/compose/releases
|
||||||
ARG DOCKER_COMPOSE_VERSION=2.18.1
|
ARG DOCKER_COMPOSE_VERSION=2.21.0
|
||||||
# https://github.com/docker/buildx/releases
|
# https://github.com/docker/buildx/releases
|
||||||
ARG DOCKER_BUILDX_VERSION=0.10.5
|
ARG DOCKER_BUILDX_VERSION=0.11.2
|
||||||
# https://github.com/buildpacks/pack/releases
|
# https://github.com/buildpacks/pack/releases
|
||||||
ARG PACK_VERSION=0.29.0
|
ARG PACK_VERSION=0.30.0
|
||||||
# https://github.com/railwayapp/nixpacks/releases
|
# https://github.com/railwayapp/nixpacks/releases
|
||||||
ARG NIXPACKS_VERSION=1.12.0
|
ARG NIXPACKS_VERSION=1.13.0
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
WORKDIR /artifacts
|
WORKDIR /artifacts
|
||||||
@@ -38,5 +38,5 @@ COPY --from=minio/mc /usr/bin/mc /usr/bin/mc
|
|||||||
RUN chmod +x /usr/bin/mc
|
RUN chmod +x /usr/bin/mc
|
||||||
|
|
||||||
ENTRYPOINT ["/sbin/tini", "--"]
|
ENTRYPOINT ["/sbin/tini", "--"]
|
||||||
CMD ["sh", "-c", "while true; do sleep 1; done"]
|
CMD ["tail", "-f", "/dev/null"]
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,28 @@
|
|||||||
FROM alpine:3.17
|
FROM debian:12-slim
|
||||||
|
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
# https://download.docker.com/linux/static/stable/
|
# https://download.docker.com/linux/static/stable/
|
||||||
ARG DOCKER_VERSION=23.0.6
|
ARG DOCKER_VERSION=24.0.5
|
||||||
# https://github.com/docker/compose/releases
|
# https://github.com/docker/compose/releases
|
||||||
ARG DOCKER_COMPOSE_VERSION=2.18.1
|
ARG DOCKER_COMPOSE_VERSION=2.21.0
|
||||||
# https://github.com/docker/buildx/releases
|
# https://github.com/docker/buildx/releases
|
||||||
ARG DOCKER_BUILDX_VERSION=0.10.5
|
ARG DOCKER_BUILDX_VERSION=0.11.2
|
||||||
# https://github.com/buildpacks/pack/releases
|
# https://github.com/buildpacks/pack/releases
|
||||||
ARG PACK_VERSION=0.29.0
|
ARG PACK_VERSION=0.30.0
|
||||||
# https://github.com/railwayapp/nixpacks/releases
|
# https://github.com/railwayapp/nixpacks/releases
|
||||||
ARG NIXPACKS_VERSION=1.12.0
|
ARG NIXPACKS_VERSION=1.13.0
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
WORKDIR /root
|
WORKDIR /root
|
||||||
RUN apk add --no-cache bash curl git git-lfs openssh-client openssh-server tar tini postgresql-client lsof
|
ENV PATH "$PATH:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin"
|
||||||
|
|
||||||
|
RUN apt update && apt -y install openssh-client openssh-server curl wget git jq jc
|
||||||
RUN mkdir -p ~/.docker/cli-plugins
|
RUN mkdir -p ~/.docker/cli-plugins
|
||||||
RUN if [[ ${TARGETPLATFORM} == 'linux/amd64' ]]; then \
|
RUN curl -sSL https://github.com/docker/buildx/releases/download/v${DOCKER_BUILDX_VERSION}/buildx-v${DOCKER_BUILDX_VERSION}.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx
|
||||||
curl -sSL https://github.com/docker/buildx/releases/download/v${DOCKER_BUILDX_VERSION}/buildx-v${DOCKER_BUILDX_VERSION}.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx && \
|
RUN curl -sSL https://github.com/docker/compose/releases/download/v${DOCKER_COMPOSE_VERSION}/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose
|
||||||
curl -sSL https://github.com/docker/compose/releases/download/v${DOCKER_COMPOSE_VERSION}/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose && \
|
RUN (curl -sSL https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz | tar -C /usr/bin/ --no-same-owner -xzv --strip-components=1 docker/docker)
|
||||||
(curl -sSL https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz | tar -C /usr/bin/ --no-same-owner -xzv --strip-components=1 docker/docker) && \
|
RUN chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker /root/.docker/cli-plugins/docker-buildx
|
||||||
(curl -sSL https://github.com/buildpacks/pack/releases/download/v${PACK_VERSION}/pack-v${PACK_VERSION}-linux.tgz | tar -C /usr/local/bin/ --no-same-owner -xzv pack) && \
|
|
||||||
curl -sSL https://nixpacks.com/install.sh | bash && \
|
|
||||||
chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker /usr/local/bin/pack /root/.docker/cli-plugins/docker-buildx \
|
|
||||||
;fi
|
|
||||||
|
|
||||||
# Setup sshd
|
# Setup sshd
|
||||||
RUN ssh-keygen -A
|
RUN ssh-keygen -A
|
||||||
@@ -32,6 +31,4 @@ RUN mkdir -p ~/.ssh
|
|||||||
RUN echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFuGmoeGq/pojrsyP1pszcNVuZx9iFkCELtxrh31QJ68 coolify@coolify-instance" >> ~/.ssh/authorized_keys
|
RUN echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFuGmoeGq/pojrsyP1pszcNVuZx9iFkCELtxrh31QJ68 coolify@coolify-instance" >> ~/.ssh/authorized_keys
|
||||||
|
|
||||||
EXPOSE 22
|
EXPOSE 22
|
||||||
ENTRYPOINT ["/sbin/tini", "--"]
|
|
||||||
CMD ["/usr/sbin/sshd", "-D", "-o", "ListenAddress=0.0.0.0"]
|
CMD ["/usr/sbin/sshd", "-D", "-o", "ListenAddress=0.0.0.0"]
|
||||||
|
|
||||||
|
|||||||
@@ -593,7 +593,7 @@ async function redirect() {
|
|||||||
targetUrl.pathname = `/source/new`
|
targetUrl.pathname = `/source/new`
|
||||||
break;
|
break;
|
||||||
case 7:
|
case 7:
|
||||||
targetUrl.pathname = `/private-key/new`
|
targetUrl.pathname = `/security/private-key/new`
|
||||||
break;
|
break;
|
||||||
case 8:
|
case 8:
|
||||||
targetUrl.pathname = `/destination/new`
|
targetUrl.pathname = `/destination/new`
|
||||||
@@ -612,7 +612,7 @@ async function redirect() {
|
|||||||
targetUrl.pathname = `/servers`
|
targetUrl.pathname = `/servers`
|
||||||
break;
|
break;
|
||||||
case 13:
|
case 13:
|
||||||
targetUrl.pathname = `/private-keys`
|
targetUrl.pathname = `/security/private-key`
|
||||||
break;
|
break;
|
||||||
case 14:
|
case 14:
|
||||||
targetUrl.pathname = `/projects`
|
targetUrl.pathname = `/projects`
|
||||||
|
|||||||
@@ -51,6 +51,11 @@
|
|||||||
{{ session('status') }}
|
{{ session('status') }}
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
@if (session('error'))
|
||||||
|
<div class="mb-4 font-medium text-red-600">
|
||||||
|
{{ session('error') }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
6
resources/views/components/emails/footer.blade.php
Normal file
6
resources/views/components/emails/footer.blade.php
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{{ Illuminate\Mail\Markdown::parse('---') }}
|
||||||
|
|
||||||
|
Thank you,<br>
|
||||||
|
{{ config('app.name') ?? 'Coolify' }}
|
||||||
|
|
||||||
|
{{ Illuminate\Mail\Markdown::parse('[Contact Support](https://docs.coollabs.io)') }}
|
||||||
1
resources/views/components/emails/header.blade.php
Normal file
1
resources/views/components/emails/header.blade.php
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Hello,
|
||||||
6
resources/views/components/emails/layout.blade.php
Normal file
6
resources/views/components/emails/layout.blade.php
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<x-emails.header />
|
||||||
|
{{ Illuminate\Mail\Markdown::parse($slot) }}
|
||||||
|
<x-emails.footer />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -12,8 +12,8 @@
|
|||||||
@if ($attributes->get('type') === 'submit')
|
@if ($attributes->get('type') === 'submit')
|
||||||
<span wire:target="submit" wire:loading.delay class="loading loading-xs text-warning loading-spinner"></span>
|
<span wire:target="submit" wire:loading.delay class="loading loading-xs text-warning loading-spinner"></span>
|
||||||
@else
|
@else
|
||||||
@if ($attributes->has('wire:click'))
|
@if ($attributes->whereStartsWith('wire:click')->first())
|
||||||
<span wire:target="{{ $attributes->get('wire:click') }}" wire:loading.delay
|
<span wire:target="{{ $attributes->whereStartsWith('wire:click')->first() }}" wire:loading.delay
|
||||||
class="loading loading-xs loading-spinner"></span>
|
class="loading loading-xs loading-spinner"></span>
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
@endif
|
@endif
|
||||||
</span>
|
</span>
|
||||||
<input @disabled($disabled) type="checkbox" {{ $attributes->merge(['class' => $defaultClass]) }}
|
<input @disabled($disabled) type="checkbox" {{ $attributes->merge(['class' => $defaultClass]) }}
|
||||||
@if ($instantSave) wire:click='{{ $instantSave === 'instantSave' || $instantSave == '1' ? 'instantSave' : $instantSave }}'
|
@if ($instantSave) wire:loading.attr="disabled" wire:click='{{ $instantSave === 'instantSave' || $instantSave == '1' ? 'instantSave' : $instantSave }}'
|
||||||
wire:model.defer={{ $id }} @else wire:model.defer={{ $value ?? $id }} @endif />
|
wire:model.defer={{ $id }} @else wire:model.defer={{ $value ?? $id }} @endif />
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,7 +26,8 @@
|
|||||||
wire:model.defer={{ $id }} wire:dirty.class.remove='text-white'
|
wire:model.defer={{ $id }} wire:dirty.class.remove='text-white'
|
||||||
wire:dirty.class="input-warning" wire:loading.attr="disabled" type="{{ $type }}"
|
wire:dirty.class="input-warning" wire:loading.attr="disabled" type="{{ $type }}"
|
||||||
@readonly($readonly) @disabled($disabled) id="{{ $id }}" name="{{ $name }}"
|
@readonly($readonly) @disabled($disabled) id="{{ $id }}" name="{{ $name }}"
|
||||||
placeholder="{{ $attributes->get('placeholder') }}">
|
placeholder="{{ $attributes->get('placeholder') }}"
|
||||||
|
aria-placeholder="{{ $attributes->get('placeholder') }}">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute hidden text-xs rounded group-hover:block border-coolgray-400 bg-coolgray-500">
|
<div class="absolute z-40 hidden text-xs rounded group-hover:block border-coolgray-400 bg-coolgray-500">
|
||||||
<div class="p-4 card-body">
|
<div class="p-4">
|
||||||
{!! $helper !!}
|
{!! $helper !!}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -43,10 +43,6 @@
|
|||||||
@endisset
|
@endisset
|
||||||
@if ($modalSubmit)
|
@if ($modalSubmit)
|
||||||
{{ $modalSubmit }}
|
{{ $modalSubmit }}
|
||||||
@else
|
|
||||||
<x-forms.button onclick="{{ $modalId }}.close()" type="submit">
|
|
||||||
Save
|
|
||||||
</x-forms.button>
|
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -50,6 +50,23 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li title="Source">
|
||||||
|
<a class="hover:bg-transparent" href="{{ route('source.all') }}">
|
||||||
|
<svg class="icon" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill="currentColor"
|
||||||
|
d="m6.793 1.207l.353.354l-.353-.354ZM1.207 6.793l-.353-.354l.353.354Zm0 1.414l.354-.353l-.354.353Zm5.586 5.586l-.354.353l.354-.353Zm1.414 0l-.353-.354l.353.354Zm5.586-5.586l.353.354l-.353-.354Zm0-1.414l-.354.353l.354-.353ZM8.207 1.207l.354-.353l-.354.353ZM6.44.854L.854 6.439l.707.707l5.585-5.585L6.44.854ZM.854 8.56l5.585 5.585l.707-.707l-5.585-5.585l-.707.707Zm7.707 5.585l5.585-5.585l-.707-.707l-5.585 5.585l.707.707Zm5.585-7.707L8.561.854l-.707.707l5.585 5.585l.707-.707Zm0 2.122a1.5 1.5 0 0 0 0-2.122l-.707.707a.5.5 0 0 1 0 .708l.707.707ZM6.44 14.146a1.5 1.5 0 0 0 2.122 0l-.707-.707a.5.5 0 0 1-.708 0l-.707.707ZM.854 6.44a1.5 1.5 0 0 0 0 2.122l.707-.707a.5.5 0 0 1 0-.708L.854 6.44Zm6.292-4.878a.5.5 0 0 1 .708 0L8.56.854a1.5 1.5 0 0 0-2.122 0l.707.707Zm-2 1.293l1 1l.708-.708l-1-1l-.708.708ZM7.5 5a.5.5 0 0 1-.5-.5H6A1.5 1.5 0 0 0 7.5 6V5Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 9 4.5H8ZM7.5 4a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 7.5 3v1Zm0-1A1.5 1.5 0 0 0 6 4.5h1a.5.5 0 0 1 .5-.5V3Zm.646 2.854l1.5 1.5l.707-.708l-1.5-1.5l-.707.708ZM10.5 8a.5.5 0 0 1-.5-.5H9A1.5 1.5 0 0 0 10.5 9V8Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 12 7.5h-1Zm-.5-.5a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 10.5 6v1Zm0-1A1.5 1.5 0 0 0 9 7.5h1a.5.5 0 0 1 .5-.5V6ZM7 5.5v4h1v-4H7Zm.5 5.5a.5.5 0 0 1-.5-.5H6A1.5 1.5 0 0 0 7.5 12v-1Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 9 10.5H8Zm-.5-.5a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 7.5 9v1Zm0-1A1.5 1.5 0 0 0 6 10.5h1a.5.5 0 0 1 .5-.5V9Z" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li title="Security">
|
||||||
|
<a class="hover:bg-transparent" href="{{ route('security.private-key.index') }}">
|
||||||
|
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="m16.555 3.843l3.602 3.602a2.877 2.877 0 0 1 0 4.069l-2.643 2.643a2.877 2.877 0 0 1-4.069 0l-.301-.301l-6.558 6.558a2 2 0 0 1-1.239.578L5.172 21H4a1 1 0 0 1-.993-.883L3 20v-1.172a2 2 0 0 1 .467-1.284l.119-.13L4 17h2v-2h2v-2l2.144-2.144l-.301-.301a2.877 2.877 0 0 1 0-4.069l2.643-2.643a2.877 2.877 0 0 1 4.069 0zM15 9h.01" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li title="Teams">
|
<li title="Teams">
|
||||||
<a class="hover:bg-transparent" href="{{ route('team.index') }}">
|
<a class="hover:bg-transparent" href="{{ route('team.index') }}">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" stroke-width="1.5"
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" stroke-width="1.5"
|
||||||
@@ -64,6 +81,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<div class="flex-1"></div>
|
<div class="flex-1"></div>
|
||||||
@if (isInstanceAdmin() && !isCloud())
|
@if (isInstanceAdmin() && !isCloud())
|
||||||
<livewire:upgrade />
|
<livewire:upgrade />
|
||||||
@@ -95,6 +113,20 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@endif
|
@endif
|
||||||
|
@if (isSubscriptionActive() || isDev())
|
||||||
|
<li title="Help" class="mt-auto">
|
||||||
|
<div class="justify-center icons" wire:click="help" onclick="help.showModal()">
|
||||||
|
<svg class="{{ request()->is('help*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
stroke-width="2">
|
||||||
|
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0-18 0m9 4v.01" />
|
||||||
|
<path d="M12 13a2 2 0 0 0 .914-3.782a1.98 1.98 0 0 0-2.414.483" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
<li class="pb-6" title="Logout">
|
<li class="pb-6" title="Logout">
|
||||||
<form action="/logout" method="POST" class=" hover:bg-transparent">
|
<form action="/logout" method="POST" class=" hover:bg-transparent">
|
||||||
@csrf
|
@csrf
|
||||||
|
|||||||
17
resources/views/components/security/navbar.blade.php
Normal file
17
resources/views/components/security/navbar.blade.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<div class="pb-6">
|
||||||
|
<h1>Security</h1>
|
||||||
|
<nav class="flex pt-2 pb-10">
|
||||||
|
<ol class="inline-flex items-center">
|
||||||
|
<li>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span>Security related settings</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
<nav class="navbar-main">
|
||||||
|
<a class="{{ request()->routeIs('security.private-key.index') ? 'text-white' : '' }}" href="{{ route('security.private-key.index') }}">
|
||||||
|
<button>Private Keys</button>
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
@if ($pull_request_id !== 0)
|
<x-emails.layout>
|
||||||
Pull Request #{{ $pull_request_id }} of {{ $name }} (<a target="_blank"
|
@if ($pull_request_id === 0)
|
||||||
href="{{ $fqdn }}">{{ $fqdn }}</a>) deployment failed:
|
Failed to deploy a new version of {{ $name }} at [{{ $fqdn }}]({{ $fqdn }}) .
|
||||||
@else
|
@else
|
||||||
Deployment failed of {{ $name }} (<a target="_blank" href="{{ $fqdn }}">{{ $fqdn }}</a>):
|
Failed to deploy a pull request #{{ $pull_request_id }} of {{ $name }} at
|
||||||
@endif
|
[{{ $fqdn }}]({{ $fqdn }}).
|
||||||
|
@endif
|
||||||
|
|
||||||
<a target="_blank" href="{{ $deployment_url }}">View Deployment Logs</a><br><br>
|
[View Deployment Logs]({{ $deployment_url }})
|
||||||
|
|
||||||
|
</x-emails.layout>
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
@if ($pull_request_id === 0)
|
<x-emails.layout>
|
||||||
A new version of <a target="_blank" href="{{ $fqdn }}">{{ $fqdn }}</a> is available:
|
@if ($pull_request_id === 0)
|
||||||
@else
|
A new version of {{ $name }} is available at [{{ $fqdn }}]({{ $fqdn }}) .
|
||||||
Pull request #{{ $pull_request_id }} of {{ $name }} deployed successfully: <a target="_blank"
|
@else
|
||||||
href="{{ $fqdn }}">Application Link</a> |
|
Pull request #{{ $pull_request_id }} of {{ $name }} deployed successfully [{{ $fqdn }}]({{ $fqdn }}).
|
||||||
@endif
|
@endif
|
||||||
<a target="_blank" href="{{ $deployment_url }}">View
|
|
||||||
Deployment Logs</a><br><br>
|
[View Deployment Logs]({{ $deployment_url }})
|
||||||
|
|
||||||
|
</x-emails.layout>
|
||||||
|
|||||||
@@ -1,2 +1,9 @@
|
|||||||
{{ $name }} has been stopped.<br><br>
|
<x-emails.layout>
|
||||||
<a target="_blank" href="{{ $application_url }}">Open in Coolify</a><br><br>
|
|
||||||
|
{{ $name }} has been stopped.
|
||||||
|
|
||||||
|
If it was your intention to stop this application, you can ignore this email.
|
||||||
|
|
||||||
|
If not, [check what is going on]({{ $application_url }}).
|
||||||
|
|
||||||
|
</x-emails.layout>
|
||||||
|
|||||||
8
resources/views/emails/backup-failed.blade.php
Normal file
8
resources/views/emails/backup-failed.blade.php
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<x-emails.layout>
|
||||||
|
Database backup for {{ $name }} with frequency of {{ $frequency }} was FAILED.
|
||||||
|
|
||||||
|
### Reason
|
||||||
|
|
||||||
|
{{ $output }}
|
||||||
|
|
||||||
|
</x-emails.layout>
|
||||||
3
resources/views/emails/backup-success.blade.php
Normal file
3
resources/views/emails/backup-success.blade.php
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<x-emails.layout>
|
||||||
|
Database backup for {{ $name }} with frequency of {{ $frequency }} was successful.
|
||||||
|
</x-emails.layout>
|
||||||
5
resources/views/emails/help.blade.php
Normal file
5
resources/views/emails/help.blade.php
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{{ $description }}
|
||||||
|
|
||||||
|
{{ Illuminate\Mail\Markdown::parse('---') }}
|
||||||
|
|
||||||
|
{{ Illuminate\Mail\Markdown::parse($debug) }}
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
Hello,<br><br>
|
<x-emails.layout>
|
||||||
|
|
||||||
You have been invited to "{{ $team }}" on "{{ config('app.name') }}".<br><br>
|
You have been invited to "{{ $team }}" on "{{ config('app.name') }}".
|
||||||
|
|
||||||
Please click here to accept the invitation: <a target="_blank" href="{{ $invitation_link }}">Accept Invitation</a><br>
|
Please [click here]({{ $invitation_link }}) to accept the invitation.
|
||||||
<br>
|
|
||||||
|
|
||||||
If you have any questions, please contact the team owner.<br><br>
|
If you have any questions, please contact the team owner.<br><br>
|
||||||
|
|
||||||
If it was not you who requested this invitation, please ignore this ema il, or instantly revoke the invitation by
|
If it was not you who requested this invitation, please ignore this email, or instantly revoke the invitation by clicking [here]({{ $invitation_link }}/revoke).
|
||||||
clicking here: <a target="_blank" href="{{ $invitation_link }}/revoke">Revoke Invitation</a><br><br>
|
|
||||||
|
|
||||||
Thank you.
|
</x-emails.layout>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
A password reset requested for your email address on "{{ config('app.name') }}".<br><br>
|
<x-emails.layout>
|
||||||
|
A password reset has been requested for this email address.
|
||||||
|
|
||||||
Please click the following link to reset your password: <a target="_blank" href="{{ $url }}">Password
|
Click [here]({{ $url }}) to reset your password.
|
||||||
Reset</a><br><br>
|
|
||||||
|
|
||||||
This password reset link will expire in {{ $count }} minutes.
|
This link will expire in {{ $count }} minutes.
|
||||||
|
</x-emails.layout>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
Your last invoice has failed to be paid for Coolify Cloud. Please <a href="{{$stripeCustomerPortal}}">update payment details on your Stripe Customer Portal</a>.
|
<x-emails.layout>
|
||||||
<br><br>
|
Your last invoice has failed to be paid for Coolify Cloud.
|
||||||
Thanks,<br>
|
|
||||||
Coolify Cloud
|
Please update payment details [here]({{$stripeCustomerPortal}}).
|
||||||
|
|
||||||
|
</x-emails.layout>
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
If you are seeing this, it means that your SMTP settings are correct.
|
<x-emails.layout>
|
||||||
|
If you are seeing this, it means that your Email settings are correct.
|
||||||
|
</x-emails.layout>
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
Someone added this email to the Coolify Cloud's waitlist.
|
<x-emails.layout>
|
||||||
<br>
|
Someone added this email to the Coolify Cloud's waitlist. [Click here]({{ $confirmation_url }}) to confirm!
|
||||||
<a href="{{ $confirmation_url }}">Click here to confirm</a>! The link will expire in {{config('constants.waitlist.expiration')}} minutes.<br><br>
|
|
||||||
You have no idea what <a href="https://coolify.io">Coolify Cloud</a> is or this waitlist? <a href="{{ $cancel_url }}">Click here to remove</a> you from the waitlist.
|
The link will expire in {{config('constants.waitlist.expiration')}} minutes.
|
||||||
|
|
||||||
|
|
||||||
|
You have no idea what [Coolify Cloud](https://coolify.io) is or this waitlist? [Click here]({{ $cancel_url }}) to remove you from the waitlist.
|
||||||
|
|
||||||
|
</x-emails.layout>
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
You have been invited to join the Coolify Cloud. <a href="{{base_url()}}/login">Login here</a>
|
<x-emails.layout>
|
||||||
<br>
|
You have been invited to join the Coolify Cloud: [Get Started]({{$loginLink}})
|
||||||
<br>
|
</x-emails.layout>
|
||||||
Here is your initial login information.
|
|
||||||
<br>
|
|
||||||
Email: <br>
|
|
||||||
{{ $email }}
|
|
||||||
<br><br>
|
|
||||||
Password:<br>
|
|
||||||
{{ $password }}
|
|
||||||
<br><br>
|
|
||||||
(You will forced to change it on first login.)
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,20 @@
|
|||||||
@extends('errors::minimal')
|
@extends('layouts.base')
|
||||||
|
<div class="min-h-screen hero">
|
||||||
@section('title', __('Unauthorized'))
|
<div class="text-center hero-content">
|
||||||
@section('code', '401')
|
<div class="">
|
||||||
@section('message', __('Unauthorized'))
|
<p class="font-mono text-6xl font-semibold text-warning">401</p>
|
||||||
|
<h1 class="mt-4 font-bold tracking-tight text-white">You shall not pass!</h1>
|
||||||
|
<p class="mt-6 text-base leading-7 text-neutral-300">You don't have permission to access this page.
|
||||||
|
</p>
|
||||||
|
<div class="flex items-center justify-center mt-10 gap-x-6">
|
||||||
|
<a href="/">
|
||||||
|
<x-forms.button>Go back home</x-forms.button>
|
||||||
|
</a>
|
||||||
|
<a target="_blank" class="text-xs" href="https://docs.coollabs.io/contact.html">Contact
|
||||||
|
support
|
||||||
|
<x-external-link />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,20 @@
|
|||||||
@extends('errors::minimal')
|
@extends('layouts.base')
|
||||||
|
<div class="min-h-screen hero">
|
||||||
@section('title', __('Forbidden'))
|
<div class="text-center hero-content">
|
||||||
@section('code', '403')
|
<div class="">
|
||||||
@section('message', __($exception->getMessage() ?: 'Forbidden'))
|
<p class="font-mono text-6xl font-semibold text-warning">403</p>
|
||||||
|
<h1 class="mt-4 font-bold tracking-tight text-white">You shall not pass!</h1>
|
||||||
|
<p class="mt-6 text-base leading-7 text-neutral-300">You don't have permission to access this page.
|
||||||
|
</p>
|
||||||
|
<div class="flex items-center justify-center mt-10 gap-x-6">
|
||||||
|
<a href="/">
|
||||||
|
<x-forms.button>Go back home</x-forms.button>
|
||||||
|
</a>
|
||||||
|
<a target="_blank" class="text-xs" href="https://docs.coollabs.io/contact.html">Contact
|
||||||
|
support
|
||||||
|
<x-external-link />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,22 +1,21 @@
|
|||||||
<x-layout>
|
@extends('layouts.base')
|
||||||
<div class="min-h-screen hero">
|
<div class="min-h-screen hero">
|
||||||
<div class="text-center hero-content">
|
<div class="text-center hero-content">
|
||||||
<div class="">
|
<div class="">
|
||||||
<p class="font-mono text-6xl font-semibold text-warning">404</p>
|
<p class="font-mono text-6xl font-semibold text-warning">404</p>
|
||||||
<h1 class="mt-4 font-bold tracking-tight text-white">How did you got here?</h1>
|
<h1 class="mt-4 font-bold tracking-tight text-white">How did you got here?</h1>
|
||||||
<p class="mt-6 text-base leading-7 text-neutral-300">Sorry, we couldn’t find the page you’re looking
|
<p class="mt-6 text-base leading-7 text-neutral-300">Sorry, we couldn’t find the page you’re looking
|
||||||
for.
|
for.
|
||||||
</p>
|
</p>
|
||||||
<div class="flex items-center justify-center mt-10 gap-x-6">
|
<div class="flex items-center justify-center mt-10 gap-x-6">
|
||||||
<a href="/">
|
<a href="/">
|
||||||
<x-forms.button isHighlighted>Go back home</x-forms.button>
|
<x-forms.button>Go back home</x-forms.button>
|
||||||
</a>
|
</a>
|
||||||
<a target="_blank" class="text-xs" href="https://docs.coollabs.io/contact.html">Contact
|
<a target="_blank" class="text-xs" href="https://docs.coollabs.io/contact.html">Contact
|
||||||
support
|
support
|
||||||
<x-external-link />
|
<x-external-link />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</x-layout>
|
</div>
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
<x-layout>
|
@extends('layouts.base')
|
||||||
<div class="min-h-screen hero">
|
<div class="min-h-screen hero">
|
||||||
<div class="text-center hero-content">
|
<div class="text-center hero-content">
|
||||||
<div class="">
|
<div class="">
|
||||||
<p class="font-mono text-6xl font-semibold text-warning">419</p>
|
<p class="font-mono text-6xl font-semibold text-warning">419</p>
|
||||||
<h1 class="mt-4 font-bold tracking-tight text-white">This page is definitely old</h1>
|
<h1 class="mt-4 font-bold tracking-tight text-white">This page is definitely old, not like you!</h1>
|
||||||
<p class="mt-6 text-base leading-7 text-neutral-300">Sorry, we couldn’t find the page you’re looking
|
<p class="mt-6 text-base leading-7 text-neutral-300">Sorry, we couldn’t find the page you’re looking
|
||||||
for.
|
for.
|
||||||
</p>
|
</p>
|
||||||
<div class="flex items-center justify-center mt-10 gap-x-6">
|
<div class="flex items-center justify-center mt-10 gap-x-6">
|
||||||
<a href="/"
|
<a href="/">
|
||||||
class="rounded-md bg-coollabs px-3.5 py-2.5 font-semibold text-white shadow-sm hover:bg-coollabs-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 hover:no-underline">Go
|
<x-forms.button>Go back home</x-forms.button>
|
||||||
back home</a>
|
</a>
|
||||||
<a href="https://docs.coollabs.io/contact.html" class="font-semibold text-white ">Contact
|
<a href="https://docs.coollabs.io/contact.html" class="font-semibold text-white ">Contact
|
||||||
support
|
support
|
||||||
<span aria-hidden="true">→</span></a>
|
<span aria-hidden="true">→</span></a>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</x-layout>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,19 @@
|
|||||||
@extends('errors::minimal')
|
@extends('layouts.base')
|
||||||
|
<div class="min-h-screen hero">
|
||||||
@section('title', __('Too Many Requests'))
|
<div class="text-center hero-content">
|
||||||
@section('code', '429')
|
<div class="">
|
||||||
@section('message', __('Too Many Requests'))
|
<p class="font-mono text-6xl font-semibold text-warning">429</p>
|
||||||
|
<h1 class="mt-4 font-bold tracking-tight text-white">Woah, slow down there!</h1>
|
||||||
|
<p class="mt-6 text-base leading-7 text-neutral-300">You're making too many requests. Please wait a few seconds before trying again.
|
||||||
|
</p>
|
||||||
|
<div class="flex items-center justify-center mt-10 gap-x-6">
|
||||||
|
<a href="/">
|
||||||
|
<x-forms.button>Go back home</x-forms.button>
|
||||||
|
</a>
|
||||||
|
<a href="https://docs.coollabs.io/contact.html" class="font-semibold text-white ">Contact
|
||||||
|
support
|
||||||
|
<span aria-hidden="true">→</span></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user