Compare commits

...

133 Commits

Author SHA1 Message Date
Andras Bacsai
7442d19611 Merge pull request #3705 from coollabsio/next
v4.0.0-beta.354
2024-10-03 22:04:36 +02:00
Andras Bacsai
d990a5691d chore: Update homarr service template and remove unnecessary code 2024-10-03 21:47:06 +02:00
Andras Bacsai
2181ef381b Merge pull request #3697 from danielalves96/add_homarr_template
feat: add homarr service tamplate and logo
2024-10-03 21:39:14 +02:00
Andras Bacsai
c80f5be974 chore: Update it-tools service template and port configuration 2024-10-03 21:38:13 +02:00
Andras Bacsai
358f6575f8 chore: Refactor modal-confirmation component 2024-10-03 21:38:08 +02:00
Andras Bacsai
de0f34734b Merge pull request #3702 from danielalves96/add-it-tools
feat: add it-tools service template and logo
2024-10-03 21:33:33 +02:00
Andras Bacsai
33cb2d150d chore: Fix application deployment queue filter logic 2024-10-03 21:32:02 +02:00
Andras Bacsai
5f07b473e9 fix: parse proxy config and check the set ports usage 2024-10-03 21:29:55 +02:00
Andras Bacsai
5bcd813792 chore: Remove commented code in Server model 2024-10-03 21:29:04 +02:00
Andras Bacsai
a0bb523507 chore: Remove debug statement in Service model 2024-10-03 21:11:14 +02:00
Andras Bacsai
0b4fc38d6b chore: Update version to 4.0.0-beta.354 2024-10-03 21:10:52 +02:00
Andras Bacsai
82c834915d Merge pull request #3703 from coollabsio/next
v4.0.0-beta.353
2024-10-03 21:03:16 +02:00
Andras Bacsai
d637675ce3 chore: Update service application view 2024-10-03 21:02:42 +02:00
Andras Bacsai
e71c04a0c7 chore: Update version to 4.0.0-beta.353 2024-10-03 20:59:10 +02:00
Andras Bacsai
885b3bdea7 Merge pull request #3701 from coollabsio/next
v4.0.0-beta.352
2024-10-03 20:53:17 +02:00
Daniel Alves
9d6757aeb7 feat: add it-tools service template and logo 2024-10-03 15:52:33 -03:00
Andras Bacsai
d84d0a816b chore: Refactor DatabaseBackupJob to handle missing team 2024-10-03 20:51:18 +02:00
Andras Bacsai
0da31c34b5 fix: add new supported database images 2024-10-03 20:47:22 +02:00
Andras Bacsai
ccdaf59ecb fix: service application view 2024-10-03 20:47:02 +02:00
Andras Bacsai
3fc9cf90ab chore: update version to 4.0.0-beta.352 2024-10-03 20:46:45 +02:00
Daniel Alves
c4be720b20 fix: update FQDN 2024-10-03 15:00:52 -03:00
Daniel Alves
aabe27efd1 feat: add homarr service tamplate and logo 2024-10-03 12:58:35 -03:00
Andras Bacsai
a8982379c9 Merge pull request #3683 from coollabsio/next
v4.0.0-beta.351
2024-10-03 15:15:21 +02:00
Andras Bacsai
6dd0bd0742 fix: api useBuildServer 2024-10-03 15:09:56 +02:00
Andras Bacsai
1d3494a6ba fix: network handling
fix: environment variable handling
2024-10-03 15:04:40 +02:00
Andras Bacsai
ee5eb427c9 fix: bitcoin core template 2024-10-03 14:07:58 +02:00
Andras Bacsai
ad7b5e6e1c Merge pull request #3689 from ALsJourney/bitcoin_core_service
Basic Bitcoin Core node service
2024-10-03 14:00:52 +02:00
Andras Bacsai
73bd344147 fix: strapi template 2024-10-03 13:49:50 +02:00
Andras Bacsai
ef448280d8 fix: able to support more database dynamically from Coolify's UI 2024-10-03 13:49:43 +02:00
Andras Bacsai
5282248cb4 Merge pull request #3685 from statickidz/add-strapi-template
Add Strapi template
2024-10-03 13:18:40 +02:00
Andras Bacsai
14c9f25c57 feat: restart service without pulling the latest image 2024-10-03 13:17:35 +02:00
ALsJourney
44b3d08d86 Finished basic bitcoin core service 2024-10-03 13:13:25 +02:00
Andras Bacsai
5da1f48ae1 fix typo 2024-10-03 12:43:41 +02:00
Andras Bacsai
2bc1b9027c Merge pull request #3679 from alepouna/typo-fix
Update docker-registry.yaml
2024-10-03 12:43:46 +02:00
Andras Bacsai
1c7ca56756 feat: backup all databases for mysql,mariadb,postgresql 2024-10-03 12:39:45 +02:00
Adrian Barrio
d70faea845 Merge remote-tracking branch 'origin/next' into add-strapi-template 2024-10-03 11:33:35 +02:00
Adrian Barrio
fdb5cab875 feat: add strapi template 2024-10-03 11:28:27 +02:00
Andras Bacsai
bb6cb8edc9 improvement: show backup button on supported db service stacks 2024-10-03 10:48:25 +02:00
Andras Bacsai
a6ec2b92fb version++ 2024-10-03 10:23:38 +02:00
Andras Bacsai
436a1d945f Merge pull request #3663 from coollabsio/next
v4.0.0-beta.350
2024-10-03 10:09:05 +02:00
Andras Bacsai
a6a3abc273 chore: Update soketi service image to version 1.0.3 2024-10-03 10:05:54 +02:00
Andras Bacsai
c4e702f096 fix: able to select root permission easier 2024-10-03 09:57:37 +02:00
alepouna
31df222798 Update docker-registry.yaml 2024-10-03 04:04:37 +03:00
Andras Bacsai
e91939a4ea refactor: Remove unnecessary watch command from soketi service entrypoint 2024-10-02 21:24:09 +02:00
Andras Bacsai
ceccd093d2 refactor: Improve socket reconnection interval in terminal.js 2024-10-02 21:24:05 +02:00
Andras Bacsai
66bb4e0fc1 refactor: Remove inactivity timer in terminal-server.js 2024-10-02 21:23:59 +02:00
Andras Bacsai
dd3ff38df7 refactor: Encode delimiter in SshMultiplexingHelper 2024-10-02 21:23:46 +02:00
Andras Bacsai
69553ec314 refactor: Improve popup component styling and button behavior 2024-10-02 18:26:51 +02:00
Andras Bacsai
7bb1bf0ae3 refactor: Improve parsing of commands for sudo in parseCommandsByLineForSudo 2024-10-02 18:26:40 +02:00
Andras Bacsai
a1a8f1336a refactor: Fix indentation in modal-confirmation.blade.php 2024-10-02 17:35:48 +02:00
Andras Bacsai
207fe1d709 wtf wtf wtf 2024-10-02 17:27:02 +02:00
Andras Bacsai
059535a676 chore: Remove commented out code for uploading to S3 in DatabaseBackupJob 2024-10-02 16:43:01 +02:00
Andras Bacsai
765a74ca4f handle errors in databasebackupjob 2024-10-02 15:33:14 +02:00
Andras Bacsai
e03e4f2e91 refactor: Improve SSH command generation in Terminal.php and terminal-server.js 2024-10-02 15:16:55 +02:00
Andras Bacsai
0ab432d5e6 chore: Remove unnecessary command from SshMultiplexingHelper 2024-10-02 14:54:48 +02:00
Andras Bacsai
6f25b548c7 fix: realtime watch in development mode 2024-10-02 14:18:52 +02:00
Andras Bacsai
d55e4bf381 feat: Handle HTTPS domain in ConfigureCloudflareTunnels 2024-10-02 13:36:25 +02:00
Andras Bacsai
ec216254b5 chore: Update modal input in server form to prevent closing on outside click 2024-10-02 13:36:09 +02:00
Andras Bacsai
fbb36bfe8e revert modal-confirmation in dev 2024-10-02 12:01:12 +02:00
Andras Bacsai
dd782e75f5 fix: local dev s3 uploads
fix: hetzner s3 uploads (mc alias instead of mc host)
2024-10-02 11:45:30 +02:00
Andras Bacsai
2be2f0ac79 feat: support Hetzner S3 2024-10-02 10:25:45 +02:00
Andras Bacsai
97943db5f4 chore: Add missing import for Attribute class in ApplicationDeploymentQueue model 2024-10-02 09:21:54 +02:00
Andras Bacsai
bbd2748ad7 chore: Update command signature and description for cleanup application deployment queue 2024-10-02 09:21:50 +02:00
Andras Bacsai
a530804a71 feat: Add command to check application deployment queue 2024-10-02 09:21:28 +02:00
Andras Bacsai
8ca8ab82b0 refactor: Remove deployment queue when deleting an application 2024-10-02 09:20:49 +02:00
Andras Bacsai
024ad8e943 fix: cleanup stucked applicationdeploymentqueue 2024-10-02 09:20:08 +02:00
Andras Bacsai
4d86b556a4 fix: ipv6 scp should use -6 flag 2024-10-02 08:15:03 +02:00
Andras Bacsai
73e38ff951 chore: Update version numbers to 4.0.0-beta.350 in configuration files 2024-10-02 08:13:49 +02:00
Andras Bacsai
5354561c39 Merge pull request #3659 from coollabsio/next
v4.0.0-beta.349
2024-10-01 21:36:50 +02:00
Andras Bacsai
223cd37031 fix: remove autofocuses 2024-10-01 21:36:16 +02:00
Andras Bacsai
93a0725a4e Merge pull request #3613 from nfnot/main
refactor: Update search input placeholder in resource index view
2024-10-01 21:32:21 +02:00
Andras Bacsai
4d1d4598b6 chore: Update version numbers to 4.0.0-beta.349 and 4.0.0-beta.350 2024-10-01 21:31:54 +02:00
Norman
888e96448c Merge branch 'main' into main 2024-10-01 13:33:02 +03:00
Andras Bacsai
0eea31544e Merge pull request #3636 from coollabsio/next
v4.0.0-beta.348
2024-10-01 12:04:39 +02:00
Andras Bacsai
8065da9fa0 Refactor input fields to include autocomplete="off" 2024-10-01 11:55:01 +02:00
Andras Bacsai
5cd81fe255 wip: server storage check 2024-10-01 11:52:36 +02:00
Andras Bacsai
b00828d4aa chore: Refactor instanceSettings() function and improve code readability 2024-10-01 11:36:37 +02:00
Andras Bacsai
5a770f9ddb fix: Handle deletion of 'hello' in confirmation modal for dev environment 2024-10-01 11:16:16 +02:00
Andras Bacsai
903d84e0dd fix: confirmation text could be empty, then skip that part
fix: confirmation modal should not be closed by clicking away
2024-10-01 10:53:20 +02:00
Andras Bacsai
16ccc87fbe chore: Add "Not Usable" indicator for storage items 2024-10-01 10:45:03 +02:00
Andras Bacsai
e4108863a8 chore: Remove unnecessary code in DatabaseBackupJob.php 2024-10-01 10:43:04 +02:00
Andras Bacsai
83549965ca Refactor instanceSettings() function for improved code readability 2024-10-01 10:37:40 +02:00
Andras Bacsai
4d9dcfb84c chore: Update Coolify Realtime image to version 1.0.2 2024-10-01 10:34:11 +02:00
Andras Bacsai
4db50bd025 chore: Refactor instanceSettings() function to improve code readability 2024-10-01 10:33:56 +02:00
Andras Bacsai
817bd9c35d chore: Update Coolify Realtime workflow to only trigger on the main branch 2024-10-01 10:09:38 +02:00
Andras Bacsai
2e10d670be Merge pull request #3647 from derpoho/next
fixes coollabsio#3645, incorrect adding of sudo if non-root user
2024-10-01 10:00:48 +02:00
Andras Bacsai
cec0d7d1f7 Merge pull request #3651 from LEstradioto/fix-enhance-terminal-2
Fix Terminal: no more exit fallback
2024-10-01 09:52:38 +02:00
Andras Bacsai
1f45532477 fix: mixpost 2024-10-01 09:35:54 +02:00
Andras Bacsai
bcc92e1f32 fix: in dev mode do not ask confirmation on delete 2024-10-01 09:31:01 +02:00
Andras Bacsai
31cf3294bf Update UUID description in API controller and OpenAPI specification 2024-10-01 09:04:01 +02:00
Andras Bacsai
26307334a4 feat: Add query parameters for deleting configurations, volumes, docker cleanup, and connected networks 2024-10-01 09:02:29 +02:00
Andras Bacsai
9212f3b24c feat: Update resource deletion job to allow configurable options through API 2024-10-01 09:02:16 +02:00
Andras Bacsai
31b2196ad9 Merge pull request #3650 from ImBIOS/patch-1
chore: fix docs link in running state
2024-10-01 07:40:59 +02:00
Luan Estradioto
2c38af1df0 - fixes: no more exit fallback to server 2024-10-01 01:42:09 -03:00
Imamuzzaki Abu Salam
89e84c5b48 chore: fix docs link in running state 2024-10-01 10:16:16 +07:00
Marcus Pohorely
39d2fdc08b Merge branch 'main' into next 2024-09-30 23:24:42 +02:00
Marcus Pohorely
ac9f817b9f fixes #3645, incorrect adding of sudo if non-root user 2024-09-30 23:21:58 +02:00
Andras Bacsai
852e881805 Merge pull request #2351 from AlejandroAkbal/main
feat: add Mixpost template
2024-09-30 16:35:55 +02:00
Andras Bacsai
001dec6604 chore: Update Mailpit logo to use SVG format 2024-09-30 16:31:36 +02:00
Andras Bacsai
cf9c62cbf0 Merge pull request #3202 from FranckKe/add_mailpit_template
feat: add Mailpit template
2024-09-30 16:16:05 +02:00
Andras Bacsai
99a4f721f1 Merge pull request #2904 from LEstradioto/add-storage-link-to-dev-environment
add storage:link to dev environment
2024-09-30 15:48:10 +02:00
Andras Bacsai
505be9dc97 add debugbar config 2024-09-30 15:44:07 +02:00
Andras Bacsai
cfec5b3a70 feat: Add support for use_build_server in API endpoints for creating/updating applications 2024-09-30 14:18:41 +02:00
Andras Bacsai
95483f1464 Merge pull request #3615 from liberocks/feature/api-use-build-server
feat: allow specify use_build_server when creating/updating an application through API
2024-09-30 14:17:59 +02:00
Andras Bacsai
987b90ead2 fix: new dev volumes and service files
fix: new parser version (4) that will fix data layout (applications goes to /applications, services goes to /services)
2024-09-30 14:16:37 +02:00
Andras Bacsai
68f541ded3 fix: filebrowser template 2024-09-30 14:16:37 +02:00
Andras Bacsai
afdf4cd5a8 service restart does no cleanup docker 2024-09-30 14:16:37 +02:00
Andras Bacsai
11dbce1471 Merge pull request #3630 from jordanamr/fix/filebrowser-template
fix(templates): filebrowser FQDN env variable
2024-09-30 14:16:14 +02:00
Andras Bacsai
e1037ac031 Merge branch 'next' into fix/filebrowser-template 2024-09-30 14:16:01 +02:00
Andras Bacsai
88c2b1e841 Merge pull request #3609 from peaklabs-dev/fix-2185
Fix: Remove memlock from dragonfly DB as it caused problems for some users
2024-09-30 12:18:38 +02:00
Andras Bacsai
f46fd8872a Merge pull request #3608 from peaklabs-dev/add-debugbar
Feat: Add debug bar for better debugging
2024-09-30 12:15:04 +02:00
Andras Bacsai
b6b4d93658 fix: compose based terminal 2024-09-30 11:58:28 +02:00
Andras Bacsai
8385b7dfe8 fix: handle edge case when build variables and env variables are in different format 2024-09-30 11:15:23 +02:00
Andras Bacsai
a660117015 use latest helper in dev 2024-09-30 11:14:53 +02:00
Andras Bacsai
6fe31c26a3 refactor: remove unnecessary code 2024-09-30 11:14:40 +02:00
Andras Bacsai
cccd05f322 chore: Refactor code to improve SSH key handling and storage 2024-09-29 20:12:30 +02:00
Andras Bacsai
795af8de52 Make sure key is not created with wrong private_key 2024-09-29 20:05:02 +02:00
Jordan Rey
606d46eef0 fix(templates): filebrowser FQDN env variable 2024-09-29 15:36:41 +02:00
Andras Bacsai
56ec0a3004 chore: Update version numbers to 4.0.0-beta.348 2024-09-29 13:27:45 +02:00
Norman
b84576ce87 Merge branch 'main' into main 2024-09-28 18:18:47 +03:00
liberocks
f5785b1f17 fix: save settings after assigning value 2024-09-28 11:28:48 +07:00
liberocks
2219219906 fix: edit is_build_server_enabled upon creating application on other application type 2024-09-28 11:25:00 +07:00
liberocks
9ec3233c0c feat: allow specify use_build_server when creating/updating an application 2024-09-28 11:11:43 +07:00
Norman
f2a9a04461 refactor: Update search input placeholder in resource index view 2024-09-28 01:14:54 +00:00
peaklabs-dev
b99474ac73 Fix: Remove memlock as it caused problems for some users 2024-09-27 17:38:34 +02:00
peaklabs-dev
c237cea280 Merge branch 'coollabsio:main' into add-debugbar 2024-09-27 16:59:21 +02:00
peaklabs-dev
26f18e25aa Create .gitignore 2024-09-27 16:54:24 +02:00
peaklabs-dev
dce8555aa7 Merge branch 'coollabsio:main' into add-debugbar 2024-09-18 20:42:29 +02:00
peaklabs-dev
894f2c66d3 Merge branch 'coollabsio:main' into add-debugbar 2024-09-12 13:03:56 +02:00
peaklabs-dev
baa236f934 Feat: Add debug bar 2024-09-11 16:46:14 +02:00
Franck Kerbiriou
7e154ba3c3 Add Mailpit template 2024-08-22 22:52:20 +02:00
Luan Estradioto
1138ec0dea add storage:link to dev environment 2024-07-21 13:30:28 -03:00
Alejandro Akbal
21fc8efb86 feat: add Mixpost template 2024-06-04 13:13:37 +01:00
145 changed files with 2061 additions and 466 deletions

