Compare commits

..

47 Commits
main ... v4.0.1

Author SHA1 Message Date
Gary
083668857d main: test update versions 2024-03-06 12:33:32 -08:00
Gary
10ac40a295 main: fix url and typo 2024-03-06 12:30:24 -08:00
Gary
9cf6925652 main: change version numbering 2024-03-06 11:55:06 -08:00
Gary
3aa9a8973b docker-compose: change imgae to 4.0.0 2024-03-06 11:43:50 -08:00
Gary
a04abdcca9 main: fix for various bugs 2024-03-05 16:39:13 -08:00
Gary
7238918f35 main: change CDN url paths 2024-03-01 19:42:21 -08:00
Gary
68c933b09e Revert "main: replace curl install commands with wget"
This reverts commit e2a5d711345eeffade88bacfd315c4b55be6d69e.
We should use a CDN url for this to work.
2024-03-01 19:36:13 -08:00
Gary
fdf832e9c7 main: replace curl with wget on install.sh 2024-03-01 19:32:37 -08:00
Gary
e2a5d71134 main: replace curl install commands with wget 2024-03-01 19:23:14 -08:00
Gary
5f87e99a61 main: fix CDN url 2024-03-01 19:17:11 -08:00
Gary
2d6b84c737 main: replace CDN with githaven urls 2024-03-01 18:43:19 -08:00
Gary
6a97eaad12 main: begin major rewrite for lasthour 2024-03-01 18:37:31 -08:00
Gary
24cac5feba main: modify copy on guided tour blade. 2024-03-01 16:46:04 -08:00
Gary
f335adaaed main: Begin launch files rewrite
Docker image now on githaven
2024-03-01 16:16:09 -08:00
Gary
3ea111478e main: change upgrade.sh and local_install.sh 2024-02-29 10:49:13 -08:00
Gary
633cd4ca7c main: change docker run upgrade.sh 2024-02-29 10:46:20 -08:00
Gary
0903cc537f main: modify compose prod file and local and upgrade sh 2024-02-29 10:38:46 -08:00
Gary
bf2b4dfa44 main: WIP docker compose and local_install.sh 2024-02-27 15:31:05 -08:00
Gary
714d059745 main: WIP compose files and local_install.sh 2024-02-27 15:15:06 -08:00
Gary
57c71fa451 main: fix local_install 2024-02-23 16:31:43 -08:00
Gary
deb8b3319d main: further changes to upgrade.sh 2024-02-23 16:22:34 -08:00
Gary
9b2d0e08e0 Revert "main: simplify launch scripts"
This reverts commit 1c27ca0ce432e24d3ca5ede5771f2d191e4ba8dd.
2024-02-23 16:20:06 -08:00
Gary
ae661990de main: modify local_install.sh 2024-02-23 15:48:11 -08:00
Gary
239f9e9b31 main: modify path local_install and upgrade 2024-02-23 15:32:23 -08:00
Gary
c34013f7e2 main: modify local_install.sh mkdir path 2024-02-23 15:26:50 -08:00
Gary
eda724cf6c main: modify local_install 2024-02-23 15:16:10 -08:00
Gary
1c27ca0ce4 main: simplify launch scripts 2024-02-23 15:06:09 -08:00
Gary
977c6c10f4 main: fix login blade 2024-02-23 11:21:57 -08:00
Gary
44a0d61d0e main: modify upgrade.sh to include copy script 2024-02-23 10:59:29 -08:00
Gary
0a31199ecd main: modify local_install.sh 2024-02-23 09:34:07 -08:00
Gary
fce392c7ed main: modify local_install.sh 2024-02-23 09:12:55 -08:00
Gary
21598780a6 main: modify docker-compose.prod.yml 2024-02-23 08:51:04 -08:00
Gary
0154e9eff8 main: change local_install.sh 2024-02-22 18:09:33 -08:00
Gary
376ee8d3d7 main: fix to local_install.sh cp commands 2024-02-22 18:04:03 -08:00
Gary
e9fad4f1a1 main: WIP upgrade.sh
Need to replace the CDN links for remote upgrade
2024-02-22 17:55:59 -08:00
Gary
8aba1f0795 main: modify local install script 2024-02-22 17:43:23 -08:00
Gary
6f6a839c8e main: change docker-compose.prod volume paths 2024-02-22 17:00:07 -08:00
Gary
ddcd7d21e6 main: changed wording on blades 2024-02-22 16:40:59 -08:00
Gary
1babe6d91b main: rename local install script 2024-02-22 16:25:04 -08:00
Gary
b60e437d66 main: modify docker compose and local launch sequence 2024-02-22 16:22:33 -08:00
Gary
e7448f5e6b main: path modify composer file 2024-02-21 17:14:38 -08:00
Gary
eed96f4134 main: change bind source path compose file 2024-02-21 17:03:53 -08:00
Gary
3ff76e16ca main: change compose bind source type 2024-02-21 17:00:59 -08:00
Gary
eb226f1a39 main: modify compose file 2024-02-21 16:58:14 -08:00
Gary
94004acdfe main: revise docker compose file 2024-02-21 16:56:01 -08:00
Gary
3e21cb29f6 main: changes to docker file 2024-02-21 15:49:11 -08:00
Gary
a2a2a69eb8 main: initial app rewrite 2024-02-21 15:21:08 -08:00
1059 changed files with 15463 additions and 54213 deletions

View File

@ -1,28 +1,17 @@
name: Bug report name: Bug report
description: "Create a new bug report." description: Create a new bug report
title: "[Bug]: " title: '[Bug]: '
body: body:
- type: markdown
attributes:
value: >-
# 💎 Bounty program (with
[algora.io](https://console.algora.io/org/coollabsio/bounties/new))
If you would like to prioritize the issue resolution, you can add bounty
to this issue.
Click [here](https://console.algora.io/org/coollabsio/bounties/new) to
get started.
- type: textarea - type: textarea
attributes: attributes:
label: Description label: Description
description: A clear and concise description of the problem description: A clear and concise description of the problem
validations:
required: true
- type: textarea - type: textarea
attributes: attributes:
label: Minimal Reproduction (if possible, example repository) label: Minimal Reproduction (if possible, example repository)
description: Please provide a step by step guide to reproduce the issue. description: Please provide a step by step guide to reproduce the issue
validations: validations:
required: true required: true
- type: textarea - type: textarea
@ -32,15 +21,6 @@ body:
- type: input - type: input
attributes: attributes:
label: Version label: Version
description: Coolify's version (see top of your screen). description: Coolify's version (see bottom left corner).
validations: validations:
required: true required: true
- type: checkboxes
attributes:
label: Cloud?
description: "Are you using the cloud version of Coolify?"
options:
- label: 'Yes'
required: false
- label: 'No'
required: false

View File

@ -1 +0,0 @@
> Always use `next` branch as destination branch for PRs, not `main`

View File

@ -18,15 +18,15 @@ jobs:
contents: read contents: read
packages: write packages: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Login to ghcr.io - name: Login to ghcr.io
uses: docker/login-action@v3 uses: docker/login-action@v2
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image and push to registry - name: Build image and push to registry
uses: docker/build-push-action@v5 uses: docker/build-push-action@v3
with: with:
no-cache: true no-cache: true
context: . context: .
@ -40,15 +40,15 @@ jobs:
contents: read contents: read
packages: write packages: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Login to ghcr.io - name: Login to ghcr.io
uses: docker/login-action@v3 uses: docker/login-action@v2
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image and push to registry - name: Build image and push to registry
uses: docker/build-push-action@v5 uses: docker/build-push-action@v3
with: with:
no-cache: true no-cache: true
context: . context: .
@ -64,13 +64,13 @@ jobs:
needs: [ amd64, aarch64 ] needs: [ amd64, aarch64 ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v2
- name: Login to ghcr.io - name: Login to ghcr.io
uses: docker/login-action@v3 uses: docker/login-action@v2
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}

View File

@ -18,15 +18,15 @@ jobs:
contents: read contents: read
packages: write packages: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Login to ghcr.io - name: Login to ghcr.io
uses: docker/login-action@v3 uses: docker/login-action@v2
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image and push to registry - name: Build image and push to registry
uses: docker/build-push-action@v5 uses: docker/build-push-action@v3
with: with:
no-cache: true no-cache: true
context: . context: .
@ -40,15 +40,15 @@ jobs:
contents: read contents: read
packages: write packages: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Login to ghcr.io - name: Login to ghcr.io
uses: docker/login-action@v3 uses: docker/login-action@v2
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image and push to registry - name: Build image and push to registry
uses: docker/build-push-action@v5 uses: docker/build-push-action@v3
with: with:
no-cache: true no-cache: true
context: . context: .
@ -64,13 +64,13 @@ jobs:
needs: [ amd64, aarch64 ] needs: [ amd64, aarch64 ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v2
- name: Login to ghcr.io - name: Login to ghcr.io
uses: docker/login-action@v3 uses: docker/login-action@v2
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}

View File

@ -18,15 +18,15 @@ jobs:
contents: read contents: read
packages: write packages: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Login to ghcr.io - name: Login to ghcr.io
uses: docker/login-action@v3 uses: docker/login-action@v2
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image and push to registry - name: Build image and push to registry
uses: docker/build-push-action@v5 uses: docker/build-push-action@v3
with: with:
no-cache: true no-cache: true
context: . context: .
@ -40,15 +40,15 @@ jobs:
contents: read contents: read
packages: write packages: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Login to ghcr.io - name: Login to ghcr.io
uses: docker/login-action@v3 uses: docker/login-action@v2
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image and push to registry - name: Build image and push to registry
uses: docker/build-push-action@v5 uses: docker/build-push-action@v3
with: with:
no-cache: true no-cache: true
context: . context: .
@ -64,13 +64,13 @@ jobs:
needs: [ amd64, aarch64 ] needs: [ amd64, aarch64 ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v2
- name: Login to ghcr.io - name: Login to ghcr.io
uses: docker/login-action@v3 uses: docker/login-action@v2
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}

View File

@ -26,7 +26,7 @@ jobs:
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
context: . context: .
file: docker/prod/Dockerfile file: docker/prod-ssu/Dockerfile
platforms: linux/amd64 platforms: linux/amd64
push: true push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }} tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
@ -47,7 +47,7 @@ jobs:
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
context: . context: .
file: docker/prod/Dockerfile file: docker/prod-ssu/Dockerfile
platforms: linux/aarch64 platforms: linux/aarch64
push: true push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64 tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64

View File

@ -14,7 +14,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Cache Docker layers - name: Cache Docker layers
uses: actions/cache@v2 uses: actions/cache@v2
with: with:

View File

@ -1,25 +0,0 @@
name: Fix PHP code style issues
on: [push]
permissions:
contents: write
jobs:
php-code-styling:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
- name: Fix PHP code style issues
uses: aglipanci/laravel-pint-action@2.4
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: Fix styling

View File

@ -3,8 +3,6 @@ name: Production Build (v4)
on: on:
push: push:
branches: ["main"] branches: ["main"]
paths-ignore:
- templates/service-templates.json
env: env:
REGISTRY: ghcr.io REGISTRY: ghcr.io
@ -29,7 +27,7 @@ jobs:
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
context: . context: .
file: docker/prod/Dockerfile file: docker/prod-ssu/Dockerfile
platforms: linux/amd64 platforms: linux/amd64
push: true push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
@ -51,7 +49,7 @@ jobs:
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
context: . context: .
file: docker/prod/Dockerfile file: docker/prod-ssu/Dockerfile
platforms: linux/aarch64 platforms: linux/aarch64
push: true push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64

View File

@ -3,7 +3,7 @@ tasks:
# Fix because of https://github.com/gitpod-io/gitpod/issues/16614 # Fix because of https://github.com/gitpod-io/gitpod/issues/16614
before: sudo curl -o /usr/local/bin/docker-compose -fsSL https://github.com/docker/compose/releases/download/v2.16.0/docker-compose-linux-$(uname -m) before: sudo curl -o /usr/local/bin/docker-compose -fsSL https://github.com/docker/compose/releases/download/v2.16.0/docker-compose-linux-$(uname -m)
init: | init: |
cp .env.development.example .env && cp .env.example .env &&
sed -i "s#APP_URL=http://localhost#APP_URL=$(gp url 8000)#g" .env sed -i "s#APP_URL=http://localhost#APP_URL=$(gp url 8000)#g" .env
sed -i "s#USERID=#USERID=33333#g" .env sed -i "s#USERID=#USERID=33333#g" .env
sed -i "s#GROUPID=#GROUPID=33333#g" .env sed -i "s#GROUPID=#GROUPID=33333#g" .env
@ -20,7 +20,7 @@ tasks:
echo "Waiting for Sail environment to boot up." echo "Waiting for Sail environment to boot up."
gp sync-await spin-is-ready gp sync-await spin-is-ready
./vendor/bin/spin exec vite npm install ./vendor/bin/spin exec vite npm install
./vendor/bin/spin exec vite npm run dev -- --host ./vendor/bin/spin exec vite npm run dev
- name: Laravel Queue Worker, listening to code changes - name: Laravel Queue Worker, listening to code changes
command: | command: |

View File

@ -0,0 +1,22 @@
<?php
use App\Models\User;
$email = 'test@example.com';
$user = User::whereEmail($email)->first();
$teams = $user->teams;
foreach ($teams as $team) {
$servers = $team->servers;
if ($servers->count() > 0) {
foreach ($servers as $server) {
dump($server);
$server->delete();
}
}
dump($team);
$team->delete();
}
if ($user) {
dump($user);
$user->delete();
}

View File

@ -1,34 +0,0 @@
# Contributing
> "First, thanks for considering to contribute to my project.
It really means a lot!" - [@andrasbacsai](https://github.com/andrasbacsai)
You can ask for guidance anytime on our
[Discord server](https://coollabs.io/discord) in the `#contribution` channel.
## Code Contribution
### 1) Setup your development environment
- You need to have Docker Engine (or equivalent) [installed](https://docs.docker.com/engine/install/) on your system.
- For better DX, install [Spin](https://serversideup.net/open-source/spin/).
### 2) Set your environment variables
- Copy [.env.development.example](./.env.development.example) to .env.
## 3) Start & setup Coolify
- Run `spin up` - You can notice that errors will be thrown. Don't worry.
- If you see weird permission errors, especially on Mac, run `sudo spin up` instead.
### 4) Start development
You can login your Coolify instance at `localhost:8000` with `test@example.com` and `password`.
Your horizon (Laravel scheduler): `localhost:8000/horizon` - Only reachable if you logged in with root user.
Mails are caught by Mailpit: `localhost:8025`
## New Service Contribution
Check out the docs [here](https://coolify.io/docs/knowledge-base/add-a-service).

View File

@ -1,16 +0,0 @@
# Security Policy
## Supported Versions
Use this section to tell people about which versions of your project are
currently being supported with security updates.
| Version | Supported |
| ------- | ------------------ |
| > 4 | :white_check_mark: |
| 3 | :x: |
## Reporting a Vulnerability
If you have any vulnerability please report at hi@coollabs.io

View File

@ -1,16 +0,0 @@
<?php
namespace App\Actions\Application;
use App\Models\Application;
use Lorisleiva\Actions\Concerns\AsAction;
class LoadComposeFile
{
use AsAction;
public function handle(Application $application)
{
$application->loadComposeFile();
}
}

View File

@ -3,17 +3,17 @@
namespace App\Actions\Application; namespace App\Actions\Application;
use App\Models\Application; use App\Models\Application;
use App\Models\StandaloneDocker;
use App\Notifications\Application\StatusChanged;
use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\AsAction;
class StopApplication class StopApplication
{ {
use AsAction; use AsAction;
public function handle(Application $application)
public function handle(Application $application, bool $previewDeployments = false)
{ {
if ($application->destination->server->isSwarm()) { if ($application->destination->server->isSwarm()) {
instant_remote_process(["docker stack rm {$application->uuid}"], $application->destination->server); instant_remote_process(["docker stack rm {$application->uuid}"], $application->destination->server);
return; return;
} }
@ -23,15 +23,10 @@ class StopApplication
$servers->push($server); $servers->push($server);
}); });
foreach ($servers as $server) { foreach ($servers as $server) {
if (! $server->isFunctional()) { if (!$server->isFunctional()) {
return 'Server is not functional'; return 'Server is not functional';
} }
if ($previewDeployments) { $containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
$containers = getCurrentApplicationContainerStatus($server, $application->id, includePullrequests: true);
} else {
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
}
ray($containers);
if ($containers->count() > 0) { if ($containers->count() > 0) {
foreach ($containers as $container) { foreach ($containers as $container) {
$containerName = data_get($container, 'Names'); $containerName = data_get($container, 'Names');
@ -43,12 +38,6 @@ class StopApplication
} }
} }
} }
if ($application->build_pack === 'dockercompose') {
// remove network
$uuid = $application->uuid;
instant_remote_process(["docker network disconnect {$uuid} coolify-proxy"], $server, false);
instant_remote_process(["docker network rm {$uuid}"], $server, false);
}
} }
} }
} }

View File

@ -9,13 +9,12 @@ use Lorisleiva\Actions\Concerns\AsAction;
class StopApplicationOneServer class StopApplicationOneServer
{ {
use AsAction; use AsAction;
public function handle(Application $application, Server $server) public function handle(Application $application, Server $server)
{ {
if ($application->destination->server->isSwarm()) { if ($application->destination->server->isSwarm()) {
return; return;
} }
if (! $server->isFunctional()) { if (!$server->isFunctional()) {
return 'Server is not functional'; return 'Server is not functional';
} }
try { try {
@ -33,7 +32,6 @@ class StopApplicationOneServer
} }
} catch (\Exception $e) { } catch (\Exception $e) {
ray($e->getMessage()); ray($e->getMessage());
return $e->getMessage(); return $e->getMessage();
} }
} }

View File

@ -3,7 +3,6 @@
namespace App\Actions\CoolifyTask; namespace App\Actions\CoolifyTask;
use App\Data\CoolifyTaskArgs; use App\Data\CoolifyTaskArgs;
use App\Enums\ActivityTypes;
use App\Jobs\CoolifyTask; use App\Jobs\CoolifyTask;
use Spatie\Activitylog\Models\Activity; use Spatie\Activitylog\Models\Activity;
@ -15,7 +14,6 @@ use Spatie\Activitylog\Models\Activity;
class PrepareCoolifyTask class PrepareCoolifyTask
{ {
protected Activity $activity; protected Activity $activity;
protected CoolifyTaskArgs $remoteProcessArgs; protected CoolifyTaskArgs $remoteProcessArgs;
public function __construct(CoolifyTaskArgs $remoteProcessArgs) public function __construct(CoolifyTaskArgs $remoteProcessArgs)
@ -30,31 +28,20 @@ class PrepareCoolifyTask
->withProperties($properties) ->withProperties($properties)
->performedOn($remoteProcessArgs->model) ->performedOn($remoteProcessArgs->model)
->event($remoteProcessArgs->type) ->event($remoteProcessArgs->type)
->log('[]'); ->log("[]");
} else { } else {
$this->activity = activity() $this->activity = activity()
->withProperties($remoteProcessArgs->toArray()) ->withProperties($remoteProcessArgs->toArray())
->event($remoteProcessArgs->type) ->event($remoteProcessArgs->type)
->log('[]'); ->log("[]");
} }
} }
public function __invoke(): Activity public function __invoke(): Activity
{ {
$job = new CoolifyTask( $job = new CoolifyTask($this->activity, ignore_errors: $this->remoteProcessArgs->ignore_errors, call_event_on_finish: $this->remoteProcessArgs->call_event_on_finish);
activity: $this->activity, dispatch($job);
ignore_errors: $this->remoteProcessArgs->ignore_errors,
call_event_on_finish: $this->remoteProcessArgs->call_event_on_finish,
call_event_data: $this->remoteProcessArgs->call_event_data,
);
if ($this->remoteProcessArgs->type === ActivityTypes::COMMAND->value) {
ray('Dispatching a high priority job');
dispatch($job)->onQueue('high');
} else {
dispatch($job);
}
$this->activity->refresh(); $this->activity->refresh();
return $this->activity; return $this->activity;
} }
} }

View File

@ -21,8 +21,6 @@ class RunRemoteProcess
public $call_event_on_finish = null; public $call_event_on_finish = null;
public $call_event_data = null;
protected $time_start; protected $time_start;
protected $current_time; protected $current_time;
@ -36,10 +34,10 @@ class RunRemoteProcess
/** /**
* Create a new job instance. * Create a new job instance.
*/ */
public function __construct(Activity $activity, bool $hide_from_output = false, bool $ignore_errors = false, $call_event_on_finish = null, $call_event_data = null) public function __construct(Activity $activity, bool $hide_from_output = false, bool $ignore_errors = false, $call_event_on_finish = null)
{ {
if ($activity->getExtraProperty('type') !== ActivityTypes::INLINE->value && $activity->getExtraProperty('type') !== ActivityTypes::COMMAND->value) { if ($activity->getExtraProperty('type') !== ActivityTypes::INLINE->value) {
throw new \RuntimeException('Incompatible Activity to run a remote command.'); throw new \RuntimeException('Incompatible Activity to run a remote command.');
} }
@ -47,7 +45,6 @@ class RunRemoteProcess
$this->hide_from_output = $hide_from_output; $this->hide_from_output = $hide_from_output;
$this->ignore_errors = $ignore_errors; $this->ignore_errors = $ignore_errors;
$this->call_event_on_finish = $call_event_on_finish; $this->call_event_on_finish = $call_event_on_finish;
$this->call_event_data = $call_event_data;
} }
public static function decodeOutput(?Activity $activity = null): string public static function decodeOutput(?Activity $activity = null): string
@ -60,7 +57,7 @@ class RunRemoteProcess
$decoded = json_decode( $decoded = json_decode(
data_get($activity, 'description'), data_get($activity, 'description'),
associative: true, associative: true,
flags: JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE flags: JSON_THROW_ON_ERROR
); );
} catch (\JsonException $exception) { } catch (\JsonException $exception) {
return ''; return '';
@ -69,7 +66,7 @@ class RunRemoteProcess
return collect($decoded) return collect($decoded)
->sortBy(fn ($i) => $i['order']) ->sortBy(fn ($i) => $i['order'])
->map(fn ($i) => $i['output']) ->map(fn ($i) => $i['output'])
->implode(''); ->implode("");
} }
public function __invoke(): ProcessResult public function __invoke(): ProcessResult
@ -91,7 +88,7 @@ class RunRemoteProcess
if ($processResult->exitCode() == 0) { if ($processResult->exitCode() == 0) {
$status = ProcessStatus::FINISHED; $status = ProcessStatus::FINISHED;
} }
if ($processResult->exitCode() != 0 && ! $this->ignore_errors) { if ($processResult->exitCode() != 0 && !$this->ignore_errors) {
$status = ProcessStatus::ERROR; $status = ProcessStatus::ERROR;
} }
// if (($processResult->exitCode() == 0 && $this->is_finished) || $this->activity->properties->get('status') === ProcessStatus::FINISHED->value) { // if (($processResult->exitCode() == 0 && $this->is_finished) || $this->activity->properties->get('status') === ProcessStatus::FINISHED->value) {
@ -109,25 +106,18 @@ class RunRemoteProcess
'status' => $status->value, 'status' => $status->value,
]); ]);
$this->activity->save(); $this->activity->save();
if ($processResult->exitCode() != 0 && ! $this->ignore_errors) { if ($processResult->exitCode() != 0 && !$this->ignore_errors) {
throw new \RuntimeException($processResult->errorOutput(), $processResult->exitCode()); throw new \RuntimeException($processResult->errorOutput(), $processResult->exitCode());
} }
if ($this->call_event_on_finish) { if ($this->call_event_on_finish) {
try { try {
if ($this->call_event_data) { event(resolve("App\\Events\\$this->call_event_on_finish", [
event(resolve("App\\Events\\$this->call_event_on_finish", [ 'userId' => $this->activity->causer_id,
'data' => $this->call_event_data, ]));
]));
} else {
event(resolve("App\\Events\\$this->call_event_on_finish", [
'userId' => $this->activity->causer_id,
]));
}
} catch (\Throwable $e) { } catch (\Throwable $e) {
ray($e); ray($e);
} }
} }
return $processResult; return $processResult;
} }
@ -165,7 +155,8 @@ class RunRemoteProcess
public function encodeOutput($type, $output) public function encodeOutput($type, $output)
{ {
$outputStack = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE); $outputStack = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR);
$outputStack[] = [ $outputStack[] = [
'type' => $type, 'type' => $type,
'output' => $output, 'output' => $output,
@ -174,16 +165,15 @@ class RunRemoteProcess
'order' => $this->getLatestCounter(), 'order' => $this->getLatestCounter(),
]; ];
return json_encode($outputStack, flags: JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE); return json_encode($outputStack, flags: JSON_THROW_ON_ERROR);
} }
protected function getLatestCounter(): int protected function getLatestCounter(): int
{ {
$description = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE); $description = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR);
if ($description === null || count($description) === 0) { if ($description === null || count($description) === 0) {
return 1; return 1;
} }
return end($description)['order'] + 1; return end($description)['order'] + 1;
} }

View File

@ -1,29 +0,0 @@
<?php
namespace App\Actions\Database;
use App\Models\StandaloneClickhouse;
use App\Models\StandaloneDragonfly;
use App\Models\StandaloneKeydb;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Lorisleiva\Actions\Concerns\AsAction;
class RestartDatabase
{
use AsAction;
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database)
{
$server = $database->destination->server;
if (! $server->isFunctional()) {
return 'Server is not functional';
}
StopDatabase::run($database);
return StartDatabase::run($database);
}
}

View File

@ -1,167 +0,0 @@
<?php
namespace App\Actions\Database;
use App\Models\StandaloneClickhouse;
use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class StartClickhouse
{
use AsAction;
public StandaloneClickhouse $database;
public array $commands = [];
public string $configuration_dir;
public function handle(StandaloneClickhouse $database)
{
$this->database = $database;
$container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
$this->commands = [
"echo 'Starting {$database->name}.'",
"mkdir -p $this->configuration_dir",
];
$persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
$docker_compose = [
'services' => [
$container_name => [
'image' => $this->database->image,
'container_name' => $container_name,
'environment' => $environment_variables,
'restart' => RESTART_MODE,
'networks' => [
$this->database->destination->network,
],
'ulimits' => [
'nofile' => [
'soft' => 262144,
'hard' => 262144,
],
],
'labels' => [
'coolify.managed' => 'true',
],
'healthcheck' => [
'test' => "clickhouse-client --password {$this->database->clickhouse_admin_password} --query 'SELECT 1'",
'interval' => '5s',
'timeout' => '5s',
'retries' => 10,
'start_period' => '5s',
],
'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap,
'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares,
],
],
'networks' => [
$this->database->destination->network => [
'external' => true,
'name' => $this->database->destination->network,
'attachable' => true,
],
],
];
if (! is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
}
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
'options' => [
'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => 'true',
'fluentd-sub-second-precision' => 'true',
],
];
}
if (count($this->database->ports_mappings_array) > 0) {
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
}
if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
}
if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path";
})->toArray();
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}
private function generate_local_persistent_volumes()
{
$local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
} else {
$volume_name = $persistentStorage->name;
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
}
}
return $local_persistent_volumes;
}
private function generate_local_persistent_volumes_only_volume_names()
{
$local_persistent_volumes_names = [];
foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path) {
continue;
}
$name = $persistentStorage->name;
$local_persistent_volumes_names[$name] = [
'name' => $name,
'external' => false,
];
}
return $local_persistent_volumes_names;
}
private function generate_environment_variables()
{
$environment_variables = collect();
foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->real_value");
}
if ($environment_variables->filter(fn ($env) => str($env)->contains('CLICKHOUSE_ADMIN_USER'))->isEmpty()) {
$environment_variables->push("CLICKHOUSE_ADMIN_USER={$this->database->clickhouse_admin_user}");
}
if ($environment_variables->filter(fn ($env) => str($env)->contains('CLICKHOUSE_ADMIN_PASSWORD'))->isEmpty()) {
$environment_variables->push("CLICKHOUSE_ADMIN_PASSWORD={$this->database->clickhouse_admin_password}");
}
return $environment_variables->all();
}
}

View File

