mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-18 12:33:06 +00:00
Compare commits
219 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c735ff545e | ||
|
|
b4f048b028 | ||
|
|
54a57d217f | ||
|
|
cf28490acc | ||
|
|
019670d5d1 | ||
|
|
b07cc500e7 | ||
|
|
82c235d5af | ||
|
|
307e4a6990 | ||
|
|
ddb7af63a6 | ||
|
|
7a429ee5bb | ||
|
|
bb591604ac | ||
|
|
81f7a65dd5 | ||
|
|
4b313bb1c6 | ||
|
|
aba0b2f13c | ||
|
|
9a284e47da | ||
|
|
9f2fbc661a | ||
|
|
949407368e | ||
|
|
93c65f6a79 | ||
|
|
d9fe16a3ee | ||
|
|
adaca4d4e3 | ||
|
|
a6d5f3038c | ||
|
|
4d49132821 | ||
|
|
c287276d0e | ||
|
|
e89868c692 | ||
|
|
f93317cd2e | ||
|
|
8412802f4d | ||
|
|
53c20e1e99 | ||
|
|
3c8c8e20b1 | ||
|
|
49f8abcd79 | ||
|
|
4a4d73b87b | ||
|
|
046eab3776 | ||
|
|
17c0e91a0d | ||
|
|
fe4a0ae166 | ||
|
|
52c84f8d22 | ||
|
|
b22fecb615 | ||
|
|
23b7fc3c54 | ||
|
|
1efb1235b4 | ||
|
|
0924070a13 | ||
|
|
12fc5a8f91 | ||
|
|
9eba058cf7 | ||
|
|
fa4f5fea8c | ||
|
|
898563fe7c | ||
|
|
c418a17161 | ||
|
|
cd0da04ea2 | ||
|
|
01e942c6a0 | ||
|
|
bb9abafa82 | ||
|
|
d0cd926517 | ||
|
|
9baf0161c7 | ||
|
|
8ba18b2ce1 | ||
|
|
ab021ee535 | ||
|
|
c76a1b1ba5 | ||
|
|
6266a5e500 | ||
|
|
5d27e89bfa | ||
|
|
6da4e78374 | ||
|
|
be30651172 | ||
|
|
95764c2b76 | ||
|
|
92a75685b5 | ||
|
|
a1592373aa | ||
|
|
5747a87f66 | ||
|
|
9cda671aef | ||
|
|
2c9983046c | ||
|
|
11d33f328e | ||
|
|
f79c741d95 | ||
|
|
8a39a4469a | ||
|
|
42daae10c6 | ||
|
|
64a65e2018 | ||
|
|
16c71f3647 | ||
|
|
363f525ad1 | ||
|
|
f4fb519d55 | ||
|
|
b7786504b8 | ||
|
|
f0adf10e6a | ||
|
|
cc8c6c5d16 | ||
|
|
4782446f42 | ||
|
|
230155312f | ||
|
|
3ab4365fca | ||
|
|
7349068b95 | ||
|
|
da6cc151d1 | ||
|
|
50527cf0a3 | ||
|
|
26f490bb00 | ||
|
|
dc4f412227 | ||
|
|
ec4234e243 | ||
|
|
f47fcb01ce | ||
|
|
02f6673345 | ||
|
|
e6cd8702b5 | ||
|
|
fda4ea8cca | ||
|
|
655d004ce7 | ||
|
|
56981d134c | ||
|
|
b9d49d2951 | ||
|
|
0c1e7c499e | ||
|
|
32fead5753 | ||
|
|
e5e9faba35 | ||
|
|
2852630d6c | ||
|
|
a4cc406114 | ||
|
|
53b15a5762 | ||
|
|
929a4e6474 | ||
|
|
45b597bbab | ||
|
|
0d1a2aa5d1 | ||
|
|
b82353d5e2 | ||
|
|
b17c09f7a7 | ||
|
|
f6c3fe7888 | ||
|
|
2e855e030f | ||
|
|
49f86621f4 | ||
|
|
03d9f93397 | ||
|
|
c472042a94 | ||
|
|
9f4356f67d | ||
|
|
a50317cc76 | ||
|
|
8afa98a1ca | ||
|
|
f6737f21dd | ||
|
|
e4a51cc116 | ||
|
|
acd78ae196 | ||
|
|
953bcfb5bb | ||
|
|
dacfab8b29 | ||
|
|
48b3e99939 | ||
|
|
41ad67c7c9 | ||
|
|
b49725cb1c | ||
|
|
75e674a966 | ||
|
|
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 | ||
|
|
806b761e74 | ||
|
|
0176b38958 | ||
|
|
7a180c7310 | ||
|
|
3e1120182c | ||
|
|
8e86ce671c | ||
|
|
f75a324030 | ||
|
|
3fabff93f6 | ||
|
|
e74efc4e76 | ||
|
|
472ed0753d | ||
|
|
67538ff60c | ||
|
|
ae8bd69106 | ||
|
|
2538890b52 | ||
|
|
87dd819ae4 | ||
|
|
7ec560d4a2 | ||
|
|
6f9cd6a16b | ||
|
|
923af88336 | ||
|
|
5b6667c461 | ||
|
|
6f00740f67 | ||
|
|
248863cf16 | ||
|
|
97d48823dd | ||
|
|
5eb41e1a15 | ||
|
|
4a4837d9f5 | ||
|
|
4ad72fab7b | ||
|
|
fe68e45609 | ||
|
|
291b9a84ef | ||
|
|
2f9b7b188a | ||
|
|
d04d41bc23 | ||
|
|
6cb3d7167f | ||
|
|
90b1659a18 | ||
|
|
1aaf44f9b0 | ||
|
|
d7cfb84351 | ||
|
|
d28cf0b76d | ||
|
|
b4a3236284 | ||
|
|
556168892d | ||
|
|
77667be570 | ||
|
|
f48a912287 | ||
|
|
af30d0831d | ||
|
|
5989eb8f6e | ||
|
|
2bb778834b | ||
|
|
a5ce191e4d | ||
|
|
7617756576 | ||
|
|
0dfd3a5b0e | ||
|
|
61a54f48c5 | ||
|
|
5ca0237e34 | ||
|
|
ab1207e461 | ||
|
|
75fea4f7c0 | ||
|
|
fb34eb5394 |
@@ -6,6 +6,7 @@
|
||||
USERID=
|
||||
GROUPID=
|
||||
############################################################################################################
|
||||
APP_NAME=Coolify-localhost
|
||||
APP_ID=development
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
|
||||
26
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
Normal file
26
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: Bug report
|
||||
description: Create a new bug report
|
||||
title: '[Bug]: '
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: A clear and concise description of the problem
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Minimal Reproduction (if possible, example repository)
|
||||
description: Please provide a step by step guide to reproduce the issue
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Exception or Error
|
||||
description: Please provide error logs if possible.
|
||||
- type: input
|
||||
attributes:
|
||||
label: Version
|
||||
description: Coolify's version (see bottom left corner).
|
||||
validations:
|
||||
required: true
|
||||
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 🤔 Community Support (Chat)
|
||||
url: https://coollabs.io/discord
|
||||
about: Reach out to us on Discord.
|
||||
- name: 🙋♂️ Feature Requests
|
||||
url: https://github.com/coollabsio/coolify/discussions/categories/feature-requests-ideas
|
||||
about: All feature requests will be discussed here.
|
||||
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:
|
||||
push:
|
||||
branches: [ "main", "next" ]
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- .github/workflows/coolify-helper.yml
|
||||
- docker/coolify-helper/Dockerfile
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
file: docker/coolify-helper/Dockerfile
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:aarch64
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64
|
||||
merge-manifest:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
@@ -78,3 +78,7 @@ jobs:
|
||||
- name: Create & publish manifest
|
||||
run: |
|
||||
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 }}
|
||||
|
||||
7
.github/workflows/development-build.yml
vendored
7
.github/workflows/development-build.yml
vendored
@@ -3,6 +3,9 @@ name: Development Build (v4)
|
||||
on:
|
||||
push:
|
||||
branches: ["next"]
|
||||
paths-ignore:
|
||||
- .github/workflows/coolify-helper.yml
|
||||
- docker/coolify-helper/Dockerfile
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
@@ -10,7 +13,7 @@ env:
|
||||
|
||||
jobs:
|
||||
amd64:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: [self-hosted, x64]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Login to ghcr.io
|
||||
@@ -73,4 +76,4 @@ jobs:
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||
|
||||
2
.github/workflows/production-build.yml
vendored
2
.github/workflows/production-build.yml
vendored
@@ -10,7 +10,7 @@ env:
|
||||
|
||||
jobs:
|
||||
amd64:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: [self-hosted, x64]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Login to ghcr.io
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -31,3 +31,4 @@ _ide_helper.php
|
||||
_ide_helper_models.php
|
||||
.rnd
|
||||
/.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
|
||||
|
||||
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.
|
||||
|
||||
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).
|
||||
There are hundreds of people using it for managing their client's applications, freelancers, hobbyists, businesses.
|
||||
|
||||
# Installation
|
||||
|
||||
@@ -26,13 +36,19 @@ You can find the installation script [here](./scripts/install.sh).
|
||||
|
||||
## Support
|
||||
|
||||
- Twitter: [@heyandras](https://twitter.com/heyandras)
|
||||
- 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)
|
||||
Contact us [here](https://docs.coollabs.io/contact).
|
||||
|
||||
---
|
||||
## 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
|
||||
|
||||
|
||||
@@ -10,9 +10,6 @@ use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
const TIMEOUT = 3600;
|
||||
const IDLE_TIMEOUT = 3600;
|
||||
|
||||
class RunRemoteProcess
|
||||
{
|
||||
public Activity $activity;
|
||||
@@ -76,8 +73,7 @@ class RunRemoteProcess
|
||||
$this->time_start = hrtime(true);
|
||||
|
||||
$status = ProcessStatus::IN_PROGRESS;
|
||||
|
||||
$processResult = Process::timeout(TIMEOUT)->idleTimeout(IDLE_TIMEOUT)->run($this->getCommand(), $this->handleOutput(...));
|
||||
$processResult = Process::forever()->run($this->getCommand(), $this->handleOutput(...));
|
||||
|
||||
if ($this->activity->properties->get('status') === ProcessStatus::ERROR->value) {
|
||||
$status = ProcessStatus::ERROR;
|
||||
@@ -97,7 +93,6 @@ class RunRemoteProcess
|
||||
'status' => $status->value,
|
||||
]);
|
||||
$this->activity->save();
|
||||
|
||||
if ($processResult->exitCode() != 0 && !$this->ignore_errors) {
|
||||
throw new \RuntimeException($processResult->errorOutput());
|
||||
}
|
||||
@@ -109,11 +104,10 @@ class RunRemoteProcess
|
||||
{
|
||||
$user = $this->activity->getExtraProperty('user');
|
||||
$server_ip = $this->activity->getExtraProperty('server_ip');
|
||||
$private_key_location = $this->activity->getExtraProperty('private_key_location');
|
||||
$port = $this->activity->getExtraProperty('port');
|
||||
$command = $this->activity->getExtraProperty('command');
|
||||
|
||||
return generate_ssh_command($private_key_location, $server_ip, $user, $port, $command);
|
||||
return generateSshCommand($server_ip, $user, $port, $command);
|
||||
}
|
||||
|
||||
protected function handleOutput(string $type, string $output)
|
||||
|
||||
@@ -65,7 +65,7 @@ class StartPostgresql
|
||||
],
|
||||
'networks' => [
|
||||
$this->database->destination->network => [
|
||||
'external' => false,
|
||||
'external' => true,
|
||||
'name' => $this->database->destination->network,
|
||||
'attachable' => true,
|
||||
]
|
||||
|
||||
@@ -57,13 +57,13 @@ class CheckResaleLicense
|
||||
throw new \Exception('Invalid license key.');
|
||||
}
|
||||
throw new \Exception('Cannot activate license key.');
|
||||
} catch (\Throwable $th) {
|
||||
ray($th);
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
$settings->update([
|
||||
'resale_license' => null,
|
||||
'is_resale_license_active' => false,
|
||||
]);
|
||||
throw $th;
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,7 @@ class CheckConfigurationSync
|
||||
|
||||
if ($reset || is_null($proxy_configuration)) {
|
||||
$proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value;
|
||||
resolve(SaveConfigurationSync::class)($server, $proxy_configuration);
|
||||
return $proxy_configuration;
|
||||
}
|
||||
|
||||
return $proxy_configuration;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,17 +7,23 @@ use Illuminate\Support\Str;
|
||||
|
||||
class SaveConfigurationSync
|
||||
{
|
||||
public function __invoke(Server $server, string $configuration)
|
||||
public function __invoke(Server $server)
|
||||
{
|
||||
$proxy_path = get_proxy_path();
|
||||
$docker_compose_yml_base64 = base64_encode($configuration);
|
||||
try {
|
||||
$proxy_settings = resolve(CheckConfigurationSync::class)($server, true);
|
||||
$proxy_path = get_proxy_path();
|
||||
$docker_compose_yml_base64 = base64_encode($proxy_settings);
|
||||
|
||||
$server->proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
||||
$server->save();
|
||||
$server->proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
||||
$server->save();
|
||||
|
||||
instant_remote_process([
|
||||
"mkdir -p $proxy_path",
|
||||
"echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
|
||||
], $server);
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
}
|
||||
|
||||
instant_remote_process([
|
||||
"mkdir -p $proxy_path",
|
||||
"echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
|
||||
], $server);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,22 +2,14 @@
|
||||
|
||||
namespace App\Actions\Proxy;
|
||||
|
||||
use App\Enums\ProxyStatus;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
class StartProxy
|
||||
{
|
||||
public function __invoke(Server $server): Activity
|
||||
public function __invoke(Server $server, bool $async = true): Activity|string
|
||||
{
|
||||
// TODO: check for other proxies
|
||||
if (is_null(data_get($server, 'proxy.type'))) {
|
||||
$server->proxy->type = ProxyTypes::TRAEFIK_V2->value;
|
||||
$server->proxy->status = ProxyStatus::EXITED->value;
|
||||
$server->save();
|
||||
}
|
||||
$proxy_path = get_proxy_path();
|
||||
$networks = collect($server->standaloneDockers)->map(function ($docker) {
|
||||
return $docker['network'];
|
||||
@@ -34,23 +26,30 @@ class StartProxy
|
||||
$docker_compose_yml_base64 = base64_encode($configuration);
|
||||
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
||||
$server->save();
|
||||
|
||||
$activity = remote_process([
|
||||
"echo 'Creating required Docker networks...'",
|
||||
$commands = [
|
||||
"echo '####### Creating required Docker networks...'",
|
||||
...$create_networks_command,
|
||||
"cd $proxy_path",
|
||||
"echo 'Creating Docker Compose file...'",
|
||||
"echo 'Pulling docker image...'",
|
||||
'docker compose pull -q',
|
||||
"echo 'Stopping existing proxy...'",
|
||||
"echo '####### Creating Docker Compose file...'",
|
||||
"echo '####### Pulling docker image...'",
|
||||
'docker compose pull',
|
||||
"echo '####### Stopping existing coolify-proxy...'",
|
||||
'docker compose down -v --remove-orphans',
|
||||
"lsof -nt -i:80 | xargs -r kill -9",
|
||||
"lsof -nt -i:443 | xargs -r kill -9",
|
||||
"echo 'Starting proxy...'",
|
||||
"systemctl disable nginx > /dev/null 2>&1 || true",
|
||||
"systemctl disable apache2 > /dev/null 2>&1 || true",
|
||||
"systemctl disable apache > /dev/null 2>&1 || true",
|
||||
"echo '####### Starting coolify-proxy...'",
|
||||
'docker compose up -d --remove-orphans',
|
||||
"echo 'Proxy installed successfully...'"
|
||||
], $server);
|
||||
|
||||
return $activity;
|
||||
"echo '####### Proxy installed successfully...'"
|
||||
];
|
||||
if (!$async) {
|
||||
instant_remote_process($commands, $server);
|
||||
return 'OK';
|
||||
} else {
|
||||
$activity = remote_process($commands, $server);
|
||||
return $activity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,41 +10,50 @@ class InstallDocker
|
||||
{
|
||||
public function __invoke(Server $server, Team $team)
|
||||
{
|
||||
$dockerVersion = '23.0';
|
||||
$config = base64_encode('{ "live-restore": true }');
|
||||
if (isDev()) {
|
||||
$activity = remote_process([
|
||||
"echo ####### Installing Prerequisites...",
|
||||
"echo ####### Installing/updating Docker Engine...",
|
||||
"echo ####### Configuring Docker Engine (merging existing configuration with the required)...",
|
||||
"echo ####### Restarting Docker Engine...",
|
||||
], $server);
|
||||
} else {
|
||||
$activity = remote_process([
|
||||
"echo ####### Installing Prerequisites...",
|
||||
"command -v jq >/dev/null || apt-get update",
|
||||
"command -v jq >/dev/null || apt install -y jq",
|
||||
"echo ####### Installing/updating Docker Engine...",
|
||||
"curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh",
|
||||
"echo ####### Configuring Docker Engine (merging existing configuration with the required)...",
|
||||
"test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-`date +\"%Y%m%d-%H%M%S\"`\" || echo '{$config}' | base64 -d > /etc/docker/daemon.json",
|
||||
"echo '{$config}' | base64 -d > /etc/docker/daemon.json.coolify",
|
||||
"cat <<< $(jq . /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json.coolify",
|
||||
"cat <<< $(jq -s '.[0] * .[1]' /etc/docker/daemon.json /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json",
|
||||
"echo ####### Restarting Docker Engine...",
|
||||
"systemctl restart docker",
|
||||
"echo ####### Creating default network...",
|
||||
"docker network create --attachable coolify",
|
||||
"echo ####### Done!"
|
||||
], $server);
|
||||
$dockerVersion = '24.0';
|
||||
$config = base64_encode('{
|
||||
"log-driver": "json-file",
|
||||
"log-opts": {
|
||||
"max-size": "10m",
|
||||
"max-file": "3"
|
||||
}
|
||||
}');
|
||||
$found = StandaloneDocker::where('server_id', $server->id);
|
||||
if ($found->count() == 0) {
|
||||
StandaloneDocker::create([
|
||||
'name' => 'coolify',
|
||||
'network' => 'coolify',
|
||||
'server_id' => $server->id,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
return $activity;
|
||||
if (isDev()) {
|
||||
return remote_process([
|
||||
"echo '####### Installing Prerequisites...'",
|
||||
"sleep 1",
|
||||
"echo '####### Installing/updating Docker Engine...'",
|
||||
"echo '####### Configuring Docker Engine (merging existing configuration with the required)...'",
|
||||
"sleep 4",
|
||||
"echo '####### Restarting Docker Engine...'",
|
||||
"ls -l /tmp"
|
||||
], $server);
|
||||
} else {
|
||||
return remote_process([
|
||||
"echo '####### Installing Prerequisites...'",
|
||||
"command -v jq >/dev/null || apt-get update",
|
||||
"command -v jq >/dev/null || apt install -y jq",
|
||||
"echo '####### Installing/updating Docker Engine...'",
|
||||
"curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh",
|
||||
"echo '####### Configuring Docker Engine (merging existing configuration with the required)...'",
|
||||
"test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-`date +\"%Y%m%d-%H%M%S\"`\" || echo '{$config}' | base64 -d > /etc/docker/daemon.json",
|
||||
"echo '{$config}' | base64 -d > /etc/docker/daemon.json.coolify",
|
||||
"cat <<< $(jq . /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json.coolify",
|
||||
"cat <<< $(jq -s '.[0] * .[1]' /etc/docker/daemon.json /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json",
|
||||
"echo '####### Restarting Docker Engine...'",
|
||||
"systemctl restart docker",
|
||||
"echo '####### Creating default Docker network (coolify)...'",
|
||||
"docker network create --attachable coolify >/dev/null 2>&1 || true",
|
||||
"echo '####### Done!'"
|
||||
], $server);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ use App\Models\Server;
|
||||
|
||||
class UpdateCoolify
|
||||
{
|
||||
public Server $server;
|
||||
public string $latest_version;
|
||||
public string $current_version;
|
||||
public ?Server $server = null;
|
||||
public ?string $latestVersion = null;
|
||||
public ?string $currentVersion = null;
|
||||
|
||||
public function __invoke(bool $force)
|
||||
{
|
||||
@@ -17,13 +17,16 @@ class UpdateCoolify
|
||||
$settings = InstanceSettings::get();
|
||||
ray('Running InstanceAutoUpdateJob');
|
||||
$localhost_name = 'localhost';
|
||||
$this->server = Server::where('name', $localhost_name)->firstOrFail();
|
||||
$this->latest_version = get_latest_version_of_coolify();
|
||||
$this->current_version = config('version');
|
||||
ray('latest version:' . $this->latest_version . " current version: " . $this->current_version . ' force: ' . $force);
|
||||
$this->server = Server::where('name', $localhost_name)->first();
|
||||
if (!$this->server) {
|
||||
return;
|
||||
}
|
||||
$this->latestVersion = get_latest_version_of_coolify();
|
||||
$this->currentVersion = config('version');
|
||||
ray('latest version:' . $this->latestVersion . " current version: " . $this->currentVersion . ' force: ' . $force);
|
||||
if ($settings->next_channel) {
|
||||
ray('next channel enabled');
|
||||
$this->latest_version = 'next';
|
||||
$this->latestVersion = 'next';
|
||||
}
|
||||
if ($force) {
|
||||
$this->update();
|
||||
@@ -31,27 +34,27 @@ class UpdateCoolify
|
||||
if (!$settings->is_auto_update_enabled) {
|
||||
return 'Auto update is disabled';
|
||||
}
|
||||
if ($this->latest_version === $this->current_version) {
|
||||
if ($this->latestVersion === $this->currentVersion) {
|
||||
return 'Already on latest version';
|
||||
}
|
||||
if (version_compare($this->latest_version, $this->current_version, '<')) {
|
||||
if (version_compare($this->latestVersion, $this->currentVersion, '<')) {
|
||||
return 'Latest version is lower than current version?!';
|
||||
}
|
||||
$this->update();
|
||||
}
|
||||
send_internal_notification('InstanceAutoUpdateJob done to version: ' . $this->latest_version . ' from version: ' . $this->current_version);
|
||||
} catch (\Exception $th) {
|
||||
send_internal_notification('InstanceAutoUpdateJob done to version: ' . $this->latestVersion . ' from version: ' . $this->currentVersion);
|
||||
} catch (\Throwable $e) {
|
||||
ray('InstanceAutoUpdateJob failed');
|
||||
ray($th->getMessage());
|
||||
send_internal_notification('InstanceAutoUpdateJob failed: ' . $th->getMessage());
|
||||
throw $th;
|
||||
ray($e->getMessage());
|
||||
send_internal_notification('InstanceAutoUpdateJob failed: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
private function update()
|
||||
{
|
||||
if (isDev()) {
|
||||
ray("Running update on local docker container. Updating to $this->latest_version");
|
||||
ray("Running update on local docker container. Updating to $this->latestVersion");
|
||||
remote_process([
|
||||
"sleep 10"
|
||||
], $this->server);
|
||||
@@ -61,7 +64,7 @@ class UpdateCoolify
|
||||
ray('Running update on production server');
|
||||
remote_process([
|
||||
"curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh",
|
||||
"bash /data/coolify/source/upgrade.sh $this->latest_version"
|
||||
"bash /data/coolify/source/upgrade.sh $this->latestVersion"
|
||||
], $this->server);
|
||||
return;
|
||||
}
|
||||
|
||||
247
app/Console/Commands/Emails.php
Normal file
247
app/Console/Commands/Emails.php
Normal file
@@ -0,0 +1,247 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\SendConfirmationForWaitlistJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\Team;
|
||||
use App\Models\TeamInvitation;
|
||||
use App\Models\User;
|
||||
use App\Models\Waitlist;
|
||||
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 Illuminate\Support\Str;
|
||||
|
||||
use function Laravel\Prompts\confirm;
|
||||
use function Laravel\Prompts\select;
|
||||
use function Laravel\Prompts\text;
|
||||
|
||||
class Emails extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'emails';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Send out test / prod emails';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
private ?MailMessage $mail = null;
|
||||
private ?string $email = null;
|
||||
public function handle()
|
||||
{
|
||||
$type = 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',
|
||||
'realusers-before-trial' => 'REAL - Registered Users Before Trial without Subscription',
|
||||
'realusers-server-lost-connection' => 'REAL - Server Lost Connection',
|
||||
],
|
||||
);
|
||||
$emailsGathered = ['realusers-before-trial','realusers-server-lost-connection'];
|
||||
if (!in_array($type, $emailsGathered)) {
|
||||
$this->email = text('Email Address to send to');
|
||||
}
|
||||
set_transanctional_email_settings();
|
||||
|
||||
$this->mail = new MailMessage();
|
||||
$this->mail->subject("Test Email");
|
||||
switch ($type) {
|
||||
case 'emails-test':
|
||||
$this->mail = (new Test())->toMail();
|
||||
$this->sendEmail();
|
||||
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', [
|
||||
'loginLink' => 'https://coolify.io',
|
||||
]);
|
||||
$this->mail->subject('Congratulations! You are invited to join Coolify Cloud.');
|
||||
$this->sendEmail();
|
||||
break;
|
||||
case 'waitlist-confirmation':
|
||||
$found = Waitlist::where('email', $this->email)->first();
|
||||
if ($found) {
|
||||
SendConfirmationForWaitlistJob::dispatch($this->email, $found->uuid);
|
||||
} else {
|
||||
throw new Exception('Waitlist not found');
|
||||
}
|
||||
|
||||
break;
|
||||
case 'realusers-before-trial':
|
||||
$this->mail = new MailMessage();
|
||||
$this->mail->view('emails.before-trial-conversion');
|
||||
$this->mail->subject('Trial period has been added for all subscription plans.');
|
||||
$teams = Team::doesntHave('subscription')->where('id', '!=', 0)->get();
|
||||
if (!$teams || $teams->isEmpty()) {
|
||||
echo 'No teams found.' . PHP_EOL;
|
||||
return;
|
||||
}
|
||||
$emails = [];
|
||||
foreach ($teams as $team) {
|
||||
foreach ($team->members as $member) {
|
||||
if ($member->email) {
|
||||
$emails[] = $member->email;
|
||||
}
|
||||
}
|
||||
}
|
||||
$emails = array_unique($emails);
|
||||
$this->info("Sending to " . count($emails) . " emails.");
|
||||
foreach ($emails as $email) {
|
||||
$this->info($email);
|
||||
}
|
||||
$confirmed = confirm('Are you sure?');
|
||||
if ($confirmed) {
|
||||
foreach ($emails as $email) {
|
||||
$this->sendEmail($email);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'realusers-server-lost-connection':
|
||||
$serverId = text('Server Id');
|
||||
$server = Server::find($serverId);
|
||||
if (!$server) {
|
||||
throw new Exception('Server not found');
|
||||
}
|
||||
$admins = [];
|
||||
$members = $server->team->members;
|
||||
foreach ($members as $member) {
|
||||
if ($member->isAdmin()) {
|
||||
$admins[] = $member->email;
|
||||
}
|
||||
}
|
||||
$this->info('Sending to ' . count($admins) . ' admins.');
|
||||
foreach ($admins as $admin) {
|
||||
$this->info($admin);
|
||||
}
|
||||
$this->mail = new MailMessage();
|
||||
$this->mail->view('emails.server-lost-connection', [
|
||||
'name' => $server->name,
|
||||
]);
|
||||
$this->mail->subject('Action required: Server ' . $server->name . ' lost connection.');
|
||||
foreach ($admins as $email) {
|
||||
$this->sendEmail($email);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
private function sendEmail(string $email = null)
|
||||
{
|
||||
if ($email) {
|
||||
$this->email = $email;
|
||||
}
|
||||
Mail::send(
|
||||
[],
|
||||
[],
|
||||
fn (Message $message) => $message
|
||||
->to($this->email)
|
||||
->subject($this->mail->subject)
|
||||
->html((string)$this->mail->render())
|
||||
);
|
||||
$this->info("Email sent to $this->email successfully. 📧");
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ class Init extends Command
|
||||
$deployment->status = ApplicationDeploymentStatus::FAILED->value;
|
||||
$deployment->save();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error: {$e->getMessage()}\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ class SyncBunny extends Command
|
||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
|
||||
]);
|
||||
echo "All files uploaded & purged...\n";
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
echo $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,20 +6,20 @@ use App\Models\User;
|
||||
use App\Models\Waitlist;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class InviteFromWaitlist extends Command
|
||||
class WaitlistInvite extends Command
|
||||
{
|
||||
public Waitlist|null $next_patient = null;
|
||||
public User|null $new_user = null;
|
||||
public Waitlist|User|null $next_patient = null;
|
||||
public string|null $password = null;
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'app:invite-from-waitlist {email?}';
|
||||
protected $signature = 'waitlist:invite {--people=1} {--only-email} {email?}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@@ -33,8 +33,23 @@ class InviteFromWaitlist extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$people = $this->option('people');
|
||||
for ($i = 0; $i < $people; $i++) {
|
||||
$this->main();
|
||||
}
|
||||
}
|
||||
private function main() {
|
||||
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) {
|
||||
$this->error("{$this->argument('email')} not found in the waitlist.");
|
||||
return;
|
||||
@@ -43,6 +58,10 @@ class InviteFromWaitlist extends Command
|
||||
$this->next_patient = Waitlist::orderBy('created_at', 'asc')->where('verified', true)->first();
|
||||
}
|
||||
if ($this->next_patient) {
|
||||
if ($this->option('only-email')) {
|
||||
$this->send_email();
|
||||
return;
|
||||
}
|
||||
$this->register_user();
|
||||
$this->remove_from_waitlist();
|
||||
$this->send_email();
|
||||
@@ -55,7 +74,7 @@ class InviteFromWaitlist extends Command
|
||||
$already_registered = User::whereEmail($this->next_patient->email)->first();
|
||||
if (!$already_registered) {
|
||||
$this->password = Str::password();
|
||||
$this->new_user = User::create([
|
||||
User::create([
|
||||
'name' => Str::of($this->next_patient->email)->before('@'),
|
||||
'email' => $this->next_patient->email,
|
||||
'password' => Hash::make($this->password),
|
||||
@@ -73,10 +92,11 @@ class InviteFromWaitlist extends Command
|
||||
}
|
||||
private function send_email()
|
||||
{
|
||||
$token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password");
|
||||
$loginLink = route('auth.link', ['token' => $token]);
|
||||
$mail = new MailMessage();
|
||||
$mail->view('emails.waitlist-invitation', [
|
||||
'email' => $this->next_patient->email,
|
||||
'password' => $this->password,
|
||||
'loginLink' => $loginLink,
|
||||
]);
|
||||
$mail->subject('Congratulations! You are invited to join Coolify Cloud.');
|
||||
send_user_an_email($mail, $this->next_patient->email);
|
||||
@@ -7,9 +7,10 @@ use App\Jobs\CleanupInstanceStuffsJob;
|
||||
use App\Jobs\DatabaseBackupJob;
|
||||
use App\Jobs\DockerCleanupJob;
|
||||
use App\Jobs\InstanceAutoUpdateJob;
|
||||
use App\Jobs\ProxyCheckJob;
|
||||
use App\Jobs\ResourceStatusJob;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
|
||||
@@ -17,28 +18,44 @@ class Kernel extends ConsoleKernel
|
||||
{
|
||||
protected function schedule(Schedule $schedule): void
|
||||
{
|
||||
// $schedule->call(fn() => $this->check_scheduled_backups($schedule))->everyTenSeconds();
|
||||
if (isDev()) {
|
||||
$schedule->command('horizon:snapshot')->everyMinute();
|
||||
$schedule->job(new ResourceStatusJob)->everyMinute();
|
||||
$schedule->job(new ProxyCheckJob)->everyFiveMinutes();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute();
|
||||
|
||||
// $schedule->job(new ContainerStatusJob(Server::find(0)))->everyTenMinutes()->onOneServer();
|
||||
// $schedule->command('horizon:snapshot')->everyMinute();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
||||
// $schedule->job(new CheckResaleLicenseJob)->hourly();
|
||||
// $schedule->job(new DockerCleanupJob)->everyOddHour();
|
||||
// $schedule->job(new InstanceAutoUpdateJob(true))->everyMinute();
|
||||
// $this->instance_auto_update($schedule);
|
||||
// $this->check_scheduled_backups($schedule);
|
||||
$this->check_resources($schedule);
|
||||
} else {
|
||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute();
|
||||
$schedule->job(new ResourceStatusJob)->everyMinute();
|
||||
$schedule->job(new CheckResaleLicenseJob)->hourly();
|
||||
$schedule->job(new ProxyCheckJob)->everyFiveMinutes();
|
||||
$schedule->job(new DockerCleanupJob)->everyTenMinutes();
|
||||
$schedule->job(new InstanceAutoUpdateJob)->everyTenMinutes();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||
$schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
||||
$schedule->job(new DockerCleanupJob)->everyTenMinutes()->onOneServer();
|
||||
$this->instance_auto_update($schedule);
|
||||
$this->check_scheduled_backups($schedule);
|
||||
$this->check_resources($schedule);
|
||||
}
|
||||
$this->check_scheduled_backups($schedule);
|
||||
}
|
||||
private function check_resources($schedule)
|
||||
{
|
||||
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true);
|
||||
ray($servers);
|
||||
|
||||
foreach ($servers as $server) {
|
||||
$schedule->job(new ContainerStatusJob($server))->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)
|
||||
{
|
||||
ray('check_scheduled_backups');
|
||||
@@ -51,13 +68,18 @@ class Kernel extends ConsoleKernel
|
||||
if (!$scheduled_backup->enabled) {
|
||||
continue;
|
||||
}
|
||||
if (is_null(data_get($scheduled_backup, 'database'))) {
|
||||
ray('database not found');
|
||||
$scheduled_backup->delete();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
|
||||
$scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency];
|
||||
}
|
||||
$schedule->job(new DatabaseBackupJob(
|
||||
backup: $scheduled_backup
|
||||
))->cron($scheduled_backup->frequency);
|
||||
))->cron($scheduled_backup->frequency)->onOneServer();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ class CoolifyTaskArgs extends Data
|
||||
{
|
||||
public function __construct(
|
||||
public string $server_ip,
|
||||
public string $private_key_location,
|
||||
public string $command,
|
||||
public int $port,
|
||||
public string $user,
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Enums;
|
||||
|
||||
enum ProxyTypes: string
|
||||
{
|
||||
case NONE = 'NONE';
|
||||
case TRAEFIK_V2 = 'TRAEFIK_V2';
|
||||
case NGINX = 'NGINX';
|
||||
case CADDY = 'CADDY';
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Exceptions;
|
||||
use App\Models\InstanceSettings;
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Sentry\Laravel\Integration;
|
||||
use Sentry\State\Scope;
|
||||
use Throwable;
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
@@ -24,7 +25,7 @@ class Handler extends ExceptionHandler
|
||||
* @var array<int, class-string<\Throwable>>
|
||||
*/
|
||||
protected $dontReport = [
|
||||
//
|
||||
ProcessException::class
|
||||
];
|
||||
/**
|
||||
* A list of the inputs that are never flashed to the session on validation exceptions.
|
||||
@@ -48,6 +49,16 @@ class Handler extends ExceptionHandler
|
||||
if ($this->settings->do_not_track || isDev()) {
|
||||
return;
|
||||
}
|
||||
app('sentry')->configureScope(
|
||||
function (Scope $scope) {
|
||||
$scope->setUser(
|
||||
[
|
||||
'id' => config('sentry.server_name'),
|
||||
'email' => auth()->user()->email
|
||||
]
|
||||
);
|
||||
}
|
||||
);
|
||||
Integration::captureUnhandledException($e);
|
||||
});
|
||||
}
|
||||
|
||||
10
app/Exceptions/ProcessException.php
Normal file
10
app/Exceptions/ProcessException.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class ProcessException extends Exception
|
||||
{
|
||||
|
||||
}
|
||||
@@ -3,41 +3,63 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Project;
|
||||
use App\Models\S3Storage;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\TeamInvitation;
|
||||
use App\Models\User;
|
||||
use App\Models\Waitlist;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
use Throwable;
|
||||
|
||||
class Controller extends BaseController
|
||||
{
|
||||
use AuthorizesRequests, ValidatesRequests;
|
||||
|
||||
public function waitlist() {
|
||||
$waiting_in_line = Waitlist::whereVerified(true)->count();
|
||||
return view('auth.waitlist', [
|
||||
'waiting_in_line' => $waiting_in_line,
|
||||
]);
|
||||
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)) {
|
||||
$invitation = TeamInvitation::whereEmail($email);
|
||||
if ($invitation->exists()) {
|
||||
$team = $invitation->first()->team;
|
||||
$user->teams()->attach($team->id, ['role' => $invitation->first()->role]);
|
||||
$invitation->delete();
|
||||
} else {
|
||||
$team = $user->teams()->first();
|
||||
}
|
||||
Auth::login($user);
|
||||
session(['currentTeam' => $team]);
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
}
|
||||
return redirect()->route('login')->with('error', 'Invalid credentials.');
|
||||
}
|
||||
public function subscription()
|
||||
{
|
||||
if (!is_cloud()) {
|
||||
if (!isCloud()) {
|
||||
abort(404);
|
||||
}
|
||||
return view('subscription.show', [
|
||||
return view('subscription.index', [
|
||||
'settings' => InstanceSettings::get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function license()
|
||||
{
|
||||
if (!is_cloud()) {
|
||||
if (!isCloud()) {
|
||||
abort(404);
|
||||
}
|
||||
return view('settings.license', [
|
||||
@@ -45,27 +67,12 @@ class Controller extends BaseController
|
||||
]);
|
||||
}
|
||||
|
||||
public function force_passoword_reset() {
|
||||
public function force_passoword_reset()
|
||||
{
|
||||
return view('auth.force-password-reset');
|
||||
}
|
||||
public function dashboard()
|
||||
public function boarding()
|
||||
{
|
||||
$projects = Project::ownedByCurrentTeam()->get();
|
||||
$servers = Server::ownedByCurrentTeam()->get();
|
||||
$s3s = S3Storage::ownedByCurrentTeam()->get();
|
||||
$resources = 0;
|
||||
foreach ($projects as $project) {
|
||||
$resources += $project->applications->count();
|
||||
$resources += $project->postgresqls->count();
|
||||
}
|
||||
return view('dashboard', [
|
||||
'servers' => $servers->count(),
|
||||
'projects' => $projects->count(),
|
||||
'resources' => $resources,
|
||||
's3s' => $s3s,
|
||||
]);
|
||||
}
|
||||
public function boarding() {
|
||||
if (currentTeam()->boarding || isDev()) {
|
||||
return view('boarding');
|
||||
} else {
|
||||
@@ -97,7 +104,7 @@ class Controller extends BaseController
|
||||
if (auth()->user()->isAdminFromSession()) {
|
||||
$invitations = TeamInvitation::whereTeamId(currentTeam()->id)->get();
|
||||
}
|
||||
return view('team.show', [
|
||||
return view('team.index', [
|
||||
'invitations' => $invitations,
|
||||
]);
|
||||
}
|
||||
@@ -134,25 +141,21 @@ class Controller extends BaseController
|
||||
try {
|
||||
$invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail();
|
||||
$user = User::whereEmail($invitation->email)->firstOrFail();
|
||||
if (is_null(auth()->user())) {
|
||||
return redirect()->route('login');
|
||||
}
|
||||
if (auth()->user()->id !== $user->id) {
|
||||
abort(401);
|
||||
}
|
||||
|
||||
$createdAt = $invitation->created_at;
|
||||
$diff = $createdAt->diffInMinutes(now());
|
||||
if ($diff <= config('constants.invitation.link.expiration')) {
|
||||
$invitationValid = $invitation->isValid();
|
||||
if ($invitationValid) {
|
||||
$user->teams()->attach($invitation->team->id, ['role' => $invitation->role]);
|
||||
refreshSession($invitation->team);
|
||||
$invitation->delete();
|
||||
return redirect()->route('team.show');
|
||||
return redirect()->route('team.index');
|
||||
} else {
|
||||
$invitation->delete();
|
||||
abort(401);
|
||||
}
|
||||
} catch (Throwable $th) {
|
||||
throw $th;
|
||||
} catch (Throwable $e) {
|
||||
ray($e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,9 +171,9 @@ class Controller extends BaseController
|
||||
abort(401);
|
||||
}
|
||||
$invitation->delete();
|
||||
return redirect()->route('team.show');
|
||||
} catch (Throwable $th) {
|
||||
throw $th;
|
||||
return redirect()->route('team.index');
|
||||
} catch (Throwable $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ class ProjectController extends Controller
|
||||
{
|
||||
$type = request()->query('type');
|
||||
$destination_uuid = request()->query('destination');
|
||||
$server = requesT()->query('server');
|
||||
|
||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||
if (!$project) {
|
||||
|
||||
@@ -12,20 +12,21 @@ class ServerController extends Controller
|
||||
|
||||
public function new_server()
|
||||
{
|
||||
if (!is_cloud() || isInstanceAdmin()) {
|
||||
$privateKeys = PrivateKey::ownedByCurrentTeam()->get();
|
||||
if (!isCloud()) {
|
||||
return view('server.create', [
|
||||
'limit_reached' => false,
|
||||
'private_keys' => PrivateKey::ownedByCurrentTeam()->get(),
|
||||
'private_keys' => $privateKeys,
|
||||
]);
|
||||
}
|
||||
$servers = currentTeam()->servers->count();
|
||||
$subscription = currentTeam()?->subscription->type();
|
||||
$your_limit = config('constants.limits.server')[strtolower($subscription)];
|
||||
$limit_reached = $servers >= $your_limit;
|
||||
$team = currentTeam();
|
||||
$servers = $team->servers->count();
|
||||
['serverLimit' => $serverLimit] = $team->limits;
|
||||
$limit_reached = $servers >= $serverLimit;
|
||||
|
||||
return view('server.create', [
|
||||
'limit_reached' => $limit_reached,
|
||||
'private_keys' => PrivateKey::ownedByCurrentTeam()->get(),
|
||||
'private_keys' => $privateKeys,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ class Kernel extends HttpKernel
|
||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
\App\Http\Middleware\CheckForcePasswordReset::class,
|
||||
\App\Http\Middleware\SubscriptionValid::class,
|
||||
\App\Http\Middleware\IsSubscriptionValid::class,
|
||||
\App\Http\Middleware\IsBoardingFlow::class,
|
||||
|
||||
],
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
namespace App\Http\Livewire\Boarding;
|
||||
|
||||
use App\Actions\Server\InstallDocker;
|
||||
use App\Models\PrivateKey;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
|
||||
class Boarding extends Component
|
||||
class Index extends Component
|
||||
{
|
||||
public string $currentState = 'welcome';
|
||||
|
||||
public ?Collection $privateKeys = null;
|
||||
public ?int $selectedExistingPrivateKey = null;
|
||||
public ?string $privateKeyType = null;
|
||||
public ?string $privateKey = null;
|
||||
public ?string $publicKey = null;
|
||||
@@ -19,6 +23,8 @@ class Boarding extends Component
|
||||
public ?string $privateKeyDescription = null;
|
||||
public ?PrivateKey $createdPrivateKey = null;
|
||||
|
||||
public ?Collection $servers = null;
|
||||
public ?int $selectedExistingServer = null;
|
||||
public ?string $remoteServerName = null;
|
||||
public ?string $remoteServerDescription = null;
|
||||
public ?string $remoteServerHost = null;
|
||||
@@ -26,6 +32,8 @@ class Boarding extends Component
|
||||
public ?string $remoteServerUser = 'root';
|
||||
public ?Server $createdServer = null;
|
||||
|
||||
public Collection|array $projects = [];
|
||||
public ?int $selectedExistingProject = null;
|
||||
public ?Project $createdProject = null;
|
||||
|
||||
public function mount()
|
||||
@@ -45,6 +53,13 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
$this->remoteServerHost = 'coolify-testing-host';
|
||||
}
|
||||
}
|
||||
public function explanation() {
|
||||
if (isCloud()) {
|
||||
return $this->setServerType('remote');
|
||||
}
|
||||
$this->currentState = 'select-server-type';
|
||||
}
|
||||
|
||||
public function restartBoarding()
|
||||
{
|
||||
if ($this->createdServer) {
|
||||
@@ -57,28 +72,71 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
}
|
||||
public function skipBoarding()
|
||||
{
|
||||
currentTeam()->update([
|
||||
Team::find(currentTeam()->id)->update([
|
||||
'show_boarding' => false
|
||||
]);
|
||||
ray(currentTeam());
|
||||
refreshSession();
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
public function setServer(string $type)
|
||||
|
||||
public function setServerType(string $type)
|
||||
{
|
||||
if ($type === 'localhost') {
|
||||
$this->createdServer = Server::find(0);
|
||||
if (!$this->createdServer) {
|
||||
return $this->emit('error', 'Localhost server is not found. Something went wrong during installation. Please try to reinstall or contact support.');
|
||||
}
|
||||
$this->currentState = 'select-proxy';
|
||||
return $this->validateServer();
|
||||
} elseif ($type === 'remote') {
|
||||
$this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
|
||||
if ($this->privateKeys->count() > 0) {
|
||||
$this->selectedExistingPrivateKey = $this->privateKeys->first()->id;
|
||||
}
|
||||
$this->servers = Server::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
|
||||
if ($this->servers->count() > 0) {
|
||||
$this->selectedExistingServer = $this->servers->first()->id;
|
||||
$this->currentState = 'select-existing-server';
|
||||
return;
|
||||
}
|
||||
$this->currentState = 'private-key';
|
||||
}
|
||||
}
|
||||
public function selectExistingServer()
|
||||
{
|
||||
$this->createdServer = Server::find($this->selectedExistingServer);
|
||||
if (!$this->createdServer) {
|
||||
$this->emit('error', 'Server is not found.');
|
||||
$this->currentState = 'private-key';
|
||||
return;
|
||||
}
|
||||
$this->selectedExistingPrivateKey = $this->createdServer->privateKey->id;
|
||||
$this->validateServer();
|
||||
$this->getProxyType();
|
||||
$this->getProjects();
|
||||
}
|
||||
public function getProxyType() {
|
||||
$proxyTypeSet = $this->createdServer->proxy->type;
|
||||
if (!$proxyTypeSet) {
|
||||
$this->currentState = 'select-proxy';
|
||||
return;
|
||||
}
|
||||
$this->getProjects();
|
||||
}
|
||||
public function selectExistingPrivateKey()
|
||||
{
|
||||
$this->currentState = 'create-server';
|
||||
}
|
||||
public function createNewServer()
|
||||
{
|
||||
$this->selectedExistingServer = null;
|
||||
$this->currentState = 'private-key';
|
||||
}
|
||||
public function setPrivateKey(string $type)
|
||||
{
|
||||
$this->selectedExistingPrivateKey = null;
|
||||
$this->privateKeyType = $type;
|
||||
if ($type === 'create' && !isDev()) {
|
||||
if ($type === 'create') {
|
||||
$this->createNewPrivateKey();
|
||||
}
|
||||
$this->currentState = 'create-private-key';
|
||||
@@ -115,11 +173,12 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
'private_key_id' => $this->createdPrivateKey->id,
|
||||
'team_id' => currentTeam()->id
|
||||
]);
|
||||
$this->validateServer();
|
||||
}
|
||||
public function validateServer() {
|
||||
try {
|
||||
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->createdServer);
|
||||
if (!$uptime) {
|
||||
$this->createdServer->delete();
|
||||
$this->createdPrivateKey->delete();
|
||||
throw new \Exception('Server is not reachable.');
|
||||
} else {
|
||||
$this->createdServer->settings->update([
|
||||
@@ -127,12 +186,15 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
]);
|
||||
$this->emit('success', 'Server is reachable.');
|
||||
}
|
||||
if ($dockerVersion) {
|
||||
ray($dockerVersion, $uptime);
|
||||
if (!$dockerVersion) {
|
||||
$this->emit('error', 'Docker is not installed on the server.');
|
||||
$this->currentState = 'install-docker';
|
||||
return;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->getProxyType();
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(customErrorMessage: "Server is not reachable. Reason: {$e->getMessage()}", that: $this);
|
||||
}
|
||||
}
|
||||
@@ -145,13 +207,25 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
public function selectProxy(string|null $proxyType = null)
|
||||
{
|
||||
if (!$proxyType) {
|
||||
return $this->currentState = 'create-project';
|
||||
return $this->getProjects();
|
||||
}
|
||||
$this->createdServer->proxy->type = $proxyType;
|
||||
$this->createdServer->proxy->status = 'exited';
|
||||
$this->createdServer->save();
|
||||
$this->getProjects();
|
||||
}
|
||||
|
||||
public function getProjects() {
|
||||
$this->projects = Project::ownedByCurrentTeam(['name'])->get();
|
||||
if ($this->projects->count() > 0) {
|
||||
$this->selectedExistingProject = $this->projects->first()->id;
|
||||
}
|
||||
$this->currentState = 'create-project';
|
||||
}
|
||||
public function selectExistingProject() {
|
||||
$this->createdProject = Project::find($this->selectedExistingProject);
|
||||
$this->currentState = 'create-resource';
|
||||
}
|
||||
public function createNewProject()
|
||||
{
|
||||
$this->createdProject = Project::create([
|
||||
@@ -168,7 +242,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
[
|
||||
'project_uuid' => $this->createdProject->uuid,
|
||||
'environment_name' => 'production',
|
||||
|
||||
'server'=> $this->createdServer->id,
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -176,6 +250,10 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
{
|
||||
$this->privateKeyName = generate_random_name();
|
||||
$this->privateKeyDescription = 'Created by Coolify';
|
||||
['private' => $this->privateKey, 'public'=> $this->publicKey] = generateSSHKey();
|
||||
['private' => $this->privateKey, 'public' => $this->publicKey] = generateSSHKey();
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.boarding.index')->layout('layouts.boarding');
|
||||
}
|
||||
}
|
||||
@@ -34,9 +34,9 @@ class CheckLicense extends Component
|
||||
try {
|
||||
resolve(CheckResaleLicense::class)();
|
||||
$this->emit('reloadWindow');
|
||||
} catch (\Throwable $th) {
|
||||
session()->flash('error', 'Something went wrong. Please contact support. <br>Error: ' . $th->getMessage());
|
||||
ray($th->getMessage());
|
||||
} catch (\Throwable $e) {
|
||||
session()->flash('error', 'Something went wrong. Please contact support. <br>Error: ' . $e->getMessage());
|
||||
ray($e->getMessage());
|
||||
return redirect()->to('/settings/license');
|
||||
}
|
||||
}
|
||||
|
||||
41
app/Http/Livewire/Dashboard.php
Normal file
41
app/Http/Livewire/Dashboard.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
|
||||
use App\Models\Project;
|
||||
use App\Models\S3Storage;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
|
||||
class Dashboard extends Component
|
||||
{
|
||||
public int $projects = 0;
|
||||
public int $servers = 0;
|
||||
public int $s3s = 0;
|
||||
public int $resources = 0;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->servers = Server::ownedByCurrentTeam()->get()->count();
|
||||
$this->s3s = S3Storage::ownedByCurrentTeam()->get()->count();
|
||||
$projects = Project::ownedByCurrentTeam()->get();
|
||||
foreach ($projects as $project) {
|
||||
$this->resources += $project->applications->count();
|
||||
$this->resources += $project->postgresqls->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()
|
||||
{
|
||||
return view('livewire.dashboard');
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ class Form extends Component
|
||||
}
|
||||
$this->destination->delete();
|
||||
return redirect()->route('dashboard');
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ class StandaloneDocker extends Component
|
||||
}
|
||||
$this->createNetworkAndAttachToProxy();
|
||||
return redirect()->route('destination.show', $docker->uuid);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Dev;
|
||||
|
||||
use App\Models\S3Storage;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
|
||||
class S3Test extends Component
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
public $s3;
|
||||
public $file;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->s3 = S3Storage::first();
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
try {
|
||||
$this->validate([
|
||||
'file' => 'required|max:150', // 1MB Max
|
||||
]);
|
||||
set_s3_target($this->s3);
|
||||
$this->file->storeAs('files', $this->file->getClientOriginalName(), 'custom-s3');
|
||||
$this->emit('success', 'File uploaded successfully.');
|
||||
} catch (\Throwable $th) {
|
||||
return general_error_handler($th, $this, false);
|
||||
}
|
||||
}
|
||||
|
||||
public function get_files()
|
||||
{
|
||||
set_s3_target($this->s3);
|
||||
dd(Storage::disk('custom-s3')->files('files'));
|
||||
}
|
||||
}
|
||||
@@ -18,22 +18,26 @@ class ForcePasswordReset extends Component
|
||||
'password' => 'required|min:8',
|
||||
'password_confirmation' => 'required|same:password',
|
||||
];
|
||||
public function mount() {
|
||||
public function mount()
|
||||
{
|
||||
$this->email = auth()->user()->email;
|
||||
}
|
||||
public function submit() {
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->rateLimit(10);
|
||||
$this->validate();
|
||||
$firstLogin = auth()->user()->created_at == auth()->user()->updated_at;
|
||||
auth()->user()->forceFill([
|
||||
'password' => Hash::make($this->password),
|
||||
'force_password_reset' => false,
|
||||
])->save();
|
||||
auth()->logout();
|
||||
return redirect()->route('login')->with('status', 'Your initial password has been set.');
|
||||
} catch(\Exception $e) {
|
||||
return general_error_handler(err:$e, that:$this);
|
||||
if ($firstLogin) {
|
||||
send_internal_notification('First login for ' . auth()->user()->email);
|
||||
}
|
||||
return redirect()->route('dashboard');
|
||||
} catch (\Throwable $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() ?? null;
|
||||
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() ?? 'Free';
|
||||
$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, auth()->user()?->email, 'hi@coollabs.io');
|
||||
$this->emit('success', 'Your message has been sent successfully. We will get in touch with you as soon as possible.');
|
||||
} catch (\Throwable $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
|
||||
{
|
||||
public Team $model;
|
||||
public Team $team;
|
||||
protected $rules = [
|
||||
'model.discord_enabled' => 'nullable|boolean',
|
||||
'model.discord_webhook_url' => 'required|url',
|
||||
'model.discord_notifications_test' => 'nullable|boolean',
|
||||
'model.discord_notifications_deployments' => 'nullable|boolean',
|
||||
'model.discord_notifications_status_changes' => 'nullable|boolean',
|
||||
'model.discord_notifications_database_backups' => 'nullable|boolean',
|
||||
'team.discord_enabled' => 'nullable|boolean',
|
||||
'team.discord_webhook_url' => 'required|url',
|
||||
'team.discord_notifications_test' => 'nullable|boolean',
|
||||
'team.discord_notifications_deployments' => 'nullable|boolean',
|
||||
'team.discord_notifications_status_changes' => 'nullable|boolean',
|
||||
'team.discord_notifications_database_backups' => 'nullable|boolean',
|
||||
];
|
||||
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()
|
||||
{
|
||||
try {
|
||||
$this->submit();
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
ray($e->getMessage());
|
||||
$this->model->discord_enabled = false;
|
||||
$this->team->discord_enabled = false;
|
||||
$this->validate();
|
||||
}
|
||||
}
|
||||
@@ -41,16 +45,13 @@ class DiscordSettings extends Component
|
||||
|
||||
public function saveModel()
|
||||
{
|
||||
$this->model->save();
|
||||
if (is_a($this->model, Team::class)) {
|
||||
refreshSession();
|
||||
}
|
||||
$this->team->save();
|
||||
$this->emit('success', 'Settings saved.');
|
||||
}
|
||||
|
||||
public function sendTestNotification()
|
||||
{
|
||||
$this->model->notify(new Test);
|
||||
$this->team->notify(new Test());
|
||||
$this->emit('success', 'Test notification sent.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,55 +6,149 @@ use App\Models\InstanceSettings;
|
||||
use App\Models\Team;
|
||||
use App\Notifications\Test;
|
||||
use Livewire\Component;
|
||||
use Log;
|
||||
|
||||
class EmailSettings extends Component
|
||||
{
|
||||
public Team $model;
|
||||
public Team $team;
|
||||
public string $emails;
|
||||
public bool $sharedEmailEnabled = false;
|
||||
|
||||
protected $rules = [
|
||||
'model.smtp_enabled' => 'nullable|boolean',
|
||||
'model.smtp_from_address' => 'required|email',
|
||||
'model.smtp_from_name' => 'required',
|
||||
'model.smtp_recipients' => 'nullable',
|
||||
'model.smtp_host' => 'required',
|
||||
'model.smtp_port' => 'required',
|
||||
'model.smtp_encryption' => 'nullable',
|
||||
'model.smtp_username' => 'nullable',
|
||||
'model.smtp_password' => 'nullable',
|
||||
'model.smtp_timeout' => 'nullable',
|
||||
'model.smtp_notifications_test' => 'nullable|boolean',
|
||||
'model.smtp_notifications_deployments' => 'nullable|boolean',
|
||||
'model.smtp_notifications_status_changes' => 'nullable|boolean',
|
||||
'model.smtp_notifications_database_backups' => 'nullable|boolean',
|
||||
'team.smtp_enabled' => 'nullable|boolean',
|
||||
'team.smtp_from_address' => 'required|email',
|
||||
'team.smtp_from_name' => 'required',
|
||||
'team.smtp_recipients' => 'nullable',
|
||||
'team.smtp_host' => 'required',
|
||||
'team.smtp_port' => 'required',
|
||||
'team.smtp_encryption' => 'nullable',
|
||||
'team.smtp_username' => 'nullable',
|
||||
'team.smtp_password' => 'nullable',
|
||||
'team.smtp_timeout' => 'nullable',
|
||||
'team.smtp_notifications_test' => 'nullable|boolean',
|
||||
'team.smtp_notifications_deployments' => 'nullable|boolean',
|
||||
'team.smtp_notifications_status_changes' => 'nullable|boolean',
|
||||
'team.smtp_notifications_database_backups' => 'nullable|boolean',
|
||||
'team.use_instance_email_settings' => 'boolean',
|
||||
'team.resend_enabled' => 'nullable|boolean',
|
||||
'team.resend_api_key' => 'nullable',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'model.smtp_from_address' => 'From Address',
|
||||
'model.smtp_from_name' => 'From Name',
|
||||
'model.smtp_recipients' => 'Recipients',
|
||||
'model.smtp_host' => 'Host',
|
||||
'model.smtp_port' => 'Port',
|
||||
'model.smtp_encryption' => 'Encryption',
|
||||
'model.smtp_username' => 'Username',
|
||||
'model.smtp_password' => 'Password',
|
||||
'team.smtp_from_address' => 'From Address',
|
||||
'team.smtp_from_name' => 'From Name',
|
||||
'team.smtp_recipients' => 'Recipients',
|
||||
'team.smtp_host' => 'Host',
|
||||
'team.smtp_port' => 'Port',
|
||||
'team.smtp_encryption' => 'Encryption',
|
||||
'team.smtp_username' => 'Username',
|
||||
'team.smtp_password' => 'Password',
|
||||
'team.smtp_timeout' => 'Timeout',
|
||||
'team.resend_enabled' => 'Resend Enabled',
|
||||
'team.resend_api_key' => 'Resend API Key',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->decrypt();
|
||||
$this->team = auth()->user()->currentTeam();
|
||||
['sharedEmailEnabled' => $this->sharedEmailEnabled] = $this->team->limits;
|
||||
$this->emails = auth()->user()->email;
|
||||
}
|
||||
|
||||
private function decrypt()
|
||||
public function submitFromFields()
|
||||
{
|
||||
if (data_get($this->model, 'smtp_password')) {
|
||||
try {
|
||||
$this->model->smtp_password = decrypt($this->model->smtp_password);
|
||||
} catch (\Exception $e) {
|
||||
try {
|
||||
$this->resetErrorBag();
|
||||
$this->validate([
|
||||
'team.smtp_from_address' => 'required|email',
|
||||
'team.smtp_from_name' => 'required',
|
||||
]);
|
||||
$this->team->save();
|
||||
$this->emit('success', 'Settings saved successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler($e, $this);
|
||||
}
|
||||
}
|
||||
public function sendTestNotification()
|
||||
{
|
||||
$this->team->notify(new Test($this->emails));
|
||||
$this->emit('success', 'Test Email sent successfully.');
|
||||
}
|
||||
public function instantSaveInstance()
|
||||
{
|
||||
try {
|
||||
if (!$this->sharedEmailEnabled) {
|
||||
throw new \Exception('Not allowed to change settings. Please upgrade your subscription.');
|
||||
}
|
||||
$this->team->smtp_enabled = false;
|
||||
$this->team->resend_enabled = false;
|
||||
$this->team->save();
|
||||
$this->emit('success', 'Settings saved successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSaveResend()
|
||||
{
|
||||
try {
|
||||
$this->team->smtp_enabled = false;
|
||||
$this->submitResend();
|
||||
} catch (\Throwable $e) {
|
||||
$this->team->smtp_enabled = false;
|
||||
return general_error_handler($e, $this);
|
||||
}
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
try {
|
||||
$this->team->resend_enabled = false;
|
||||
$this->submit();
|
||||
} catch (\Throwable $e) {
|
||||
$this->team->smtp_enabled = false;
|
||||
return general_error_handler($e, $this);
|
||||
}
|
||||
}
|
||||
public function saveModel()
|
||||
{
|
||||
$this->team->save();
|
||||
$this->emit('success', 'Settings saved.');
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->resetErrorBag();
|
||||
$this->validate([
|
||||
'team.smtp_from_address' => 'required|email',
|
||||
'team.smtp_from_name' => 'required',
|
||||
'team.smtp_host' => 'required',
|
||||
'team.smtp_port' => 'required|numeric',
|
||||
'team.smtp_encryption' => 'nullable',
|
||||
'team.smtp_username' => 'nullable',
|
||||
'team.smtp_password' => 'nullable',
|
||||
'team.smtp_timeout' => 'nullable',
|
||||
]);
|
||||
$this->team->save();
|
||||
$this->emit('success', 'Settings saved successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
$this->team->smtp_enabled = false;
|
||||
return general_error_handler($e, $this);
|
||||
}
|
||||
}
|
||||
public function submitResend()
|
||||
{
|
||||
try {
|
||||
$this->resetErrorBag();
|
||||
$this->validate([
|
||||
'team.smtp_from_address' => 'required|email',
|
||||
'team.smtp_from_name' => 'required',
|
||||
'team.resend_api_key' => 'required'
|
||||
]);
|
||||
$this->team->save();
|
||||
$this->emit('success', 'Settings saved successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
$this->team->resend_enabled = false;
|
||||
return general_error_handler($e, $this);
|
||||
}
|
||||
}
|
||||
public function copyFromInstanceSettings()
|
||||
{
|
||||
$settings = InstanceSettings::get();
|
||||
@@ -72,55 +166,22 @@ class EmailSettings extends Component
|
||||
'smtp_password' => $settings->smtp_password,
|
||||
'smtp_timeout' => $settings->smtp_timeout,
|
||||
]);
|
||||
$this->decrypt();
|
||||
if (is_a($team, Team::class)) {
|
||||
refreshSession();
|
||||
}
|
||||
$this->model = $team;
|
||||
$this->emit('success', 'Settings saved.');
|
||||
} else {
|
||||
$this->emit('error', 'Instance SMTP settings are not enabled.');
|
||||
}
|
||||
}
|
||||
|
||||
public function sendTestNotification()
|
||||
{
|
||||
$this->model->notify(new Test($this->emails));
|
||||
$this->emit('success', 'Test Email sent successfully.');
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
{
|
||||
try {
|
||||
$this->submit();
|
||||
} catch (\Exception $e) {
|
||||
$this->model->smtp_enabled = false;
|
||||
$this->validate();
|
||||
}
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
$this->validate();
|
||||
|
||||
if ($this->model->smtp_password) {
|
||||
$this->model->smtp_password = encrypt($this->model->smtp_password);
|
||||
} else {
|
||||
$this->model->smtp_password = null;
|
||||
}
|
||||
|
||||
$this->model->smtp_recipients = str_replace(' ', '', $this->model->smtp_recipients);
|
||||
$this->saveModel();
|
||||
}
|
||||
|
||||
public function saveModel()
|
||||
{
|
||||
$this->model->save();
|
||||
$this->decrypt();
|
||||
if (is_a($this->model, Team::class)) {
|
||||
refreshSession();
|
||||
$this->team = $team;
|
||||
$this->emit('success', 'Settings saved.');
|
||||
return;
|
||||
}
|
||||
$this->emit('success', 'Settings saved.');
|
||||
if ($settings->resend_enabled) {
|
||||
$team = currentTeam();
|
||||
$team->update([
|
||||
'resend_enabled' => $settings->resend_enabled,
|
||||
'resend_api_key' => $settings->resend_api_key,
|
||||
]);
|
||||
refreshSession();
|
||||
$this->team = $team;
|
||||
$this->emit('success', 'Settings saved.');
|
||||
return;
|
||||
}
|
||||
$this->emit('error', 'Instance SMTP/Resend settings are not enabled.');
|
||||
}
|
||||
}
|
||||
|
||||
63
app/Http/Livewire/Notifications/TelegramSettings.php
Normal file
63
app/Http/Livewire/Notifications/TelegramSettings.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?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',
|
||||
'team.telegram_notifications_test_message_thread_id' => 'nullable|string',
|
||||
'team.telegram_notifications_deployments_message_thread_id' => 'nullable|string',
|
||||
'team.telegram_notifications_status_changes_message_thread_id' => 'nullable|string',
|
||||
'team.telegram_notifications_database_backups_message_thread_id' => 'nullable|string',
|
||||
];
|
||||
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 (\Throwable $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();
|
||||
$this->emit('success', 'Settings saved.');
|
||||
}
|
||||
|
||||
public function sendTestNotification()
|
||||
{
|
||||
$this->team->notify(new Test());
|
||||
$this->emit('success', 'Test notification sent.');
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ use Livewire\Component;
|
||||
class Change extends Component
|
||||
{
|
||||
public PrivateKey $private_key;
|
||||
|
||||
public $public_key;
|
||||
protected $rules = [
|
||||
'private_key.name' => 'required|string',
|
||||
'private_key.description' => 'nullable|string',
|
||||
@@ -21,16 +21,24 @@ class Change extends Component
|
||||
'private_key.private_key' => 'private key'
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
try {
|
||||
$this->public_key = $this->private_key->publicKey();
|
||||
}catch(\Exception $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
}
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
if ($this->private_key->isEmpty()) {
|
||||
$this->private_key->delete();
|
||||
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.');
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
}
|
||||
@@ -41,7 +49,7 @@ class Change extends Component
|
||||
$this->private_key->private_key = formatPrivateKey($this->private_key->private_key);
|
||||
$this->private_key->save();
|
||||
refresh_server_connection($this->private_key);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,15 @@ namespace App\Http\Livewire\PrivateKey;
|
||||
|
||||
use App\Models\PrivateKey;
|
||||
use Livewire\Component;
|
||||
use phpseclib3\Crypt\PublicKeyLoader;
|
||||
|
||||
class Create extends Component
|
||||
{
|
||||
public string|null $from = null;
|
||||
public ?string $from = null;
|
||||
public string $name;
|
||||
public string|null $description = null;
|
||||
public ?string $description = null;
|
||||
public string $value;
|
||||
public ?string $publicKey = null;
|
||||
protected $rules = [
|
||||
'name' => 'required|string',
|
||||
'value' => 'required|string',
|
||||
@@ -20,6 +22,23 @@ class Create extends Component
|
||||
'value' => 'private Key',
|
||||
];
|
||||
|
||||
public function generateNewKey()
|
||||
{
|
||||
$this->name = generate_random_name();
|
||||
$this->description = 'Created by Coolify';
|
||||
['private' => $this->value, 'public' => $this->publicKey] = generateSSHKey();
|
||||
}
|
||||
public function updated($updateProperty)
|
||||
{
|
||||
if ($updateProperty === 'value') {
|
||||
try {
|
||||
$this->publicKey = PublicKeyLoader::load($this->$updateProperty)->getPublicKey()->toString('OpenSSH',['comment' => '']);
|
||||
} catch (\Throwable $e) {
|
||||
$this->publicKey = "Invalid private key";
|
||||
}
|
||||
}
|
||||
$this->validateOnly($updateProperty);
|
||||
}
|
||||
public function createPrivateKey()
|
||||
{
|
||||
$this->validate();
|
||||
@@ -37,8 +56,8 @@ class Create extends Component
|
||||
if ($this->from === 'server') {
|
||||
return redirect()->route('server.create');
|
||||
}
|
||||
return redirect()->route('private-key.show', ['private_key_uuid' => $private_key->uuid]);
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->route('security.private-key.show', ['private_key_uuid' => $private_key->uuid]);
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ class AddEmpty extends Component
|
||||
'team_id' => currentTeam()->id,
|
||||
]);
|
||||
return redirect()->route('project.show', $project->uuid);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
general_error_handler($e, $this);
|
||||
} finally {
|
||||
$this->name = '';
|
||||
|
||||
@@ -31,7 +31,7 @@ class AddEnvironment extends Component
|
||||
'project_uuid' => $this->project->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
general_error_handler($e, $this);
|
||||
} finally {
|
||||
$this->name = '';
|
||||
|
||||
@@ -159,7 +159,7 @@ class General extends Component
|
||||
}
|
||||
$this->application->save();
|
||||
$this->emit('success', 'Application settings updated!');
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
|
||||
namespace App\Http\Livewire\Project\Application;
|
||||
|
||||
use App\Jobs\ApplicationContainerStatusJob;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Models\Application;
|
||||
use App\Notifications\Application\StatusChanged;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
@@ -22,10 +21,11 @@ class Heading extends Component
|
||||
|
||||
public function check_status()
|
||||
{
|
||||
dispatch_sync(new ApplicationContainerStatusJob(
|
||||
application: $this->application,
|
||||
));
|
||||
dispatch_sync(new ContainerStatusJob($this->application->destination->server));
|
||||
$this->application->refresh();
|
||||
$this->application->previews->each(function ($preview) {
|
||||
$preview->refresh();
|
||||
});
|
||||
}
|
||||
|
||||
public function force_deploy_without_cache()
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Http\Livewire\Project\Application;
|
||||
|
||||
use App\Jobs\ApplicationContainerStatusJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -23,14 +22,6 @@ class Previews extends Component
|
||||
$this->parameters = get_route_parameters();
|
||||
}
|
||||
|
||||
public function loadStatus($pull_request_id)
|
||||
{
|
||||
dispatch(new ApplicationContainerStatusJob(
|
||||
application: $this->application,
|
||||
pullRequestId: $pull_request_id
|
||||
));
|
||||
}
|
||||
|
||||
public function load_prs()
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -51,7 +51,7 @@ class BackupEdit extends Component
|
||||
$this->backup->save();
|
||||
$this->backup->refresh();
|
||||
$this->emit('success', 'Backup updated successfully');
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
$this->emit('error', $e->getMessage());
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ class BackupEdit extends Component
|
||||
$this->backup->save();
|
||||
$this->backup->refresh();
|
||||
$this->emit('success', 'Backup updated successfully');
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
$this->emit('error', $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ class CreateScheduledBackup extends Component
|
||||
'team_id' => currentTeam()->id,
|
||||
]);
|
||||
$this->emit('refreshScheduledBackups');
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
general_error_handler($e, $this);
|
||||
} finally {
|
||||
$this->frequency = '';
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
namespace App\Http\Livewire\Project\Database;
|
||||
|
||||
use App\Actions\Database\StartPostgresql;
|
||||
use App\Jobs\DatabaseContainerStatusJob;
|
||||
use App\Notifications\Application\StatusChanged;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use Livewire\Component;
|
||||
|
||||
class Heading extends Component
|
||||
@@ -25,9 +24,7 @@ class Heading extends Component
|
||||
|
||||
public function check_status()
|
||||
{
|
||||
dispatch_sync(new DatabaseContainerStatusJob(
|
||||
database: $this->database,
|
||||
));
|
||||
dispatch_sync(new ContainerStatusJob($this->database->destination->server));
|
||||
$this->database->refresh();
|
||||
}
|
||||
|
||||
@@ -42,8 +39,13 @@ class Heading extends Component
|
||||
["docker rm -f {$this->database->uuid}"],
|
||||
$this->database->destination->server
|
||||
);
|
||||
if ($this->database->is_public) {
|
||||
stopPostgresProxy($this->database);
|
||||
$this->database->is_public = false;
|
||||
}
|
||||
$this->database->status = 'stopped';
|
||||
$this->database->save();
|
||||
$this->emit('refresh');
|
||||
// $this->database->environment->project->team->notify(new StatusChanged($this->database));
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ class General extends Component
|
||||
public StandalonePostgresql $database;
|
||||
public string $new_filename;
|
||||
public string $new_content;
|
||||
public string $db_url;
|
||||
|
||||
protected $listeners = ['refresh', 'save_init_script', 'delete_init_script'];
|
||||
|
||||
@@ -26,6 +27,8 @@ class General extends Component
|
||||
'database.init_scripts' => 'nullable',
|
||||
'database.image' => 'required',
|
||||
'database.ports_mappings' => 'nullable',
|
||||
'database.is_public' => 'nullable|boolean',
|
||||
'database.public_port' => 'nullable|integer',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'database.name' => 'Name',
|
||||
@@ -38,8 +41,44 @@ class General extends Component
|
||||
'database.init_scripts' => 'Init Scripts',
|
||||
'database.image' => 'Image',
|
||||
'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) {
|
||||
$this->emit('success', 'Starting TCP proxy...');
|
||||
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)
|
||||
{
|
||||
$this->database->init_scripts = filter($this->database->init_scripts, fn ($s) => $s['filename'] !== $script['filename']);
|
||||
|
||||
@@ -19,7 +19,7 @@ class Edit extends Component
|
||||
try {
|
||||
$this->project->save();
|
||||
$this->emit('saved');
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,15 +7,19 @@ use App\Models\GithubApp;
|
||||
use App\Models\Project;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\SwarmDocker;
|
||||
use App\Traits\SaveFromRedirect;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Livewire\Component;
|
||||
use Route;
|
||||
|
||||
class GithubPrivateRepository extends Component
|
||||
{
|
||||
use SaveFromRedirect;
|
||||
public $current_step = 'github_apps';
|
||||
public $github_apps;
|
||||
public GithubApp $github_app;
|
||||
public $parameters;
|
||||
public $currentRoute;
|
||||
public $query;
|
||||
public $type;
|
||||
|
||||
@@ -36,14 +40,30 @@ class GithubPrivateRepository extends Component
|
||||
public string|null $publish_directory = null;
|
||||
protected int $page = 1;
|
||||
|
||||
// public function saveFromRedirect(string $route, ?Collection $parameters = null){
|
||||
// session()->forget('from');
|
||||
// if (!$parameters || $parameters->count() === 0) {
|
||||
// $parameters = $this->parameters;
|
||||
// }
|
||||
// $parameters = collect($parameters) ?? collect([]);
|
||||
// $queries = collect($this->query) ?? collect([]);
|
||||
// $parameters = $parameters->merge($queries);
|
||||
// session(['from'=> [
|
||||
// 'back'=> $this->currentRoute,
|
||||
// 'route' => $route,
|
||||
// 'parameters' => $parameters
|
||||
// ]]);
|
||||
// return redirect()->route($route);
|
||||
// }
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->currentRoute = Route::currentRouteName();
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->query = request()->query();
|
||||
$this->repositories = $this->branches = collect();
|
||||
$this->github_apps = GithubApp::private();
|
||||
}
|
||||
|
||||
public function loadRepositories($github_app_id)
|
||||
{
|
||||
$this->repositories = collect();
|
||||
@@ -144,7 +164,7 @@ class GithubPrivateRepository extends Component
|
||||
'environment_name' => $environment->name,
|
||||
'project_uuid' => $project->uuid,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
'environment_name' => $environment->name,
|
||||
'application_uuid' => $application->uuid,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ class PublicGitRepository extends Component
|
||||
'publish_directory' => 'publish directory',
|
||||
];
|
||||
private object $repository_url_parsed;
|
||||
private GithubApp|GitlabApp $git_source;
|
||||
private GithubApp|GitlabApp|null $git_source = null;
|
||||
private string $git_host;
|
||||
private string $git_repository;
|
||||
|
||||
@@ -67,23 +67,22 @@ class PublicGitRepository extends Component
|
||||
|
||||
public function load_branch()
|
||||
{
|
||||
$this->branch_found = false;
|
||||
try {
|
||||
$this->branch_found = false;
|
||||
$this->validate([
|
||||
'repository_url' => 'required|url'
|
||||
]);
|
||||
$this->get_git_source();
|
||||
try {
|
||||
$this->get_branch();
|
||||
$this->selected_branch = $this->git_branch;
|
||||
} catch (\Exception $e) {
|
||||
$this->get_branch();
|
||||
$this->selected_branch = $this->git_branch;
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
|
||||
if (!$this->branch_found && $this->git_branch == 'main') {
|
||||
try {
|
||||
$this->git_branch = 'master';
|
||||
$this->get_branch();
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
}
|
||||
@@ -103,6 +102,9 @@ class PublicGitRepository extends Component
|
||||
} elseif ($this->git_host == 'bitbucket.org') {
|
||||
// Not supported yet
|
||||
}
|
||||
if (is_null($this->git_source)) {
|
||||
throw new \Exception('Git source not found. What?!');
|
||||
}
|
||||
}
|
||||
|
||||
private function get_branch()
|
||||
@@ -159,7 +161,7 @@ class PublicGitRepository extends Component
|
||||
'environment_name' => $environment->name,
|
||||
'application_uuid' => $application->uuid,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,45 +3,79 @@
|
||||
namespace App\Http\Livewire\Project\New;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\SwarmDocker;
|
||||
use Countable;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
use Route;
|
||||
|
||||
class Select extends Component
|
||||
{
|
||||
public $current_step = 'type';
|
||||
public ?int $server = null;
|
||||
public string $type;
|
||||
public string $server_id;
|
||||
public string $destination_uuid;
|
||||
public Countable|array|Server $servers;
|
||||
public $destinations = [];
|
||||
public Collection|array $standaloneDockers = [];
|
||||
public Collection|array $swarmDockers = [];
|
||||
public array $parameters;
|
||||
|
||||
public ?string $existingPostgresqlUrl = null;
|
||||
|
||||
protected $queryString = [
|
||||
'server',
|
||||
];
|
||||
public function mount()
|
||||
{
|
||||
$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 (\Throwable $e) {
|
||||
// return general_error_handler($e, $this);
|
||||
// }
|
||||
// }
|
||||
public function setType(string $type)
|
||||
{
|
||||
$this->type = $type;
|
||||
if ($type === "existing-postgresql") {
|
||||
$this->current_step = $type;
|
||||
return;
|
||||
}
|
||||
if (count($this->servers) === 1) {
|
||||
$server = $this->servers->first();
|
||||
$this->set_server($server);
|
||||
$this->setServer($server);
|
||||
if (count($server->destinations()) === 1) {
|
||||
$this->set_destination($server->destinations()->first()->uuid);
|
||||
$this->setDestination($server->destinations()->first()->uuid);
|
||||
}
|
||||
}
|
||||
if (!is_null($this->server)) {
|
||||
$foundServer = $this->servers->where('id', $this->server)->first();
|
||||
if ($foundServer) {
|
||||
return $this->setServer($foundServer);
|
||||
}
|
||||
}
|
||||
$this->current_step = 'servers';
|
||||
}
|
||||
|
||||
public function set_server(Server $server)
|
||||
public function setServer(Server $server)
|
||||
{
|
||||
$this->server_id = $server->id;
|
||||
$this->destinations = $server->destinations();
|
||||
$this->standaloneDockers = $server->standaloneDockers;
|
||||
$this->swarmDockers = $server->swarmDockers;
|
||||
$this->current_step = 'destinations';
|
||||
}
|
||||
|
||||
public function set_destination(string $destination_uuid)
|
||||
public function setDestination(string $destination_uuid)
|
||||
{
|
||||
$this->destination_uuid = $destination_uuid;
|
||||
redirect()->route('project.resources.new', [
|
||||
|
||||
@@ -5,21 +5,89 @@ namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
|
||||
use App\Models\EnvironmentVariable;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class All extends Component
|
||||
{
|
||||
public $resource;
|
||||
public bool $showPreview = false;
|
||||
public string|null $modalId = null;
|
||||
public ?string $variables = null;
|
||||
public ?string $variablesPreview = null;
|
||||
public string $view = 'normal';
|
||||
protected $listeners = ['refreshEnvs', 'submit'];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$resourceClass = get_class($this->resource);
|
||||
$resourceWithPreviews = ['App\Models\Application'];
|
||||
$simpleDockerfile = !is_null(data_get($this->resource, 'dockerfile'));
|
||||
if (Str::of($resourceClass)->contains($resourceWithPreviews) && !$simpleDockerfile) {
|
||||
$this->showPreview = true;
|
||||
}
|
||||
$this->modalId = new Cuid2(7);
|
||||
$this->getDevView();
|
||||
}
|
||||
public function getDevView()
|
||||
{
|
||||
$this->variables = $this->resource->environment_variables->map(function ($item) {
|
||||
return "$item->key=$item->value";
|
||||
})->sort()->join('
|
||||
');
|
||||
if ($this->showPreview) {
|
||||
$this->variablesPreview = $this->resource->environment_variables_preview->map(function ($item) {
|
||||
return "$item->key=$item->value";
|
||||
})->sort()->join('
|
||||
');
|
||||
}
|
||||
}
|
||||
public function switch()
|
||||
{
|
||||
$this->view = $this->view === 'normal' ? 'dev' : 'normal';
|
||||
}
|
||||
public function saveVariables($isPreview)
|
||||
{
|
||||
if ($isPreview) {
|
||||
$variables = parseEnvFormatToArray($this->variablesPreview);
|
||||
$existingVariables = $this->resource->environment_variables_preview();
|
||||
$this->resource->environment_variables_preview()->delete();
|
||||
} else {
|
||||
$variables = parseEnvFormatToArray($this->variables);
|
||||
$existingVariables = $this->resource->environment_variables();
|
||||
$this->resource->environment_variables()->delete();
|
||||
}
|
||||
foreach ($variables as $key => $variable) {
|
||||
$found = $existingVariables->where('key', $key)->first();
|
||||
if ($found) {
|
||||
$found->value = $variable;
|
||||
$found->save();
|
||||
continue;
|
||||
} else {
|
||||
$environment = new EnvironmentVariable();
|
||||
$environment->key = $key;
|
||||
$environment->value = $variable;
|
||||
$environment->is_build_time = false;
|
||||
$environment->is_preview = $isPreview ? true : false;
|
||||
if ($this->resource->type() === 'application') {
|
||||
$environment->application_id = $this->resource->id;
|
||||
}
|
||||
if ($this->resource->type() === 'standalone-postgresql') {
|
||||
$environment->standalone_postgresql_id = $this->resource->id;
|
||||
}
|
||||
$environment->save();
|
||||
}
|
||||
}
|
||||
if ($isPreview) {
|
||||
$this->emit('success', 'Preview environment variables updated successfully.');
|
||||
} else {
|
||||
$this->emit('success', 'Environment variables updated successfully.');
|
||||
}
|
||||
$this->refreshEnvs();
|
||||
}
|
||||
|
||||
public function refreshEnvs()
|
||||
{
|
||||
$this->resource->refresh();
|
||||
$this->getDevView();
|
||||
}
|
||||
|
||||
public function submit($data)
|
||||
@@ -43,9 +111,9 @@ class All extends Component
|
||||
$environment->standalone_postgresql_id = $this->resource->id;
|
||||
}
|
||||
$environment->save();
|
||||
$this->resource->refresh();
|
||||
$this->refreshEnvs();
|
||||
$this->emit('success', 'Environment variable added successfully.');
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ class ResourceLimits extends Component
|
||||
$this->validate();
|
||||
$this->resource->save();
|
||||
$this->emit('success', 'Resource limits updated successfully.');
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ class All extends Component
|
||||
$this->resource->refresh();
|
||||
$this->emit('success', 'Storage added successfully');
|
||||
$this->emit('clearAddStorage');
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ class RunCommand extends Component
|
||||
try {
|
||||
$activity = remote_process([$this->command], Server::where('uuid', $this->server)->first(), ignore_errors: true);
|
||||
$this->emit('newMonitorActivity', $activity->id);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e);
|
||||
}
|
||||
}
|
||||
|
||||
20
app/Http/Livewire/Server/All.php
Normal file
20
app/Http/Livewire/Server/All.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Server;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Livewire\Component;
|
||||
|
||||
class All extends Component
|
||||
{
|
||||
public ?Collection $servers = null;
|
||||
|
||||
public function mount () {
|
||||
$this->servers = Server::ownedByCurrentTeam()->get();
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.server.all');
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,12 @@ namespace App\Http\Livewire\Server;
|
||||
|
||||
use App\Actions\Server\InstallDocker;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Livewire\Component;
|
||||
|
||||
class Form extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
public Server $server;
|
||||
public $uptime;
|
||||
public $dockerVersion;
|
||||
@@ -52,40 +54,47 @@ class Form extends Component
|
||||
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server);
|
||||
if ($uptime) {
|
||||
$this->uptime = $uptime;
|
||||
$this->emit('success', 'Server is reachable!');
|
||||
} else {
|
||||
$this->emit('error', 'Server is not reachable');
|
||||
return;
|
||||
}
|
||||
if ($dockerVersion) {
|
||||
$this->dockerVersion = $dockerVersion;
|
||||
$this->emit('proxyStatusUpdated');
|
||||
$this->emit('success', 'Docker Engine 23+ is installed!');
|
||||
} else {
|
||||
$this->emit('error', 'Old (lower than 23) or no Docker version detected. Install Docker Engine on the General tab.');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(customErrorMessage: "Server is not reachable. Reason: {$e->getMessage()}", that: $this);
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler($e, that: $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
if (!$this->server->isEmpty()) {
|
||||
$this->emit('error', 'Server has defined resources. Please delete them first.');
|
||||
return;
|
||||
try {
|
||||
$this->authorize('delete', $this->server);
|
||||
if (!$this->server->isEmpty()) {
|
||||
$this->emit('error', 'Server has defined resources. Please delete them first.');
|
||||
return;
|
||||
}
|
||||
$this->server->delete();
|
||||
return redirect()->route('server.all');
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
$this->server->delete();
|
||||
redirect()->route('server.all');
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
// $validation = Validator::make($this->server->toArray(), [
|
||||
// 'ip' => [
|
||||
// 'ip'
|
||||
// ],
|
||||
// ]);
|
||||
// if ($validation->fails()) {
|
||||
// foreach ($validation->errors()->getMessages() as $key => $value) {
|
||||
// $this->addError("server.{$key}", $value[0]);
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
$uniqueIPs = Server::all()->reject(function (Server $server) {
|
||||
return $server->id === $this->server->id;
|
||||
})->pluck('ip')->toArray();
|
||||
if (in_array($this->server->ip, $uniqueIPs)) {
|
||||
$this->emit('error', 'IP address is already in use by another team.');
|
||||
return;
|
||||
}
|
||||
$this->server->settings->wildcard_domain = $this->wildcard_domain;
|
||||
$this->server->settings->cleanup_after_percentage = $this->cleanup_after_percentage;
|
||||
$this->server->settings->save();
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Http\Livewire\Server\New;
|
||||
|
||||
use App\Enums\ProxyStatus;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
|
||||
@@ -56,7 +58,7 @@ class ByIp extends Component
|
||||
{
|
||||
$this->validate();
|
||||
try {
|
||||
if (!$this->private_key_id) {
|
||||
if (is_null($this->private_key_id)) {
|
||||
return $this->emit('error', 'You must select a private key');
|
||||
}
|
||||
$server = Server::create([
|
||||
@@ -67,11 +69,16 @@ class ByIp extends Component
|
||||
'port' => $this->port,
|
||||
'team_id' => currentTeam()->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->save();
|
||||
return redirect()->route('server.show', $server->uuid);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Server;
|
||||
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
use Masmerise\Toaster\Toaster;
|
||||
|
||||
class PrivateKey extends Component
|
||||
{
|
||||
public Server $server;
|
||||
public $privateKeys;
|
||||
public $parameters;
|
||||
|
||||
public function setPrivateKey($private_key_id)
|
||||
{
|
||||
$this->server->update([
|
||||
'private_key_id' => $private_key_id
|
||||
]);
|
||||
refresh_server_connection($this->server->privateKey);
|
||||
$this->server->refresh();
|
||||
$this->checkConnection();
|
||||
}
|
||||
|
||||
public function checkConnection()
|
||||
{
|
||||
try {
|
||||
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server);
|
||||
if ($uptime) {
|
||||
Toaster::success('Server is reachable with this private key.');
|
||||
}
|
||||
if ($dockerVersion) {
|
||||
Toaster::success('Server is usable for Coolify.');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(customErrorMessage: "Server is not reachable. Reason: {$e->getMessage()}", that: $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
}
|
||||
}
|
||||
@@ -12,15 +12,16 @@ class Proxy extends Component
|
||||
{
|
||||
public Server $server;
|
||||
|
||||
public ProxyTypes $selectedProxy = ProxyTypes::TRAEFIK_V2;
|
||||
public ?string $selectedProxy = null;
|
||||
public $proxy_settings = null;
|
||||
public string|null $redirect_url = null;
|
||||
public ?string $redirect_url = null;
|
||||
|
||||
protected $listeners = ['proxyStatusUpdated', 'saveConfiguration' => 'submit'];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->redirect_url = $this->server->proxy->redirect_url;
|
||||
$this->selectedProxy = data_get($this->server, 'proxy.type');
|
||||
$this->redirect_url = data_get($this->server, 'proxy.redirect_url');
|
||||
}
|
||||
|
||||
public function proxyStatusUpdated()
|
||||
@@ -35,25 +36,26 @@ class Proxy extends Component
|
||||
$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->status = 'exited';
|
||||
$this->server->save();
|
||||
$this->selectedProxy = $this->server->proxy->type;
|
||||
$this->emit('proxyStatusUpdated');
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
resolve(SaveConfigurationSync::class)($this->server, $this->proxy_settings);
|
||||
resolve(SaveConfigurationSync::class)($this->server);
|
||||
|
||||
$this->server->proxy->redirect_url = $this->redirect_url;
|
||||
$this->server->save();
|
||||
|
||||
setup_default_redirect_404(redirect_url: $this->server->proxy->redirect_url, server: $this->server);
|
||||
$this->emit('success', 'Proxy configuration saved.');
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e);
|
||||
}
|
||||
}
|
||||
@@ -62,16 +64,17 @@ class Proxy extends Component
|
||||
{
|
||||
try {
|
||||
$this->proxy_settings = resolve(CheckConfigurationSync::class)($this->server, true);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e);
|
||||
}
|
||||
}
|
||||
|
||||
public function load_proxy_configuration()
|
||||
public function loadProxyConfiguration()
|
||||
{
|
||||
try {
|
||||
ray('loadProxyConfiguration');
|
||||
$this->proxy_settings = resolve(CheckConfigurationSync::class)($this->server);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Livewire\Server\Proxy;
|
||||
|
||||
use App\Actions\Proxy\SaveConfigurationSync;
|
||||
use App\Actions\Proxy\StartProxy;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
@@ -10,15 +11,20 @@ class Deploy extends Component
|
||||
{
|
||||
public Server $server;
|
||||
public $proxy_settings = null;
|
||||
protected $listeners = ['proxyStatusUpdated'];
|
||||
|
||||
public function start_proxy()
|
||||
public function proxyStatusUpdated() {
|
||||
$this->server->refresh();
|
||||
}
|
||||
public function startProxy()
|
||||
{
|
||||
if (
|
||||
$this->server->proxy->last_applied_settings &&
|
||||
$this->server->proxy->last_saved_settings !== $this->server->proxy->last_applied_settings
|
||||
) {
|
||||
$this->emit('saveConfiguration', $this->server);
|
||||
resolve(SaveConfigurationSync::class)($this->server);
|
||||
}
|
||||
|
||||
$activity = resolve(StartProxy::class)($this->server);
|
||||
$this->emit('newMonitorActivity', $activity->id);
|
||||
}
|
||||
|
||||
16
app/Http/Livewire/Server/Proxy/Modal.php
Normal file
16
app/Http/Livewire/Server/Proxy/Modal.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Server\Proxy;
|
||||
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
|
||||
class Modal extends Component
|
||||
{
|
||||
public Server $server;
|
||||
|
||||
public function proxyStatusUpdated()
|
||||
{
|
||||
$this->emit('proxyStatusUpdated');
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Http\Livewire\Server\Proxy;
|
||||
|
||||
use App\Jobs\ProxyContainerStatusJob;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
|
||||
@@ -10,12 +9,27 @@ class Status extends Component
|
||||
{
|
||||
public Server $server;
|
||||
|
||||
public function get_status()
|
||||
protected $listeners = ['proxyStatusUpdated'];
|
||||
public function proxyStatusUpdated()
|
||||
{
|
||||
dispatch_sync(new ProxyContainerStatusJob(
|
||||
server: $this->server
|
||||
));
|
||||
$this->server->refresh();
|
||||
$this->emit('proxyStatusUpdated');
|
||||
}
|
||||
public function getProxyStatus()
|
||||
{
|
||||
try {
|
||||
if ($this->server->isFunctional()) {
|
||||
$container = getContainerStatus(server: $this->server, container_id: 'coolify-proxy');
|
||||
$this->server->proxy->status = $container;
|
||||
$this->server->save();
|
||||
$this->emit('proxyStatusUpdated');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e);
|
||||
}
|
||||
}
|
||||
public function getProxyStatusWithNoti()
|
||||
{
|
||||
$this->emit('success', 'Refreshed proxy status.');
|
||||
$this->getProxyStatus();
|
||||
}
|
||||
}
|
||||
|
||||
28
app/Http/Livewire/Server/Show.php
Normal file
28
app/Http/Livewire/Server/Show.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Server;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Livewire\Component;
|
||||
|
||||
class Show extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
public ?Server $server = null;
|
||||
public function mount()
|
||||
{
|
||||
try {
|
||||
$this->server = Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user', 'proxy'])->whereUuid(request()->server_uuid)->first();
|
||||
if (is_null($this->server)) {
|
||||
return redirect()->route('server.all');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.server.show');
|
||||
}
|
||||
}
|
||||
59
app/Http/Livewire/Server/ShowPrivateKey.php
Normal file
59
app/Http/Livewire/Server/ShowPrivateKey.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Server;
|
||||
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
|
||||
class ShowPrivateKey extends Component
|
||||
{
|
||||
public Server $server;
|
||||
public $privateKeys;
|
||||
public $parameters;
|
||||
|
||||
public function setPrivateKey($newPrivateKeyId)
|
||||
{
|
||||
try {
|
||||
refresh_server_connection($this->server->privateKey);
|
||||
$oldPrivateKeyId = $this->server->private_key_id;
|
||||
$this->server->update([
|
||||
'private_key_id' => $newPrivateKeyId
|
||||
]);
|
||||
$this->server->refresh();
|
||||
refresh_server_connection($this->server->privateKey);
|
||||
$this->checkConnection();
|
||||
} catch (\Throwable $e) {
|
||||
$this->server->update([
|
||||
'private_key_id' => $oldPrivateKeyId
|
||||
]);
|
||||
$this->server->refresh();
|
||||
refresh_server_connection($this->server->privateKey);
|
||||
return general_error_handler($e, that: $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function checkConnection()
|
||||
{
|
||||
try {
|
||||
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server);
|
||||
if ($uptime) {
|
||||
$this->emit('success', 'Server is reachable with this private key.');
|
||||
} else {
|
||||
$this->emit('error', 'Server is not reachable with this private key.');
|
||||
return;
|
||||
}
|
||||
if ($dockerVersion) {
|
||||
$this->emit('success', 'Server is usable for Coolify.');
|
||||
} else {
|
||||
$this->emit('error', 'Old (lower than 23) or no Docker version detected. Install Docker Engine on the General tab.');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
throw new \Exception($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Http\Livewire\Settings;
|
||||
|
||||
use App\Jobs\ProxyStartJob;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Models\InstanceSettings as ModelsInstanceSettings;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
@@ -124,7 +124,7 @@ class Configuration extends Component
|
||||
];
|
||||
}
|
||||
$this->save_configuration_to_disk($traefik_dynamic_conf, $file);
|
||||
dispatch(new ProxyStartJob($this->server));
|
||||
dispatch(new ContainerStatusJob($this->server));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,9 @@ class Email extends Component
|
||||
'settings.smtp_timeout' => 'nullable',
|
||||
'settings.smtp_from_address' => 'required|email',
|
||||
'settings.smtp_from_name' => 'required',
|
||||
'settings.resend_enabled' => 'nullable|boolean',
|
||||
'settings.resend_api_key' => 'nullable'
|
||||
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'settings.smtp_from_address' => 'From Address',
|
||||
@@ -30,48 +33,75 @@ class Email extends Component
|
||||
'settings.smtp_encryption' => 'Encryption',
|
||||
'settings.smtp_username' => 'Username',
|
||||
'settings.smtp_password' => 'Password',
|
||||
'settings.smtp_timeout' => 'Timeout',
|
||||
'settings.resend_api_key' => 'Resend API Key'
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->decrypt();
|
||||
$this->emails = auth()->user()->email;
|
||||
}
|
||||
|
||||
private function decrypt()
|
||||
{
|
||||
if (data_get($this->settings, 'smtp_password')) {
|
||||
try {
|
||||
$this->settings->smtp_password = decrypt($this->settings->smtp_password);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
public function submitFromFields() {
|
||||
try {
|
||||
$this->resetErrorBag();
|
||||
$this->validate([
|
||||
'settings.smtp_from_address' => 'required|email',
|
||||
'settings.smtp_from_name' => 'required',
|
||||
]);
|
||||
$this->settings->save();
|
||||
$this->emit('success', 'Settings saved successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler($e, $this);
|
||||
}
|
||||
}
|
||||
public function submitResend() {
|
||||
try {
|
||||
$this->resetErrorBag();
|
||||
$this->validate([
|
||||
'settings.resend_api_key' => 'required'
|
||||
]);
|
||||
$this->settings->save();
|
||||
$this->emit('success', 'Settings saved successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
$this->settings->resend_enabled = false;
|
||||
return general_error_handler($e, $this);
|
||||
}
|
||||
}
|
||||
public function instantSaveResend() {
|
||||
try {
|
||||
$this->settings->smtp_enabled = false;
|
||||
$this->submitResend();
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
{
|
||||
try {
|
||||
$this->settings->resend_enabled = false;
|
||||
$this->submit();
|
||||
$this->emit('success', 'Settings saved successfully.');
|
||||
} catch (\Exception $e) {
|
||||
$this->settings->smtp_enabled = false;
|
||||
$this->validate();
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
$this->validate();
|
||||
if ($this->settings->smtp_password) {
|
||||
$this->settings->smtp_password = encrypt($this->settings->smtp_password);
|
||||
} else {
|
||||
$this->settings->smtp_password = null;
|
||||
try {
|
||||
$this->resetErrorBag();
|
||||
$this->validate([
|
||||
'settings.smtp_host' => 'required',
|
||||
'settings.smtp_port' => 'required|numeric',
|
||||
'settings.smtp_encryption' => 'nullable',
|
||||
'settings.smtp_username' => 'nullable',
|
||||
'settings.smtp_password' => 'nullable',
|
||||
'settings.smtp_timeout' => 'nullable',
|
||||
]);
|
||||
$this->settings->save();
|
||||
$this->emit('success', 'Settings saved successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler($e, $this);
|
||||
}
|
||||
|
||||
$this->settings->save();
|
||||
$this->emit('success', 'Transaction email settings updated successfully.');
|
||||
$this->decrypt();
|
||||
}
|
||||
|
||||
public function sendTestNotification()
|
||||
|
||||
@@ -37,9 +37,13 @@ class Change extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->webhook_endpoint = $this->ipv4;
|
||||
if (isCloud() && !isDev()) {
|
||||
$this->webhook_endpoint = config('app.url');
|
||||
} else {
|
||||
$this->webhook_endpoint = $this->ipv4;
|
||||
$this->is_system_wide = $this->github_app->is_system_wide;
|
||||
}
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->is_system_wide = $this->github_app->is_system_wide;
|
||||
}
|
||||
|
||||
public function submit()
|
||||
@@ -47,7 +51,7 @@ class Change extends Component
|
||||
try {
|
||||
$this->validate();
|
||||
$this->github_app->save();
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
}
|
||||
@@ -61,7 +65,7 @@ class Change extends Component
|
||||
try {
|
||||
$this->github_app->delete();
|
||||
redirect()->route('source.all');
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,18 +32,24 @@ class Create extends Component
|
||||
"custom_port" => 'required|int',
|
||||
"is_system_wide" => 'required|bool',
|
||||
]);
|
||||
$github_app = GithubApp::create([
|
||||
$payload = [
|
||||
'name' => $this->name,
|
||||
'organization' => $this->organization,
|
||||
'api_url' => $this->api_url,
|
||||
'html_url' => $this->html_url,
|
||||
'custom_user' => $this->custom_user,
|
||||
'custom_port' => $this->custom_port,
|
||||
'is_system_wide' => $this->is_system_wide,
|
||||
'team_id' => currentTeam()->id,
|
||||
]);
|
||||
];
|
||||
if (isCloud()) {
|
||||
$payload['is_system_wide'] = $this->is_system_wide;
|
||||
}
|
||||
$github_app = GithubApp::create($payload);
|
||||
if (session('from')) {
|
||||
session(['from' => session('from') + ['source_id' => $github_app->id]]);
|
||||
}
|
||||
redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ class Actions extends Component
|
||||
$this->emit('success', 'Subscription cancelled successfully. Reloading in 5s.');
|
||||
$this->emit('reloadWindow', 5000);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler($e, $this);
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,7 @@ class Actions extends Component
|
||||
$this->emit('success', 'Subscription resumed successfully. Reloading in 5s.');
|
||||
$this->emit('reloadWindow', 5000);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,13 @@ use Stripe\Checkout\Session;
|
||||
|
||||
class PricingPlans extends Component
|
||||
{
|
||||
public bool $isTrial = false;
|
||||
public function mount() {
|
||||
$this->isTrial = !data_get(currentTeam(),'subscription.stripe_trial_already_ended');
|
||||
}
|
||||
public function subscribeStripe($type)
|
||||
{
|
||||
$team = currentTeam();
|
||||
Stripe::setApiKey(config('subscription.stripe_api_key'));
|
||||
switch ($type) {
|
||||
case 'basic-monthly':
|
||||
@@ -39,6 +44,7 @@ class PricingPlans extends Component
|
||||
return;
|
||||
}
|
||||
$payload = [
|
||||
'billing_address_collection' => 'required',
|
||||
'client_reference_id' => auth()->user()->id . ':' . currentTeam()->id,
|
||||
'line_items' => [[
|
||||
'price' => $priceId,
|
||||
@@ -47,10 +53,26 @@ class PricingPlans extends Component
|
||||
'tax_id_collection' => [
|
||||
'enabled' => true,
|
||||
],
|
||||
'automatic_tax' => [
|
||||
'enabled' => true,
|
||||
],
|
||||
|
||||
'mode' => 'subscription',
|
||||
'success_url' => route('subscription.success'),
|
||||
'cancel_url' => route('subscription.show',['cancelled' => true]),
|
||||
'success_url' => route('dashboard', ['success' => true]),
|
||||
'cancel_url' => route('subscription.index', ['cancelled' => true]),
|
||||
];
|
||||
|
||||
if (!data_get($team,'subscription.stripe_trial_already_ended')) {
|
||||
$payload['subscription_data'] = [
|
||||
'trial_period_days' => config('constants.limits.trial_period'),
|
||||
'trial_settings' => [
|
||||
'end_behavior' => [
|
||||
'missing_payment_method' => 'cancel',
|
||||
]
|
||||
],
|
||||
];
|
||||
$payload['payment_method_collection'] = 'if_required';
|
||||
}
|
||||
$customer = currentTeam()->subscription?->stripe_customer_id ?? null;
|
||||
if ($customer) {
|
||||
$payload['customer'] = $customer;
|
||||
|
||||
@@ -23,7 +23,7 @@ class SwitchTeam extends Component
|
||||
if (!$team_to_switch_to) {
|
||||
return;
|
||||
}
|
||||
session(['currentTeam' => $team_to_switch_to]);
|
||||
refreshSession($team_to_switch_to);
|
||||
return redirect(request()->header('Referer'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,9 +30,9 @@ class Create extends Component
|
||||
]);
|
||||
auth()->user()->teams()->attach($team, ['role' => 'admin']);
|
||||
refreshSession();
|
||||
return redirect()->route('team.show');
|
||||
} catch (\Throwable $th) {
|
||||
return general_error_handler($th, $this);
|
||||
return redirect()->route('team.index');
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ class Delete extends Component
|
||||
$currentTeam = currentTeam();
|
||||
$currentTeam->delete();
|
||||
|
||||
$team = auth()->user()->teams()->first();
|
||||
$currentTeam->members->each(function ($user) use ($currentTeam) {
|
||||
if ($user->id === auth()->user()->id) {
|
||||
return;
|
||||
@@ -25,6 +24,6 @@ class Delete extends Component
|
||||
});
|
||||
|
||||
refreshSession();
|
||||
return redirect()->route('team.show');
|
||||
return redirect()->route('team.index');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,10 +27,8 @@ class Form extends Component
|
||||
$this->validate();
|
||||
try {
|
||||
$this->team->save();
|
||||
refreshSession();
|
||||
$this->emit('reloadWindow');
|
||||
} catch (\Throwable $th) {
|
||||
return general_error_handler($th, $this);
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ class Invitations extends Component
|
||||
{
|
||||
TeamInvitation::find($invitation_id)->delete();
|
||||
$this->refreshInvitations();
|
||||
$this->emit('success', 'Invitation revoked.');
|
||||
}
|
||||
|
||||
public function refreshInvitations()
|
||||
|
||||
@@ -4,9 +4,13 @@ namespace App\Http\Livewire\Team;
|
||||
|
||||
use App\Models\TeamInvitation;
|
||||
use App\Models\User;
|
||||
use App\Notifications\TransactionalEmails\InvitationLink;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class InviteLink extends Component
|
||||
{
|
||||
@@ -20,53 +24,68 @@ class InviteLink extends Component
|
||||
|
||||
public function viaEmail()
|
||||
{
|
||||
$this->generate_invite_link(isEmail: true);
|
||||
$this->generate_invite_link(sendEmail: true);
|
||||
}
|
||||
|
||||
private function generate_invite_link(bool $isEmail = false)
|
||||
public function viaLink()
|
||||
{
|
||||
$this->generate_invite_link(sendEmail: false);
|
||||
}
|
||||
private function generate_invite_link(bool $sendEmail = false)
|
||||
{
|
||||
try {
|
||||
$uuid = new Cuid2(32);
|
||||
$link = url('/') . config('constants.invitation.link.base_url') . $uuid;
|
||||
|
||||
$user = User::whereEmail($this->email);
|
||||
|
||||
if (!$user->exists()) {
|
||||
return general_error_handler(that: $this, customErrorMessage: "$this->email must be registered first (or activate transactional emails to invite via email).");
|
||||
}
|
||||
|
||||
$member_emails = currentTeam()->members()->get()->pluck('email');
|
||||
if ($member_emails->contains($this->email)) {
|
||||
return general_error_handler(that: $this, customErrorMessage: "$this->email is already a member of " . currentTeam()->name . ".");
|
||||
}
|
||||
$uuid = new Cuid2(32);
|
||||
$link = url('/') . config('constants.invitation.link.base_url') . $uuid;
|
||||
$user = User::whereEmail($this->email)->first();
|
||||
|
||||
$invitation = TeamInvitation::whereEmail($this->email);
|
||||
|
||||
if ($invitation->exists()) {
|
||||
$created_at = $invitation->first()->created_at;
|
||||
$diff = $created_at->diffInMinutes(now());
|
||||
if ($diff <= config('constants.invitation.link.expiration')) {
|
||||
return general_error_handler(that: $this, customErrorMessage: "Invitation already sent to $this->email and waiting for action.");
|
||||
if (is_null($user)) {
|
||||
$password = Str::password();
|
||||
$user = User::create([
|
||||
'name' => Str::of($this->email)->before('@'),
|
||||
'email' => $this->email,
|
||||
'password' => Hash::make($password),
|
||||
'force_password_reset' => true,
|
||||
]);
|
||||
$token = Crypt::encryptString("{$user->email}@@@$password");
|
||||
$link = route('auth.link', ['token' => $token]);
|
||||
}
|
||||
$invitation = TeamInvitation::whereEmail($this->email)->first();
|
||||
if (!is_null($invitation)) {
|
||||
$invitationValid = $invitation->isValid();
|
||||
if ($invitationValid) {
|
||||
return general_error_handler(that: $this, customErrorMessage: "Pending invitation already exists for $this->email.");
|
||||
} else {
|
||||
$invitation->delete();
|
||||
}
|
||||
}
|
||||
|
||||
TeamInvitation::firstOrCreate([
|
||||
$invitation = TeamInvitation::firstOrCreate([
|
||||
'team_id' => currentTeam()->id,
|
||||
'uuid' => $uuid,
|
||||
'email' => $this->email,
|
||||
'role' => $this->role,
|
||||
'link' => $link,
|
||||
'via' => $isEmail ? 'email' : 'link',
|
||||
'via' => $sendEmail ? 'email' : 'link',
|
||||
]);
|
||||
if ($isEmail) {
|
||||
$user->first()->notify(new InvitationLink);
|
||||
if ($sendEmail) {
|
||||
$mail = new MailMessage();
|
||||
$mail->view('emails.invitation-link', [
|
||||
'team' => currentTeam()->name,
|
||||
'invitation_link' => $link,
|
||||
]);
|
||||
$mail->subject('You have been invited to ' . currentTeam()->name . ' on ' . config('app.name') . '.');
|
||||
send_user_an_email($mail, $this->email);
|
||||
$this->emit('success', 'Invitation sent via email successfully.');
|
||||
$this->emit('refreshInvitations');
|
||||
return;
|
||||
} else {
|
||||
$this->emit('success', 'Invitation link generated.');
|
||||
$this->emit('refreshInvitations');
|
||||
}
|
||||
$this->emit('refreshInvitations');
|
||||
} catch (\Throwable $e) {
|
||||
$error_message = $e->getMessage();
|
||||
if ($e->getCode() === '23505') {
|
||||
@@ -75,9 +94,4 @@ class InviteLink extends Component
|
||||
return general_error_handler(err: $e, that: $this, customErrorMessage: $error_message);
|
||||
}
|
||||
}
|
||||
|
||||
public function viaLink()
|
||||
{
|
||||
$this->generate_invite_link();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Http\Livewire\Team;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Livewire\Component;
|
||||
|
||||
class Member extends Component
|
||||
@@ -24,6 +25,10 @@ class Member extends Component
|
||||
public function remove()
|
||||
{
|
||||
$this->member->teams()->detach(currentTeam());
|
||||
Cache::forget("team:{$this->member->id}");
|
||||
Cache::remember('team:' . $this->member->id, 3600, function() {
|
||||
return $this->member->teams()->first();
|
||||
});
|
||||
$this->emit('reloadWindow');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,8 +67,8 @@ class Create extends Component
|
||||
$this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
|
||||
$this->storage->save();
|
||||
return redirect()->route('team.storages.show', $this->storage->uuid);
|
||||
} catch (\Throwable $th) {
|
||||
return general_error_handler($th, $this);
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,8 +77,8 @@ class Create extends Component
|
||||
try {
|
||||
$this->storage->testConnection();
|
||||
return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
|
||||
} catch (\Throwable $th) {
|
||||
return general_error_handler($th, $this);
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@ class Form extends Component
|
||||
try {
|
||||
$this->storage->testConnection();
|
||||
return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
|
||||
} catch (\Throwable $th) {
|
||||
return general_error_handler($th, $this);
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@ class Form extends Component
|
||||
try {
|
||||
$this->storage->delete();
|
||||
return redirect()->route('team.storages.all');
|
||||
} catch (\Throwable $th) {
|
||||
return general_error_handler($th, $this);
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +55,8 @@ class Form extends Component
|
||||
$this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
|
||||
$this->storage->save();
|
||||
$this->emit('success', 'Storage settings saved.');
|
||||
} catch (\Throwable $th) {
|
||||
return general_error_handler($th, $this);
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ class Upgrade extends Component
|
||||
$this->showProgress = true;
|
||||
resolve(UpdateCoolify::class)(true);
|
||||
Toaster::success("Upgrading to {$this->latestVersion} version...");
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
namespace App\Http\Livewire\Waitlist;
|
||||
|
||||
use App\Jobs\SendConfirmationForWaitlistJob;
|
||||
use App\Models\User;
|
||||
use App\Models\Waitlist as ModelsWaitlist;
|
||||
use App\Models\Waitlist;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Waitlist extends Component
|
||||
class Index extends Component
|
||||
{
|
||||
public string $email;
|
||||
public int $waiting_in_line = 0;
|
||||
public int $users = 0;
|
||||
public int $waitingInLine = 0;
|
||||
|
||||
protected $rules = [
|
||||
'email' => 'required|email',
|
||||
];
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.waitlist.index')->layout('layouts.simple');
|
||||
}
|
||||
public function mount()
|
||||
{
|
||||
$this->waitingInLine = Waitlist::whereVerified(true)->count();
|
||||
$this->users = User::count();
|
||||
if (isDev()) {
|
||||
$this->email = 'waitlist@example.com';
|
||||
}
|
||||
@@ -29,7 +37,7 @@ class Waitlist extends Component
|
||||
if ($already_registered) {
|
||||
throw new \Exception('You are already on the waitlist or registered. <br>Please check your email to verify your email address or contact support.');
|
||||
}
|
||||
$found = ModelsWaitlist::where('email', $this->email)->first();
|
||||
$found = Waitlist::where('email', $this->email)->first();
|
||||
if ($found) {
|
||||
if (!$found->verified) {
|
||||
$this->emit('error', 'You are already on the waitlist. <br>Please check your email to verify your email address.');
|
||||
@@ -38,14 +46,14 @@ class Waitlist extends Component
|
||||
$this->emit('error', 'You are already on the waitlist. <br>You will be notified when your turn comes. <br>Thank you.');
|
||||
return;
|
||||
}
|
||||
$waitlist = ModelsWaitlist::create([
|
||||
'email' => $this->email,
|
||||
$waitlist = Waitlist::create([
|
||||
'email' => Str::lower($this->email),
|
||||
'type' => 'registration',
|
||||
]);
|
||||
|
||||
$this->emit('success', 'Check your email to verify your email address.');
|
||||
dispatch(new SendConfirmationForWaitlistJob($this->email, $waitlist->uuid));
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,12 @@ class CheckForcePasswordReset
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
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;
|
||||
if ($force_password_reset) {
|
||||
if ($request->routeIs('auth.force-password-reset') || $request->path() === 'livewire/message/force-password-reset') {
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Http\Middleware;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class IsBoardingFlow
|
||||
{
|
||||
@@ -15,8 +16,11 @@ class IsBoardingFlow
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
// ray('IsBoardingFlow Middleware');
|
||||
// ray()->showQueries()->color('orange');
|
||||
if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
||||
if (Str::startsWith($request->path(), 'invitations')) {
|
||||
return $next($request);
|
||||
}
|
||||
return redirect('boarding');
|
||||
}
|
||||
return $next($request);
|
||||
|
||||
@@ -5,15 +5,16 @@ namespace App\Http\Middleware;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class SubscriptionValid
|
||||
class IsSubscriptionValid
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (isInstanceAdmin()) {
|
||||
return $next($request);
|
||||
}
|
||||
if (!auth()->user() || !is_cloud()) {
|
||||
if (!auth()->user() || !isCloud()) {
|
||||
if ($request->path() === 'subscription') {
|
||||
return redirect('/');
|
||||
} else {
|
||||
@@ -31,6 +32,9 @@ class SubscriptionValid
|
||||
if (!isSubscriptionActive() && !isSubscriptionOnGracePeriod()) {
|
||||
// ray('SubscriptionValid Middleware');
|
||||
if (!in_array($request->path(), allowedPathsForUnsubscribedAccounts())) {
|
||||
if (Str::startsWith($request->path(), 'invitations')) {
|
||||
return $next($request);
|
||||
}
|
||||
return redirect('subscription');
|
||||
} else {
|
||||
return $next($request);
|
||||
@@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Notifications\Application\StatusChanged;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ApplicationContainerStatusJob implements ShouldQueue, ShouldBeUnique
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public string $containerName;
|
||||
|
||||
public function __construct(
|
||||
public Application $application,
|
||||
public int $pullRequestId = 0)
|
||||
{
|
||||
$this->containerName = generateApplicationContainerName($application->uuid, $pullRequestId);
|
||||
}
|
||||
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return $this->containerName;
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
$status = getApplicationContainerStatus(application: $this->application);
|
||||
if ($this->application->status === 'running' && $status !== 'running') {
|
||||
// $this->application->environment->project->team->notify(new StatusChanged($this->application));
|
||||
}
|
||||
|
||||
if ($this->pullRequestId !== 0) {
|
||||
$preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pullRequestId);
|
||||
$preview->status = $status;
|
||||
$preview->save();
|
||||
} else {
|
||||
$this->application->status = $status;
|
||||
$this->application->save();
|
||||
}
|
||||
} catch (\Exception $th) {
|
||||
ray($th->getMessage());
|
||||
throw $th;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ use App\Notifications\Application\DeploymentSuccess;
|
||||
use App\Traits\ExecuteRemoteCommand;
|
||||
use Exception;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
@@ -28,7 +29,7 @@ use Symfony\Component\Yaml\Yaml;
|
||||
use Throwable;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class ApplicationDeploymentJob implements ShouldQueue
|
||||
class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, ExecuteRemoteCommand;
|
||||
|
||||
@@ -46,7 +47,6 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
private GithubApp|GitlabApp $source;
|
||||
private StandaloneDocker|SwarmDocker $destination;
|
||||
private Server $server;
|
||||
private string $private_key_location;
|
||||
private ApplicationPreview|null $preview = null;
|
||||
|
||||
private string $container_name;
|
||||
@@ -65,6 +65,7 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
private $log_model;
|
||||
private Collection $saved_outputs;
|
||||
|
||||
public $tries = 1;
|
||||
public function __construct(int $application_deployment_queue_id)
|
||||
{
|
||||
ray()->clearScreen();
|
||||
@@ -88,7 +89,7 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||
|
||||
$this->container_name = generateApplicationContainerName($this->application->uuid, $this->pull_request_id);
|
||||
$this->private_key_location = save_private_key_for_server($this->server);
|
||||
addPrivateKeyToSshAgent($this->server);
|
||||
$this->saved_outputs = collect();
|
||||
|
||||
// Set preview fqdn
|
||||
@@ -118,6 +119,9 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
if ($containers->count() > 0) {
|
||||
$this->currently_running_container_name = data_get($containers[0], 'Names');
|
||||
}
|
||||
if ($this->pull_request_id !== 0 && $this->pull_request_id !== null) {
|
||||
$this->currently_running_container_name = $this->container_name;
|
||||
}
|
||||
$this->application_deployment_queue->update([
|
||||
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||
]);
|
||||
@@ -131,7 +135,9 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
$this->deploy();
|
||||
}
|
||||
}
|
||||
if ($this->application->fqdn) dispatch(new ProxyStartJob($this->server));
|
||||
if ($this->server->isProxyShouldRun()) {
|
||||
dispatch(new ContainerStatusJob($this->server));
|
||||
}
|
||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||
} catch (Exception $e) {
|
||||
ray($e);
|
||||
@@ -174,8 +180,8 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
$this->execute_in_builder("echo '$dockerfile_base64' | base64 -d > $this->workdir/Dockerfile")
|
||||
],
|
||||
);
|
||||
$this->build_image_name = "{$this->application->git_repository}:build";
|
||||
$this->production_image_name = "{$this->application->uuid}:latest";
|
||||
$this->build_image_name = Str::lower("{$this->application->git_repository}:build");
|
||||
$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();
|
||||
$this->generate_compose_file();
|
||||
$this->generate_build_env_variables();
|
||||
@@ -199,8 +205,8 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
$tag = $tag->substr(0, 128);
|
||||
}
|
||||
|
||||
$this->build_image_name = "{$this->application->git_repository}:{$tag}-build";
|
||||
$this->production_image_name = "{$this->application->uuid}:{$tag}";
|
||||
$this->build_image_name = Str::lower("{$this->application->git_repository}:{$tag}-build");
|
||||
$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();
|
||||
|
||||
if (!$this->force_rebuild) {
|
||||
@@ -235,7 +241,7 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
}
|
||||
private function health_check()
|
||||
{
|
||||
ray('New container name: ',$this->container_name);
|
||||
ray('New container name: ', $this->container_name);
|
||||
if ($this->container_name) {
|
||||
$counter = 0;
|
||||
$this->execute_remote_command(
|
||||
@@ -257,7 +263,7 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
);
|
||||
$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')) {
|
||||
@@ -266,6 +272,7 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
"echo 'Rolling update completed.'"
|
||||
],
|
||||
);
|
||||
$this->application->update(['status' => 'running']);
|
||||
break;
|
||||
}
|
||||
$counter++;
|
||||
@@ -275,8 +282,8 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
}
|
||||
private function deploy_pull_request()
|
||||
{
|
||||
$this->build_image_name = "{$this->application->uuid}:pr-{$this->pull_request_id}-build";
|
||||
$this->production_image_name = "{$this->application->uuid}:pr-{$this->pull_request_id}";
|
||||
$this->build_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}-build");
|
||||
$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();
|
||||
$this->execute_remote_command([
|
||||
"echo 'Starting pull request (#{$this->pull_request_id}) deployment of {$this->application->git_repository}:{$this->application->git_branch}.'",
|
||||
@@ -292,17 +299,28 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
// $this->generate_build_env_variables();
|
||||
// $this->add_build_env_variables_to_dockerfile();
|
||||
$this->build_image();
|
||||
$this->rolling_update();
|
||||
$this->stop_running_container();
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Starting preview deployment.'"],
|
||||
[$this->execute_in_builder("docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true],
|
||||
);
|
||||
}
|
||||
|
||||
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(
|
||||
[
|
||||
"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,
|
||||
],
|
||||
[
|
||||
@@ -487,9 +505,9 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
],
|
||||
'networks' => [
|
||||
$this->destination->network => [
|
||||
'external' => false,
|
||||
'external' => true,
|
||||
'name' => $this->destination->network,
|
||||
'attachable' => true,
|
||||
'attachable' => true
|
||||
]
|
||||
]
|
||||
];
|
||||
@@ -565,10 +583,15 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
|
||||
private function set_labels_for_applications()
|
||||
{
|
||||
|
||||
$appId = $this->application->id;
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$appId = $appId . '-pr-' . $this->pull_request_id;
|
||||
}
|
||||
$labels = [];
|
||||
$labels[] = 'coolify.managed=true';
|
||||
$labels[] = 'coolify.version=' . config('version');
|
||||
$labels[] = 'coolify.applicationId=' . $this->application->id;
|
||||
$labels[] = 'coolify.applicationId=' . $appId;
|
||||
$labels[] = 'coolify.type=application';
|
||||
$labels[] = 'coolify.name=' . $this->application->name;
|
||||
if ($this->pull_request_id !== 0) {
|
||||
@@ -629,6 +652,10 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
|
||||
private function generate_healthcheck_commands()
|
||||
{
|
||||
if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile') {
|
||||
// TODO: disabled HC because there are several ways to hc a simple docker image, hard to figure out a good way. Like some docker images (pocketbase) does not have curl.
|
||||
return 'exit 0';
|
||||
}
|
||||
if (!$this->application->health_check_port) {
|
||||
$this->application->health_check_port = $this->application->ports_exposes_array[0];
|
||||
}
|
||||
@@ -647,12 +674,12 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
private function build_image()
|
||||
{
|
||||
$this->execute_remote_command([
|
||||
"echo -n 'Building docker image.'",
|
||||
"echo -n 'Building docker image for your application.'",
|
||||
]);
|
||||
|
||||
if ($this->application->settings->is_static) {
|
||||
$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}
|
||||
@@ -685,12 +712,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("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 {
|
||||
$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
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -699,7 +726,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
{
|
||||
if ($this->currently_running_container_name) {
|
||||
$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],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,12 +6,13 @@ use App\Enums\ProcessStatus;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ApplicationPullRequestUpdateJob implements ShouldQueue
|
||||
class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
@@ -57,7 +58,7 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue
|
||||
} else {
|
||||
$this->create_comment();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
@@ -4,12 +4,13 @@ namespace App\Jobs;
|
||||
|
||||
use App\Actions\License\CheckResaleLicense;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CheckResaleLicenseJob implements ShouldQueue
|
||||
class CheckResaleLicenseJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
@@ -21,10 +22,10 @@ class CheckResaleLicenseJob implements ShouldQueue
|
||||
{
|
||||
try {
|
||||
resolve(CheckResaleLicense::class)();
|
||||
} catch (\Throwable $th) {
|
||||
send_internal_notification('CheckResaleLicenseJob failed with: ' . $th->getMessage());
|
||||
ray($th);
|
||||
throw $th;
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('CheckResaleLicenseJob failed with: ' . $e->getMessage());
|
||||
ray($e);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,17 @@
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\TeamInvitation;
|
||||
use App\Models\Waitlist;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CleanupInstanceStuffsJob implements ShouldQueue, ShouldBeUnique
|
||||
class CleanupInstanceStuffsJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
@@ -27,19 +29,31 @@ class CleanupInstanceStuffsJob implements ShouldQueue, ShouldBeUnique
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
$this->cleanup_waitlist();
|
||||
} catch (\Exception $e) {
|
||||
// $this->cleanup_waitlist();
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('CleanupInstanceStuffsJob failed with error: ' . $e->getMessage());
|
||||
ray($e->getMessage());
|
||||
}
|
||||
try {
|
||||
$this->cleanup_invitation_link();
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('CleanupInstanceStuffsJob failed with error: ' . $e->getMessage());
|
||||
ray($e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
private function cleanup_waitlist()
|
||||
{
|
||||
$waitlist = Waitlist::whereVerified(false)->where('created_at', '<', now()->subMinutes(config('constants.waitlist.confirmation_valid_for_minutes')))->get();
|
||||
$waitlist = Waitlist::whereVerified(false)->where('created_at', '<', now()->subMinutes(config('constants.waitlist.expiration')))->get();
|
||||
foreach ($waitlist as $item) {
|
||||
$item->delete();
|
||||
}
|
||||
}
|
||||
private function cleanup_invitation_link()
|
||||
{
|
||||
$invitation = TeamInvitation::all();
|
||||
foreach ($invitation as $item) {
|
||||
$item->isValid();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
290
app/Jobs/ContainerStatusJob.php
Normal file
290
app/Jobs/ContainerStatusJob.php
Normal file
@@ -0,0 +1,290 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Actions\Proxy\StartProxy;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\Server;
|
||||
use App\Notifications\Container\ContainerRestarted;
|
||||
use App\Notifications\Container\ContainerStopped;
|
||||
use App\Notifications\Server\Unreachable;
|
||||
use Arr;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $tries = 1;
|
||||
public $timeout = 120;
|
||||
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
|
||||
public function middleware(): array
|
||||
{
|
||||
return [new WithoutOverlapping($this->server->uuid)];
|
||||
}
|
||||
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return $this->server->uuid;
|
||||
}
|
||||
|
||||
private function checkServerConnection() {
|
||||
ray("Checking server connection to {$this->server->ip}");
|
||||
$uptime = instant_remote_process(['uptime'], $this->server, false);
|
||||
if (!is_null($uptime)) {
|
||||
ray('Server is up');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
ray()->clearAll();
|
||||
$serverUptimeCheckNumber = 0;
|
||||
$serverUptimeCheckNumberMax = 5;
|
||||
while (true) {
|
||||
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
|
||||
$this->server->settings()->update(['is_reachable' => false]);
|
||||
$this->server->team->notify(new Unreachable($this->server));
|
||||
return;
|
||||
}
|
||||
$result = $this->checkServerConnection();
|
||||
if ($result) {
|
||||
break;
|
||||
}
|
||||
$serverUptimeCheckNumber++;
|
||||
sleep(5);
|
||||
}
|
||||
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server);
|
||||
$containers = format_docker_command_output_to_json($containers);
|
||||
$applications = $this->server->applications();
|
||||
$databases = $this->server->databases();
|
||||
$previews = $this->server->previews();
|
||||
if ($this->server->isProxyShouldRun()) {
|
||||
$foundProxyContainer = $containers->filter(function ($value, $key) {
|
||||
return data_get($value, 'Name') === '/coolify-proxy';
|
||||
})->first();
|
||||
if (!$foundProxyContainer) {
|
||||
resolve(StartProxy::class)($this->server, false);
|
||||
$this->server->team->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||
}
|
||||
}
|
||||
$foundApplications = [];
|
||||
$foundApplicationPreviews = [];
|
||||
$foundDatabases = [];
|
||||
foreach ($containers as $container) {
|
||||
$containerStatus = data_get($container, 'State.Status');
|
||||
$labels = data_get($container, 'Config.Labels');
|
||||
$labels = Arr::undot(format_docker_labels_to_json($labels));
|
||||
$labelId = data_get($labels, 'coolify.applicationId');
|
||||
if ($labelId) {
|
||||
if (str_contains($labelId,'-pr-')) {
|
||||
$previewId = (int) Str::after($labelId, '-pr-');
|
||||
$applicationId = (int) Str::before($labelId, '-pr-');
|
||||
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id',$previewId)->first();
|
||||
if ($preview) {
|
||||
$foundApplicationPreviews[] = $preview->id;
|
||||
$statusFromDb = $preview->status;
|
||||
if ($statusFromDb !== $containerStatus) {
|
||||
$preview->update(['status' => $containerStatus]);
|
||||
}
|
||||
} else {
|
||||
//Notify user that this container should not be there.
|
||||
}
|
||||
} else {
|
||||
$application = $applications->where('id', $labelId)->first();
|
||||
if ($application) {
|
||||
$foundApplications[] = $application->id;
|
||||
$statusFromDb = $application->status;
|
||||
if ($statusFromDb !== $containerStatus) {
|
||||
$application->update(['status' => $containerStatus]);
|
||||
}
|
||||
} else {
|
||||
//Notify user that this container should not be there.
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$uuid = data_get($labels, 'com.docker.compose.service');
|
||||
if ($uuid) {
|
||||
$database = $databases->where('uuid', $uuid)->first();
|
||||
if ($database) {
|
||||
$foundDatabases[] = $database->id;
|
||||
$statusFromDb = $database->status;
|
||||
if ($statusFromDb !== $containerStatus) {
|
||||
$database->update(['status' => $containerStatus]);
|
||||
}
|
||||
} else {
|
||||
// Notify user that this container should not be there.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
$notRunningApplications = $applications->pluck('id')->diff($foundApplications);
|
||||
foreach($notRunningApplications as $applicationId) {
|
||||
$application = $applications->where('id', $applicationId)->first();
|
||||
if ($application->status === 'exited') {
|
||||
continue;
|
||||
}
|
||||
$application->update(['status' => 'exited']);
|
||||
|
||||
$name = data_get($application, 'name');
|
||||
$fqdn = data_get($application, 'fqdn');
|
||||
|
||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||
|
||||
$project = data_get($application, 'environment.project');
|
||||
$environment = data_get($application, 'environment');
|
||||
|
||||
$url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/application/" . $application->uuid;
|
||||
|
||||
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
}
|
||||
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
||||
foreach ($notRunningApplicationPreviews as $previewId) {
|
||||
$preview = $previews->where('id', $previewId)->first();
|
||||
if ($preview->status === 'exited') {
|
||||
continue;
|
||||
}
|
||||
$preview->update(['status' => 'exited']);
|
||||
|
||||
$name = data_get($preview, 'name');
|
||||
$fqdn = data_get($preview, 'fqdn');
|
||||
|
||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||
|
||||
$project = data_get($preview, 'application.environment.project');
|
||||
$environment = data_get($preview, 'application.environment');
|
||||
|
||||
$url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/application/" . $preview->application->uuid;
|
||||
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
}
|
||||
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
||||
foreach($notRunningDatabases as $database) {
|
||||
$database = $databases->where('id', $database)->first();
|
||||
if ($database->status === 'exited') {
|
||||
continue;
|
||||
}
|
||||
$database->update(['status' => 'exited']);
|
||||
|
||||
$name = data_get($database, 'name');
|
||||
$fqdn = data_get($database, 'fqdn');
|
||||
|
||||
$containerName = $name;
|
||||
|
||||
$project = data_get($database, 'environment.project');
|
||||
$environment = data_get($database, 'environment');
|
||||
|
||||
$url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/database/" . $database->uuid;
|
||||
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return;
|
||||
foreach ($applications as $application) {
|
||||
$uuid = data_get($application, 'uuid');
|
||||
$id = data_get($application, 'id');
|
||||
$foundContainer = $containers->filter(function ($value, $key) use ($id, $uuid) {
|
||||
$labels = data_get($value, 'Config.Labels');
|
||||
$labels = Arr::undot(format_docker_labels_to_json($labels));
|
||||
$labelId = data_get($labels, 'coolify.applicationId');
|
||||
if ($labelId == $id) {
|
||||
return $value;
|
||||
}
|
||||
$isPR = Str::startsWith(data_get($value, 'Name'), "/$uuid");
|
||||
$isPR = Str::contains(data_get($value, 'Name'), "-pr-");
|
||||
if ($isPR) {
|
||||
ray('is pr');
|
||||
return false;
|
||||
}
|
||||
return $value;
|
||||
})->first();
|
||||
ray($foundContainer);
|
||||
if ($foundContainer) {
|
||||
$containerStatus = data_get($foundContainer, 'State.Status');
|
||||
$databaseStatus = data_get($application, 'status');
|
||||
if ($containerStatus !== $databaseStatus) {
|
||||
$application->update(['status' => $containerStatus]);
|
||||
}
|
||||
} else {
|
||||
$databaseStatus = data_get($application, 'status');
|
||||
if ($databaseStatus !== 'exited') {
|
||||
$application->update(['status' => 'exited']);
|
||||
$name = data_get($application, 'name');
|
||||
$fqdn = data_get($application, 'fqdn');
|
||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||
$project = data_get($application, 'environment.project');
|
||||
$environment = data_get($application, 'environment');
|
||||
$url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/application/" . $application->uuid;
|
||||
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
}
|
||||
}
|
||||
$previews = $application->previews;
|
||||
foreach ($previews as $preview) {
|
||||
$foundContainer = $containers->filter(function ($value, $key) use ($id, $uuid, $preview) {
|
||||
$labels = data_get($value, 'Config.Labels');
|
||||
$labels = Arr::undot(format_docker_labels_to_json($labels));
|
||||
$labelId = data_get($labels, 'coolify.applicationId');
|
||||
if ($labelId == "$id-pr-{$preview->id}") {
|
||||
return $value;
|
||||
}
|
||||
return Str::startsWith(data_get($value, 'Name'), "/$uuid-pr-{$preview->id}");
|
||||
})->first();
|
||||
}
|
||||
|
||||
}
|
||||
foreach ($databases as $database) {
|
||||
$uuid = data_get($database, 'uuid');
|
||||
$foundContainer = $containers->filter(function ($value, $key) use ($uuid) {
|
||||
return Str::startsWith(data_get($value, 'Name'), "/$uuid");
|
||||
})->first();
|
||||
|
||||
if ($foundContainer) {
|
||||
$containerStatus = data_get($foundContainer, 'State.Status');
|
||||
$databaseStatus = data_get($database, 'status');
|
||||
if ($containerStatus !== $databaseStatus) {
|
||||
$database->update(['status' => $containerStatus]);
|
||||
}
|
||||
} else {
|
||||
$databaseStatus = data_get($database, 'status');
|
||||
if ($databaseStatus !== 'exited') {
|
||||
$database->update(['status' => 'exited']);
|
||||
$name = data_get($database, 'name');
|
||||
$containerName = $name;
|
||||
$project = data_get($database, 'environment.project');
|
||||
$environment = data_get($database, 'environment');
|
||||
$url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/database/" . $database->uuid;
|
||||
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO Monitor other containers not managed by Coolify
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('ContainerStatusJob failed with: ' . $e->getMessage());
|
||||
ray($e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,14 @@ namespace App\Jobs;
|
||||
|
||||
use App\Actions\CoolifyTask\RunRemoteProcess;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
class CoolifyTask implements ShouldQueue
|
||||
class CoolifyTask implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ use App\Notifications\Database\BackupFailed;
|
||||
use App\Notifications\Database\BackupSuccess;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
@@ -20,35 +21,31 @@ use Illuminate\Queue\SerializesModels;
|
||||
use Throwable;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class DatabaseBackupJob implements ShouldQueue
|
||||
class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public Team|null $team = null;
|
||||
public ?Team $team = null;
|
||||
public Server $server;
|
||||
public ScheduledDatabaseBackup|null $backup;
|
||||
public string $database_type;
|
||||
public ScheduledDatabaseBackup $backup;
|
||||
public StandalonePostgresql $database;
|
||||
public string $database_status;
|
||||
|
||||
public string|null $container_name = null;
|
||||
public ScheduledDatabaseBackupExecution|null $backup_log = null;
|
||||
public ?string $container_name = null;
|
||||
public ?ScheduledDatabaseBackupExecution $backup_log = null;
|
||||
public string $backup_status;
|
||||
public string|null $backup_location = null;
|
||||
public ?string $backup_location = null;
|
||||
public string $backup_dir;
|
||||
public string $backup_file;
|
||||
public int $size = 0;
|
||||
public string|null $backup_output = null;
|
||||
public S3Storage|null $s3 = null;
|
||||
public ?string $backup_output = null;
|
||||
public ?S3Storage $s3 = null;
|
||||
|
||||
public function __construct($backup)
|
||||
{
|
||||
$this->backup = $backup;
|
||||
$this->team = Team::find($backup->team_id);
|
||||
$this->database = $this->backup->database;
|
||||
$this->database_type = $this->database->type();
|
||||
$this->database = data_get($this->backup, 'database');
|
||||
$this->server = $this->database->destination->server;
|
||||
$this->database_status = $this->database->status;
|
||||
$this->s3 = $this->backup->s3;
|
||||
}
|
||||
|
||||
@@ -65,7 +62,7 @@ class DatabaseBackupJob implements ShouldQueue
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
if ($this->database_status !== 'running') {
|
||||
if (data_get($this->database, 'status') !== 'running') {
|
||||
ray('database not running');
|
||||
return;
|
||||
}
|
||||
@@ -77,14 +74,14 @@ class DatabaseBackupJob implements ShouldQueue
|
||||
$ip = Str::slug($this->server->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_log = ScheduledDatabaseBackupExecution::create([
|
||||
'filename' => $this->backup_location,
|
||||
'scheduled_database_backup_id' => $this->backup->id,
|
||||
]);
|
||||
if ($this->database_type === 'standalone-postgresql') {
|
||||
if ($this->database->type() === 'standalone-postgresql') {
|
||||
$this->backup_standalone_postgresql();
|
||||
}
|
||||
$this->calculate_size();
|
||||
@@ -94,12 +91,11 @@ class DatabaseBackupJob implements ShouldQueue
|
||||
}
|
||||
$this->save_backup_logs();
|
||||
// TODO: Notify user
|
||||
} catch (\Throwable $th) {
|
||||
ray($th->getMessage());
|
||||
send_internal_notification('DatabaseBackupJob failed with: ' . $th->getMessage());
|
||||
throw $th;
|
||||
} catch (\Throwable $e) {
|
||||
ray($e->getMessage());
|
||||
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function backup_standalone_postgresql(): void
|
||||
@@ -107,7 +103,7 @@ class DatabaseBackupJob implements ShouldQueue
|
||||
try {
|
||||
ray($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);
|
||||
|
||||
@@ -121,10 +117,10 @@ class DatabaseBackupJob implements ShouldQueue
|
||||
|
||||
$this->backup_status = 'success';
|
||||
$this->team->notify(new BackupSuccess($this->backup, $this->database));
|
||||
} catch (Throwable $th) {
|
||||
} catch (Throwable $e) {
|
||||
$this->backup_status = 'failed';
|
||||
$this->add_to_backup_output($th->getMessage());
|
||||
ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $th->getMessage());
|
||||
$this->add_to_backup_output($e->getMessage());
|
||||
ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $e->getMessage());
|
||||
$this->team->notify(new BackupFailed($this->backup, $this->database, $this->backup_output));
|
||||
} finally {
|
||||
$this->backup_log->update([
|
||||
@@ -178,9 +174,9 @@ class DatabaseBackupJob implements ShouldQueue
|
||||
instant_remote_process($commands, $this->server);
|
||||
$this->add_to_backup_output('Uploaded to S3.');
|
||||
ray('Uploaded to S3. ' . $this->backup_location . ' to s3://' . $bucket . $this->backup_dir);
|
||||
} catch (\Throwable $th) {
|
||||
$this->add_to_backup_output($th->getMessage());
|
||||
ray($th->getMessage());
|
||||
} catch (\Throwable $e) {
|
||||
$this->add_to_backup_output($e->getMessage());
|
||||
ray($e->getMessage());
|
||||
} finally {
|
||||
$command = "docker rm -f backup-of-{$this->backup->uuid}";
|
||||
instant_remote_process([$command], $this->server);
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Notifications\Application\StatusChanged;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class DatabaseContainerStatusJob implements ShouldQueue, ShouldBeUnique
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public string $containerName;
|
||||
|
||||
public function __construct(
|
||||
public StandalonePostgresql $database,
|
||||
) {
|
||||
$this->containerName = $database->uuid;
|
||||
}
|
||||
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return $this->containerName;
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
$status = getContainerStatus(
|
||||
server: $this->database->destination->server,
|
||||
container_id: $this->containerName,
|
||||
throwError: false
|
||||
);
|
||||
|
||||
if ($this->database->status === 'running' && $status !== 'running') {
|
||||
if (data_get($this->database, 'environment.project.team')) {
|
||||
// $this->database->environment->project->team->notify(new StatusChanged($this->database));
|
||||
}
|
||||
}
|
||||
if ($this->database->status !== $status) {
|
||||
$this->database->status = $status;
|
||||
$this->database->save();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
send_internal_notification('DatabaseContainerStatusJob failed with: ' . $e->getMessage());
|
||||
ray($e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user