Merge branch 'next' into include-tip-in-www-redirect-helper

This commit is contained in:
🏔️ Peak
2024-11-08 14:40:09 +01:00
committed by GitHub
579 changed files with 15075 additions and 7887 deletions

View File

@@ -23,3 +23,5 @@ yarn-error.log
.rnd
/.ssh
.ignition.json
.env.dusk.local
docker/coolify-realtime/node_modules

15
.env.dusk.ci Normal file
View File

@@ -0,0 +1,15 @@
APP_ENV=production
APP_NAME="Coolify Staging"
APP_ID=development
APP_KEY=
APP_URL=http://localhost
APP_PORT=8000
SSH_MUX_ENABLED=true
# PostgreSQL Database Configuration
DB_DATABASE=coolify
DB_USERNAME=coolify
DB_PASSWORD=password
DB_HOST=localhost
DB_PORT=5432

View File

@@ -4,6 +4,7 @@ APP_ID=coolify-windows-docker-desktop
APP_NAME=Coolify
APP_KEY=base64:ssTlCmrIE/q7whnKMvT6DwURikg69COzGsAwFVROm80=
DB_USERNAME=coolify
DB_PASSWORD=coolify
REDIS_PASSWORD=coolify

65
.github/workflows/browser-tests.yml vendored Normal file
View File

@@ -0,0 +1,65 @@
name: Dusk
on:
push:
branches: [ "not-existing" ]
jobs:
dusk:
runs-on: ubuntu-latest
services:
redis:
image: redis
env:
REDIS_HOST: localhost
REDIS_PORT: 6379
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Set up PostgreSQL
run: |
sudo systemctl start postgresql
sudo -u postgres psql -c "CREATE DATABASE coolify;"
sudo -u postgres psql -c "CREATE USER coolify WITH PASSWORD 'password';"
sudo -u postgres psql -c "ALTER ROLE coolify SET client_encoding TO 'utf8';"
sudo -u postgres psql -c "ALTER ROLE coolify SET default_transaction_isolation TO 'read committed';"
sudo -u postgres psql -c "ALTER ROLE coolify SET timezone TO 'UTC';"
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE coolify TO coolify;"
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
- name: Copy .env
run: cp .env.dusk.ci .env
- name: Install Dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: Generate key
run: php artisan key:generate
- name: Install Chrome binaries
run: php artisan dusk:chrome-driver --detect
- name: Start Chrome Driver
run: ./vendor/laravel/dusk/bin/chromedriver-linux --port=4444 &
- name: Build assets
run: npm install && npm run build
- name: Run Laravel Server
run: php artisan serve --no-reload &
- name: Execute tests
run: php artisan dusk
- name: Upload Screenshots
if: failure()
uses: actions/upload-artifact@v4
with:
name: screenshots
path: tests/Browser/screenshots
- name: Upload Console Logs
if: failure()
uses: actions/upload-artifact@v4
with:
name: console
path: tests/Browser/console

View File

@@ -1,4 +1,4 @@
name: Coolify Helper Image Development (v4)
name: Coolify Helper Image Development
on:
push:
@@ -8,7 +8,8 @@ on:
- docker/coolify-helper/Dockerfile
env:
REGISTRY: ghcr.io
GITHUB_REGISTRY: ghcr.io
DOCKER_REGISTRY: docker.io
IMAGE_NAME: "coollabsio/coolify-helper"
jobs:
@@ -19,25 +20,36 @@ jobs:
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to ghcr.io
- name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to ${{ env.DOCKER_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- name: Build image and push to registry
uses: docker/build-push-action@v5
- name: Build and Push Image
uses: docker/build-push-action@v6
with:
no-cache: true
context: .
file: docker/coolify-helper/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
tags: |
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
labels: |
coolify.managed=true
aarch64:
@@ -47,27 +59,39 @@ jobs:
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to ghcr.io
- name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to ${{ env.DOCKER_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- name: Build image and push to registry
uses: docker/build-push-action@v5
- name: Build and Push Image
uses: docker/build-push-action@v6
with:
no-cache: true
context: .
file: docker/coolify-helper/Dockerfile
platforms: linux/aarch64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64
tags: |
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64
labels: |
coolify.managed=true
merge-manifest:
runs-on: ubuntu-latest
permissions:
@@ -75,25 +99,42 @@ jobs:
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: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to ${{ env.DOCKER_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- name: Create & publish manifest
- name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
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 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
docker buildx imagetools create \
--append ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 \
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next \
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:next
- name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }}
run: |
docker buildx imagetools create \
--append ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 \
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next \
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:next
- uses: sarisia/actions-status-discord@v1
if: always()
with:

View File

@@ -1,4 +1,4 @@
name: Coolify Helper Image (v4)
name: Coolify Helper Image
on:
push:
@@ -8,7 +8,8 @@ on:
- docker/coolify-helper/Dockerfile
env:
REGISTRY: ghcr.io
GITHUB_REGISTRY: ghcr.io
DOCKER_REGISTRY: docker.io
IMAGE_NAME: "coollabsio/coolify-helper"
jobs:
@@ -19,25 +20,36 @@ jobs:
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to ghcr.io
- name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to ${{ env.DOCKER_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- name: Build image and push to registry
uses: docker/build-push-action@v5
- name: Build and Push Image
uses: docker/build-push-action@v6
with:
no-cache: true
context: .
file: docker/coolify-helper/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
tags: |
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
labels: |
coolify.managed=true
aarch64:
@@ -47,25 +59,36 @@ jobs:
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to ghcr.io
- name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to ${{ env.DOCKER_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- name: Build image and push to registry
uses: docker/build-push-action@v5
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- name: Build and Push Image
uses: docker/build-push-action@v6
with:
no-cache: true
context: .
file: docker/coolify-helper/Dockerfile
platforms: linux/aarch64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
tags: |
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
labels: |
coolify.managed=true
merge-manifest:
@@ -75,25 +98,43 @@ jobs:
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: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to ${{ env.DOCKER_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- name: Create & publish manifest
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
run: |
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
docker buildx imagetools create \
--append ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 \
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} \
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }}
run: |
docker buildx imagetools create \
--append ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 \
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} \
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- uses: sarisia/actions-status-discord@v1
if: always()
with:

View File

@@ -0,0 +1,139 @@
name: Production Build (v4)
on:
push:
branches: ["main"]
paths-ignore:
- .github/workflows/coolify-helper.yml
- .github/workflows/coolify-helper-next.yml
- .github/workflows/coolify-realtime.yml
- .github/workflows/coolify-realtime-next.yml
- docker/coolify-helper/Dockerfile
- docker/coolify-realtime/Dockerfile
- docker/testing-host/Dockerfile
- templates/*
env:
GITHUB_REGISTRY: ghcr.io
DOCKER_REGISTRY: docker.io
IMAGE_NAME: "coollabsio/coolify"
jobs:
amd64:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to ${{ env.DOCKER_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
- name: Build and Push Image
uses: docker/build-push-action@v6
with:
context: .
file: docker/prod/Dockerfile
platforms: linux/amd64
push: true
tags: |
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
aarch64:
runs-on: [self-hosted, arm64]
steps:
- uses: actions/checkout@v4
- name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to ${{ env.DOCKER_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
- name: Build and Push Image
uses: docker/build-push-action@v6
with:
context: .
file: docker/prod/Dockerfile
platforms: linux/aarch64
push: true
tags: |
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
merge-manifest:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
needs: [amd64, aarch64]
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to ${{ env.DOCKER_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
- name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
run: |
docker buildx imagetools create \
--append ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 \
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} \
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }}
run: |
docker buildx imagetools create \
--append ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 \
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} \
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}

View File

@@ -1,17 +1,19 @@
name: Coolify Realtime Development (v4)
name: Coolify Realtime Development
on:
push:
branches: [ "next" ]
paths:
- .github/workflows/coolify-realtime.yml
- .github/workflows/coolify-realtime-next.yml
- docker/coolify-realtime/Dockerfile
- docker/coolify-realtime/terminal-server.js
- docker/coolify-realtime/package.json
- docker/coolify-realtime/package-lock.json
- docker/coolify-realtime/soketi-entrypoint.sh
env:
REGISTRY: ghcr.io
GITHUB_REGISTRY: ghcr.io
DOCKER_REGISTRY: docker.io
IMAGE_NAME: "coollabsio/coolify-realtime"
jobs:
@@ -22,27 +24,39 @@ jobs:
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to ghcr.io
- name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to ${{ env.DOCKER_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_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
- name: Build and Push Image
uses: docker/build-push-action@v6
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
tags: |
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
labels: |
coolify.managed=true
aarch64:
runs-on: [ self-hosted, arm64 ]
permissions:
@@ -50,27 +64,39 @@ jobs:
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to ghcr.io
- name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to ${{ env.DOCKER_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_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
- name: Build and Push Image
uses: docker/build-push-action@v6
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
tags: |
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64
labels: |
coolify.managed=true
merge-manifest:
runs-on: ubuntu-latest
permissions:
@@ -78,26 +104,44 @@ jobs:
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: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to ${{ env.DOCKER_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_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
- name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
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
docker buildx imagetools create \
--append ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 \
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next \
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:next
- name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }}
run: |
docker buildx imagetools create \
--append ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 \
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next \
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:next
- uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}

View File

@@ -1,4 +1,4 @@
name: Coolify Realtime (v4)
name: Coolify Realtime
on:
push:
@@ -8,10 +8,12 @@ on:
- docker/coolify-realtime/Dockerfile
- docker/coolify-realtime/terminal-server.js
- docker/coolify-realtime/package.json
- docker/coolify-realtime/package-lock.json
- docker/coolify-realtime/soketi-entrypoint.sh
env:
REGISTRY: ghcr.io
GITHUB_REGISTRY: ghcr.io
DOCKER_REGISTRY: docker.io
IMAGE_NAME: "coollabsio/coolify-realtime"
jobs:
@@ -22,27 +24,39 @@ jobs:
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to ghcr.io
- name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to ${{ env.DOCKER_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_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
- name: Build and Push Image
uses: docker/build-push-action@v6
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 }}
tags: |
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
labels: |
coolify.managed=true
aarch64:
runs-on: [ self-hosted, arm64 ]
permissions:
@@ -50,27 +64,39 @@ jobs:
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to ghcr.io
- name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to ${{ env.DOCKER_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_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
- name: Build and Push Image
uses: docker/build-push-action@v6
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 }}-aarch64
tags: |
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
labels: |
coolify.managed=true
merge-manifest:
runs-on: ubuntu-latest
permissions:
@@ -78,25 +104,43 @@ jobs:
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: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to ${{ env.DOCKER_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_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
- name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
run: |
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
docker buildx imagetools create \
--append ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 \
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} \
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }}
run: |
docker buildx imagetools create \
--append ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 \
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} \
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- uses: sarisia/actions-status-discord@v1
if: always()
with:

View File

@@ -0,0 +1,125 @@
name: Staging Build
on:
push:
branches-ignore: ["main", "v3"]
paths-ignore:
- .github/workflows/coolify-helper.yml
- .github/workflows/coolify-helper-next.yml
- .github/workflows/coolify-realtime.yml
- .github/workflows/coolify-realtime-next.yml
- docker/coolify-helper/Dockerfile
- docker/coolify-realtime/Dockerfile
- docker/testing-host/Dockerfile
- templates/*
env:
GITHUB_REGISTRY: ghcr.io
DOCKER_REGISTRY: docker.io
IMAGE_NAME: "coollabsio/coolify"
jobs:
amd64:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to ${{ env.DOCKER_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Build and Push Image
uses: docker/build-push-action@v6
with:
context: .
file: docker/prod/Dockerfile
platforms: linux/amd64
push: true
tags: |
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
aarch64:
runs-on: [self-hosted, arm64]
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to ${{ env.DOCKER_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Build and Push Image
uses: docker/build-push-action@v6
with:
context: .
file: docker/prod/Dockerfile
platforms: linux/aarch64
push: true
tags: |
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64
merge-manifest:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
needs: [amd64, aarch64]
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to ${{ env.DOCKER_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
run: |
docker buildx imagetools create \
--append ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64 \
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
- name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }}
run: |
docker buildx imagetools create \
--append ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64 \
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
- uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}

View File

@@ -1,14 +1,15 @@
name: Coolify Testing Host (v4-non-prod)
name: Coolify Testing Host
on:
push:
branches: [ "main", "next" ]
branches: [ "next" ]
paths:
- .github/workflows/coolify-testing-host.yml
- docker/testing-host/Dockerfile
env:
REGISTRY: ghcr.io
GITHUB_REGISTRY: ghcr.io
DOCKER_REGISTRY: docker.io
IMAGE_NAME: "coollabsio/coolify-testing-host"
jobs:
@@ -19,21 +20,34 @@ jobs:
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to ghcr.io
- name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image and push to registry
uses: docker/build-push-action@v5
- name: Login to ${{ env.DOCKER_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Build and Push Image
uses: docker/build-push-action@v6
with:
no-cache: true
context: .
file: docker/testing-host/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
tags: |
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
labels: |
coolify.managed=true
aarch64:
runs-on: [ self-hosted, arm64 ]
permissions:
@@ -41,21 +55,34 @@ jobs:
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to ghcr.io
- name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image and push to registry
uses: docker/build-push-action@v5
- name: Login to ${{ env.DOCKER_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Build and Push Image
uses: docker/build-push-action@v6
with:
no-cache: true
context: .
file: docker/testing-host/Dockerfile
platforms: linux/aarch64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64
tags: |
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64
labels: |
coolify.managed=true
merge-manifest:
runs-on: ubuntu-latest
permissions:
@@ -63,21 +90,36 @@ jobs:
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: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create & publish manifest
- name: Login to ${{ env.DOCKER_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
run: |
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
docker buildx imagetools create \
--append ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 \
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }}
run: |
docker buildx imagetools create \
--append ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 \
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- uses: sarisia/actions-status-discord@v1
if: always()
with:

View File

@@ -1,79 +0,0 @@
name: Development Build (v4)
on:
push:
branches-ignore: ["main", "v3"]
paths-ignore:
- .github/workflows/coolify-helper.yml
- docker/coolify-helper/Dockerfile
env:
REGISTRY: ghcr.io
IMAGE_NAME: "coollabsio/coolify"
jobs:
amd64:
runs-on: ubuntu-latest
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: Build image and push to registry
uses: docker/build-push-action@v5
with:
context: .
file: docker/prod/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
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: Build image and push to registry
uses: docker/build-push-action@v5
with:
context: .
file: docker/prod/Dockerfile
platforms: linux/aarch64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64
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: Create & publish manifest
run: |
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
- uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}

View File

@@ -1,89 +0,0 @@
name: Production Build (v4)
on:
push:
branches: ["main"]
paths-ignore:
- .github/workflows/coolify-helper.yml
- docker/coolify-helper/Dockerfile
- templates/service-templates.json
env:
REGISTRY: ghcr.io
IMAGE_NAME: "coollabsio/coolify"
jobs:
amd64:
runs-on: ubuntu-latest
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 php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
- name: Build image and push to registry
uses: docker/build-push-action@v5
with:
context: .
file: docker/prod/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
aarch64:
runs-on: [self-hosted, arm64]
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 php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
- name: Build image and push to registry
uses: docker/build-push-action@v5
with:
context: .
file: docker/prod/Dockerfile
platforms: linux/aarch64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
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 php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
- name: Create & publish manifest
run: |
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}

2
.gitignore vendored
View File

@@ -33,3 +33,5 @@ _ide_helper_models.php
/.ssh
scripts/load-test/*
.ignition.json
.env.dusk.local
docker/coolify-realtime/node_modules

View File

@@ -11,7 +11,6 @@ class GenerateConfig
public function handle(Application $application, bool $is_json = false)
{
ray()->clearAll();
return $application->generateConfig(is_json: $is_json);
}
}

View File

@@ -17,7 +17,6 @@ class StopApplication
if (! $server->isFunctional()) {
return 'Server is not functional';
}
ray('Stopping application: '.$application->name);
if ($server->isSwarm()) {
instant_remote_process(["docker stack rm {$application->uuid}"], $server);
@@ -36,8 +35,6 @@ class StopApplication
CleanupDocker::dispatch($server, true);
}
} catch (\Exception $e) {
ray($e->getMessage());
return $e->getMessage();
}
}

View File

@@ -32,8 +32,6 @@ class StopApplicationOneServer
}
}
} catch (\Exception $e) {
ray($e->getMessage());
return $e->getMessage();
}
}

View File

@@ -48,7 +48,6 @@ class PrepareCoolifyTask
call_event_data: $this->remoteProcessArgs->call_event_data,
);
if ($this->remoteProcessArgs->type === ActivityTypes::COMMAND->value) {
ray('Dispatching a high priority job');
dispatch($job)->onQueue('high');
} else {
dispatch($job);

View File

@@ -9,6 +9,7 @@ use App\Jobs\ApplicationDeploymentJob;
use App\Models\Server;
use Illuminate\Process\ProcessResult;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Process;
use Spatie\Activitylog\Models\Activity;
@@ -39,7 +40,6 @@ class RunRemoteProcess
*/
public function __construct(Activity $activity, bool $hide_from_output = false, bool $ignore_errors = false, $call_event_on_finish = null, $call_event_data = null)
{
if ($activity->getExtraProperty('type') !== ActivityTypes::INLINE->value && $activity->getExtraProperty('type') !== ActivityTypes::COMMAND->value) {
throw new \RuntimeException('Incompatible Activity to run a remote command.');
}
@@ -125,7 +125,7 @@ class RunRemoteProcess
]));
}
} catch (\Throwable $e) {
ray($e);
Log::error('Error calling event: '.$e->getMessage());
}
}

View File

@@ -51,6 +51,8 @@ class StartClickhouse
],
'labels' => [
'coolify.managed' => 'true',
'coolify.type' => 'database',
'coolify.databaseId' => $this->database->id,
],
'healthcheck' => [
'test' => "clickhouse-client --password {$this->database->clickhouse_admin_password} --query 'SELECT 1'",
@@ -97,8 +99,8 @@ class StartClickhouse
}
// Add custom docker run options
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options);
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);