@ -1,57 +0,0 @@
<?php
namespace App\Actions\Database;
use App\Models\StandaloneClickhouse;
use App\Models\StandaloneDragonfly;
use App\Models\StandaloneKeydb;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Lorisleiva\Actions\Concerns\AsAction;
class StartDatabase
{
use AsAction;
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database)
{
$server = $database->destination->server;
if (! $server->isFunctional()) {
return 'Server is not functional';
}
switch ($database->getMorphClass()) {
case 'App\Models\StandalonePostgresql':
$activity = StartPostgresql::run($database);
break;
case 'App\Models\StandaloneRedis':
$activity = StartRedis::run($database);
break;
case 'App\Models\StandaloneMongodb':
$activity = StartMongodb::run($database);
break;
case 'App\Models\StandaloneMysql':
$activity = StartMysql::run($database);
break;
case 'App\Models\StandaloneMariadb':
$activity = StartMariadb::run($database);
break;
case 'App\Models\StandaloneKeydb':
$activity = StartKeydb::run($database);
break;
case 'App\Models\StandaloneDragonfly':
$activity = StartDragonfly::run($database);
break;
case 'App\Models\StandaloneClickhouse':
$activity = StartClickhouse::run($database);
break;
}
if ($database->is_public && $database->public_port) {
StartDatabaseProxy::dispatch($database);
}
return $activity;
}
}

View File

@ -3,9 +3,6 @@
namespace App\Actions\Database; namespace App\Actions\Database;
use App\Models\ServiceDatabase; use App\Models\ServiceDatabase;
use App\Models\StandaloneClickhouse;
use App\Models\StandaloneDragonfly;
use App\Models\StandaloneKeydb;
use App\Models\StandaloneMariadb; use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb; use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql; use App\Models\StandaloneMysql;
@ -18,7 +15,7 @@ class StartDatabaseProxy
{ {
use AsAction; use AsAction;
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|ServiceDatabase $database) public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database)
{ {
$internalPort = null; $internalPort = null;
$type = $database->getMorphClass(); $type = $database->getMorphClass();
@ -28,8 +25,7 @@ class StartDatabaseProxy
$proxyContainerName = "{$database->uuid}-proxy"; $proxyContainerName = "{$database->uuid}-proxy";
if ($database->getMorphClass() === 'App\Models\ServiceDatabase') { if ($database->getMorphClass() === 'App\Models\ServiceDatabase') {
$databaseType = $database->databaseType(); $databaseType = $database->databaseType();
// $connectPredefined = data_get($database, 'service.connect_to_docker_network'); $network = data_get($database, 'service.destination.network');
$network = $database->service->uuid;
$server = data_get($database, 'service.destination.server'); $server = data_get($database, 'service.destination.server');
$proxyContainerName = "{$database->service->uuid}-proxy"; $proxyContainerName = "{$database->service->uuid}-proxy";
switch ($databaseType) { switch ($databaseType) {
@ -53,36 +49,18 @@ class StartDatabaseProxy
$type = 'App\Models\StandaloneRedis'; $type = 'App\Models\StandaloneRedis';
$containerName = "redis-{$database->service->uuid}"; $containerName = "redis-{$database->service->uuid}";
break; break;
case 'standalone-keydb':
$type = 'App\Models\StandaloneKeydb';
$containerName = "keydb-{$database->service->uuid}";
break;
case 'standalone-dragonfly':
$type = 'App\Models\StandaloneDragonfly';
$containerName = "dragonfly-{$database->service->uuid}";
break;
case 'standalone-clickhouse':
$type = 'App\Models\StandaloneClickhouse';
$containerName = "clickhouse-{$database->service->uuid}";
break;
} }
} }
if ($type === 'App\Models\StandaloneRedis') { if ($type === 'App\Models\StandaloneRedis') {
$internalPort = 6379; $internalPort = 6379;
} elseif ($type === 'App\Models\StandalonePostgresql') { } else if ($type === 'App\Models\StandalonePostgresql') {
$internalPort = 5432; $internalPort = 5432;
} elseif ($type === 'App\Models\StandaloneMongodb') { } else if ($type === 'App\Models\StandaloneMongodb') {
$internalPort = 27017; $internalPort = 27017;
} elseif ($type === 'App\Models\StandaloneMysql') { } else if ($type === 'App\Models\StandaloneMysql') {
$internalPort = 3306; $internalPort = 3306;
} elseif ($type === 'App\Models\StandaloneMariadb') { } else if ($type === 'App\Models\StandaloneMariadb') {
$internalPort = 3306; $internalPort = 3306;
} elseif ($type === 'App\Models\StandaloneKeydb') {
$internalPort = 6379;
} elseif ($type === 'App\Models\StandaloneDragonfly') {
$internalPort = 6379;
} elseif ($type === 'App\Models\StandaloneClickhouse') {
$internalPort = 9000;
} }
$configuration_dir = database_proxy_dir($database->uuid); $configuration_dir = database_proxy_dir($database->uuid);
$nginxconf = <<<EOF $nginxconf = <<<EOF
@ -101,19 +79,20 @@ class StartDatabaseProxy
} }
} }
EOF; EOF;
$dockerfile = <<< 'EOF' $dockerfile = <<< EOF
FROM nginx:stable-alpine FROM nginx:stable-alpine
COPY nginx.conf /etc/nginx/nginx.conf COPY nginx.conf /etc/nginx/nginx.conf
EOF; EOF;
$docker_compose = [ $docker_compose = [
'version' => '3.8',
'services' => [ 'services' => [
$proxyContainerName => [ $proxyContainerName => [
'build' => [ 'build' => [
'context' => $configuration_dir, 'context' => $configuration_dir,
'dockerfile' => 'Dockerfile', 'dockerfile' => 'Dockerfile',
], ],
'image' => 'nginx:stable-alpine', 'image' => "nginx:stable-alpine",
'container_name' => $proxyContainerName, 'container_name' => $proxyContainerName,
'restart' => RESTART_MODE, 'restart' => RESTART_MODE,
'ports' => [ 'ports' => [
@ -130,27 +109,26 @@ class StartDatabaseProxy
'interval' => '5s', 'interval' => '5s',
'timeout' => '5s', 'timeout' => '5s',
'retries' => 3, 'retries' => 3,
'start_period' => '1s', 'start_period' => '1s'
], ],
], ]
], ],
'networks' => [ 'networks' => [
$network => [ $network => [
'external' => true, 'external' => true,
'name' => $network, 'name' => $network,
'attachable' => true, 'attachable' => true,
], ]
], ]
]; ];
$dockercompose_base64 = base64_encode(Yaml::dump($docker_compose, 4, 2)); $dockercompose_base64 = base64_encode(Yaml::dump($docker_compose, 4, 2));
$nginxconf_base64 = base64_encode($nginxconf); $nginxconf_base64 = base64_encode($nginxconf);
$dockerfile_base64 = base64_encode($dockerfile); $dockerfile_base64 = base64_encode($dockerfile);
instant_remote_process(["docker rm -f $proxyContainerName"], $server, false);
instant_remote_process([ instant_remote_process([
"mkdir -p $configuration_dir", "mkdir -p $configuration_dir",
"echo '{$dockerfile_base64}' | base64 -d | tee $configuration_dir/Dockerfile > /dev/null", "echo '{$dockerfile_base64}' | base64 -d > $configuration_dir/Dockerfile",
"echo '{$nginxconf_base64}' | base64 -d | tee $configuration_dir/nginx.conf > /dev/null", "echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf",
"echo '{$dockercompose_base64}' | base64 -d | tee $configuration_dir/docker-compose.yaml > /dev/null", "echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml",
"docker compose --project-directory {$configuration_dir} pull", "docker compose --project-directory {$configuration_dir} pull",
"docker compose --project-directory {$configuration_dir} up --build -d", "docker compose --project-directory {$configuration_dir} up --build -d",
], $server); ], $server);

View File

@ -1,163 +0,0 @@
<?php
namespace App\Actions\Database;
use App\Models\StandaloneDragonfly;
use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class StartDragonfly
{
use AsAction;
public StandaloneDragonfly $database;
public array $commands = [];
public string $configuration_dir;
public function handle(StandaloneDragonfly $database)
{
$this->database = $database;
$startCommand = "dragonfly --requirepass {$this->database->dragonfly_password}";
$container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
$this->commands = [
"echo 'Starting {$database->name}.'",
"mkdir -p $this->configuration_dir",
];
$persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
$docker_compose = [
'services' => [
$container_name => [
'image' => $this->database->image,
'command' => $startCommand,
'container_name' => $container_name,
'environment' => $environment_variables,
'restart' => RESTART_MODE,
'networks' => [
$this->database->destination->network,
],
'ulimits' => [
'memlock' => '-1',
],
'labels' => [
'coolify.managed' => 'true',
],
'healthcheck' => [
'test' => "redis-cli -a {$this->database->dragonfly_password} ping",
'interval' => '5s',
'timeout' => '5s',
'retries' => 10,
'start_period' => '5s',
],
'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap,
'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares,
],
],
'networks' => [
$this->database->destination->network => [
'external' => true,
'name' => $this->database->destination->network,
'attachable' => true,
],
],
];
if (! is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
}
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
'options' => [
'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => 'true',
'fluentd-sub-second-precision' => 'true',
],
];
}
if (count($this->database->ports_mappings_array) > 0) {
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
}
if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
}
if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path";
})->toArray();
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}
private function generate_local_persistent_volumes()
{
$local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
} else {
$volume_name = $persistentStorage->name;
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
}
}
return $local_persistent_volumes;
}
private function generate_local_persistent_volumes_only_volume_names()
{
$local_persistent_volumes_names = [];
foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path) {
continue;
}
$name = $persistentStorage->name;
$local_persistent_volumes_names[$name] = [
'name' => $name,
'external' => false,
];
}
return $local_persistent_volumes_names;
}
private function generate_environment_variables()
{
$environment_variables = collect();
foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->real_value");
}
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
$environment_variables->push("REDIS_PASSWORD={$this->database->dragonfly_password}");
}
return $environment_variables->all();
}
}

View File

@ -1,183 +0,0 @@
<?php
namespace App\Actions\Database;
use App\Models\StandaloneKeydb;
use Illuminate\Support\Facades\Storage;
use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class StartKeydb
{
use AsAction;
public StandaloneKeydb $database;
public array $commands = [];
public string $configuration_dir;
public function handle(StandaloneKeydb $database)
{
$this->database = $database;
$startCommand = "keydb-server --requirepass {$this->database->keydb_password} --appendonly yes";
$container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
$this->commands = [
"echo 'Starting {$database->name}.'",
"mkdir -p $this->configuration_dir",
];
$persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
$this->add_custom_keydb();
$docker_compose = [
'services' => [
$container_name => [
'image' => $this->database->image,
'command' => $startCommand,
'container_name' => $container_name,
'environment' => $environment_variables,
'restart' => RESTART_MODE,
'networks' => [
$this->database->destination->network,
],
'labels' => [
'coolify.managed' => 'true',
],
'healthcheck' => [
'test' => "keydb-cli --pass {$this->database->keydb_password} ping",
'interval' => '5s',
'timeout' => '5s',
'retries' => 10,
'start_period' => '5s',
],
'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap,
'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares,
],
],
'networks' => [
$this->database->destination->network => [
'external' => true,
'name' => $this->database->destination->network,
'attachable' => true,
],
],
];
if (! is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
}
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
'options' => [
'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => 'true',
'fluentd-sub-second-precision' => 'true',
],
];
}
if (count($this->database->ports_mappings_array) > 0) {
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
}
if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
}
if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path";
})->toArray();
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}
if (! is_null($this->database->keydb_conf) || ! empty($this->database->keydb_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind',
'source' => $this->configuration_dir.'/keydb.conf',
'target' => '/etc/keydb/keydb.conf',
'read_only' => true,
];
$docker_compose['services'][$container_name]['command'] = "keydb-server /etc/keydb/keydb.conf --requirepass {$this->database->keydb_password} --appendonly yes";
}
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}
private function generate_local_persistent_volumes()
{
$local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
} else {
$volume_name = $persistentStorage->name;
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
}
}
return $local_persistent_volumes;
}
private function generate_local_persistent_volumes_only_volume_names()
{
$local_persistent_volumes_names = [];
foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path) {
continue;
}
$name = $persistentStorage->name;
$local_persistent_volumes_names[$name] = [
'name' => $name,
'external' => false,
];
}
return $local_persistent_volumes_names;
}
private function generate_environment_variables()
{
$environment_variables = collect();
foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->real_value");
}
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
$environment_variables->push("REDIS_PASSWORD={$this->database->keydb_password}");
}
return $environment_variables->all();
}
private function add_custom_keydb()
{
if (is_null($this->database->keydb_conf) || empty($this->database->keydb_conf)) {
return;
}
$filename = 'keydb.conf';
Storage::disk('local')->put("tmp/keydb.conf_{$this->database->uuid}", $this->database->keydb_conf);
$path = Storage::path("tmp/keydb.conf_{$this->database->uuid}");
instant_scp($path, "{$this->configuration_dir}/{$filename}", $this->database->destination->server);
Storage::disk('local')->delete("tmp/keydb.conf_{$this->database->uuid}");
}
}

View File

@ -3,17 +3,16 @@
namespace App\Actions\Database; namespace App\Actions\Database;
use App\Models\StandaloneMariadb; use App\Models\StandaloneMariadb;
use Lorisleiva\Actions\Concerns\AsAction; use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction;
class StartMariadb class StartMariadb
{ {
use AsAction; use AsAction;
public StandaloneMariadb $database; public StandaloneMariadb $database;
public array $commands = []; public array $commands = [];
public string $configuration_dir; public string $configuration_dir;
public function handle(StandaloneMariadb $database) public function handle(StandaloneMariadb $database)
@ -21,7 +20,7 @@ class StartMariadb
$this->database = $database; $this->database = $database;
$container_name = $this->database->uuid; $container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir().'/'.$container_name; $this->configuration_dir = database_configuration_dir() . '/' . $container_name;
$this->commands = [ $this->commands = [
"echo 'Starting {$database->name}.'", "echo 'Starting {$database->name}.'",
@ -29,11 +28,11 @@ class StartMariadb
]; ];
$persistent_storages = $this->generate_local_persistent_volumes(); $persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables(); $environment_variables = $this->generate_environment_variables();
$this->add_custom_mysql(); $this->add_custom_mysql();
$docker_compose = [ $docker_compose = [
'version' => '3.8',
'services' => [ 'services' => [
$container_name => [ $container_name => [
'image' => $this->database->image, 'image' => $this->database->image,
@ -47,11 +46,11 @@ class StartMariadb
'coolify.managed' => 'true', 'coolify.managed' => 'true',
], ],
'healthcheck' => [ 'healthcheck' => [
'test' => ['CMD', 'healthcheck.sh', '--connect', '--innodb_initialized'], 'test' => ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"],
'interval' => '5s', 'interval' => '5s',
'timeout' => '5s', 'timeout' => '5s',
'retries' => 10, 'retries' => 10,
'start_period' => '5s', 'start_period' => '5s'
], ],
'mem_limit' => $this->database->limits_memory, 'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap, 'memswap_limit' => $this->database->limits_memory_swap,
@ -59,27 +58,27 @@ class StartMariadb
'mem_reservation' => $this->database->limits_memory_reservation, 'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus, 'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares, 'cpu_shares' => $this->database->limits_cpu_shares,
], ]
], ],
'networks' => [ 'networks' => [
$this->database->destination->network => [ $this->database->destination->network => [
'external' => true, 'external' => true,
'name' => $this->database->destination->network, 'name' => $this->database->destination->network,
'attachable' => true, 'attachable' => true,
], ]
], ]
]; ];
if (! is_null($this->database->limits_cpuset)) { if (!is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
} }
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [ $docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd', 'driver' => 'fluentd',
'options' => [ 'options' => [
'fluentd-address' => 'tcp://127.0.0.1:24224', 'fluentd-address' => "tcp://127.0.0.1:24224",
'fluentd-async' => 'true', 'fluentd-async' => "true",
'fluentd-sub-second-precision' => 'true', 'fluentd-sub-second-precision' => "true",
], ]
]; ];
} }
if (count($this->database->ports_mappings_array) > 0) { if (count($this->database->ports_mappings_array) > 0) {
@ -88,32 +87,26 @@ class StartMariadb
if (count($persistent_storages) > 0) { if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages; $docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
} }
if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path";
})->toArray();
}
if (count($volume_names) > 0) { if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names; $docker_compose['volumes'] = $volume_names;
} }
if (! is_null($this->database->mariadb_conf) || ! empty($this->database->mariadb_conf)) { if (!is_null($this->database->mariadb_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [ $docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind', 'type' => 'bind',
'source' => $this->configuration_dir.'/custom-config.cnf', 'source' => $this->configuration_dir . '/custom-config.cnf',
'target' => '/etc/mysql/conf.d/custom-config.cnf', 'target' => '/etc/mysql/conf.d/custom-config.cnf',
'read_only' => true, 'read_only' => true,
]; ];
} }
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
$readme = generate_readme_file($this->database->name, now()); $readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'"; $this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
} }
@ -121,14 +114,9 @@ class StartMariadb
{ {
$local_persistent_volumes = []; $local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) { foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { $volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
} else {
$volume_name = $persistentStorage->name;
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
}
} }
return $local_persistent_volumes; return $local_persistent_volumes;
} }
@ -145,7 +133,6 @@ class StartMariadb
'external' => false, 'external' => false,
]; ];
} }
return $local_persistent_volumes_names; return $local_persistent_volumes_names;
} }
@ -156,32 +143,30 @@ class StartMariadb
$environment_variables->push("$env->key=$env->real_value"); $environment_variables->push("$env->key=$env->real_value");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) { if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) {
$environment_variables->push("MARIADB_ROOT_PASSWORD={$this->database->mariadb_root_password}"); $environment_variables->push("MARIADB_ROOT_PASSWORD={$this->database->mariadb_root_password}");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_DATABASE'))->isEmpty()) { if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_DATABASE'))->isEmpty()) {
$environment_variables->push("MARIADB_DATABASE={$this->database->mariadb_database}"); $environment_variables->push("MARIADB_DATABASE={$this->database->mariadb_database}");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_USER'))->isEmpty()) { if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_USER'))->isEmpty()) {
$environment_variables->push("MARIADB_USER={$this->database->mariadb_user}"); $environment_variables->push("MARIADB_USER={$this->database->mariadb_user}");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_PASSWORD'))->isEmpty()) { if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_PASSWORD'))->isEmpty()) {
$environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}"); $environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}");
} }
return $environment_variables->all(); return $environment_variables->all();
} }
private function add_custom_mysql() private function add_custom_mysql()
{ {
if (is_null($this->database->mariadb_conf) || empty($this->database->mariadb_conf)) { if (is_null($this->database->mariadb_conf)) {
return; return;
} }
$filename = 'custom-config.cnf'; $filename = 'custom-config.cnf';
$content = $this->database->mariadb_conf; $content = $this->database->mariadb_conf;
$content_base64 = base64_encode($content); $content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null"; $this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
} }
} }

View File

@ -3,27 +3,26 @@
namespace App\Actions\Database; namespace App\Actions\Database;
use App\Models\StandaloneMongodb; use App\Models\StandaloneMongodb;
use Lorisleiva\Actions\Concerns\AsAction; use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction;
class StartMongodb class StartMongodb
{ {
use AsAction; use AsAction;
public StandaloneMongodb $database; public StandaloneMongodb $database;
public array $commands = []; public array $commands = [];
public string $configuration_dir; public string $configuration_dir;
public function handle(StandaloneMongodb $database) public function handle(StandaloneMongodb $database)
{ {
$this->database = $database; $this->database = $database;
$startCommand = 'mongod'; $startCommand = "mongod";
$container_name = $this->database->uuid; $container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir().'/'.$container_name; $this->configuration_dir = database_configuration_dir() . '/' . $container_name;
$this->commands = [ $this->commands = [
"echo 'Starting {$database->name}.'", "echo 'Starting {$database->name}.'",
@ -31,12 +30,12 @@ class StartMongodb
]; ];
$persistent_storages = $this->generate_local_persistent_volumes(); $persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables(); $environment_variables = $this->generate_environment_variables();
$this->add_custom_mongo_conf(); $this->add_custom_mongo_conf();
$docker_compose = [ $docker_compose = [
'version' => '3.8',
'services' => [ 'services' => [
$container_name => [ $container_name => [
'image' => $this->database->image, 'image' => $this->database->image,
@ -52,14 +51,13 @@ class StartMongodb
], ],
'healthcheck' => [ 'healthcheck' => [
'test' => [ 'test' => [
'CMD', 'CMD-SHELL',
'echo', 'mongosh --eval "printjson(db.runCommand(\"ping\"))"'
'ok',
], ],
'interval' => '5s', 'interval' => '5s',
'timeout' => '5s', 'timeout' => '5s',
'retries' => 10, 'retries' => 10,
'start_period' => '5s', 'start_period' => '5s'
], ],
'mem_limit' => $this->database->limits_memory, 'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap, 'memswap_limit' => $this->database->limits_memory_swap,
@ -67,27 +65,27 @@ class StartMongodb
'mem_reservation' => $this->database->limits_memory_reservation, 'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus, 'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares, 'cpu_shares' => $this->database->limits_cpu_shares,
], ]
], ],
'networks' => [ 'networks' => [
$this->database->destination->network => [ $this->database->destination->network => [
'external' => true, 'external' => true,
'name' => $this->database->destination->network, 'name' => $this->database->destination->network,
'attachable' => true, 'attachable' => true,
], ]
], ]
]; ];
if (! is_null($this->database->limits_cpuset)) { if (!is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
} }
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [ $docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd', 'driver' => 'fluentd',
'options' => [ 'options' => [
'fluentd-address' => 'tcp://127.0.0.1:24224', 'fluentd-address' => "tcp://127.0.0.1:24224",
'fluentd-async' => 'true', 'fluentd-async' => "true",
'fluentd-sub-second-precision' => 'true', 'fluentd-sub-second-precision' => "true",
], ]
]; ];
} }
if (count($this->database->ports_mappings_array) > 0) { if (count($this->database->ports_mappings_array) > 0) {
@ -96,41 +94,35 @@ class StartMongodb
if (count($persistent_storages) > 0) { if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages; $docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
} }
if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path";
})->toArray();
}
if (count($volume_names) > 0) { if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names; $docker_compose['volumes'] = $volume_names;
} }
if (! is_null($this->database->mongo_conf) || ! empty($this->database->mongo_conf)) { if (!is_null($this->database->mongo_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [ $docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind', 'type' => 'bind',
'source' => $this->configuration_dir.'/mongod.conf', 'source' => $this->configuration_dir . '/mongod.conf',
'target' => '/etc/mongo/mongod.conf', 'target' => '/etc/mongo/mongod.conf',
'read_only' => true, 'read_only' => true,
]; ];
$docker_compose['services'][$container_name]['command'] = $startCommand.' --config /etc/mongo/mongod.conf'; $docker_compose['services'][$container_name]['command'] = $startCommand . ' --config /etc/mongo/mongod.conf';
} }
$this->add_default_database(); $this->add_default_database();
$docker_compose['services'][$container_name]['volumes'][] = [ $docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind', 'type' => 'bind',
'source' => $this->configuration_dir.'/docker-entrypoint-initdb.d', 'source' => $this->configuration_dir . '/docker-entrypoint-initdb.d',
'target' => '/docker-entrypoint-initdb.d', 'target' => '/docker-entrypoint-initdb.d',
'read_only' => true, 'read_only' => true,
]; ];
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
$readme = generate_readme_file($this->database->name, now()); $readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'"; $this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
} }
@ -138,14 +130,9 @@ class StartMongodb
{ {
$local_persistent_volumes = []; $local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) { foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { $volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
} else {
$volume_name = $persistentStorage->name;
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
}
} }
return $local_persistent_volumes; return $local_persistent_volumes;
} }
@ -162,7 +149,6 @@ class StartMongodb
'external' => false, 'external' => false,
]; ];
} }
return $local_persistent_volumes_names; return $local_persistent_volumes_names;
} }
@ -173,37 +159,34 @@ class StartMongodb
$environment_variables->push("$env->key=$env->real_value"); $environment_variables->push("$env->key=$env->real_value");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) { if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) {
$environment_variables->push("MONGO_INITDB_ROOT_USERNAME={$this->database->mongo_initdb_root_username}"); $environment_variables->push("MONGO_INITDB_ROOT_USERNAME={$this->database->mongo_initdb_root_username}");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_ROOT_PASSWORD'))->isEmpty()) { if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_ROOT_PASSWORD'))->isEmpty()) {
$environment_variables->push("MONGO_INITDB_ROOT_PASSWORD={$this->database->mongo_initdb_root_password}"); $environment_variables->push("MONGO_INITDB_ROOT_PASSWORD={$this->database->mongo_initdb_root_password}");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) { if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) {
$environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}"); $environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}");
} }
return $environment_variables->all(); return $environment_variables->all();
} }
private function add_custom_mongo_conf() private function add_custom_mongo_conf()
{ {
if (is_null($this->database->mongo_conf) || empty($this->database->mongo_conf)) { if (is_null($this->database->mongo_conf)) {
return; return;
} }
$filename = 'mongod.conf'; $filename = 'mongod.conf';
$content = $this->database->mongo_conf; $content = $this->database->mongo_conf;
$content_base64 = base64_encode($content); $content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null"; $this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
} }
private function add_default_database() private function add_default_database()
{ {
$content = "db = db.getSiblingDB(\"{$this->database->mongo_initdb_database}\");db.createCollection('init_collection');db.createUser({user: \"{$this->database->mongo_initdb_root_username}\", pwd: \"{$this->database->mongo_initdb_root_password}\",roles: [{role:\"readWrite\",db:\"{$this->database->mongo_initdb_database}\"}]});"; $content = "db = db.getSiblingDB(\"{$this->database->mongo_initdb_database}\");db.createCollection('init_collection');db.createUser({user: \"{$this->database->mongo_initdb_root_username}\", pwd: \"{$this->database->mongo_initdb_root_password}\",roles: [{role:\"readWrite\",db:\"{$this->database->mongo_initdb_database}\"}]});";
$content_base64 = base64_encode($content); $content_base64 = base64_encode($content);
$this->commands[] = "mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d"; $this->commands[] = "mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d";
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/docker-entrypoint-initdb.d/01-default-database.js > /dev/null"; $this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/docker-entrypoint-initdb.d/01-default-database.js";
} }
} }

View File