View File

@@ -0,0 +1,103 @@
name: Coolify Realtime Development (v4)
on:
push:
branches: [ "next" ]
paths:
- .github/workflows/coolify-realtime.yml
- docker/coolify-realtime/Dockerfile
- docker/coolify-realtime/terminal-server.js
- docker/coolify-realtime/package.json
- docker/coolify-realtime/soketi-entrypoint.sh
env:
REGISTRY: ghcr.io
IMAGE_NAME: "coollabsio/coolify-realtime"
jobs:
amd64:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to ghcr.io
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- name: Build image and push to registry
uses: docker/build-push-action@v5
with:
no-cache: true
context: .
file: docker/coolify-realtime/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
labels: |
coolify.managed=true
aarch64:
runs-on: [ self-hosted, arm64 ]
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to ghcr.io
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- name: Build image and push to registry
uses: docker/build-push-action@v5
with:
no-cache: true
context: .
file: docker/coolify-realtime/Dockerfile
platforms: linux/aarch64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64
labels: |
coolify.managed=true
merge-manifest:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
needs: [ amd64, aarch64 ]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to ghcr.io
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- name: Create & publish manifest
run: |
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
- uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}

View File

@@ -2,7 +2,7 @@ name: Coolify Realtime (v4)
on:
push:
branches: [ "main", "next" ]
branches: [ "main" ]
paths:
- .github/workflows/coolify-realtime.yml
- docker/coolify-realtime/Dockerfile

View File

@@ -46,9 +46,6 @@ class StartDragonfly
'networks' => [
$this->database->destination->network,
],
'ulimits' => [
'memlock' => '-1',
],
'labels' => [
'coolify.managed' => 'true',
],

View File