View File

@@ -23,33 +23,33 @@ class StartDatabase
return 'Server is not functional';
}
switch ($database->getMorphClass()) {
case 'App\Models\StandalonePostgresql':
case \App\Models\StandalonePostgresql::class:
$activity = StartPostgresql::run($database);
break;
case 'App\Models\StandaloneRedis':
case \App\Models\StandaloneRedis::class:
$activity = StartRedis::run($database);
break;
case 'App\Models\StandaloneMongodb':
case \App\Models\StandaloneMongodb::class:
$activity = StartMongodb::run($database);
break;
case 'App\Models\StandaloneMysql':
case \App\Models\StandaloneMysql::class:
$activity = StartMysql::run($database);
break;
case 'App\Models\StandaloneMariadb':
case \App\Models\StandaloneMariadb::class:
$activity = StartMariadb::run($database);
break;
case 'App\Models\StandaloneKeydb':
case \App\Models\StandaloneKeydb::class:
$activity = StartKeydb::run($database);
break;
case 'App\Models\StandaloneDragonfly':
case \App\Models\StandaloneDragonfly::class:
$activity = StartDragonfly::run($database);
break;
case 'App\Models\StandaloneClickhouse':
case \App\Models\StandaloneClickhouse::class:
$activity = StartClickhouse::run($database);
break;
}
if ($database->is_public && $database->public_port) {
StartDatabaseProxy::dispatch($database);
StartDatabaseProxy::dispatch($database)->onQueue('high');
}
return $activity;

View File

@@ -26,7 +26,7 @@ class StartDatabaseProxy
$server = data_get($database, 'destination.server');
$containerName = data_get($database, 'uuid');
$proxyContainerName = "{$database->uuid}-proxy";
if ($database->getMorphClass() === 'App\Models\ServiceDatabase') {
if ($database->getMorphClass() === \App\Models\ServiceDatabase::class) {
$databaseType = $database->databaseType();
// $connectPredefined = data_get($database, 'service.connect_to_docker_network');
$network = $database->service->uuid;
@@ -34,54 +34,54 @@ class StartDatabaseProxy
$proxyContainerName = "{$database->service->uuid}-proxy";
switch ($databaseType) {
case 'standalone-mariadb':
$type = 'App\Models\StandaloneMariadb';
$type = \App\Models\StandaloneMariadb::class;
$containerName = "mariadb-{$database->service->uuid}";
break;
case 'standalone-mongodb':
$type = 'App\Models\StandaloneMongodb';
$type = \App\Models\StandaloneMongodb::class;
$containerName = "mongodb-{$database->service->uuid}";
break;
case 'standalone-mysql':
$type = 'App\Models\StandaloneMysql';
$type = \App\Models\StandaloneMysql::class;
$containerName = "mysql-{$database->service->uuid}";
break;
case 'standalone-postgresql':
$type = 'App\Models\StandalonePostgresql';
$type = \App\Models\StandalonePostgresql::class;
$containerName = "postgresql-{$database->service->uuid}";
break;
case 'standalone-redis':
$type = 'App\Models\StandaloneRedis';
$type = \App\Models\StandaloneRedis::class;
$containerName = "redis-{$database->service->uuid}";
break;
case 'standalone-keydb':
$type = 'App\Models\StandaloneKeydb';
$type = \App\Models\StandaloneKeydb::class;
$containerName = "keydb-{$database->service->uuid}";
break;
case 'standalone-dragonfly':
$type = 'App\Models\StandaloneDragonfly';
$type = \App\Models\StandaloneDragonfly::class;
$containerName = "dragonfly-{$database->service->uuid}";
break;
case 'standalone-clickhouse':
$type = 'App\Models\StandaloneClickhouse';
$type = \App\Models\StandaloneClickhouse::class;
$containerName = "clickhouse-{$database->service->uuid}";
break;
}
}
if ($type === 'App\Models\StandaloneRedis') {
if ($type === \App\Models\StandaloneRedis::class) {
$internalPort = 6379;
} elseif ($type === 'App\Models\StandalonePostgresql') {
} elseif ($type === \App\Models\StandalonePostgresql::class) {
$internalPort = 5432;
} elseif ($type === 'App\Models\StandaloneMongodb') {
} elseif ($type === \App\Models\StandaloneMongodb::class) {
$internalPort = 27017;
} elseif ($type === 'App\Models\StandaloneMysql') {
} elseif ($type === \App\Models\StandaloneMysql::class) {
$internalPort = 3306;
} elseif ($type === 'App\Models\StandaloneMariadb') {
} elseif ($type === \App\Models\StandaloneMariadb::class) {
$internalPort = 3306;
} elseif ($type === 'App\Models\StandaloneKeydb') {
} elseif ($type === \App\Models\StandaloneKeydb::class) {
$internalPort = 6379;
} elseif ($type === 'App\Models\StandaloneDragonfly') {
} elseif ($type === \App\Models\StandaloneDragonfly::class) {
$internalPort = 6379;
} elseif ($type === 'App\Models\StandaloneClickhouse') {
} elseif ($type === \App\Models\StandaloneClickhouse::class) {
$internalPort = 9000;
}
$configuration_dir = database_proxy_dir($database->uuid);

View File

@@ -48,6 +48,8 @@ class StartDragonfly
],
'labels' => [
'coolify.managed' => 'true',
'coolify.type' => 'database',
'coolify.databaseId' => $this->database->id,
],
'healthcheck' => [
'test' => "redis-cli -a {$this->database->dragonfly_password} ping",
@@ -94,8 +96,8 @@ class StartDragonfly
}
// Add custom docker run options
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options);
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);

View File

@@ -50,6 +50,8 @@ class StartKeydb
],
'labels' => [
'coolify.managed' => 'true',
'coolify.type' => 'database',
'coolify.databaseId' => $this->database->id,
],
'healthcheck' => [
'test' => "keydb-cli --pass {$this->database->keydb_password} ping",
@@ -105,8 +107,8 @@ class StartKeydb
}
// Add custom docker run options
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options);
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";

View File

@@ -45,6 +45,8 @@ class StartMariadb
],
'labels' => [
'coolify.managed' => 'true',
'coolify.type' => 'database',
'coolify.databaseId' => $this->database->id,
],
'healthcheck' => [
'test' => ['CMD', 'healthcheck.sh', '--connect', '--innodb_initialized'],
@@ -99,8 +101,8 @@ class StartMariadb
}
// Add custom docker run options
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options);
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);

View File

@@ -25,6 +25,10 @@ class StartMongodb
$container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
if (isDev()) {
$this->configuration_dir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/databases/'.$container_name;
}
$this->commands = [
"echo 'Starting {$database->name}.'",
"mkdir -p $this->configuration_dir",
@@ -49,6 +53,8 @@ class StartMongodb
],
'labels' => [
'coolify.managed' => 'true',
'coolify.type' => 'database',
'coolify.databaseId' => $this->database->id,
],
'healthcheck' => [
'test' => [
@@ -115,8 +121,8 @@ class StartMongodb
];
// Add custom docker run options
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options);
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);

View File

@@ -45,6 +45,8 @@ class StartMysql
],
'labels' => [
'coolify.managed' => 'true',
'coolify.type' => 'database',
'coolify.databaseId' => $this->database->id,
],
'healthcheck' => [
'test' => ['CMD', 'mysqladmin', 'ping', '-h', 'localhost', '-u', 'root', "-p{$this->database->mysql_root_password}"],
@@ -99,8 +101,8 @@ class StartMysql
}
// Add custom docker run options
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options);
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);

View File

@@ -49,6 +49,8 @@ class StartPostgresql
],
'labels' => [
'coolify.managed' => 'true',
'coolify.type' => 'database',
'coolify.databaseId' => $this->database->id,
],
'healthcheck' => [
'test' => [
@@ -120,8 +122,8 @@ class StartPostgresql
];
}
// Add custom docker run options
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options);
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);

View File

@@ -21,8 +21,6 @@ class StartRedis
{
$this->database = $database;
$startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
$container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
@@ -37,6 +35,8 @@ class StartRedis
$environment_variables = $this->generate_environment_variables();
$this->add_custom_redis();
$startCommand = $this->buildStartCommand();
$docker_compose = [
'services' => [
$container_name => [
@@ -50,6 +50,8 @@ class StartRedis
],
'labels' => [
'coolify.managed' => 'true',
'coolify.type' => 'database',
'coolify.databaseId' => $this->database->id,
],
'healthcheck' => [
'test' => [
@@ -105,12 +107,11 @@ class StartRedis
'target' => '/usr/local/etc/redis/redis.conf',
'read_only' => true,
];
$docker_compose['services'][$container_name]['command'] = "redis-server /usr/local/etc/redis/redis.conf --requirepass {$this->database->redis_password} --appendonly yes";
}
// Add custom docker run options
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options);
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
@@ -160,12 +161,26 @@ class StartRedis
private function generate_environment_variables()
{
$environment_variables = collect();
foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->real_value");
}
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
$environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}");
foreach ($this->database->runtime_environment_variables as $env) {
if ($env->is_shared) {
$environment_variables->push("$env->key=$env->real_value");
if ($env->key === 'REDIS_PASSWORD') {
$this->database->update(['redis_password' => $env->real_value]);
}
if ($env->key === 'REDIS_USERNAME') {
$this->database->update(['redis_username' => $env->real_value]);
}
} else {
if ($env->key === 'REDIS_PASSWORD') {
$env->update(['value' => $this->database->redis_password]);
} elseif ($env->key === 'REDIS_USERNAME') {
$env->update(['value' => $this->database->redis_username]);
}
$environment_variables->push("$env->key=$env->real_value");
}
}
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
@@ -173,6 +188,27 @@ class StartRedis
return $environment_variables->all();
}
private function buildStartCommand(): string
{
$hasRedisConf = ! is_null($this->database->redis_conf) && ! empty($this->database->redis_conf);
$redisConfPath = '/usr/local/etc/redis/redis.conf';
if ($hasRedisConf) {
$confContent = $this->database->redis_conf;
$hasRequirePass = str_contains($confContent, 'requirepass');
if ($hasRequirePass) {
$command = "redis-server $redisConfPath";
} else {
$command = "redis-server $redisConfPath --requirepass {$this->database->redis_password}";
}
} else {
$command = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
}
return $command;
}
private function add_custom_redis()
{
if (is_null($this->database->redis_conf) || empty($this->database->redis_conf)) {

View File

@@ -2,7 +2,7 @@
namespace App\Actions\Database;
use App\Events\DatabaseStatusChanged;
use App\Events\DatabaseProxyStopped;
use App\Models\ServiceDatabase;
use App\Models\StandaloneClickhouse;
use App\Models\StandaloneDragonfly;
@@ -22,12 +22,16 @@ class StopDatabaseProxy
{
$server = data_get($database, 'destination.server');
$uuid = $database->uuid;
if ($database->getMorphClass() === 'App\Models\ServiceDatabase') {
if ($database->getMorphClass() === \App\Models\ServiceDatabase::class) {
$uuid = $database->service->uuid;
$server = data_get($database, 'service.server');
}
instant_remote_process(["docker rm -f {$uuid}-proxy"], $server);
$database->is_public = false;
$database->save();
DatabaseStatusChanged::dispatch();
DatabaseProxyStopped::dispatch();
}
}

View File

@@ -3,14 +3,11 @@
namespace App\Actions\Docker;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Proxy\CheckProxy;
use App\Actions\Proxy\StartProxy;
use App\Actions\Shared\ComplexStatusCheck;
use App\Models\ApplicationPreview;
use App\Models\Server;
use App\Models\ServiceDatabase;
use App\Notifications\Container\ContainerRestarted;
use App\Notifications\Container\ContainerStopped;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -33,7 +30,7 @@ class GetContainersStatus
$this->containerReplicates = $containerReplicates;
$this->server = $server;
if (! $this->server->isFunctional()) {
return 'Server is not ready.';
return 'Server is not functional.';
}
$this->applications = $this->server->applications();
$skip_these_applications = collect([]);
@@ -49,323 +46,8 @@ class GetContainersStatus
$this->applications = $this->applications->filter(function ($value, $key) use ($skip_these_applications) {
return ! $skip_these_applications->pluck('id')->contains($value->id);
});
$this->old_way();
// if ($this->server->isSwarm()) {
// $this->old_way();
// } else {
// if (!$this->server->is_metrics_enabled) {
// $this->old_way();
// return;
// }
// $sentinel_found = instant_remote_process(["docker inspect coolify-sentinel"], $this->server, false);
// $sentinel_found = json_decode($sentinel_found, true);
// $status = data_get($sentinel_found, '0.State.Status', 'exited');
// if ($status === 'running') {
// ray('Checking with Sentinel');
// $this->sentinel();
// } else {
// ray('Checking the Old way');
// $this->old_way();
// }
// }
}
// private function sentinel()
// {
// try {
// $this->containers = $this->server->getContainersWithSentinel();
// if ($this->containers->count() === 0) {
// return;
// }
// $databases = $this->server->databases();
// $services = $this->server->services()->get();
// $previews = $this->server->previews();
// $foundApplications = [];
// $foundApplicationPreviews = [];
// $foundDatabases = [];
// $foundServices = [];
// foreach ($this->containers as $container) {
// $labels = Arr::undot(data_get($container, 'labels'));
// $containerStatus = data_get($container, 'state');
// $containerHealth = data_get($container, 'health_status', 'unhealthy');
// $containerStatus = "$containerStatus ($containerHealth)";
// $applicationId = data_get($labels, 'coolify.applicationId');
// if ($applicationId) {
// $pullRequestId = data_get($labels, 'coolify.pullRequestId');
// if ($pullRequestId) {
// if (str($applicationId)->contains('-')) {
// $applicationId = str($applicationId)->before('-');
// }
// $preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
// if ($preview) {
// $foundApplicationPreviews[] = $preview->id;
// $statusFromDb = $preview->status;
// if ($statusFromDb !== $containerStatus) {
// $preview->update(['status' => $containerStatus]);
// }
// } else {
// //Notify user that this container should not be there.
// }
// } else {
// $application = $this->applications->where('id', $applicationId)->first();
// if ($application) {
// $foundApplications[] = $application->id;
// $statusFromDb = $application->status;
// if ($statusFromDb !== $containerStatus) {
// $application->update(['status' => $containerStatus]);
// }
// } else {
// //Notify user that this container should not be there.
// }
// }
// } else {
// $uuid = data_get($labels, 'com.docker.compose.service');
// $type = data_get($labels, 'coolify.type');
// if ($uuid) {
// if ($type === 'service') {
// $database_id = data_get($labels, 'coolify.service.subId');
// if ($database_id) {
// $service_db = ServiceDatabase::where('id', $database_id)->first();
// if ($service_db) {
// $uuid = $service_db->service->uuid;
// $isPublic = data_get($service_db, 'is_public');
// if ($isPublic) {
// $foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
// if ($this->server->isSwarm()) {
// // TODO: fix this with sentinel
// return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
// } else {
// return data_get($value, 'name') === "$uuid-proxy";
// }
// })->first();
// if (! $foundTcpProxy) {
// StartDatabaseProxy::run($service_db);
// // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
// }
// }
// }
// }
// } else {
// $database = $databases->where('uuid', $uuid)->first();
// if ($database) {
// $isPublic = data_get($database, 'is_public');
// $foundDatabases[] = $database->id;
// $statusFromDb = $database->status;
// if ($statusFromDb !== $containerStatus) {
// $database->update(['status' => $containerStatus]);
// }
// if ($isPublic) {
// $foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
// if ($this->server->isSwarm()) {
// // TODO: fix this with sentinel
// return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
// } else {
// return data_get($value, 'name') === "$uuid-proxy";
// }
// })->first();
// if (! $foundTcpProxy) {
// StartDatabaseProxy::run($database);
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
// }
// }
// } else {
// // Notify user that this container should not be there.
// }
// }
// }
// if (data_get($container, 'name') === 'coolify-db') {
// $foundDatabases[] = 0;
// }
// }
// $serviceLabelId = data_get($labels, 'coolify.serviceId');
// if ($serviceLabelId) {
// $subType = data_get($labels, 'coolify.service.subType');
// $subId = data_get($labels, 'coolify.service.subId');
// $service = $services->where('id', $serviceLabelId)->first();
// if (! $service) {
// continue;
// }
// if ($subType === 'application') {
// $service = $service->applications()->where('id', $subId)->first();
// } else {
// $service = $service->databases()->where('id', $subId)->first();
// }
// if ($service) {
// $foundServices[] = "$service->id-$service->name";
// $statusFromDb = $service->status;
// if ($statusFromDb !== $containerStatus) {
// // ray('Updating status: ' . $containerStatus);
// $service->update(['status' => $containerStatus]);
// }
// }
// }
// }
// $exitedServices = collect([]);
// foreach ($services as $service) {
// $apps = $service->applications()->get();
// $dbs = $service->databases()->get();
// foreach ($apps as $app) {
// if (in_array("$app->id-$app->name", $foundServices)) {
// continue;
// } else {
// $exitedServices->push($app);
// }
// }
// foreach ($dbs as $db) {
// if (in_array("$db->id-$db->name", $foundServices)) {
// continue;
// } else {
// $exitedServices->push($db);
// }
// }
// }
// $exitedServices = $exitedServices->unique('id');
// foreach ($exitedServices as $exitedService) {
// if (str($exitedService->status)->startsWith('exited')) {
// continue;
// }
// $name = data_get($exitedService, 'name');
// $fqdn = data_get($exitedService, 'fqdn');
// if ($name) {
// if ($fqdn) {
// $containerName = "$name, available at $fqdn";
// } else {
// $containerName = $name;
// }
// } else {
// if ($fqdn) {
// $containerName = $fqdn;
// } else {
// $containerName = null;
// }
// }
// $projectUuid = data_get($service, 'environment.project.uuid');
// $serviceUuid = data_get($service, 'uuid');
// $environmentName = data_get($service, 'environment.name');
// if ($projectUuid && $serviceUuid && $environmentName) {
// $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/service/'.$serviceUuid;
// } else {
// $url = null;
// }
// // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
// $exitedService->update(['status' => 'exited']);
// }
// $notRunningApplications = $this->applications->pluck('id')->diff($foundApplications);
// foreach ($notRunningApplications as $applicationId) {
// $application = $this->applications->where('id', $applicationId)->first();
// if (str($application->status)->startsWith('exited')) {
// continue;
// }
// $application->update(['status' => 'exited']);
// $name = data_get($application, 'name');
// $fqdn = data_get($application, 'fqdn');
// $containerName = $name ? "$name ($fqdn)" : $fqdn;
// $projectUuid = data_get($application, 'environment.project.uuid');
// $applicationUuid = data_get($application, 'uuid');
// $environment = data_get($application, 'environment.name');
// if ($projectUuid && $applicationUuid && $environment) {
// $url = base_url().'/project/'.$projectUuid.'/'.$environment.'/application/'.$applicationUuid;
// } else {
// $url = null;
// }
// // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
// }
// $notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
// foreach ($notRunningApplicationPreviews as $previewId) {
// $preview = $previews->where('id', $previewId)->first();
// if (str($preview->status)->startsWith('exited')) {
// continue;
// }
// $preview->update(['status' => 'exited']);
// $name = data_get($preview, 'name');
// $fqdn = data_get($preview, 'fqdn');
// $containerName = $name ? "$name ($fqdn)" : $fqdn;
// $projectUuid = data_get($preview, 'application.environment.project.uuid');
// $environmentName = data_get($preview, 'application.environment.name');
// $applicationUuid = data_get($preview, 'application.uuid');
// if ($projectUuid && $applicationUuid && $environmentName) {
// $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/application/'.$applicationUuid;
// } else {
// $url = null;
// }
// // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
// }
// $notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
// foreach ($notRunningDatabases as $database) {
// $database = $databases->where('id', $database)->first();
// if (str($database->status)->startsWith('exited')) {
// continue;
// }
// $database->update(['status' => 'exited']);
// $name = data_get($database, 'name');
// $fqdn = data_get($database, 'fqdn');
// $containerName = $name;
// $projectUuid = data_get($database, 'environment.project.uuid');
// $environmentName = data_get($database, 'environment.name');
// $databaseUuid = data_get($database, 'uuid');
// if ($projectUuid && $databaseUuid && $environmentName) {
// $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/database/'.$databaseUuid;
// } else {
// $url = null;
// }
// // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
// }
// // Check if proxy is running
// $this->server->proxyType();
// $foundProxyContainer = $this->containers->filter(function ($value, $key) {
// if ($this->server->isSwarm()) {
// // TODO: fix this with sentinel
// return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
// } else {
// return data_get($value, 'name') === 'coolify-proxy';
// }
// })->first();
// if (! $foundProxyContainer) {
// try {
// $shouldStart = CheckProxy::run($this->server);
// if ($shouldStart) {
// StartProxy::run($this->server, false);
// $this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
// }
// } catch (\Throwable $e) {
// ray($e);
// }
// } else {
// $this->server->proxy->status = data_get($foundProxyContainer, 'state');
// $this->server->save();
// $connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
// instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
// }
// } catch (\Exception $e) {
// // send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
// ray($e->getMessage());
// return handleError($e);
// }
// }
private function old_way()
{
if ($this->containers === null) {
['containers' => $this->containers,'containerReplicates' => $this->containerReplicates] = $this->server->getContainers();
['containers' => $this->containers, 'containerReplicates' => $this->containerReplicates] = $this->server->getContainers();
}
if (is_null($this->containers)) {
@@ -425,6 +107,8 @@ class GetContainersStatus
$statusFromDb = $preview->status;
if ($statusFromDb !== $containerStatus) {
$preview->update(['status' => $containerStatus]);
} else {
$preview->update(['last_online_at' => now()]);
}
} else {
//Notify user that this container should not be there.
@@ -436,6 +120,8 @@ class GetContainersStatus
$statusFromDb = $application->status;
if ($statusFromDb !== $containerStatus) {
$application->update(['status' => $containerStatus]);
} else {
$application->update(['last_online_at' => now()]);
}
} else {
//Notify user that this container should not be there.
@@ -478,7 +164,10 @@ class GetContainersStatus
$statusFromDb = $database->status;
if ($statusFromDb !== $containerStatus) {
$database->update(['status' => $containerStatus]);
} else {
$database->update(['last_online_at' => now()]);
}
if ($isPublic) {
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
if ($this->server->isSwarm()) {
@@ -489,7 +178,7 @@ class GetContainersStatus
})->first();
if (! $foundTcpProxy) {
StartDatabaseProxy::run($database);
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
}
}
} else {
@@ -520,6 +209,8 @@ class GetContainersStatus
if ($statusFromDb !== $containerStatus) {
// ray('Updating status: ' . $containerStatus);
$service->update(['status' => $containerStatus]);
} else {
$service->update(['last_online_at' => now()]);
}
}
}
@@ -650,32 +341,5 @@ class GetContainersStatus
}
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
}
if (! $this->server->proxySet() || $this->server->proxy->force_stop) {
return;
}
$foundProxyContainer = $this->containers->filter(function ($value, $key) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
} else {
return data_get($value, 'Name') === '/coolify-proxy';
}
})->first();
if (! $foundProxyContainer) {
try {
$shouldStart = CheckProxy::run($this->server);
if ($shouldStart) {
StartProxy::run($this->server, false);
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
}
} catch (\Throwable $e) {
ray($e);
}
} else {
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
$this->server->save();
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
}
}
}