@ -3,17 +3,16 @@
namespace App\Actions\Database; namespace App\Actions\Database;
use App\Models\StandaloneMysql; use App\Models\StandaloneMysql;
use Lorisleiva\Actions\Concerns\AsAction; use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction;
class StartMysql class StartMysql
{ {
use AsAction; use AsAction;
public StandaloneMysql $database; public StandaloneMysql $database;
public array $commands = []; public array $commands = [];
public string $configuration_dir; public string $configuration_dir;
public function handle(StandaloneMysql $database) public function handle(StandaloneMysql $database)
@ -21,7 +20,7 @@ class StartMysql
$this->database = $database; $this->database = $database;
$container_name = $this->database->uuid; $container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir().'/'.$container_name; $this->configuration_dir = database_configuration_dir() . '/' . $container_name;
$this->commands = [ $this->commands = [
"echo 'Starting {$database->name}.'", "echo 'Starting {$database->name}.'",
@ -29,11 +28,11 @@ class StartMysql
]; ];
$persistent_storages = $this->generate_local_persistent_volumes(); $persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables(); $environment_variables = $this->generate_environment_variables();
$this->add_custom_mysql(); $this->add_custom_mysql();
$docker_compose = [ $docker_compose = [
'version' => '3.8',
'services' => [ 'services' => [
$container_name => [ $container_name => [
'image' => $this->database->image, 'image' => $this->database->image,
@ -47,11 +46,11 @@ class StartMysql
'coolify.managed' => 'true', 'coolify.managed' => 'true',
], ],
'healthcheck' => [ 'healthcheck' => [
'test' => ['CMD', 'mysqladmin', 'ping', '-h', 'localhost', '-u', 'root', "-p{$this->database->mysql_root_password}"], 'test' => ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p{$this->database->mysql_root_password}"],
'interval' => '5s', 'interval' => '5s',
'timeout' => '5s', 'timeout' => '5s',
'retries' => 10, 'retries' => 10,
'start_period' => '5s', 'start_period' => '5s'
], ],
'mem_limit' => $this->database->limits_memory, 'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap, 'memswap_limit' => $this->database->limits_memory_swap,
@ -59,27 +58,27 @@ class StartMysql
'mem_reservation' => $this->database->limits_memory_reservation, 'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus, 'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares, 'cpu_shares' => $this->database->limits_cpu_shares,
], ]
], ],
'networks' => [ 'networks' => [
$this->database->destination->network => [ $this->database->destination->network => [
'external' => true, 'external' => true,
'name' => $this->database->destination->network, 'name' => $this->database->destination->network,
'attachable' => true, 'attachable' => true,
], ]
], ]
]; ];
if (! is_null($this->database->limits_cpuset)) { if (!is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
} }
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [ $docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd', 'driver' => 'fluentd',
'options' => [ 'options' => [
'fluentd-address' => 'tcp://127.0.0.1:24224', 'fluentd-address' => "tcp://127.0.0.1:24224",
'fluentd-async' => 'true', 'fluentd-async' => "true",
'fluentd-sub-second-precision' => 'true', 'fluentd-sub-second-precision' => "true",
], ]
]; ];
} }
if (count($this->database->ports_mappings_array) > 0) { if (count($this->database->ports_mappings_array) > 0) {
@ -88,47 +87,36 @@ class StartMysql
if (count($persistent_storages) > 0) { if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages; $docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
} }
if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path";
})->toArray();
}
if (count($volume_names) > 0) { if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names; $docker_compose['volumes'] = $volume_names;
} }
if (! is_null($this->database->mysql_conf) || ! empty($this->database->mysql_conf)) { if (!is_null($this->database->mysql_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [ $docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind', 'type' => 'bind',
'source' => $this->configuration_dir.'/custom-config.cnf', 'source' => $this->configuration_dir . '/custom-config.cnf',
'target' => '/etc/mysql/conf.d/custom-config.cnf', 'target' => '/etc/mysql/conf.d/custom-config.cnf',
'read_only' => true, 'read_only' => true,
]; ];
} }
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
$readme = generate_readme_file($this->database->name, now()); $readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'"; $this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server,callEventOnFinish: 'DatabaseStatusChanged');
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
} }
private function generate_local_persistent_volumes() private function generate_local_persistent_volumes()
{ {
$local_persistent_volumes = []; $local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) { foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { $volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
} else {
$volume_name = $persistentStorage->name;
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
}
} }
return $local_persistent_volumes; return $local_persistent_volumes;
} }
@ -145,7 +133,6 @@ class StartMysql
'external' => false, 'external' => false,
]; ];
} }
return $local_persistent_volumes_names; return $local_persistent_volumes_names;
} }
@ -156,32 +143,30 @@ class StartMysql
$environment_variables->push("$env->key=$env->real_value"); $environment_variables->push("$env->key=$env->real_value");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) { if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) {
$environment_variables->push("MYSQL_ROOT_PASSWORD={$this->database->mysql_root_password}"); $environment_variables->push("MYSQL_ROOT_PASSWORD={$this->database->mysql_root_password}");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_DATABASE'))->isEmpty()) { if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_DATABASE'))->isEmpty()) {
$environment_variables->push("MYSQL_DATABASE={$this->database->mysql_database}"); $environment_variables->push("MYSQL_DATABASE={$this->database->mysql_database}");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_USER'))->isEmpty()) { if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_USER'))->isEmpty()) {
$environment_variables->push("MYSQL_USER={$this->database->mysql_user}"); $environment_variables->push("MYSQL_USER={$this->database->mysql_user}");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_PASSWORD'))->isEmpty()) { if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_PASSWORD'))->isEmpty()) {
$environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}"); $environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}");
} }
return $environment_variables->all(); return $environment_variables->all();
} }
private function add_custom_mysql() private function add_custom_mysql()
{ {
if (is_null($this->database->mysql_conf) || empty($this->database->mysql_conf)) { if (is_null($this->database->mysql_conf)) {
return; return;
} }
$filename = 'custom-config.cnf'; $filename = 'custom-config.cnf';
$content = $this->database->mysql_conf; $content = $this->database->mysql_conf;
$content_base64 = base64_encode($content); $content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null"; $this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
} }
} }

View File

@ -3,41 +3,39 @@
namespace App\Actions\Database; namespace App\Actions\Database;
use App\Models\StandalonePostgresql; use App\Models\StandalonePostgresql;
use Lorisleiva\Actions\Concerns\AsAction; use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction;
class StartPostgresql class StartPostgresql
{ {
use AsAction; use AsAction;
public StandalonePostgresql $database; public StandalonePostgresql $database;
public array $commands = []; public array $commands = [];
public array $init_scripts = []; public array $init_scripts = [];
public string $configuration_dir; public string $configuration_dir;
public function handle(StandalonePostgresql $database) public function handle(StandalonePostgresql $database)
{ {
$this->database = $database; $this->database = $database;
$container_name = $this->database->uuid; $container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir().'/'.$container_name; $this->configuration_dir = database_configuration_dir() . '/' . $container_name;
$this->commands = [ $this->commands = [
"echo 'Starting {$database->name}.'", "echo 'Starting {$database->name}.'",
"mkdir -p $this->configuration_dir", "mkdir -p $this->configuration_dir",
"mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/", "mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/"
]; ];
$persistent_storages = $this->generate_local_persistent_volumes(); $persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables(); $environment_variables = $this->generate_environment_variables();
$this->generate_init_scripts(); $this->generate_init_scripts();
$this->add_custom_conf(); $this->add_custom_conf();
$docker_compose = [ $docker_compose = [
'version' => '3.8',
'services' => [ 'services' => [
$container_name => [ $container_name => [
'image' => $this->database->image, 'image' => $this->database->image,
@ -52,13 +50,13 @@ class StartPostgresql
], ],
'healthcheck' => [ 'healthcheck' => [
'test' => [ 'test' => [
'CMD-SHELL', "CMD-SHELL",
"psql -U {$this->database->postgres_user} -d {$this->database->postgres_db} -c 'SELECT 1' || exit 1", "psql -U {$this->database->postgres_user} -d {$this->database->postgres_db} -c 'SELECT 1' || exit 1"
], ],
'interval' => '5s', 'interval' => '5s',
'timeout' => '5s', 'timeout' => '5s',
'retries' => 10, 'retries' => 10,
'start_period' => '5s', 'start_period' => '5s'
], ],
'mem_limit' => $this->database->limits_memory, 'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap, 'memswap_limit' => $this->database->limits_memory_swap,
@ -66,27 +64,28 @@ class StartPostgresql
'mem_reservation' => $this->database->limits_memory_reservation, 'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus, 'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares, 'cpu_shares' => $this->database->limits_cpu_shares,
], ]
], ],
'networks' => [ 'networks' => [
$this->database->destination->network => [ $this->database->destination->network => [
'external' => true, 'external' => true,
'name' => $this->database->destination->network, 'name' => $this->database->destination->network,
'attachable' => true, 'attachable' => true,
], ]
], ]
]; ];
if (! is_null($this->database->limits_cpuset)) { if (!is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
} }
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
ray('Log Drain Enabled');
$docker_compose['services'][$container_name]['logging'] = [ $docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd', 'driver' => 'fluentd',
'options' => [ 'options' => [
'fluentd-address' => 'tcp://127.0.0.1:24224', 'fluentd-address' => "tcp://127.0.0.1:24224",
'fluentd-async' => 'true', 'fluentd-async' => "true",
'fluentd-sub-second-precision' => 'true', 'fluentd-sub-second-precision' => "true",
], ]
]; ];
} }
if (count($this->database->ports_mappings_array) > 0) { if (count($this->database->ports_mappings_array) > 0) {
@ -95,11 +94,6 @@ class StartPostgresql
if (count($persistent_storages) > 0) { if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages; $docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
} }
if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path";
})->toArray();
}
if (count($volume_names) > 0) { if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names; $docker_compose['volumes'] = $volume_names;
} }
@ -108,15 +102,15 @@ class StartPostgresql
$docker_compose['services'][$container_name]['volumes'][] = [ $docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind', 'type' => 'bind',
'source' => $init_script, 'source' => $init_script,
'target' => '/docker-entrypoint-initdb.d/'.basename($init_script), 'target' => '/docker-entrypoint-initdb.d/' . basename($init_script),
'read_only' => true, 'read_only' => true,
]; ];
} }
} }
if (! is_null($this->database->postgres_conf) && ! empty($this->database->postgres_conf)) { if (!is_null($this->database->postgres_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [ $docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind', 'type' => 'bind',
'source' => $this->configuration_dir.'/custom-postgres.conf', 'source' => $this->configuration_dir . '/custom-postgres.conf',
'target' => '/etc/postgresql/postgresql.conf', 'target' => '/etc/postgresql/postgresql.conf',
'read_only' => true, 'read_only' => true,
]; ];
@ -128,14 +122,13 @@ class StartPostgresql
} }
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
$readme = generate_readme_file($this->database->name, now()); $readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'"; $this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
} }
@ -143,14 +136,9 @@ class StartPostgresql
{ {
$local_persistent_volumes = []; $local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) { foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { $volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
} else {
$volume_name = $persistentStorage->name;
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
}
} }
return $local_persistent_volumes; return $local_persistent_volumes;
} }
@ -167,32 +155,32 @@ class StartPostgresql
'external' => false, 'external' => false,
]; ];
} }
return $local_persistent_volumes_names; return $local_persistent_volumes_names;
} }
private function generate_environment_variables() private function generate_environment_variables()
{ {
$environment_variables = collect(); $environment_variables = collect();
ray('Generate Environment Variables')->green();
ray($this->database->runtime_environment_variables)->green();
foreach ($this->database->runtime_environment_variables as $env) { foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->real_value"); $environment_variables->push("$env->key=$env->real_value");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('POSTGRES_USER'))->isEmpty()) { if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_USER'))->isEmpty()) {
$environment_variables->push("POSTGRES_USER={$this->database->postgres_user}"); $environment_variables->push("POSTGRES_USER={$this->database->postgres_user}");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('PGUSER'))->isEmpty()) { if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('PGUSER'))->isEmpty()) {
$environment_variables->push("PGUSER={$this->database->postgres_user}"); $environment_variables->push("PGUSER={$this->database->postgres_user}");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('POSTGRES_PASSWORD'))->isEmpty()) { if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_PASSWORD'))->isEmpty()) {
$environment_variables->push("POSTGRES_PASSWORD={$this->database->postgres_password}"); $environment_variables->push("POSTGRES_PASSWORD={$this->database->postgres_password}");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('POSTGRES_DB'))->isEmpty()) { if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_DB'))->isEmpty()) {
$environment_variables->push("POSTGRES_DB={$this->database->postgres_db}"); $environment_variables->push("POSTGRES_DB={$this->database->postgres_db}");
} }
return $environment_variables->all(); return $environment_variables->all();
} }
@ -205,24 +193,18 @@ class StartPostgresql
$filename = data_get($init_script, 'filename'); $filename = data_get($init_script, 'filename');
$content = data_get($init_script, 'content'); $content = data_get($init_script, 'content');
$content_base64 = base64_encode($content); $content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/docker-entrypoint-initdb.d/{$filename} > /dev/null"; $this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/docker-entrypoint-initdb.d/{$filename}";
$this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}"; $this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}";
} }
} }
private function add_custom_conf() private function add_custom_conf()
{ {
if (is_null($this->database->postgres_conf) || empty($this->database->postgres_conf)) { if (is_null($this->database->postgres_conf)) {
return; return;
} }
$filename = 'custom-postgres.conf'; $filename = 'custom-postgres.conf';
$content = $this->database->postgres_conf; $content = $this->database->postgres_conf;
if (! str($content)->contains('listen_addresses')) {
$content .= "\nlisten_addresses = '*'";
$this->database->postgres_conf = $content;
$this->database->save();
}
$content_base64 = base64_encode($content); $content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null"; $this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
} }
} }

View File

@ -4,19 +4,19 @@ namespace App\Actions\Database;
use App\Models\StandaloneRedis; use App\Models\StandaloneRedis;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Lorisleiva\Actions\Concerns\AsAction; use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction;
class StartRedis class StartRedis
{ {
use AsAction; use AsAction;
public StandaloneRedis $database; public StandaloneRedis $database;
public array $commands = []; public array $commands = [];
public string $configuration_dir; public string $configuration_dir;
public function handle(StandaloneRedis $database) public function handle(StandaloneRedis $database)
{ {
$this->database = $database; $this->database = $database;
@ -24,7 +24,7 @@ class StartRedis
$startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes"; $startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
$container_name = $this->database->uuid; $container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir().'/'.$container_name; $this->configuration_dir = database_configuration_dir() . '/' . $container_name;
$this->commands = [ $this->commands = [
"echo 'Starting {$database->name}.'", "echo 'Starting {$database->name}.'",
@ -32,12 +32,12 @@ class StartRedis
]; ];
$persistent_storages = $this->generate_local_persistent_volumes(); $persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables(); $environment_variables = $this->generate_environment_variables();
$this->add_custom_redis(); $this->add_custom_redis();
$docker_compose = [ $docker_compose = [
'version' => '3.8',
'services' => [ 'services' => [
$container_name => [ $container_name => [
'image' => $this->database->image, 'image' => $this->database->image,
@ -55,12 +55,12 @@ class StartRedis
'test' => [ 'test' => [
'CMD-SHELL', 'CMD-SHELL',
'redis-cli', 'redis-cli',
'ping', 'ping'
], ],
'interval' => '5s', 'interval' => '5s',
'timeout' => '5s', 'timeout' => '5s',
'retries' => 10, 'retries' => 10,
'start_period' => '5s', 'start_period' => '5s'
], ],
'mem_limit' => $this->database->limits_memory, 'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap, 'memswap_limit' => $this->database->limits_memory_swap,
@ -68,27 +68,27 @@ class StartRedis
'mem_reservation' => $this->database->limits_memory_reservation, 'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus, 'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares, 'cpu_shares' => $this->database->limits_cpu_shares,
], ]
], ],
'networks' => [ 'networks' => [
$this->database->destination->network => [ $this->database->destination->network => [
'external' => true, 'external' => true,
'name' => $this->database->destination->network, 'name' => $this->database->destination->network,
'attachable' => true, 'attachable' => true,
], ]
], ]
]; ];
if (! is_null($this->database->limits_cpuset)) { if (!is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
} }
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [ $docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd', 'driver' => 'fluentd',
'options' => [ 'options' => [
'fluentd-address' => 'tcp://127.0.0.1:24224', 'fluentd-address' => "tcp://127.0.0.1:24224",
'fluentd-async' => 'true', 'fluentd-async' => "true",
'fluentd-sub-second-precision' => 'true', 'fluentd-sub-second-precision' => "true",
], ]
]; ];
} }
if (count($this->database->ports_mappings_array) > 0) { if (count($this->database->ports_mappings_array) > 0) {
@ -97,18 +97,13 @@ class StartRedis
if (count($persistent_storages) > 0) { if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages; $docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
} }
if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path";
})->toArray();
}
if (count($volume_names) > 0) { if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names; $docker_compose['volumes'] = $volume_names;
} }
if (! is_null($this->database->redis_conf) || ! empty($this->database->redis_conf)) { if (!is_null($this->database->redis_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [ $docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind', 'type' => 'bind',
'source' => $this->configuration_dir.'/redis.conf', 'source' => $this->configuration_dir . '/redis.conf',
'target' => '/usr/local/etc/redis/redis.conf', 'target' => '/usr/local/etc/redis/redis.conf',
'read_only' => true, 'read_only' => true,
]; ];
@ -116,14 +111,13 @@ class StartRedis
} }
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
$readme = generate_readme_file($this->database->name, now()); $readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'"; $this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
} }
@ -131,14 +125,9 @@ class StartRedis
{ {
$local_persistent_volumes = []; $local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) { foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { $volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
} else {
$volume_name = $persistentStorage->name;
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
}
} }
return $local_persistent_volumes; return $local_persistent_volumes;
} }
@ -155,7 +144,6 @@ class StartRedis
'external' => false, 'external' => false,
]; ];
} }
return $local_persistent_volumes_names; return $local_persistent_volumes_names;
} }
@ -166,16 +154,15 @@ class StartRedis
$environment_variables->push("$env->key=$env->real_value"); $environment_variables->push("$env->key=$env->real_value");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) { if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
$environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}"); $environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}");
} }
return $environment_variables->all(); return $environment_variables->all();
} }
private function add_custom_redis() private function add_custom_redis()
{ {
if (is_null($this->database->redis_conf) || empty($this->database->redis_conf)) { if (is_null($this->database->redis_conf)) {
return; return;
} }
$filename = 'redis.conf'; $filename = 'redis.conf';

View File

@ -2,9 +2,7 @@
namespace App\Actions\Database; namespace App\Actions\Database;
use App\Models\StandaloneClickhouse; use App\Events\DatabaseStatusChanged;
use App\Models\StandaloneDragonfly;
use App\Models\StandaloneKeydb;
use App\Models\StandaloneMariadb; use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb; use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql; use App\Models\StandaloneMysql;
@ -16,10 +14,10 @@ class StopDatabase
{ {
use AsAction; use AsAction;
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database) public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database)
{ {
$server = $database->destination->server; $server = $database->destination->server;
if (! $server->isFunctional()) { if (!$server->isFunctional()) {
return 'Server is not functional'; return 'Server is not functional';
} }
instant_remote_process( instant_remote_process(
@ -29,5 +27,7 @@ class StopDatabase
if ($database->is_public) { if ($database->is_public) {
StopDatabaseProxy::run($database); StopDatabaseProxy::run($database);
} }
// TODO: make notification for services
// $database->environment->project->team->notify(new StatusChanged($database));
} }
} }

View File

@ -2,11 +2,7 @@
namespace App\Actions\Database; namespace App\Actions\Database;
use App\Events\DatabaseStatusChanged;
use App\Models\ServiceDatabase; use App\Models\ServiceDatabase;
use App\Models\StandaloneClickhouse;
use App\Models\StandaloneDragonfly;
use App\Models\StandaloneKeydb;
use App\Models\StandaloneMariadb; use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb; use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql; use App\Models\StandaloneMysql;
@ -18,16 +14,14 @@ class StopDatabaseProxy
{ {
use AsAction; use AsAction;
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|ServiceDatabase|StandaloneDragonfly|StandaloneClickhouse $database) public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database)
{ {
$server = data_get($database, 'destination.server'); $server = data_get($database, 'destination.server');
$uuid = $database->uuid;
if ($database->getMorphClass() === 'App\Models\ServiceDatabase') { if ($database->getMorphClass() === 'App\Models\ServiceDatabase') {
$uuid = $database->service->uuid;
$server = data_get($database, 'service.server'); $server = data_get($database, 'service.server');
} }
instant_remote_process(["docker rm -f {$uuid}-proxy"], $server); instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $server);
$database->is_public = false;
$database->save(); $database->save();
DatabaseStatusChanged::dispatch();
} }
} }

View File

@ -1,686 +0,0 @@
<?php
namespace App\Actions\Docker;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Proxy\CheckProxy;
use App\Actions\Proxy\StartProxy;
use App\Actions\Shared\ComplexStatusCheck;
use App\Models\ApplicationPreview;
use App\Models\Server;
use App\Models\ServiceDatabase;
use App\Notifications\Container\ContainerRestarted;
use App\Notifications\Container\ContainerStopped;
use Illuminate\Support\Arr;
use Lorisleiva\Actions\Concerns\AsAction;
class GetContainersStatus
{
use AsAction;
public $applications;
public $server;
public function handle(Server $server)
{
// if (isDev()) {
// $server = Server::find(0);
// }
$this->server = $server;
if (! $this->server->isFunctional()) {
return 'Server is not ready.';
}
$this->applications = $this->server->applications();
$skip_these_applications = collect([]);
foreach ($this->applications as $application) {
if ($application->additional_servers->count() > 0) {
$skip_these_applications->push($application);
ComplexStatusCheck::run($application);
$this->applications = $this->applications->filter(function ($value, $key) use ($application) {
return $value->id !== $application->id;
});
}
}
$this->applications = $this->applications->filter(function ($value, $key) use ($skip_these_applications) {
return ! $skip_these_applications->pluck('id')->contains($value->id);
});
$this->old_way();
// if ($this->server->isSwarm()) {
// $this->old_way();
// } else {
// if (!$this->server->is_metrics_enabled) {
// $this->old_way();
// return;
// }
// $sentinel_found = instant_remote_process(["docker inspect coolify-sentinel"], $this->server, false);
// $sentinel_found = json_decode($sentinel_found, true);
// $status = data_get($sentinel_found, '0.State.Status', 'exited');
// if ($status === 'running') {
// ray('Checking with Sentinel');
// $this->sentinel();
// } else {
// ray('Checking the Old way');
// $this->old_way();
// }
// }
}
private function sentinel()
{
try {
$containers = $this->server->getContainers();
if ($containers->count() === 0) {
return;
}
$databases = $this->server->databases();
$services = $this->server->services()->get();
$previews = $this->server->previews();
$foundApplications = [];
$foundApplicationPreviews = [];
$foundDatabases = [];
$foundServices = [];
foreach ($containers as $container) {
$labels = Arr::undot(data_get($container, 'labels'));
$containerStatus = data_get($container, 'state');
$containerHealth = data_get($container, 'health_status', 'unhealthy');
$containerStatus = "$containerStatus ($containerHealth)";
$applicationId = data_get($labels, 'coolify.applicationId');
if ($applicationId) {
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
if ($pullRequestId) {
if (str($applicationId)->contains('-')) {
$applicationId = str($applicationId)->before('-');
}
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->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 = $this->applications->where('id', $applicationId)->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');
$type = data_get($labels, 'coolify.type');
if ($uuid) {
if ($type === 'service') {
$database_id = data_get($labels, 'coolify.service.subId');
if ($database_id) {
$service_db = ServiceDatabase::where('id', $database_id)->first();
if ($service_db) {
$uuid = $service_db->service->uuid;
$isPublic = data_get($service_db, 'is_public');
if ($isPublic) {
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
if ($this->server->isSwarm()) {
// TODO: fix this with sentinel
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else {
return data_get($value, 'name') === "$uuid-proxy";
}
})->first();
if (! $foundTcpProxy) {
StartDatabaseProxy::run($service_db);
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
}
}
}
}
} else {
$database = $databases->where('uuid', $uuid)->first();
if ($database) {
$isPublic = data_get($database, 'is_public');
$foundDatabases[] = $database->id;
$statusFromDb = $database->status;
if ($statusFromDb !== $containerStatus) {
$database->update(['status' => $containerStatus]);
}
if ($isPublic) {
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
if ($this->server->isSwarm()) {
// TODO: fix this with sentinel
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else {
return data_get($value, 'name') === "$uuid-proxy";
}
})->first();
if (! $foundTcpProxy) {
StartDatabaseProxy::run($database);
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
}
}
} else {
// Notify user that this container should not be there.
}
}
}
if (data_get($container, 'name') === 'coolify-db') {
$foundDatabases[] = 0;
}
}
$serviceLabelId = data_get($labels, 'coolify.serviceId');
if ($serviceLabelId) {
$subType = data_get($labels, 'coolify.service.subType');
$subId = data_get($labels, 'coolify.service.subId');
$service = $services->where('id', $serviceLabelId)->first();
if (! $service) {
continue;
}
if ($subType === 'application') {
$service = $service->applications()->where('id', $subId)->first();
} else {
$service = $service->databases()->where('id', $subId)->first();
}
if ($service) {
$foundServices[] = "$service->id-$service->name";
$statusFromDb = $service->status;
if ($statusFromDb !== $containerStatus) {
// ray('Updating status: ' . $containerStatus);
$service->update(['status' => $containerStatus]);
}
}
}
}
$exitedServices = collect([]);
foreach ($services as $service) {
$apps = $service->applications()->get();
$dbs = $service->databases()->get();
foreach ($apps as $app) {
if (in_array("$app->id-$app->name", $foundServices)) {
continue;
} else {
$exitedServices->push($app);
}
}
foreach ($dbs as $db) {
if (in_array("$db->id-$db->name", $foundServices)) {
continue;
} else {
$exitedServices->push($db);
}
}
}
$exitedServices = $exitedServices->unique('id');
foreach ($exitedServices as $exitedService) {
if (str($exitedService->status)->startsWith('exited')) {
continue;
}
$name = data_get($exitedService, 'name');
$fqdn = data_get($exitedService, 'fqdn');
if ($name) {
if ($fqdn) {
$containerName = "$name, available at $fqdn";
} else {
$containerName = $name;
}
} else {
if ($fqdn) {
$containerName = $fqdn;
} else {
$containerName = null;
}
}
$projectUuid = data_get($service, 'environment.project.uuid');
$serviceUuid = data_get($service, 'uuid');
$environmentName = data_get($service, 'environment.name');
if ($projectUuid && $serviceUuid && $environmentName) {
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/service/'.$serviceUuid;
} else {
$url = null;
}
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
$exitedService->update(['status' => 'exited']);
}
$notRunningApplications = $this->applications->pluck('id')->diff($foundApplications);
foreach ($notRunningApplications as $applicationId) {
$application = $this->applications->where('id', $applicationId)->first();
if (str($application->status)->startsWith('exited')) {
continue;
}
$application->update(['status' => 'exited']);
$name = data_get($application, 'name');
$fqdn = data_get($application, 'fqdn');
$containerName = $name ? "$name ($fqdn)" : $fqdn;
$projectUuid = data_get($application, 'environment.project.uuid');
$applicationUuid = data_get($application, 'uuid');
$environment = data_get($application, 'environment.name');
if ($projectUuid && $applicationUuid && $environment) {
$url = base_url().'/project/'.$projectUuid.'/'.$environment.'/application/'.$applicationUuid;
} else {
$url = null;
}
// $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 (str($preview->status)->startsWith('exited')) {
continue;
}
$preview->update(['status' => 'exited']);
$name = data_get($preview, 'name');
$fqdn = data_get($preview, 'fqdn');
$containerName = $name ? "$name ($fqdn)" : $fqdn;
$projectUuid = data_get($preview, 'application.environment.project.uuid');
$environmentName = data_get($preview, 'application.environment.name');
$applicationUuid = data_get($preview, 'application.uuid');
if ($projectUuid && $applicationUuid && $environmentName) {
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/application/'.$applicationUuid;
} else {
$url = null;
}
// $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 (str($database->status)->startsWith('exited')) {
continue;
}
$database->update(['status' => 'exited']);
$name = data_get($database, 'name');
$fqdn = data_get($database, 'fqdn');
$containerName = $name;
$projectUuid = data_get($database, 'environment.project.uuid');
$environmentName = data_get($database, 'environment.name');
$databaseUuid = data_get($database, 'uuid');
if ($projectUuid && $databaseUuid && $environmentName) {
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/database/'.$databaseUuid;
} else {
$url = null;
}
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
}
// Check if proxy is running
$this->server->proxyType();
$foundProxyContainer = $containers->filter(function ($value, $key) {
if ($this->server->isSwarm()) {
// TODO: fix this with sentinel
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
} else {
return data_get($value, 'name') === 'coolify-proxy';
}
})->first();
if (! $foundProxyContainer) {
try {
$shouldStart = CheckProxy::run($this->server);
if ($shouldStart) {
StartProxy::run($this->server, false);
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
}
} catch (\Throwable $e) {
ray($e);
}
} else {
$this->server->proxy->status = data_get($foundProxyContainer, 'state');
$this->server->save();
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
}
} catch (\Exception $e) {
// send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
ray($e->getMessage());
return handleError($e);
}
}
private function old_way()
{
if ($this->server->isSwarm()) {
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
$containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
} else {
// Precheck for containers
$containers = instant_remote_process(['docker container ls -q'], $this->server, false);
if (! $containers) {
return;
}
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false);
$containerReplicates = null;
}
if (is_null($containers)) {
return;
}
$containers = format_docker_command_output_to_json($containers);
if ($containerReplicates) {
$containerReplicates = format_docker_command_output_to_json($containerReplicates);
foreach ($containerReplicates as $containerReplica) {
$name = data_get($containerReplica, 'Name');
$containers = $containers->map(function ($container) use ($name, $containerReplica) {
if (data_get($container, 'Spec.Name') === $name) {
$replicas = data_get($containerReplica, 'Replicas');
$running = str($replicas)->explode('/')[0];
$total = str($replicas)->explode('/')[1];
if ($running === $total) {
data_set($container, 'State.Status', 'running');
data_set($container, 'State.Health.Status', 'healthy');
} else {
data_set($container, 'State.Status', 'starting');
data_set($container, 'State.Health.Status', 'unhealthy');
}
}
return $container;
});
}
}
$databases = $this->server->databases();
$services = $this->server->services()->get();
$previews = $this->server->previews();
$foundApplications = [];
$foundApplicationPreviews = [];
$foundDatabases = [];
$foundServices = [];
foreach ($containers as $container) {
if ($this->server->isSwarm()) {
$labels = data_get($container, 'Spec.Labels');
$uuid = data_get($labels, 'coolify.name');
} else {
$labels = data_get($container, 'Config.Labels');
}
$containerStatus = data_get($container, 'State.Status');
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
$containerStatus = "$containerStatus ($containerHealth)";
$labels = Arr::undot(format_docker_labels_to_json($labels));
$applicationId = data_get($labels, 'coolify.applicationId');
if ($applicationId) {
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
if ($pullRequestId) {
if (str($applicationId)->contains('-')) {
$applicationId = str($applicationId)->before('-');
}
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->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 = $this->applications->where('id', $applicationId)->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');
$type = data_get($labels, 'coolify.type');
if ($uuid) {
if ($type === 'service') {
$database_id = data_get($labels, 'coolify.service.subId');
if ($database_id) {
$service_db = ServiceDatabase::where('id', $database_id)->first();
if ($service_db) {
$uuid = data_get($service_db, 'service.uuid');
if ($uuid) {
$isPublic = data_get($service_db, 'is_public');
if ($isPublic) {
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else {
return data_get($value, 'Name') === "/$uuid-proxy";
}
})->first();
if (! $foundTcpProxy) {
StartDatabaseProxy::run($service_db);
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
}
}
}
}
}
} else {
$database = $databases->where('uuid', $uuid)->first();
if ($database) {
$isPublic = data_get($database, 'is_public');
$foundDatabases[] = $database->id;
$statusFromDb = $database->status;
if ($statusFromDb !== $containerStatus) {
$database->update(['status' => $containerStatus]);
}
if ($isPublic) {
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else {
return data_get($value, 'Name') === "/$uuid-proxy";
}
})->first();
if (! $foundTcpProxy) {
StartDatabaseProxy::run($database);
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
}
}
} else {
// Notify user that this container should not be there.
}
}
}
if (data_get($container, 'Name') === '/coolify-db') {
$foundDatabases[] = 0;
}
}
$serviceLabelId = data_get($labels, 'coolify.serviceId');
if ($serviceLabelId) {
$subType = data_get($labels, 'coolify.service.subType');
$subId = data_get($labels, 'coolify.service.subId');
$service = $services->where('id', $serviceLabelId)->first();
if (! $service) {
continue;
}
if ($subType === 'application') {
$service = $service->applications()->where('id', $subId)->first();
} else {
$service = $service->databases()->where('id', $subId)->first();
}
if ($service) {
$foundServices[] = "$service->id-$service->name";
$statusFromDb = $service->status;
if ($statusFromDb !== $containerStatus) {
// ray('Updating status: ' . $containerStatus);
$service->update(['status' => $containerStatus]);
}
}
}
}
$exitedServices = collect([]);
foreach ($services as $service) {
$apps = $service->applications()->get();
$dbs = $service->databases()->get();
foreach ($apps as $app) {
if (in_array("$app->id-$app->name", $foundServices)) {
continue;
} else {
$exitedServices->push($app);
}
}
foreach ($dbs as $db) {
if (in_array("$db->id-$db->name", $foundServices)) {
continue;
} else {
$exitedServices->push($db);
}
}
}
$exitedServices = $exitedServices->unique('id');
foreach ($exitedServices as $exitedService) {
if (str($exitedService->status)->startsWith('exited')) {
continue;
}
$name = data_get($exitedService, 'name');
$fqdn = data_get($exitedService, 'fqdn');
if ($name) {
if ($fqdn) {
$containerName = "$name, available at $fqdn";
} else {
$containerName = $name;
}
} else {
if ($fqdn) {
$containerName = $fqdn;
} else {
$containerName = null;
}
}
$projectUuid = data_get($service, 'environment.project.uuid');
$serviceUuid = data_get($service, 'uuid');
$environmentName = data_get($service, 'environment.name');
if ($projectUuid && $serviceUuid && $environmentName) {
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/service/'.$serviceUuid;
} else {
$url = null;
}
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
$exitedService->update(['status' => 'exited']);
}
$notRunningApplications = $this->applications->pluck('id')->diff($foundApplications);
foreach ($notRunningApplications as $applicationId) {
$application = $this->applications->where('id', $applicationId)->first();
if (str($application->status)->startsWith('exited')) {
continue;
}
$application->update(['status' => 'exited']);
$name = data_get($application, 'name');
$fqdn = data_get($application, 'fqdn');
$containerName = $name ? "$name ($fqdn)" : $fqdn;
$projectUuid = data_get($application, 'environment.project.uuid');
$applicationUuid = data_get($application, 'uuid');
$environment = data_get($application, 'environment.name');
if ($projectUuid && $applicationUuid && $environment) {
$url = base_url().'/project/'.$projectUuid.'/'.$environment.'/application/'.$applicationUuid;
} else {
$url = null;
}
// $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 (str($preview->status)->startsWith('exited')) {
continue;
}
$preview->update(['status' => 'exited']);
$name = data_get($preview, 'name');
$fqdn = data_get($preview, 'fqdn');
$containerName = $name ? "$name ($fqdn)" : $fqdn;
$projectUuid = data_get($preview, 'application.environment.project.uuid');
$environmentName = data_get($preview, 'application.environment.name');
$applicationUuid = data_get($preview, 'application.uuid');
if ($projectUuid && $applicationUuid && $environmentName) {
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/application/'.$applicationUuid;
} else {
$url = null;
}
// $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 (str($database->status)->startsWith('exited')) {
continue;
}
$database->update(['status' => 'exited']);
$name = data_get($database, 'name');
$fqdn = data_get($database, 'fqdn');
$containerName = $name;
$projectUuid = data_get($database, 'environment.project.uuid');
$environmentName = data_get($database, 'environment.name');
$databaseUuid = data_get($database, 'uuid');
if ($projectUuid && $databaseUuid && $environmentName) {
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/database/'.$databaseUuid;
} else {
$url = null;
}
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
}
// Check if proxy is running
$this->server->proxyType();
$foundProxyContainer = $containers->filter(function ($value, $key) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
} else {
return data_get($value, 'Name') === '/coolify-proxy';
}
})->first();
if (! $foundProxyContainer) {
try {
$shouldStart = CheckProxy::run($this->server);
if ($shouldStart) {
StartProxy::run($this->server, false);
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
}
} catch (\Throwable $e) {
ray($e);
}
} else {
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
$this->server->save();
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
}
}
}