@@ -2,7 +2,6 @@
namespace App\Actions\Fortify;
use App\Models\InstanceSettings;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
@@ -20,7 +19,7 @@ class CreateNewUser implements CreatesNewUsers
*/
public function create(array $input): User
{
$settings = InstanceSettings::get();
$settings = instanceSettings();
if (! $settings->is_registration_enabled) {
abort(403);
}
@@ -48,7 +47,7 @@ class CreateNewUser implements CreatesNewUsers
$team = $user->teams()->first();
// Disable registration after first user is created
$settings = InstanceSettings::get();
$settings = instanceSettings();
$settings->is_registration_enabled = false;
$settings->save();
} else {

View File

@@ -2,7 +2,6 @@
namespace App\Actions\License;
use App\Models\InstanceSettings;
use Illuminate\Support\Facades\Http;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -13,7 +12,7 @@ class CheckResaleLicense
public function handle()
{
try {
$settings = InstanceSettings::get();
$settings = instanceSettings();
if (isDev()) {
$settings->update([
'is_resale_license_active' => true,

View File

@@ -2,14 +2,17 @@
namespace App\Actions\Proxy;
use App\Enums\ProxyTypes;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class CheckProxy
{
use AsAction;
public function handle(Server $server, $fromUI = false)
// It should return if the proxy should be started (true) or not (false)
public function handle(Server $server, $fromUI = false): bool
{
if (! $server->isFunctional()) {
return false;
@@ -62,22 +65,42 @@ class CheckProxy
$ip = 'host.docker.internal';
}
$connection80 = @fsockopen($ip, '80');
$connection443 = @fsockopen($ip, '443');
$port80 = is_resource($connection80) && fclose($connection80);
$port443 = is_resource($connection443) && fclose($connection443);
if ($port80) {
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>");
$portsToCheck = ['80', '443'];
try {
if ($server->proxyType() !== ProxyTypes::NONE->value) {
$proxyCompose = CheckConfiguration::run($server);
if (isset($proxyCompose)) {
$yaml = Yaml::parse($proxyCompose);
$portsToCheck = [];
if ($server->proxyType() === ProxyTypes::TRAEFIK->value) {
$ports = data_get($yaml, 'services.traefik.ports');
} elseif ($server->proxyType() === ProxyTypes::CADDY->value) {
$ports = data_get($yaml, 'services.caddy.ports');
}
if (isset($ports)) {
foreach ($ports as $port) {
$portsToCheck[] = str($port)->before(':')->value();
}
}
}
} else {
return false;
$portsToCheck = [];
}
} catch (\Exception $e) {
ray($e->getMessage());
}
if ($port443) {
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>");
} else {
return false;
if (count($portsToCheck) === 0) {
return false;
}
foreach ($portsToCheck as $port) {
$connection = @fsockopen($ip, $port);
if (is_resource($connection) && fclose($connection)) {
if ($fromUI) {
throw new \Exception("Port $port 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 {
return false;
}
}
}

View File

@@ -2,7 +2,6 @@
namespace App\Actions\Server;
use App\Models\InstanceSettings;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -12,7 +11,7 @@ class CleanupDocker
public function handle(Server $server)
{
$settings = InstanceSettings::get();
$settings = instanceSettings();
$helperImageVersion = data_get($settings, 'helper_version');
$helperImage = config('coolify.helper_image');
$helperImageWithVersion = "$helperImage:$helperImageVersion";

View File

@@ -3,7 +3,6 @@
namespace App\Actions\Server;
use App\Jobs\PullHelperImageJob;
use App\Models\InstanceSettings;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -20,7 +19,7 @@ class UpdateCoolify
public function handle($manual_update = false)
{
try {
$settings = InstanceSettings::get();
$settings = instanceSettings();
$this->server = Server::find(0);
if (! $this->server) {
return;

View File

@@ -0,0 +1,50 @@
<?php
namespace App\Console\Commands;
use App\Enums\ApplicationDeploymentStatus;
use App\Models\ApplicationDeploymentQueue;
use Illuminate\Console\Command;
class CheckApplicationDeploymentQueue extends Command
{
protected $signature = 'check:deployment-queue {--force} {--seconds=3600}';
protected $description = 'Check application deployment queue.';
public function handle()
{
$seconds = $this->option('seconds');
$deployments = ApplicationDeploymentQueue::whereIn('status', [
ApplicationDeploymentStatus::IN_PROGRESS,
ApplicationDeploymentStatus::QUEUED,
])->where('created_at', '<=', now()->subSeconds($seconds))->get();
if ($deployments->isEmpty()) {
$this->info('No deployments found in the last '.$seconds.' seconds.');
return;
}
$this->info('Found '.$deployments->count().' deployments created in the last '.$seconds.' seconds.');
foreach ($deployments as $deployment) {
if ($this->option('force')) {
$this->info('Deployment '.$deployment->id.' created at '.$deployment->created_at.' is older than '.$seconds.' seconds. Setting status to failed.');
$this->cancelDeployment($deployment);
} else {
$this->info('Deployment '.$deployment->id.' created at '.$deployment->created_at.' is older than '.$seconds.' seconds. Setting status to failed.');
if ($this->confirm('Do you want to cancel this deployment?', true)) {
$this->cancelDeployment($deployment);
}
}
}
}
private function cancelDeployment(ApplicationDeploymentQueue $deployment)
{
$deployment->update(['status' => ApplicationDeploymentStatus::FAILED]);
if ($deployment->server?->isFunctional()) {
remote_process(['docker rm -f '.$deployment->deployment_uuid], $deployment->server, false);
}
}
}

View File

@@ -7,9 +7,9 @@ use Illuminate\Console\Command;
class CleanupApplicationDeploymentQueue extends Command
{
protected $signature = 'cleanup:application-deployment-queue {--team-id=}';
protected $signature = 'cleanup:deployment-queue {--team-id=}';
protected $description = 'CleanupApplicationDeploymentQueue';
protected $description = 'Cleanup application deployment queue.';
public function handle()
{

View File

@@ -4,6 +4,7 @@ namespace App\Console\Commands;
use App\Jobs\CleanupHelperContainersJob;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\ApplicationPreview;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledTask;
@@ -47,6 +48,17 @@ class CleanupStuckedResources extends Command
} catch (\Throwable $e) {
echo "Error in cleaning stucked resources: {$e->getMessage()}\n";
}
try {
$applicationsDeploymentQueue = ApplicationDeploymentQueue::get();
foreach ($applicationsDeploymentQueue as $applicationDeploymentQueue) {
if (is_null($applicationDeploymentQueue->application)) {
echo "Deleting stuck application deployment queue: {$applicationDeploymentQueue->id}\n";
$applicationDeploymentQueue->delete();
}
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck application deployment queue: {$e->getMessage()}\n";
}
try {
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($applications as $application) {

View File

@@ -48,6 +48,13 @@ class Dev extends Command
echo "Generating APP_KEY.\n";
Artisan::call('key:generate');
}
// Generate STORAGE link if not exists
if (! file_exists(public_path('storage'))) {
echo "Generating STORAGE link.\n";
Artisan::call('storage:link');
}
// Seed database if it's empty
$settings = InstanceSettings::find(0);
if (! $settings) {

View File

@@ -7,7 +7,6 @@ use App\Enums\ActivityTypes;
use App\Enums\ApplicationDeploymentStatus;
use App\Models\ApplicationDeploymentQueue;
use App\Models\Environment;
use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
use App\Models\Server;
use App\Models\StandalonePostgresql;
@@ -69,7 +68,7 @@ class Init extends Command
} catch (\Throwable $e) {
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
}
$settings = InstanceSettings::get();
$settings = instanceSettings();
if (! is_null(env('AUTOUPDATE', null))) {
if (env('AUTOUPDATE') == true) {
$settings->update(['is_auto_update_enabled' => true]);
@@ -196,7 +195,7 @@ class Init extends Command
{
$id = config('app.id');
$version = config('version');
$settings = InstanceSettings::get();
$settings = instanceSettings();
$do_not_track = data_get($settings, 'do_not_track');
if ($do_not_track == true) {
echo "Skipping alive as do_not_track is enabled\n";

View File

@@ -12,8 +12,8 @@ use App\Jobs\PullSentinelImageJob;
use App\Jobs\PullTemplatesFromCDN;
use App\Jobs\ScheduledTaskJob;
use App\Jobs\ServerCheckJob;
use App\Jobs\ServerStorageCheckJob;
use App\Jobs\UpdateCoolifyJob;
use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledTask;
use App\Models\Server;
@@ -28,7 +28,7 @@ class Kernel extends ConsoleKernel
protected function schedule(Schedule $schedule): void
{
$this->all_servers = Server::all();
$settings = InstanceSettings::get();
$settings = instanceSettings();
$schedule->job(new CleanupStaleMultiplexedConnections)->hourly();
@@ -66,7 +66,7 @@ class Kernel extends ConsoleKernel
private function pull_images($schedule)
{
$settings = InstanceSettings::get();
$settings = instanceSettings();
$servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
foreach ($servers as $server) {
if ($server->isSentinelEnabled()) {
@@ -88,7 +88,7 @@ class Kernel extends ConsoleKernel
private function schedule_updates($schedule)
{
$settings = InstanceSettings::get();
$settings = instanceSettings();
$updateCheckFrequency = $settings->update_check_frequency;
$schedule->job(new CheckForUpdatesJob)
@@ -116,6 +116,7 @@ class Kernel extends ConsoleKernel
}
foreach ($servers as $server) {
$schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
// $schedule->job(new ServerStorageCheckJob($server))->everyMinute()->onOneServer();
$serverTimezone = $server->settings->server_timezone;
if ($server->settings->force_docker_cleanup) {
$schedule->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer();

View File

@@ -65,7 +65,7 @@ class Handler extends ExceptionHandler
if ($e instanceof RuntimeException) {
return;
}
$this->settings = \App\Models\InstanceSettings::get();
$this->settings = instanceSettings();
if ($this->settings->do_not_track) {
return;
}

View File

@@ -94,7 +94,9 @@ class SshMultiplexingHelper
$muxPersistTime = config('constants.ssh.mux_persist_time');
$scp_command = "timeout $timeout scp ";
if ($server->isIpv6()) {
$scp_command .= '-6 ';
}
if (self::isMultiplexingEnabled()) {
$scp_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
self::ensureMultiplexedConnection($server);
@@ -136,8 +138,8 @@ class SshMultiplexingHelper
$ssh_command .= self::getCommonSshOptions($server, $sshKeyLocation, config('constants.ssh.connection_timeout'), config('constants.ssh.server_interval'));
$command = "PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin && $command";
$delimiter = Hash::make($command);
$delimiter = base64_encode($delimiter);
$command = str_replace($delimiter, '', $command);
$ssh_command .= "{$server->user}@{$server->ip} 'bash -se' << \\$delimiter".PHP_EOL

View File

@@ -177,6 +177,7 @@ class ApplicationsController extends Controller
'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -279,6 +280,7 @@ class ApplicationsController extends Controller
'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -381,6 +383,7 @@ class ApplicationsController extends Controller
'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -468,6 +471,7 @@ class ApplicationsController extends Controller
'manual_webhook_secret_gitea' => ['type' => 'string', 'description' => 'Manual webhook secret for Gitea.'],
'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -552,6 +556,7 @@ class ApplicationsController extends Controller
'manual_webhook_secret_gitea' => ['type' => 'string', 'description' => 'Manual webhook secret for Gitea.'],
'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -602,6 +607,7 @@ class ApplicationsController extends Controller
'name' => ['type' => 'string', 'description' => 'The application name.'],
'description' => ['type' => 'string', 'description' => 'The application description.'],
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -627,7 +633,7 @@ class ApplicationsController extends Controller
private function create_application(Request $request, $type)
{
$allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths'];
$allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server'];
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
@@ -665,6 +671,7 @@ class ApplicationsController extends Controller
$fqdn = $request->domains;
$instantDeploy = $request->instant_deploy;
$githubAppUuid = $request->github_app_uuid;
$useBuildServer = $request->use_build_server;
$project = Project::whereTeamId($teamId)->whereUuid($request->project_uuid)->first();
if (! $project) {
@@ -737,6 +744,10 @@ class ApplicationsController extends Controller
$application->destination_id = $destination->id;
$application->destination_type = $destination->getMorphClass();
$application->environment_id = $environment->id;
if (isset($useBuildServer)) {
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
}
$application->save();
$application->refresh();
if (! $application->settings->is_container_label_readonly_enabled) {
@@ -833,6 +844,10 @@ class ApplicationsController extends Controller
$application->environment_id = $environment->id;
$application->source_type = $githubApp->getMorphClass();
$application->source_id = $githubApp->id;
if (isset($useBuildServer)) {
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
}
$application->save();
$application->refresh();
if (! $application->settings->is_container_label_readonly_enabled) {
@@ -925,6 +940,10 @@ class ApplicationsController extends Controller
$application->destination_id = $destination->id;
$application->destination_type = $destination->getMorphClass();
$application->environment_id = $environment->id;
if (isset($useBuildServer)) {
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
}
$application->save();
$application->refresh();
if (! $application->settings->is_container_label_readonly_enabled) {
@@ -1004,6 +1023,10 @@ class ApplicationsController extends Controller
$application->destination_id = $destination->id;
$application->destination_type = $destination->getMorphClass();
$application->environment_id = $environment->id;
if (isset($useBuildServer)) {
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
}
$application->git_repository = 'coollabsio/coolify';
$application->git_branch = 'main';
@@ -1062,6 +1085,10 @@ class ApplicationsController extends Controller
$application->destination_id = $destination->id;
$application->destination_type = $destination->getMorphClass();
$application->environment_id = $environment->id;
if (isset($useBuildServer)) {
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
}
$application->git_repository = 'coollabsio/coolify';
$application->git_branch = 'main';
@@ -1259,16 +1286,10 @@ class ApplicationsController extends Controller
format: 'uuid',
)
),
new OA\Parameter(
name: 'cleanup',
in: 'query',
description: 'Delete configurations and volumes.',
required: false,
schema: new OA\Schema(
type: 'boolean',
default: true,
)
),
new OA\Parameter(name: 'delete_configurations', in: 'query', required: false, description: 'Delete configurations.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'delete_volumes', in: 'query', required: false, description: 'Delete volumes.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'docker_cleanup', in: 'query', required: false, description: 'Run docker cleanup.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'delete_connected_networks', in: 'query', required: false, description: 'Delete connected networks.', schema: new OA\Schema(type: 'boolean', default: true)),
],
responses: [
new OA\Response(
@@ -1316,10 +1337,14 @@ class ApplicationsController extends Controller
'message' => 'Application not found',
], 404);
}
DeleteResourceJob::dispatch(
resource: $application,
deleteConfigurations: $cleanup,
deleteVolumes: $cleanup);
deleteConfigurations: $request->query->get('delete_configurations', true),
deleteVolumes: $request->query->get('delete_volumes', true),
dockerCleanup: $request->query->get('docker_cleanup', true),
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
);
return response()->json([
'message' => 'Application deletion request queued.',
@@ -1404,6 +1429,7 @@ class ApplicationsController extends Controller
'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -1460,7 +1486,7 @@ class ApplicationsController extends Controller
], 404);
}
$server = $application->destination->server;
$allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy'];
$allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server'];
$validator = customApiValidator($request->all(), [
sharedDataApplications(),
@@ -1538,6 +1564,13 @@ class ApplicationsController extends Controller
}
$instantDeploy = $request->instant_deploy;
$use_build_server = $request->use_build_server;
if (isset($use_build_server)) {
$application->settings->is_build_server_enabled = $use_build_server;
$application->settings->save();
}
removeUnnecessaryFieldsFromRequest($request);
$data = $request->all();

View File

@@ -1541,16 +1541,10 @@ class DatabasesController extends Controller
format: 'uuid',
)
),
new OA\Parameter(
name: 'cleanup',
in: 'query',
description: 'Delete configurations and volumes.',
required: false,
schema: new OA\Schema(
type: 'boolean',
default: true,
)
),
new OA\Parameter(name: 'delete_configurations', in: 'query', required: false, description: 'Delete configurations.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'delete_volumes', in: 'query', required: false, description: 'Delete volumes.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'docker_cleanup', in: 'query', required: false, description: 'Run docker cleanup.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'delete_connected_networks', in: 'query', required: false, description: 'Delete connected networks.', schema: new OA\Schema(type: 'boolean', default: true)),
],
responses: [
new OA\Response(
@@ -1595,10 +1589,14 @@ class DatabasesController extends Controller
if (! $database) {
return response()->json(['message' => 'Database not found.'], 404);
}
DeleteResourceJob::dispatch(
resource: $database,
deleteConfigurations: $cleanup,
deleteVolumes: $cleanup);
deleteConfigurations: $request->query->get('delete_configurations', true),
deleteVolumes: $request->query->get('delete_volumes', true),
dockerCleanup: $request->query->get('docker_cleanup', true),
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
);
return response()->json([
'message' => 'Database deletion request queued.',

View File

@@ -86,7 +86,7 @@ class OtherController extends Controller
if ($teamId !== '0') {
return response()->json(['message' => 'You are not allowed to enable the API.'], 403);
}
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
$settings->update(['is_api_enabled' => true]);
return response()->json(['message' => 'API enabled.'], 200);
@@ -138,7 +138,7 @@ class OtherController extends Controller
if ($teamId !== '0') {
return response()->json(['message' => 'You are not allowed to disable the API.'], 403);
}
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
$settings->update(['is_api_enabled' => false]);
return response()->json(['message' => 'API disabled.'], 200);

View File

@@ -308,7 +308,7 @@ class ServersController extends Controller
$projects = Project::where('team_id', $teamId)->get();
$domains = collect();
$applications = $projects->pluck('applications')->flatten();
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
if ($applications->count() > 0) {
foreach ($applications as $application) {
$ip = $application->destination->server->ip;

View File

@@ -432,6 +432,10 @@ class ServicesController extends Controller
tags: ['Services'],
parameters: [
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Service UUID', schema: new OA\Schema(type: 'string')),
new OA\Parameter(name: 'delete_configurations', in: 'query', required: false, description: 'Delete configurations.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'delete_volumes', in: 'query', required: false, description: 'Delete volumes.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'docker_cleanup', in: 'query', required: false, description: 'Run docker cleanup.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'delete_connected_networks', in: 'query', required: false, description: 'Delete connected networks.', schema: new OA\Schema(type: 'boolean', default: true)),
],
responses: [
new OA\Response(
@@ -476,7 +480,14 @@ class ServicesController extends Controller
if (! $service) {
return response()->json(['message' => 'Service not found.'], 404);
}
DeleteResourceJob::dispatch($service);
DeleteResourceJob::dispatch(
resource: $service,
deleteConfigurations: $request->query->get('delete_configurations', true),
deleteVolumes: $request->query->get('delete_volumes', true),
dockerCleanup: $request->query->get('docker_cleanup', true),
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
);
return response()->json([
'message' => 'Service deletion request queued.',
@@ -516,7 +527,8 @@ class ServicesController extends Controller
items: new OA\Items(ref: '#/components/schemas/EnvironmentVariable')
)
),
]),
]
),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -619,7 +631,8 @@ class ServicesController extends Controller
]
)
),
]),
]
),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -738,7 +751,8 @@ class ServicesController extends Controller
]
)
),
]),
]
),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -853,7 +867,8 @@ class ServicesController extends Controller
]
)
),
]),
]
),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -953,7 +968,8 @@ class ServicesController extends Controller
]
)
),
]),
]
),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -1025,9 +1041,11 @@ class ServicesController extends Controller
type: 'object',
properties: [
'message' => ['type' => 'string', 'example' => 'Service starting request queued.'],
])
]
)
),
]),
]
),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -1101,9 +1119,11 @@ class ServicesController extends Controller
type: 'object',
properties: [
'message' => ['type' => 'string', 'example' => 'Service stopping request queued.'],
])
]
)
),
]),
]
),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -1177,9 +1197,11 @@ class ServicesController extends Controller
type: 'object',
properties: [
'message' => ['type' => 'string', 'example' => 'Service restaring request queued.'],
])
]
)
),
]),
]
),
new OA\Response(
response: 401,
ref: '#/components/responses/401',

View File

@@ -2,7 +2,6 @@
namespace App\Http\Controllers;
use App\Models\InstanceSettings;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpKernel\Exception\HttpException;
@@ -22,7 +21,7 @@ class OauthController extends Controller
$oauthUser = get_socialite_provider($provider)->user();
$user = User::whereEmail($oauthUser->email)->first();
if (! $user) {
$settings = InstanceSettings::get();
$settings = instanceSettings();
if (! $settings->is_registration_enabled) {
abort(403, 'Registration is disabled');
}

View File

@@ -14,7 +14,7 @@ class ApiAllowed
if (isCloud()) {
return $next($request);
}
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
if ($settings->is_api_enabled === false) {
return response()->json(['success' => true, 'message' => 'API is disabled.'], 403);
}

View File

@@ -12,7 +12,6 @@ use App\Models\ApplicationPreview;
use App\Models\EnvironmentVariable;
use App\Models\GithubApp;
use App\Models\GitlabApp;
use App\Models\InstanceSettings;
use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
@@ -962,7 +961,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}
}
if ($this->application->environment_variables->where('key', 'COOLIFY_FQDN')->isEmpty()) {
if ($this->application->compose_parsing_version === '3') {
if ((int) $this->application->compose_parsing_version >= 3) {
$envs->push("COOLIFY_URL={$this->application->fqdn}");
} else {
$envs->push("COOLIFY_FQDN={$this->application->fqdn}");
@@ -970,7 +969,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}
if ($this->application->environment_variables->where('key', 'COOLIFY_URL')->isEmpty()) {
$url = str($this->application->fqdn)->replace('http://', '')->replace('https://', '');
if ($this->application->compose_parsing_version === '3') {
if ((int) $this->application->compose_parsing_version >= 3) {
$envs->push("COOLIFY_FQDN={$url}");
} else {
$envs->push("COOLIFY_URL={$url}");
@@ -1334,7 +1333,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
private function prepare_builder_image()
{
$settings = InstanceSettings::get();
$settings = instanceSettings();
$helperImage = config('coolify.helper_image');
$helperImage = "{$helperImage}:{$settings->helper_version}";
// Get user home directory

View File

@@ -2,7 +2,6 @@
namespace App\Jobs;
use App\Models\InstanceSettings;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
@@ -22,7 +21,7 @@ class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue
if (isDev() || isCloud()) {
return;
}
$settings = InstanceSettings::get();
$settings = instanceSettings();
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
if ($response->successful()) {
$versions = $response->json();

View File

@@ -2,9 +2,7 @@
namespace App\Jobs;
use App\Actions\Database\StopDatabase;
use App\Events\BackupCreated;
use App\Models\InstanceSettings;
use App\Models\S3Storage;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledDatabaseBackupExecution;
@@ -25,7 +23,6 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Str;
use Visus\Cuid2\Cuid2;
class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
{
@@ -64,32 +61,32 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
public function __construct($backup)
{
$this->backup = $backup;
$this->team = Team::find($backup->team_id);
if (is_null($this->team)) {
return;
}
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
$this->database = data_get($this->backup, 'database');
$this->server = $this->database->service->server;
$this->s3 = $this->backup->s3;
} else {
$this->database = data_get($this->backup, 'database');
$this->server = $this->database->destination->server;
$this->s3 = $this->backup->s3;
}
}
public function handle(): void
{
try {
// Check if team is exists
if (is_null($this->team)) {
$this->backup->update(['status' => 'failed']);
StopDatabase::run($this->database);
$this->database->delete();
$this->team = Team::find($this->backup->team_id);
if (! $this->team) {
$this->backup->delete();
return;
}
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
$this->database = data_get($this->backup, 'database');
$this->server = $this->database->service->server;
$this->s3 = $this->backup->s3;
} else {
$this->database = data_get($this->backup, 'database');
$this->server = $this->database->destination->server;
$this->s3 = $this->backup->s3;
}
if (is_null($this->server)) {
throw new \Exception('Server not found?!');
}
if (is_null($this->database)) {
throw new \Exception('Database not found?!');
}
BackupCreated::dispatch($this->team->id);
@@ -239,7 +236,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
}
}
$this->backup_dir = backup_dir().'/databases/'.str($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name;
if ($this->database->name === 'coolify-db') {
$databasesToBackup = ['coolify'];
$this->directory_name = $this->container_name = 'coolify-db';
@@ -252,6 +248,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
try {
if (str($databaseType)->contains('postgres')) {
$this->backup_file = "/pg-dump-$database-".Carbon::now()->timestamp.'.dmp';
if ($this->backup->dump_all) {
$this->backup_file = '/pg-dump-all-'.Carbon::now()->timestamp.'.gz';
}
$this->backup_location = $this->backup_dir.$this->backup_file;
$this->backup_log = ScheduledDatabaseBackupExecution::create([
'database_name' => $database,
@@ -280,6 +279,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
$this->backup_standalone_mongodb($database);
} elseif (str($databaseType)->contains('mysql')) {
$this->backup_file = "/mysql-dump-$database-".Carbon::now()->timestamp.'.dmp';
if ($this->backup->dump_all) {
$this->backup_file = '/mysql-dump-all-'.Carbon::now()->timestamp.'.gz';
}
$this->backup_location = $this->backup_dir.$this->backup_file;
$this->backup_log = ScheduledDatabaseBackupExecution::create([
'database_name' => $database,
@@ -289,6 +291,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
$this->backup_standalone_mysql($database);
} elseif (str($databaseType)->contains('mariadb')) {
$this->backup_file = "/mariadb-dump-$database-".Carbon::now()->timestamp.'.dmp';
if ($this->backup->dump_all) {
$this->backup_file = '/mariadb-dump-all-'.Carbon::now()->timestamp.'.gz';
}
$this->backup_location = $this->backup_dir.$this->backup_file;
$this->backup_log = ScheduledDatabaseBackupExecution::create([
'database_name' => $database,
@@ -327,7 +332,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
send_internal_notification('DatabaseBackupJob failed with: '.$e->getMessage());
throw $e;
} finally {
BackupCreated::dispatch($this->team->id);
if ($this->team) {
BackupCreated::dispatch($this->team->id);
}
}
}
@@ -386,7 +393,11 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
if ($this->postgres_password) {
$backupCommand .= " -e PGPASSWORD=$this->postgres_password";
}
$backupCommand .= " $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
if ($this->backup->dump_all) {
$backupCommand .= " $this->container_name pg_dumpall --username {$this->database->postgres_user} | gzip > $this->backup_location";
} else {
$backupCommand .= " $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
}
$commands[] = $backupCommand;
ray($commands);
@@ -407,8 +418,11 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
{
try {
$commands[] = 'mkdir -p '.$this->backup_dir;
$commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} $database > $this->backup_location";
ray($commands);
if ($this->backup->dump_all) {
$commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} --all-databases --single-transaction --quick --lock-tables=false --compress | gzip > $this->backup_location";
} else {
$commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} $database > $this->backup_location";
}
$this->backup_output = instant_remote_process($commands, $this->server);
$this->backup_output = trim($this->backup_output);
if ($this->backup_output === '') {
@@ -426,7 +440,11 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
{
try {
$commands[] = 'mkdir -p '.$this->backup_dir;
$commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} $database > $this->backup_location";
if ($this->backup->dump_all) {
$commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} --all-databases --single-transaction --quick --lock-tables=false --compress > $this->backup_location";
} else {
$commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} $database > $this->backup_location";
}
ray($commands);
$this->backup_output = instant_remote_process($commands, $this->server);
$this->backup_output = trim($this->backup_output);
@@ -468,34 +486,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
}
}
// private function upload_to_s3(): void
// {
// try {
// if (is_null($this->s3)) {
// return;
// }
// $key = $this->s3->key;
// $secret = $this->s3->secret;
// // $region = $this->s3->region;
// $bucket = $this->s3->bucket;
// $endpoint = $this->s3->endpoint;
// $this->s3->testConnection(shouldSave: true);
// $configName = new Cuid2;
// $s3_copy_dir = str($this->backup_location)->replace(backup_dir(), '/var/www/html/storage/app/backups/');
// $commands[] = "docker exec coolify bash -c 'mc config host add {$configName} {$endpoint} $key $secret'";
// $commands[] = "docker exec coolify bash -c 'mc cp $s3_copy_dir {$configName}/{$bucket}{$this->backup_dir}/'";
// instant_remote_process($commands, $this->server);
// $this->add_to_backup_output('Uploaded to S3.');
// } catch (\Throwable $e) {
// $this->add_to_backup_output($e->getMessage());
// throw $e;
// } finally {
// $removeConfigCommands[] = "docker exec coolify bash -c 'mc config remove {$configName}'";
// $removeConfigCommands[] = "docker exec coolify bash -c 'mc alias rm {$configName}'";
// instant_remote_process($removeConfigCommands, $this->server, false);
// }
// }
private function upload_to_s3(): void
{
try {
@@ -517,10 +507,27 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
$this->ensureHelperImageAvailable();
$fullImageName = $this->getFullImageName();
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}";
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret";
if (isDev()) {
if ($this->database->name === 'coolify-db') {
$backup_location_from = '/var/lib/docker/volumes/coolify_dev_backups_data/_data/coolify/coolify-db-'.$this->server->ip.$this->backup_file;
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $backup_location_from:$this->backup_location:ro {$fullImageName}";
} else {
$backup_location_from = '/var/lib/docker/volumes/coolify_dev_backups_data/_data/databases/'.str($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name.$this->backup_file;
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $backup_location_from:$this->backup_location:ro {$fullImageName}";
}
} else {
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}";
}
if ($this->s3->isHetzner()) {
$endpointWithoutBucket = 'https://'.str($endpoint)->after('https://')->after('.')->value();
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc alias set --path=off --api=S3v4 temporary {$endpointWithoutBucket} $key $secret";
} else {
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret";
}
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/";
instant_remote_process($commands, $this->server);
$this->add_to_backup_output('Uploaded to S3.');
} catch (\Throwable $e) {
$this->add_to_backup_output($e->getMessage());
@@ -562,7 +569,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
private function getFullImageName(): string
{
$settings = InstanceSettings::get();
$settings = instanceSettings();
$helperImage = config('coolify.helper_image');
$latestVersion = $settings->helper_version;

View File

@@ -31,10 +31,10 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
public function __construct(
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource,
public bool $deleteConfigurations,
public bool $deleteVolumes,
public bool $dockerCleanup,
public bool $deleteConnectedNetworks
public bool $deleteConfigurations = true,
public bool $deleteVolumes = true,
public bool $dockerCleanup = true,
public bool $deleteConnectedNetworks = true
) {}
public function handle()

View File

@@ -2,7 +2,6 @@
namespace App\Jobs;
use App\Models\InstanceSettings;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
@@ -26,7 +25,7 @@ class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
if ($response->successful()) {
$versions = $response->json();
$settings = InstanceSettings::get();
$settings = instanceSettings();
$latest_version = data_get($versions, 'coolify.helper.version');
$current_version = $settings->helper_version;
if (version_compare($latest_version, $current_version, '>')) {

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Jobs;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ServerStorageCheckJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 1;
public $timeout = 60;
public $containers;
public $applications;
public $databases;
public $services;
public $previews;
public function backoff(): int
{
return isDev() ? 1 : 3;
}
public function __construct(public Server $server) {}
public function handle()
{
try {
if (! $this->server->isFunctional()) {
ray('Server is not ready.');
return 'Server is not ready.';
}
$team = $this->server->team;
$percentage = $this->server->storageCheck();
if ($percentage > 1) {
ray('Server storage is at '.$percentage.'%');
}
} catch (\Throwable $e) {
ray($e->getMessage());
return handleError($e);
}
}
}

View File

@@ -3,7 +3,6 @@
namespace App\Jobs;
use App\Actions\Server\UpdateCoolify;
use App\Models\InstanceSettings;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
@@ -23,7 +22,7 @@ class UpdateCoolifyJob implements ShouldBeEncrypted, ShouldQueue
{
try {
CheckForUpdatesJob::dispatchSync();
$settings = InstanceSettings::get();
$settings = instanceSettings();
if (! $settings->new_version_available) {
Log::info('No new version available. Skipping update.');

View File

@@ -30,7 +30,7 @@ class Dashboard extends Component
public function cleanup_queue()
{
Artisan::queue('cleanup:application-deployment-queue', [
Artisan::queue('cleanup:deployment-queue', [
'--team-id' => currentTeam()->id,
]);
}

View File

@@ -47,7 +47,7 @@ class Help extends Component
]
);
$mail->subject("[HELP]: {$this->subject}");
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
$type = set_transanctional_email_settings($settings);
if (! $type) {
$url = 'https://app.coolify.io/api/feedback';

View File

@@ -172,7 +172,7 @@ class Email extends Component
public function copyFromInstanceSettings()
{
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
if ($settings->smtp_enabled) {
$team = currentTeam();
$team->update([

View File

@@ -31,6 +31,7 @@ class BackupEdit extends Component
'backup.save_s3' => 'required|boolean',
'backup.s3_storage_id' => 'nullable|integer',
'backup.databases_to_backup' => 'nullable',
'backup.dump_all' => 'required|boolean',
];
protected $validationAttributes = [
@@ -40,6 +41,7 @@ class BackupEdit extends Component
'backup.save_s3' => 'Save to S3',
'backup.s3_storage_id' => 'S3 Storage',
'backup.databases_to_backup' => 'Databases to Backup',
'backup.dump_all' => 'Backup All Databases',
];
protected $messages = [

View File

@@ -108,8 +108,23 @@ class Navbar extends Component
return;
}
StopService::run(service: $this->service, dockerCleanup: false);
$this->service->parse();
$this->dispatch('imagePulled');
$activity = StartService::run($this->service);
$this->dispatch('activityMonitor', $activity->id);
}
public function pullAndRestartEvent()
{
$this->checkDeployments();
if ($this->isDeploymentProgress) {
$this->dispatch('error', 'There is a deployment in progress.');
return;
}
PullImage::run($this->service);
StopService::run($this->service);
StopService::run(service: $this->service, dockerCleanup: false);
$this->service->parse();
$this->dispatch('imagePulled');
$activity = StartService::run($this->service);

View File

@@ -91,10 +91,12 @@ class Danger extends Component
public function delete($password)
{
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
if (isProduction()) {
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
return;
return;
}
}
if (! $this->resource) {

View File

@@ -11,6 +11,8 @@ use Livewire\Component;
class ExecuteContainerCommand extends Component
{
public $selected_container = 'default';
public $container;
public Collection $containers;
@@ -83,11 +85,14 @@ class ExecuteContainerCommand extends Component
$containers = getCurrentApplicationContainerStatus($server, $this->resource->id, includePullrequests: true);
}
foreach ($containers as $container) {
$payload = [
'server' => $server,
'container' => $container,
];
$this->containers = $this->containers->push($payload);
// if container state is running
if (data_get($container, 'State') === 'running') {
$payload = [
'server' => $server,
'container' => $container,
];
$this->containers = $this->containers->push($payload);
}
}
} elseif (data_get($this->parameters, 'database_uuid')) {
if ($this->resource->isRunning()) {
@@ -100,7 +105,6 @@ class ExecuteContainerCommand extends Component
}
} elseif (data_get($this->parameters, 'service_uuid')) {
$this->resource->applications()->get()->each(function ($application) {
ray($application);
if ($application->isRunning()) {
$this->containers->push([
'server' => $this->resource->server,
@@ -131,9 +135,14 @@ class ExecuteContainerCommand extends Component
#[On('connectToContainer')]
public function connectToContainer()
{
if ($this->selected_container === 'default') {
$this->dispatch('error', 'Please select a container.');
return;
}
try {
$container_name = data_get($this->container, 'container.Names');
if (is_null($container_name)) {
$container = collect($this->containers)->firstWhere('container.Names', $this->selected_container);
if (is_null($container)) {
throw new \RuntimeException('Container not found.');
}
$server = data_get($this->container, 'server');
@@ -141,11 +150,11 @@ class ExecuteContainerCommand extends Component
if ($server->isForceDisabled()) {
throw new \RuntimeException('Server is disabled.');
}
$this->dispatch('send-terminal-command',
true,
$container_name,
$server->uuid,
$this->dispatch(
'send-terminal-command',
isset($container),
data_get($container, 'container.Names'),
data_get($container, 'server.uuid')
);
} catch (\Throwable $e) {

View File

@@ -34,9 +34,9 @@ class Terminal extends Component
if ($status !== 'running') {
return;
}
$command = SshMultiplexingHelper::generateSshCommand($server, "docker exec -it {$identifier} sh -c 'if [ -f ~/.profile ]; then . ~/.profile; fi; if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'");
$command = SshMultiplexingHelper::generateSshCommand($server, "docker exec -it {$identifier} sh -c 'PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'");
} else {
$command = SshMultiplexingHelper::generateSshCommand($server, "sh -c 'if [ -f ~/.profile ]; then . ~/.profile; fi; if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'");
$command = SshMultiplexingHelper::generateSshCommand($server, 'PATH=$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n "$SHELL" ]; then exec $SHELL; else sh; fi');
}
// ssh command is sent back to frontend then to websocket

View File

@@ -15,6 +15,8 @@ class ApiTokens extends Component
public bool $readOnly = true;
public bool $rootAccess = false;
public array $permissions = ['read-only'];
public $isApiEnabled;
@@ -35,12 +37,11 @@ class ApiTokens extends Component
if ($this->viewSensitiveData) {
$this->permissions[] = 'view:sensitive';
$this->permissions = array_diff($this->permissions, ['*']);
$this->rootAccess = false;
} else {
$this->permissions = array_diff($this->permissions, ['view:sensitive']);
}
if (count($this->permissions) == 0) {
$this->permissions = ['*'];
}
$this->makeSureOneIsSelected();
}
public function updatedReadOnly()
@@ -48,11 +49,30 @@ class ApiTokens extends Component
if ($this->readOnly) {
$this->permissions[] = 'read-only';
$this->permissions = array_diff($this->permissions, ['*']);
$this->rootAccess = false;
} else {
$this->permissions = array_diff($this->permissions, ['read-only']);
}
if (count($this->permissions) == 0) {
$this->makeSureOneIsSelected();
}
public function updatedRootAccess()
{
if ($this->rootAccess) {
$this->permissions = ['*'];
$this->readOnly = false;
$this->viewSensitiveData = false;
} else {
$this->readOnly = true;
$this->permissions = ['read-only'];
}
}
public function makeSureOneIsSelected()
{
if (count($this->permissions) == 0) {
$this->permissions = ['read-only'];
$this->readOnly = true;
}
}
@@ -62,12 +82,6 @@ class ApiTokens extends Component
$this->validate([
'description' => 'required|min:3|max:255',
]);
// if ($this->viewSensitiveData) {
// $this->permissions[] = 'view:sensitive';
// }
// if ($this->readOnly) {
// $this->permissions[] = 'read-only';
// }
$token = auth()->user()->createToken($this->description, $this->permissions);
$this->tokens = auth()->user()->tokens;
session()->flash('token', $token->plainTextToken);

View File

@@ -30,6 +30,11 @@ class ConfigureCloudflareTunnels extends Component
public function submit()
{
try {
if (str($this->ssh_domain)->contains('https://')) {
$this->ssh_domain = str($this->ssh_domain)->replace('https://', '')->replace('http://', '')->trim();
// remove / from the end
$this->ssh_domain = str($this->ssh_domain)->replace('/', '');
}
$server = Server::ownedByCurrentTeam()->where('id', $this->server_id)->firstOrFail();
ConfigureCloudflared::dispatch($server, $this->cloudflare_token);
$server->settings->is_cloudflare_tunnel = true;

View File

@@ -4,7 +4,7 @@ namespace App\Livewire\Server\Proxy;
use App\Actions\Docker\GetContainersStatus;
use App\Actions\Proxy\CheckProxy;
use App\Jobs\ContainerStatusJob;
use App\Actions\Proxy\StartProxy;
use App\Models\Server;
use Livewire\Component;
@@ -44,7 +44,10 @@ class Status extends Component
}
$this->numberOfPolls++;
}
CheckProxy::run($this->server, true);
$shouldStart = CheckProxy::run($this->server, true);
if ($shouldStart) {
StartProxy::run($this->server, false);
}
$this->dispatch('proxyStatusUpdated');
if ($this->server->proxy->status === 'running') {
$this->polling = false;

View File

@@ -60,7 +60,7 @@ class Index extends Component
public function mount()
{
if (isInstanceAdmin()) {
$this->settings = InstanceSettings::get();
$this->settings = instanceSettings();
$this->do_not_track = $this->settings->do_not_track;
$this->is_auto_update_enabled = $this->settings->is_auto_update_enabled;
$this->is_registration_enabled = $this->settings->is_registration_enabled;
@@ -162,7 +162,7 @@ class Index extends Component
{
CheckForUpdatesJob::dispatchSync();
$this->dispatch('updateAvailable');
$settings = InstanceSettings::get();
$settings = instanceSettings();
if ($settings->new_version_available) {
$this->dispatch('success', 'New version available!');
} else {

View File

@@ -29,7 +29,7 @@ class License extends Component
abort(404);
}
$this->instance_id = config('app.id');
$this->settings = \App\Models\InstanceSettings::get();
$this->settings = instanceSettings();
}
public function render()

View File

@@ -42,7 +42,7 @@ class SettingsBackup extends Component
public function mount()
{
if (isInstanceAdmin()) {
$settings = InstanceSettings::get();
$settings = instanceSettings();
$this->database = StandalonePostgresql::whereName('coolify-db')->first();
$s3s = S3Storage::whereTeamId(0)->get() ?? [];
if ($this->database) {

View File

@@ -43,7 +43,7 @@ class SettingsEmail extends Component
public function mount()
{
if (isInstanceAdmin()) {
$this->settings = InstanceSettings::get();
$this->settings = instanceSettings();
$this->emails = auth()->user()->email;
} else {
return redirect()->route('dashboard');

View File

@@ -99,7 +99,7 @@ class Change extends Component
return redirect()->route('source.all');
}
$this->applications = $this->github_app->applications;
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
$this->name = str($this->github_app->name)->kebab();

View File

@@ -43,15 +43,17 @@ class Create extends Component
'endpoint' => 'Endpoint',
];
public function mount()
public function updatedEndpoint($value)
{
if (isDev()) {
$this->name = 'Local MinIO';
$this->description = 'Local MinIO';
$this->key = 'minioadmin';
$this->secret = 'minioadmin';
$this->bucket = 'local';
$this->endpoint = 'http://coolify-minio:9000';
if (! str($value)->startsWith('https://') && ! str($value)->startsWith('http://')) {
$this->endpoint = 'https://'.$value;
$value = $this->endpoint;
}
if (str($value)->contains('your-objectstorage.com') && ! isset($this->bucket)) {
$this->bucket = str($value)->after('//')->before('.');
} elseif (str($value)->contains('your-objectstorage.com')) {
$this->bucket = $this->bucket ?: str($value)->after('//')->before('.');
}
}

View File

@@ -23,7 +23,7 @@ class Index extends Component
if (data_get(currentTeam(), 'subscription') && isSubscriptionActive()) {
return redirect()->route('subscription.show');
}
$this->settings = \App\Models\InstanceSettings::get();
$this->settings = instanceSettings();
$this->alreadySubscribed = currentTeam()->subscription()->exists();
}

View File

@@ -104,7 +104,7 @@ class Application extends BaseModel
{
use SoftDeletes;
private static $parserVersion = '3';
private static $parserVersion = '4';
protected $guarded = [];
@@ -143,6 +143,9 @@ class Application extends BaseModel
}
$application->tags()->detach();
$application->previews()->delete();
foreach ($application->deployment_queue as $deployment) {
$deployment->delete();
}
});
}
@@ -304,7 +307,7 @@ class Application extends BaseModel
'application_uuid' => data_get($this, 'uuid'),
'task_uuid' => $task_uuid,
]);
$settings = InstanceSettings::get();
$settings = instanceSettings();
if (data_get($settings, 'fqdn')) {
$url = Url::fromString($route);
$url = $url->withPort(null);
@@ -710,6 +713,11 @@ class Application extends BaseModel
return $this->hasMany(ApplicationPreview::class);
}
public function deployment_queue()
{
return $this->hasMany(ApplicationDeploymentQueue::class);
}
public function destination()
{
return $this->morphTo();
@@ -1150,7 +1158,7 @@ class Application extends BaseModel
public function parse(int $pull_request_id = 0, ?int $preview_id = null)
{
if ($this->compose_parsing_version === '3') {
if ((int) $this->compose_parsing_version >= 3) {
return newParser($this, $pull_request_id, $preview_id);
} elseif ($this->docker_compose_raw) {
return parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, preview_id: $preview_id);

View File

@@ -2,6 +2,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use OpenApi\Attributes as OA;
@@ -39,6 +40,20 @@ class ApplicationDeploymentQueue extends Model
{
protected $guarded = [];
public function application(): Attribute
{
return Attribute::make(
get: fn () => Application::find($this->application_id),
);
}
public function server(): Attribute
{
return Attribute::make(
get: fn () => Server::find($this->server_id),
);
}
public function setStatus(string $status)
{
$this->update([

View File

@@ -126,11 +126,6 @@ class EnvironmentVariable extends Model
$env = $this->get_real_environment_variables($this->value, $resource);
return data_get($env, 'value', $env);
if (is_string($env)) {
return $env;
}
return $env->value;
}
);
}

View File

@@ -85,4 +85,17 @@ class InstanceSettings extends Model implements SendsEmail
return "[{$instanceName}]";
}
public function helperVersion(): Attribute
{
return Attribute::make(
get: function ($value) {
if (isDev()) {
return 'latest';
}
return $value;
}
);
}
}

View File

@@ -40,6 +40,16 @@ class S3Storage extends BaseModel
return "{$this->endpoint}/{$this->bucket}";
}
public function isHetzner()
{
return str($this->endpoint)->contains('your-objectstorage.com');
}
public function isDigitalOcean()
{
return str($this->endpoint)->contains('digitaloceanspaces.com');
}
public function testConnection(bool $shouldSave = false)
{
try {

View File

@@ -268,7 +268,7 @@ respond 404
public function setupDynamicProxyConfiguration()
{
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
$dynamic_config_path = $this->proxyPath().'/dynamic';
if ($this->proxyType() === ProxyTypes::TRAEFIK->value) {
$file = "$dynamic_config_path/coolify.yaml";
@@ -460,15 +460,6 @@ $schema://$host {
public function proxyType()
{
// $proxyType = $this->proxy->get('type');
// if ($proxyType === ProxyTypes::NONE->value) {
// return $proxyType;
// }
// if (is_null($proxyType)) {
// $this->proxy->type = ProxyTypes::TRAEFIK->value;
// $this->proxy->status = ProxyStatus::EXITED->value;
// $this->save();
// }
return data_get($this->proxy, 'type');
}
@@ -1212,4 +1203,18 @@ $schema://$host {
return $this;
}
public function storageCheck(): ?string
{
$commands = [
'df / --output=pcent | tr -cd 0-9',
];
return instant_remote_process($commands, $this, false);
}
public function isIpv6(): bool
{
return str($this->ip)->contains(':');
}
}

View File

@@ -42,7 +42,7 @@ class Service extends BaseModel
{
use HasFactory, SoftDeletes;
private static $parserVersion = '3';
private static $parserVersion = '4';
protected $guarded = [];
@@ -770,6 +770,30 @@ class Service extends BaseModel
}
$fields->put('Code Server', $data->toArray());
break;
case str($image)->contains('elestio/strapi'):
$data = collect([]);
$license = $this->environment_variables()->where('key', 'STRAPI_LICENSE')->first();
if ($license) {
$data = $data->merge([
'License' => [
'key' => data_get($license, 'key'),
'value' => data_get($license, 'value'),
],
]);
}
$nodeEnv = $this->environment_variables()->where('key', 'NODE_ENV')->first();
if ($nodeEnv) {
$data = $data->merge([
'Node Environment' => [
'key' => data_get($nodeEnv, 'key'),
'value' => data_get($nodeEnv, 'value'),
],
]);
}
$fields->put('Strapi', $data->toArray());
break;
}
}
$databases = $this->databases()->get();
@@ -1095,7 +1119,21 @@ class Service extends BaseModel
return 3;
});
foreach ($sorted as $env) {
$commands[] = "echo '{$env->key}={$env->real_value}' >> .env";
if (version_compare($env->version, '4.0.0-beta.347', '<=')) {
$commands[] = "echo '{$env->key}={$env->real_value}' >> .env";
} else {
$real_value = $env->real_value;
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
if ($env->is_literal || $env->is_multiline) {
$real_value = '\''.$real_value.'\'';
} else {
$real_value = escapeEnvVariables($env->real_value);
}
}
$commands[] = "echo \"{$env->key}={$real_value}\" >> .env";
}
}
if ($sorted->count() === 0) {
$commands[] = 'touch .env';
@@ -1105,7 +1143,7 @@ class Service extends BaseModel
public function parse(bool $isNew = false): Collection
{
if ($this->compose_parsing_version === '3') {
if ((int) $this->compose_parsing_version >= 3) {
return newParser($this);
} elseif ($this->docker_compose_raw) {
return parseDockerComposeFile($this, $isNew);

View File

@@ -112,4 +112,9 @@ class ServiceApplication extends BaseModel
{
getFilesystemVolumesFromServer($this, $isInit);
}
public function isBackupSolutionAvailable()
{
return false;
}
}

View File

@@ -115,4 +115,13 @@ class ServiceDatabase extends BaseModel
{
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
public function isBackupSolutionAvailable()
{
return str($this->databaseType())->contains('mysql') ||
str($this->databaseType())->contains('postgres') ||
str($this->databaseType())->contains('postgis') ||
str($this->databaseType())->contains('mariadb') ||
str($this->databaseType())->contains('mongodb');
}
}

View File

@@ -294,4 +294,9 @@ class StandaloneClickhouse extends BaseModel
return $parsedCollection->toArray();
}
}
public function isBackupSolutionAvailable()
{
return false;
}
}

View File

@@ -294,4 +294,9 @@ class StandaloneDragonfly extends BaseModel
return $parsedCollection->toArray();
}
}
public function isBackupSolutionAvailable()
{
return false;
}
}

View File

@@ -294,4 +294,9 @@ class StandaloneKeydb extends BaseModel
return $parsedCollection->toArray();
}
}
public function isBackupSolutionAvailable()
{
return false;
}
}

View File

@@ -294,4 +294,9 @@ class StandaloneMariadb extends BaseModel
return $parsedCollection->toArray();
}
}
public function isBackupSolutionAvailable()
{
return true;
}
}

View File

@@ -314,4 +314,9 @@ class StandaloneMongodb extends BaseModel
return $parsedCollection->toArray();
}
}
public function isBackupSolutionAvailable()
{
return true;
}
}

View File

@@ -295,4 +295,9 @@ class StandaloneMysql extends BaseModel
return $parsedCollection->toArray();
}
}
public function isBackupSolutionAvailable()
{
return true;
}
}

View File

@@ -296,4 +296,9 @@ class StandalonePostgresql extends BaseModel
return $parsedCollection->toArray();
}
}
public function isBackupSolutionAvailable()
{
return true;
}
}

View File

@@ -290,4 +290,9 @@ class StandaloneRedis extends BaseModel
return $parsedCollection->toArray();
}
}
public function isBackupSolutionAvailable()
{
return false;
}
}

View File

@@ -13,7 +13,7 @@ class TransactionalEmailChannel
{
public function send(User $notifiable, Notification $notification): void
{
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
if (! data_get($settings, 'smtp_enabled') && ! data_get($settings, 'resend_enabled')) {
Log::info('SMTP/Resend not enabled');

View File

@@ -18,7 +18,7 @@ class ResetPassword extends Notification
public function __construct($token)
{
$this->settings = \App\Models\InstanceSettings::get();
$this->settings = instanceSettings();
$this->token = $token;
}

View File

@@ -2,10 +2,8 @@
namespace App\Providers;
use App\Models\InstanceSettings;
use App\Models\PersonalAccessToken;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
use Laravel\Sanctum\Sanctum;
@@ -30,9 +28,5 @@ class AppServiceProvider extends ServiceProvider
])->baseUrl($api_url);
}
});
// if (! env('CI')) {
// View::share('instanceSettings', InstanceSettings::get());
// }
}
}

View File

@@ -46,7 +46,7 @@ class FortifyServiceProvider extends ServiceProvider
Fortify::registerView(function () {
$isFirstUser = User::count() === 0;
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
if (! $settings->is_registration_enabled) {
return redirect()->route('login');
}
@@ -60,7 +60,7 @@ class FortifyServiceProvider extends ServiceProvider
});
Fortify::loginView(function () {
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
$enabled_oauth_providers = OauthSetting::where('enabled', true)->get();
$users = User::count();
if ($users == 0) {

View File

@@ -175,4 +175,5 @@ function removeUnnecessaryFieldsFromRequest(Request $request)
$request->offsetUnset('instant_deploy');
$request->offsetUnset('github_app_uuid');
$request->offsetUnset('private_key_uuid');
$request->offsetUnset('use_build_server');
}

View File

@@ -20,12 +20,16 @@ const RESTART_MODE = 'unless-stopped';
const DATABASE_DOCKER_IMAGES = [
'bitnami/mariadb',
'bitnami/mongodb',
'bitnami/mysql',
'bitnami/postgresql',
'bitnami/redis',
'mysql',
'bitnami/mysql',
'mysql/mysql-server',
'mariadb',
'postgis/postgis',
'postgres',
'bitnami/postgresql',
'supabase/postgres',
'elestio/postgres',
'mongo',
'redis',
'memcached',
@@ -33,7 +37,6 @@ const DATABASE_DOCKER_IMAGES = [
'neo4j',
'influxdb',
'clickhouse/clickhouse-server',
'supabase/postgres',
];
const SPECIFIC_SERVICES = [
'quay.io/minio/minio',

View File

@@ -1,14 +1,11 @@
<?php
use App\Models\S3Storage;
use Illuminate\Support\Str;
function set_s3_target(S3Storage $s3)
{
$is_digital_ocean = false;
if ($s3->endpoint) {
$is_digital_ocean = Str::contains($s3->endpoint, 'digitaloceanspaces.com');
}
config()->set('filesystems.disks.custom-s3', [
'driver' => 's3',
'region' => $s3['region'],
@@ -17,7 +14,7 @@ function set_s3_target(S3Storage $s3)
'bucket' => $s3['bucket'],
'endpoint' => $s3['endpoint'],
'use_path_style_endpoint' => true,
'bucket_endpoint' => $is_digital_ocean,
'bucket_endpoint' => $s3->isHetzner() || $s3->isDigitalOcean(),
'aws_url' => $s3->awsUrl(),
]);
}

View File

@@ -247,7 +247,7 @@ function is_transactional_emails_active(): bool
function set_transanctional_email_settings(?InstanceSettings $settings = null): ?string
{
if (! $settings) {
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
}
config()->set('mail.from.address', data_get($settings, 'smtp_from_address'));
config()->set('mail.from.name', data_get($settings, 'smtp_from_name'));
@@ -281,7 +281,7 @@ function base_ip(): string
if (isDev()) {
return 'localhost';
}
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
if ($settings->public_ipv4) {
return "$settings->public_ipv4";
}
@@ -309,7 +309,7 @@ function getFqdnWithoutPort(string $fqdn)
*/
function base_url(bool $withPort = true): string
{
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
if ($settings->fqdn) {
return $settings->fqdn;
}
@@ -343,6 +343,11 @@ function isSubscribed()
{
return isSubscriptionActive() || auth()->user()->isInstanceAdmin();
}
function isProduction(): bool
{
return ! isDev();
}
function isDev(): bool
{
return config('app.env') === 'local';
@@ -384,7 +389,7 @@ function send_internal_notification(string $message): void
}
function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null): void
{
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
$type = set_transanctional_email_settings($settings);
if (! $type) {
throw new Exception('No email settings found.');
@@ -703,7 +708,9 @@ function getTopLevelNetworks(Service|Application $resource)
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
$topLevelNetworks->put($networkDetails, null);
if (is_string($networkDetails) || is_int($networkDetails)) {
$topLevelNetworks->put($networkDetails, null);
}
}
}
}
@@ -753,7 +760,9 @@ function getTopLevelNetworks(Service|Application $resource)
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
$topLevelNetworks->put($networkDetails, null);
if (is_string($networkDetails) || is_int($networkDetails)) {
$topLevelNetworks->put($networkDetails, null);
}
}
}
}
@@ -970,7 +979,7 @@ function validate_dns_entry(string $fqdn, Server $server)
if (str($host)->contains('sslip.io')) {
return true;
}
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
$is_dns_validation_enabled = data_get($settings, 'is_dns_validation_enabled');
if (! $is_dns_validation_enabled) {
return true;
@@ -1090,7 +1099,7 @@ function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId =
if ($domainFound) {
return true;
}
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
if (data_get($settings, 'fqdn')) {
$domain = data_get($settings, 'fqdn');
if (str($domain)->endsWith('/')) {
@@ -1162,7 +1171,7 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
}
}
if ($resource) {
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
if (data_get($settings, 'fqdn')) {
$domain = data_get($settings, 'fqdn');
if (str($domain)->endsWith('/')) {
@@ -1179,12 +1188,26 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
function parseCommandsByLineForSudo(Collection $commands, Server $server): array
{
$commands = $commands->map(function ($line) {
if (! str($line)->startsWith('cd') && ! str($line)->startsWith('command') && ! str($line)->startsWith('echo') && ! str($line)->startsWith('true')) {
if (
! str(trim($line))->startsWith([
'cd',
'command',
'echo',
'true',
'if',
'fi',
])
) {
return "sudo $line";
}
if (str(trim($line))->startsWith('if')) {
return str_replace('if', 'if sudo', $line);
}
return $line;
});
$commands = $commands->map(function ($line) use ($server) {
if (Str::startsWith($line, 'sudo mkdir -p')) {
return "$line && sudo chown -R $server->user:$server->user ".Str::after($line, 'sudo mkdir -p').' && sudo chmod -R o-rwx '.Str::after($line, 'sudo mkdir -p');
@@ -1192,6 +1215,7 @@ function parseCommandsByLineForSudo(Collection $commands, Server $server): array
return $line;
});
$commands = $commands->map(function ($line) {
$line = str($line);
if (str($line)->contains('$(')) {
@@ -1236,8 +1260,6 @@ function parseLineForSudo(string $command, Server $server): string
function get_public_ips()
{
try {
echo "Refreshing public ips!\n";
$settings = \App\Models\InstanceSettings::get();
[$first, $second] = Process::concurrently(function (Pool $pool) {
$pool->path(__DIR__)->command('curl -4s https://ifconfig.io');
$pool->path(__DIR__)->command('curl -6s https://ifconfig.io');
@@ -1251,7 +1273,7 @@ function get_public_ips()
return;
}
$settings->update(['public_ipv4' => $ipv4]);
InstanceSettings::get()->update(['public_ipv4' => $ipv4]);
}
} catch (\Exception $e) {
echo "Error: {$e->getMessage()}\n";
@@ -1266,7 +1288,7 @@ function get_public_ips()
return;
}
$settings->update(['public_ipv6' => $ipv6]);
InstanceSettings::get()->update(['public_ipv6' => $ipv6]);
}
} catch (\Throwable $e) {
echo "Error: {$e->getMessage()}\n";
@@ -1590,7 +1612,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
$topLevelNetworks->put($networkDetails, null);
if (is_string($networkDetails) || is_int($networkDetails)) {
$topLevelNetworks->put($networkDetails, null);
}
}
}
}
@@ -2505,7 +2529,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
$topLevelNetworks->put($networkDetails, null);
if (is_string($networkDetails) || is_int($networkDetails)) {
$topLevelNetworks->put($networkDetails, null);
}
}
}
}
@@ -2966,11 +2992,22 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
$predefinedPort = '8000';
}
if ($isDatabase) {
$savedService = ServiceDatabase::firstOrCreate([
'name' => $serviceName,
'image' => $image,
'service_id' => $resource->id,
]);
$applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first();
if ($applicationFound) {
$savedService = $applicationFound;
$savedService = ServiceDatabase::firstOrCreate([
'name' => $applicationFound->name,
'image' => $applicationFound->image,
'service_id' => $applicationFound->service_id,
]);
$applicationFound->delete();
} else {
$savedService = ServiceDatabase::firstOrCreate([
'name' => $serviceName,
'image' => $image,
'service_id' => $resource->id,
]);
}
} else {
$savedService = ServiceApplication::firstOrCreate([
'name' => $serviceName,
@@ -3191,12 +3228,24 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
if ($serviceName === 'plausible') {
$predefinedPort = '8000';
}
if ($isDatabase) {
$savedService = ServiceDatabase::firstOrCreate([
'name' => $serviceName,
'image' => $image,
'service_id' => $resource->id,
]);
$applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first();
if ($applicationFound) {
$savedService = $applicationFound;
$savedService = ServiceDatabase::firstOrCreate([
'name' => $applicationFound->name,
'image' => $applicationFound->image,
'service_id' => $applicationFound->service_id,
]);
$applicationFound->delete();
} else {
$savedService = ServiceDatabase::firstOrCreate([
'name' => $serviceName,
'image' => $image,
'service_id' => $resource->id,
]);
}
} else {
$savedService = ServiceApplication::firstOrCreate([
'name' => $serviceName,
@@ -3266,7 +3315,15 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
} elseif ($source->value() === '/tmp' || $source->value() === '/tmp/') {
$volume = $source->value().':'.$target->value();
} else {
$mainDirectory = str(base_configuration_dir().'/applications/'.$uuid);
if ((int) $resource->compose_parsing_version >= 4) {
if ($isApplication) {
$mainDirectory = str(base_configuration_dir().'/applications/'.$uuid);
} elseif ($isService) {
$mainDirectory = str(base_configuration_dir().'/services/'.$uuid);
}
} else {
$mainDirectory = str(base_configuration_dir().'/applications/'.$uuid);
}
$source = replaceLocalSource($source, $mainDirectory);
if ($isApplication && $isPullRequest) {
$source = $source."-pr-$pullRequestId";
@@ -3286,6 +3343,17 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
'resource_type' => get_class($originalResource),
]
);
if (isDev()) {
if ((int) $resource->compose_parsing_version >= 4) {
if ($isApplication) {
$source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid);
} elseif ($isService) {
$source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/services/'.$uuid);
}
} else {
$source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid);
}
}
$volume = "$source:$target";
}
} elseif ($type->value() === 'volume') {
@@ -3828,14 +3896,37 @@ function convertComposeEnvironmentToArray($environment)
{
$convertedServiceVariables = collect([]);
if (isAssociativeArray($environment)) {
// Example: $environment = ['FOO' => 'bar', 'BAZ' => 'qux'];
if ($environment instanceof Collection) {
$changedEnvironment = collect([]);
$environment->each(function ($value, $key) use ($changedEnvironment) {
if (is_numeric($key)) {
$parts = explode('=', $value, 2);
if (count($parts) === 2) {
$key = $parts[0];
$realValue = $parts[1] ?? '';
$changedEnvironment->put($key, $realValue);
} else {
$changedEnvironment->put($key, $value);
}
} else {
$changedEnvironment->put($key, $value);
}
});
return $changedEnvironment;
}
$convertedServiceVariables = $environment;
} else {
// Example: $environment = ['FOO=bar', 'BAZ=qux'];
foreach ($environment as $value) {
$parts = explode('=', $value, 2);
$key = $parts[0];
$realValue = $parts[1] ?? '';
if ($key) {
$convertedServiceVariables->put($key, $realValue);
if (is_string($value)) {
$parts = explode('=', $value, 2);
$key = $parts[0];
$realValue = $parts[1] ?? '';
if ($key) {
$convertedServiceVariables->put($key, $realValue);
}
}
}
}
@@ -3843,3 +3934,7 @@ function convertComposeEnvironmentToArray($environment)
return $convertedServiceVariables;
}
function instanceSettings()
{
return InstanceSettings::get();
}

View File

@@ -48,6 +48,7 @@
"zircote/swagger-php": "^4.10"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.13",
"fakerphp/faker": "^v1.21.0",
"laravel/dusk": "^v8.0",
"laravel/pint": "^1.16",

154
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "96f8146407d0e6e897ff097c5eccd3a4",
"content-hash": "42c28ab141b70fcabf75b51afa96c670",
"packages": [
{
"name": "amphp/amp",
@@ -11823,6 +11823,90 @@
}
],
"packages-dev": [
{
"name": "barryvdh/laravel-debugbar",
"version": "v3.13.5",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-debugbar.git",
"reference": "92d86be45ee54edff735e46856f64f14b6a8bb07"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/92d86be45ee54edff735e46856f64f14b6a8bb07",
"reference": "92d86be45ee54edff735e46856f64f14b6a8bb07",
"shasum": ""
},
"require": {
"illuminate/routing": "^9|^10|^11",
"illuminate/session": "^9|^10|^11",
"illuminate/support": "^9|^10|^11",
"maximebf/debugbar": "~1.22.0",
"php": "^8.0",
"symfony/finder": "^6|^7"
},
"require-dev": {
"mockery/mockery": "^1.3.3",
"orchestra/testbench-dusk": "^5|^6|^7|^8|^9",
"phpunit/phpunit": "^9.6|^10.5",
"squizlabs/php_codesniffer": "^3.5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.13-dev"
},
"laravel": {
"providers": [
"Barryvdh\\Debugbar\\ServiceProvider"
],
"aliases": {
"Debugbar": "Barryvdh\\Debugbar\\Facades\\Debugbar"
}
}
},
"autoload": {
"files": [
"src/helpers.php"
],
"psr-4": {
"Barryvdh\\Debugbar\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Barry vd. Heuvel",
"email": "barryvdh@gmail.com"
}
],
"description": "PHP Debugbar integration for Laravel",
"keywords": [
"debug",
"debugbar",
"laravel",
"profiler",
"webprofiler"
],
"support": {
"issues": "https://github.com/barryvdh/laravel-debugbar/issues",
"source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.13.5"
},
"funding": [
{
"url": "https://fruitcake.nl",
"type": "custom"
},
{
"url": "https://github.com/barryvdh",
"type": "github"
}
],
"time": "2024-04-12T11:20:37+00:00"
},
{
"name": "brianium/paratest",
"version": "v7.4.3",
@@ -12301,6 +12385,74 @@
},
"time": "2024-09-03T15:00:28+00:00"
},
{
"name": "maximebf/debugbar",
"version": "v1.22.5",
"source": {
"type": "git",
"url": "https://github.com/maximebf/php-debugbar.git",
"reference": "1b5cabe0ce013134cf595bfa427bbf2f6abcd989"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/1b5cabe0ce013134cf595bfa427bbf2f6abcd989",
"reference": "1b5cabe0ce013134cf595bfa427bbf2f6abcd989",
"shasum": ""
},
"require": {
"php": "^7.2|^8",
"psr/log": "^1|^2|^3",
"symfony/var-dumper": "^4|^5|^6|^7"
},
"require-dev": {
"dbrekelmans/bdi": "^1",
"phpunit/phpunit": "^8|^9",
"symfony/panther": "^1|^2.1",
"twig/twig": "^1.38|^2.7|^3.0"
},
"suggest": {
"kriswallsmith/assetic": "The best way to manage assets",
"monolog/monolog": "Log using Monolog",
"predis/predis": "Redis storage"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.22-dev"
}
},
"autoload": {
"psr-4": {
"DebugBar\\": "src/DebugBar/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Maxime Bouroumeau-Fuseau",
"email": "maxime.bouroumeau@gmail.com",
"homepage": "http://maximebf.com"
},
{
"name": "Barry vd. Heuvel",
"email": "barryvdh@gmail.com"
}
],
"description": "Debug bar in the browser for php application",
"homepage": "https://github.com/maximebf/php-debugbar",
"keywords": [
"debug",
"debugbar"
],
"support": {
"issues": "https://github.com/maximebf/php-debugbar/issues",
"source": "https://github.com/maximebf/php-debugbar/tree/v1.22.5"
},
"time": "2024-09-09T08:05:55+00:00"
},
{
"name": "mockery/mockery",
"version": "1.6.12",

325
config/debugbar.php Normal file
View File

@@ -0,0 +1,325 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Debugbar Settings
|--------------------------------------------------------------------------
|
| Debugbar is enabled by default, when debug is set to true in app.php.
| You can override the value by setting enable to true or false instead of null.
|
| You can provide an array of URI's that must be ignored (eg. 'api/*')
|
*/
'enabled' => env('DEBUGBAR_ENABLED', null),
'except' => [
'telescope*',
'horizon*',
],
/*
|--------------------------------------------------------------------------
| Storage settings
|--------------------------------------------------------------------------
|
| DebugBar stores data for session/ajax requests.
| You can disable this, so the debugbar stores data in headers/session,
| but this can cause problems with large data collectors.
| By default, file storage (in the storage folder) is used. Redis and PDO
| can also be used. For PDO, run the package migrations first.
|
| Warning: Enabling storage.open will allow everyone to access previous
| request, do not enable open storage in publicly available environments!
| Specify a callback if you want to limit based on IP or authentication.
| Leaving it to null will allow localhost only.
*/
'storage' => [
'enabled' => true,
'open' => env('DEBUGBAR_OPEN_STORAGE'), // bool/callback.
'driver' => 'file', // redis, file, pdo, socket, custom
'path' => storage_path('debugbar'), // For file driver
'connection' => null, // Leave null for default connection (Redis/PDO)
'provider' => '', // Instance of StorageInterface for custom driver
'hostname' => '127.0.0.1', // Hostname to use with the "socket" driver
'port' => 2304, // Port to use with the "socket" driver
],
/*
|--------------------------------------------------------------------------
| Editor
|--------------------------------------------------------------------------
|
| Choose your preferred editor to use when clicking file name.
|
| Supported: "phpstorm", "vscode", "vscode-insiders", "vscode-remote",
| "vscode-insiders-remote", "vscodium", "textmate", "emacs",
| "sublime", "atom", "nova", "macvim", "idea", "netbeans",
| "xdebug", "espresso"
|
*/
'editor' => env('DEBUGBAR_EDITOR') ?: env('IGNITION_EDITOR', 'phpstorm'),
/*
|--------------------------------------------------------------------------
| Remote Path Mapping
|--------------------------------------------------------------------------
|
| If you are using a remote dev server, like Laravel Homestead, Docker, or
| even a remote VPS, it will be necessary to specify your path mapping.
|
| Leaving one, or both of these, empty or null will not trigger the remote
| URL changes and Debugbar will treat your editor links as local files.
|
| "remote_sites_path" is an absolute base path for your sites or projects
| in Homestead, Vagrant, Docker, or another remote development server.
|
| Example value: "/home/vagrant/Code"
|
| "local_sites_path" is an absolute base path for your sites or projects
| on your local computer where your IDE or code editor is running on.
|
| Example values: "/Users/<name>/Code", "C:\Users\<name>\Documents\Code"
|
*/
'remote_sites_path' => env('DEBUGBAR_REMOTE_SITES_PATH'),
'local_sites_path' => env('DEBUGBAR_LOCAL_SITES_PATH', env('IGNITION_LOCAL_SITES_PATH')),
/*
|--------------------------------------------------------------------------
| Vendors
|--------------------------------------------------------------------------
|
| Vendor files are included by default, but can be set to false.
| This can also be set to 'js' or 'css', to only include javascript or css vendor files.
| Vendor files are for css: font-awesome (including fonts) and highlight.js (css files)
| and for js: jquery and highlight.js
| So if you want syntax highlighting, set it to true.
| jQuery is set to not conflict with existing jQuery scripts.
|
*/
'include_vendors' => true,
/*
|--------------------------------------------------------------------------
| Capture Ajax Requests
|--------------------------------------------------------------------------
|
| The Debugbar can capture Ajax requests and display them. If you don't want this (ie. because of errors),
| you can use this option to disable sending the data through the headers.
|
| Optionally, you can also send ServerTiming headers on ajax requests for the Chrome DevTools.
|
| Note for your request to be identified as ajax requests they must either send the header
| X-Requested-With with the value XMLHttpRequest (most JS libraries send this), or have application/json as a Accept header.
|
| By default `ajax_handler_auto_show` is set to true allowing ajax requests to be shown automatically in the Debugbar.
| Changing `ajax_handler_auto_show` to false will prevent the Debugbar from reloading.
*/
'capture_ajax' => true,
'add_ajax_timing' => false,
'ajax_handler_auto_show' => true,
'ajax_handler_enable_tab' => true,
/*
|--------------------------------------------------------------------------
| Custom Error Handler for Deprecated warnings
|--------------------------------------------------------------------------
|
| When enabled, the Debugbar shows deprecated warnings for Symfony components
| in the Messages tab.
|
*/
'error_handler' => false,
/*
|--------------------------------------------------------------------------
| Clockwork integration
|--------------------------------------------------------------------------
|
| The Debugbar can emulate the Clockwork headers, so you can use the Chrome
| Extension, without the server-side code. It uses Debugbar collectors instead.
|
*/
'clockwork' => false,
/*
|--------------------------------------------------------------------------
| DataCollectors
|--------------------------------------------------------------------------
|
| Enable/disable DataCollectors
|
*/
'collectors' => [
'phpinfo' => true, // Php version
'messages' => true, // Messages
'time' => true, // Time Datalogger
'memory' => true, // Memory usage
'exceptions' => true, // Exception displayer
'log' => true, // Logs from Monolog (merged in messages if enabled)
'db' => true, // Show database (PDO) queries and bindings
'views' => true, // Views with their data
'route' => true, // Current route information
'auth' => false, // Display Laravel authentication status
'gate' => true, // Display Laravel Gate checks
'session' => true, // Display session data
'symfony_request' => true, // Only one can be enabled..
'mail' => true, // Catch mail messages
'laravel' => false, // Laravel version and environment
'events' => false, // All events fired
'default_request' => false, // Regular or special Symfony request logger
'logs' => false, // Add the latest log messages
'files' => false, // Show the included files
'config' => false, // Display config settings
'cache' => false, // Display cache events
'models' => true, // Display models
'livewire' => true, // Display Livewire (when available)
'jobs' => false, // Display dispatched jobs
],
/*
|--------------------------------------------------------------------------
| Extra options
|--------------------------------------------------------------------------
|
| Configure some DataCollectors
|
*/
'options' => [
'time' => [
'memory_usage' => false, // Calculated by subtracting memory start and end, it may be inaccurate
],
'messages' => [
'trace' => true, // Trace the origin of the debug message
],
'memory' => [
'reset_peak' => false, // run memory_reset_peak_usage before collecting
'with_baseline' => false, // Set boot memory usage as memory peak baseline
'precision' => 0, // Memory rounding precision
],
'auth' => [
'show_name' => true, // Also show the users name/email in the debugbar
'show_guards' => true, // Show the guards that are used
],
'db' => [
'with_params' => true, // Render SQL with the parameters substituted
'backtrace' => true, // Use a backtrace to find the origin of the query in your files.
'backtrace_exclude_paths' => [], // Paths to exclude from backtrace. (in addition to defaults)
'timeline' => false, // Add the queries to the timeline
'duration_background' => true, // Show shaded background on each query relative to how long it took to execute.
'explain' => [ // Show EXPLAIN output on queries
'enabled' => false,
'types' => ['SELECT'], // Deprecated setting, is always only SELECT
],
'hints' => false, // Show hints for common mistakes
'show_copy' => false, // Show copy button next to the query,
'slow_threshold' => false, // Only track queries that last longer than this time in ms
'memory_usage' => false, // Show queries memory usage
'soft_limit' => 100, // After the soft limit, no parameters/backtrace are captured
'hard_limit' => 500, // After the hard limit, queries are ignored
],
'mail' => [
'timeline' => false, // Add mails to the timeline
'show_body' => true,
],
'views' => [
'timeline' => false, // Add the views to the timeline (Experimental)
'data' => false, //true for all data, 'keys' for only names, false for no parameters.
'group' => 50, // Group duplicate views. Pass value to auto-group, or true/false to force
'exclude_paths' => [ // Add the paths which you don't want to appear in the views
'vendor/filament', // Exclude Filament components by default
],
],
'route' => [
'label' => true, // show complete route on bar
],
'session' => [
'hiddens' => [], // hides sensitive values using array paths
],
'symfony_request' => [
'hiddens' => [], // hides sensitive values using array paths, example: request_request.password
],
'events' => [
'data' => false, // collect events data, listeners
],
'logs' => [
'file' => null,
],
'cache' => [
'values' => true, // collect cache values
],
],
/*
|--------------------------------------------------------------------------
| Inject Debugbar in Response
|--------------------------------------------------------------------------
|
| Usually, the debugbar is added just before </body>, by listening to the
| Response after the App is done. If you disable this, you have to add them
| in your template yourself. See http://phpdebugbar.com/docs/rendering.html
|
*/
'inject' => true,
/*
|--------------------------------------------------------------------------
| DebugBar route prefix
|--------------------------------------------------------------------------
|
| Sometimes you want to set route prefix to be used by DebugBar to load
| its resources from. Usually the need comes from misconfigured web server or
| from trying to overcome bugs like this: http://trac.nginx.org/nginx/ticket/97
|
*/
'route_prefix' => '_debugbar',
/*
|--------------------------------------------------------------------------
| DebugBar route middleware
|--------------------------------------------------------------------------
|
| Additional middleware to run on the Debugbar routes
*/
'route_middleware' => [],
/*
|--------------------------------------------------------------------------
| DebugBar route domain
|--------------------------------------------------------------------------
|
| By default DebugBar route served from the same domain that request served.
| To override default domain, specify it as a non-empty value.
*/
'route_domain' => null,
/*
|--------------------------------------------------------------------------
| DebugBar theme
|--------------------------------------------------------------------------
|
| Switches between light and dark theme. If set to auto it will respect system preferences
| Possible values: auto, light, dark
*/
'theme' => env('DEBUGBAR_THEME', 'auto'),
/*
|--------------------------------------------------------------------------
| Backtrace stack limit
|--------------------------------------------------------------------------
|
| By default, the DebugBar limits the number of frames returned by the 'debug_backtrace()' function.
| If you need larger stacktraces, you can increase this number. Setting it to 0 will result in no limit.
*/
'debug_backtrace_limit' => 50,
];

View File

@@ -7,7 +7,7 @@ return [
// The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.347',
'release' => '4.0.0-beta.354',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),

View File

@@ -1,3 +1,3 @@
<?php
return '4.0.0-beta.347';
return '4.0.0-beta.354';

View File

@@ -163,7 +163,7 @@ return new class extends Migration
$table->schemalessAttributes('smtp');
});
$instance_setting = InstanceSettings::get();
$instance_setting = instanceSettings();
$instance_setting->smtp = [
'enabled' => $instance_setting->smtp_enabled,
'from_address' => $instance_setting->smtp_from_address,

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('scheduled_database_backups', function (Blueprint $table) {
$table->boolean('dump_all')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('scheduled_database_backups', function (Blueprint $table) {
$table->dropColumn('dump_all');
});
}
};

View File

@@ -27,14 +27,14 @@ class InstanceSettingsSeeder extends Seeder
$ipv4 = Process::run('curl -4s https://ifconfig.io')->output();
$ipv4 = trim($ipv4);
$ipv4 = filter_var($ipv4, FILTER_VALIDATE_IP);
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
if (is_null($settings->public_ipv4) && $ipv4) {
$settings->update(['public_ipv4' => $ipv4]);
}
$ipv6 = Process::run('curl -6s https://ifconfig.io')->output();
$ipv6 = trim($ipv6);
$ipv6 = filter_var($ipv6, FILTER_VALIDATE_IP);
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
if (is_null($settings->public_ipv6) && $ipv6) {
$settings->update(['public_ipv6' => $ipv6]);
}

View File

@@ -19,7 +19,6 @@ class PopulateSshKeysDirectorySeeder extends Seeder
PrivateKey::chunk(100, function ($keys) {
foreach ($keys as $key) {
echo 'Storing key: '.$key->name."\n";
$key->storeInFileSystem();
}
});

View File

@@ -101,18 +101,13 @@ class ProductionSeeder extends Seeder
}
if (! isCloud() && config('coolify.is_windows_docker_desktop') == false) {
echo "Checking localhost key.\n";
$coolify_key_name = '@host.docker.internal';
$ssh_keys_directory = Storage::disk('ssh-keys')->files();
$coolify_key = collect($ssh_keys_directory)->firstWhere(fn ($item) => str($item)->contains($coolify_key_name));
$found = PrivateKey::find(0);
if ($found) {
echo 'Private Key found in database.\n';
if ($coolify_key) {
echo "SSH key found for the Coolify host machine (localhost).\n";
}
} else {
$server = Server::find(0);
$found = $server->privateKey;
if (! $found) {
if ($coolify_key) {
$user = str($coolify_key)->before('@')->after('id.');
$coolify_key = Storage::disk('ssh-keys')->get($coolify_key);
@@ -125,17 +120,7 @@ class ProductionSeeder extends Seeder
]);
$server->update(['user' => $user]);
echo "SSH key found for the Coolify host machine (localhost).\n";
} else {
PrivateKey::create(
[
'id' => 0,
'team_id' => 0,
'name' => 'localhost\'s key',
'description' => 'The private key for the Coolify host machine (localhost).',
'private_key' => 'Paste here you private key!!',
]
);
echo "No SSH key found for the Coolify host machine (localhost).\n";
echo "Please read the following documentation (point 3) to fix it: https://coolify.io/docs/knowledge-base/server/openssh/\n";
echo "Your localhost connection won't work until then.";

View File

@@ -58,6 +58,7 @@ services:
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID:-coolify}"
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY:-coolify}"
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}"
entrypoint: ["/bin/sh", "/soketi-entrypoint.sh"]
vite:
image: node:20
pull_policy: always

View File

@@ -113,7 +113,7 @@ services:
retries: 10
timeout: 2s
soketi:
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.1'
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.3'
ports:
- "${SOKETI_PORT:-6001}:6001"
- "6002:6002"

View File

@@ -1,9 +1,27 @@
FROM quay.io/soketi/soketi:1.6-16-alpine
ARG TARGETPLATFORM
# https://github.com/cloudflare/cloudflared/releases
ARG CLOUDFLARED_VERSION=2024.4.1
WORKDIR /terminal
RUN apk add --no-cache openssh-client make g++ python3
RUN apk add --no-cache openssh-client make g++ python3 curl
COPY docker/coolify-realtime/package.json ./
RUN npm i
RUN npm rebuild node-pty --update-binary
COPY docker/coolify-realtime/soketi-entrypoint.sh /soketi-entrypoint.sh
COPY docker/coolify-realtime/terminal-server.js /terminal/terminal-server.js
RUN /bin/sh -c "if [[ ${TARGETPLATFORM} == 'linux/amd64' ]]; then \
echo 'amd64' && \
curl -sSL https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \
;fi"
RUN /bin/sh -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \
echo 'arm64' && \
curl -L https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \
;fi"
ENTRYPOINT ["/bin/sh", "/soketi-entrypoint.sh"]

View File

@@ -1,11 +1,19 @@
#!/bin/sh
# Function to timestamp logs
# Check if the first argument is 'watch'
if [ "$1" = "watch" ]; then
WATCH_MODE="--watch"
else
WATCH_MODE=""
fi
timestamp() {
date "+%Y-%m-%d %H:%M:%S"
}
# Start the terminal server in the background with logging
node /terminal/terminal-server.js > >(while read line; do echo "$(timestamp) [TERMINAL] $line"; done) 2>&1 &
node $WATCH_MODE /terminal/terminal-server.js > >(while read line; do echo "$(timestamp) [TERMINAL] $line"; done) 2>&1 &
TERMINAL_PID=$!
# Start the Soketi process in the background with logging

View File

@@ -61,9 +61,13 @@ wss.on('connection', (ws) => {
const userSession = { ws, userId, ptyProcess: null, isActive: false };
userSessions.set(userId, userSession);
ws.on('message', (message) => handleMessage(userSession, message));
ws.on('message', (message) => {
handleMessage(userSession, message);
});
ws.on('error', (err) => handleError(err, userId));
ws.on('close', () => handleClose(userId));
});
const messageHandlers = {
@@ -108,7 +112,6 @@ function parseMessage(message) {
async function handleCommand(ws, command, userId) {
const userSession = userSessions.get(userId);
if (userSession && userSession.isActive) {
const result = await killPtyProcess(userId);
if (!result) {
@@ -127,27 +130,29 @@ async function handleCommand(ws, command, userId) {
cols: 80,
rows: 30,
cwd: process.env.HOME,
env: {},
};
// NOTE: - Initiates a process within the Terminal container
// Establishes an SSH connection to root@coolify with RequestTTY enabled
// Executes the 'docker exec' command to connect to a specific container
// If the user types 'exit', it terminates the container connection and reverts to the server.
const ptyProcess = pty.spawn('ssh', sshArgs.concat(['bash']), options);
const ptyProcess = pty.spawn('ssh', sshArgs.concat([hereDocContent]), options);
userSession.ptyProcess = ptyProcess;
userSession.isActive = true;
ptyProcess.write(hereDocContent + '\n');
// clear the terminal if the user has clear command
ptyProcess.write('command -v clear >/dev/null 2>&1 && clear\n');
ws.send('pty-ready');
ptyProcess.onData((data) => ws.send(data));
ptyProcess.onData((data) => {
ws.send(data);
});
// when parent closes
ptyProcess.onExit(({ exitCode, signal }) => {
console.error(`Process exited with code ${exitCode} and signal ${signal}`);
ws.send('pty-exited');
userSession.isActive = false;
});
if (timeout) {
@@ -181,7 +186,7 @@ async function killPtyProcess(userId) {
// session.ptyProcess.kill() wont work here because of https://github.com/moby/moby/issues/9098
// patch with https://github.com/moby/moby/issues/9098#issuecomment-189743947
session.ptyProcess.write('kill -TERM -$$ && exit\n');
session.ptyProcess.write('set +o history\nkill -TERM -$$ && exit\nset -o history\n');
setTimeout(() => {
if (!session.isActive || !session.ptyProcess) {
@@ -230,5 +235,5 @@ function extractHereDocContent(commandString) {
}
server.listen(6002, () => {
console.log('Server listening on port 6002');
console.log('Coolify realtime terminal server listening on port 6002. Let the hacking begin!');
});

View File

@@ -236,6 +236,10 @@ paths:
watch_paths:
type: string
description: 'The watch paths.'
use_build_server:
type: boolean
nullable: true
description: 'Use build server.'
type: object
responses:
'200':
@@ -457,6 +461,10 @@ paths:
watch_paths:
type: string
description: 'The watch paths.'
use_build_server:
type: boolean
nullable: true
description: 'Use build server.'
type: object
responses:
'200':
@@ -678,6 +686,10 @@ paths:
watch_paths:
type: string
description: 'The watch paths.'
use_build_server:
type: boolean
nullable: true
description: 'Use build server.'
type: object
responses:
'200':
@@ -850,6 +862,10 @@ paths:
instant_deploy:
type: boolean
description: 'The flag to indicate if the application should be deployed instantly.'
use_build_server:
type: boolean
nullable: true
description: 'Use build server.'
type: object
responses:
'200':
@@ -1013,6 +1029,10 @@ paths:
instant_deploy:
type: boolean
description: 'The flag to indicate if the application should be deployed instantly.'
use_build_server:
type: boolean
nullable: true
description: 'Use build server.'
type: object
responses:
'200':
@@ -1067,6 +1087,10 @@ paths:
instant_deploy:
type: boolean
description: 'The flag to indicate if the application should be deployed instantly.'
use_build_server:
type: boolean
nullable: true
description: 'Use build server.'
type: object
responses:
'200':
@@ -1126,9 +1150,33 @@ paths:
type: string
format: uuid
-
name: cleanup
name: delete_configurations
in: query
description: 'Delete configurations and volumes.'
description: 'Delete configurations.'
required: false
schema:
type: boolean
default: true
-
name: delete_volumes
in: query
description: 'Delete volumes.'
required: false
schema:
type: boolean
default: true
-
name: docker_cleanup
in: query
description: 'Run docker cleanup.'
required: false
schema:
type: boolean
default: true
-
name: delete_connected_networks
in: query
description: 'Delete connected networks.'
required: false
schema:
type: boolean
@@ -1351,6 +1399,10 @@ paths:
watch_paths:
type: string
description: 'The watch paths.'
use_build_server:
type: boolean
nullable: true
description: 'Use build server.'
type: object
responses:
'200':
@@ -1738,6 +1790,52 @@ paths:
security:
-
bearerAuth: []
'/applications/{uuid}/execute':
post:
tags:
- Applications
summary: 'Execute Command'
description: "Execute a command on the application's current container."
operationId: execute-command-application
parameters:
-
name: uuid
in: path
description: 'UUID of the application.'
required: true
schema:
type: string
format: uuid
requestBody:
description: 'Command to execute.'
required: true
content:
application/json:
schema:
properties:
command:
type: string
description: 'Command to execute.'
type: object
responses:
'200':
description: "Execute a command on the application's current container."
content:
application/json:
schema:
properties:
message: { type: string, example: 'Command executed.' }
response: { type: string }
type: object
'401':
$ref: '#/components/responses/401'
'400':
$ref: '#/components/responses/400'
'404':
$ref: '#/components/responses/404'
security:
-
bearerAuth: []
/databases:
get:
tags:
@@ -1809,9 +1907,33 @@ paths:
type: string
format: uuid
-
name: cleanup
name: delete_configurations
in: query
description: 'Delete configurations and volumes.'
description: 'Delete configurations.'
required: false
schema:
type: boolean
default: true
-
name: delete_volumes
in: query
description: 'Delete volumes.'
required: false
schema:
type: boolean
default: true
-
name: docker_cleanup
in: query
description: 'Run docker cleanup.'
required: false
schema:
type: boolean
default: true
-
name: delete_connected_networks
in: query
description: 'Delete connected networks.'
required: false
schema:
type: boolean
@@ -3812,6 +3934,38 @@ paths:
required: true
schema:
type: string
-
name: delete_configurations
in: query
description: 'Delete configurations.'
required: false
schema:
type: boolean
default: true
-
name: delete_volumes
in: query
description: 'Delete volumes.'
required: false
schema:
type: boolean
default: true
-
name: docker_cleanup
in: query
description: 'Run docker cleanup.'
required: false
schema:
type: boolean
default: true
-
name: delete_connected_networks
in: query
description: 'Delete connected networks.'
required: false
schema:
type: boolean
default: true
responses:
'200':
description: 'Delete a service by UUID'
@@ -4769,6 +4923,10 @@ components:
type: boolean
swarm_cluster:
type: string
delete_unused_volumes:
type: boolean
delete_unused_networks:
type: boolean
type: object
ServerSetting:
description: 'Server Settings model'

View File

@@ -46,6 +46,9 @@ services:
- PUSHER_APP_ID
- PUSHER_APP_KEY
- PUSHER_APP_SECRET
- TERMINAL_PROTOCOL
- TERMINAL_HOST
- TERMINAL_PORT
- AUTOUPDATE
- SELF_HOSTED
- SSH_MUX_ENABLED
@@ -110,7 +113,7 @@ services:
retries: 10
timeout: 2s
soketi:
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.1'
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.3'
ports:
- "${SOKETI_PORT:-6001}:6001"
- "6002:6002"

View File

@@ -1,16 +1,16 @@
{
"coolify": {
"v4": {
"version": "4.0.0-beta.347"
"version": "4.0.0-beta.354"
},
"nightly": {
"version": "4.0.0-beta.348"
"version": "4.0.0-beta.355"
},
"helper": {
"version": "1.0.1"
},
"realtime": {
"version": "1.0.1"
"version": "1.0.3"
}
}
}

15
public/svgs/bitcoin.svg Normal file
View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW 2019 (64-Bit) -->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="100%" height="100%" version="1.1" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd"
viewBox="0 0 4091.27 4091.73"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xodm="http://www.corel.com/coreldraw/odm/2003">
<g id="Layer_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<g id="_1421344023328">
<path fill="#F7931A" fill-rule="nonzero" d="M4030.06 2540.77c-273.24,1096.01 -1383.32,1763.02 -2479.46,1489.71 -1095.68,-273.24 -1762.69,-1383.39 -1489.33,-2479.31 273.12,-1096.13 1383.2,-1763.19 2479,-1489.95 1096.06,273.24 1763.03,1383.51 1489.76,2479.57l0.02 -0.02z"/>
<path fill="white" fill-rule="nonzero" d="M2947.77 1754.38c40.72,-272.26 -166.56,-418.61 -450,-516.24l91.95 -368.8 -224.5 -55.94 -89.51 359.09c-59.02,-14.72 -119.63,-28.59 -179.87,-42.34l90.16 -361.46 -224.36 -55.94 -92 368.68c-48.84,-11.12 -96.81,-22.11 -143.35,-33.69l0.26 -1.16 -309.59 -77.31 -59.72 239.78c0,0 166.56,38.18 163.05,40.53 90.91,22.69 107.35,82.87 104.62,130.57l-104.74 420.15c6.26,1.59 14.38,3.89 23.34,7.49 -7.49,-1.86 -15.46,-3.89 -23.73,-5.87l-146.81 588.57c-11.11,27.62 -39.31,69.07 -102.87,53.33 2.25,3.26 -163.17,-40.72 -163.17,-40.72l-111.46 256.98 292.15 72.83c54.35,13.63 107.61,27.89 160.06,41.3l-92.9 373.03 224.24 55.94 92 -369.07c61.26,16.63 120.71,31.97 178.91,46.43l-91.69 367.33 224.51 55.94 92.89 -372.33c382.82,72.45 670.67,43.24 791.83,-303.02 97.63,-278.78 -4.86,-439.58 -206.26,-544.44 146.69,-33.83 257.18,-130.31 286.64,-329.61l-0.07 -0.05zm-512.93 719.26c-69.38,278.78 -538.76,128.08 -690.94,90.29l123.28 -494.2c152.17,37.99 640.17,113.17 567.67,403.91zm69.43 -723.3c-63.29,253.58 -453.96,124.75 -580.69,93.16l111.77 -448.21c126.73,31.59 534.85,90.55 468.94,355.05l-0.02 0z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

11
public/svgs/homarr.svg Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="484px" height="329px" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" xmlns:xlink="http://www.w3.org/1999/xlink">
<g><path style="opacity:0.947" fill="#f95251" d="M 157.5,-0.5 C 158.833,-0.5 160.167,-0.5 161.5,-0.5C 200.102,3.59921 222.268,24.9325 228,63.5C 228.667,88.5 228.667,113.5 228,138.5C 222.962,132.265 216.629,130.265 209,132.5C 208.667,109.167 208.333,85.8333 208,62.5C 202.332,36.3319 186.165,21.9986 159.5,19.5C 141.783,20.273 127.949,27.9397 118,42.5C 112.924,38.3791 107.757,34.3791 102.5,30.5C 115.97,11.5969 134.303,1.26361 157.5,-0.5 Z"/></g>
<g><path style="opacity:0.947" fill="#f95251" d="M 321.5,-0.5 C 322.833,-0.5 324.167,-0.5 325.5,-0.5C 348.697,1.26361 367.03,11.5969 380.5,30.5C 375.243,34.3791 370.076,38.3791 365,42.5C 349.005,21.3908 328.505,15.2242 303.5,24C 287.107,31.7273 277.607,44.5606 275,62.5C 274.667,85.8333 274.333,109.167 274,132.5C 266.371,130.265 260.038,132.265 255,138.5C 254.333,113.5 254.333,88.5 255,63.5C 260.732,24.9325 282.898,3.59921 321.5,-0.5 Z"/></g>
<g><path style="opacity:0.984" fill="#f95251" d="M -0.5,139.5 C -0.5,137.167 -0.5,134.833 -0.5,132.5C 19.5,132.5 39.5,132.5 59.5,132.5C 51.7127,98.1844 43.3793,64.0177 34.5,30C 83.3876,36.5512 118.554,62.0512 140,106.5C 159.513,155.397 153.846,201.064 123,243.5C 114.899,253.77 105.399,262.437 94.5,269.5C 90.7735,258.059 87.6068,246.392 85,234.5C 39.0428,218.384 10.5428,186.717 -0.5,139.5 Z"/></g>
<g><path style="opacity:0.984" fill="#f95251" d="M 483.5,132.5 C 483.5,134.833 483.5,137.167 483.5,139.5C 472.457,186.717 443.957,218.384 398,234.5C 395.393,246.392 392.226,258.059 388.5,269.5C 351.514,243.037 332.514,206.871 331.5,161C 333.865,105.236 359.865,64.9024 409.5,40C 421.97,34.3953 434.97,31.0619 448.5,30C 439.621,64.0177 431.287,98.1844 423.5,132.5C 443.5,132.5 463.5,132.5 483.5,132.5 Z"/></g>
<g><path style="opacity:0.95" fill="#f95251" d="M 211.5,170.5 C 225.127,170.958 231.627,177.958 231,191.5C 226.507,203.825 218.007,207.659 205.5,203C 197.535,196.871 195.369,189.037 199,179.5C 201.917,174.637 206.083,171.637 211.5,170.5 Z"/></g>
<g><path style="opacity:0.949" fill="#f95251" d="M 265.5,170.5 C 280.848,170.68 287.348,178.347 285,193.5C 279.06,204.76 270.227,207.593 258.5,202C 249.176,192.625 249.176,183.292 258.5,174C 260.925,172.787 263.259,171.621 265.5,170.5 Z"/></g>
<g><path style="opacity:0.987" fill="#f95251" d="M 388.5,328.5 C 386.5,328.5 384.5,328.5 382.5,328.5C 285.992,327.966 189.325,326.966 92.5,325.5C 120.96,261.579 170.294,227.913 240.5,224.5C 299.804,226.887 345.304,252.887 377,302.5C 381.676,310.846 385.509,319.513 388.5,328.5 Z"/></g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

6
public/svgs/it-tools.svg Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="512px" height="512px" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" xmlns:xlink="http://www.w3.org/1999/xlink">
<g><path style="opacity:0.995" fill="#18a057" d="M 230.5,-0.5 C 247.5,-0.5 264.5,-0.5 281.5,-0.5C 290.12,3.28885 296.287,9.62218 300,18.5C 299.871,28.5889 300.704,38.4222 302.5,48C 326.717,53.6219 349.217,63.1219 370,76.5C 376.469,71.0323 382.636,65.199 388.5,59C 398.925,52.0844 409.591,51.7511 420.5,58C 432,68.1667 442.833,79 453,90.5C 459,100.5 459,110.5 453,120.5C 447.017,127.317 440.851,133.984 434.5,140.5C 447.998,161.821 457.498,184.821 463,209.5C 474.144,210.648 485.31,211.815 496.5,213C 503.496,216.822 508.496,222.322 511.5,229.5C 511.5,246.5 511.5,263.5 511.5,280.5C 508.841,288.664 503.508,294.497 495.5,298C 484.573,299.444 473.573,300.277 462.5,300.5C 457.369,325.4 448.036,348.566 434.5,370C 441.693,377.526 448.526,385.359 455,393.5C 458.667,402.825 458.001,411.825 453,420.5C 443.167,430.333 433.333,440.167 423.5,450C 414.895,457.565 405.229,459.232 394.5,455C 386.322,448.153 378.155,441.32 370,434.5C 348.501,447.828 325.334,457.161 300.5,462.5C 301.01,473.958 300.177,485.291 298,496.5C 294.006,504.342 287.839,509.342 279.5,511.5C 263.167,511.5 246.833,511.5 230.5,511.5C 222.336,508.841 216.503,503.508 213,495.5C 212,484.833 211,474.167 210,463.5C 198.505,459.749 187.005,455.915 175.5,452C 173.842,451.275 173.342,450.108 174,448.5C 192.195,429.971 210.695,411.805 229.5,394C 293.836,401.915 343.336,378.748 378,324.5C 408.025,263.569 400.691,207.569 356,156.5C 315.267,118.147 267.767,106.314 213.5,121C 164.374,138.458 132.874,172.291 119,222.5C 117.485,228.075 116.485,233.742 116,239.5C 115.261,253.906 115.594,268.239 117,282.5C 98.8333,300.667 80.6667,318.833 62.5,337C 61.552,337.483 60.552,337.649 59.5,337.5C 54.8294,325.486 51.1627,313.153 48.5,300.5C 37.4331,300.188 26.4331,299.355 15.5,298C 7.189,293.843 1.85567,287.343 -0.5,278.5C -0.5,262.833 -0.5,247.167 -0.5,231.5C 2.22646,223.578 7.22646,217.411 14.5,213C 25.7307,211.098 37.0641,210.265 48.5,210.5C 53.718,185.511 63.0513,162.178 76.5,140.5C 71.2189,133.716 65.3855,127.383 59,121.5C 51.8558,110.353 52.1892,99.353 60,88.5C 69.8333,78.6667 79.6667,68.8333 89.5,59C 98.3002,53.1199 107.633,52.1199 117.5,56C 125.629,62.4604 133.463,69.2938 141,76.5C 162.028,62.9274 184.861,53.4274 209.5,48C 210.93,37.2282 212.097,26.3949 213,15.5C 216.503,7.49214 222.336,2.15881 230.5,-0.5 Z"/></g>
<g><path style="opacity:0.99" fill="#1d1d1d" d="M 52.5,511.5 C 46.5,511.5 40.5,511.5 34.5,511.5C 16.5,506.167 4.83333,494.5 -0.5,476.5C -0.5,470.167 -0.5,463.833 -0.5,457.5C 1.14258,451.876 3.64258,446.542 7,441.5C 55.9723,391.528 105.306,341.861 155,292.5C 141.187,245.876 152.02,206.042 187.5,173C 216.657,150.235 248.991,143.902 284.5,154C 289.202,158.922 290.702,164.755 289,171.5C 275,186.167 261,200.833 247,215.5C 234.564,236.922 238.73,254.422 259.5,268C 272.178,272.999 284.178,271.666 295.5,264C 309.596,248.899 324.596,234.899 340.5,222C 353.68,220.169 360.513,226.003 361,239.5C 365.554,290.573 345.387,328.073 300.5,352C 273.591,363.046 246.258,364.379 218.5,356C 169.472,405.361 120.139,454.361 70.5,503C 64.8398,506.712 58.8398,509.545 52.5,511.5 Z"/></g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

5
public/svgs/mailpit.svg Normal file
View File

@@ -0,0 +1,5 @@
<svg width="500" height="460" viewBox="0 0 132.292 121.708" version="1.1" id="svg6" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<defs id="defs10"/>
<path d="M12.321 0l53.861 53.918L120.365 0zM5.155 9.025l60.842 59.673 61.211-59.489-.185 36.835L66.921 70.54l15.164 12.616-8.137 5.986-41.609.184c-4.838-.022-25.877-18.34-27.185-41.255z" fill-opacity=".941" fill="#2d4a5f" id="path2" style="fill:#ffffff;fill-opacity:1"/>
<path d="M78.385 72.049l53.907-21.679-8.031 57.318-11.845-9.132c-21.727 23.171-45.255 26.289-67.997 20.837S12.281 98.39 5.155 83.8-.67 53.116 2.843 38.769c1.13 10.511-1.313 16.316 6.38 33.612 6.31 11.399 14.413 20.417 25.89 24.956 13.9 6.195 32.247 3.357 41.701-3.039l14.24-12.156z" fill="#00b786" id="path4"/>
<script xmlns=""/></svg>

After

Width:  |  Height:  |  Size: 792 B

5
public/svgs/mixpost.svg Normal file
View File

@@ -0,0 +1,5 @@
<svg viewBox="0 0 170 170" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="27" y="25" width="100" height="100" rx="20" fill="#84E9F5"/>
<rect x="45" y="44" width="100" height="100" rx="20" fill="#FFAB4C"/>
<rect x="36" y="36" width="100" height="100" rx="20" fill="#4F46BB"/>
</svg>

After

Width:  |  Height:  |  Size: 304 B

8
public/svgs/strapi.svg Normal file
View File

@@ -0,0 +1,8 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 22.1867C0 11.7278 0 6.49832 3.24916 3.24916C6.49832 0 11.7278 0 22.1867 0H41.8133C52.2722 0 57.5017 0 60.7508 3.24916C64 6.49832 64 11.7278 64 22.1867V41.8133C64 52.2722 64 57.5017 60.7508 60.7508C57.5017 64 52.2722 64 41.8133 64H22.1867C11.7278 64 6.49832 64 3.24916 60.7508C0 57.5017 0 52.2722 0 41.8133V22.1867Z" fill="#4945FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M44.156 19.4131H22.6094V30.4004H33.596V41.3864H44.5827V19.8398C44.5827 19.6041 44.3917 19.4131 44.156 19.4131Z" fill="white"/>
<rect x="33.1719" y="30.4004" width="0.426667" height="0.426667" fill="white"/>
<path d="M22.6172 30.4004H33.1772C33.4128 30.4004 33.6039 30.5914 33.6039 30.8271V41.3871H23.0439C22.8082 41.3871 22.6172 41.196 22.6172 40.9604V30.4004Z" fill="#9593FF"/>
<path d="M33.6016 41.3867H44.5882L33.9657 52.0092C33.8314 52.1436 33.6016 52.0484 33.6016 51.8584V41.3867Z" fill="#9593FF"/>
<path d="M22.6151 30.3998H12.1434C11.9534 30.3998 11.8582 30.17 11.9926 30.0356L22.6151 19.4131V30.3998Z" fill="#9593FF"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -16,6 +16,7 @@ export function initializeTerminalComponent() {
paused: false,
MAX_PENDING_WRITES: 5,
keepAliveInterval: null,
reconnectInterval: null,
init() {
this.setupTerminal();
@@ -48,6 +49,9 @@ export function initializeTerminalComponent() {
document.addEventListener(event, () => {
this.checkIfProcessIsRunningAndKillIt();
clearInterval(this.keepAliveInterval);
if (this.reconnectInterval) {
clearInterval(this.reconnectInterval);
}
}, { once: true });
});
@@ -103,11 +107,27 @@ export function initializeTerminalComponent() {
};
this.socket.onclose = () => {
console.log('WebSocket connection closed');
this.reconnect();
};
}
},
reconnect() {
if (this.reconnectInterval) {
clearInterval(this.reconnectInterval);
}
this.reconnectInterval = setInterval(() => {
console.log('Attempting to reconnect...');
this.initializeWebSocket();
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
console.log('Reconnected successfully');
clearInterval(this.reconnectInterval);
this.reconnectInterval = null;
window.location.reload();
}
}, 2000);
},
handleSocketMessage(event) {
this.message = '(connection closed)';
if (event.data === 'pty-ready') {
@@ -125,6 +145,10 @@ export function initializeTerminalComponent() {
if (this.term) this.term.reset();
this.terminalActive = false;
this.message = '(sorry, something went wrong, please try again)';
} else if (event.data === 'pty-exited') {
this.terminalActive = false;
this.term.reset();
this.commandBuffer = '';
} else {
this.pendingWrites++;
this.term.write(event.data, this.flowControlCallback.bind(this));
@@ -136,9 +160,12 @@ export function initializeTerminalComponent() {
if (this.pendingWrites > this.MAX_PENDING_WRITES && !this.paused) {
this.paused = true;
this.socket.send(JSON.stringify({ pause: true }));
} else if (this.pendingWrites <= this.MAX_PENDING_WRITES && this.paused) {
return;
}
if (this.pendingWrites <= this.MAX_PENDING_WRITES && this.paused) {
this.paused = false;
this.socket.send(JSON.stringify({ resume: true }));
return;
}
},
@@ -147,15 +174,7 @@ export function initializeTerminalComponent() {
this.term.onData((data) => {
this.socket.send(JSON.stringify({ message: data }));
// Handle CTRL + D or exit command
if (data === '\x04' || (data === '\r' && this.stripAnsiCommands(this.commandBuffer).trim().includes('exit'))) {
this.checkIfProcessIsRunningAndKillIt();
setTimeout(() => {
this.terminalActive = false;
this.term.reset();
}, 500);
this.commandBuffer = '';
} else if (data === '\r') {
if (data === '\r') {
this.commandBuffer = '';
} else {
this.commandBuffer += data;
@@ -183,10 +202,6 @@ export function initializeTerminalComponent() {
});
},
stripAnsiCommands(input) {
return input.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
},
keepAlive() {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify({ ping: true }));

View File

@@ -8,7 +8,7 @@
<div class="w-96">
<form action="/user/confirm-password" method="POST" class="flex flex-col gap-2">
@csrf
<x-forms.input required type="password" name="password" label="{{ __('input.password') }}" autofocus />
<x-forms.input required type="password" name="password" label="{{ __('input.password') }}" />
<x-forms.button type="submit">{{ __('auth.confirm_password') }}</x-forms.button>
</form>
@if ($errors->any())

View File

@@ -12,7 +12,7 @@
@if (is_transactional_emails_active())
<form action="/forgot-password" method="POST" class="flex flex-col gap-2">
@csrf
<x-forms.input required type="email" name="email" label="{{ __('input.email') }}" autofocus />
<x-forms.input required type="email" name="email" label="{{ __('input.email') }}" />
<x-forms.button type="submit">{{ __('auth.forgot_password_send_email') }}</x-forms.button>
</form>
@else

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