View File

@@ -6,12 +6,11 @@ use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Password;
use Laravel\Fortify\Contracts\CreatesNewUsers;
class CreateNewUser implements CreatesNewUsers
{
use PasswordValidationRules;
/**
* Validate and create a newly registered user.
*
@@ -32,7 +31,7 @@ class CreateNewUser implements CreatesNewUsers
'max:255',
Rule::unique(User::class),
],
'password' => $this->passwordRules(),
'password' => ['required', Password::defaults(), 'confirmed'],
])->validate();
if (User::count() == 0) {
@@ -41,7 +40,7 @@ class CreateNewUser implements CreatesNewUsers
$user = User::create([
'id' => 0,
'name' => $input['name'],
'email' => $input['email'],
'email' => strtolower($input['email']),
'password' => Hash::make($input['password']),
]);
$team = $user->teams()->first();
@@ -53,7 +52,7 @@ class CreateNewUser implements CreatesNewUsers
} else {
$user = User::create([
'name' => $input['name'],
'email' => $input['email'],
'email' => strtolower($input['email']),
'password' => Hash::make($input['password']),
]);
$team = $user->teams()->first();

View File

@@ -1,18 +0,0 @@
<?php
namespace App\Actions\Fortify;
use Laravel\Fortify\Rules\Password;
trait PasswordValidationRules
{
/**
* Get the validation rules used to validate passwords.
*
* @return array<int, \Illuminate\Contracts\Validation\Rule|array|string>
*/
protected function passwordRules(): array
{
return ['required', 'string', new Password, 'confirmed'];
}
}

View File