View File

@ -16,12 +16,12 @@ class CreateNewUser implements CreatesNewUsers
/** /**
* Validate and create a newly registered user. * Validate and create a newly registered user.
* *
* @param array<string, string> $input * @param array<string, string> $input
*/ */
public function create(array $input): User public function create(array $input): User
{ {
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();
if (! $settings->is_registration_enabled) { if (!$settings->is_registration_enabled) {
abort(403); abort(403);
} }
Validator::make($input, [ Validator::make($input, [
@ -66,7 +66,6 @@ class CreateNewUser implements CreatesNewUsers
} }
// Set session variable // Set session variable
session(['currentTeam' => $user->currentTeam = $team]); session(['currentTeam' => $user->currentTeam = $team]);
return $user; return $user;
} }
} }

View File

@ -14,7 +14,7 @@ class ResetUserPassword implements ResetsUserPasswords
/** /**
* Validate and reset the user's forgotten password. * Validate and reset the user's forgotten password.
* *
* @param array<string, string> $input * @param array<string, string> $input
*/ */
public function reset(User $user, array $input): void public function reset(User $user, array $input): void
{ {

View File

@ -14,7 +14,7 @@ class UpdateUserPassword implements UpdatesUserPasswords
/** /**
* Validate and update the user's password. * Validate and update the user's password.
* *
* @param array<string, string> $input * @param array<string, string> $input
*/ */
public function update(User $user, array $input): void public function update(User $user, array $input): void
{ {

View File

@ -13,7 +13,7 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation
/** /**
* Validate and update the given user's profile information. * Validate and update the given user's profile information.
* *
* @param array<string, string> $input * @param array<string, string> $input
*/ */
public function update(User $user, array $input): void public function update(User $user, array $input): void
{ {
@ -45,7 +45,7 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation
/** /**
* Update the given verified user's profile information. * Update the given verified user's profile information.
* *
* @param array<string, string> $input * @param array<string, string> $input
*/ */
protected function updateVerifiedUser(User $user, array $input): void protected function updateVerifiedUser(User $user, array $input): void
{ {

View File

@ -6,10 +6,10 @@ use App\Models\InstanceSettings;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\AsAction;
class CheckResaleLicense class CheckResaleLicense
{ {
use AsAction; use AsAction;
public function handle() public function handle()
{ {
try { try {
@ -18,7 +18,6 @@ class CheckResaleLicense
$settings->update([ $settings->update([
'is_resale_license_active' => true, 'is_resale_license_active' => true,
]); ]);
return; return;
} }
// if (!$settings->resale_license) { // if (!$settings->resale_license) {
@ -39,7 +38,6 @@ class CheckResaleLicense
$settings->update([ $settings->update([
'is_resale_license_active' => true, 'is_resale_license_active' => true,
]); ]);
return; return;
} }
$data = Http::withHeaders([ $data = Http::withHeaders([
@ -53,7 +51,6 @@ class CheckResaleLicense
$settings->update([ $settings->update([
'is_resale_license_active' => true, 'is_resale_license_active' => true,
]); ]);
return; return;
} }
if (data_get($data, 'license_key.status') === 'active') { if (data_get($data, 'license_key.status') === 'active') {

View File

@ -2,32 +2,27 @@
namespace App\Actions\Proxy; namespace App\Actions\Proxy;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Server;
use Illuminate\Support\Str;
class CheckConfiguration class CheckConfiguration
{ {
use AsAction; use AsAction;
public function handle(Server $server, bool $reset = false) public function handle(Server $server, bool $reset = false)
{ {
$proxyType = $server->proxyType(); $proxy_path = get_proxy_path();
if ($proxyType === 'NONE') { $proxy_configuration = instant_remote_process([
return 'OK';
}
$proxy_path = $server->proxyPath();
$payload = [
"mkdir -p $proxy_path", "mkdir -p $proxy_path",
"cat $proxy_path/docker-compose.yml", "cat $proxy_path/docker-compose.yml",
]; ], $server, false);
$proxy_configuration = instant_remote_process($payload, $server, false);
if ($reset || ! $proxy_configuration || is_null($proxy_configuration)) {
$proxy_configuration = str(generate_default_proxy_configuration($server))->trim()->value;
}
if (! $proxy_configuration || is_null($proxy_configuration)) {
throw new \Exception('Could not generate proxy configuration');
}
if ($reset || !$proxy_configuration || is_null($proxy_configuration)) {
$proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value;
}
if (!$proxy_configuration || is_null($proxy_configuration)) {
throw new \Exception("Could not generate proxy configuration");
}
return $proxy_configuration; return $proxy_configuration;
} }
} }

View File

@ -8,31 +8,11 @@ use Lorisleiva\Actions\Concerns\AsAction;
class CheckProxy class CheckProxy
{ {
use AsAction; use AsAction;
public function handle(Server $server, $fromUI = false) public function handle(Server $server, $fromUI = false)
{ {
if (! $server->isFunctional()) { if (!$server->isProxyShouldRun()) {
return false;
}
if ($server->isBuildServer()) {
if ($server->proxy) {
$server->proxy = null;
$server->save();
}
return false;
}
$proxyType = $server->proxyType();
if (is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop) {
return false;
}
['uptime' => $uptime, 'error' => $error] = $server->validateConnection();
if (! $uptime) {
throw new \Exception($error);
}
if (! $server->isProxyShouldRun()) {
if ($fromUI) { if ($fromUI) {
throw new \Exception('Proxy should not run. You selected the Custom Proxy.'); throw new \Exception("Proxy should not run. You selected the Custom Proxy.");
} else { } else {
return false; return false;
} }
@ -44,17 +24,12 @@ class CheckProxy
if ($status === 'running') { if ($status === 'running') {
return false; return false;
} }
return true; return true;
} else { } else {
$status = getContainerStatus($server, 'coolify-proxy'); $status = getContainerStatus($server, 'coolify-proxy');
if ($status === 'running') { if ($status === 'running') {
$server->proxy->set('status', 'running'); $server->proxy->set('status', 'running');
$server->save(); $server->save();
return false;
}
if ($server->settings->is_cloudflare_tunnel) {
return false; return false;
} }
$ip = $server->ip; $ip = $server->ip;
@ -68,19 +43,18 @@ class CheckProxy
$port443 = is_resource($connection443) && fclose($connection443); $port443 = is_resource($connection443) && fclose($connection443);
if ($port80) { if ($port80) {
if ($fromUI) { if ($fromUI) {
throw new \Exception("Port 80 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a><br>Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>"); throw new \Exception("Port 80 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
} else { } else {
return false; return false;
} }
} }
if ($port443) { if ($port443) {
if ($fromUI) { if ($fromUI) {
throw new \Exception("Port 443 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a><br>Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>"); throw new \Exception("Port 443 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
} else { } else {
return false; return false;
} }
} }
return true; return true;
} }
} }

View File

@ -3,6 +3,7 @@
namespace App\Actions\Proxy; namespace App\Actions\Proxy;
use App\Models\Server; use App\Models\Server;
use Illuminate\Support\Str;
use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\AsAction;
class SaveConfiguration class SaveConfiguration
@ -14,15 +15,15 @@ class SaveConfiguration
if (is_null($proxy_settings)) { if (is_null($proxy_settings)) {
$proxy_settings = CheckConfiguration::run($server, true); $proxy_settings = CheckConfiguration::run($server, true);
} }
$proxy_path = $server->proxyPath(); $proxy_path = get_proxy_path();
$docker_compose_yml_base64 = base64_encode($proxy_settings); $docker_compose_yml_base64 = base64_encode($proxy_settings);
$server->proxy->last_saved_settings = str($docker_compose_yml_base64)->pipe('md5')->value; $server->proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
$server->save(); $server->save();
return instant_remote_process([ return instant_remote_process([
"mkdir -p $proxy_path", "mkdir -p $proxy_path",
"echo '$docker_compose_yml_base64' | base64 -d | tee $proxy_path/docker-compose.yml > /dev/null", "echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
], $server); ], $server);
} }
} }

View File

@ -2,70 +2,60 @@
namespace App\Actions\Proxy; namespace App\Actions\Proxy;
use App\Events\ProxyStarted; use App\Events\ProxyStatusChanged;
use App\Models\Server; use App\Models\Server;
use Illuminate\Support\Str;
use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\AsAction;
use Spatie\Activitylog\Models\Activity; use Spatie\Activitylog\Models\Activity;
class StartProxy class StartProxy
{ {
use AsAction; use AsAction;
public function handle(Server $server, bool $async = true): string|Activity
public function handle(Server $server, bool $async = true, bool $force = false): string|Activity
{ {
try { try {
$proxyType = $server->proxyType(); $proxyType = $server->proxyType();
if ((is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) && $force === false) {
return 'OK';
}
$commands = collect([]); $commands = collect([]);
$proxy_path = $server->proxyPath(); $proxy_path = get_proxy_path();
$configuration = CheckConfiguration::run($server); $configuration = CheckConfiguration::run($server);
if (! $configuration) { if (!$configuration) {
throw new \Exception('Configuration is not synced'); throw new \Exception("Configuration is not synced");
} }
SaveConfiguration::run($server, $configuration); SaveConfiguration::run($server, $configuration);
$docker_compose_yml_base64 = base64_encode($configuration); $docker_compose_yml_base64 = base64_encode($configuration);
$server->proxy->last_applied_settings = str($docker_compose_yml_base64)->pipe('md5')->value; $server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
$server->save(); $server->save();
if ($server->isSwarm()) { if ($server->isSwarm()) {
$commands = $commands->merge([ $commands = $commands->merge([
"mkdir -p $proxy_path/dynamic", "mkdir -p $proxy_path/dynamic && cd $proxy_path",
"cd $proxy_path",
"echo 'Creating required Docker Compose file.'", "echo 'Creating required Docker Compose file.'",
"echo 'Starting coolify-proxy.'", "echo 'Starting coolify-proxy.'",
'docker stack deploy -c docker-compose.yml coolify-proxy', "cd $proxy_path && docker stack deploy -c docker-compose.yml coolify-proxy",
"echo 'Proxy started successfully.'", "echo 'Proxy started successfully.'"
]); ]);
} else { } else {
$caddfile = 'import /dynamic/*.caddy';
$commands = $commands->merge([ $commands = $commands->merge([
"mkdir -p $proxy_path/dynamic", "mkdir -p $proxy_path/dynamic && cd $proxy_path",
"cd $proxy_path",
"echo '$caddfile' > $proxy_path/dynamic/Caddyfile",
"echo 'Creating required Docker Compose file.'", "echo 'Creating required Docker Compose file.'",
"echo 'Pulling docker image.'", "echo 'Pulling docker image.'",
'docker compose pull', 'docker compose pull',
"echo 'Stopping existing coolify-proxy.'", "echo 'Stopping existing coolify-proxy.'",
'docker compose down -v --remove-orphans > /dev/null 2>&1', "docker compose down -v --remove-orphans > /dev/null 2>&1",
"echo 'Starting coolify-proxy.'", "echo 'Starting coolify-proxy.'",
'docker compose up -d --remove-orphans', 'docker compose up -d --remove-orphans',
"echo 'Proxy started successfully.'", "echo 'Proxy started successfully.'"
]); ]);
$commands = $commands->merge(connectProxyToNetworks($server)); $commands = $commands->merge(connectProxyToNetworks($server));
} }
if ($async) { if ($async) {
$activity = remote_process($commands, $server, callEventOnFinish: 'ProxyStarted', callEventData: $server); $activity = remote_process($commands, $server);
return $activity; return $activity;
} else { } else {
instant_remote_process($commands, $server); instant_remote_process($commands, $server);
$server->proxy->set('status', 'running'); $server->proxy->set('status', 'running');
$server->proxy->set('type', $proxyType); $server->proxy->set('type', $proxyType);
$server->save(); $server->save();
ProxyStarted::dispatch($server);
return 'OK'; return 'OK';
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

@ -2,17 +2,14 @@
namespace App\Actions\Server; namespace App\Actions\Server;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Server;
class CleanupDocker class CleanupDocker
{ {
use AsAction; use AsAction;
public function handle(Server $server, bool $force = true) public function handle(Server $server, bool $force = true)
{ {
// cleanup docker images, containers, and builder caches
if ($force) { if ($force) {
instant_remote_process(['docker image prune -af'], $server, false); instant_remote_process(['docker image prune -af'], $server, false);
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false); instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
@ -22,15 +19,5 @@ class CleanupDocker
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false); instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
instant_remote_process(['docker builder prune -f'], $server, false); instant_remote_process(['docker builder prune -f'], $server, false);
} }
// cleanup networks
// $networks = collectDockerNetworksByServer($server);
// $proxyNetworks = collectProxyDockerNetworksByServer($server);
// $diff = $proxyNetworks->diff($networks);
// if ($diff->count() > 0) {
// $diff->map(function ($network) use ($server) {
// instant_remote_process(["docker network disconnect $network coolify-proxy"], $server);
// instant_remote_process(["docker network rm $network"], $server);
// });
// }
} }
} }

View File

@ -1,51 +0,0 @@
<?php
namespace App\Actions\Server;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class ConfigureCloudflared
{
use AsAction;
public function handle(Server $server, string $cloudflare_token)
{
try {
$config = [
'services' => [
'coolify-cloudflared' => [
'container_name' => 'coolify-cloudflared',
'image' => 'cloudflare/cloudflared:latest',
'restart' => RESTART_MODE,
'network_mode' => 'host',
'command' => 'tunnel run',
'environment' => [
"TUNNEL_TOKEN={$cloudflare_token}",
],
],
],
];
$config = Yaml::dump($config, 12, 2);
$docker_compose_yml_base64 = base64_encode($config);
$commands = collect([
'mkdir -p /tmp/cloudflared',
'cd /tmp/cloudflared',
"echo '$docker_compose_yml_base64' | base64 -d | tee docker-compose.yml > /dev/null",
'docker compose pull',
'docker compose down -v --remove-orphans > /dev/null 2>&1',
'docker compose up -d --remove-orphans',
]);
instant_remote_process($commands, $server);
} catch (\Throwable $e) {
ray($e);
throw $e;
} finally {
$commands = collect([
'rm -fr /tmp/cloudflared',
]);
instant_remote_process($commands, $server);
}
}
}

View File

@ -2,21 +2,20 @@
namespace App\Actions\Server; namespace App\Actions\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Server; use App\Models\Server;
use App\Models\StandaloneDocker; use App\Models\StandaloneDocker;
use Lorisleiva\Actions\Concerns\AsAction;
class InstallDocker class InstallDocker
{ {
use AsAction; use AsAction;
public function handle(Server $server) public function handle(Server $server)
{ {
$supported_os_type = $server->validateOS(); $supported_os_type = $server->validateOS();
if (! $supported_os_type) { if (!$supported_os_type) {
throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://coolify.io/docs/installation#manually">documentation</a>.'); throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://coolify.io/docs/servers#install-docker-engine-manually">documentation</a>.');
} }
ray('Installing Docker on server: '.$server->name.' ('.$server->ip.')'.' with OS type: '.$supported_os_type); ray('Installing Docker on server: ' . $server->name . ' (' . $server->ip . ')' . ' with OS type: ' . $supported_os_type);
$dockerVersion = '24.0'; $dockerVersion = '24.0';
$config = base64_encode('{ $config = base64_encode('{
"log-driver": "json-file", "log-driver": "json-file",
@ -37,41 +36,32 @@ class InstallDocker
if (isDev() && $server->id === 0) { if (isDev() && $server->id === 0) {
$command = $command->merge([ $command = $command->merge([
"echo 'Installing Prerequisites...'", "echo 'Installing Prerequisites...'",
'sleep 1', "sleep 1",
"echo 'Installing Docker Engine...'", "echo 'Installing Docker Engine...'",
"echo 'Configuring Docker Engine (merging existing configuration with the required)...'", "echo 'Configuring Docker Engine (merging existing configuration with the required)...'",
'sleep 4', "sleep 4",
"echo 'Restarting Docker Engine...'", "echo 'Restarting Docker Engine...'",
'ls -l /tmp', "ls -l /tmp"
]); ]);
return remote_process($command, $server); return remote_process($command, $server);
} else { } else {
if ($supported_os_type->contains('debian')) { if ($supported_os_type->contains('debian')) {
$command = $command->merge([ $command = $command->merge([
"echo 'Installing Prerequisites...'", "echo 'Installing Prerequisites...'",
'apt-get update -y', "command -v jq >/dev/null || apt-get update -y",
'command -v curl >/dev/null || apt install -y curl', "command -v jq >/dev/null || apt install -y curl wget git jq",
'command -v wget >/dev/null || apt install -y wget',
'command -v git >/dev/null || apt install -y git',
'command -v jq >/dev/null || apt install -y jq',
]); ]);
} elseif ($supported_os_type->contains('rhel')) { } else if ($supported_os_type->contains('rhel')) {
$command = $command->merge([ $command = $command->merge([
"echo 'Installing Prerequisites...'", "echo 'Installing Prerequisites...'",
'command -v curl >/dev/null || dnf install -y curl', "command -v jq >/dev/null || dnf install -y curl wget git jq",
'command -v wget >/dev/null || dnf install -y wget',
'command -v git >/dev/null || dnf install -y git',
'command -v jq >/dev/null || dnf install -y jq',
]); ]);
} elseif ($supported_os_type->contains('sles')) { } else if ($supported_os_type->contains('sles')) {
$command = $command->merge([ $command = $command->merge([
"echo 'Installing Prerequisites...'", "echo 'Installing Prerequisites...'",
'zypper update -y', "command -v jq >/dev/null || zypper update -y",
'command -v curl >/dev/null || zypper install -y curl', "command -v jq >/dev/null || zypper install -y curl wget git jq",
'command -v wget >/dev/null || zypper install -y wget',
'command -v git >/dev/null || zypper install -y git',
'command -v jq >/dev/null || zypper install -y jq',
]); ]);
} else { } else {
throw new \Exception('Unsupported OS'); throw new \Exception('Unsupported OS');
@ -80,30 +70,26 @@ class InstallDocker
"echo 'Installing Docker Engine...'", "echo 'Installing Docker Engine...'",
"curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh || curl https://get.docker.com | sh -s -- --version {$dockerVersion}", "curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh || curl https://get.docker.com | sh -s -- --version {$dockerVersion}",
"echo 'Configuring Docker Engine (merging existing configuration with the required)...'", "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")"', "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",
"test ! -s /etc/docker/daemon.json && echo '{$config}' | base64 -d | tee /etc/docker/daemon.json > /dev/null", "echo '{$config}' | base64 -d > /etc/docker/daemon.json.coolify",
"echo '{$config}' | base64 -d | tee /etc/docker/daemon.json.coolify > /dev/null", "cat <<< $(jq . /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json.coolify",
'jq . /etc/docker/daemon.json.coolify | tee /etc/docker/daemon.json.coolify.pretty > /dev/null', "cat <<< $(jq -s '.[0] * .[1]' /etc/docker/daemon.json /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json",
'mv /etc/docker/daemon.json.coolify.pretty /etc/docker/daemon.json.coolify',
"jq -s '.[0] * .[1]' /etc/docker/daemon.json.coolify /etc/docker/daemon.json | tee /etc/docker/daemon.json.appended > /dev/null",
'mv /etc/docker/daemon.json.appended /etc/docker/daemon.json',
"echo 'Restarting Docker Engine...'", "echo 'Restarting Docker Engine...'",
'systemctl enable docker >/dev/null 2>&1 || true', "systemctl enable docker >/dev/null 2>&1 || true",
'systemctl restart docker', "systemctl restart docker",
]); ]);
if ($server->isSwarm()) { if ($server->isSwarm()) {
$command = $command->merge([ $command = $command->merge([
'docker network create --attachable --driver overlay coolify-overlay >/dev/null 2>&1 || true', "docker network create --attachable --driver overlay coolify-overlay >/dev/null 2>&1 || true",
]); ]);
} else { } else {
$command = $command->merge([ $command = $command->merge([
'docker network create --attachable coolify >/dev/null 2>&1 || true', "docker network create --attachable coolify >/dev/null 2>&1 || true",
]); ]);
$command = $command->merge([ $command = $command->merge([
"echo 'Done!'", "echo 'Done!'",
]); ]);
} }
return remote_process($command, $server); return remote_process($command, $server);
} }
} }

View File

@ -2,22 +2,21 @@
namespace App\Actions\Server; namespace App\Actions\Server;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Server;
class InstallLogDrain class InstallLogDrain
{ {
use AsAction; use AsAction;
public function handle(Server $server) public function handle(Server $server)
{ {
if ($server->settings->is_logdrain_newrelic_enabled) { if ($server->settings->is_logdrain_newrelic_enabled) {
$type = 'newrelic'; $type = 'newrelic';
} elseif ($server->settings->is_logdrain_highlight_enabled) { } else if ($server->settings->is_logdrain_highlight_enabled) {
$type = 'highlight'; $type = 'highlight';
} elseif ($server->settings->is_logdrain_axiom_enabled) { } else if ($server->settings->is_logdrain_axiom_enabled) {
$type = 'axiom'; $type = 'axiom';
} elseif ($server->settings->is_logdrain_custom_enabled) { } else if ($server->settings->is_logdrain_custom_enabled) {
$type = 'custom'; $type = 'custom';
} else { } else {
$type = 'none'; $type = 'none';
@ -26,12 +25,11 @@ class InstallLogDrain
if ($type === 'none') { if ($type === 'none') {
$command = [ $command = [
"echo 'Stopping old Fluent Bit'", "echo 'Stopping old Fluent Bit'",
'docker rm -f coolify-log-drain || true', "docker rm -f coolify-log-drain || true",
]; ];
return instant_remote_process($command, $server); return instant_remote_process($command, $server);
} elseif ($type === 'newrelic') { } else if ($type === 'newrelic') {
if (! $server->settings->is_logdrain_newrelic_enabled) { if (!$server->settings->is_logdrain_newrelic_enabled) {
throw new \Exception('New Relic log drain is not enabled.'); throw new \Exception('New Relic log drain is not enabled.');
} }
$config = base64_encode(" $config = base64_encode("
@ -61,11 +59,11 @@ class InstallLogDrain
# https://log-api.newrelic.com/log/v1 - US # https://log-api.newrelic.com/log/v1 - US
base_uri \${BASE_URI} base_uri \${BASE_URI}
"); ");
} elseif ($type === 'highlight') { } else if ($type === 'highlight') {
if (! $server->settings->is_logdrain_highlight_enabled) { if (!$server->settings->is_logdrain_highlight_enabled) {
throw new \Exception('Highlight log drain is not enabled.'); throw new \Exception('Highlight log drain is not enabled.');
} }
$config = base64_encode(' $config = base64_encode("
[SERVICE] [SERVICE]
Flush 5 Flush 5
Daemon off Daemon off
@ -73,7 +71,7 @@ class InstallLogDrain
Parsers_File parsers.conf Parsers_File parsers.conf
[INPUT] [INPUT]
Name forward Name forward
tag ${HIGHLIGHT_PROJECT_ID} tag \${HIGHLIGHT_PROJECT_ID}
Buffer_Chunk_Size 1M Buffer_Chunk_Size 1M
Buffer_Max_Size 6M Buffer_Max_Size 6M
[OUTPUT] [OUTPUT]
@ -81,9 +79,9 @@ class InstallLogDrain
Match * Match *
Host otel.highlight.io Host otel.highlight.io
Port 24224 Port 24224
'); ");
} elseif ($type === 'axiom') { } else if ($type === 'axiom') {
if (! $server->settings->is_logdrain_axiom_enabled) { if (!$server->settings->is_logdrain_axiom_enabled) {
throw new \Exception('Axiom log drain is not enabled.'); throw new \Exception('Axiom log drain is not enabled.');
} }
$config = base64_encode(" $config = base64_encode("
@ -118,8 +116,8 @@ class InstallLogDrain
json_date_format iso8601 json_date_format iso8601
tls On tls On
"); ");
} elseif ($type === 'custom') { } else if ($type === 'custom') {
if (! $server->settings->is_logdrain_custom_enabled) { if (!$server->settings->is_logdrain_custom_enabled) {
throw new \Exception('Custom log drain is not enabled.'); throw new \Exception('Custom log drain is not enabled.');
} }
$config = base64_encode($server->settings->logdrain_custom_config); $config = base64_encode($server->settings->logdrain_custom_config);
@ -135,7 +133,7 @@ class InstallLogDrain
Regex /^(?!\s*$).+/ Regex /^(?!\s*$).+/
"); ");
} }
$compose = base64_encode(' $compose = base64_encode("
services: services:
coolify-log-drain: coolify-log-drain:
image: cr.fluentbit.io/fluent/fluent-bit:2.0 image: cr.fluentbit.io/fluent/fluent-bit:2.0
@ -149,7 +147,7 @@ services:
ports: ports:
- 127.0.0.1:24224:24224 - 127.0.0.1:24224:24224
restart: unless-stopped restart: unless-stopped
'); ");
$readme = base64_encode('# New Relic Log Drain $readme = base64_encode('# New Relic Log Drain
This log drain is based on [Fluent Bit](https://fluentbit.io/) and New Relic Log Forwarder. This log drain is based on [Fluent Bit](https://fluentbit.io/) and New Relic Log Forwarder.
@ -162,18 +160,18 @@ Files:
$base_uri = $server->settings->logdrain_newrelic_base_uri; $base_uri = $server->settings->logdrain_newrelic_base_uri;
$base_path = config('coolify.base_config_path'); $base_path = config('coolify.base_config_path');
$config_path = $base_path.'/log-drains'; $config_path = $base_path . '/log-drains';
$fluent_bit_config = $config_path.'/fluent-bit.conf'; $fluent_bit_config = $config_path . '/fluent-bit.conf';
$parsers_config = $config_path.'/parsers.conf'; $parsers_config = $config_path . '/parsers.conf';
$compose_path = $config_path.'/docker-compose.yml'; $compose_path = $config_path . '/docker-compose.yml';
$readme_path = $config_path.'/README.md'; $readme_path = $config_path . '/README.md';
$command = [ $command = [
"echo 'Saving configuration'", "echo 'Saving configuration'",
"mkdir -p $config_path", "mkdir -p $config_path",
"echo '{$parsers}' | base64 -d | tee $parsers_config > /dev/null", "echo '{$parsers}' | base64 -d > $parsers_config",
"echo '{$config}' | base64 -d | tee $fluent_bit_config > /dev/null", "echo '{$config}' | base64 -d > $fluent_bit_config",
"echo '{$compose}' | base64 -d | tee $compose_path > /dev/null", "echo '{$compose}' | base64 -d > $compose_path",
"echo '{$readme}' | base64 -d | tee $readme_path > /dev/null", "echo '{$readme}' | base64 -d > $readme_path",
"test -f $config_path/.env && rm $config_path/.env", "test -f $config_path/.env && rm $config_path/.env",
]; ];
@ -182,18 +180,18 @@ Files:
"echo LICENSE_KEY=$license_key >> $config_path/.env", "echo LICENSE_KEY=$license_key >> $config_path/.env",
"echo BASE_URI=$base_uri >> $config_path/.env", "echo BASE_URI=$base_uri >> $config_path/.env",
]; ];
} elseif ($type === 'highlight') { } else if ($type === 'highlight') {
$add_envs_command = [ $add_envs_command = [
"echo HIGHLIGHT_PROJECT_ID={$server->settings->logdrain_highlight_project_id} >> $config_path/.env", "echo HIGHLIGHT_PROJECT_ID={$server->settings->logdrain_highlight_project_id} >> $config_path/.env",
]; ];
} elseif ($type === 'axiom') { } else if ($type === 'axiom') {
$add_envs_command = [ $add_envs_command = [
"echo AXIOM_DATASET_NAME={$server->settings->logdrain_axiom_dataset_name} >> $config_path/.env", "echo AXIOM_DATASET_NAME={$server->settings->logdrain_axiom_dataset_name} >> $config_path/.env",
"echo AXIOM_API_KEY={$server->settings->logdrain_axiom_api_key} >> $config_path/.env", "echo AXIOM_API_KEY={$server->settings->logdrain_axiom_api_key} >> $config_path/.env",
]; ];
} elseif ($type === 'custom') { } else if ($type === 'custom') {
$add_envs_command = [ $add_envs_command = [
"touch $config_path/.env", "touch $config_path/.env"
]; ];
} else { } else {
throw new \Exception('Unknown log drain type.'); throw new \Exception('Unknown log drain type.');
@ -205,7 +203,6 @@ Files:
"cd $config_path && docker compose up -d --remove-orphans", "cd $config_path && docker compose up -d --remove-orphans",
]; ];
$command = array_merge($command, $add_envs_command, $restart_command); $command = array_merge($command, $add_envs_command, $restart_command);
return instant_remote_process($command, $server); return instant_remote_process($command, $server);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e); return handleError($e);

View File

@ -1,19 +0,0 @@
<?php
namespace App\Actions\Server;
use App\Enums\ActivityTypes;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
class RunCommand
{
use AsAction;
public function handle(Server $server, $command)
{
$activity = remote_process(command: [$command], server: $server, ignore_errors: true, type: ActivityTypes::COMMAND->value);
return $activity;
}
}

View File

@ -1,26 +0,0 @@
<?php
namespace App\Actions\Server;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
class StartSentinel
{
use AsAction;
public function handle(Server $server, $version = 'latest', bool $restart = false)
{
if ($restart) {
StopSentinel::run($server);
}
$metrics_history = $server->settings->metrics_history_days;
$refresh_rate = $server->settings->metrics_refresh_rate_seconds;
$token = $server->settings->metrics_token;
instant_remote_process([
"docker run --rm --pull always -d -e \"TOKEN={$token}\" -e \"SCHEDULER=true\" -e \"METRICS_HISTORY={$metrics_history}\" -e \"REFRESH_RATE={$refresh_rate}\" --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/metrics:/app/metrics -v /data/coolify/logs:/app/logs --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 ghcr.io/coollabsio/sentinel:$version",
'chown -R 9999:root /data/coolify/metrics /data/coolify/logs',
'chmod -R 700 /data/coolify/metrics /data/coolify/logs',
], $server, true);
}
}

View File

@ -1,16 +0,0 @@
<?php
namespace App\Actions\Server;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
class StopSentinel
{
use AsAction;
public function handle(Server $server)
{
instant_remote_process(['docker rm -f coolify-sentinel'], $server, false);
}
}

View File

@ -2,21 +2,18 @@
namespace App\Actions\Server; namespace App\Actions\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\InstanceSettings; use App\Models\InstanceSettings;
use App\Models\Server; use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
class UpdateCoolify class UpdateCoolify
{ {
use AsAction; use AsAction;
public ?Server $server = null; public ?Server $server = null;
public ?string $latestVersion = null; public ?string $latestVersion = null;
public ?string $currentVersion = null; public ?string $currentVersion = null;
public function handle($manual_update = false) public function handle(bool $force)
{ {
try { try {
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();
@ -25,22 +22,32 @@ class UpdateCoolify
if (!$this->server) { if (!$this->server) {
return; return;
} }
CleanupDocker::dispatch($this->server, false)->onQueue('high'); CleanupDocker::run($this->server, false);
$this->latestVersion = get_latest_version_of_coolify(); $this->latestVersion = get_latest_version_of_coolify();
$this->currentVersion = config('version'); $this->currentVersion = config('version');
if (!$manual_update) { if ($settings->next_channel) {
ray('next channel enabled');
$this->latestVersion = 'next';
}
if ($force) {
$this->update();
} else {
if (!$settings->is_auto_update_enabled) { if (!$settings->is_auto_update_enabled) {
return; return 'Auto update is disabled';
} }
if ($this->latestVersion === $this->currentVersion) { if ($this->latestVersion === $this->currentVersion) {
return; return 'Already on latest version';
} }
if (version_compare($this->latestVersion, $this->currentVersion, '<')) { if (version_compare($this->latestVersion, $this->currentVersion, '<')) {
return; return 'Latest version is lower than current version?!';
} }
$this->update();
} }
$this->update(); send_internal_notification("Instance updated from {$this->currentVersion} -> {$this->latestVersion}");
} catch (\Throwable $e) { } catch (\Throwable $e) {
ray('InstanceAutoUpdateJob failed');
ray($e->getMessage());
send_internal_notification('InstanceAutoUpdateJob failed: ' . $e->getMessage());
throw $e; throw $e;
} }
} }
@ -48,7 +55,7 @@ class UpdateCoolify
private function update() private function update()
{ {
if (isDev()) { if (isDev()) {
ray('Running in dev mode'); ray("Running update on local docker container. Updating to $this->latestVersion");
remote_process([ remote_process([
"sleep 10" "sleep 10"
], $this->server); ], $this->server);
@ -60,12 +67,7 @@ class UpdateCoolify
"curl -fsSL https://cdn.lasthourhosting.org/lasthourcloud/scripts/upgrade.sh -o /data/coolify/source/upgrade.sh", "curl -fsSL https://cdn.lasthourhosting.org/lasthourcloud/scripts/upgrade.sh -o /data/coolify/source/upgrade.sh",
"bash /data/coolify/source/upgrade.sh $this->latestVersion" "bash /data/coolify/source/upgrade.sh $this->latestVersion"
], $this->server); ], $this->server);
return; return;
} }
remote_process([
'curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh',
"bash /data/coolify/source/upgrade.sh $this->latestVersion",
], $this->server);
} }
} }

View File

@ -1,67 +0,0 @@
<?php
namespace App\Actions\Server;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
class ValidateServer
{
use AsAction;
public ?string $uptime = null;
public ?string $error = null;
public ?string $supported_os_type = null;
public ?string $docker_installed = null;
public ?string $docker_compose_installed = null;
public ?string $docker_version = null;
public function handle(Server $server)
{
$server->update([
'validation_logs' => null,
]);
['uptime' => $this->uptime, 'error' => $error] = $server->validateConnection();
if (! $this->uptime) {
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br>Check this <a target="_blank" class="text-black underline dark:text-white" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br><div class="text-error">Error: '.$error.'</div>';
$server->update([
'validation_logs' => $this->error,
]);
throw new \Exception($this->error);
}
$this->supported_os_type = $server->validateOS();
if (! $this->supported_os_type) {
$this->error = 'Server OS type is not supported. Please install Docker manually before continuing: <a target="_blank" class="text-black underline dark:text-white" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
$server->update([
'validation_logs' => $this->error,
]);
throw new \Exception($this->error);
}
$this->docker_installed = $server->validateDockerEngine();
$this->docker_compose_installed = $server->validateDockerCompose();
if (! $this->docker_installed || ! $this->docker_compose_installed) {
$this->error = 'Docker Engine is not installed. Please install Docker manually before continuing: <a target="_blank" class="text-black underline dark:text-white" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
$server->update([
'validation_logs' => $this->error,
]);
throw new \Exception($this->error);
}
$this->docker_version = $server->validateDockerEngineVersion();
if ($this->docker_version) {
return 'OK';
} else {
$this->error = 'Docker Engine is not installed. Please install Docker manually before continuing: <a target="_blank" class="text-black underline dark:text-white" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
$server->update([
'validation_logs' => $this->error,
]);
throw new \Exception($this->error);
}
}
}

View File

@ -2,13 +2,12 @@
namespace App\Actions\Service; namespace App\Actions\Service;
use App\Models\Service;
use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Service;
class DeleteService class DeleteService
{ {
use AsAction; use AsAction;
public function handle(Service $service) public function handle(Service $service)
{ {
try { try {

View File

@ -1,18 +0,0 @@
<?php
namespace App\Actions\Service;
use App\Models\Service;
use Lorisleiva\Actions\Concerns\AsAction;
class RestartService
{
use AsAction;
public function handle(Service $service)
{
StopService::run($service);
return StartService::run($service);
}
}

View File

@ -2,27 +2,26 @@
namespace App\Actions\Service; namespace App\Actions\Service;
use App\Models\Service;
use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Service;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
class StartService class StartService
{ {
use AsAction; use AsAction;
public function handle(Service $service) public function handle(Service $service)
{ {
ray('Starting service: '.$service->name); ray('Starting service: ' . $service->name);
$service->saveComposeConfigs(); $service->saveComposeConfigs();
$commands[] = 'cd '.$service->workdir(); $commands[] = "cd " . $service->workdir();
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'"; $commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
$commands[] = "echo 'Creating Docker network.'"; $commands[] = "echo 'Creating Docker network.'";
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid"; $commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid >/dev/null 2>&1 || true";
$commands[] = 'echo Starting service.'; $commands[] = "echo Starting service.";
$commands[] = "echo 'Pulling images.'"; $commands[] = "echo 'Pulling images.'";
$commands[] = 'docker compose pull'; $commands[] = "docker compose pull";
$commands[] = "echo 'Starting containers.'"; $commands[] = "echo 'Starting containers.'";
$commands[] = 'docker compose up -d --remove-orphans --force-recreate --build'; $commands[] = "docker compose up -d --remove-orphans --force-recreate --build";
$commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true"; $commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true";
if (data_get($service, 'connect_to_docker_network')) { if (data_get($service, 'connect_to_docker_network')) {
$compose = data_get($service, 'docker_compose', []); $compose = data_get($service, 'docker_compose', []);
@ -33,7 +32,6 @@ class StartService
} }
} }
$activity = remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged'); $activity = remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged');
return $activity; return $activity;
} }
} }

View File

@ -2,37 +2,37 @@
namespace App\Actions\Service; namespace App\Actions\Service;
use App\Models\Service;
use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Service;
class StopService class StopService
{ {
use AsAction; use AsAction;
public function handle(Service $service) public function handle(Service $service)
{ {
try { try {
$server = $service->destination->server; $server = $service->destination->server;
if (! $server->isFunctional()) { if (!$server->isFunctional()) {
return 'Server is not functional'; return 'Server is not functional';
} }
ray('Stopping service: '.$service->name); ray('Stopping service: ' . $service->name);
$applications = $service->applications()->get(); $applications = $service->applications()->get();
foreach ($applications as $application) { foreach ($applications as $application) {
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server, false); instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server);
$application->update(['status' => 'exited']); $application->update(['status' => 'exited']);
} }
$dbs = $service->databases()->get(); $dbs = $service->databases()->get();
foreach ($dbs as $db) { foreach ($dbs as $db) {
instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server, false); instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server);
$db->update(['status' => 'exited']); $db->update(['status' => 'exited']);
} }
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy"], $service->server); instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy 2>/dev/null"], $service->server, false);
instant_remote_process(["docker network rm {$service->uuid}"], $service->server); instant_remote_process(["docker network rm {$service->uuid} 2>/dev/null"], $service->server, false);
// TODO: make notification for databases
// $service->environment->project->team->notify(new StatusChanged($service));
} catch (\Exception $e) { } catch (\Exception $e) {
echo $e->getMessage(); echo $e->getMessage();
ray($e->getMessage()); ray($e->getMessage());
return $e->getMessage(); return $e->getMessage();
} }

View File

@ -8,21 +8,18 @@ use Lorisleiva\Actions\Concerns\AsAction;
class ComplexStatusCheck class ComplexStatusCheck
{ {
use AsAction; use AsAction;
public function handle(Application $application) public function handle(Application $application)
{ {
$servers = $application->additional_servers; $servers = $application->additional_servers;
$servers->push($application->destination->server); $servers->push($application->destination->server);
foreach ($servers as $server) { foreach ($servers as $server) {
$is_main_server = $application->destination->server->id === $server->id; $is_main_server = $application->destination->server->id === $server->id;
if (! $server->isFunctional()) { if (!$server->isFunctional()) {
if ($is_main_server) { if ($is_main_server) {
$application->update(['status' => 'exited:unhealthy']); $application->update(['status' => 'exited:unhealthy']);
continue; continue;
} else { } else {
$application->additional_servers()->updateExistingPivot($server->id, ['status' => 'exited:unhealthy']); $application->additional_servers()->updateExistingPivot($server->id, ['status' => 'exited:unhealthy']);
continue; continue;
} }
} }
@ -47,11 +44,9 @@ class ComplexStatusCheck
} else { } else {
if ($is_main_server) { if ($is_main_server) {
$application->update(['status' => 'exited:unhealthy']); $application->update(['status' => 'exited:unhealthy']);
continue; continue;
} else { } else {
$application->additional_servers()->updateExistingPivot($server->id, ['status' => 'exited:unhealthy']); $application->additional_servers()->updateExistingPivot($server->id, ['status' => 'exited:unhealthy']);
continue; continue;
} }
} }

View File

@ -8,20 +8,17 @@ use Lorisleiva\Actions\Concerns\AsAction;
class PullImage class PullImage
{ {
use AsAction; use AsAction;
public function handle(Service $resource) public function handle(Service $resource)
{ {
$resource->saveComposeConfigs(); $resource->saveComposeConfigs();
$commands[] = 'cd '.$resource->workdir(); $commands[] = "cd " . $resource->workdir();
$commands[] = "echo 'Saved configuration files to {$resource->workdir()}.'"; $commands[] = "echo 'Saved configuration files to {$resource->workdir()}.'";
$commands[] = 'docker compose pull'; $commands[] = "docker compose pull";
$server = data_get($resource, 'server'); $server = data_get($resource, 'server');
if (! $server) { if (!$server) return;
return;
}
instant_remote_process($commands, $resource->server); instant_remote_process($commands, $resource->server);
} }

View File

@ -1,56 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Models\User;
use Illuminate\Console\Command;
class AdminRemoveUser extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'admin:remove-user {email}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Remove User from database';
/**
* Execute the console command.
*/
public function handle()
{
try {
$email = $this->argument('email');
$confirm = $this->confirm('Are you sure you want to remove user with email: '.$email.'?');
if (! $confirm) {
$this->info('User removal cancelled.');
return;
}
$this->info("Removing user with email: $email");
$user = User::whereEmail($email)->firstOrFail();
$teams = $user->teams;
foreach ($teams as $team) {
if ($team->members->count() > 1) {
$this->error('User is a member of a team with more than one member. Please remove user from team first.');
return;
}
$team->delete();
}
$user->delete();
} catch (\Exception $e) {
$this->error('Failed to remove user.');
$this->error($e->getMessage());
return;
}
}
}

View File

@ -1,26 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Models\ApplicationDeploymentQueue;
use Illuminate\Console\Command;
class CleanupApplicationDeploymentQueue extends Command
{
protected $signature = 'cleanup:application-deployment-queue {--team-id=}';
protected $description = 'CleanupApplicationDeploymentQueue';
public function handle()
{
$team_id = $this->option('team-id');
$servers = \App\Models\Server::where('team_id', $team_id)->get();
foreach ($servers as $server) {
$deployments = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->where('server_id', $server->id)->get();
foreach ($deployments as $deployment) {
$deployment->update(['status' => 'failed']);
instant_remote_process(['docker rm -f '.$deployment->deployment_uuid], $server, false);
}
}
}
}

View File

@ -1,64 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class CleanupDatabase extends Command
{
protected $signature = 'cleanup:database {--yes}';
protected $description = 'Cleanup database';
public function handle()
{
if ($this->option('yes')) {
echo "Running database cleanup...\n";
} else {
echo "Running database cleanup in dry-run mode...\n";
}
$keep_days = 60;
echo "Keep days: $keep_days\n";
// Cleanup failed jobs table
$failed_jobs = DB::table('failed_jobs')->where('failed_at', '<', now()->subDays(1));
$count = $failed_jobs->count();
echo "Delete $count entries from failed_jobs.\n";
if ($this->option('yes')) {
$failed_jobs->delete();
}
// Cleanup sessions table
$sessions = DB::table('sessions')->where('last_activity', '<', now()->subDays($keep_days)->timestamp);
$count = $sessions->count();
echo "Delete $count entries from sessions.\n";
if ($this->option('yes')) {
$sessions->delete();
}
// Cleanup activity_log table
$activity_log = DB::table('activity_log')->where('created_at', '<', now()->subDays($keep_days))->orderBy('created_at', 'desc')->skip(10);
$count = $activity_log->count();
echo "Delete $count entries from activity_log.\n";
if ($this->option('yes')) {
$activity_log->delete();
}
// Cleanup application_deployment_queues table
$application_deployment_queues = DB::table('application_deployment_queues')->where('created_at', '<', now()->subDays($keep_days))->orderBy('created_at', 'desc')->skip(10);
$count = $application_deployment_queues->count();
echo "Delete $count entries from application_deployment_queues.\n";
if ($this->option('yes')) {
$application_deployment_queues->delete();
}
// Cleanup webhooks table
$webhooks = DB::table('webhooks')->where('created_at', '<', now()->subDays($keep_days));
$count = $webhooks->count();
echo "Delete $count entries from webhooks.\n";
if ($this->option('yes')) {
$webhooks->delete();
}
}
}

View File

@ -8,7 +8,6 @@ use Illuminate\Support\Facades\Redis;
class CleanupQueue extends Command class CleanupQueue extends Command
{ {
protected $signature = 'cleanup:queue'; protected $signature = 'cleanup:queue';
protected $description = 'Cleanup Queue'; protected $description = 'Cleanup Queue';
public function handle() public function handle()

View File

@ -7,9 +7,6 @@ use App\Models\ScheduledTask;
use App\Models\Service; use App\Models\Service;
use App\Models\ServiceApplication; use App\Models\ServiceApplication;
use App\Models\ServiceDatabase; use App\Models\ServiceDatabase;
use App\Models\StandaloneClickhouse;
use App\Models\StandaloneDragonfly;
use App\Models\StandaloneKeydb;
use App\Models\StandaloneMariadb; use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb; use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql; use App\Models\StandaloneMysql;
@ -20,7 +17,6 @@ use Illuminate\Console\Command;
class CleanupStuckedResources extends Command class CleanupStuckedResources extends Command
{ {
protected $signature = 'cleanup:stucked-resources'; protected $signature = 'cleanup:stucked-resources';
protected $description = 'Cleanup Stucked Resources'; protected $description = 'Cleanup Stucked Resources';
public function handle() public function handle()
@ -29,7 +25,6 @@ class CleanupStuckedResources extends Command
echo "Running cleanup stucked resources.\n"; echo "Running cleanup stucked resources.\n";
$this->cleanup_stucked_resources(); $this->cleanup_stucked_resources();
} }
private function cleanup_stucked_resources() private function cleanup_stucked_resources()
{ {
@ -60,33 +55,6 @@ class CleanupStuckedResources extends Command
} catch (\Throwable $e) { } catch (\Throwable $e) {
echo "Error in cleaning stuck redis: {$e->getMessage()}\n"; echo "Error in cleaning stuck redis: {$e->getMessage()}\n";
} }
try {
$keydbs = StandaloneKeydb::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($keydbs as $keydb) {
echo "Deleting stuck keydb: {$keydb->name}\n";
$keydb->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck keydb: {$e->getMessage()}\n";
}
try {
$dragonflies = StandaloneDragonfly::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($dragonflies as $dragonfly) {
echo "Deleting stuck dragonfly: {$dragonfly->name}\n";
$dragonfly->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck dragonfly: {$e->getMessage()}\n";
}
try {
$clickhouses = StandaloneClickhouse::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($clickhouses as $clickhouse) {
echo "Deleting stuck clickhouse: {$clickhouse->name}\n";
$clickhouse->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck clickhouse: {$e->getMessage()}\n";
}
try { try {
$mongodbs = StandaloneMongodb::withTrashed()->whereNotNull('deleted_at')->get(); $mongodbs = StandaloneMongodb::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($mongodbs as $mongodb) { foreach ($mongodbs as $mongodb) {
@ -144,7 +112,7 @@ class CleanupStuckedResources extends Command
try { try {
$scheduled_tasks = ScheduledTask::all(); $scheduled_tasks = ScheduledTask::all();
foreach ($scheduled_tasks as $scheduled_task) { foreach ($scheduled_tasks as $scheduled_task) {
if (! $scheduled_task->service && ! $scheduled_task->application) { if (!$scheduled_task->service && !$scheduled_task->application) {
echo "Deleting stuck scheduledtask: {$scheduled_task->name}\n"; echo "Deleting stuck scheduledtask: {$scheduled_task->name}\n";
$scheduled_task->delete(); $scheduled_task->delete();
} }
@ -157,22 +125,19 @@ class CleanupStuckedResources extends Command
try { try {
$applications = Application::all(); $applications = Application::all();
foreach ($applications as $application) { foreach ($applications as $application) {
if (! data_get($application, 'environment')) { if (!data_get($application, 'environment')) {
echo 'Application without environment: '.$application->name.'\n'; echo 'Application without environment: ' . $application->name . '\n';
$application->forceDelete(); $application->forceDelete();
continue; continue;
} }
if (! $application->destination()) { if (!$application->destination()) {
echo 'Application without destination: '.$application->name.'\n'; echo 'Application without destination: ' . $application->name . '\n';
$application->forceDelete(); $application->forceDelete();
continue; continue;
} }
if (! data_get($application, 'destination.server')) { if (!data_get($application, 'destination.server')) {
echo 'Application without server: '.$application->name.'\n'; echo 'Application without server: ' . $application->name . '\n';
$application->forceDelete(); $application->forceDelete();
continue; continue;
} }
} }
@ -182,22 +147,19 @@ class CleanupStuckedResources extends Command
try { try {
$postgresqls = StandalonePostgresql::all()->where('id', '!=', 0); $postgresqls = StandalonePostgresql::all()->where('id', '!=', 0);
foreach ($postgresqls as $postgresql) { foreach ($postgresqls as $postgresql) {
if (! data_get($postgresql, 'environment')) { if (!data_get($postgresql, 'environment')) {
echo 'Postgresql without environment: '.$postgresql->name.'\n'; echo 'Postgresql without environment: ' . $postgresql->name . '\n';
$postgresql->forceDelete(); $postgresql->forceDelete();
continue; continue;
} }
if (! $postgresql->destination()) { if (!$postgresql->destination()) {
echo 'Postgresql without destination: '.$postgresql->name.'\n'; echo 'Postgresql without destination: ' . $postgresql->name . '\n';
$postgresql->forceDelete(); $postgresql->forceDelete();
continue; continue;
} }
if (! data_get($postgresql, 'destination.server')) { if (!data_get($postgresql, 'destination.server')) {
echo 'Postgresql without server: '.$postgresql->name.'\n'; echo 'Postgresql without server: ' . $postgresql->name . '\n';
$postgresql->forceDelete(); $postgresql->forceDelete();
continue; continue;
} }
} }
@ -207,22 +169,19 @@ class CleanupStuckedResources extends Command
try { try {
$redis = StandaloneRedis::all(); $redis = StandaloneRedis::all();
foreach ($redis as $redis) { foreach ($redis as $redis) {
if (! data_get($redis, 'environment')) { if (!data_get($redis, 'environment')) {
echo 'Redis without environment: '.$redis->name.'\n'; echo 'Redis without environment: ' . $redis->name . '\n';
$redis->forceDelete(); $redis->forceDelete();
continue; continue;
} }
if (! $redis->destination()) { if (!$redis->destination()) {
echo 'Redis without destination: '.$redis->name.'\n'; echo 'Redis without destination: ' . $redis->name . '\n';
$redis->forceDelete(); $redis->forceDelete();
continue; continue;
} }
if (! data_get($redis, 'destination.server')) { if (!data_get($redis, 'destination.server')) {
echo 'Redis without server: '.$redis->name.'\n'; echo 'Redis without server: ' . $redis->name . '\n';
$redis->forceDelete(); $redis->forceDelete();
continue; continue;
} }
} }
@ -233,22 +192,19 @@ class CleanupStuckedResources extends Command
try { try {
$mongodbs = StandaloneMongodb::all(); $mongodbs = StandaloneMongodb::all();
foreach ($mongodbs as $mongodb) { foreach ($mongodbs as $mongodb) {
if (! data_get($mongodb, 'environment')) { if (!data_get($mongodb, 'environment')) {
echo 'Mongodb without environment: '.$mongodb->name.'\n'; echo 'Mongodb without environment: ' . $mongodb->name . '\n';
$mongodb->forceDelete(); $mongodb->forceDelete();
continue; continue;
} }
if (! $mongodb->destination()) { if (!$mongodb->destination()) {
echo 'Mongodb without destination: '.$mongodb->name.'\n'; echo 'Mongodb without destination: ' . $mongodb->name . '\n';
$mongodb->forceDelete(); $mongodb->forceDelete();
continue; continue;
} }
if (! data_get($mongodb, 'destination.server')) { if (!data_get($mongodb, 'destination.server')) {
echo 'Mongodb without server: '.$mongodb->name.'\n'; echo 'Mongodb without server: ' . $mongodb->name . '\n';
$mongodb->forceDelete(); $mongodb->forceDelete();
continue; continue;
} }
} }
@ -259,22 +215,19 @@ class CleanupStuckedResources extends Command
try { try {
$mysqls = StandaloneMysql::all(); $mysqls = StandaloneMysql::all();
foreach ($mysqls as $mysql) { foreach ($mysqls as $mysql) {
if (! data_get($mysql, 'environment')) { if (!data_get($mysql, 'environment')) {
echo 'Mysql without environment: '.$mysql->name.'\n'; echo 'Mysql without environment: ' . $mysql->name . '\n';
$mysql->forceDelete(); $mysql->forceDelete();
continue; continue;
} }
if (! $mysql->destination()) { if (!$mysql->destination()) {
echo 'Mysql without destination: '.$mysql->name.'\n'; echo 'Mysql without destination: ' . $mysql->name . '\n';
$mysql->forceDelete(); $mysql->forceDelete();
continue; continue;
} }
if (! data_get($mysql, 'destination.server')) { if (!data_get($mysql, 'destination.server')) {
echo 'Mysql without server: '.$mysql->name.'\n'; echo 'Mysql without server: ' . $mysql->name . '\n';
$mysql->forceDelete(); $mysql->forceDelete();
continue; continue;
} }
} }
@ -285,22 +238,19 @@ class CleanupStuckedResources extends Command
try { try {
$mariadbs = StandaloneMariadb::all(); $mariadbs = StandaloneMariadb::all();
foreach ($mariadbs as $mariadb) { foreach ($mariadbs as $mariadb) {
if (! data_get($mariadb, 'environment')) { if (!data_get($mariadb, 'environment')) {
echo 'Mariadb without environment: '.$mariadb->name.'\n'; echo 'Mariadb without environment: ' . $mariadb->name . '\n';
$mariadb->forceDelete(); $mariadb->forceDelete();
continue; continue;
} }
if (! $mariadb->destination()) { if (!$mariadb->destination()) {
echo 'Mariadb without destination: '.$mariadb->name.'\n'; echo 'Mariadb without destination: ' . $mariadb->name . '\n';
$mariadb->forceDelete(); $mariadb->forceDelete();
continue; continue;
} }
if (! data_get($mariadb, 'destination.server')) { if (!data_get($mariadb, 'destination.server')) {
echo 'Mariadb without server: '.$mariadb->name.'\n'; echo 'Mariadb without server: ' . $mariadb->name . '\n';
$mariadb->forceDelete(); $mariadb->forceDelete();
continue; continue;
} }
} }
@ -311,22 +261,19 @@ class CleanupStuckedResources extends Command
try { try {
$services = Service::all(); $services = Service::all();
foreach ($services as $service) { foreach ($services as $service) {
if (! data_get($service, 'environment')) { if (!data_get($service, 'environment')) {
echo 'Service without environment: '.$service->name.'\n'; echo 'Service without environment: ' . $service->name . '\n';
$service->forceDelete(); $service->forceDelete();
continue; continue;
} }
if (! $service->destination()) { if (!$service->destination()) {
echo 'Service without destination: '.$service->name.'\n'; echo 'Service without destination: ' . $service->name . '\n';
$service->forceDelete(); $service->forceDelete();
continue; continue;
} }
if (! data_get($service, 'server')) { if (!data_get($service, 'server')) {
echo 'Service without server: '.$service->name.'\n'; echo 'Service without server: ' . $service->name . '\n';
$service->forceDelete(); $service->forceDelete();
continue; continue;
} }
} }
@ -336,10 +283,9 @@ class CleanupStuckedResources extends Command
try { try {
$serviceApplications = ServiceApplication::all(); $serviceApplications = ServiceApplication::all();
foreach ($serviceApplications as $service) { foreach ($serviceApplications as $service) {
if (! data_get($service, 'service')) { if (!data_get($service, 'service')) {
echo 'ServiceApplication without service: '.$service->name.'\n'; echo 'ServiceApplication without service: ' . $service->name . '\n';
$service->forceDelete(); $service->forceDelete();
continue; continue;
} }
} }
@ -349,10 +295,9 @@ class CleanupStuckedResources extends Command
try { try {
$serviceDatabases = ServiceDatabase::all(); $serviceDatabases = ServiceDatabase::all();
foreach ($serviceDatabases as $service) { foreach ($serviceDatabases as $service) {
if (! data_get($service, 'service')) { if (!data_get($service, 'service')) {
echo 'ServiceDatabase without service: '.$service->name.'\n'; echo 'ServiceDatabase without service: ' . $service->name . '\n';
$service->forceDelete(); $service->forceDelete();
continue; continue;
} }
} }

View File

@ -8,19 +8,17 @@ use Illuminate\Console\Command;
class CleanupUnreachableServers extends Command class CleanupUnreachableServers extends Command
{ {
protected $signature = 'cleanup:unreachable-servers'; protected $signature = 'cleanup:unreachable-servers';
protected $description = 'Cleanup Unreachable Servers (3 days)';
protected $description = 'Cleanup Unreachable Servers (7 days)';
public function handle() public function handle()
{ {
echo "Running unreachable server cleanup...\n"; echo "Running unreachable server cleanup...\n";
$servers = Server::where('unreachable_count', 3)->where('unreachable_notification_sent', true)->where('updated_at', '<', now()->subDays(7))->get(); $servers = Server::where('unreachable_count', 3)->where('unreachable_notification_sent', true)->where('updated_at', '<', now()->subDays(3))->get();
if ($servers->count() > 0) { if ($servers->count() > 0) {
foreach ($servers as $server) { foreach ($servers as $server) {
echo "Cleanup unreachable server ($server->id) with name $server->name"; echo "Cleanup unreachable server ($server->id) with name $server->name";
// send_internal_notification("Server $server->name is unreachable for 7 days. Cleaning up...");
$server->update([ $server->update([
'ip' => '1.2.3.4', 'ip' => '1.2.3.4'
]); ]);
} }
} }

View File

@ -0,0 +1,33 @@
<?php
namespace App\Console\Commands;
use App\Models\Server;
use Illuminate\Console\Command;
class Cloud extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'cloud:unused-servers';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Get Unused Servers from Cloud';
/**
* Execute the console command.
*/
public function handle()
{
Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended',true)->each(function($server){
$this->info($server->name);
});
}
}

View File

@ -1,101 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Models\Team;
use Illuminate\Console\Command;
class CloudCleanupSubscriptions extends Command
{
protected $signature = 'cloud:cleanup-subs';
protected $description = 'Cleanup subcriptions teams';
public function handle()
{
try {
if (! isCloud()) {
$this->error('This command can only be run on cloud');
return;
}
ray()->clearAll();
$this->info('Cleaning up subcriptions teams');
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
$teams = Team::all()->filter(function ($team) {
return $team->id !== 0;
})->sortBy('id');
foreach ($teams as $team) {
if ($team) {
$this->info("Checking team {$team->id}");
}
if (! data_get($team, 'subscription')) {
$this->disableServers($team);
continue;
}
// If the team has no subscription id and the invoice is paid, we need to reset the invoice paid status
if (! (data_get($team, 'subscription.stripe_subscription_id'))) {
$this->info("Resetting invoice paid status for team {$team->id} {$team->name}");
$team->subscription->update([
'stripe_invoice_paid' => false,
'stripe_trial_already_ended' => false,
'stripe_subscription_id' => null,
]);
$this->disableServers($team);
continue;
} else {
$subscription = $stripe->subscriptions->retrieve(data_get($team, 'subscription.stripe_subscription_id'), []);
$status = data_get($subscription, 'status');
if ($status === 'active' || $status === 'past_due') {
$team->subscription->update([
'stripe_invoice_paid' => true,
'stripe_trial_already_ended' => false,
]);
continue;
}
$this->info('Subscription status: '.$status);
$this->info('Subscription id: '.data_get($team, 'subscription.stripe_subscription_id'));
$confirm = $this->confirm('Do you want to cancel the subscription?', true);
if (! $confirm) {
$this->info("Skipping team {$team->id} {$team->name}");
} else {
$this->info("Cancelling subscription for team {$team->id} {$team->name}");
$team->subscription->update([
'stripe_invoice_paid' => false,
'stripe_trial_already_ended' => false,
'stripe_subscription_id' => null,
]);
$this->disableServers($team);
}
}
}
} catch (\Exception $e) {
$this->error($e->getMessage());
return;
}
}
private function disableServers(Team $team)
{
foreach ($team->servers as $server) {
if ($server->settings->is_usable === true || $server->settings->is_reachable === true || $server->ip !== '1.2.3.4') {
$this->info("Disabling server {$server->id} {$server->name}");
$server->settings()->update([
'is_usable' => false,
'is_reachable' => false,
]);
$server->update([
'ip' => '1.2.3.4',
]);
}
}
}
}

View File

@ -9,48 +9,19 @@ use Illuminate\Support\Facades\Process;
class Dev extends Command class Dev extends Command
{ {
protected $signature = 'dev {--init} {--generate-openapi}'; protected $signature = 'dev:init';
protected $description = 'Init the app in dev mode';
protected $description = 'Helper commands for development.';
public function handle() public function handle()
{
if ($this->option('init')) {
$this->init();
return;
}
if ($this->option('generate-openapi')) {
$this->generateOpenApi();
return;
}
}
public function generateOpenApi()
{
// Generate OpenAPI documentation
echo "Generating OpenAPI documentation.\n";
$process = Process::run(['/var/www/html/vendor/bin/openapi', 'app', '-o', 'openapi.yaml']);
$error = $process->errorOutput();
$error = preg_replace('/^.*an object literal,.*$/m', '', $error);
$error = preg_replace('/^\h*\v+/m', '', $error);
echo $error;
echo $process->output();
}
public function init()
{ {
// Generate APP_KEY if not exists // Generate APP_KEY if not exists
if (empty(env('APP_KEY'))) { if (empty(env('APP_KEY'))) {
echo "Generating APP_KEY.\n"; echo "Generating APP_KEY.\n";
Artisan::call('key:generate'); Artisan::call('key:generate');
} }
// Seed database if it's empty // Seed database if it's empty
$settings = InstanceSettings::find(0); $settings = InstanceSettings::find(0);
if (! $settings) { if (!$settings) {
echo "Initializing instance, seeding database.\n"; echo "Initializing instance, seeding database.\n";
Artisan::call('migrate --seed'); Artisan::call('migrate --seed');
} else { } else {

View File

@ -15,7 +15,6 @@ use App\Notifications\Application\DeploymentSuccess;
use App\Notifications\Application\StatusChanged; use App\Notifications\Application\StatusChanged;
use App\Notifications\Database\BackupFailed; use App\Notifications\Database\BackupFailed;
use App\Notifications\Database\BackupSuccess; use App\Notifications\Database\BackupSuccess;
use App\Notifications\Database\DailyBackup;
use App\Notifications\Test; use App\Notifications\Test;
use Exception; use Exception;
use Illuminate\Console\Command; use Illuminate\Console\Command;
@ -47,9 +46,7 @@ class Emails extends Command
* Execute the console command. * Execute the console command.
*/ */
private ?MailMessage $mail = null; private ?MailMessage $mail = null;
private ?string $email = null; private ?string $email = null;
public function handle() public function handle()
{ {
$type = select( $type = select(
@ -57,8 +54,6 @@ class Emails extends Command
options: [ options: [
'updates' => 'Send Update Email to all users', 'updates' => 'Send Update Email to all users',
'emails-test' => 'Test', 'emails-test' => 'Test',
'database-backup-statuses-daily' => 'Database - Backup Statuses (Daily)',
'application-deployment-success-daily' => 'Application - Deployment Success (Daily)',
'application-deployment-success' => 'Application - Deployment Success', 'application-deployment-success' => 'Application - Deployment Success',
'application-deployment-failed' => 'Application - Deployment Failed', 'application-deployment-failed' => 'Application - Deployment Failed',
'application-status-changed' => 'Application - Status Changed', 'application-status-changed' => 'Application - Status Changed',
@ -72,23 +67,18 @@ class Emails extends Command
], ],
); );
$emailsGathered = ['realusers-before-trial', 'realusers-server-lost-connection']; $emailsGathered = ['realusers-before-trial', 'realusers-server-lost-connection'];
if (isDev()) { if (!in_array($type, $emailsGathered)) {
$this->email = 'test@example.com'; $this->email = text('Email Address to send to');
} else {
if (! in_array($type, $emailsGathered)) {
$this->email = text('Email Address to send to:');
}
} }
set_transanctional_email_settings(); set_transanctional_email_settings();
$this->mail = new MailMessage; $this->mail = new MailMessage();
$this->mail->subject('Test Email'); $this->mail->subject("Test Email");
switch ($type) { switch ($type) {
case 'updates': case 'updates':
$teams = Team::all(); $teams = Team::all();
if (! $teams || $teams->isEmpty()) { if (!$teams || $teams->isEmpty()) {
echo 'No teams found.'.PHP_EOL; echo 'No teams found.' . PHP_EOL;
return; return;
} }
$emails = []; $emails = [];
@ -100,56 +90,27 @@ class Emails extends Command
} }
} }
$emails = array_unique($emails); $emails = array_unique($emails);
$this->info('Sending to '.count($emails).' emails.'); $this->info("Sending to " . count($emails) . " emails.");
foreach ($emails as $email) { foreach ($emails as $email) {
$this->info($email); $this->info($email);
} }
$confirmed = confirm('Are you sure?'); $confirmed = confirm('Are you sure?');
if ($confirmed) { if ($confirmed) {
foreach ($emails as $email) { foreach ($emails as $email) {
$this->mail = new MailMessage; $this->mail = new MailMessage();
$this->mail->subject('One-click Services, Docker Compose support'); $this->mail->subject('One-click Services, Docker Compose support');
$unsubscribeUrl = route('unsubscribe.marketing.emails', [ $unsubscribeUrl = route('unsubscribe.marketing.emails', [
'token' => encrypt($email), 'token' => encrypt($email),
]); ]);
$this->mail->view('emails.updates', ['unsubscribeUrl' => $unsubscribeUrl]); $this->mail->view('emails.updates',["unsubscribeUrl" => $unsubscribeUrl]);
$this->sendEmail($email); $this->sendEmail($email);
} }
} }
break; break;
case 'emails-test': case 'emails-test':
$this->mail = (new Test)->toMail(); $this->mail = (new Test())->toMail();
$this->sendEmail(); $this->sendEmail();
break; break;
case 'database-backup-statuses-daily':
$scheduled_backups = ScheduledDatabaseBackup::all();
$databases = collect();
foreach ($scheduled_backups as $scheduled_backup) {
$last_days_backups = $scheduled_backup->get_last_days_backup_status();
if ($last_days_backups->isEmpty()) {
continue;
}
$failed = $last_days_backups->where('status', 'failed');
$database = $scheduled_backup->database;
$databases->put($database->name, [
'failed_count' => $failed->count(),
]);
}
$this->mail = (new DailyBackup($databases))->toMail();
$this->sendEmail();
break;
case 'application-deployment-success-daily':
$applications = Application::all();
foreach ($applications as $application) {
$deployments = $application->get_last_days_deployments();
ray($deployments);
if ($deployments->isEmpty()) {
continue;
}
$this->mail = (new DeploymentSuccess($application, 'test'))->toMail();
$this->sendEmail();
}
break;
case 'application-deployment-success': case 'application-deployment-success':
$application = Application::all()->first(); $application = Application::all()->first();
$this->mail = (new DeploymentSuccess($application, 'test'))->toMail(); $this->mail = (new DeploymentSuccess($application, 'test'))->toMail();
@ -158,7 +119,7 @@ class Emails extends Command
case 'application-deployment-failed': case 'application-deployment-failed':
$application = Application::all()->first(); $application = Application::all()->first();
$preview = ApplicationPreview::all()->first(); $preview = ApplicationPreview::all()->first();
if (! $preview) { if (!$preview) {
$preview = ApplicationPreview::create([ $preview = ApplicationPreview::create([
'application_id' => $application->id, 'application_id' => $application->id,
'pull_request_id' => 1, 'pull_request_id' => 1,
@ -179,7 +140,7 @@ class Emails extends Command
case 'backup-failed': case 'backup-failed':
$backup = ScheduledDatabaseBackup::all()->first(); $backup = ScheduledDatabaseBackup::all()->first();
$db = StandalonePostgresql::all()->first(); $db = StandalonePostgresql::all()->first();
if (! $backup) { if (!$backup) {
$backup = ScheduledDatabaseBackup::create([ $backup = ScheduledDatabaseBackup::create([
'enabled' => true, 'enabled' => true,
'frequency' => 'daily', 'frequency' => 'daily',
@ -189,14 +150,14 @@ class Emails extends Command
'team_id' => 0, 'team_id' => 0,
]); ]);
} }
$output = 'Because of an error, the backup of the database '.$db->name.' failed.'; $output = 'Because of an error, the backup of the database ' . $db->name . ' failed.';
$this->mail = (new BackupFailed($backup, $db, $output))->toMail(); $this->mail = (new BackupFailed($backup, $db, $output))->toMail();
$this->sendEmail(); $this->sendEmail();
break; break;
case 'backup-success': case 'backup-success':
$backup = ScheduledDatabaseBackup::all()->first(); $backup = ScheduledDatabaseBackup::all()->first();
$db = StandalonePostgresql::all()->first(); $db = StandalonePostgresql::all()->first();
if (! $backup) { if (!$backup) {
$backup = ScheduledDatabaseBackup::create([ $backup = ScheduledDatabaseBackup::create([
'enabled' => true, 'enabled' => true,
'frequency' => 'daily', 'frequency' => 'daily',
@ -224,7 +185,7 @@ class Emails extends Command
// $this->sendEmail(); // $this->sendEmail();
// break; // break;
case 'waitlist-invitation-link': case 'waitlist-invitation-link':
$this->mail = new MailMessage; $this->mail = new MailMessage();
$this->mail->view('emails.waitlist-invitation', [ $this->mail->view('emails.waitlist-invitation', [
'loginLink' => 'https://coolify.io', 'loginLink' => 'https://coolify.io',
]); ]);
@ -241,13 +202,12 @@ class Emails extends Command
break; break;
case 'realusers-before-trial': case 'realusers-before-trial':
$this->mail = new MailMessage; $this->mail = new MailMessage();
$this->mail->view('emails.before-trial-conversion'); $this->mail->view('emails.before-trial-conversion');
$this->mail->subject('Trial period has been added for all subscription plans.'); $this->mail->subject('Trial period has been added for all subscription plans.');
$teams = Team::doesntHave('subscription')->where('id', '!=', 0)->get(); $teams = Team::doesntHave('subscription')->where('id', '!=', 0)->get();
if (! $teams || $teams->isEmpty()) { if (!$teams || $teams->isEmpty()) {
echo 'No teams found.'.PHP_EOL; echo 'No teams found.' . PHP_EOL;
return; return;
} }
$emails = []; $emails = [];
@ -259,7 +219,7 @@ class Emails extends Command
} }
} }
$emails = array_unique($emails); $emails = array_unique($emails);
$this->info('Sending to '.count($emails).' emails.'); $this->info("Sending to " . count($emails) . " emails.");
foreach ($emails as $email) { foreach ($emails as $email) {
$this->info($email); $this->info($email);
} }
@ -273,7 +233,7 @@ class Emails extends Command
case 'realusers-server-lost-connection': case 'realusers-server-lost-connection':
$serverId = text('Server Id'); $serverId = text('Server Id');
$server = Server::find($serverId); $server = Server::find($serverId);
if (! $server) { if (!$server) {
throw new Exception('Server not found'); throw new Exception('Server not found');
} }
$admins = []; $admins = [];
@ -283,23 +243,22 @@ class Emails extends Command
$admins[] = $member->email; $admins[] = $member->email;
} }
} }
$this->info('Sending to '.count($admins).' admins.'); $this->info('Sending to ' . count($admins) . ' admins.');
foreach ($admins as $admin) { foreach ($admins as $admin) {
$this->info($admin); $this->info($admin);
} }
$this->mail = new MailMessage; $this->mail = new MailMessage();
$this->mail->view('emails.server-lost-connection', [ $this->mail->view('emails.server-lost-connection', [
'name' => $server->name, 'name' => $server->name,
]); ]);
$this->mail->subject('Action required: Server '.$server->name.' lost connection.'); $this->mail->subject('Action required: Server ' . $server->name . ' lost connection.');
foreach ($admins as $email) { foreach ($admins as $email) {
$this->sendEmail($email); $this->sendEmail($email);
} }
break; break;
} }
} }
private function sendEmail(string $email = null)
private function sendEmail(?string $email = null)
{ {
if ($email) { if ($email) {
$this->email = $email; $this->email = $email;
@ -310,7 +269,7 @@ class Emails extends Command
fn (Message $message) => $message fn (Message $message) => $message
->to($this->email) ->to($this->email)
->subject($this->mail->subject) ->subject($this->mail->subject)
->html((string) $this->mail->render()) ->html((string)$this->mail->render())
); );
$this->info("Email sent to $this->email successfully. 📧"); $this->info("Email sent to $this->email successfully. 📧");
} }

View File

@ -1,23 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class Horizon extends Command
{
protected $signature = 'start:horizon';
protected $description = 'Start Horizon';
public function handle()
{
if (config('coolify.is_horizon_enabled')) {
$this->info('Horizon is enabled. Starting.');
$this->call('horizon');
exit(0);
} else {
exit(0);
}
}
}

View File

@ -2,11 +2,9 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Actions\Server\StopSentinel;
use App\Enums\ApplicationDeploymentStatus; use App\Enums\ApplicationDeploymentStatus;
use App\Jobs\CleanupHelperContainersJob; use App\Jobs\CleanupHelperContainersJob;
use App\Models\ApplicationDeploymentQueue; use App\Models\ApplicationDeploymentQueue;
use App\Models\Environment;
use App\Models\InstanceSettings; use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup; use App\Models\ScheduledDatabaseBackup;
use App\Models\Server; use App\Models\Server;
@ -17,31 +15,16 @@ use Illuminate\Support\Facades\Http;
class Init extends Command class Init extends Command
{ {
protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments}'; protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments}';
protected $description = 'Cleanup instance related stuffs'; protected $description = 'Cleanup instance related stuffs';
public function handle() public function handle()
{ {
$this->alive(); $this->alive();
get_public_ips();
if (version_compare('4.0.0-beta.312', config('version'), '<=')) {
$servers = Server::all();
foreach ($servers as $server) {
$server->settings->update(['is_metrics_enabled' => false]);
if ($server->isFunctional()) {
StopSentinel::dispatch($server);
}
}
}
$full_cleanup = $this->option('full-cleanup'); $full_cleanup = $this->option('full-cleanup');
$cleanup_deployments = $this->option('cleanup-deployments'); $cleanup_deployments = $this->option('cleanup-deployments');
$this->replace_slash_in_environment_name();
if ($cleanup_deployments) { if ($cleanup_deployments) {
echo "Running cleanup deployments.\n"; echo "Running cleanup deployments.\n";
$this->cleanup_in_progress_application_deployments(); $this->cleanup_in_progress_application_deployments();
return; return;
} }
if ($full_cleanup) { if ($full_cleanup) {
@ -51,30 +34,25 @@ class Init extends Command
$this->cleanup_stucked_helper_containers(); $this->cleanup_stucked_helper_containers();
$this->call('cleanup:queue'); $this->call('cleanup:queue');
$this->call('cleanup:stucked-resources'); $this->call('cleanup:stucked-resources');
if (! isCloud()) { try {
try { setup_dynamic_configuration();
$server = Server::find(0)->first(); } catch (\Throwable $e) {
$server->setupDynamicProxyConfiguration(); echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
} catch (\Throwable $e) {
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
}
} }
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();
if (! is_null(env('AUTOUPDATE', null))) { if (!is_null(env('AUTOUPDATE', null))) {
if (env('AUTOUPDATE') == true) { if (env('AUTOUPDATE') == true) {
$settings->update(['is_auto_update_enabled' => true]); $settings->update(['is_auto_update_enabled' => true]);
} else { } else {
$settings->update(['is_auto_update_enabled' => false]); $settings->update(['is_auto_update_enabled' => false]);
} }
} }
return; return;
} }
$this->cleanup_stucked_helper_containers(); $this->cleanup_stucked_helper_containers();
$this->call('cleanup:stucked-resources'); $this->call('cleanup:stucked-resources');
} }
private function restore_coolify_db_backup() private function restore_coolify_db_backup()
{ {
try { try {
@ -83,7 +61,7 @@ class Init extends Command
echo "Restoring Last Hour Cloud db backup\n"; echo "Restoring Last Hour Cloud db backup\n";
$database->restore(); $database->restore();
$scheduledBackup = ScheduledDatabaseBackup::find(0); $scheduledBackup = ScheduledDatabaseBackup::find(0);
if (! $scheduledBackup) { if (!$scheduledBackup) {
ScheduledDatabaseBackup::create([ ScheduledDatabaseBackup::create([
'id' => 0, 'id' => 0,
'enabled' => true, 'enabled' => true,
@ -99,7 +77,6 @@ class Init extends Command
echo "Error in restoring Last Hour Cloud db backup: {$e->getMessage()}\n"; echo "Error in restoring Last Hour Cloud db backup: {$e->getMessage()}\n";
} }
} }
private function cleanup_stucked_helper_containers() private function cleanup_stucked_helper_containers()
{ {
$servers = Server::all(); $servers = Server::all();
@ -109,7 +86,6 @@ class Init extends Command
} }
} }
} }
private function alive() private function alive()
{ {
$id = config('app.id'); $id = config('app.id');
@ -118,7 +94,6 @@ class Init extends Command
$do_not_track = data_get($settings, 'do_not_track'); $do_not_track = data_get($settings, 'do_not_track');
if ($do_not_track == true) { if ($do_not_track == true) {
echo "Skipping alive as do_not_track is enabled\n"; echo "Skipping alive as do_not_track is enabled\n";
return; return;
} }
try { try {
@ -164,15 +139,4 @@ class Init extends Command
echo "Error: {$e->getMessage()}\n"; echo "Error: {$e->getMessage()}\n";
} }
} }
private function replace_slash_in_environment_name()
{
$environments = Environment::all();
foreach ($environments as $environment) {
if (str_contains($environment->name, '/')) {
$environment->name = str_replace('/', '-', $environment->name);
$environment->save();
}
}
}
} }

View File

@ -3,7 +3,6 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use function Termwind\ask; use function Termwind\ask;
use function Termwind\render; use function Termwind\render;
use function Termwind\style; use function Termwind\style;
@ -33,7 +32,6 @@ class NotifyDemo extends Command
if (blank($channel)) { if (blank($channel)) {
$this->showHelp(); $this->showHelp();
return; return;
} }

View File

@ -35,7 +35,6 @@ class RootChangeEmail extends Command
$this->info('Root user\'s email updated successfully.'); $this->info('Root user\'s email updated successfully.');
} catch (\Exception $e) { } catch (\Exception $e) {
$this->error('Failed to update root user\'s email.'); $this->error('Failed to update root user\'s email.');
return; return;
} }
} }

View File

@ -29,12 +29,12 @@ class RootResetPassword extends Command
*/ */
public function handle() public function handle()
{ {
//
$this->info('You are about to reset the root password.'); $this->info('You are about to reset the root password.');
$password = password('Give me a new password for root user: '); $password = password('Give me a new password for root user: ');
$passwordAgain = password('Again'); $passwordAgain = password('Again');
if ($password != $passwordAgain) { if ($password != $passwordAgain) {
$this->error('Passwords do not match.'); $this->error('Passwords do not match.');
return; return;
} }
$this->info('Updating root password...'); $this->info('Updating root password...');
@ -43,7 +43,6 @@ class RootResetPassword extends Command
$this->info('Root password updated successfully.'); $this->info('Root password updated successfully.');
} catch (\Exception $e) { } catch (\Exception $e) {
$this->error('Failed to update root password.'); $this->error('Failed to update root password.');
return; return;
} }
} }

View File

@ -1,23 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class Scheduler extends Command
{
protected $signature = 'start:scheduler';
protected $description = 'Start Scheduler';
public function handle()
{
if (config('coolify.is_scheduler_enabled')) {
$this->info('Scheduler is enabled. Starting.');
$this->call('schedule:work');
exit(0);
} else {
exit(0);
}
}
}

View File

@ -48,13 +48,11 @@ class ServicesDelete extends Command
$this->deleteServer(); $this->deleteServer();
} }
} }
private function deleteServer() private function deleteServer()
{ {
$servers = Server::all(); $servers = Server::all();
if ($servers->count() === 0) { if ($servers->count() === 0) {
$this->error('There are no applications to delete.'); $this->error('There are no applications to delete.');
return; return;
} }
$serversToDelete = multiselect( $serversToDelete = multiselect(
@ -66,21 +64,19 @@ class ServicesDelete extends Command
$toDelete = $servers->where('id', $server)->first(); $toDelete = $servers->where('id', $server)->first();
if ($toDelete) { if ($toDelete) {
$this->info($toDelete); $this->info($toDelete);
$confirmed = confirm('Are you sure you want to delete all selected resources?'); $confirmed = confirm("Are you sure you want to delete all selected resources?");
if (! $confirmed) { if (!$confirmed) {
break; break;
} }
$toDelete->delete(); $toDelete->delete();
} }
} }
} }
private function deleteApplication() private function deleteApplication()
{ {
$applications = Application::all(); $applications = Application::all();
if ($applications->count() === 0) { if ($applications->count() === 0) {
$this->error('There are no applications to delete.'); $this->error('There are no applications to delete.');
return; return;
} }
$applicationsToDelete = multiselect( $applicationsToDelete = multiselect(
@ -92,21 +88,19 @@ class ServicesDelete extends Command
$toDelete = $applications->where('id', $application)->first(); $toDelete = $applications->where('id', $application)->first();
if ($toDelete) { if ($toDelete) {
$this->info($toDelete); $this->info($toDelete);
$confirmed = confirm('Are you sure you want to delete all selected resources? '); $confirmed = confirm("Are you sure you want to delete all selected resources? ");
if (! $confirmed) { if (!$confirmed) {
break; break;
} }
DeleteResourceJob::dispatch($toDelete); DeleteResourceJob::dispatch($toDelete);
} }
} }
} }
private function deleteDatabase() private function deleteDatabase()
{ {
$databases = StandalonePostgresql::all(); $databases = StandalonePostgresql::all();
if ($databases->count() === 0) { if ($databases->count() === 0) {
$this->error('There are no databases to delete.'); $this->error('There are no databases to delete.');
return; return;
} }
$databasesToDelete = multiselect( $databasesToDelete = multiselect(
@ -118,21 +112,19 @@ class ServicesDelete extends Command
$toDelete = $databases->where('id', $database)->first(); $toDelete = $databases->where('id', $database)->first();
if ($toDelete) { if ($toDelete) {
$this->info($toDelete); $this->info($toDelete);
$confirmed = confirm('Are you sure you want to delete all selected resources?'); $confirmed = confirm("Are you sure you want to delete all selected resources?");
if (! $confirmed) { if (!$confirmed) {
return; return;
} }
DeleteResourceJob::dispatch($toDelete); DeleteResourceJob::dispatch($toDelete);
} }
} }
} }
private function deleteService() private function deleteService()
{ {
$services = Service::all(); $services = Service::all();
if ($services->count() === 0) { if ($services->count() === 0) {
$this->error('There are no services to delete.'); $this->error('There are no services to delete.');
return; return;
} }
$servicesToDelete = multiselect( $servicesToDelete = multiselect(
@ -144,8 +136,8 @@ class ServicesDelete extends Command
$toDelete = $services->where('id', $service)->first(); $toDelete = $services->where('id', $service)->first();
if ($toDelete) { if ($toDelete) {
$this->info($toDelete); $this->info($toDelete);
$confirmed = confirm('Are you sure you want to delete all selected resources?'); $confirmed = confirm("Are you sure you want to delete all selected resources?");
if (! $confirmed) { if (!$confirmed) {
return; return;
} }
DeleteResourceJob::dispatch($toDelete); DeleteResourceJob::dispatch($toDelete);

View File

@ -26,6 +26,7 @@ class ServicesGenerate extends Command
*/ */
public function handle() public function handle()
{ {
// ray()->clearAll();
$files = array_diff(scandir(base_path('templates/compose')), ['.', '..']); $files = array_diff(scandir(base_path('templates/compose')), ['.', '..']);
$files = array_filter($files, function ($file) { $files = array_filter($files, function ($file) {
return strpos($file, '.yaml') !== false; return strpos($file, '.yaml') !== false;
@ -39,7 +40,7 @@ class ServicesGenerate extends Command
$serviceTemplatesJson[$name] = $parsed; $serviceTemplatesJson[$name] = $parsed;
} }
} }
$serviceTemplatesJson = json_encode($serviceTemplatesJson); $serviceTemplatesJson = json_encode($serviceTemplatesJson, JSON_PRETTY_PRINT);
file_put_contents(base_path('templates/service-templates.json'), $serviceTemplatesJson); file_put_contents(base_path('templates/service-templates.json'), $serviceTemplatesJson);
} }
@ -50,20 +51,18 @@ class ServicesGenerate extends Command
// $this->info($content); // $this->info($content);
$ignore = collect(preg_grep('/^# ignore:/', explode("\n", $content)))->values(); $ignore = collect(preg_grep('/^# ignore:/', explode("\n", $content)))->values();
if ($ignore->count() > 0) { if ($ignore->count() > 0) {
$ignore = (bool) str($ignore[0])->after('# ignore:')->trim()->value(); $ignore = (bool)str($ignore[0])->after('# ignore:')->trim()->value();
} else { } else {
$ignore = false; $ignore = false;
} }
if ($ignore) { if ($ignore) {
$this->info("Ignoring $file"); $this->info("Ignoring $file");
return; return;
} }
$this->info("Processing $file"); $this->info("Processing $file");
$documentation = collect(preg_grep('/^# documentation:/', explode("\n", $content)))->values(); $documentation = collect(preg_grep('/^# documentation:/', explode("\n", $content)))->values();
if ($documentation->count() > 0) { if ($documentation->count() > 0) {
$documentation = str($documentation[0])->after('# documentation:')->trim()->value(); $documentation = str($documentation[0])->after('# documentation:')->trim()->value();
$documentation = str($documentation)->append('?utm_source=coolify.io');
} else { } else {
$documentation = 'https://coolify.io/docs'; $documentation = 'https://coolify.io/docs';
} }
@ -74,18 +73,6 @@ class ServicesGenerate extends Command
} else { } else {
$slogan = str($file)->headline()->value(); $slogan = str($file)->headline()->value();
} }
$logo = collect(preg_grep('/^# logo:/', explode("\n", $content)))->values();
if ($logo->count() > 0) {
$logo = str($logo[0])->after('# logo:')->trim()->value();
} else {
$logo = 'svgs/unknown.svg';
}
$minversion = collect(preg_grep('/^# minversion:/', explode("\n", $content)))->values();
if ($minversion->count() > 0) {
$minversion = str($minversion[0])->after('# minversion:')->trim()->value();
} else {
$minversion = '0.0.0';
}
$env_file = collect(preg_grep('/^# env_file:/', explode("\n", $content)))->values(); $env_file = collect(preg_grep('/^# env_file:/', explode("\n", $content)))->values();
if ($env_file->count() > 0) { if ($env_file->count() > 0) {
$env_file = str($env_file[0])->after('# env_file:')->trim()->value(); $env_file = str($env_file[0])->after('# env_file:')->trim()->value();
@ -101,12 +88,6 @@ class ServicesGenerate extends Command
} else { } else {
$tags = null; $tags = null;
} }
$port = collect(preg_grep('/^# port:/', explode("\n", $content)))->values();
if ($port->count() > 0) {
$port = str($port[0])->after('# port:')->trim()->value();
} else {
$port = null;
}
$json = Yaml::parse($content); $json = Yaml::parse($content);
$yaml = base64_encode(Yaml::dump($json, 10, 2)); $yaml = base64_encode(Yaml::dump($json, 10, 2));
$payload = [ $payload = [
@ -115,18 +96,12 @@ class ServicesGenerate extends Command
'slogan' => $slogan, 'slogan' => $slogan,
'compose' => $yaml, 'compose' => $yaml,
'tags' => $tags, 'tags' => $tags,
'logo' => $logo,
'minversion' => $minversion,
]; ];
if ($port) {
$payload['port'] = $port;
}
if ($env_file) { if ($env_file) {
$env_file_content = file_get_contents(base_path("templates/compose/$env_file")); $env_file_content = file_get_contents(base_path("templates/compose/$env_file"));
$env_file_base64 = base64_encode($env_file_content); $env_file_base64 = base64_encode($env_file_content);
$payload['envs'] = $env_file_base64; $payload['envs'] = $env_file_base64;
} }
return $payload; return $payload;
} }
} }

View File

@ -33,31 +33,30 @@ class SyncBunny extends Command
$that = $this; $that = $this;
$only_template = $this->option('templates'); $only_template = $this->option('templates');
$only_version = $this->option('release'); $only_version = $this->option('release');
$bunny_cdn = 'https://cdn.coollabs.io'; $bunny_cdn = "https://cdn.coollabs.io";
$bunny_cdn_path = 'coolify'; $bunny_cdn_path = "coolify";
$bunny_cdn_storage_name = 'coolcdn'; $bunny_cdn_storage_name = "coolcdn";
$parent_dir = realpath(dirname(__FILE__).'/../../..'); $parent_dir = realpath(dirname(__FILE__) . '/../../..');
$compose_file = 'docker-compose.yml'; $compose_file = "docker-compose.yml";
$compose_file_prod = 'docker-compose.prod.yml'; $compose_file_prod = "docker-compose.prod.yml";
$install_script = 'install.sh'; $install_script = "install.sh";
$upgrade_script = 'upgrade.sh'; $upgrade_script = "upgrade.sh";
$production_env = '.env.production'; $production_env = ".env.production";
$service_template = 'service-templates.json'; $service_template = "service-templates.json";
$versions = 'versions.json'; $versions = "versions.json";
PendingRequest::macro('storage', function ($fileName) use ($that) { PendingRequest::macro('storage', function ($fileName) use ($that) {
$headers = [ $headers = [
'AccessKey' => env('BUNNY_STORAGE_API_KEY'), 'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
'Accept' => 'application/json', 'Accept' => 'application/json',
'Content-Type' => 'application/octet-stream', 'Content-Type' => 'application/octet-stream'
]; ];
$fileStream = fopen($fileName, 'r'); $fileStream = fopen($fileName, "r");
$file = fread($fileStream, filesize($fileName)); $file = fread($fileStream, filesize($fileName));
$that->info('Uploading: '.$fileName); $that->info('Uploading: ' . $fileName);
return PendingRequest::baseUrl('https://storage.bunnycdn.com')->withHeaders($headers)->withBody($file)->throw(); return PendingRequest::baseUrl('https://storage.bunnycdn.com')->withHeaders($headers)->withBody($file)->throw();
}); });
PendingRequest::macro('purge', function ($url) use ($that) { PendingRequest::macro('purge', function ($url) use ($that) {
@ -65,21 +64,20 @@ class SyncBunny extends Command
'AccessKey' => env('BUNNY_API_KEY'), 'AccessKey' => env('BUNNY_API_KEY'),
'Accept' => 'application/json', 'Accept' => 'application/json',
]; ];
$that->info('Purging: '.$url); $that->info('Purging: ' . $url);
return PendingRequest::withHeaders($headers)->get('https://api.bunny.net/purge', [ return PendingRequest::withHeaders($headers)->get('https://api.bunny.net/purge', [
'url' => $url, "url" => $url,
'async' => false, "async" => false
]); ]);
}); });
try { try {
if (! $only_template && ! $only_version) { if (!$only_template && !$only_version) {
$this->info('About to sync files (docker-compose.prod.yaml, upgrade.sh, install.sh, etc) to BunnyCDN.'); $this->info('About to sync files (docker-compose.prod.yaml, upgrade.sh, install.sh, etc) to BunnyCDN.');
} }
if ($only_template) { if ($only_template) {
$this->info('About to sync service-templates.json to BunnyCDN.'); $this->info('About to sync service-templates.json to BunnyCDN.');
$confirmed = confirm('Are you sure you want to sync?'); $confirmed = confirm("Are you sure you want to sync?");
if (! $confirmed) { if (!$confirmed) {
return; return;
} }
Http::pool(fn (Pool $pool) => [ Http::pool(fn (Pool $pool) => [
@ -87,16 +85,15 @@ class SyncBunny extends Command
$pool->purge("$bunny_cdn/$bunny_cdn_path/$service_template"), $pool->purge("$bunny_cdn/$bunny_cdn_path/$service_template"),
]); ]);
$this->info('Service template uploaded & purged...'); $this->info('Service template uploaded & purged...');
return; return;
} elseif ($only_version) { } else if ($only_version) {
$this->info('About to sync versions.json to BunnyCDN.'); $this->info('About to sync versions.json to BunnyCDN.');
$file = file_get_contents("$parent_dir/$versions"); $file = file_get_contents("$parent_dir/$versions");
$json = json_decode($file, true); $json = json_decode($file, true);
$actual_version = data_get($json, 'coolify.v4.version'); $actual_version = data_get($json, 'coolify.v4.version');
$confirmed = confirm("Are you sure you want to sync to {$actual_version}?"); $confirmed = confirm("Are you sure you want to sync to {$actual_version}?");
if (! $confirmed) { if (!$confirmed) {
return; return;
} }
Http::pool(fn (Pool $pool) => [ Http::pool(fn (Pool $pool) => [
@ -104,10 +101,10 @@ class SyncBunny extends Command
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"), $pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
]); ]);
$this->info('versions.json uploaded & purged...'); $this->info('versions.json uploaded & purged...');
return; return;
} }
Http::pool(fn (Pool $pool) => [ Http::pool(fn (Pool $pool) => [
$pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"), $pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
$pool->storage(fileName: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"), $pool->storage(fileName: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
@ -122,9 +119,9 @@ class SyncBunny extends Command
$pool->purge("$bunny_cdn/$bunny_cdn_path/$upgrade_script"), $pool->purge("$bunny_cdn/$bunny_cdn_path/$upgrade_script"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$install_script"), $pool->purge("$bunny_cdn/$bunny_cdn_path/$install_script"),
]); ]);
$this->info('All files uploaded & purged...'); $this->info("All files uploaded & purged...");
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->error('Error: '.$e->getMessage()); $this->error("Error: " . $e->getMessage());
} }
} }
} }

View File

@ -13,9 +13,7 @@ use Illuminate\Support\Str;
class WaitlistInvite extends Command class WaitlistInvite extends Command
{ {
public Waitlist|User|null $next_patient = null; public Waitlist|User|null $next_patient = null;
public string|null $password = null;
public ?string $password = null;
/** /**
* The name and signature of the console command. * The name and signature of the console command.
* *
@ -40,9 +38,7 @@ class WaitlistInvite extends Command
$this->main(); $this->main();
} }
} }
private function main() {
private function main()
{
if ($this->argument('email')) { if ($this->argument('email')) {
if ($this->option('only-email')) { if ($this->option('only-email')) {
$this->next_patient = User::whereEmail($this->argument('email'))->first(); $this->next_patient = User::whereEmail($this->argument('email'))->first();
@ -54,9 +50,8 @@ class WaitlistInvite extends Command
} else { } else {
$this->next_patient = Waitlist::where('email', $this->argument('email'))->first(); $this->next_patient = Waitlist::where('email', $this->argument('email'))->first();
} }
if (! $this->next_patient) { if (!$this->next_patient) {
$this->error("{$this->argument('email')} not found in the waitlist."); $this->error("{$this->argument('email')} not found in the waitlist.");
return; return;
} }
} else { } else {
@ -65,7 +60,6 @@ class WaitlistInvite extends Command
if ($this->next_patient) { if ($this->next_patient) {
if ($this->option('only-email')) { if ($this->option('only-email')) {
$this->send_email(); $this->send_email();
return; return;
} }
$this->register_user(); $this->register_user();
@ -75,14 +69,13 @@ class WaitlistInvite extends Command
$this->info('No verified user found in the waitlist. 👀'); $this->info('No verified user found in the waitlist. 👀');
} }
} }
private function register_user() private function register_user()
{ {
$already_registered = User::whereEmail($this->next_patient->email)->first(); $already_registered = User::whereEmail($this->next_patient->email)->first();
if (! $already_registered) { if (!$already_registered) {
$this->password = Str::password(); $this->password = Str::password();
User::create([ User::create([
'name' => str($this->next_patient->email)->before('@'), 'name' => Str::of($this->next_patient->email)->before('@'),
'email' => $this->next_patient->email, 'email' => $this->next_patient->email,
'password' => Hash::make($this->password), 'password' => Hash::make($this->password),
'force_password_reset' => true, 'force_password_reset' => true,
@ -92,23 +85,21 @@ class WaitlistInvite extends Command
throw new \Exception('User already registered'); throw new \Exception('User already registered');
} }
} }
private function remove_from_waitlist() private function remove_from_waitlist()
{ {
$this->next_patient->delete(); $this->next_patient->delete();
$this->info('User removed from waitlist successfully.'); $this->info("User removed from waitlist successfully.");
} }
private function send_email() private function send_email()
{ {
$token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password"); $token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password");
$loginLink = route('auth.link', ['token' => $token]); $loginLink = route('auth.link', ['token' => $token]);
$mail = new MailMessage; $mail = new MailMessage();
$mail->view('emails.waitlist-invitation', [ $mail->view('emails.waitlist-invitation', [
'loginLink' => $loginLink, 'loginLink' => $loginLink,
]); ]);
$mail->subject('Congratulations! You are invited to join Coolify Cloud.'); $mail->subject('Congratulations! You are invited to join Coolify Cloud.');
send_user_an_email($mail, $this->next_patient->email); send_user_an_email($mail, $this->next_patient->email);
$this->info('Email sent successfully. 📧'); $this->info("Email sent successfully. 📧");
} }
} }

View File

@ -4,15 +4,14 @@ namespace App\Console;
use App\Jobs\CheckLogDrainContainerJob; use App\Jobs\CheckLogDrainContainerJob;
use App\Jobs\CleanupInstanceStuffsJob; use App\Jobs\CleanupInstanceStuffsJob;
use App\Jobs\ContainerStatusJob; use App\Jobs\ComplexContainerStatusJob;
use App\Jobs\DatabaseBackupJob; use App\Jobs\DatabaseBackupJob;
use App\Jobs\DockerCleanupJob;
use App\Jobs\PullCoolifyImageJob;
use App\Jobs\PullHelperImageJob;
use App\Jobs\PullSentinelImageJob;
use App\Jobs\PullTemplatesFromCDN;
use App\Jobs\ScheduledTaskJob; use App\Jobs\ScheduledTaskJob;
use App\Jobs\InstanceAutoUpdateJob;
use App\Jobs\ContainerStatusJob;
use App\Jobs\PullHelperImageJob;
use App\Jobs\ServerStatusJob; use App\Jobs\ServerStatusJob;
use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup; use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledTask; use App\Models\ScheduledTask;
use App\Models\Server; use App\Models\Server;
@ -22,62 +21,51 @@ use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel class Kernel extends ConsoleKernel
{ {
private $all_servers;
protected function schedule(Schedule $schedule): void protected function schedule(Schedule $schedule): void
{ {
$this->all_servers = Server::all();
if (isDev()) { if (isDev()) {
// Instance Jobs // Instance Jobs
$schedule->command('horizon:snapshot')->everyMinute(); $schedule->command('horizon:snapshot')->everyMinute();
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer(); $schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
$schedule->job(new PullTemplatesFromCDN)->everyTwoHours()->onOneServer(); // $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
// Server Jobs // Server Jobs
$this->check_scheduled_backups($schedule); $this->check_scheduled_backups($schedule);
$this->check_resources($schedule); $this->check_resources($schedule);
$this->check_scheduled_backups($schedule); $this->check_scheduled_backups($schedule);
$this->pull_helper_image($schedule);
$this->check_scheduled_tasks($schedule); $this->check_scheduled_tasks($schedule);
$schedule->command('uploads:clear')->everyTwoMinutes();
} else { } else {
// Instance Jobs // Instance Jobs
$schedule->command('horizon:snapshot')->everyFiveMinutes(); $schedule->command('horizon:snapshot')->everyFiveMinutes();
$schedule->command('cleanup:unreachable-servers')->daily(); $schedule->command('cleanup:unreachable-servers')->daily();
$schedule->job(new PullCoolifyImageJob)->everyTenMinutes()->onOneServer();
$schedule->job(new PullTemplatesFromCDN)->everyThirtyMinutes()->onOneServer();
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer(); $schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer(); // $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
// Server Jobs // Server Jobs
$this->instance_auto_update($schedule);
$this->check_scheduled_backups($schedule); $this->check_scheduled_backups($schedule);
$this->check_resources($schedule); $this->check_resources($schedule);
$this->pull_images($schedule); $this->pull_helper_image($schedule);
$this->check_scheduled_tasks($schedule); $this->check_scheduled_tasks($schedule);
$schedule->command('cleanup:database --yes')->daily();
$schedule->command('uploads:clear')->everyTwoMinutes();
} }
} }
private function pull_helper_image($schedule)
private function pull_images($schedule)
{ {
$servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4'); $servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
foreach ($servers as $server) { foreach ($servers as $server) {
if ($server->isSentinelEnabled()) { $schedule->job(new PullHelperImageJob($server))->everyTenMinutes()->onOneServer();
$schedule->job(new PullSentinelImageJob($server))->everyFiveMinutes()->onOneServer();
}
$schedule->job(new PullHelperImageJob($server))->everyFiveMinutes()->onOneServer();
} }
} }
private function check_resources($schedule) private function check_resources($schedule)
{ {
if (isCloud()) { if (isCloud()) {
$servers = $this->all_servers->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false)->where('ip', '!=', '1.2.3.4'); $servers = Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false)->where('ip', '!=', '1.2.3.4');
$own = Team::find(0)->servers; $own = Team::find(0)->servers;
$servers = $servers->merge($own); $servers = $servers->merge($own);
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false); $containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
} else { } else {
$servers = $this->all_servers->where('ip', '!=', '1.2.3.4'); $servers = Server::all()->where('ip', '!=', '1.2.3.4');
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false); $containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
} }
foreach ($containerServers as $server) { foreach ($containerServers as $server) {
@ -88,10 +76,18 @@ class Kernel extends ConsoleKernel
} }
foreach ($servers as $server) { foreach ($servers as $server) {
$schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer(); $schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer();
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->onOneServer();
} }
} }
private function instance_auto_update($schedule)
{
if (isDev()) {
return;
}
$settings = InstanceSettings::get();
if ($settings->is_auto_update_enabled) {
$schedule->job(new InstanceAutoUpdateJob)->everyTenMinutes()->onOneServer();
}
}
private function check_scheduled_backups($schedule) private function check_scheduled_backups($schedule)
{ {
$scheduled_backups = ScheduledDatabaseBackup::all(); $scheduled_backups = ScheduledDatabaseBackup::all();
@ -99,13 +95,12 @@ class Kernel extends ConsoleKernel
return; return;
} }
foreach ($scheduled_backups as $scheduled_backup) { foreach ($scheduled_backups as $scheduled_backup) {
if (! $scheduled_backup->enabled) { if (!$scheduled_backup->enabled) {
continue; continue;
} }
if (is_null(data_get($scheduled_backup, 'database'))) { if (is_null(data_get($scheduled_backup, 'database'))) {
ray('database not found'); ray('database not found');
$scheduled_backup->delete(); $scheduled_backup->delete();
continue; continue;
} }
@ -125,28 +120,15 @@ class Kernel extends ConsoleKernel
return; return;
} }
foreach ($scheduled_tasks as $scheduled_task) { foreach ($scheduled_tasks as $scheduled_task) {
if ($scheduled_task->enabled === false) {
continue;
}
$service = $scheduled_task->service; $service = $scheduled_task->service;
$application = $scheduled_task->application; $application = $scheduled_task->application;
if (! $application && ! $service) { if (!$application && !$service) {
ray('application/service attached to scheduled task does not exist'); ray('application/service attached to scheduled task does not exist');
$scheduled_task->delete(); $scheduled_task->delete();
continue; continue;
} }
if ($application) {
if (str($application->status)->contains('running') === false) {
continue;
}
}
if ($service) {
if (str($service->status())->contains('running') === false) {
continue;
}
}
if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) { if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) {
$scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency]; $scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency];
} }
@ -158,7 +140,7 @@ class Kernel extends ConsoleKernel
protected function commands(): void protected function commands(): void
{ {
$this->load(__DIR__.'/Commands'); $this->load(__DIR__ . '/Commands');
require base_path('routes/console.php'); require base_path('routes/console.php');
} }

View File

@ -12,18 +12,17 @@ use Spatie\LaravelData\Data;
class CoolifyTaskArgs extends Data class CoolifyTaskArgs extends Data
{ {
public function __construct( public function __construct(
public string $server_uuid, public string $server_uuid,
public string $command, public string $command,
public string $type, public string $type,
public ?string $type_uuid = null, public ?string $type_uuid = null,
public ?int $process_id = null, public ?int $process_id = null,
public ?Model $model = null, public ?Model $model = null,
public ?string $status = null, public ?string $status = null ,
public bool $ignore_errors = false, public bool $ignore_errors = false,
public $call_event_on_finish = null, public $call_event_on_finish = null,
public $call_event_data = null
) { ) {
if (is_null($status)) { if(is_null($status)){
$this->status = ProcessStatus::QUEUED->value; $this->status = ProcessStatus::QUEUED->value;
} }
} }

View File

@ -9,7 +9,8 @@ use Spatie\LaravelData\Data;
class ServerMetadata extends Data class ServerMetadata extends Data
{ {
public function __construct( public function __construct(
public ?ProxyTypes $type, public ?ProxyTypes $type,
public ?ProxyStatus $status public ?ProxyStatus $status
) {} ) {
}
} }

View File

@ -5,5 +5,4 @@ namespace App\Enums;
enum ActivityTypes: string enum ActivityTypes: string
{ {
case INLINE = 'inline'; case INLINE = 'inline';
case COMMAND = 'command';
} }

View File

@ -1,11 +0,0 @@
<?php
namespace App\Enums;
enum BuildPackTypes: string
{
case NIXPACKS = 'nixpacks';
case STATIC = 'static';
case DOCKERFILE = 'dockerfile';
case DOCKERCOMPOSE = 'dockercompose';
}

View File

@ -1,15 +0,0 @@
<?php
namespace App\Enums;
enum NewDatabaseTypes: string
{
case POSTGRESQL = 'postgresql';
case MYSQL = 'mysql';
case MONGODB = 'mongodb';
case REDIS = 'redis';
case MARIADB = 'mariadb';
case KEYDB = 'keydb';
case DRAGONFLY = 'dragonfly';
case CLICKHOUSE = 'clickhouse';
}

View File

@ -1,22 +0,0 @@
<?php
namespace App\Enums;
enum NewResourceTypes: string
{
case PUBLIC = 'public';
case PRIVATE_GH_APP = 'private-gh-app';
case PRIVATE_DEPLOY_KEY = 'private-deploy-key';
case DOCKERFILE = 'dockerfile';
case DOCKERCOMPOSE = 'dockercompose';
case DOCKER_IMAGE = 'docker-image';
case SERVICE = 'service';
case POSTGRESQL = 'postgresql';
case MYSQL = 'mysql';
case MONGODB = 'mongodb';
case REDIS = 'redis';
case MARIADB = 'mariadb';
case KEYDB = 'keydb';
case DRAGONFLY = 'dragonfly';
case CLICKHOUSE = 'clickhouse';
}

View File

@ -1,10 +0,0 @@
<?php
namespace App\Enums;
enum RedirectTypes: string
{
case BOTH = 'both';
case WWW = 'www';
case NON_WWW = 'non-www';
}

View File

@ -2,7 +2,9 @@
namespace App\Events; namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
@ -11,16 +13,14 @@ use Illuminate\Queue\SerializesModels;
class ApplicationStatusChanged implements ShouldBroadcast class ApplicationStatusChanged implements ShouldBroadcast
{ {
use Dispatchable, InteractsWithSockets, SerializesModels; use Dispatchable, InteractsWithSockets, SerializesModels;
public $teamId; public $teamId;
public function __construct($teamId = null) public function __construct($teamId = null)
{ {
if (is_null($teamId)) { if (is_null($teamId)) {
$teamId = auth()->user()->currentTeam()->id ?? null; $teamId = auth()->user()->currentTeam()->id ?? null;
} }
if (is_null($teamId)) { if (is_null($teamId)) {
throw new \Exception('Team id is null'); throw new \Exception("Team id is null");
} }
$this->teamId = $teamId; $this->teamId = $teamId;
} }

View File

@ -2,7 +2,9 @@
namespace App\Events; namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
@ -11,16 +13,14 @@ use Illuminate\Queue\SerializesModels;
class BackupCreated implements ShouldBroadcast class BackupCreated implements ShouldBroadcast
{ {
use Dispatchable, InteractsWithSockets, SerializesModels; use Dispatchable, InteractsWithSockets, SerializesModels;
public $teamId; public $teamId;
public function __construct($teamId = null) public function __construct($teamId = null)
{ {
if (is_null($teamId)) { if (is_null($teamId)) {
$teamId = auth()->user()->currentTeam()->id ?? null; $teamId = auth()->user()->currentTeam()->id ?? null;
} }
if (is_null($teamId)) { if (is_null($teamId)) {
throw new \Exception('Team id is null'); throw new \Exception("Team id is null");
} }
$this->teamId = $teamId; $this->teamId = $teamId;
} }

View File

@ -2,7 +2,9 @@
namespace App\Events; namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
@ -11,28 +13,22 @@ use Illuminate\Queue\SerializesModels;
class DatabaseStatusChanged implements ShouldBroadcast class DatabaseStatusChanged implements ShouldBroadcast
{ {
use Dispatchable, InteractsWithSockets, SerializesModels; use Dispatchable, InteractsWithSockets, SerializesModels;
public $userId;
public ?string $userId = null;
public function __construct($userId = null) public function __construct($userId = null)
{ {
if (is_null($userId)) { if (is_null($userId)) {
$userId = auth()->user()->id ?? null; $userId = auth()->user()->id ?? null;
} }
if (is_null($userId)) { if (is_null($userId)) {
return false; throw new \Exception("User id is null");
} }
$this->userId = $userId; $this->userId = $userId;
} }
public function broadcastOn(): ?array public function broadcastOn(): array
{ {
if ($this->userId) { return [
return [ new PrivateChannel("user.{$this->userId}"),
new PrivateChannel("user.{$this->userId}"), ];
];
}
return null;
} }
} }

View File

@ -1,14 +0,0 @@
<?php
namespace App\Events;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ProxyStarted
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct(public $data) {}
}

View File

@ -2,7 +2,9 @@
namespace App\Events; namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
@ -11,16 +13,14 @@ use Illuminate\Queue\SerializesModels;
class ProxyStatusChanged implements ShouldBroadcast class ProxyStatusChanged implements ShouldBroadcast
{ {
use Dispatchable, InteractsWithSockets, SerializesModels; use Dispatchable, InteractsWithSockets, SerializesModels;
public $teamId; public $teamId;
public function __construct($teamId = null) public function __construct($teamId = null)
{ {
if (is_null($teamId)) { if (is_null($teamId)) {
$teamId = auth()->user()->currentTeam()->id ?? null; $teamId = auth()->user()->currentTeam()->id ?? null;
} }
if (is_null($teamId)) { if (is_null($teamId)) {
throw new \Exception('Team id is null'); throw new \Exception("Team id is null");
} }
$this->teamId = $teamId; $this->teamId = $teamId;
} }

View File

@ -2,7 +2,9 @@
namespace App\Events; namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
@ -11,28 +13,22 @@ use Illuminate\Queue\SerializesModels;
class ServiceStatusChanged implements ShouldBroadcast class ServiceStatusChanged implements ShouldBroadcast
{ {
use Dispatchable, InteractsWithSockets, SerializesModels; use Dispatchable, InteractsWithSockets, SerializesModels;
public $userId;
public ?string $userId = null;
public function __construct($userId = null) public function __construct($userId = null)
{ {
if (is_null($userId)) { if (is_null($userId)) {
$userId = auth()->user()->id ?? null; $userId = auth()->user()->id ?? null;
} }
if (is_null($userId)) { if (is_null($userId)) {
return false; throw new \Exception("User id is null");
} }
$this->userId = $userId; $this->userId = $userId;
} }
public function broadcastOn(): ?array public function broadcastOn(): array
{ {
if (! is_null($this->userId)) { return [
return [ new PrivateChannel("user.{$this->userId}"),
new PrivateChannel("user.{$this->userId}"), ];
];
}
return null;
} }
} }

View File

@ -2,7 +2,9 @@
namespace App\Events; namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
@ -11,9 +13,7 @@ use Illuminate\Queue\SerializesModels;
class TestEvent implements ShouldBroadcast class TestEvent implements ShouldBroadcast
{ {
use Dispatchable, InteractsWithSockets, SerializesModels; use Dispatchable, InteractsWithSockets, SerializesModels;
public $teamId; public $teamId;
public function __construct() public function __construct()
{ {
$this->teamId = auth()->user()->currentTeam()->id; $this->teamId = auth()->user()->currentTeam()->id;

View File

@ -13,6 +13,7 @@ use Throwable;
class Handler extends ExceptionHandler class Handler extends ExceptionHandler
{ {
/** /**
* A list of exception types with their corresponding custom log levels. * A list of exception types with their corresponding custom log levels.
* *
@ -21,16 +22,14 @@ class Handler extends ExceptionHandler
protected $levels = [ protected $levels = [
// //
]; ];
/** /**
* A list of the exception types that are not reported. * A list of the exception types that are not reported.
* *
* @var array<int, class-string<\Throwable>> * @var array<int, class-string<\Throwable>>
*/ */
protected $dontReport = [ protected $dontReport = [
ProcessException::class, ProcessException::class
]; ];
/** /**
* A list of the inputs that are never flashed to the session on validation exceptions. * A list of the inputs that are never flashed to the session on validation exceptions.
* *
@ -41,7 +40,6 @@ class Handler extends ExceptionHandler
'password', 'password',
'password_confirmation', 'password_confirmation',
]; ];
private InstanceSettings $settings; private InstanceSettings $settings;
protected function unauthenticated($request, AuthenticationException $exception) protected function unauthenticated($request, AuthenticationException $exception)
@ -49,10 +47,8 @@ class Handler extends ExceptionHandler
if ($request->is('api/*') || $request->expectsJson() || $this->shouldReturnJson($request, $exception)) { if ($request->is('api/*') || $request->expectsJson() || $this->shouldReturnJson($request, $exception)) {
return response()->json(['message' => $exception->getMessage()], 401); return response()->json(['message' => $exception->getMessage()], 401);
} }
return redirect()->guest($exception->redirectTo() ?? route('login'));
return redirect()->guest($exception->redirectTo($request) ?? route('login'));
} }
/** /**
* Register the exception handling callbacks for the application. * Register the exception handling callbacks for the application.
*/ */
@ -60,12 +56,12 @@ class Handler extends ExceptionHandler
{ {
$this->reportable(function (Throwable $e) { $this->reportable(function (Throwable $e) {
if (isDev()) { if (isDev()) {
return; // return;
} }
if ($e instanceof RuntimeException) { if ($e instanceof RuntimeException) {
return; return;
} }
$this->settings = \App\Models\InstanceSettings::get(); $this->settings = InstanceSettings::get();
if ($this->settings->do_not_track) { if ($this->settings->do_not_track) {
return; return;
} }
@ -76,14 +72,11 @@ class Handler extends ExceptionHandler
$scope->setUser( $scope->setUser(
[ [
'email' => $email, 'email' => $email,
'instanceAdmin' => $instanceAdmin, 'instanceAdmin' => $instanceAdmin
] ]
); );
} }
); );
if (str($e->getMessage())->contains('No space left on device')) {
return;
}
ray('reporting to sentry'); ray('reporting to sentry');
Integration::captureUnhandledException($e); Integration::captureUnhandledException($e);
}); });

View File

@ -4,4 +4,7 @@ namespace App\Exceptions;
use Exception; use Exception;
class ProcessException extends Exception {} class ProcessException extends Exception
{
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -9,33 +9,13 @@ use App\Actions\Database\StartPostgresql;
use App\Actions\Database\StartRedis; use App\Actions\Database\StartRedis;
use App\Actions\Service\StartService; use App\Actions\Service\StartService;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\ApplicationDeploymentQueue;
use App\Models\Server;
use App\Models\Tag; use App\Models\Tag;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
class Deploy extends Controller class Deploy extends Controller
{ {
public function deployments(Request $request)
{
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return invalid_token();
}
$servers = Server::whereTeamId($teamId)->get();
$deployments_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn("server_id", $servers->pluck("id"))->get([
"id",
"application_id",
"application_name",
"deployment_url",
"pull_request_id",
"server_name",
"server_id",
"status"
])->sortBy('id')->toArray();
return response()->json($deployments_per_server, 200);
}
public function deploy(Request $request) public function deploy(Request $request)
{ {
$teamId = get_team_id_from_token(); $teamId = get_team_id_from_token();
@ -47,7 +27,7 @@ class Deploy extends Controller
return response()->json(['error' => 'You can only use uuid or tag, not both.', 'upstream docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400); return response()->json(['error' => 'You can only use uuid or tag, not both.', 'upstream docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
} }
if (is_null($teamId)) { if (is_null($teamId)) {
return invalid_token(); return response()->json(['error' => 'Invalid token.', 'upstream docs' => 'https://coolify.io/docs/api/authentication'], 400);
} }
if ($tags) { if ($tags) {
return $this->by_tags($tags, $teamId, $force); return $this->by_tags($tags, $teamId, $force);
@ -64,22 +44,16 @@ class Deploy extends Controller
if (count($uuids) === 0) { if (count($uuids) === 0) {
return response()->json(['error' => 'No UUIDs provided.', 'upstream docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400); return response()->json(['error' => 'No UUIDs provided.', 'upstream docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
} }
$deployments = collect(); $message = collect([]);
$payload = collect();
foreach ($uuids as $uuid) { foreach ($uuids as $uuid) {
$resource = getResourceByUuid($uuid, $teamId); $resource = getResourceByUuid($uuid, $teamId);
if ($resource) { if ($resource) {
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force); $return_message = $this->deploy_resource($resource, $force);
if ($deployment_uuid) { $message = $message->merge($return_message);
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
} else {
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid]);
}
} }
} }
if ($deployments->count() > 0) { if ($message->count() > 0) {
$payload->put('deployments', $deployments->toArray()); return response()->json(['message' => $message->toArray()], 200);
return response()->json($payload->toArray(), 200);
} }
return response()->json(['error' => "No resources found.", 'upstream docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404); return response()->json(['error' => "No resources found.", 'upstream docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
} }
@ -92,12 +66,10 @@ class Deploy extends Controller
return response()->json(['error' => 'No TAGs provided.', 'upstream docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400); return response()->json(['error' => 'No TAGs provided.', 'upstream docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
} }
$message = collect([]); $message = collect([]);
$deployments = collect();
$payload = collect();
foreach ($tags as $tag) { foreach ($tags as $tag) {
$found_tag = Tag::where(['name' => $tag, 'team_id' => $team_id])->first(); $found_tag = Tag::where(['name' => $tag, 'team_id' => $team_id])->first();
if (!$found_tag) { if (!$found_tag) {
// $message->push("Tag {$tag} not found."); $message->push("Tag {$tag} not found.");
continue; continue;
} }
$applications = $found_tag->applications()->get(); $applications = $found_tag->applications()->get();
@ -107,78 +79,83 @@ class Deploy extends Controller
continue; continue;
} }
foreach ($applications as $resource) { foreach ($applications as $resource) {
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force); $return_message = $this->deploy_resource($resource, $force);
if ($deployment_uuid) {
$deployments->push(['resource_uuid' => $resource->uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
}
$message = $message->merge($return_message); $message = $message->merge($return_message);
} }
foreach ($services as $resource) { foreach ($services as $resource) {
['message' => $return_message] = $this->deploy_resource($resource, $force); $return_message = $this->deploy_resource($resource, $force);
$message = $message->merge($return_message); $message = $message->merge($return_message);
} }
} }
ray($message);
if ($message->count() > 0) { if ($message->count() > 0) {
$payload->put('message', $message->toArray()); return response()->json(['message' => $message->toArray()], 200);
if ($deployments->count() > 0) {
$payload->put('details', $deployments->toArray());
}
return response()->json($payload->toArray(), 200);
} }
return response()->json(['error' => "No resources found with this tag.", 'upstream docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404); return response()->json(['error' => "No resources found.", 'upstream docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
} }
public function deploy_resource($resource, bool $force = false): array public function deploy_resource($resource, bool $force = false): Collection
{ {
$message = null; $message = collect([]);
$deployment_uuid = null;
if (gettype($resource) !== 'object') { if (gettype($resource) !== 'object') {
return ['message' => "Resource ($resource) not found.", 'deployment_uuid' => $deployment_uuid]; return $message->push("Resource ($resource) not found.");
} }
$type = $resource?->getMorphClass(); $type = $resource?->getMorphClass();
if ($type === 'App\Models\Application') { if ($type === 'App\Models\Application') {
$deployment_uuid = new Cuid2(7);
queue_application_deployment( queue_application_deployment(
application: $resource, application: $resource,
deployment_uuid: $deployment_uuid, deployment_uuid: new Cuid2(7),
force_rebuild: $force, force_rebuild: $force,
); );
$message = "Application {$resource->name} deployment queued."; $message->push("Application {$resource->name} deployment queued.");
} else if ($type === 'App\Models\StandalonePostgresql') { } else if ($type === 'App\Models\StandalonePostgresql') {
if (str($resource->status)->startsWith('running')) {
$message->push("Database {$resource->name} already running.");
}
StartPostgresql::run($resource); StartPostgresql::run($resource);
$resource->update([ $resource->update([
'started_at' => now(), 'started_at' => now(),
]); ]);
$message = "Database {$resource->name} started."; $message->push("Database {$resource->name} started.");
} else if ($type === 'App\Models\StandaloneRedis') { } else if ($type === 'App\Models\StandaloneRedis') {
if (str($resource->status)->startsWith('running')) {
$message->push("Database {$resource->name} already running.");
}
StartRedis::run($resource); StartRedis::run($resource);
$resource->update([ $resource->update([
'started_at' => now(), 'started_at' => now(),
]); ]);
$message = "Database {$resource->name} started."; $message->push("Database {$resource->name} started.");
} else if ($type === 'App\Models\StandaloneMongodb') { } else if ($type === 'App\Models\StandaloneMongodb') {
if (str($resource->status)->startsWith('running')) {
$message->push("Database {$resource->name} already running.");
}
StartMongodb::run($resource); StartMongodb::run($resource);
$resource->update([ $resource->update([
'started_at' => now(), 'started_at' => now(),
]); ]);
$message = "Database {$resource->name} started."; $message->push("Database {$resource->name} started.");
} else if ($type === 'App\Models\StandaloneMysql') { } else if ($type === 'App\Models\StandaloneMysql') {
if (str($resource->status)->startsWith('running')) {
$message->push("Database {$resource->name} already running.");
}
StartMysql::run($resource); StartMysql::run($resource);
$resource->update([ $resource->update([
'started_at' => now(), 'started_at' => now(),
]); ]);
$message = "Database {$resource->name} started."; $message->push("Database {$resource->name} started.");
} else if ($type === 'App\Models\StandaloneMariadb') { } else if ($type === 'App\Models\StandaloneMariadb') {
if (str($resource->status)->startsWith('running')) {
$message->push("Database {$resource->name} already running.");
}
StartMariadb::run($resource); StartMariadb::run($resource);
$resource->update([ $resource->update([
'started_at' => now(), 'started_at' => now(),
]); ]);
$message = "Database {$resource->name} started."; $message->push("Database {$resource->name} started.");
} else if ($type === 'App\Models\Service') { } else if ($type === 'App\Models\Service') {
StartService::run($resource); StartService::run($resource);
$message = "Service {$resource->name} started. It could take a while, be patient."; $message->push("Service {$resource->name} started. It could take a while, be patient.");
} }
return ['message' => $message, 'deployment_uuid' => $deployment_uuid]; return $message;
} }
} }

View File

@ -1,317 +0,0 @@
<?php
namespace App\Http\Controllers\Api;
use App\Actions\Database\StartDatabase;
use App\Actions\Service\StartService;
use App\Http\Controllers\Controller;
use App\Models\ApplicationDeploymentQueue;
use App\Models\Server;
use App\Models\Tag;
use Illuminate\Http\Request;
use OpenApi\Attributes as OA;
use Visus\Cuid2\Cuid2;
class DeployController extends Controller
{
private function removeSensitiveData($deployment)
{
$token = auth()->user()->currentAccessToken();
if ($token->can('view:sensitive')) {
return serializeApiResponse($deployment);
}
$deployment->makeHidden([
'logs',
]);
return serializeApiResponse($deployment);
}
#[OA\Get(
summary: 'List',
description: 'List currently running deployments',
path: '/deployments',
security: [
['bearerAuth' => []],
],
tags: ['Deployments'],
responses: [
new OA\Response(
response: 200,
description: 'Get all currently running deployments.',
content: [
new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(
type: 'array',
items: new OA\Items(ref: '#/components/schemas/ApplicationDeploymentQueue'),
)
),
]),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
),
new OA\Response(
response: 400,
ref: '#/components/responses/400',
),
]
)]
public function deployments(Request $request)
{
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$servers = Server::whereTeamId($teamId)->get();
$deployments_per_server = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('server_id', $servers->pluck('id'))->get()->sortBy('id');
$deployments_per_server = $deployments_per_server->map(function ($deployment) {
return $this->removeSensitiveData($deployment);
});
return response()->json($deployments_per_server);
}
#[OA\Get(
summary: 'Get',
description: 'Get deployment by UUID.',
path: '/deployments/{uuid}',
security: [
['bearerAuth' => []],
],
tags: ['Deployments'],
parameters: [
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Deployment Uuid', schema: new OA\Schema(type: 'integer')),
],
responses: [
new OA\Response(
response: 200,
description: 'Get deployment by UUID.',
content: [
new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(
ref: '#/components/schemas/ApplicationDeploymentQueue',
)
),
]),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
),
new OA\Response(
response: 400,
ref: '#/components/responses/400',
),
new OA\Response(
response: 404,
ref: '#/components/responses/404',
),
]
)]
public function deployment_by_uuid(Request $request)
{
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$uuid = $request->route('uuid');
if (! $uuid) {
return response()->json(['message' => 'UUID is required.'], 400);
}
$deployment = ApplicationDeploymentQueue::where('deployment_uuid', $uuid)->first();
if (! $deployment) {
return response()->json(['message' => 'Deployment not found.'], 404);
}
return response()->json($this->removeSensitiveData($deployment));
}
#[OA\Get(
summary: 'Deploy',
description: 'Deploy by tag or uuid. `Post` request also accepted.',
path: '/deploy',
security: [
['bearerAuth' => []],
],
tags: ['Deployments'],
parameters: [
new OA\Parameter(name: 'tag', in: 'query', description: 'Tag name(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')),
new OA\Parameter(name: 'uuid', in: 'query', description: 'Resource UUID(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')),
new OA\Parameter(name: 'force', in: 'query', description: 'Force rebuild (without cache)', schema: new OA\Schema(type: 'boolean')),
],
responses: [
new OA\Response(
response: 200,
description: 'Get deployment(s) Uuid\'s',
content: [
new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(
type: 'object',
properties: [
'deployments' => new OA\Property(
property: 'deployments',
type: 'array',
items: new OA\Items(
type: 'object',
properties: [
'message' => ['type' => 'string'],
'resource_uuid' => ['type' => 'string'],
'deployment_uuid' => ['type' => 'string'],
]
),
),
],
)
),
]),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
),
new OA\Response(
response: 400,
ref: '#/components/responses/400',
),
]
)]
public function deploy(Request $request)
{
$teamId = getTeamIdFromToken();
$uuids = $request->query->get('uuid');
$tags = $request->query->get('tag');
$force = $request->query->get('force') ?? false;
if ($uuids && $tags) {
return response()->json(['message' => 'You can only use uuid or tag, not both.'], 400);
}
if (is_null($teamId)) {
return invalidTokenResponse();
}
if ($tags) {
return $this->by_tags($tags, $teamId, $force);
} elseif ($uuids) {
return $this->by_uuids($uuids, $teamId, $force);
}
return response()->json(['message' => 'You must provide uuid or tag.'], 400);
}
private function by_uuids(string $uuid, int $teamId, bool $force = false)
{
$uuids = explode(',', $uuid);
$uuids = collect(array_filter($uuids));
if (count($uuids) === 0) {
return response()->json(['message' => 'No UUIDs provided.'], 400);
}
$deployments = collect();
$payload = collect();
foreach ($uuids as $uuid) {
$resource = getResourceByUuid($uuid, $teamId);
if ($resource) {
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
if ($deployment_uuid) {
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
} else {
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid]);
}
}
}
if ($deployments->count() > 0) {
$payload->put('deployments', $deployments->toArray());
return response()->json(serializeApiResponse($payload->toArray()));
}
return response()->json(['message' => 'No resources found.'], 404);
}
public function by_tags(string $tags, int $team_id, bool $force = false)
{
$tags = explode(',', $tags);
$tags = collect(array_filter($tags));
if (count($tags) === 0) {
return response()->json(['message' => 'No TAGs provided.'], 400);
}
$message = collect([]);
$deployments = collect();
$payload = collect();
foreach ($tags as $tag) {
$found_tag = Tag::where(['name' => $tag, 'team_id' => $team_id])->first();
if (! $found_tag) {
// $message->push("Tag {$tag} not found.");
continue;
}
$applications = $found_tag->applications()->get();
$services = $found_tag->services()->get();
if ($applications->count() === 0 && $services->count() === 0) {
$message->push("No resources found for tag {$tag}.");
continue;
}
foreach ($applications as $resource) {
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
if ($deployment_uuid) {
$deployments->push(['resource_uuid' => $resource->uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
}
$message = $message->merge($return_message);
}
foreach ($services as $resource) {
['message' => $return_message] = $this->deploy_resource($resource, $force);
$message = $message->merge($return_message);
}
}
if ($message->count() > 0) {
$payload->put('message', $message->toArray());
if ($deployments->count() > 0) {
$payload->put('details', $deployments->toArray());
}
return response()->json(serializeApiResponse($payload->toArray()));
}
return response()->json(['message' => 'No resources found with this tag.'], 404);
}
public function deploy_resource($resource, bool $force = false): array
{
$message = null;
$deployment_uuid = null;
if (gettype($resource) !== 'object') {
return ['message' => "Resource ($resource) not found.", 'deployment_uuid' => $deployment_uuid];
}
switch ($resource?->getMorphClass()) {
case 'App\Models\Application':
$deployment_uuid = new Cuid2(7);
queue_application_deployment(
application: $resource,
deployment_uuid: $deployment_uuid,
force_rebuild: $force,
);
$message = "Application {$resource->name} deployment queued.";
break;
case 'App\Models\Service':
StartService::run($resource);
$message = "Service {$resource->name} started. It could take a while, be patient.";
break;
default:
// Database resource
StartDatabase::dispatch($resource);
$resource->update([
'started_at' => now(),
]);
$message = "Database {$resource->name} started.";
break;
}
return ['message' => $message, 'deployment_uuid' => $deployment_uuid];
}
}

View File

@ -1,35 +0,0 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\EnvironmentVariable;
use Illuminate\Http\Request;
class EnvironmentVariablesController extends Controller
{
public function delete_env_by_uuid(Request $request)
{
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$env = EnvironmentVariable::where('uuid', $request->env_uuid)->first();
if (! $env) {
return response()->json([
'message' => 'Environment variable not found.',
], 404);
}
$found_app = $env->resource()->whereRelation('environment.project.team', 'id', $teamId)->first();
if (! $found_app) {
return response()->json([
'message' => 'Environment variable not found.',
], 404);
}
$env->delete();
return response()->json([
'message' => 'Environment variable deleted.',
]);
}
}

View File

@ -1,51 +0,0 @@
<?php
namespace App\Http\Controllers\Api;
use OpenApi\Attributes as OA;
#[OA\Info(title: 'Coolify', version: '0.1')]
#[OA\Server(url: 'https://app.coolify.io/api/v1')]
#[OA\SecurityScheme(
type: 'http',
scheme: 'bearer',
securityScheme: 'bearerAuth',
description: 'Go to `Keys & Tokens` / `API tokens` and create a new token. Use the token as the bearer token.')]
#[OA\Components(
responses: [
new OA\Response(
response: 400,
description: 'Invalid token.',
content: new OA\JsonContent(
type: 'object',
properties: [
new OA\Property(property: 'message', type: 'string', example: 'Invalid token.'),
]
)),
new OA\Response(
response: 401,
description: 'Unauthenticated.',
content: new OA\JsonContent(
type: 'object',
properties: [
new OA\Property(property: 'message', type: 'string', example: 'Unauthenticated.'),
]
)),
new OA\Response(
response: 404,
description: 'Resource not found.',
content: new OA\JsonContent(
type: 'object',
properties: [
new OA\Property(property: 'message', type: 'string', example: 'Resource not found.'),
]
)),
],
)]
class OpenApi
{
// This class is used to generate OpenAPI documentation
// for the Coolify API. It is not a controller and does
// not contain any routes. It is used to define the
// OpenAPI metadata and security scheme for the API.
}

View File

@ -1,183 +0,0 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use OpenApi\Attributes as OA;
class OtherController extends Controller
{
#[OA\Get(
summary: 'Version',
description: 'Get Coolify version.',
path: '/version',
security: [
['bearerAuth' => []],
],
responses: [
new OA\Response(
response: 200,
description: 'Returns the version of the application',
content: new OA\JsonContent(
type: 'string',
example: 'v4.0.0',
)),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
),
new OA\Response(
response: 400,
ref: '#/components/responses/400',
),
]
)]
public function version(Request $request)
{
return response(config('version'));
}
#[OA\Get(
summary: 'Enable API',
description: 'Enable API (only with root permissions).',
path: '/enable',
security: [
['bearerAuth' => []],
],
responses: [
new OA\Response(
response: 200,
description: 'Enable API.',
content: new OA\JsonContent(
type: 'object',
properties: [
new OA\Property(property: 'message', type: 'string', example: 'API enabled.'),
]
)),
new OA\Response(
response: 403,
description: 'You are not allowed to enable the API.',
content: new OA\JsonContent(
type: 'object',
properties: [
new OA\Property(property: 'message', type: 'string', example: 'You are not allowed to enable the API.'),
]
)),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
),
new OA\Response(
response: 400,
ref: '#/components/responses/400',
),
]
)]
public function enable_api(Request $request)
{
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
if ($teamId !== '0') {
return response()->json(['message' => 'You are not allowed to enable the API.'], 403);
}
$settings = \App\Models\InstanceSettings::get();
$settings->update(['is_api_enabled' => true]);
return response()->json(['message' => 'API enabled.'], 200);
}
#[OA\Get(
summary: 'Disable API',
description: 'Disable API (only with root permissions).',
path: '/disable',
security: [
['bearerAuth' => []],
],
responses: [
new OA\Response(
response: 200,
description: 'Disable API.',
content: new OA\JsonContent(
type: 'object',
properties: [
new OA\Property(property: 'message', type: 'string', example: 'API disabled.'),
]
)),
new OA\Response(
response: 403,
description: 'You are not allowed to disable the API.',
content: new OA\JsonContent(
type: 'object',
properties: [
new OA\Property(property: 'message', type: 'string', example: 'You are not allowed to disable the API.'),
]
)),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
),
new OA\Response(
response: 400,
ref: '#/components/responses/400',
),
]
)]
public function disable_api(Request $request)
{
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
if ($teamId !== '0') {
return response()->json(['message' => 'You are not allowed to disable the API.'], 403);
}
$settings = \App\Models\InstanceSettings::get();
$settings->update(['is_api_enabled' => false]);
return response()->json(['message' => 'API disabled.'], 200);
}
public function feedback(Request $request)
{
$content = $request->input('content');
$webhook_url = config('coolify.feedback_discord_webhook');
if ($webhook_url) {
Http::post($webhook_url, [
'content' => $content,
]);
}
return response()->json(['message' => 'Feedback sent.'], 200);
}
#[OA\Get(
summary: 'Healthcheck',
description: 'Healthcheck endpoint.',
path: '/healthcheck',
responses: [
new OA\Response(
response: 200,
description: 'Healthcheck endpoint.',
content: new OA\JsonContent(
type: 'string',
example: 'OK',
)),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
),
new OA\Response(
response: 400,
ref: '#/components/responses/400',
),
]
)]
public function healthcheck(Request $request)
{
return 'OK';
}
}

Some files were not shown because too many files have changed in this diff Show More