@@ -5,12 +5,11 @@ namespace App\Actions\Fortify;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rules\Password;
use Laravel\Fortify\Contracts\ResetsUserPasswords;
class ResetUserPassword implements ResetsUserPasswords
{
use PasswordValidationRules;
/**
* Validate and reset the user's forgotten password.
*
@@ -19,7 +18,7 @@ class ResetUserPassword implements ResetsUserPasswords
public function reset(User $user, array $input): void
{
Validator::make($input, [
'password' => $this->passwordRules(),
'password' => ['required', Password::defaults(), 'confirmed'],
])->validate();
$user->forceFill([

View File

@@ -5,12 +5,11 @@ namespace App\Actions\Fortify;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rules\Password;
use Laravel\Fortify\Contracts\UpdatesUserPasswords;
class UpdateUserPassword implements UpdatesUserPasswords
{
use PasswordValidationRules;
/**
* Validate and update the user's password.
*
@@ -20,7 +19,7 @@ class UpdateUserPassword implements UpdatesUserPasswords
{
Validator::make($input, [
'current_password' => ['required', 'string', 'current_password:web'],
'password' => $this->passwordRules(),
'password' => ['required', Password::defaults(), 'confirmed'],
], [
'current_password.current_password' => __('The provided password does not match your current password.'),
])->validateWithBag('updatePassword');

View File

@@ -25,8 +25,6 @@ class CheckResaleLicense
// }
$base_url = config('coolify.license_url');
$instance_id = config('app.id');
ray("Checking license key against $base_url/lemon/validate");
$data = Http::withHeaders([
'Accept' => 'application/json',
])->get("$base_url/lemon/validate", [
@@ -34,7 +32,6 @@ class CheckResaleLicense
'instance_id' => $instance_id,
])->json();
if (data_get($data, 'valid') === true && data_get($data, 'license_key.status') === 'active') {
ray('Valid & active license key');
$settings->update([
'is_resale_license_active' => true,
]);
@@ -48,7 +45,6 @@ class CheckResaleLicense
'instance_id' => $instance_id,
])->json();
if (data_get($data, 'activated') === true) {
ray('Activated license key');
$settings->update([
'is_resale_license_active' => true,
]);
@@ -60,7 +56,6 @@ class CheckResaleLicense
}
throw new \Exception('Cannot activate license key.');
} catch (\Throwable $e) {
ray($e);
$settings->update([
'resale_license' => null,
'is_resale_license_active' => false,

View File

@@ -4,6 +4,7 @@ namespace App\Actions\Proxy;
use App\Enums\ProxyTypes;
use App\Models\Server;
use Illuminate\Support\Facades\Log;
use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
@@ -88,7 +89,7 @@ class CheckProxy
$portsToCheck = [];
}
} catch (\Exception $e) {
ray($e->getMessage());
Log::error('Error checking proxy: '.$e->getMessage());
}
if (count($portsToCheck) === 0) {
return false;

View File

@@ -13,67 +13,60 @@ class StartProxy
public function handle(Server $server, bool $async = true, bool $force = false): string|Activity
{
try {
$proxyType = $server->proxyType();
if ((is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) && $force === false) {
return 'OK';
}
$commands = collect([]);
$proxy_path = $server->proxyPath();
$configuration = CheckConfiguration::run($server);
if (! $configuration) {
throw new \Exception('Configuration is not synced');
}
SaveConfiguration::run($server, $configuration);
$docker_compose_yml_base64 = base64_encode($configuration);
$server->proxy->last_applied_settings = str($docker_compose_yml_base64)->pipe('md5')->value();
$proxyType = $server->proxyType();
if ((is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) && $force === false) {
return 'OK';
}
$commands = collect([]);
$proxy_path = $server->proxyPath();
$configuration = CheckConfiguration::run($server);
if (! $configuration) {
throw new \Exception('Configuration is not synced');
}
SaveConfiguration::run($server, $configuration);
$docker_compose_yml_base64 = base64_encode($configuration);
$server->proxy->last_applied_settings = str($docker_compose_yml_base64)->pipe('md5')->value();
$server->save();
if ($server->isSwarm()) {
$commands = $commands->merge([
"mkdir -p $proxy_path/dynamic",
"cd $proxy_path",
"echo 'Creating required Docker Compose file.'",
"echo 'Starting coolify-proxy.'",
'docker stack deploy -c docker-compose.yml coolify-proxy',
"echo 'Successfully started coolify-proxy.'",
]);
} else {
$caddfile = 'import /dynamic/*.caddy';
$commands = $commands->merge([
"mkdir -p $proxy_path/dynamic",
"cd $proxy_path",
"echo '$caddfile' > $proxy_path/dynamic/Caddyfile",
"echo 'Creating required Docker Compose file.'",
"echo 'Pulling docker image.'",
'docker compose pull',
'if docker ps -a --format "{{.Names}}" | grep -q "^coolify-proxy$"; then',
" echo 'Stopping and removing existing coolify-proxy.'",
' docker rm -f coolify-proxy || true',
" echo 'Successfully stopped and removed existing coolify-proxy.'",
'fi',
"echo 'Starting coolify-proxy.'",
'docker compose up -d --remove-orphans',
"echo 'Successfully started coolify-proxy.'",
]);
$commands = $commands->merge(connectProxyToNetworks($server));
}
if ($async) {
return remote_process($commands, $server, callEventOnFinish: 'ProxyStarted', callEventData: $server);
} else {
instant_remote_process($commands, $server);
$server->proxy->set('status', 'running');
$server->proxy->set('type', $proxyType);
$server->save();
if ($server->isSwarm()) {
$commands = $commands->merge([
"mkdir -p $proxy_path/dynamic",
"cd $proxy_path",
"echo 'Creating required Docker Compose file.'",
"echo 'Starting coolify-proxy.'",
'docker stack deploy -c docker-compose.yml coolify-proxy',
"echo 'Successfully started coolify-proxy.'",
]);
} else {
$caddfile = 'import /dynamic/*.caddy';
$commands = $commands->merge([
"mkdir -p $proxy_path/dynamic",
"cd $proxy_path",
"echo '$caddfile' > $proxy_path/dynamic/Caddyfile",
"echo 'Creating required Docker Compose file.'",
"echo 'Pulling docker image.'",
'docker compose pull',
'if docker ps -a --format "{{.Names}}" | grep -q "^coolify-proxy$"; then',
" echo 'Stopping and removing existing coolify-proxy.'",
' docker rm -f coolify-proxy || true',
" echo 'Successfully stopped and removed existing coolify-proxy.'",
'fi',
"echo 'Starting coolify-proxy.'",
'docker compose up -d --remove-orphans',
"echo 'Successfully started coolify-proxy.'",
]);
$commands = $commands->merge(connectProxyToNetworks($server));
}
ProxyStarted::dispatch($server);
if ($async) {
$activity = remote_process($commands, $server, callEventOnFinish: 'ProxyStarted', callEventData: $server);
return $activity;
} else {
instant_remote_process($commands, $server);
$server->proxy->set('status', 'running');
$server->proxy->set('type', $proxyType);
$server->save();
ProxyStarted::dispatch($server);
return 'OK';
}
} catch (\Throwable $e) {
ray($e);
throw $e;
return 'OK';
}
}
}

View File

@@ -40,7 +40,6 @@ class ConfigureCloudflared
]);
instant_remote_process($commands, $server);
} catch (\Throwable $e) {
ray($e);
$server->settings->is_cloudflare_tunnel = false;
$server->settings->save();
throw $e;
@@ -51,7 +50,6 @@ class ConfigureCloudflared
'rm -fr /tmp/cloudflared',
]);
instant_remote_process($commands, $server);
}
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Actions\Server;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
class DeleteServer
{
use AsAction;
public function handle(Server $server)
{
StopSentinel::run($server);
$server->forceDelete();
}
}

View File

@@ -12,12 +12,11 @@ class InstallDocker
public function handle(Server $server)
{
$dockerVersion = config('constants.docker_install_version');
$supported_os_type = $server->validateOS();
if (! $supported_os_type) {
throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://coolify.io/docs/installation#manually">documentation</a>.');
}
ray('Installing Docker on server: '.$server->name.' ('.$server->ip.')'.' with OS type: '.$supported_os_type);
$dockerVersion = '24.0';
$config = base64_encode('{
"log-driver": "json-file",
"log-opts": {

View File

@@ -0,0 +1,41 @@
<?php
namespace App\Actions\Server;
use App\Models\Application;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use App\Models\StandaloneClickhouse;
use App\Models\StandaloneDragonfly;
use App\Models\StandaloneKeydb;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Lorisleiva\Actions\Concerns\AsAction;
class ResourcesCheck
{
use AsAction;
public function handle()
{
$seconds = 60;
try {
Application::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
ServiceApplication::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
ServiceDatabase::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
StandalonePostgresql::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
StandaloneRedis::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
StandaloneMongodb::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
StandaloneMysql::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
StandaloneMariadb::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
StandaloneKeydb::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
StandaloneDragonfly::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
StandaloneClickhouse::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
} catch (\Throwable $e) {
return handleError($e);
}
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Actions\Server;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
class RestartContainer
{
use AsAction;
public function handle(Server $server, string $containerName)
{
$server->restartContainer($containerName);
}
}

View File

@@ -12,8 +12,6 @@ class RunCommand
public function handle(Server $server, $command)
{
$activity = remote_process(command: [$command], server: $server, ignore_errors: true, type: ActivityTypes::COMMAND->value);
return $activity;
return remote_process(command: [$command], server: $server, ignore_errors: true, type: ActivityTypes::COMMAND->value);
}
}

View File

@@ -0,0 +1,269 @@
<?php
namespace App\Actions\Server;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Proxy\CheckProxy;
use App\Actions\Proxy\StartProxy;
use App\Jobs\CheckAndStartSentinelJob;
use App\Jobs\ServerStorageCheckJob;
use App\Models\Application;
use App\Models\ApplicationPreview;
use App\Models\Server;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use App\Notifications\Container\ContainerRestarted;
use Illuminate\Support\Arr;
use Lorisleiva\Actions\Concerns\AsAction;
class ServerCheck
{
use AsAction;
public Server $server;
public bool $isSentinel = false;
public $containers;
public $databases;
public function handle(Server $server, $data = null)
{
$this->server = $server;
try {
if ($this->server->isFunctional() === false) {
return 'Server is not functional.';
}
if (! $this->server->isSwarmWorker() && ! $this->server->isBuildServer()) {
if (isset($data)) {
$data = collect($data);
$this->server->sentinelHeartbeat();
$this->containers = collect(data_get($data, 'containers'));
$filesystemUsageRoot = data_get($data, 'filesystem_usage_root.used_percentage');
ServerStorageCheckJob::dispatch($this->server, $filesystemUsageRoot);
$containerReplicates = null;
$this->isSentinel = true;
} else {
['containers' => $this->containers, 'containerReplicates' => $containerReplicates] = $this->server->getContainers();
// ServerStorageCheckJob::dispatch($this->server);
}
if (is_null($this->containers)) {
return 'No containers found.';
}
if (isset($containerReplicates)) {
foreach ($containerReplicates as $containerReplica) {
$name = data_get($containerReplica, 'Name');
$this->containers = $this->containers->map(function ($container) use ($name, $containerReplica) {
if (data_get($container, 'Spec.Name') === $name) {
$replicas = data_get($containerReplica, 'Replicas');
$running = str($replicas)->explode('/')[0];
$total = str($replicas)->explode('/')[1];
if ($running === $total) {
data_set($container, 'State.Status', 'running');
data_set($container, 'State.Health.Status', 'healthy');
} else {
data_set($container, 'State.Status', 'starting');
data_set($container, 'State.Health.Status', 'unhealthy');
}
}
return $container;
});
}
}
$this->checkContainers();
if ($this->server->isSentinelEnabled() && $this->isSentinel === false) {
CheckAndStartSentinelJob::dispatch($this->server);
}
if ($this->server->isLogDrainEnabled()) {
$this->checkLogDrainContainer();
}
if ($this->server->proxySet() && ! $this->server->proxy->force_stop) {
$foundProxyContainer = $this->containers->filter(function ($value, $key) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
} else {
return data_get($value, 'Name') === '/coolify-proxy';
}
})->first();
if (! $foundProxyContainer) {
try {
$shouldStart = CheckProxy::run($this->server);
if ($shouldStart) {
StartProxy::run($this->server, false);
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
}
} catch (\Throwable $e) {
}
} else {
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
$this->server->save();
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
}
}
}
} catch (\Throwable $e) {
return handleError($e);
}
}
private function checkLogDrainContainer()
{
$foundLogDrainContainer = $this->containers->filter(function ($value, $key) {
return data_get($value, 'Name') === '/coolify-log-drain';
})->first();
if ($foundLogDrainContainer) {
$status = data_get($foundLogDrainContainer, 'State.Status');
if ($status !== 'running') {
StartLogDrain::dispatch($this->server)->onQueue('high');
}
} else {
StartLogDrain::dispatch($this->server)->onQueue('high');
}
}
private function checkContainers()
{
foreach ($this->containers as $container) {
if ($this->isSentinel) {
$labels = Arr::undot(data_get($container, 'labels'));
} else {
if ($this->server->isSwarm()) {
$labels = Arr::undot(data_get($container, 'Spec.Labels'));
} else {
$labels = Arr::undot(data_get($container, 'Config.Labels'));
}
}
$managed = data_get($labels, 'coolify.managed');
if (! $managed) {
continue;
}
$uuid = data_get($labels, 'coolify.name');
if (! $uuid) {
$uuid = data_get($labels, 'com.docker.compose.service');
}
if ($this->isSentinel) {
$containerStatus = data_get($container, 'state');
$containerHealth = data_get($container, 'health_status');
} else {
$containerStatus = data_get($container, 'State.Status');
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
}
$containerStatus = "$containerStatus ($containerHealth)";
$applicationId = data_get($labels, 'coolify.applicationId');
$serviceId = data_get($labels, 'coolify.serviceId');
$databaseId = data_get($labels, 'coolify.databaseId');
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
if ($applicationId) {
// Application
if ($pullRequestId != 0) {
if (str($applicationId)->contains('-')) {
$applicationId = str($applicationId)->before('-');
}
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
if ($preview) {
$preview->update(['status' => $containerStatus]);
}
} else {
$application = Application::where('id', $applicationId)->first();
if ($application) {
$application->update([
'status' => $containerStatus,
'last_online_at' => now(),
]);
}
}
} elseif (isset($serviceId)) {
// Service
$subType = data_get($labels, 'coolify.service.subType');
$subId = data_get($labels, 'coolify.service.subId');
$service = Service::where('id', $serviceId)->first();
if (! $service) {
continue;
}
if ($subType === 'application') {
$service = ServiceApplication::where('id', $subId)->first();
} else {
$service = ServiceDatabase::where('id', $subId)->first();
}
if ($service) {
$service->update([
'status' => $containerStatus,
'last_online_at' => now(),
]);
if ($subType === 'database') {
$isPublic = data_get($service, 'is_public');
if ($isPublic) {
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
if ($this->isSentinel) {
return data_get($value, 'name') === $uuid.'-proxy';
} else {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else {
return data_get($value, 'Name') === "/$uuid-proxy";
}
}
})->first();
if (! $foundTcpProxy) {
StartDatabaseProxy::run($service);
}
}
}
}
} else {
// Database
if (is_null($this->databases)) {
$this->databases = $this->server->databases();
}
$database = $this->databases->where('uuid', $uuid)->first();
if ($database) {
$database->update([
'status' => $containerStatus,
'last_online_at' => now(),
]);
$isPublic = data_get($database, 'is_public');
if ($isPublic) {
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
if ($this->isSentinel) {
return data_get($value, 'name') === $uuid.'-proxy';
} else {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else {
return data_get($value, 'Name') === "/$uuid-proxy";
}
}
})->first();
if (! $foundTcpProxy) {
StartDatabaseProxy::run($database);
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
}
}
}
}
}
}
}

View File

@@ -5,7 +5,7 @@ namespace App\Actions\Server;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
class InstallLogDrain
class StartLogDrain
{
use AsAction;
@@ -13,12 +13,16 @@ class InstallLogDrain
{
if ($server->settings->is_logdrain_newrelic_enabled) {
$type = 'newrelic';
StopLogDrain::run($server);
} elseif ($server->settings->is_logdrain_highlight_enabled) {
$type = 'highlight';
StopLogDrain::run($server);
} elseif ($server->settings->is_logdrain_axiom_enabled) {
$type = 'axiom';
StopLogDrain::run($server);
} elseif ($server->settings->is_logdrain_custom_enabled) {
$type = 'custom';
StopLogDrain::run($server);
} else {
$type = 'none';
}
@@ -151,6 +155,8 @@ services:
- ./parsers.conf:/parsers.conf
ports:
- 127.0.0.1:24224:24224
labels:
- coolify.managed=true
restart: unless-stopped
');
$readme = base64_encode('# New Relic Log Drain
@@ -202,10 +208,8 @@ Files:
throw new \Exception('Unknown log drain type.');
}
$restart_command = [
"echo 'Stopping old Fluent Bit'",
"cd $config_path && docker compose down --remove-orphans || true",
"echo 'Starting Fluent Bit'",
"cd $config_path && docker compose up -d --remove-orphans",
"cd $config_path && docker compose up -d",
];
$command = array_merge($command, $add_envs_command, $restart_command);

View File

@@ -9,18 +9,57 @@ class StartSentinel
{
use AsAction;
public function handle(Server $server, $version = 'latest', bool $restart = false)
public function handle(Server $server, bool $restart = false, ?string $latestVersion = null)
{
if ($server->isSwarm() || $server->isBuildServer()) {
return;
}
if ($restart) {
StopSentinel::run($server);
}
$metrics_history = $server->settings->metrics_history_days;
$refresh_rate = $server->settings->metrics_refresh_rate_seconds;
$token = $server->settings->metrics_token;
$version = $latestVersion ?? get_latest_sentinel_version();
$metricsHistory = data_get($server, 'settings.sentinel_metrics_history_days');
$refreshRate = data_get($server, 'settings.sentinel_metrics_refresh_rate_seconds');
$pushInterval = data_get($server, 'settings.sentinel_push_interval_seconds');
$token = data_get($server, 'settings.sentinel_token');
$endpoint = data_get($server, 'settings.sentinel_custom_url');
$debug = data_get($server, 'settings.is_sentinel_debug_enabled');
$mountDir = '/data/coolify/sentinel';
$image = "ghcr.io/coollabsio/sentinel:$version";
if (! $endpoint) {
throw new \Exception('You should set FQDN in Instance Settings.');
}
$environments = [
'TOKEN' => $token,
'DEBUG' => $debug ? 'true' : 'false',
'PUSH_ENDPOINT' => $endpoint,
'PUSH_INTERVAL_SECONDS' => $pushInterval,
'COLLECTOR_ENABLED' => $server->isMetricsEnabled() ? 'true' : 'false',
'COLLECTOR_REFRESH_RATE_SECONDS' => $refreshRate,
'COLLECTOR_RETENTION_PERIOD_DAYS' => $metricsHistory,
];
$labels = [
'coolify.managed' => 'true',
];
if (isDev()) {
// data_set($environments, 'DEBUG', 'true');
// $image = 'sentinel';
$mountDir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/sentinel';
}
$dockerEnvironments = '-e "'.implode('" -e "', array_map(fn ($key, $value) => "$key=$value", array_keys($environments), $environments)).'"';
$dockerLabels = implode(' ', array_map(fn ($key, $value) => "$key=$value", array_keys($labels), $labels));
$dockerCommand = "docker run -d $dockerEnvironments --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v $mountDir:/app/db --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 --add-host=host.docker.internal:host-gateway --label $dockerLabels $image";
instant_remote_process([
"docker run --rm --pull always -d -e \"TOKEN={$token}\" -e \"SCHEDULER=true\" -e \"METRICS_HISTORY={$metrics_history}\" -e \"REFRESH_RATE={$refresh_rate}\" --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/metrics:/app/metrics -v /data/coolify/logs:/app/logs --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 ghcr.io/coollabsio/sentinel:$version",
'chown -R 9999:root /data/coolify/metrics /data/coolify/logs',
'chmod -R 700 /data/coolify/metrics /data/coolify/logs',
], $server, true);
'docker rm -f coolify-sentinel || true',
"mkdir -p $mountDir",
$dockerCommand,
"chown -R 9999:root $mountDir",
"chmod -R 700 $mountDir",
], $server);
$server->settings->is_sentinel_enabled = true;
$server->settings->save();
$server->sentinelHeartbeat();
}
}

View File

@@ -12,7 +12,7 @@ class StopLogDrain
public function handle(Server $server)
{
try {
return instant_remote_process(['docker rm -f coolify-log-drain || true'], $server);
return instant_remote_process(['docker rm -f coolify-log-drain'], $server, false);
} catch (\Throwable $e) {
return handleError($e);
}

View File

@@ -12,5 +12,6 @@ class StopSentinel
public function handle(Server $server)
{
instant_remote_process(['docker rm -f coolify-sentinel'], $server, false);
$server->sentinelHeartbeat(isReset: true);
}
}

View File

@@ -18,32 +18,28 @@ class UpdateCoolify
public function handle($manual_update = false)
{
try {
$settings = instanceSettings();
$this->server = Server::find(0);
if (! $this->server) {
$settings = instanceSettings();
$this->server = Server::find(0);
if (! $this->server) {
return;
}
CleanupDocker::dispatch($this->server)->onQueue('high');
$this->latestVersion = get_latest_version_of_coolify();
$this->currentVersion = config('version');
if (! $manual_update) {
if (! $settings->is_auto_update_enabled) {
return;
}
CleanupDocker::dispatch($this->server)->onQueue('high');
$this->latestVersion = get_latest_version_of_coolify();
$this->currentVersion = config('version');
if (! $manual_update) {
if (! $settings->is_auto_update_enabled) {
return;
}
if ($this->latestVersion === $this->currentVersion) {
return;
}
if (version_compare($this->latestVersion, $this->currentVersion, '<')) {
return;
}
if ($this->latestVersion === $this->currentVersion) {
return;
}
if (version_compare($this->latestVersion, $this->currentVersion, '<')) {
return;
}
$this->update();
$settings->new_version_available = false;
$settings->save();
} catch (\Throwable $e) {
throw $e;
}
$this->update();
$settings->new_version_available = false;
$settings->save();
}
private function update()

View File

@@ -4,6 +4,7 @@ namespace App\Actions\Service;
use App\Actions\Server\CleanupDocker;
use App\Models\Service;
use Illuminate\Support\Facades\Log;
use Lorisleiva\Actions\Concerns\AsAction;
class DeleteService
@@ -39,8 +40,8 @@ class DeleteService
if (! empty($commands)) {
foreach ($commands as $command) {
$result = instant_remote_process([$command], $server, false);
if ($result !== 0) {
ray("Failed to execute: $command");
if ($result !== null && $result !== 0) {
Log::error('Error deleting volumes: '.$result);
}
}
}

View File

@@ -12,7 +12,6 @@ class StartService
public function handle(Service $service)
{
ray('Starting service: '.$service->name);
$service->saveComposeConfigs();
$commands[] = 'cd '.$service->workdir();
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
@@ -34,8 +33,7 @@ class StartService
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} >/dev/null 2>&1 || true";
}
}
$activity = remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged');
return $activity;
return remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged');
}
}

View File

@@ -28,8 +28,6 @@ class StopService
}
}
} catch (\Exception $e) {
ray($e->getMessage());
return $e->getMessage();
}
}

View File

@@ -7,7 +7,7 @@ use Illuminate\Support\Facades\DB;
class CleanupDatabase extends Command
{
protected $signature = 'cleanup:database {--yes}';
protected $signature = 'cleanup:database {--yes} {--keep-days=}';
protected $description = 'Cleanup database';
@@ -20,9 +20,9 @@ class CleanupDatabase extends Command
}
if (isCloud()) {
// Later on we can increase this to 180 days or dynamically set
$keep_days = 60;
$keep_days = $this->option('keep-days') ?? 60;
} else {
$keep_days = 60;
$keep_days = $this->option('keep-days') ?? 60;
}
echo "Keep days: $keep_days\n";
// Cleanup failed jobs table
@@ -64,6 +64,5 @@ class CleanupDatabase extends Command
if ($this->option('yes')) {
$webhooks->delete();
}
}
}

View File

@@ -26,6 +26,5 @@ class CleanupRedis extends Command
collect($queueOverlaps)->each(function ($key) {
Redis::connection()->del($key);
});
}
}

View File

@@ -30,14 +30,12 @@ class CleanupStuckedResources extends Command
public function handle()
{
ray('Running cleanup stucked resources.');
echo "Running cleanup stucked resources.\n";
$this->cleanup_stucked_resources();
}
private function cleanup_stucked_resources()
{
try {
$servers = Server::all()->filter(function ($server) {
return $server->isFunctional();

View File

@@ -19,7 +19,6 @@ class CloudCleanupSubscriptions extends Command
return;
}
ray()->clearAll();
$this->info('Cleaning up subcriptions teams');
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
@@ -74,7 +73,6 @@ class CloudCleanupSubscriptions extends Command
}
}
}
} catch (\Exception $e) {
$this->error($e->getMessage());
@@ -96,6 +94,5 @@ class CloudCleanupSubscriptions extends Command
]);
}
}
}
}

View File

@@ -25,7 +25,6 @@ class Dev extends Command
return;
}
}
public function generateOpenApi()

View File

@@ -15,7 +15,6 @@ use App\Notifications\Application\DeploymentSuccess;
use App\Notifications\Application\StatusChanged;
use App\Notifications\Database\BackupFailed;
use App\Notifications\Database\BackupSuccess;
use App\Notifications\Database\DailyBackup;
use App\Notifications\Test;
use Exception;
use Illuminate\Console\Command;
@@ -121,28 +120,10 @@ class Emails extends Command
$this->mail = (new Test)->toMail();
$this->sendEmail();
break;
case 'database-backup-statuses-daily':
$scheduled_backups = ScheduledDatabaseBackup::all();
$databases = collect();
foreach ($scheduled_backups as $scheduled_backup) {
$last_days_backups = $scheduled_backup->get_last_days_backup_status();
if ($last_days_backups->isEmpty()) {
continue;
}
$failed = $last_days_backups->where('status', 'failed');
$database = $scheduled_backup->database;
$databases->put($database->name, [
'failed_count' => $failed->count(),
]);
}
$this->mail = (new DailyBackup($databases))->toMail();
$this->sendEmail();
break;
case 'application-deployment-success-daily':
$applications = Application::all();
foreach ($applications as $application) {
$deployments = $application->get_last_days_deployments();
ray($deployments);
if ($deployments->isEmpty()) {
continue;
}

View File

@@ -10,6 +10,7 @@ use App\Models\Environment;
use App\Models\ScheduledDatabaseBackup;
use App\Models\Server;
use App\Models\StandalonePostgresql;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
@@ -32,16 +33,16 @@ class Init extends Command
$this->servers = Server::all();
if (isCloud()) {
} else {
$this->send_alive_signal();
get_public_ips();
}
// Backward compatibility
$this->disable_metrics();
// $this->disable_metrics();
$this->replace_slash_in_environment_name();
$this->restore_coolify_db_backup();
$this->update_user_emails();
//
$this->update_traefik_labels();
if (! isCloud() || $this->option('force-cloud')) {
@@ -79,17 +80,26 @@ class Init extends Command
}
}
private function disable_metrics()
// private function disable_metrics()
// {
// if (version_compare('4.0.0-beta.312', config('version'), '<=')) {
// foreach ($this->servers as $server) {
// if ($server->settings->is_metrics_enabled === true) {
// $server->settings->update(['is_metrics_enabled' => false]);
// }
// if ($server->isFunctional()) {
// StopSentinel::dispatch($server)->onQueue('high');
// }
// }
// }
// }
private function update_user_emails()
{
if (version_compare('4.0.0-beta.312', config('version'), '<=')) {
foreach ($this->servers as $server) {
if ($server->settings->is_metrics_enabled === true) {
$server->settings->update(['is_metrics_enabled' => false]);
}
if ($server->isFunctional()) {
StopSentinel::dispatch($server);
}
}
try {
User::whereRaw('email ~ \'[A-Z]\'')->get()->each(fn (User $user) => $user->update(['email' => strtolower($user->email)]));
} catch (\Throwable $e) {
echo "Error in updating user emails: {$e->getMessage()}\n";
}
}
@@ -120,7 +130,6 @@ class Init extends Command
} catch (\Throwable $e) {
echo "Error in cleaning up unnecessary dynamic proxy configuration: {$e->getMessage()}\n";
}
}
}
@@ -180,7 +189,7 @@ class Init extends Command
'save_s3' => false,
'frequency' => '0 0 * * *',
'database_id' => $database->id,
'database_type' => 'App\Models\StandalonePostgresql',
'database_type' => \App\Models\StandalonePostgresql::class,
'team_id' => 0,
]);
}
@@ -219,7 +228,6 @@ class Init extends Command
}
$queued_inprogress_deployments = ApplicationDeploymentQueue::whereIn('status', [ApplicationDeploymentStatus::IN_PROGRESS->value, ApplicationDeploymentStatus::QUEUED->value])->get();
foreach ($queued_inprogress_deployments as $deployment) {
ray($deployment->id, $deployment->status);
echo "Cleaning up deployment: {$deployment->id}\n";
$deployment->status = ApplicationDeploymentStatus::FAILED->value;
$deployment->save();

View File

@@ -36,8 +36,6 @@ class NotifyDemo extends Command
return;
}
ray($channel);
}
private function showHelp()

View File

@@ -21,6 +21,5 @@ class OpenApi extends Command
$error = preg_replace('/^\h*\v+/m', '', $error);
echo $error;
echo $process->output();
}
}

View File

@@ -96,7 +96,7 @@ class ServicesDelete extends Command
if (! $confirmed) {
break;
}
DeleteResourceJob::dispatch($toDelete);
DeleteResourceJob::dispatch($toDelete)->onQueue('high');
}
}
}
@@ -122,7 +122,7 @@ class ServicesDelete extends Command
if (! $confirmed) {
return;
}
DeleteResourceJob::dispatch($toDelete);
DeleteResourceJob::dispatch($toDelete)->onQueue('high');
}
}
}
@@ -148,7 +148,7 @@ class ServicesDelete extends Command
if (! $confirmed) {
return;
}
DeleteResourceJob::dispatch($toDelete);
DeleteResourceJob::dispatch($toDelete)->onQueue('high');
}
}
}

View File

@@ -3,128 +3,82 @@
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Arr;
use Symfony\Component\Yaml\Yaml;
class ServicesGenerate extends Command
{
/**
* The name and signature of the console command.
*
* @var string
* {@inheritdoc}
*/
protected $signature = 'services:generate';
/**
* The console command description.
*
* @var string
* {@inheritdoc}
*/
protected $description = 'Generate service-templates.yaml based on /templates/compose directory';
/**
* Execute the console command.
*/
public function handle()
public function handle(): int
{
$files = array_diff(scandir(base_path('templates/compose')), ['.', '..']);
$files = array_filter($files, function ($file) {
return strpos($file, '.yaml') !== false;
});
$serviceTemplatesJson = [];
foreach ($files as $file) {
$parsed = $this->process_file($file);
if ($parsed) {
$name = data_get($parsed, 'name');
$parsed = data_forget($parsed, 'name');
$serviceTemplatesJson[$name] = $parsed;
}
}
$serviceTemplatesJson = json_encode($serviceTemplatesJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
$serviceTemplatesJson = collect(glob(base_path('templates/compose/*.yaml')))
->mapWithKeys(function ($file): array {
$file = basename($file);
$parsed = $this->processFile($file);
return $parsed === false ? [] : [
Arr::pull($parsed, 'name') => $parsed,
];
})->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
file_put_contents(base_path('templates/service-templates.json'), $serviceTemplatesJson.PHP_EOL);
return self::SUCCESS;
}
private function process_file($file)
private function processFile(string $file): false|array
{
$serviceName = str($file)->before('.yaml')->value();
$content = file_get_contents(base_path("templates/compose/$file"));
// $this->info($content);
$ignore = collect(preg_grep('/^# ignore:/', explode("\n", $content)))->values();
if ($ignore->count() > 0) {
$ignore = (bool) str($ignore[0])->after('# ignore:')->trim()->value();
} else {
$ignore = false;
}
if ($ignore) {
$data = collect(explode(PHP_EOL, $content))->mapWithKeys(function ($line): array {
preg_match('/^#(?<key>.*):(?<value>.*)$/U', $line, $m);
return $m ? [trim($m['key']) => trim($m['value'])] : [];
});
if (str($data->get('ignore'))->toBoolean()) {
$this->info("Ignoring $file");
return;
return false;
}
$this->info("Processing $file");
$documentation = collect(preg_grep('/^# documentation:/', explode("\n", $content)))->values();
if ($documentation->count() > 0) {
$documentation = str($documentation[0])->after('# documentation:')->trim()->value();
$documentation = str($documentation)->append('?utm_source=coolify.io');
} else {
$documentation = 'https://coolify.io/docs';
}
$slogan = collect(preg_grep('/^# slogan:/', explode("\n", $content)))->values();
if ($slogan->count() > 0) {
$slogan = str($slogan[0])->after('# slogan:')->trim()->value();
} else {
$slogan = str($file)->headline()->value();
}
$logo = collect(preg_grep('/^# logo:/', explode("\n", $content)))->values();
if ($logo->count() > 0) {
$logo = str($logo[0])->after('# logo:')->trim()->value();
} else {
$logo = 'svgs/coolify.png';
}
$minversion = collect(preg_grep('/^# minversion:/', explode("\n", $content)))->values();
if ($minversion->count() > 0) {
$minversion = str($minversion[0])->after('# minversion:')->trim()->value();
} else {
$minversion = '0.0.0';
}
$env_file = collect(preg_grep('/^# env_file:/', explode("\n", $content)))->values();
if ($env_file->count() > 0) {
$env_file = str($env_file[0])->after('# env_file:')->trim()->value();
} else {
$env_file = null;
}
$documentation = $data->get('documentation');
$documentation = $documentation ? $documentation.'?utm_source=coolify.io' : 'https://coolify.io/docs';
$tags = collect(preg_grep('/^# tags:/', explode("\n", $content)))->values();
if ($tags->count() > 0) {
$tags = str($tags[0])->after('# tags:')->trim()->explode(',')->map(function ($tag) {
return str($tag)->trim()->lower()->value();
})->values();
} else {
$tags = null;
}
$port = collect(preg_grep('/^# port:/', explode("\n", $content)))->values();
if ($port->count() > 0) {
$port = str($port[0])->after('# port:')->trim()->value();
} else {
$port = null;
}
$json = Yaml::parse($content);
$yaml = base64_encode(Yaml::dump($json, 10, 2));
$compose = base64_encode(Yaml::dump($json, 10, 2));
$tags = str($data->get('tags'))->lower()->explode(',')->map(fn ($tag) => trim($tag))->filter();
$tags = $tags->isEmpty() ? null : $tags->all();
$payload = [
'name' => $serviceName,
'name' => pathinfo($file, PATHINFO_FILENAME),
'documentation' => $documentation,
'slogan' => $slogan,
'compose' => $yaml,
'slogan' => $data->get('slogan', str($file)->headline()),
'compose' => $compose,
'tags' => $tags,
'logo' => $logo,
'minversion' => $minversion,
'logo' => $data->get('logo', 'svgs/coolify.png'),
'minversion' => $data->get('minversion', '0.0.0'),
];
if ($port) {
if ($port = $data->get('port')) {
$payload['port'] = $port;
}
if ($env_file) {
$env_file_content = file_get_contents(base_path("templates/compose/$env_file"));
$env_file_base64 = base64_encode($env_file_content);
$payload['envs'] = $env_file_base64;
if ($envFile = $data->get('env_file')) {
$envFileContent = file_get_contents(base_path("templates/compose/$envFile"));
$payload['envs'] = base64_encode($envFileContent);
}
return $payload;

View File

@@ -0,0 +1,58 @@
<?php
namespace App\Console\Commands;
use App\Actions\Server\ServerCheck;
use App\Enums\ProxyStatus;
use App\Enums\ProxyTypes;
use App\Models\Server;
use Illuminate\Console\Command;
use Str;
class Weird extends Command
{
protected $signature = 'weird {--number=1} {--run}';
protected $description = 'Weird stuff';
public function handle()
{
try {
if (! isDev()) {
$this->error('This command can only be run in development mode');
return;
}
$run = $this->option('run');
if ($run) {
$servers = Server::all();
foreach ($servers as $server) {
ServerCheck::dispatch($server);
}
return;
}
$number = $this->option('number');
for ($i = 0; $i < $number; $i++) {
$uuid = Str::uuid();
$server = Server::create([
'name' => 'localhost-'.$uuid,
'description' => 'This is a test docker container in development mode',
'ip' => 'coolify-testing-host',
'team_id' => 0,
'private_key_id' => 1,
'proxy' => [
'type' => ProxyTypes::NONE->value,
'status' => ProxyStatus::EXITED->value,
],
]);
$server->settings->update([
'is_usable' => true,
'is_reachable' => true,
]);
}
} catch (\Exception $e) {
$this->error($e->getMessage());
}
}
}

View File

@@ -2,33 +2,45 @@
namespace App\Console;
use App\Jobs\CheckAndStartSentinelJob;
use App\Jobs\CheckForUpdatesJob;
use App\Jobs\CheckHelperImageJob;
use App\Jobs\CleanupInstanceStuffsJob;
use App\Jobs\CleanupStaleMultiplexedConnections;
use App\Jobs\DatabaseBackupJob;
use App\Jobs\DockerCleanupJob;
use App\Jobs\PullHelperImageJob;
use App\Jobs\PullSentinelImageJob;
use App\Jobs\PullTemplatesFromCDN;
use App\Jobs\ScheduledTaskJob;
use App\Jobs\ServerCheckJob;
use App\Jobs\ServerCleanupMux;
use App\Jobs\ServerStorageCheckJob;
use App\Jobs\UpdateCoolifyJob;
use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledTask;
use App\Models\Server;
use App\Models\Team;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use Illuminate\Support\Carbon;
class Kernel extends ConsoleKernel
{
private $all_servers;
private $allServers;
private InstanceSettings $settings;
private string $updateCheckFrequency;
private string $instanceTimezone;
protected function schedule(Schedule $schedule): void
{
$this->all_servers = Server::all();
$settings = instanceSettings();
$this->allServers = Server::where('ip', '!=', '1.2.3.4');
$this->settings = instanceSettings();
$this->updateCheckFrequency = $this->settings->update_check_frequency ?: '0 * * * *';
$this->instanceTimezone = $this->settings->instance_timezone ?: config('app.timezone');
$schedule->job(new CleanupStaleMultiplexedConnections)->hourly();
@@ -36,108 +48,118 @@ class Kernel extends ConsoleKernel
// Instance Jobs
$schedule->command('horizon:snapshot')->everyMinute();
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
$schedule->job(new CheckHelperImageJob)->everyTenMinutes()->onOneServer();
// Server Jobs
$this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
$this->check_scheduled_tasks($schedule);
$this->checkResources($schedule);
$this->checkScheduledBackups($schedule);
$this->checkScheduledTasks($schedule);
$schedule->command('uploads:clear')->everyTwoMinutes();
$schedule->command('telescope:prune')->daily();
$schedule->job(new PullHelperImageJob)->everyFiveMinutes()->onOneServer();
} else {
// Instance Jobs
$schedule->command('horizon:snapshot')->everyFiveMinutes();
$schedule->command('cleanup:unreachable-servers')->daily()->onOneServer();
$schedule->job(new PullTemplatesFromCDN)->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
$schedule->job(new PullTemplatesFromCDN)->cron($this->updateCheckFrequency)->timezone($this->instanceTimezone)->onOneServer();
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
$this->schedule_updates($schedule);
$this->scheduleUpdates($schedule);
// Server Jobs
$this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
$this->pull_images($schedule);
$this->check_scheduled_tasks($schedule);
$this->checkResources($schedule);
$this->pullImages($schedule);
$this->checkScheduledBackups($schedule);
$this->checkScheduledTasks($schedule);
$schedule->command('cleanup:database --yes')->daily();
$schedule->command('uploads:clear')->everyTwoMinutes();
}
}
private function pull_images($schedule)
private function pullImages($schedule): void
{
$settings = instanceSettings();
$servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
$servers = $this->allServers->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_reachable', true)->get();
foreach ($servers as $server) {
if ($server->isSentinelEnabled()) {
$schedule->job(function () use ($server) {
$sentinel_found = instant_remote_process(['docker inspect coolify-sentinel'], $server, false);
$sentinel_found = json_decode($sentinel_found, true);
$status = data_get($sentinel_found, '0.State.Status', 'exited');
if ($status !== 'running') {
PullSentinelImageJob::dispatch($server);
}
})->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
CheckAndStartSentinelJob::dispatch($server);
})->cron($this->updateCheckFrequency)->timezone($this->instanceTimezone)->onOneServer();
}
}
$schedule->job(new PullHelperImageJob)
->cron($settings->update_check_frequency)
->timezone($settings->instance_timezone)
$schedule->job(new CheckHelperImageJob)
->cron($this->updateCheckFrequency)
->timezone($this->instanceTimezone)
->onOneServer();
}
private function schedule_updates($schedule)
private function scheduleUpdates($schedule): void
{
$settings = instanceSettings();
$updateCheckFrequency = $settings->update_check_frequency;
$schedule->job(new CheckForUpdatesJob)
->cron($updateCheckFrequency)
->timezone($settings->instance_timezone)
->cron($this->updateCheckFrequency)
->timezone($this->instanceTimezone)
->onOneServer();
if ($settings->is_auto_update_enabled) {
$autoUpdateFrequency = $settings->auto_update_frequency;
if ($this->settings->is_auto_update_enabled) {
$autoUpdateFrequency = $this->settings->auto_update_frequency;
$schedule->job(new UpdateCoolifyJob)
->cron($autoUpdateFrequency)
->timezone($settings->instance_timezone)
->timezone($this->instanceTimezone)
->onOneServer();
}
}
private function check_resources($schedule)
private function checkResources($schedule): void
{
if (isCloud()) {
$servers = $this->all_servers->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false)->where('ip', '!=', '1.2.3.4');
$servers = $this->allServers->whereHas('team.subscription')->get();
$own = Team::find(0)->servers;
$servers = $servers->merge($own);
} else {
$servers = $this->all_servers->where('ip', '!=', '1.2.3.4');
$servers = $this->allServers->get();
}
foreach ($servers as $server) {
$schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
// $schedule->job(new ServerStorageCheckJob($server))->everyMinute()->onOneServer();
$serverTimezone = $server->settings->server_timezone;
// Sentinel check
$lastSentinelUpdate = $server->sentinel_updated_at;
if (Carbon::parse($lastSentinelUpdate)->isBefore(now()->subSeconds($server->waitBeforeDoingSshCheck()))) {
// Check container status every minute if Sentinel does not activated
$schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
// $schedule->job(new \App\Jobs\ServerCheckNewJob($server))->everyMinute()->onOneServer();
// Check storage usage every 10 minutes if Sentinel does not activated
$schedule->job(new ServerStorageCheckJob($server))->everyTenMinutes()->onOneServer();
}
if ($server->settings->force_docker_cleanup) {
$schedule->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer();
} else {
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->timezone($serverTimezone)->onOneServer();
}
// Cleanup multiplexed connections every hour
$schedule->job(new ServerCleanupMux($server))->hourly()->onOneServer();
// Temporary solution until we have better memory management for Sentinel
if ($server->isSentinelEnabled()) {
$schedule->job(function () use ($server) {
$server->restartContainer('coolify-sentinel');
})->daily()->onOneServer();
}
}
}
private function check_scheduled_backups($schedule)
private function checkScheduledBackups($schedule): void
{
$scheduled_backups = ScheduledDatabaseBackup::all();
$scheduled_backups = ScheduledDatabaseBackup::where('enabled', true)->get();
if ($scheduled_backups->isEmpty()) {
return;
}
foreach ($scheduled_backups as $scheduled_backup) {
if (! $scheduled_backup->enabled) {
continue;
}
if (is_null(data_get($scheduled_backup, 'database'))) {
ray('database not found');
$scheduled_backup->delete();
continue;
@@ -145,35 +167,30 @@ class Kernel extends ConsoleKernel
$server = $scheduled_backup->server();
if (! $server) {
if (is_null($server)) {
continue;
}
$serverTimezone = $server->settings->server_timezone;
if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
$scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency];
}
$schedule->job(new DatabaseBackupJob(
backup: $scheduled_backup
))->cron($scheduled_backup->frequency)->timezone($serverTimezone)->onOneServer();
))->cron($scheduled_backup->frequency)->timezone($this->instanceTimezone)->onOneServer();
}
}
private function check_scheduled_tasks($schedule)
private function checkScheduledTasks($schedule): void
{
$scheduled_tasks = ScheduledTask::all();
$scheduled_tasks = ScheduledTask::where('enabled', true)->get();
if ($scheduled_tasks->isEmpty()) {
return;
}
foreach ($scheduled_tasks as $scheduled_task) {
if ($scheduled_task->enabled === false) {
continue;
}
$service = $scheduled_task->service;
$application = $scheduled_task->application;
if (! $application && ! $service) {
ray('application/service attached to scheduled task does not exist');
$scheduled_task->delete();
continue;
@@ -193,14 +210,13 @@ class Kernel extends ConsoleKernel
if (! $server) {
continue;
}
$serverTimezone = $server->settings->server_timezone ?: config('app.timezone');
if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) {
$scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency];
}
$schedule->job(new ScheduledTaskJob(
task: $scheduled_task
))->cron($scheduled_task->frequency)->timezone($serverTimezone)->onOneServer();
))->cron($scheduled_task->frequency)->timezone($this->instanceTimezone)->onOneServer();
}
}

37
app/Enums/Role.php Normal file
View File

@@ -0,0 +1,37 @@
<?php
namespace App\Enums;
enum Role: string
{
case MEMBER = 'member';
case ADMIN = 'admin';
case OWNER = 'owner';
public function rank(): int
{
return match ($this) {
self::MEMBER => 1,
self::ADMIN => 2,
self::OWNER => 3,
};
}
public function lt(Role|string $role): bool
{
if (is_string($role)) {
$role = Role::from($role);
}
return $this->rank() < $role->rank();
}
public function gt(Role|string $role): bool
{
if (is_string($role)) {
$role = Role::from($role);
}
return $this->rank() > $role->rank();
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Events;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Auth;
class DatabaseProxyStopped implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $teamId;
public function __construct($teamId = null)
{
if (is_null($teamId)) {
$teamId = Auth::user()->currentTeam()->id ?? null;
}
if (is_null($teamId)) {
throw new \Exception('Team id is null');
}
$this->teamId = $teamId;
}
public function broadcastOn(): array
{
return [
new PrivateChannel("team.{$this->teamId}"),
];
}
}

View File

@@ -7,27 +7,29 @@ use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Auth;
class DatabaseStatusChanged implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public ?string $userId = null;
public $userId = null;
public function __construct($userId = null)
{
if (is_null($userId)) {
$userId = auth()->user()->id ?? null;
$userId = Auth::id() ?? null;
}
if (is_null($userId)) {
return false;
}
$this->userId = $userId;
}
public function broadcastOn(): ?array
{
if ($this->userId) {
if (! is_null($this->userId)) {
return [
new PrivateChannel("user.{$this->userId}"),
];

View File

@@ -16,7 +16,6 @@ class FileStorageChanged implements ShouldBroadcast
public function __construct($teamId = null)
{
ray($teamId);
if (is_null($teamId)) {
throw new \Exception('Team id is null');
}

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Events;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ScheduledTaskDone implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $teamId;
public function __construct($teamId = null)
{
if (is_null($teamId)) {
$teamId = auth()->user()->currentTeam()->id ?? null;
}
if (is_null($teamId)) {
throw new \Exception('Team id is null');
}
$this->teamId = $teamId;
}
public function broadcastOn(): array
{
return [
new PrivateChannel("team.{$this->teamId}"),
];
}
}

View File

@@ -7,6 +7,7 @@ use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Auth;
class ServiceStatusChanged implements ShouldBroadcast
{
@@ -17,7 +18,7 @@ class ServiceStatusChanged implements ShouldBroadcast
public function __construct($userId = null)
{
if (is_null($userId)) {
$userId = auth()->user()->id ?? null;
$userId = Auth::id() ?? null;
}
if (is_null($userId)) {
return false;

View File

@@ -84,7 +84,6 @@ class Handler extends ExceptionHandler
if (str($e->getMessage())->contains('No space left on device')) {
return;
}
ray('reporting to sentry');
Integration::captureUnhandledException($e);
});
}

View File

@@ -1203,7 +1203,7 @@ class ApplicationsController extends Controller
$service->name = "service-$service->uuid";
$service->parse(isNew: true);
if ($instantDeploy) {
StartService::dispatch($service);
StartService::dispatch($service)->onQueue('high');
}
return response()->json(serializeApiResponse([
@@ -1213,7 +1213,6 @@ class ApplicationsController extends Controller
}
return response()->json(['message' => 'Invalid type.'], 400);
}
#[OA\Get(
@@ -1359,7 +1358,7 @@ class ApplicationsController extends Controller
deleteVolumes: $request->query->get('delete_volumes', true),
dockerCleanup: $request->query->get('docker_cleanup', true),
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
);
)->onQueue('high');
return response()->json([
'message' => 'Application deletion request queued.',
@@ -1579,11 +1578,16 @@ class ApplicationsController extends Controller
$request->offsetUnset('docker_compose_domains');
}
$instantDeploy = $request->instant_deploy;
$isStatic = $request->is_static;
$useBuildServer = $request->use_build_server;
$use_build_server = $request->use_build_server;
if (isset($useBuildServer)) {
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
}
if (isset($use_build_server)) {
$application->settings->is_build_server_enabled = $use_build_server;
if (isset($isStatic)) {
$application->settings->is_static = $isStatic;
$application->settings->save();
}
@@ -1687,9 +1691,8 @@ class ApplicationsController extends Controller
'standalone_postgresql_id',
'standalone_redis_id',
]);
$env = $this->removeSensitiveData($env);
return $env;
return $this->removeSensitiveData($env);
});
return response()->json($envs);
@@ -1864,18 +1867,15 @@ class ApplicationsController extends Controller
return response()->json($this->removeSensitiveData($env))->setStatusCode(201);
} else {
return response()->json([
'message' => 'Environment variable not found.',
], 404);
}
}
return response()->json([
'message' => 'Something is not okay. Are you okay?',
], 500);
}
#[OA\Patch(
@@ -2220,14 +2220,12 @@ class ApplicationsController extends Controller
return response()->json([
'uuid' => $env->uuid,
])->setStatusCode(201);
}
}
return response()->json([
'message' => 'Something went wrong.',
], 500);
}
#[OA\Delete(
@@ -2484,7 +2482,7 @@ class ApplicationsController extends Controller
if (! $application) {
return response()->json(['message' => 'Application not found.'], 404);
}
StopApplication::dispatch($application);
StopApplication::dispatch($application)->onQueue('high');
return response()->json(
[
@@ -2575,7 +2573,6 @@ class ApplicationsController extends Controller
'deployment_uuid' => $deployment_uuid->toString(),
],
);
}
#[OA\Post(
@@ -2741,7 +2738,6 @@ class ApplicationsController extends Controller
'custom_labels' => 'The custom_labels should be base64 encoded.',
],
], 422);
}
}
if ($request->has('domains') && $server->isProxyShouldRun()) {

View File

@@ -471,7 +471,6 @@ class DatabasesController extends Controller
$request->offsetSet('mysql_conf', $mysqlConf);
}
break;
}
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
if ($validator->fails() || ! empty($extraFields)) {
@@ -498,15 +497,14 @@ class DatabasesController extends Controller
$database->update($request->all());
if ($whatToDoWithDatabaseProxy === 'start') {
StartDatabaseProxy::dispatch($database);
StartDatabaseProxy::dispatch($database)->onQueue('high');
} elseif ($whatToDoWithDatabaseProxy === 'stop') {
StopDatabaseProxy::dispatch($database);
StopDatabaseProxy::dispatch($database)->onQueue('high');
}
return response()->json([
'message' => 'Database updated.',
]);
}
#[OA\Post(
@@ -1153,7 +1151,7 @@ class DatabasesController extends Controller
}
$database = create_standalone_postgresql($environment->id, $destination->uuid, $request->all());
if ($instantDeploy) {
StartDatabase::dispatch($database);
StartDatabase::dispatch($database)->onQueue('high');
}
$database->refresh();
$payload = [
@@ -1165,7 +1163,6 @@ class DatabasesController extends Controller
}
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
} elseif ($type === NewDatabaseTypes::MARIADB) {
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database'];
$validator = customApiValidator($request->all(), [
@@ -1209,7 +1206,7 @@ class DatabasesController extends Controller
}
$database = create_standalone_mariadb($environment->id, $destination->uuid, $request->all());
if ($instantDeploy) {
StartDatabase::dispatch($database);
StartDatabase::dispatch($database)->onQueue('high');
}
$database->refresh();
@@ -1267,7 +1264,7 @@ class DatabasesController extends Controller
}
$database = create_standalone_mysql($environment->id, $destination->uuid, $request->all());
if ($instantDeploy) {
StartDatabase::dispatch($database);
StartDatabase::dispatch($database)->onQueue('high');
}
$database->refresh();
@@ -1323,7 +1320,7 @@ class DatabasesController extends Controller
}
$database = create_standalone_redis($environment->id, $destination->uuid, $request->all());
if ($instantDeploy) {
StartDatabase::dispatch($database);
StartDatabase::dispatch($database)->onQueue('high');
}
$database->refresh();
@@ -1360,7 +1357,7 @@ class DatabasesController extends Controller
removeUnnecessaryFieldsFromRequest($request);
$database = create_standalone_dragonfly($environment->id, $destination->uuid, $request->all());
if ($instantDeploy) {
StartDatabase::dispatch($database);
StartDatabase::dispatch($database)->onQueue('high');
}
return response()->json(serializeApiResponse([
@@ -1409,7 +1406,7 @@ class DatabasesController extends Controller
}
$database = create_standalone_keydb($environment->id, $destination->uuid, $request->all());
if ($instantDeploy) {
StartDatabase::dispatch($database);
StartDatabase::dispatch($database)->onQueue('high');
}
$database->refresh();
@@ -1445,7 +1442,7 @@ class DatabasesController extends Controller
removeUnnecessaryFieldsFromRequest($request);
$database = create_standalone_clickhouse($environment->id, $destination->uuid, $request->all());
if ($instantDeploy) {
StartDatabase::dispatch($database);
StartDatabase::dispatch($database)->onQueue('high');
}
$database->refresh();
@@ -1503,7 +1500,7 @@ class DatabasesController extends Controller
}
$database = create_standalone_mongodb($environment->id, $destination->uuid, $request->all());
if ($instantDeploy) {
StartDatabase::dispatch($database);
StartDatabase::dispatch($database)->onQueue('high');
}
$database->refresh();
@@ -1596,7 +1593,7 @@ class DatabasesController extends Controller
deleteVolumes: $request->query->get('delete_volumes', true),
dockerCleanup: $request->query->get('docker_cleanup', true),
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
);
)->onQueue('high');
return response()->json([
'message' => 'Database deletion request queued.',
@@ -1669,7 +1666,7 @@ class DatabasesController extends Controller
if (str($database->status)->contains('running')) {
return response()->json(['message' => 'Database is already running.'], 400);
}
StartDatabase::dispatch($database);
StartDatabase::dispatch($database)->onQueue('high');
return response()->json(
[
@@ -1745,7 +1742,7 @@ class DatabasesController extends Controller
if (str($database->status)->contains('stopped') || str($database->status)->contains('exited')) {
return response()->json(['message' => 'Database is already stopped.'], 400);
}
StopDatabase::dispatch($database);
StopDatabase::dispatch($database)->onQueue('high');
return response()->json(
[
@@ -1818,7 +1815,7 @@ class DatabasesController extends Controller
if (! $database) {
return response()->json(['message' => 'Database not found.'], 404);
}
RestartDatabase::dispatch($database);
RestartDatabase::dispatch($database)->onQueue('high');
return response()->json(
[
@@ -1826,6 +1823,5 @@ class DatabasesController extends Controller
],
200
);
}
}

View File

@@ -292,7 +292,7 @@ class DeployController extends Controller
return ['message' => "Resource ($resource) not found.", 'deployment_uuid' => $deployment_uuid];
}
switch ($resource?->getMorphClass()) {
case 'App\Models\Application':
case \App\Models\Application::class:
$deployment_uuid = new Cuid2;
queue_application_deployment(
application: $resource,
@@ -301,13 +301,13 @@ class DeployController extends Controller
);
$message = "Application {$resource->name} deployment queued.";
break;
case 'App\Models\Service':
case \App\Models\Service::class:
StartService::run($resource);
$message = "Service {$resource->name} started. It could take a while, be patient.";
break;
default:
// Database resource
StartDatabase::dispatch($resource);
StartDatabase::dispatch($resource)->onQueue('high');
$resource->update([
'started_at' => now(),
]);

View File

@@ -160,7 +160,7 @@ class OtherController extends Controller
#[OA\Get(
summary: 'Healthcheck',
description: 'Healthcheck endpoint.',
path: '/healthcheck',
path: '/health',
operationId: 'healthcheck',
responses: [
new OA\Response(

View File

@@ -356,7 +356,6 @@ class ProjectController extends Controller
'name' => $project->name,
'description' => $project->description,
])->setStatusCode(201);
}
#[OA\Delete(
@@ -423,7 +422,7 @@ class ProjectController extends Controller
if (! $project) {
return response()->json(['message' => 'Project not found.'], 404);
}
if ($project->resource_count() > 0) {
if (! $project->isEmpty()) {
return response()->json(['message' => 'Project has resources, so it cannot be deleted.'], 400);
}

View File

@@ -53,7 +53,7 @@ class ResourcesController extends Controller
$resources = $resources->flatten();
$resources = $resources->map(function ($resource) {
$payload = $resource->toArray();
if ($resource->getMorphClass() === 'App\Models\Service') {
if ($resource->getMorphClass() === \App\Models\Service::class) {
$payload['status'] = $resource->status();
} else {
$payload['status'] = $resource->status;

View File

@@ -2,6 +2,7 @@
namespace App\Http\Controllers\Api;
use App\Actions\Server\DeleteServer;
use App\Actions\Server\ValidateServer;
use App\Enums\ProxyStatus;
use App\Enums\ProxyTypes;
@@ -23,7 +24,7 @@ class ServersController extends Controller
return serializeApiResponse($settings);
}
$settings = $settings->makeHidden([
'metrics_token',
'sentinel_token',
]);
return serializeApiResponse($settings);
@@ -248,7 +249,6 @@ class ServersController extends Controller
return $payload;
});
$server = $this->removeSensitiveData($server);
ray($server);
return response()->json(serializeApiResponse(data_get($server, 'resources')));
}
@@ -538,7 +538,7 @@ class ServersController extends Controller
'is_build_server' => $request->is_build_server,
]);
if ($request->instant_validate) {
ValidateServer::dispatch($server);
ValidateServer::dispatch($server)->onQueue('high');
}
return response()->json([
@@ -651,7 +651,7 @@ class ServersController extends Controller
]);
}
if ($request->instant_validate) {
ValidateServer::dispatch($server);
ValidateServer::dispatch($server)->onQueue('high');
}
return response()->json(serializeApiResponse($server))->setStatusCode(201);
@@ -726,6 +726,7 @@ class ServersController extends Controller
return response()->json(['message' => 'Server has resources, so you need to delete them before.'], 400);
}
$server->delete();
DeleteServer::dispatch($server);
return response()->json(['message' => 'Server deleted.']);
}
@@ -786,7 +787,7 @@ class ServersController extends Controller
if (! $server) {
return response()->json(['message' => 'Server not found.'], 404);
}
ValidateServer::dispatch($server);
ValidateServer::dispatch($server)->onQueue('high');
return response()->json(['message' => 'Validation started.']);
}

View File

@@ -342,7 +342,7 @@ class ServicesController extends Controller
}
$service->parse(isNew: true);
if ($instantDeploy) {
StartService::dispatch($service);
StartService::dispatch($service)->onQueue('high');
}
$domains = $service->applications()->get()->pluck('fqdn')->sort();
$domains = $domains->map(function ($domain) {
@@ -487,7 +487,7 @@ class ServicesController extends Controller
deleteVolumes: $request->query->get('delete_volumes', true),
dockerCleanup: $request->query->get('docker_cleanup', true),
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
);
)->onQueue('high');
return response()->json([
'message' => 'Service deletion request queued.',
@@ -566,9 +566,8 @@ class ServicesController extends Controller
'standalone_postgresql_id',
'standalone_redis_id',
]);
$env = $this->removeSensitiveData($env);
return $env;
return $this->removeSensitiveData($env);
});
return response()->json($envs);
@@ -1077,7 +1076,7 @@ class ServicesController extends Controller
if (str($service->status())->contains('running')) {
return response()->json(['message' => 'Service is already running.'], 400);
}
StartService::dispatch($service);
StartService::dispatch($service)->onQueue('high');
return response()->json(
[
@@ -1155,7 +1154,7 @@ class ServicesController extends Controller
if (str($service->status())->contains('stopped') || str($service->status())->contains('exited')) {
return response()->json(['message' => 'Service is already stopped.'], 400);
}
StopService::dispatch($service);
StopService::dispatch($service)->onQueue('high');
return response()->json(
[
@@ -1230,7 +1229,7 @@ class ServicesController extends Controller
if (! $service) {
return response()->json(['message' => 'Service not found.'], 404);
}
RestartService::dispatch($service);
RestartService::dispatch($service)->onQueue('high');
return response()->json(
[
@@ -1238,6 +1237,5 @@ class ServicesController extends Controller
],
200
);
}
}

View File

@@ -110,59 +110,54 @@ class Controller extends BaseController
return redirect()->route('login')->with('error', 'Invalid credentials.');
}
public function accept_invitation()
public function acceptInvitation()
{
try {
$resetPassword = request()->query('reset-password');
$invitationUuid = request()->route('uuid');
$invitation = TeamInvitation::whereUuid($invitationUuid)->firstOrFail();
$user = User::whereEmail($invitation->email)->firstOrFail();
$invitationValid = $invitation->isValid();
if ($invitationValid) {
if ($resetPassword) {
$user->update([
'password' => Hash::make($invitationUuid),
'force_password_reset' => true,
]);
}
if ($user->teams()->where('team_id', $invitation->team->id)->exists()) {
$invitation->delete();
$resetPassword = request()->query('reset-password');
$invitationUuid = request()->route('uuid');
return redirect()->route('team.index');
}
$user->teams()->attach($invitation->team->id, ['role' => $invitation->role]);
$invitation = TeamInvitation::whereUuid($invitationUuid)->firstOrFail();
$user = User::whereEmail($invitation->email)->firstOrFail();
if (Auth::id() !== $user->id) {
abort(400, 'You are not allowed to accept this invitation.');
}
$invitationValid = $invitation->isValid();
if ($invitationValid) {
if ($resetPassword) {
$user->update([
'password' => Hash::make($invitationUuid),
'force_password_reset' => true,
]);
}
if ($user->teams()->where('team_id', $invitation->team->id)->exists()) {
$invitation->delete();
if (auth()->user()?->id !== $user->id) {
return redirect()->route('login');
}
refreshSession($invitation->team);
return redirect()->route('team.index');
} else {
abort(401);
}
} catch (\Throwable $e) {
ray($e->getMessage());
throw $e;
$user->teams()->attach($invitation->team->id, ['role' => $invitation->role]);
$invitation->delete();
refreshSession($invitation->team);
return redirect()->route('team.index');
} else {
abort(400, 'Invitation expired.');
}
}
public function revoke_invitation()
{
try {
$invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail();
$user = User::whereEmail($invitation->email)->firstOrFail();
if (is_null(auth()->user())) {
return redirect()->route('login');
}
if (auth()->user()->id !== $user->id) {
abort(401);
}
$invitation->delete();
return redirect()->route('team.index');
} catch (\Throwable $e) {
throw $e;
$invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail();
$user = User::whereEmail($invitation->email)->firstOrFail();
if (is_null(Auth::user())) {
return redirect()->route('login');
}
if (Auth::id() !== $user->id) {
abort(401);
}
$invitation->delete();
return redirect()->route('team.index');
}
}

View File

@@ -35,8 +35,6 @@ class OauthController extends Controller
return redirect('/');
} catch (\Exception $e) {
ray($e->getMessage());
$errorCode = $e instanceof HttpException ? 'auth.failed' : 'auth.failed.callback';
return redirect()->route('login')->withErrors([__($errorCode)]);

View File

@@ -5,7 +5,6 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Storage;
use Pion\Laravel\ChunkUpload\Exceptions\UploadMissingFileException;
use Pion\Laravel\ChunkUpload\Handler\HandlerFactory;
use Pion\Laravel\ChunkUpload\Receiver\FileReceiver;

View File

@@ -16,7 +16,6 @@ class Bitbucket extends Controller
{
try {
if (app()->isDownForMaintenance()) {
ray('Maintenance mode is on');
$epoch = now()->valueOf();
$data = [
'attributes' => $request->attributes->all(),
@@ -55,7 +54,6 @@ class Bitbucket extends Controller
'message' => 'Nothing to do. No branch found in the request.',
]);
}
ray('Manual webhook bitbucket push event with branch: '.$branch);
}
if ($x_bitbucket_event === 'pullrequest:created' || $x_bitbucket_event === 'pullrequest:rejected' || $x_bitbucket_event === 'pullrequest:fulfilled') {
$branch = data_get($payload, 'pullrequest.destination.branch.name');
@@ -85,7 +83,6 @@ class Bitbucket extends Controller
'status' => 'failed',
'message' => 'Invalid signature.',
]);
ray('Invalid signature');
continue;
}
@@ -96,13 +93,11 @@ class Bitbucket extends Controller
'status' => 'failed',
'message' => 'Server is not functional.',
]);
ray('Server is not functional: '.$application->destination->server->name);
continue;
}
if ($x_bitbucket_event === 'repo:push') {
if ($application->isDeployable()) {
ray('Deploying '.$application->name.' with branch '.$branch);
$deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
@@ -126,7 +121,6 @@ class Bitbucket extends Controller
}
if ($x_bitbucket_event === 'pullrequest:created') {
if ($application->isPRDeployable()) {
ray('Deploying preview for '.$application->name.' with branch '.$branch.' and base branch '.$base_branch.' and pull request id '.$pull_request_id);
$deployment_uuid = new Cuid2;
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found) {
@@ -171,7 +165,6 @@ class Bitbucket extends Controller
}
}
if ($x_bitbucket_event === 'pullrequest:rejected' || $x_bitbucket_event === 'pullrequest:fulfilled') {
ray('Pull request rejected');
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if ($found) {
$found->delete();
@@ -191,12 +184,9 @@ class Bitbucket extends Controller
}
}
}
ray($return_payloads);
return response($return_payloads);
} catch (Exception $e) {
ray($e);
return handleError($e);
}
}

View File

@@ -19,15 +19,12 @@ class Gitea extends Controller
$return_payloads = collect([]);
$x_gitea_delivery = request()->header('X-Gitea-Delivery');
if (app()->isDownForMaintenance()) {
ray('Maintenance mode is on');
$epoch = now()->valueOf();
$files = Storage::disk('webhooks-during-maintenance')->files();
$gitea_delivery_found = collect($files)->filter(function ($file) use ($x_gitea_delivery) {
return Str::contains($file, $x_gitea_delivery);
})->first();
if ($gitea_delivery_found) {
ray('Webhook already found');
return;
}
$data = [
@@ -67,8 +64,6 @@ class Gitea extends Controller
$removed_files = data_get($payload, 'commits.*.removed');
$modified_files = data_get($payload, 'commits.*.modified');
$changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten();
ray($changed_files);
ray('Manual Webhook Gitea Push Event with branch: '.$branch);
}
if ($x_gitea_event === 'pull_request') {
$action = data_get($payload, 'action');
@@ -77,7 +72,6 @@ class Gitea extends Controller
$pull_request_html_url = data_get($payload, 'pull_request.html_url');
$branch = data_get($payload, 'pull_request.head.ref');
$base_branch = data_get($payload, 'pull_request.base.ref');
ray('Webhook Gitea Pull Request Event with branch: '.$branch.' and base branch: '.$base_branch.' and pull request id: '.$pull_request_id);
}
if (! $branch) {
return response('Nothing to do. No branch found in the request.');
@@ -99,7 +93,6 @@ class Gitea extends Controller
$webhook_secret = data_get($application, 'manual_webhook_secret_gitea');
$hmac = hash_hmac('sha256', $request->getContent(), $webhook_secret);
if (! hash_equals($x_hub_signature_256, $hmac) && ! isDev()) {
ray('Invalid signature');
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
@@ -122,7 +115,6 @@ class Gitea extends Controller
if ($application->isDeployable()) {
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
ray('Deploying '.$application->name.' with branch '.$branch);
$deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
@@ -182,7 +174,6 @@ class Gitea extends Controller
'pull_request_html_url' => $pull_request_html_url,
]);
}
}
queue_application_deployment(
application: $application,
@@ -228,12 +219,9 @@ class Gitea extends Controller
}
}
}
ray($return_payloads);
return response($return_payloads);
} catch (Exception $e) {
ray($e->getMessage());
return handleError($e);
}
}

View File

@@ -25,15 +25,12 @@ class Github extends Controller
$return_payloads = collect([]);
$x_github_delivery = request()->header('X-GitHub-Delivery');
if (app()->isDownForMaintenance()) {
ray('Maintenance mode is on');
$epoch = now()->valueOf();
$files = Storage::disk('webhooks-during-maintenance')->files();
$github_delivery_found = collect($files)->filter(function ($file) use ($x_github_delivery) {
return Str::contains($file, $x_github_delivery);
})->first();
if ($github_delivery_found) {
ray('Webhook already found');
return;
}
$data = [
@@ -73,7 +70,6 @@ class Github extends Controller
$removed_files = data_get($payload, 'commits.*.removed');
$modified_files = data_get($payload, 'commits.*.modified');
$changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten();
ray('Manual Webhook GitHub Push Event with branch: '.$branch);
}
if ($x_github_event === 'pull_request') {
$action = data_get($payload, 'action');
@@ -82,7 +78,6 @@ class Github extends Controller
$pull_request_html_url = data_get($payload, 'pull_request.html_url');
$branch = data_get($payload, 'pull_request.head.ref');
$base_branch = data_get($payload, 'pull_request.base.ref');
ray('Webhook GitHub Pull Request Event with branch: '.$branch.' and base branch: '.$base_branch.' and pull request id: '.$pull_request_id);
}
if (! $branch) {
return response('Nothing to do. No branch found in the request.');
@@ -104,7 +99,6 @@ class Github extends Controller
$webhook_secret = data_get($application, 'manual_webhook_secret_github');
$hmac = hash_hmac('sha256', $request->getContent(), $webhook_secret);
if (! hash_equals($x_hub_signature_256, $hmac) && ! isDev()) {
ray('Invalid signature');
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
@@ -127,7 +121,6 @@ class Github extends Controller
if ($application->isDeployable()) {
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
ray('Deploying '.$application->name.' with branch '.$branch);
$deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
@@ -232,12 +225,9 @@ class Github extends Controller
}
}
}
ray($return_payloads);
return response($return_payloads);
} catch (Exception $e) {
ray($e->getMessage());
return handleError($e);
}
}
@@ -249,15 +239,12 @@ class Github extends Controller
$id = null;
$x_github_delivery = $request->header('X-GitHub-Delivery');
if (app()->isDownForMaintenance()) {
ray('Maintenance mode is on');
$epoch = now()->valueOf();
$files = Storage::disk('webhooks-during-maintenance')->files();
$github_delivery_found = collect($files)->filter(function ($file) use ($x_github_delivery) {
return Str::contains($file, $x_github_delivery);
})->first();
if ($github_delivery_found) {
ray('Webhook already found');
return;
}
$data = [
@@ -313,7 +300,6 @@ class Github extends Controller
$removed_files = data_get($payload, 'commits.*.removed');
$modified_files = data_get($payload, 'commits.*.modified');
$changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten();
ray('Webhook GitHub Push Event: '.$id.' with branch: '.$branch);
}
if ($x_github_event === 'pull_request') {
$action = data_get($payload, 'action');
@@ -322,7 +308,6 @@ class Github extends Controller
$pull_request_html_url = data_get($payload, 'pull_request.html_url');
$branch = data_get($payload, 'pull_request.head.ref');
$base_branch = data_get($payload, 'pull_request.base.ref');
ray('Webhook GitHub Pull Request Event: '.$id.' with branch: '.$branch.' and base branch: '.$base_branch.' and pull request id: '.$pull_request_id);
}
if (! $id || ! $branch) {
return response('Nothing to do. No id or branch found.');
@@ -356,7 +341,6 @@ class Github extends Controller
if ($application->isDeployable()) {
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
ray('Deploying '.$application->name.' with branch '.$branch);
$deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
@@ -460,8 +444,6 @@ class Github extends Controller
return response($return_payloads);
} catch (Exception $e) {
ray($e->getMessage());
return handleError($e);
}
}
@@ -505,7 +487,6 @@ class Github extends Controller
try {
$installation_id = $request->get('installation_id');
if (app()->isDownForMaintenance()) {
ray('Maintenance mode is on');
$epoch = now()->valueOf();
$data = [
'attributes' => $request->attributes->all(),

View File

@@ -17,7 +17,6 @@ class Gitlab extends Controller
{
try {
if (app()->isDownForMaintenance()) {
ray('Maintenance mode is on');
$epoch = now()->valueOf();
$data = [
'attributes' => $request->attributes->all(),
@@ -67,7 +66,6 @@ class Gitlab extends Controller
$removed_files = data_get($payload, 'commits.*.removed');
$modified_files = data_get($payload, 'commits.*.modified');
$changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten();
ray('Manual Webhook GitLab Push Event with branch: '.$branch);
}
if ($x_gitlab_event === 'merge_request') {
$action = data_get($payload, 'object_attributes.action');
@@ -84,7 +82,6 @@ class Gitlab extends Controller
return response($return_payloads);
}
ray('Webhook GitHub Pull Request Event with branch: '.$branch.' and base branch: '.$base_branch.' and pull request id: '.$pull_request_id);
}
$applications = Application::where('git_repository', 'like', "%$full_name%");
if ($x_gitlab_event === 'push') {
@@ -117,7 +114,6 @@ class Gitlab extends Controller
'status' => 'failed',
'message' => 'Invalid signature.',
]);
ray('Invalid signature');
continue;
}
@@ -128,7 +124,6 @@ class Gitlab extends Controller
'status' => 'failed',
'message' => 'Server is not functional',
]);
ray('Server is not functional: '.$application->destination->server->name);
continue;
}
@@ -136,7 +131,6 @@ class Gitlab extends Controller
if ($application->isDeployable()) {
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
ray('Deploying '.$application->name.' with branch '.$branch);
$deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
@@ -171,7 +165,6 @@ class Gitlab extends Controller
'application_uuid' => $application->uuid,
'application_name' => $application->name,
]);
ray('Deployments disabled for '.$application->name);
}
}
if ($x_gitlab_event === 'merge_request') {
@@ -207,7 +200,6 @@ class Gitlab extends Controller
is_webhook: true,
git_type: 'gitlab'
);
ray('Deploying preview for '.$application->name.' with branch '.$branch.' and base branch '.$base_branch.' and pull request id '.$pull_request_id);
$return_payloads->push([
'application' => $application->name,
'status' => 'success',
@@ -219,7 +211,6 @@ class Gitlab extends Controller
'status' => 'failed',
'message' => 'Preview deployments disabled',
]);
ray('Preview deployments disabled for '.$application->name);
}
} elseif ($action === 'closed' || $action === 'close' || $action === 'merge') {
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
@@ -253,8 +244,6 @@ class Gitlab extends Controller
return response($return_payloads);
} catch (Exception $e) {
ray($e->getMessage());
return handleError($e);
}
}

View File

@@ -13,7 +13,6 @@ use App\Models\Webhook;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Sleep;
use Illuminate\Support\Str;
class Stripe extends Controller
@@ -22,7 +21,6 @@ class Stripe extends Controller
{
try {
if (app()->isDownForMaintenance()) {
ray('Maintenance mode is on');
$epoch = now()->valueOf();
$data = [
'attributes' => $request->attributes->all(),
@@ -65,22 +63,18 @@ class Stripe extends Controller
$piData = $stripe->paymentIntents->retrieve($pi, []);
$customerId = data_get($piData, 'customer');
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
if (! $subscription) {
Sleep::for(5)->seconds();
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
}
if (! $subscription) {
Sleep::for(5)->seconds();
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
}
if ($subscription) {
$subscriptionId = data_get($subscription, 'stripe_subscription_id');
$stripe->subscriptions->cancel($subscriptionId, []);
$subscription->update([
'stripe_invoice_paid' => false,
]);
send_internal_notification("Early fraud warning created Refunded, subscription canceled. Charge: {$charge}, id: {$id}, pi: {$pi}");
} else {
send_internal_notification("Early fraud warning: subscription not found. Charge: {$charge}, id: {$id}, pi: {$pi}");
return response("Early fraud warning: subscription not found. Charge: {$charge}, id: {$id}, pi: {$pi}", 400);
}
send_internal_notification("Early fraud warning created Refunded, subscription canceled. Charge: {$charge}, id: {$id}, pi: {$pi}");
break;
case 'checkout.session.completed':
$clientReferenceId = data_get($data, 'client_reference_id');
@@ -96,7 +90,8 @@ class Stripe extends Controller
$found = $team->members->where('id', $userId)->first();
if (! $found->isAdmin()) {
send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}.");
throw new Exception("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}.");
return response("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}.", 400);
}
$subscription = Subscription::where('team_id', $teamId)->first();
if ($subscription) {
@@ -124,13 +119,13 @@ class Stripe extends Controller
break;
}
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
if (! $subscription) {
Sleep::for(5)->seconds();
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
if ($subscription) {
$subscription->update([
'stripe_invoice_paid' => true,
]);
} else {
return response("No subscription found for customer: {$customerId}", 400);
}
$subscription->update([
'stripe_invoice_paid' => true,
]);
break;
case 'invoice.payment_failed':
$customerId = data_get($data, 'customer');
@@ -168,7 +163,42 @@ class Stripe extends Controller
}
send_internal_notification('Subscription payment failed for customer: '.$customerId);
break;
case 'customer.subscription.created':
$customerId = data_get($data, 'customer');
$subscriptionId = data_get($data, 'id');
$teamId = data_get($data, 'metadata.team_id');
$userId = data_get($data, 'metadata.user_id');
if (! $teamId || ! $userId) {
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
if ($subscription) {
return response("Subscription already exists for customer: {$customerId}", 200);
}
return response('No team id or user id found', 400);
}
$team = Team::find($teamId);
$found = $team->members->where('id', $userId)->first();
if (! $found->isAdmin()) {
send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}.");
return response("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}.", 400);
}
$subscription = Subscription::where('team_id', $teamId)->first();
if ($subscription) {
return response("Subscription already exists for team: {$teamId}", 200);
} else {
Subscription::create([
'team_id' => $teamId,
'stripe_subscription_id' => $subscriptionId,
'stripe_customer_id' => $customerId,
'stripe_invoice_paid' => false,
]);
return response('Subscription created');
}
case 'customer.subscription.updated':
$teamId = data_get($data, 'metadata.team_id');
$userId = data_get($data, 'metadata.user_id');
$customerId = data_get($data, 'customer');
$status = data_get($data, 'status');
$subscriptionId = data_get($data, 'items.data.0.subscription');
@@ -178,32 +208,27 @@ class Stripe extends Controller
break;
}
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
if (! $subscription) {
Sleep::for(5)->seconds();
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
}
if (! $subscription) {
if ($status === 'incomplete_expired') {
// send_internal_notification('Subscription incomplete expired for customer: '.$customerId);
return response('Subscription incomplete expired', 200);
}
// send_internal_notification('No subscription found for: '.$customerId);
return response('No subscription found', 400);
if ($teamId) {
$subscription = Subscription::create([
'team_id' => $teamId,
'stripe_subscription_id' => $subscriptionId,
'stripe_customer_id' => $customerId,
'stripe_invoice_paid' => false,
]);
} else {
return response('No subscription and team id found', 400);
}
}
$trialEndedAlready = data_get($subscription, 'stripe_trial_already_ended');
$cancelAtPeriodEnd = data_get($data, 'cancel_at_period_end');
$alreadyCancelAtPeriodEnd = data_get($subscription, 'stripe_cancel_at_period_end');
$feedback = data_get($data, 'cancellation_details.feedback');
$comment = data_get($data, 'cancellation_details.comment');
$lookup_key = data_get($data, 'items.data.0.price.lookup_key');
if (str($lookup_key)->contains('ultimate') || str($lookup_key)->contains('dynamic')) {
if (str($lookup_key)->contains('dynamic')) {
$quantity = data_get($data, 'items.data.0.quantity', 2);
} else {
$quantity = data_get($data, 'items.data.0.quantity', 10);
}
if (str($lookup_key)->contains('dynamic')) {
$quantity = data_get($data, 'items.data.0.quantity', 2);
$team = data_get($subscription, 'team');
if ($team) {
$team->update([
@@ -222,28 +247,12 @@ class Stripe extends Controller
$subscription->update([
'stripe_invoice_paid' => false,
]);
// send_internal_notification('Subscription paused or incomplete for customer: '.$customerId);
}
// Trial ended but subscribed, reactive servers
if ($trialEndedAlready && $status === 'active') {
$team = data_get($subscription, 'team');
$team->trialEndedButSubscribed();
}
if ($feedback) {
$reason = "Cancellation feedback for {$customerId}: '".$feedback."'";
if ($comment) {
$reason .= ' with comment: \''.$comment."'";
}
// send_internal_notification($reason);
}
if ($alreadyCancelAtPeriodEnd !== $cancelAtPeriodEnd) {
if ($cancelAtPeriodEnd) {
// send_internal_notification('Subscription cancelled at period end for team: ' . $subscription->team->id);
} else {
// send_internal_notification('customer.subscription.updated for customer: '.$customerId);
}
}
break;
case 'customer.subscription.deleted':
@@ -269,7 +278,7 @@ class Stripe extends Controller
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
$team = data_get($subscription, 'team');
if (! $team) {
throw new Exception('No team found for subscription: '.$subscription->id);
return response('No team found for subscription: '.$subscription->id, 400);
}
SubscriptionTrialEndsSoonJob::dispatch($team);
break;
@@ -278,7 +287,7 @@ class Stripe extends Controller
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
$team = data_get($subscription, 'team');
if (! $team) {
throw new Exception('No team found for subscription: '.$subscription->id);
return response('No team found for subscription: '.$subscription->id, 400);
}
$team->trialEnded();
$subscription->update([

View File

@@ -13,7 +13,6 @@ class Waitlist extends Controller
{
$email = request()->get('email');
$confirmation_code = request()->get('confirmation_code');
ray($email, $confirmation_code);
try {
$found = ModelsWaitlist::where('uuid', $confirmation_code)->where('email', $email)->first();
if ($found) {
@@ -36,7 +35,6 @@ class Waitlist extends Controller
return redirect()->route('dashboard');
} catch (Exception $e) {
send_internal_notification('Waitlist confirmation failed: '.$e->getMessage());
ray($e->getMessage());
return redirect()->route('dashboard');
}
@@ -58,7 +56,6 @@ class Waitlist extends Controller
return redirect()->route('dashboard');
} catch (Exception $e) {
send_internal_notification('Waitlist cancellation failed: '.$e->getMessage());
ray($e->getMessage());
return redirect()->route('dashboard');
}

View File

@@ -10,7 +10,6 @@ class ApiAllowed
{
public function handle(Request $request, Closure $next): Response
{
ray()->clearAll();
if (isCloud()) {
return $next($request);
}

View File

@@ -208,7 +208,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->container_name = "{$this->application->settings->custom_internal_name}-pr-{$this->pull_request_id}";
}
}
ray('New container name: ', $this->container_name)->green();
$this->saved_outputs = collect();
@@ -231,7 +230,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->application_deployment_queue->update([
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
]);
if (! $this->server->isFunctional()) {
if ($this->server->isFunctional() === false) {
$this->application_deployment_queue->addLogEntry('Server is not functional.');
$this->fail('Server is not functional.');
@@ -298,7 +297,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
if ($this->pull_request_id !== 0 && $this->application->is_github_based()) {
ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::ERROR);
}
ray($e);
$this->fail($e);
throw $e;
} finally {
@@ -389,7 +387,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
} else {
$this->dockerImageTag = $this->application->docker_registry_image_tag;
}
ray("echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag} to {$this->server->name}.'");
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->dockerImage}:{$this->dockerImageTag} to {$this->server->name}.");
$this->generate_image_names();
$this->prepare_builder_image();
@@ -712,38 +709,26 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
{
$forceFail = true;
if (str($this->application->docker_registry_image_name)->isEmpty()) {
ray('empty docker_registry_image_name');
return;
}
if ($this->restart_only) {
ray('restart_only');
return;
}
if ($this->application->build_pack === 'dockerimage') {
ray('dockerimage');
return;
}
if ($this->use_build_server) {
ray('use_build_server');
$forceFail = true;
}
if ($this->server->isSwarm() && $this->build_pack !== 'dockerimage') {
ray('isSwarm');
$forceFail = true;
}
if ($this->application->additional_servers->count() > 0) {
ray('additional_servers');
$forceFail = true;
}
if ($this->is_this_additional_server) {
ray('this is an additional_servers, no pushy pushy');
return;
}
ray('push_to_docker_registry noww: '.$this->production_image_name);
try {
instant_remote_process(["docker images --format '{{json .}}' {$this->production_image_name}"], $this->server);
$this->application_deployment_queue->addLogEntry('----------------------------------------');
@@ -775,7 +760,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
if ($forceFail) {
throw new RuntimeException($e->getMessage(), 69420);
}
ray($e);
}
}
@@ -1386,8 +1370,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
return;
}
if ($destination_ids->contains($this->destination->id)) {
ray('Same destination found in additional destinations. Skipping.');
return;
}
foreach ($destination_ids as $destination_id) {
@@ -1854,7 +1836,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}
if ($this->pull_request_id === 0) {
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
$custom_compose = convertDockerRunToCompose($this->application->custom_docker_run_options);
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
if (! $this->application->settings->custom_internal_name) {
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
@@ -2449,7 +2431,6 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
if ($this->application->build_pack !== 'dockercompose') {
$code = $exception->getCode();
ray($code);
if ($code !== 69420) {
// 69420 means failed to push the image to the registry, so we don't need to remove the new version as it is the currently running one
if ($this->application->settings->is_consistent_container_name_enabled || str($this->application->settings->custom_internal_name)->isNotEmpty()) {

View File

@@ -31,8 +31,6 @@ class ApplicationPullRequestUpdateJob implements ShouldBeEncrypted, ShouldQueue
{
try {
if ($this->application->is_public_repository()) {
ray('Public repository. Skipping comment update.');
return;
}
if ($this->status === ProcessStatus::CLOSED) {
@@ -53,16 +51,12 @@ class ApplicationPullRequestUpdateJob implements ShouldBeEncrypted, ShouldQueue
$this->body .= '[Open Build Logs]('.$this->build_logs_url.")\n\n\n";
$this->body .= 'Last updated at: '.now()->toDateTimeString().' CET';
ray('Updating comment', $this->body);
if ($this->preview->pull_request_issue_comment_id) {
$this->update_comment();
} else {
$this->create_comment();
}
} catch (\Throwable $e) {
ray($e);
return $e;
}
}
@@ -73,7 +67,6 @@ class ApplicationPullRequestUpdateJob implements ShouldBeEncrypted, ShouldQueue
'body' => $this->body,
], throwError: false);
if (data_get($data, 'message') === 'Not Found') {
ray('Comment not found. Creating new one.');
$this->create_comment();
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace App\Jobs;
use App\Actions\Server\StartSentinel;
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 CheckAndStartSentinelJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 120;
public function __construct(public Server $server) {}
public function handle(): void
{
$latestVersion = get_latest_sentinel_version();
// Check if sentinel is running
$sentinelFound = instant_remote_process(['docker inspect coolify-sentinel'], $this->server, false);
$sentinelFoundJson = json_decode($sentinelFound, true);
$sentinelStatus = data_get($sentinelFoundJson, '0.State.Status', 'exited');
if ($sentinelStatus !== 'running') {
StartSentinel::run(server: $this->server, restart: true, latestVersion: $latestVersion);
return;
}
// If sentinel is running, check if it needs an update
$runningVersion = instant_remote_process(['docker exec coolify-sentinel sh -c "curl http://127.0.0.1:8888/api/version"'], $this->server, false);
if (empty($runningVersion)) {
$runningVersion = '0.0.0';
}
if ($latestVersion === '0.0.0' && $runningVersion === '0.0.0') {
StartSentinel::run(server: $this->server, restart: true, latestVersion: 'latest');
return;
} else {
if (version_compare($runningVersion, $latestVersion, '<')) {
StartSentinel::run(server: $this->server, restart: true, latestVersion: $latestVersion);
return;
}
}
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
class CheckHelperImageJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 1000;
public function __construct() {}
public function handle(): void
{
try {
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
if ($response->successful()) {
$versions = $response->json();
$settings = instanceSettings();
$latest_version = data_get($versions, 'coolify.helper.version');
$current_version = $settings->helper_version;
if (version_compare($latest_version, $current_version, '>')) {
$settings->update(['helper_version' => $latest_version]);
}
}
} catch (\Throwable $e) {
send_internal_notification('CheckHelperImageJob failed with: '.$e->getMessage());
throw $e;
}
}
}

View File

@@ -22,7 +22,6 @@ class CheckResaleLicenseJob implements ShouldBeEncrypted, ShouldQueue
CheckResaleLicense::run();
} catch (\Throwable $e) {
send_internal_notification('CheckResaleLicenseJob failed with: '.$e->getMessage());
ray($e);
throw $e;
}
}

View File

@@ -20,18 +20,15 @@ class CleanupHelperContainersJob implements ShouldBeEncrypted, ShouldBeUnique, S
public function handle(): void
{
try {
ray('Cleaning up helper containers on '.$this->server->name);
$containers = instant_remote_process(['docker container ps --format \'{{json .}}\' | jq -s \'map(select(.Image | contains("ghcr.io/coollabsio/coolify-helper")))\''], $this->server, false);
$containerIds = collect(json_decode($containers))->pluck('ID');
if ($containerIds->count() > 0) {
foreach ($containerIds as $containerId) {
ray('Removing container '.$containerId);
instant_remote_process(['docker container rm -f '.$containerId], $this->server, false);
}
}
} catch (\Throwable $e) {
send_internal_notification('CleanupHelperContainersJob failed with error: '.$e->getMessage());
ray($e->getMessage());
}
}
}

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