Merge branch 'next' into main
This commit is contained in:
		
							
								
								
									
										15
									
								
								.env.dusk.ci
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								.env.dusk.ci
									
									
									
									
									
										Normal 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 | ||||||
|  |  | ||||||
| @@ -4,6 +4,7 @@ APP_ID=coolify-windows-docker-desktop | |||||||
| APP_NAME=Coolify | APP_NAME=Coolify | ||||||
| APP_KEY=base64:ssTlCmrIE/q7whnKMvT6DwURikg69COzGsAwFVROm80= | APP_KEY=base64:ssTlCmrIE/q7whnKMvT6DwURikg69COzGsAwFVROm80= | ||||||
|  |  | ||||||
|  | DB_USERNAME=coolify | ||||||
| DB_PASSWORD=coolify | DB_PASSWORD=coolify | ||||||
| REDIS_PASSWORD=coolify | REDIS_PASSWORD=coolify | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										65
									
								
								.github/workflows/browser-tests.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								.github/workflows/browser-tests.yml
									
									
									
									
										vendored
									
									
										Normal 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 | ||||||
							
								
								
									
										89
									
								
								.github/workflows/coolify-helper-next.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										89
									
								
								.github/workflows/coolify-helper-next.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,4 +1,4 @@ | |||||||
| name: Coolify Helper Image Development (v4) | name: Coolify Helper Image Development | ||||||
|  |  | ||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
| @@ -8,7 +8,8 @@ on: | |||||||
|       - docker/coolify-helper/Dockerfile |       - docker/coolify-helper/Dockerfile | ||||||
|  |  | ||||||
| env: | env: | ||||||
|   REGISTRY: ghcr.io |   GITHUB_REGISTRY: ghcr.io | ||||||
|  |   DOCKER_REGISTRY: docker.io | ||||||
|   IMAGE_NAME: "coollabsio/coolify-helper" |   IMAGE_NAME: "coollabsio/coolify-helper" | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
| @@ -19,25 +20,36 @@ jobs: | |||||||
|       packages: write |       packages: write | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - name: Login to ghcr.io |  | ||||||
|  |       - name: Login to ${{ env.GITHUB_REGISTRY }} | ||||||
|         uses: docker/login-action@v3 |         uses: docker/login-action@v3 | ||||||
|         with: |         with: | ||||||
|           registry: ${{ env.REGISTRY }} |           registry: ${{ env.GITHUB_REGISTRY }} | ||||||
|           username: ${{ github.actor }} |           username: ${{ github.actor }} | ||||||
|           password: ${{ secrets.GITHUB_TOKEN  }} |           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 |       - name: Get Version | ||||||
|         id: version |         id: version | ||||||
|         run: | |         run: | | ||||||
|           echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT |           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: |         with: | ||||||
|           no-cache: true |  | ||||||
|           context: . |           context: . | ||||||
|           file: docker/coolify-helper/Dockerfile |           file: docker/coolify-helper/Dockerfile | ||||||
|           platforms: linux/amd64 |           platforms: linux/amd64 | ||||||
|           push: true |           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: | |           labels: | | ||||||
|             coolify.managed=true |             coolify.managed=true | ||||||
|   aarch64: |   aarch64: | ||||||
| @@ -47,27 +59,39 @@ jobs: | |||||||
|       packages: write |       packages: write | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - name: Login to ghcr.io |  | ||||||
|  |       - name: Login to ${{ env.GITHUB_REGISTRY }} | ||||||
|         uses: docker/login-action@v3 |         uses: docker/login-action@v3 | ||||||
|         with: |         with: | ||||||
|           registry: ${{ env.REGISTRY }} |           registry: ${{ env.GITHUB_REGISTRY }} | ||||||
|           username: ${{ github.actor }} |           username: ${{ github.actor }} | ||||||
|           password: ${{ secrets.GITHUB_TOKEN  }} |           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 |       - name: Get Version | ||||||
|         id: version |         id: version | ||||||
|         run: | |         run: | | ||||||
|           echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT |           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: |         with: | ||||||
|           no-cache: true |  | ||||||
|           context: . |           context: . | ||||||
|           file: docker/coolify-helper/Dockerfile |           file: docker/coolify-helper/Dockerfile | ||||||
|           platforms: linux/aarch64 |           platforms: linux/aarch64 | ||||||
|           push: true |           push: true | ||||||
|           tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ 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: | |           labels: | | ||||||
|             coolify.managed=true |             coolify.managed=true | ||||||
|  |  | ||||||
|   merge-manifest: |   merge-manifest: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     permissions: |     permissions: | ||||||
| @@ -75,25 +99,42 @@ jobs: | |||||||
|       packages: write |       packages: write | ||||||
|     needs: [ amd64, aarch64 ] |     needs: [ amd64, aarch64 ] | ||||||
|     steps: |     steps: | ||||||
|       - name: Checkout |       - uses: actions/checkout@v4 | ||||||
|         uses: actions/checkout@v4 |       - uses: docker/setup-buildx-action@v3 | ||||||
|       - name: Set up QEMU |  | ||||||
|         uses: docker/setup-qemu-action@v3 |       - name: Login to ${{ env.GITHUB_REGISTRY }} | ||||||
|       - name: Set up Docker Buildx |  | ||||||
|         uses: docker/setup-buildx-action@v3 |  | ||||||
|       - name: Login to ghcr.io |  | ||||||
|         uses: docker/login-action@v3 |         uses: docker/login-action@v3 | ||||||
|         with: |         with: | ||||||
|           registry: ${{ env.REGISTRY }} |           registry: ${{ env.GITHUB_REGISTRY }} | ||||||
|           username: ${{ github.actor }} |           username: ${{ github.actor }} | ||||||
|           password: ${{ secrets.GITHUB_TOKEN  }} |           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 |       - name: Get Version | ||||||
|         id: version |         id: version | ||||||
|         run: | |         run: | | ||||||
|            echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT |            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: | |         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 |       - uses: sarisia/actions-status-discord@v1 | ||||||
|         if: always() |         if: always() | ||||||
|         with: |         with: | ||||||
|   | |||||||
							
								
								
									
										89
									
								
								.github/workflows/coolify-helper.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										89
									
								
								.github/workflows/coolify-helper.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,4 +1,4 @@ | |||||||
| name: Coolify Helper Image (v4) | name: Coolify Helper Image | ||||||
|  |  | ||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
| @@ -8,7 +8,8 @@ on: | |||||||
|       - docker/coolify-helper/Dockerfile |       - docker/coolify-helper/Dockerfile | ||||||
|  |  | ||||||
| env: | env: | ||||||
|   REGISTRY: ghcr.io |   GITHUB_REGISTRY: ghcr.io | ||||||
|  |   DOCKER_REGISTRY: docker.io | ||||||
|   IMAGE_NAME: "coollabsio/coolify-helper" |   IMAGE_NAME: "coollabsio/coolify-helper" | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
| @@ -19,25 +20,36 @@ jobs: | |||||||
|       packages: write |       packages: write | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - name: Login to ghcr.io |  | ||||||
|  |       - name: Login to ${{ env.GITHUB_REGISTRY }} | ||||||
|         uses: docker/login-action@v3 |         uses: docker/login-action@v3 | ||||||
|         with: |         with: | ||||||
|           registry: ${{ env.REGISTRY }} |           registry: ${{ env.GITHUB_REGISTRY }} | ||||||
|           username: ${{ github.actor }} |           username: ${{ github.actor }} | ||||||
|           password: ${{ secrets.GITHUB_TOKEN  }} |           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 |       - name: Get Version | ||||||
|         id: version |         id: version | ||||||
|         run: | |         run: | | ||||||
|           echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT |           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: |         with: | ||||||
|           no-cache: true |  | ||||||
|           context: . |           context: . | ||||||
|           file: docker/coolify-helper/Dockerfile |           file: docker/coolify-helper/Dockerfile | ||||||
|           platforms: linux/amd64 |           platforms: linux/amd64 | ||||||
|           push: true |           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: | |           labels: | | ||||||
|             coolify.managed=true |             coolify.managed=true | ||||||
|   aarch64: |   aarch64: | ||||||
| @@ -47,25 +59,36 @@ jobs: | |||||||
|       packages: write |       packages: write | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - name: Login to ghcr.io |  | ||||||
|  |       - name: Login to ${{ env.GITHUB_REGISTRY }} | ||||||
|         uses: docker/login-action@v3 |         uses: docker/login-action@v3 | ||||||
|         with: |         with: | ||||||
|           registry: ${{ env.REGISTRY }} |           registry: ${{ env.GITHUB_REGISTRY }} | ||||||
|           username: ${{ github.actor }} |           username: ${{ github.actor }} | ||||||
|           password: ${{ secrets.GITHUB_TOKEN  }} |           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 |       - name: Get Version | ||||||
|         id: version |         id: version | ||||||
|         run: | |         run: | | ||||||
|           echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT |           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: |         with: | ||||||
|           no-cache: true |  | ||||||
|           context: . |           context: . | ||||||
|           file: docker/coolify-helper/Dockerfile |           file: docker/coolify-helper/Dockerfile | ||||||
|           platforms: linux/aarch64 |           platforms: linux/aarch64 | ||||||
|           push: true |           push: true | ||||||
|           tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ 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: | |           labels: | | ||||||
|             coolify.managed=true |             coolify.managed=true | ||||||
|   merge-manifest: |   merge-manifest: | ||||||
| @@ -75,25 +98,43 @@ jobs: | |||||||
|       packages: write |       packages: write | ||||||
|     needs: [ amd64, aarch64 ] |     needs: [ amd64, aarch64 ] | ||||||
|     steps: |     steps: | ||||||
|       - name: Checkout |       - uses: actions/checkout@v4 | ||||||
|         uses: actions/checkout@v4 |  | ||||||
|       - name: Set up QEMU |       - uses: docker/setup-buildx-action@v3 | ||||||
|         uses: docker/setup-qemu-action@v3 |  | ||||||
|       - name: Set up Docker Buildx |       - name: Login to ${{ env.GITHUB_REGISTRY }} | ||||||
|         uses: docker/setup-buildx-action@v3 |  | ||||||
|       - name: Login to ghcr.io |  | ||||||
|         uses: docker/login-action@v3 |         uses: docker/login-action@v3 | ||||||
|         with: |         with: | ||||||
|           registry: ${{ env.REGISTRY }} |           registry: ${{ env.GITHUB_REGISTRY }} | ||||||
|           username: ${{ github.actor }} |           username: ${{ github.actor }} | ||||||
|           password: ${{ secrets.GITHUB_TOKEN  }} |           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 |       - name: Get Version | ||||||
|         id: version |         id: version | ||||||
|         run: | |         run: | | ||||||
|            echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT |            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: | |         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 |       - uses: sarisia/actions-status-discord@v1 | ||||||
|         if: always() |         if: always() | ||||||
|         with: |         with: | ||||||
|   | |||||||
							
								
								
									
										143
									
								
								.github/workflows/coolify-production-build.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								.github/workflows/coolify-production-build.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,143 @@ | |||||||
|  | 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/service-templates.json | ||||||
|  |  | ||||||
|  | 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 }} | ||||||
|  |           labels: | | ||||||
|  |             coolify.managed=true | ||||||
|  |  | ||||||
|  |   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 | ||||||
|  |           labels: | | ||||||
|  |             coolify.managed=true | ||||||
|  |  | ||||||
|  |   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  }} | ||||||
							
								
								
									
										95
									
								
								.github/workflows/coolify-realtime-next.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										95
									
								
								.github/workflows/coolify-realtime-next.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,17 +1,18 @@ | |||||||
| name: Coolify Realtime Development (v4) | name: Coolify Realtime Development | ||||||
|  |  | ||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
|     branches: [ "next" ] |     branches: [ "next" ] | ||||||
|     paths: |     paths: | ||||||
|       - .github/workflows/coolify-realtime.yml |       - .github/workflows/coolify-realtime-next.yml | ||||||
|       - docker/coolify-realtime/Dockerfile |       - docker/coolify-realtime/Dockerfile | ||||||
|       - docker/coolify-realtime/terminal-server.js |       - docker/coolify-realtime/terminal-server.js | ||||||
|       - docker/coolify-realtime/package.json |       - docker/coolify-realtime/package.json | ||||||
|       - docker/coolify-realtime/soketi-entrypoint.sh |       - docker/coolify-realtime/soketi-entrypoint.sh | ||||||
|  |  | ||||||
| env: | env: | ||||||
|   REGISTRY: ghcr.io |   GITHUB_REGISTRY: ghcr.io | ||||||
|  |   DOCKER_REGISTRY: docker.io | ||||||
|   IMAGE_NAME: "coollabsio/coolify-realtime" |   IMAGE_NAME: "coollabsio/coolify-realtime" | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
| @@ -22,27 +23,39 @@ jobs: | |||||||
|       packages: write |       packages: write | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - name: Login to ghcr.io |  | ||||||
|  |       - name: Login to ${{ env.GITHUB_REGISTRY }} | ||||||
|         uses: docker/login-action@v3 |         uses: docker/login-action@v3 | ||||||
|         with: |         with: | ||||||
|           registry: ${{ env.REGISTRY }} |           registry: ${{ env.GITHUB_REGISTRY }} | ||||||
|           username: ${{ github.actor }} |           username: ${{ github.actor }} | ||||||
|           password: ${{ secrets.GITHUB_TOKEN  }} |           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 |       - name: Get Version | ||||||
|         id: version |         id: version | ||||||
|         run: | |         run: | | ||||||
|           echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT |           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: |         with: | ||||||
|           no-cache: true |  | ||||||
|           context: . |           context: . | ||||||
|           file: docker/coolify-realtime/Dockerfile |           file: docker/coolify-realtime/Dockerfile | ||||||
|           platforms: linux/amd64 |           platforms: linux/amd64 | ||||||
|           push: true |           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: | |           labels: | | ||||||
|             coolify.managed=true |             coolify.managed=true | ||||||
|  |  | ||||||
|   aarch64: |   aarch64: | ||||||
|     runs-on: [ self-hosted, arm64 ] |     runs-on: [ self-hosted, arm64 ] | ||||||
|     permissions: |     permissions: | ||||||
| @@ -50,27 +63,39 @@ jobs: | |||||||
|       packages: write |       packages: write | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - name: Login to ghcr.io |  | ||||||
|  |       - name: Login to ${{ env.GITHUB_REGISTRY }} | ||||||
|         uses: docker/login-action@v3 |         uses: docker/login-action@v3 | ||||||
|         with: |         with: | ||||||
|           registry: ${{ env.REGISTRY }} |           registry: ${{ env.GITHUB_REGISTRY }} | ||||||
|           username: ${{ github.actor }} |           username: ${{ github.actor }} | ||||||
|           password: ${{ secrets.GITHUB_TOKEN  }} |           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 |       - name: Get Version | ||||||
|         id: version |         id: version | ||||||
|         run: | |         run: | | ||||||
|            echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT |            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: |         with: | ||||||
|           no-cache: true |  | ||||||
|           context: . |           context: . | ||||||
|           file: docker/coolify-realtime/Dockerfile |           file: docker/coolify-realtime/Dockerfile | ||||||
|           platforms: linux/aarch64 |           platforms: linux/aarch64 | ||||||
|           push: true |           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: | |           labels: | | ||||||
|             coolify.managed=true |             coolify.managed=true | ||||||
|  |  | ||||||
|   merge-manifest: |   merge-manifest: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     permissions: |     permissions: | ||||||
| @@ -78,26 +103,44 @@ jobs: | |||||||
|       packages: write |       packages: write | ||||||
|     needs: [ amd64, aarch64 ] |     needs: [ amd64, aarch64 ] | ||||||
|     steps: |     steps: | ||||||
|       - name: Checkout |       - uses: actions/checkout@v4 | ||||||
|         uses: actions/checkout@v4 |  | ||||||
|       - name: Set up QEMU |       - uses: docker/setup-buildx-action@v3 | ||||||
|         uses: docker/setup-qemu-action@v3 |  | ||||||
|       - name: Set up Docker Buildx |       - name: Login to ${{ env.GITHUB_REGISTRY }} | ||||||
|         uses: docker/setup-buildx-action@v3 |  | ||||||
|       - name: Login to ghcr.io |  | ||||||
|         uses: docker/login-action@v3 |         uses: docker/login-action@v3 | ||||||
|         with: |         with: | ||||||
|           registry: ${{ env.REGISTRY }} |           registry: ${{ env.GITHUB_REGISTRY }} | ||||||
|           username: ${{ github.actor }} |           username: ${{ github.actor }} | ||||||
|           password: ${{ secrets.GITHUB_TOKEN  }} |           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 |       - name: Get Version | ||||||
|         id: version |         id: version | ||||||
|         run: | |         run: | | ||||||
|            echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT |            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: | |         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 |       - uses: sarisia/actions-status-discord@v1 | ||||||
|         if: always() |         if: always() | ||||||
|         with: |         with: | ||||||
|           webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL  }} |           webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL  }} | ||||||
|   | |||||||
							
								
								
									
										91
									
								
								.github/workflows/coolify-realtime.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										91
									
								
								.github/workflows/coolify-realtime.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,4 +1,4 @@ | |||||||
| name: Coolify Realtime (v4) | name: Coolify Realtime | ||||||
|  |  | ||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
| @@ -11,7 +11,8 @@ on: | |||||||
|       - docker/coolify-realtime/soketi-entrypoint.sh |       - docker/coolify-realtime/soketi-entrypoint.sh | ||||||
|  |  | ||||||
| env: | env: | ||||||
|   REGISTRY: ghcr.io |   GITHUB_REGISTRY: ghcr.io | ||||||
|  |   DOCKER_REGISTRY: docker.io | ||||||
|   IMAGE_NAME: "coollabsio/coolify-realtime" |   IMAGE_NAME: "coollabsio/coolify-realtime" | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
| @@ -22,27 +23,39 @@ jobs: | |||||||
|       packages: write |       packages: write | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - name: Login to ghcr.io |  | ||||||
|  |       - name: Login to ${{ env.GITHUB_REGISTRY }} | ||||||
|         uses: docker/login-action@v3 |         uses: docker/login-action@v3 | ||||||
|         with: |         with: | ||||||
|           registry: ${{ env.REGISTRY }} |           registry: ${{ env.GITHUB_REGISTRY }} | ||||||
|           username: ${{ github.actor }} |           username: ${{ github.actor }} | ||||||
|           password: ${{ secrets.GITHUB_TOKEN  }} |           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 |       - name: Get Version | ||||||
|         id: version |         id: version | ||||||
|         run: | |         run: | | ||||||
|           echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT |           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: |         with: | ||||||
|           no-cache: true |  | ||||||
|           context: . |           context: . | ||||||
|           file: docker/coolify-realtime/Dockerfile |           file: docker/coolify-realtime/Dockerfile | ||||||
|           platforms: linux/amd64 |           platforms: linux/amd64 | ||||||
|           push: true |           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: | |           labels: | | ||||||
|             coolify.managed=true |             coolify.managed=true | ||||||
|  |  | ||||||
|   aarch64: |   aarch64: | ||||||
|     runs-on: [ self-hosted, arm64 ] |     runs-on: [ self-hosted, arm64 ] | ||||||
|     permissions: |     permissions: | ||||||
| @@ -50,27 +63,39 @@ jobs: | |||||||
|       packages: write |       packages: write | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - name: Login to ghcr.io |  | ||||||
|  |       - name: Login to ${{ env.GITHUB_REGISTRY }} | ||||||
|         uses: docker/login-action@v3 |         uses: docker/login-action@v3 | ||||||
|         with: |         with: | ||||||
|           registry: ${{ env.REGISTRY }} |           registry: ${{ env.GITHUB_REGISTRY }} | ||||||
|           username: ${{ github.actor }} |           username: ${{ github.actor }} | ||||||
|           password: ${{ secrets.GITHUB_TOKEN  }} |           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 |       - name: Get Version | ||||||
|         id: version |         id: version | ||||||
|         run: | |         run: | | ||||||
|            echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT |            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: |         with: | ||||||
|           no-cache: true |  | ||||||
|           context: . |           context: . | ||||||
|           file: docker/coolify-realtime/Dockerfile |           file: docker/coolify-realtime/Dockerfile | ||||||
|           platforms: linux/aarch64 |           platforms: linux/aarch64 | ||||||
|           push: true |           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: | |           labels: | | ||||||
|             coolify.managed=true |             coolify.managed=true | ||||||
|  |  | ||||||
|   merge-manifest: |   merge-manifest: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     permissions: |     permissions: | ||||||
| @@ -78,25 +103,43 @@ jobs: | |||||||
|       packages: write |       packages: write | ||||||
|     needs: [ amd64, aarch64 ] |     needs: [ amd64, aarch64 ] | ||||||
|     steps: |     steps: | ||||||
|       - name: Checkout |       - uses: actions/checkout@v4 | ||||||
|         uses: actions/checkout@v4 |  | ||||||
|       - name: Set up QEMU |       - uses: docker/setup-buildx-action@v3 | ||||||
|         uses: docker/setup-qemu-action@v3 |  | ||||||
|       - name: Set up Docker Buildx |       - name: Login to ${{ env.GITHUB_REGISTRY }} | ||||||
|         uses: docker/setup-buildx-action@v3 |  | ||||||
|       - name: Login to ghcr.io |  | ||||||
|         uses: docker/login-action@v3 |         uses: docker/login-action@v3 | ||||||
|         with: |         with: | ||||||
|           registry: ${{ env.REGISTRY }} |           registry: ${{ env.GITHUB_REGISTRY }} | ||||||
|           username: ${{ github.actor }} |           username: ${{ github.actor }} | ||||||
|           password: ${{ secrets.GITHUB_TOKEN  }} |           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 |       - name: Get Version | ||||||
|         id: version |         id: version | ||||||
|         run: | |         run: | | ||||||
|            echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT |            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: | |         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 |       - uses: sarisia/actions-status-discord@v1 | ||||||
|         if: always() |         if: always() | ||||||
|         with: |         with: | ||||||
|   | |||||||
							
								
								
									
										129
									
								
								.github/workflows/coolify-staging-build.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								.github/workflows/coolify-staging-build.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,129 @@ | |||||||
|  | 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/service-templates.json | ||||||
|  |  | ||||||
|  | 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 }} | ||||||
|  |           labels: | | ||||||
|  |             coolify.managed=true | ||||||
|  |  | ||||||
|  |   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 | ||||||
|  |           labels: | | ||||||
|  |             coolify.managed=true | ||||||
|  |  | ||||||
|  |   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  }} | ||||||
							
								
								
									
										92
									
								
								.github/workflows/coolify-testing-host.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										92
									
								
								.github/workflows/coolify-testing-host.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,14 +1,15 @@ | |||||||
| name: Coolify Testing Host (v4-non-prod) | name: Coolify Testing Host | ||||||
|  |  | ||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
|     branches: [ "main", "next" ] |     branches: [ "next" ] | ||||||
|     paths: |     paths: | ||||||
|       - .github/workflows/coolify-testing-host.yml |       - .github/workflows/coolify-testing-host.yml | ||||||
|       - docker/testing-host/Dockerfile |       - docker/testing-host/Dockerfile | ||||||
|  |  | ||||||
| env: | env: | ||||||
|   REGISTRY: ghcr.io |   GITHUB_REGISTRY: ghcr.io | ||||||
|  |   DOCKER_REGISTRY: docker.io | ||||||
|   IMAGE_NAME: "coollabsio/coolify-testing-host" |   IMAGE_NAME: "coollabsio/coolify-testing-host" | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
| @@ -19,21 +20,34 @@ jobs: | |||||||
|       packages: write |       packages: write | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - name: Login to ghcr.io |  | ||||||
|  |       - name: Login to ${{ env.GITHUB_REGISTRY }} | ||||||
|         uses: docker/login-action@v3 |         uses: docker/login-action@v3 | ||||||
|         with: |         with: | ||||||
|           registry: ${{ env.REGISTRY }} |           registry: ${{ env.GITHUB_REGISTRY }} | ||||||
|           username: ${{ github.actor }} |           username: ${{ github.actor }} | ||||||
|           password: ${{ secrets.GITHUB_TOKEN  }} |           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: |         with: | ||||||
|           no-cache: true |  | ||||||
|           context: . |           context: . | ||||||
|           file: docker/testing-host/Dockerfile |           file: docker/testing-host/Dockerfile | ||||||
|           platforms: linux/amd64 |           platforms: linux/amd64 | ||||||
|           push: true |           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: |   aarch64: | ||||||
|     runs-on: [ self-hosted, arm64 ] |     runs-on: [ self-hosted, arm64 ] | ||||||
|     permissions: |     permissions: | ||||||
| @@ -41,21 +55,34 @@ jobs: | |||||||
|       packages: write |       packages: write | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - name: Login to ghcr.io |  | ||||||
|  |       - name: Login to ${{ env.GITHUB_REGISTRY }} | ||||||
|         uses: docker/login-action@v3 |         uses: docker/login-action@v3 | ||||||
|         with: |         with: | ||||||
|           registry: ${{ env.REGISTRY }} |           registry: ${{ env.GITHUB_REGISTRY }} | ||||||
|           username: ${{ github.actor }} |           username: ${{ github.actor }} | ||||||
|           password: ${{ secrets.GITHUB_TOKEN  }} |           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: |         with: | ||||||
|           no-cache: true |  | ||||||
|           context: . |           context: . | ||||||
|           file: docker/testing-host/Dockerfile |           file: docker/testing-host/Dockerfile | ||||||
|           platforms: linux/aarch64 |           platforms: linux/aarch64 | ||||||
|           push: true |           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: |   merge-manifest: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     permissions: |     permissions: | ||||||
| @@ -63,21 +90,36 @@ jobs: | |||||||
|       packages: write |       packages: write | ||||||
|     needs: [ amd64, aarch64 ] |     needs: [ amd64, aarch64 ] | ||||||
|     steps: |     steps: | ||||||
|       - name: Checkout |       - uses: actions/checkout@v4 | ||||||
|         uses: actions/checkout@v4 |  | ||||||
|       - name: Set up QEMU |       - uses: docker/setup-buildx-action@v3 | ||||||
|         uses: docker/setup-qemu-action@v3 |  | ||||||
|       - name: Set up Docker Buildx |       - name: Login to ${{ env.GITHUB_REGISTRY }} | ||||||
|         uses: docker/setup-buildx-action@v3 |  | ||||||
|       - name: Login to ghcr.io |  | ||||||
|         uses: docker/login-action@v3 |         uses: docker/login-action@v3 | ||||||
|         with: |         with: | ||||||
|           registry: ${{ env.REGISTRY }} |           registry: ${{ env.GITHUB_REGISTRY }} | ||||||
|           username: ${{ github.actor }} |           username: ${{ github.actor }} | ||||||
|           password: ${{ secrets.GITHUB_TOKEN  }} |           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: | |         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 |       - uses: sarisia/actions-status-discord@v1 | ||||||
|         if: always() |         if: always() | ||||||
|         with: |         with: | ||||||
|   | |||||||
							
								
								
									
										79
									
								
								.github/workflows/development-build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										79
									
								
								.github/workflows/development-build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -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  }} |  | ||||||
							
								
								
									
										89
									
								
								.github/workflows/production-build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										89
									
								
								.github/workflows/production-build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -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  }} |  | ||||||
| @@ -12,6 +12,7 @@ class GenerateConfig | |||||||
|     public function handle(Application $application, bool $is_json = false) |     public function handle(Application $application, bool $is_json = false) | ||||||
|     { |     { | ||||||
|         ray()->clearAll(); |         ray()->clearAll(); | ||||||
|  | 
 | ||||||
|         return $application->generateConfig(is_json: $is_json); |         return $application->generateConfig(is_json: $is_json); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -21,8 +21,6 @@ class StartRedis | |||||||
|     { |     { | ||||||
|         $this->database = $database; |         $this->database = $database; | ||||||
| 
 | 
 | ||||||
|         $startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes"; |  | ||||||
| 
 |  | ||||||
|         $container_name = $this->database->uuid; |         $container_name = $this->database->uuid; | ||||||
|         $this->configuration_dir = database_configuration_dir().'/'.$container_name; |         $this->configuration_dir = database_configuration_dir().'/'.$container_name; | ||||||
| 
 | 
 | ||||||
| @@ -37,6 +35,8 @@ class StartRedis | |||||||
|         $environment_variables = $this->generate_environment_variables(); |         $environment_variables = $this->generate_environment_variables(); | ||||||
|         $this->add_custom_redis(); |         $this->add_custom_redis(); | ||||||
| 
 | 
 | ||||||
|  |         $startCommand = $this->buildStartCommand(); | ||||||
|  | 
 | ||||||
|         $docker_compose = [ |         $docker_compose = [ | ||||||
|             'services' => [ |             'services' => [ | ||||||
|                 $container_name => [ |                 $container_name => [ | ||||||
| @@ -105,7 +105,6 @@ class StartRedis | |||||||
|                 'target' => '/usr/local/etc/redis/redis.conf', |                 'target' => '/usr/local/etc/redis/redis.conf', | ||||||
|                 'read_only' => true, |                 '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
 |         // Add custom docker run options
 | ||||||
| @@ -160,12 +159,26 @@ class StartRedis | |||||||
|     private function generate_environment_variables() |     private function generate_environment_variables() | ||||||
|     { |     { | ||||||
|         $environment_variables = collect(); |         $environment_variables = collect(); | ||||||
|  | 
 | ||||||
|         foreach ($this->database->runtime_environment_variables as $env) { |         foreach ($this->database->runtime_environment_variables as $env) { | ||||||
|  |             if ($env->is_shared) { | ||||||
|                 $environment_variables->push("$env->key=$env->real_value"); |                 $environment_variables->push("$env->key=$env->real_value"); | ||||||
|  | 
 | ||||||
|  |                 if ($env->key === 'REDIS_PASSWORD') { | ||||||
|  |                     $this->database->update(['redis_password' => $env->real_value]); | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|         if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) { |                 if ($env->key === 'REDIS_USERNAME') { | ||||||
|             $environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}"); |                     $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); |         add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables); | ||||||
| @@ -173,6 +186,27 @@ class StartRedis | |||||||
|         return $environment_variables->all(); |         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() |     private function add_custom_redis() | ||||||
|     { |     { | ||||||
|         if (is_null($this->database->redis_conf) || empty($this->database->redis_conf)) { |         if (is_null($this->database->redis_conf) || empty($this->database->redis_conf)) { | ||||||
|   | |||||||
| @@ -651,31 +651,5 @@ class GetContainersStatus | |||||||
|             // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
 |             // $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); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										17
									
								
								app/Actions/Server/DeleteServer.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								app/Actions/Server/DeleteServer.php
									
									
									
									
									
										Normal 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(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -17,7 +17,7 @@ class InstallDocker | |||||||
|             throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://coolify.io/docs/installation#manually">documentation</a>.'); |             throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://coolify.io/docs/installation#manually">documentation</a>.'); | ||||||
|         } |         } | ||||||
|         ray('Installing Docker on server: '.$server->name.' ('.$server->ip.')'.' with OS type: '.$supported_os_type); |         ray('Installing Docker on server: '.$server->name.' ('.$server->ip.')'.' with OS type: '.$supported_os_type); | ||||||
|         $dockerVersion = '24.0'; |         $dockerVersion = '26.0'; | ||||||
|         $config = base64_encode('{ |         $config = base64_encode('{ | ||||||
|             "log-driver": "json-file", |             "log-driver": "json-file", | ||||||
|             "log-opts": { |             "log-opts": { | ||||||
|   | |||||||
| @@ -9,18 +9,48 @@ class StartSentinel | |||||||
| { | { | ||||||
|     use AsAction; |     use AsAction; | ||||||
| 
 | 
 | ||||||
|     public function handle(Server $server, $version = 'latest', bool $restart = false) |     public function handle(Server $server, $version = 'next', bool $restart = false) | ||||||
|     { |     { | ||||||
|         if ($restart) { |         if ($restart) { | ||||||
|             StopSentinel::run($server); |             StopSentinel::run($server); | ||||||
|         } |         } | ||||||
|         $metrics_history = $server->settings->metrics_history_days; |         $metrics_history = data_get($server, 'settings.sentinel_metrics_history_days'); | ||||||
|         $refresh_rate = $server->settings->metrics_refresh_rate_seconds; |         $refresh_rate = data_get($server, 'settings.sentinel_metrics_refresh_rate_seconds'); | ||||||
|         $token = $server->settings->metrics_token; |         $push_interval = data_get($server, 'settings.sentinel_push_interval_seconds'); | ||||||
|  |         $token = data_get($server, 'settings.sentinel_token'); | ||||||
|  |         $endpoint = data_get($server, 'settings.sentinel_custom_url'); | ||||||
|  |         $mount_dir = '/data/coolify/sentinel'; | ||||||
|  |         $image = "ghcr.io/coollabsio/sentinel:$version"; | ||||||
|  |         if (! $endpoint) { | ||||||
|  |             throw new \Exception('You should set FQDN in Instance Settings.'); | ||||||
|  |         } | ||||||
|  |         $environments = [ | ||||||
|  |             'TOKEN' => $token, | ||||||
|  |             'PUSH_ENDPOINT' => $endpoint, | ||||||
|  |             'PUSH_INTERVAL_SECONDS' => $push_interval, | ||||||
|  |             'COLLECTOR_ENABLED' => $server->isMetricsEnabled() ? 'true' : 'false', | ||||||
|  |             'COLLECTOR_REFRESH_RATE_SECONDS' => $refresh_rate, | ||||||
|  |             'COLLECTOR_RETENTION_PERIOD_DAYS' => $metrics_history, | ||||||
|  |         ]; | ||||||
|  |         if (isDev()) { | ||||||
|  |             // data_set($environments, 'DEBUG', 'true');
 | ||||||
|  |             $mount_dir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/sentinel'; | ||||||
|  |             // $image = 'sentinel';
 | ||||||
|  |         } | ||||||
|  |         $docker_environments = '-e "'.implode('" -e "', array_map(fn ($key, $value) => "$key=$value", array_keys($environments), $environments)).'"'; | ||||||
|  | 
 | ||||||
|  |         $docker_command = "docker run -d $docker_environments --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v $mount_dir:/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 $image"; | ||||||
|  | 
 | ||||||
|         instant_remote_process([ |         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", |             'docker rm -f coolify-sentinel || true', | ||||||
|             'chown -R 9999:root /data/coolify/metrics /data/coolify/logs', |             "mkdir -p $mount_dir", | ||||||
|             'chmod -R 700 /data/coolify/metrics /data/coolify/logs', |             $docker_command, | ||||||
|         ], $server, true); |             "chown -R 9999:root $mount_dir", | ||||||
|  |             "chmod -R 700 $mount_dir", | ||||||
|  |         ], $server); | ||||||
|  | 
 | ||||||
|  |         $server->settings->is_sentinel_enabled = true; | ||||||
|  |         $server->settings->save(); | ||||||
|  |         $server->sentinelHeartbeat(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -12,5 +12,6 @@ class StopSentinel | |||||||
|     public function handle(Server $server) |     public function handle(Server $server) | ||||||
|     { |     { | ||||||
|         instant_remote_process(['docker rm -f coolify-sentinel'], $server, false); |         instant_remote_process(['docker rm -f coolify-sentinel'], $server, false); | ||||||
|  |         $server->sentinelHeartbeat(isReset: true); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,16 +3,15 @@ | |||||||
| namespace App\Console; | namespace App\Console; | ||||||
| 
 | 
 | ||||||
| use App\Jobs\CheckForUpdatesJob; | use App\Jobs\CheckForUpdatesJob; | ||||||
|  | use App\Jobs\CheckHelperImageJob; | ||||||
| use App\Jobs\CleanupInstanceStuffsJob; | use App\Jobs\CleanupInstanceStuffsJob; | ||||||
| use App\Jobs\CleanupStaleMultiplexedConnections; | use App\Jobs\CleanupStaleMultiplexedConnections; | ||||||
| use App\Jobs\DatabaseBackupJob; | use App\Jobs\DatabaseBackupJob; | ||||||
| use App\Jobs\DockerCleanupJob; | use App\Jobs\DockerCleanupJob; | ||||||
| use App\Jobs\PullHelperImageJob; |  | ||||||
| use App\Jobs\PullSentinelImageJob; | use App\Jobs\PullSentinelImageJob; | ||||||
| use App\Jobs\PullTemplatesFromCDN; | use App\Jobs\PullTemplatesFromCDN; | ||||||
| use App\Jobs\ScheduledTaskJob; | use App\Jobs\ScheduledTaskJob; | ||||||
| use App\Jobs\ServerCheckJob; | use App\Jobs\ServerCheckJob; | ||||||
| use App\Jobs\ServerStorageCheckJob; |  | ||||||
| use App\Jobs\UpdateCoolifyJob; | use App\Jobs\UpdateCoolifyJob; | ||||||
| use App\Models\ScheduledDatabaseBackup; | use App\Models\ScheduledDatabaseBackup; | ||||||
| use App\Models\ScheduledTask; | use App\Models\ScheduledTask; | ||||||
| @@ -20,6 +19,7 @@ use App\Models\Server; | |||||||
| use App\Models\Team; | use App\Models\Team; | ||||||
| use Illuminate\Console\Scheduling\Schedule; | use Illuminate\Console\Scheduling\Schedule; | ||||||
| use Illuminate\Foundation\Console\Kernel as ConsoleKernel; | use Illuminate\Foundation\Console\Kernel as ConsoleKernel; | ||||||
|  | use Illuminate\Support\Carbon; | ||||||
| 
 | 
 | ||||||
| class Kernel extends ConsoleKernel | class Kernel extends ConsoleKernel | ||||||
| { | { | ||||||
| @@ -44,7 +44,7 @@ class Kernel extends ConsoleKernel | |||||||
| 
 | 
 | ||||||
|             $schedule->command('telescope:prune')->daily(); |             $schedule->command('telescope:prune')->daily(); | ||||||
| 
 | 
 | ||||||
|             $schedule->job(new PullHelperImageJob)->everyFiveMinutes()->onOneServer(); |             $schedule->job(new CheckHelperImageJob)->everyFiveMinutes()->onOneServer(); | ||||||
|         } else { |         } else { | ||||||
|             // Instance Jobs
 |             // Instance Jobs
 | ||||||
|             $schedule->command('horizon:snapshot')->everyFiveMinutes(); |             $schedule->command('horizon:snapshot')->everyFiveMinutes(); | ||||||
| @@ -80,7 +80,7 @@ class Kernel extends ConsoleKernel | |||||||
|                 })->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer(); |                 })->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         $schedule->job(new PullHelperImageJob) |         $schedule->job(new CheckHelperImageJob) | ||||||
|             ->cron($settings->update_check_frequency) |             ->cron($settings->update_check_frequency) | ||||||
|             ->timezone($settings->instance_timezone) |             ->timezone($settings->instance_timezone) | ||||||
|             ->onOneServer(); |             ->onOneServer(); | ||||||
| @@ -115,7 +115,10 @@ class Kernel extends ConsoleKernel | |||||||
|             $servers = $this->all_servers->where('ip', '!=', '1.2.3.4'); |             $servers = $this->all_servers->where('ip', '!=', '1.2.3.4'); | ||||||
|         } |         } | ||||||
|         foreach ($servers as $server) { |         foreach ($servers as $server) { | ||||||
|  |             $last_sentinel_update = $server->sentinel_updated_at; | ||||||
|  |             if (Carbon::parse($last_sentinel_update)->isBefore(now()->subMinutes(4))) { | ||||||
|                 $schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer(); |                 $schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer(); | ||||||
|  |             } | ||||||
|             // $schedule->job(new ServerStorageCheckJob($server))->everyMinute()->onOneServer();
 |             // $schedule->job(new ServerStorageCheckJob($server))->everyMinute()->onOneServer();
 | ||||||
|             $serverTimezone = $server->settings->server_timezone; |             $serverTimezone = $server->settings->server_timezone; | ||||||
|             if ($server->settings->force_docker_cleanup) { |             if ($server->settings->force_docker_cleanup) { | ||||||
|   | |||||||
| @@ -1579,11 +1579,16 @@ class ApplicationsController extends Controller | |||||||
|             $request->offsetUnset('docker_compose_domains'); |             $request->offsetUnset('docker_compose_domains'); | ||||||
|         } |         } | ||||||
|         $instantDeploy = $request->instant_deploy; |         $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)) { |         if (isset($isStatic)) { | ||||||
|             $application->settings->is_build_server_enabled = $use_build_server; |             $application->settings->is_static = $isStatic; | ||||||
|             $application->settings->save(); |             $application->settings->save(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -160,7 +160,7 @@ class OtherController extends Controller | |||||||
|     #[OA\Get(
 |     #[OA\Get(
 | ||||||
|         summary: 'Healthcheck', |         summary: 'Healthcheck', | ||||||
|         description: 'Healthcheck endpoint.', |         description: 'Healthcheck endpoint.', | ||||||
|         path: '/healthcheck', |         path: '/health', | ||||||
|         operationId: 'healthcheck', |         operationId: 'healthcheck', | ||||||
|         responses: [ |         responses: [ | ||||||
|             new OA\Response( |             new OA\Response( | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Http\Controllers\Api; | namespace App\Http\Controllers\Api; | ||||||
| 
 | 
 | ||||||
|  | use App\Actions\Server\DeleteServer; | ||||||
| use App\Actions\Server\ValidateServer; | use App\Actions\Server\ValidateServer; | ||||||
| use App\Enums\ProxyStatus; | use App\Enums\ProxyStatus; | ||||||
| use App\Enums\ProxyTypes; | use App\Enums\ProxyTypes; | ||||||
| @@ -23,7 +24,7 @@ class ServersController extends Controller | |||||||
|             return serializeApiResponse($settings); |             return serializeApiResponse($settings); | ||||||
|         } |         } | ||||||
|         $settings = $settings->makeHidden([ |         $settings = $settings->makeHidden([ | ||||||
|             'metrics_token', |             'sentinel_token', | ||||||
|         ]); |         ]); | ||||||
| 
 | 
 | ||||||
|         return serializeApiResponse($settings); |         return serializeApiResponse($settings); | ||||||
| @@ -726,6 +727,7 @@ class ServersController extends Controller | |||||||
|             return response()->json(['message' => 'Server has resources, so you need to delete them before.'], 400); |             return response()->json(['message' => 'Server has resources, so you need to delete them before.'], 400); | ||||||
|         } |         } | ||||||
|         $server->delete(); |         $server->delete(); | ||||||
|  |         DeleteServer::dispatch($server); | ||||||
| 
 | 
 | ||||||
|         return response()->json(['message' => 'Server deleted.']); |         return response()->json(['message' => 'Server deleted.']); | ||||||
|     } |     } | ||||||
|   | |||||||
							
								
								
									
										40
									
								
								app/Jobs/CheckHelperImageJob.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								app/Jobs/CheckHelperImageJob.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | <?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; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -504,8 +504,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue | |||||||
|                 $network = $this->database->destination->network; |                 $network = $this->database->destination->network; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             $this->ensureHelperImageAvailable(); |  | ||||||
| 
 |  | ||||||
|             $fullImageName = $this->getFullImageName(); |             $fullImageName = $this->getFullImageName(); | ||||||
| 
 | 
 | ||||||
|             if (isDev()) { |             if (isDev()) { | ||||||
| @@ -538,35 +536,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private function ensureHelperImageAvailable(): void |  | ||||||
|     { |  | ||||||
|         $fullImageName = $this->getFullImageName(); |  | ||||||
| 
 |  | ||||||
|         $imageExists = $this->checkImageExists($fullImageName); |  | ||||||
| 
 |  | ||||||
|         if (! $imageExists) { |  | ||||||
|             $this->pullHelperImage($fullImageName); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private function checkImageExists(string $fullImageName): bool |  | ||||||
|     { |  | ||||||
|         $result = instant_remote_process(["docker image inspect {$fullImageName} >/dev/null 2>&1 && echo 'exists' || echo 'not exists'"], $this->server, false); |  | ||||||
| 
 |  | ||||||
|         return trim($result) === 'exists'; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private function pullHelperImage(string $fullImageName): void |  | ||||||
|     { |  | ||||||
|         try { |  | ||||||
|             instant_remote_process(["docker pull {$fullImageName}"], $this->server); |  | ||||||
|         } catch (\Exception $e) { |  | ||||||
|             $errorMessage = 'Failed to pull helper image: '.$e->getMessage(); |  | ||||||
|             $this->add_to_backup_output($errorMessage); |  | ||||||
|             throw new \RuntimeException($errorMessage); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private function getFullImageName(): string |     private function getFullImageName(): string | ||||||
|     { |     { | ||||||
|         $settings = instanceSettings(); |         $settings = instanceSettings(); | ||||||
|   | |||||||
| @@ -9,7 +9,6 @@ use Illuminate\Contracts\Queue\ShouldQueue; | |||||||
| use Illuminate\Foundation\Bus\Dispatchable; | use Illuminate\Foundation\Bus\Dispatchable; | ||||||
| use Illuminate\Queue\InteractsWithQueue; | use Illuminate\Queue\InteractsWithQueue; | ||||||
| use Illuminate\Queue\SerializesModels; | use Illuminate\Queue\SerializesModels; | ||||||
| use Illuminate\Support\Facades\Http; |  | ||||||
| 
 | 
 | ||||||
| class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue | class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue | ||||||
| { | { | ||||||
| @@ -17,28 +16,15 @@ class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue | |||||||
| 
 | 
 | ||||||
|     public $timeout = 1000; |     public $timeout = 1000; | ||||||
| 
 | 
 | ||||||
|     public function __construct() {} |     public function __construct(public Server $server) {} | ||||||
| 
 | 
 | ||||||
|     public function handle(): void |     public function handle(): void | ||||||
|     { |     { | ||||||
|         try { |         try { | ||||||
|             $response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json'); |             $helperImage = config('coolify.helper_image'); | ||||||
|             if ($response->successful()) { |             $latest_version = instanceSettings()->helper_version; | ||||||
|                 $versions = $response->json(); |             instant_remote_process(["docker pull -q {$helperImage}:{$latest_version}"], $this->server, false); | ||||||
|                 $settings = instanceSettings(); |  | ||||||
|                 $latest_version = data_get($versions, 'coolify.helper.version'); |  | ||||||
|                 $current_version = $settings->helper_version; |  | ||||||
|                 if (version_compare($latest_version, $current_version, '>')) { |  | ||||||
|                     // New version available
 |  | ||||||
|                     // $helperImage = config('coolify.helper_image');
 |  | ||||||
|                     // instant_remote_process(["docker pull -q {$helperImage}:{$latest_version}"], $this->server);
 |  | ||||||
|                     $settings->update(['helper_version' => $latest_version]); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             send_internal_notification('PullHelperImageJob failed with: '.$e->getMessage()); |  | ||||||
|             ray($e->getMessage()); |  | ||||||
|             throw $e; |             throw $e; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
							
								
								
									
										407
									
								
								app/Jobs/PushServerUpdateJob.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										407
									
								
								app/Jobs/PushServerUpdateJob.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,407 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Jobs; | ||||||
|  | 
 | ||||||
|  | use App\Actions\Database\StartDatabaseProxy; | ||||||
|  | use App\Actions\Database\StopDatabaseProxy; | ||||||
|  | use App\Actions\Proxy\CheckProxy; | ||||||
|  | use App\Actions\Proxy\StartProxy; | ||||||
|  | use App\Actions\Server\InstallLogDrain; | ||||||
|  | use App\Actions\Shared\ComplexStatusCheck; | ||||||
|  | use App\Models\Application; | ||||||
|  | use App\Models\ApplicationPreview; | ||||||
|  | use App\Models\Server; | ||||||
|  | use App\Models\ServiceApplication; | ||||||
|  | use App\Models\ServiceDatabase; | ||||||
|  | use Illuminate\Bus\Queueable; | ||||||
|  | use Illuminate\Contracts\Queue\ShouldQueue; | ||||||
|  | use Illuminate\Foundation\Bus\Dispatchable; | ||||||
|  | use Illuminate\Queue\InteractsWithQueue; | ||||||
|  | use Illuminate\Queue\SerializesModels; | ||||||
|  | use Illuminate\Support\Collection; | ||||||
|  | 
 | ||||||
|  | class PushServerUpdateJob implements ShouldQueue | ||||||
|  | { | ||||||
|  |     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||||||
|  | 
 | ||||||
|  |     public $tries = 1; | ||||||
|  | 
 | ||||||
|  |     public $timeout = 30; | ||||||
|  | 
 | ||||||
|  |     public Collection $containers; | ||||||
|  | 
 | ||||||
|  |     public Collection $applications; | ||||||
|  | 
 | ||||||
|  |     public Collection $previews; | ||||||
|  | 
 | ||||||
|  |     public Collection $databases; | ||||||
|  | 
 | ||||||
|  |     public Collection $services; | ||||||
|  | 
 | ||||||
|  |     public Collection $allApplicationIds; | ||||||
|  | 
 | ||||||
|  |     public Collection $allDatabaseUuids; | ||||||
|  | 
 | ||||||
|  |     public Collection $allTcpProxyUuids; | ||||||
|  | 
 | ||||||
|  |     public Collection $allServiceApplicationIds; | ||||||
|  | 
 | ||||||
|  |     public Collection $allApplicationPreviewsIds; | ||||||
|  | 
 | ||||||
|  |     public Collection $allServiceDatabaseIds; | ||||||
|  | 
 | ||||||
|  |     public Collection $allApplicationsWithAdditionalServers; | ||||||
|  | 
 | ||||||
|  |     public Collection $foundApplicationIds; | ||||||
|  | 
 | ||||||
|  |     public Collection $foundDatabaseUuids; | ||||||
|  | 
 | ||||||
|  |     public Collection $foundServiceApplicationIds; | ||||||
|  | 
 | ||||||
|  |     public Collection $foundServiceDatabaseIds; | ||||||
|  | 
 | ||||||
|  |     public Collection $foundApplicationPreviewsIds; | ||||||
|  | 
 | ||||||
|  |     public bool $foundProxy = false; | ||||||
|  | 
 | ||||||
|  |     public bool $foundLogDrainContainer = false; | ||||||
|  | 
 | ||||||
|  |     public function backoff(): int | ||||||
|  |     { | ||||||
|  |         return isDev() ? 1 : 3; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function __construct(public Server $server, public $data) | ||||||
|  |     { | ||||||
|  |         $this->containers = collect(); | ||||||
|  |         $this->foundApplicationIds = collect(); | ||||||
|  |         $this->foundDatabaseUuids = collect(); | ||||||
|  |         $this->foundServiceApplicationIds = collect(); | ||||||
|  |         $this->foundApplicationPreviewsIds = collect(); | ||||||
|  |         $this->foundServiceDatabaseIds = collect(); | ||||||
|  |         $this->allApplicationIds = collect(); | ||||||
|  |         $this->allDatabaseUuids = collect(); | ||||||
|  |         $this->allTcpProxyUuids = collect(); | ||||||
|  |         $this->allServiceApplicationIds = collect(); | ||||||
|  |         $this->allServiceDatabaseIds = collect(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function handle() | ||||||
|  |     { | ||||||
|  |         try { | ||||||
|  |             if (! $this->data) { | ||||||
|  |                 throw new \Exception('No data provided'); | ||||||
|  |             } | ||||||
|  |             $data = collect($this->data); | ||||||
|  | 
 | ||||||
|  |             $this->serverStatus(); | ||||||
|  | 
 | ||||||
|  |             $this->server->sentinelHeartbeat(); | ||||||
|  | 
 | ||||||
|  |             $this->containers = collect(data_get($data, 'containers')); | ||||||
|  |             if ($this->containers->isEmpty()) { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             $this->applications = $this->server->applications(); | ||||||
|  |             $this->databases = $this->server->databases(); | ||||||
|  |             $this->previews = $this->server->previews(); | ||||||
|  |             $this->services = $this->server->services()->get(); | ||||||
|  |             $this->allApplicationIds = $this->applications->filter(function ($application) { | ||||||
|  |                 return $application->additional_servers->count() === 0; | ||||||
|  |             })->pluck('id'); | ||||||
|  |             $this->allApplicationsWithAdditionalServers = $this->applications->filter(function ($application) { | ||||||
|  |                 return $application->additional_servers->count() > 0; | ||||||
|  |             }); | ||||||
|  |             $this->allApplicationPreviewsIds = $this->previews->pluck('id'); | ||||||
|  |             $this->allDatabaseUuids = $this->databases->pluck('uuid'); | ||||||
|  |             $this->allTcpProxyUuids = $this->databases->where('is_public', true)->pluck('uuid'); | ||||||
|  |             $this->services->each(function ($service) { | ||||||
|  |                 $service->applications()->pluck('id')->each(function ($applicationId) { | ||||||
|  |                     $this->allServiceApplicationIds->push($applicationId); | ||||||
|  |                 }); | ||||||
|  |                 $service->databases()->pluck('id')->each(function ($databaseId) { | ||||||
|  |                     $this->allServiceDatabaseIds->push($databaseId); | ||||||
|  |                 }); | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             ray('allServiceApplicationIds', ['allServiceApplicationIds' => $this->allServiceApplicationIds]); | ||||||
|  | 
 | ||||||
|  |             foreach ($this->containers as $container) { | ||||||
|  |                 $containerStatus = data_get($container, 'state', 'exited'); | ||||||
|  |                 $containerHealth = data_get($container, 'health_status', 'unhealthy'); | ||||||
|  |                 $containerStatus = "$containerStatus ($containerHealth)"; | ||||||
|  |                 $labels = collect(data_get($container, 'labels')); | ||||||
|  |                 $coolify_managed = $labels->has('coolify.managed'); | ||||||
|  |                 if ($coolify_managed) { | ||||||
|  |                     $name = data_get($container, 'name'); | ||||||
|  |                     if ($name === 'coolify-log-drain' && $this->isRunning($containerStatus)) { | ||||||
|  |                         $this->foundLogDrainContainer = true; | ||||||
|  |                     } | ||||||
|  |                     if ($labels->has('coolify.applicationId')) { | ||||||
|  |                         $applicationId = $labels->get('coolify.applicationId'); | ||||||
|  |                         $pullRequestId = data_get($labels, 'coolify.pullRequestId', '0'); | ||||||
|  |                         try { | ||||||
|  |                             if ($pullRequestId === '0') { | ||||||
|  |                                 if ($this->allApplicationIds->contains($applicationId) && $this->isRunning($containerStatus)) { | ||||||
|  |                                     $this->foundApplicationIds->push($applicationId); | ||||||
|  |                                 } | ||||||
|  |                                 $this->updateApplicationStatus($applicationId, $containerStatus); | ||||||
|  |                             } else { | ||||||
|  |                                 if ($this->allApplicationPreviewsIds->contains($applicationId) && $this->isRunning($containerStatus)) { | ||||||
|  |                                     $this->foundApplicationPreviewsIds->push($applicationId); | ||||||
|  |                                 } | ||||||
|  |                                 $this->updateApplicationPreviewStatus($applicationId, $containerStatus); | ||||||
|  |                             } | ||||||
|  |                         } catch (\Exception $e) { | ||||||
|  |                             ray()->error($e); | ||||||
|  |                         } | ||||||
|  |                     } elseif ($labels->has('coolify.serviceId')) { | ||||||
|  |                         $serviceId = $labels->get('coolify.serviceId'); | ||||||
|  |                         $subType = $labels->get('coolify.service.subType'); | ||||||
|  |                         $subId = $labels->get('coolify.service.subId'); | ||||||
|  |                         if ($subType === 'application' && $this->isRunning($containerStatus)) { | ||||||
|  |                             $this->foundServiceApplicationIds->push($subId); | ||||||
|  |                             $this->updateServiceSubStatus($serviceId, $subType, $subId, $containerStatus); | ||||||
|  |                         } elseif ($subType === 'database' && $this->isRunning($containerStatus)) { | ||||||
|  |                             $this->foundServiceDatabaseIds->push($subId); | ||||||
|  |                             $this->updateServiceSubStatus($serviceId, $subType, $subId, $containerStatus); | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                     } else { | ||||||
|  |                         $uuid = $labels->get('com.docker.compose.service'); | ||||||
|  |                         $type = $labels->get('coolify.type'); | ||||||
|  |                         if ($name === 'coolify-proxy' && $this->isRunning($containerStatus)) { | ||||||
|  |                             $this->foundProxy = true; | ||||||
|  |                         } elseif ($type === 'service' && $this->isRunning($containerStatus)) { | ||||||
|  |                             ray("Service: $uuid, $containerStatus"); | ||||||
|  |                         } else { | ||||||
|  |                             if ($this->allDatabaseUuids->contains($uuid) && $this->isRunning($containerStatus)) { | ||||||
|  |                                 $this->foundDatabaseUuids->push($uuid); | ||||||
|  |                                 if ($this->allTcpProxyUuids->contains($uuid) && $this->isRunning($containerStatus)) { | ||||||
|  |                                     $this->updateDatabaseStatus($uuid, $containerStatus, tcpProxy: true); | ||||||
|  |                                 } else { | ||||||
|  |                                     $this->updateDatabaseStatus($uuid, $containerStatus, tcpProxy: false); | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             $this->updateProxyStatus(); | ||||||
|  | 
 | ||||||
|  |             $this->updateNotFoundApplicationStatus(); | ||||||
|  |             $this->updateNotFoundApplicationPreviewStatus(); | ||||||
|  |             $this->updateNotFoundDatabaseStatus(); | ||||||
|  |             $this->updateNotFoundServiceStatus(); | ||||||
|  | 
 | ||||||
|  |             $this->updateAdditionalServersStatus(); | ||||||
|  | 
 | ||||||
|  |             $this->checkLogDrainContainer(); | ||||||
|  | 
 | ||||||
|  |         } catch (\Exception $e) { | ||||||
|  |             throw $e; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private function serverStatus() | ||||||
|  |     { | ||||||
|  |         if ($this->server->isFunctional() === false) { | ||||||
|  |             throw new \Exception('Server is not ready.'); | ||||||
|  |         } | ||||||
|  |         if ($this->server->status() === false) { | ||||||
|  |             throw new \Exception('Server is not reachable.'); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private function updateApplicationStatus(string $applicationId, string $containerStatus) | ||||||
|  |     { | ||||||
|  |         $application = $this->applications->where('id', $applicationId)->first(); | ||||||
|  |         if (! $application) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         $application->status = $containerStatus; | ||||||
|  |         $application->save(); | ||||||
|  |         ray('Application updated', ['application_id' => $applicationId, 'status' => $containerStatus]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private function updateApplicationPreviewStatus(string $applicationId, string $containerStatus) | ||||||
|  |     { | ||||||
|  |         $application = $this->previews->where('id', $applicationId)->first(); | ||||||
|  |         if (! $application) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         $application->status = $containerStatus; | ||||||
|  |         $application->save(); | ||||||
|  |         ray('Application preview updated', ['application_id' => $applicationId, 'status' => $containerStatus]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private function updateNotFoundApplicationStatus() | ||||||
|  |     { | ||||||
|  |         $notFoundApplicationIds = $this->allApplicationIds->diff($this->foundApplicationIds); | ||||||
|  |         if ($notFoundApplicationIds->isNotEmpty()) { | ||||||
|  |             ray('Not found application ids', ['application_ids' => $notFoundApplicationIds]); | ||||||
|  |             $notFoundApplicationIds->each(function ($applicationId) { | ||||||
|  |                 ray('Updating application status', ['application_id' => $applicationId, 'status' => 'exited']); | ||||||
|  |                 $application = Application::find($applicationId); | ||||||
|  |                 if ($application) { | ||||||
|  |                     $application->status = 'exited'; | ||||||
|  |                     $application->save(); | ||||||
|  |                     ray('Application status updated', ['application_id' => $applicationId, 'status' => 'exited']); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private function updateNotFoundApplicationPreviewStatus() | ||||||
|  |     { | ||||||
|  |         $notFoundApplicationPreviewsIds = $this->allApplicationPreviewsIds->diff($this->foundApplicationPreviewsIds); | ||||||
|  |         if ($notFoundApplicationPreviewsIds->isNotEmpty()) { | ||||||
|  |             ray('Not found application previews ids', ['application_previews_ids' => $notFoundApplicationPreviewsIds]); | ||||||
|  |             $notFoundApplicationPreviewsIds->each(function ($applicationPreviewId) { | ||||||
|  |                 ray('Updating application preview status', ['application_preview_id' => $applicationPreviewId, 'status' => 'exited']); | ||||||
|  |                 $applicationPreview = ApplicationPreview::find($applicationPreviewId); | ||||||
|  |                 if ($applicationPreview) { | ||||||
|  |                     $applicationPreview->status = 'exited'; | ||||||
|  |                     $applicationPreview->save(); | ||||||
|  |                     ray('Application preview status updated', ['application_preview_id' => $applicationPreviewId, 'status' => 'exited']); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private function updateProxyStatus() | ||||||
|  |     { | ||||||
|  |         // If proxy is not found, start it
 | ||||||
|  |         if ($this->server->isProxyShouldRun()) { | ||||||
|  |             if ($this->foundProxy === false) { | ||||||
|  |                 try { | ||||||
|  |                     if (CheckProxy::run($this->server)) { | ||||||
|  |                         StartProxy::run($this->server, false); | ||||||
|  |                     } | ||||||
|  |                 } catch (\Throwable $e) { | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 $connectProxyToDockerNetworks = connectProxyToNetworks($this->server); | ||||||
|  |                 instant_remote_process($connectProxyToDockerNetworks, $this->server, false); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private function updateDatabaseStatus(string $databaseUuid, string $containerStatus, bool $tcpProxy = false) | ||||||
|  |     { | ||||||
|  |         $database = $this->databases->where('uuid', $databaseUuid)->first(); | ||||||
|  |         if (! $database) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         $database->status = $containerStatus; | ||||||
|  |         $database->save(); | ||||||
|  |         ray('Database status updated', ['database_uuid' => $databaseUuid, 'status' => $containerStatus]); | ||||||
|  |         if ($this->isRunning($containerStatus) && $tcpProxy) { | ||||||
|  |             $tcpProxyContainerFound = $this->containers->filter(function ($value, $key) use ($databaseUuid) { | ||||||
|  |                 return data_get($value, 'name') === "$databaseUuid-proxy" && data_get($value, 'state') === 'running'; | ||||||
|  |             })->first(); | ||||||
|  |             if (! $tcpProxyContainerFound) { | ||||||
|  |                 ray('Starting TCP proxy for database', ['database_uuid' => $databaseUuid]); | ||||||
|  |                 StartDatabaseProxy::dispatch($database); | ||||||
|  |             } else { | ||||||
|  |                 ray('TCP proxy for database found in containers', ['database_uuid' => $databaseUuid]); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private function updateNotFoundDatabaseStatus() | ||||||
|  |     { | ||||||
|  |         $notFoundDatabaseUuids = $this->allDatabaseUuids->diff($this->foundDatabaseUuids); | ||||||
|  |         if ($notFoundDatabaseUuids->isNotEmpty()) { | ||||||
|  |             ray('Not found database uuids', ['database_uuids' => $notFoundDatabaseUuids]); | ||||||
|  |             $notFoundDatabaseUuids->each(function ($databaseUuid) { | ||||||
|  |                 ray('Updating database status', ['database_uuid' => $databaseUuid, 'status' => 'exited']); | ||||||
|  |                 $database = $this->databases->where('uuid', $databaseUuid)->first(); | ||||||
|  |                 if ($database) { | ||||||
|  |                     $database->status = 'exited'; | ||||||
|  |                     $database->save(); | ||||||
|  |                     ray('Database status updated', ['database_uuid' => $databaseUuid, 'status' => 'exited']); | ||||||
|  |                     ray('Database is public', ['database_uuid' => $databaseUuid, 'is_public' => $database->is_public]); | ||||||
|  |                     if ($database->is_public) { | ||||||
|  |                         ray('Stopping TCP proxy for database', ['database_uuid' => $databaseUuid]); | ||||||
|  |                         StopDatabaseProxy::dispatch($database); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private function updateServiceSubStatus(string $serviceId, string $subType, string $subId, string $containerStatus) | ||||||
|  |     { | ||||||
|  |         $service = $this->services->where('id', $serviceId)->first(); | ||||||
|  |         if (! $service) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         if ($subType === 'application') { | ||||||
|  |             $application = $service->applications()->where('id', $subId)->first(); | ||||||
|  |             $application->status = $containerStatus; | ||||||
|  |             $application->save(); | ||||||
|  |             ray('Service application updated', ['service_id' => $serviceId, 'sub_type' => $subType, 'sub_id' => $subId, 'status' => $containerStatus]); | ||||||
|  |         } elseif ($subType === 'database') { | ||||||
|  |             $database = $service->databases()->where('id', $subId)->first(); | ||||||
|  |             $database->status = $containerStatus; | ||||||
|  |             $database->save(); | ||||||
|  |             ray('Service database updated', ['service_id' => $serviceId, 'sub_type' => $subType, 'sub_id' => $subId, 'status' => $containerStatus]); | ||||||
|  |         } else { | ||||||
|  |             ray()->warning('Unknown sub type', ['service_id' => $serviceId, 'sub_type' => $subType, 'sub_id' => $subId, 'status' => $containerStatus]); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private function updateNotFoundServiceStatus() | ||||||
|  |     { | ||||||
|  |         $notFoundServiceApplicationIds = $this->allServiceApplicationIds->diff($this->foundServiceApplicationIds); | ||||||
|  |         $notFoundServiceDatabaseIds = $this->allServiceDatabaseIds->diff($this->foundServiceDatabaseIds); | ||||||
|  |         if ($notFoundServiceApplicationIds->isNotEmpty()) { | ||||||
|  |             ray('Not found service application ids', ['service_application_ids' => $notFoundServiceApplicationIds]); | ||||||
|  |             $notFoundServiceApplicationIds->each(function ($serviceApplicationId) { | ||||||
|  |                 ray('Updating service application status', ['service_application_id' => $serviceApplicationId, 'status' => 'exited']); | ||||||
|  |                 $application = ServiceApplication::find($serviceApplicationId); | ||||||
|  |                 if ($application) { | ||||||
|  |                     $application->status = 'exited'; | ||||||
|  |                     $application->save(); | ||||||
|  |                     ray('Service application status updated', ['service_application_id' => $serviceApplicationId, 'status' => 'exited']); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |         if ($notFoundServiceDatabaseIds->isNotEmpty()) { | ||||||
|  |             ray('Not found service database ids', ['service_database_ids' => $notFoundServiceDatabaseIds]); | ||||||
|  |             $notFoundServiceDatabaseIds->each(function ($serviceDatabaseId) { | ||||||
|  |                 ray('Updating service database status', ['service_database_id' => $serviceDatabaseId, 'status' => 'exited']); | ||||||
|  |                 $database = ServiceDatabase::find($serviceDatabaseId); | ||||||
|  |                 if ($database) { | ||||||
|  |                     $database->status = 'exited'; | ||||||
|  |                     $database->save(); | ||||||
|  |                     ray('Service database status updated', ['service_database_id' => $serviceDatabaseId, 'status' => 'exited']); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private function updateAdditionalServersStatus() | ||||||
|  |     { | ||||||
|  |         $this->allApplicationsWithAdditionalServers->each(function ($application) { | ||||||
|  |             ray('Updating additional servers status for application', ['application_id' => $application->id]); | ||||||
|  |             ComplexStatusCheck::run($application); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private function isRunning(string $containerStatus) | ||||||
|  |     { | ||||||
|  |         return str($containerStatus)->contains('running'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private function checkLogDrainContainer() | ||||||
|  |     { | ||||||
|  |         if ($this->server->isLogDrainEnabled() && $this->foundLogDrainContainer === false) { | ||||||
|  |             InstallLogDrain::dispatch($this->server); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -72,6 +72,32 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue | |||||||
|                 if ($this->server->isLogDrainEnabled()) { |                 if ($this->server->isLogDrainEnabled()) { | ||||||
|                     $this->checkLogDrainContainer(); |                     $this->checkLogDrainContainer(); | ||||||
|                 } |                 } | ||||||
|  |                 if ($this->server->proxySet() && ! $this->server->proxy->force_stop) { | ||||||
|  |                     $this->server->proxyType(); | ||||||
|  |                     $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); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
| @@ -387,31 +413,5 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue | |||||||
|             } |             } | ||||||
|             // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
 |             // $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()) { |  | ||||||
|                 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); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -66,7 +66,7 @@ class Show extends Component | |||||||
|             return ! $alreadyAddedNetworks->contains('network', $network['Name']); |             return ! $alreadyAddedNetworks->contains('network', $network['Name']); | ||||||
|         }); |         }); | ||||||
|         if ($this->networks->count() === 0) { |         if ($this->networks->count() === 0) { | ||||||
|             $this->dispatch('success', 'No new networks found.'); |             $this->dispatch('success', 'No new destinations found on this server.'); | ||||||
| 
 | 
 | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -241,7 +241,6 @@ class General extends Component | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     public function updatedApplicationBuildPack() |     public function updatedApplicationBuildPack() | ||||||
|     { |     { | ||||||
|         if ($this->application->build_pack !== 'nixpacks') { |         if ($this->application->build_pack !== 'nixpacks') { | ||||||
| @@ -335,9 +334,15 @@ class General extends Component | |||||||
|             $this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim(); |             $this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim(); | ||||||
|             $this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) { |             $this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) { | ||||||
|                 Url::fromString($domain, ['http', 'https']); |                 Url::fromString($domain, ['http', 'https']); | ||||||
|  | 
 | ||||||
|                 return str($domain)->trim()->lower(); |                 return str($domain)->trim()->lower(); | ||||||
|             }); |             }); | ||||||
|  | 
 | ||||||
|             $this->application->fqdn = $this->application->fqdn->unique()->implode(','); |             $this->application->fqdn = $this->application->fqdn->unique()->implode(','); | ||||||
|  |             $warning = sslipDomainWarning($this->application->fqdn); | ||||||
|  |             if ($warning) { | ||||||
|  |                 $this->dispatch('warning', __('warning.sslipdomain')); | ||||||
|  |             } | ||||||
|             $this->resetDefaultLabels(); |             $this->resetDefaultLabels(); | ||||||
| 
 | 
 | ||||||
|             if ($this->application->isDirty('redirect')) { |             if ($this->application->isDirty('redirect')) { | ||||||
| @@ -403,17 +408,19 @@ class General extends Component | |||||||
|             } |             } | ||||||
|             $this->application->custom_labels = base64_encode($this->customLabels); |             $this->application->custom_labels = base64_encode($this->customLabels); | ||||||
|             $this->application->save(); |             $this->application->save(); | ||||||
|             $showToaster && $this->dispatch('success', 'Application settings updated!'); |             $showToaster && ! $warning && $this->dispatch('success', 'Application settings updated!'); | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             $originalFqdn = $this->application->getOriginal('fqdn'); |             $originalFqdn = $this->application->getOriginal('fqdn'); | ||||||
|             if ($originalFqdn !== $this->application->fqdn) { |             if ($originalFqdn !== $this->application->fqdn) { | ||||||
|                 $this->application->fqdn = $originalFqdn; |                 $this->application->fqdn = $originalFqdn; | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|         } finally { |         } finally { | ||||||
|             $this->dispatch('configurationChanged'); |             $this->dispatch('configurationChanged'); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     public function downloadConfig() |     public function downloadConfig() | ||||||
|     { |     { | ||||||
|         $config = GenerateConfig::run($this->application, true); |         $config = GenerateConfig::run($this->application, true); | ||||||
|   | |||||||
| @@ -11,12 +11,21 @@ use Livewire\Component; | |||||||
| 
 | 
 | ||||||
| class General extends Component | class General extends Component | ||||||
| { | { | ||||||
|     protected $listeners = ['refresh']; |     protected $listeners = [ | ||||||
|  |         'envsUpdated' => 'refresh', | ||||||
|  |         'refresh', | ||||||
|  |     ]; | ||||||
| 
 | 
 | ||||||
|     public Server $server; |     public Server $server; | ||||||
| 
 | 
 | ||||||
|     public StandaloneRedis $database; |     public StandaloneRedis $database; | ||||||
| 
 | 
 | ||||||
|  |     public string $redis_username; | ||||||
|  | 
 | ||||||
|  |     public string $redis_password; | ||||||
|  | 
 | ||||||
|  |     public string $redis_version; | ||||||
|  | 
 | ||||||
|     public ?string $db_url = null; |     public ?string $db_url = null; | ||||||
| 
 | 
 | ||||||
|     public ?string $db_url_public = null; |     public ?string $db_url_public = null; | ||||||
| @@ -25,33 +34,33 @@ class General extends Component | |||||||
|         'database.name' => 'required', |         'database.name' => 'required', | ||||||
|         'database.description' => 'nullable', |         'database.description' => 'nullable', | ||||||
|         'database.redis_conf' => 'nullable', |         'database.redis_conf' => 'nullable', | ||||||
|         'database.redis_password' => 'required', |  | ||||||
|         'database.image' => 'required', |         'database.image' => 'required', | ||||||
|         'database.ports_mappings' => 'nullable', |         'database.ports_mappings' => 'nullable', | ||||||
|         'database.is_public' => 'nullable|boolean', |         'database.is_public' => 'nullable|boolean', | ||||||
|         'database.public_port' => 'nullable|integer', |         'database.public_port' => 'nullable|integer', | ||||||
|         'database.is_log_drain_enabled' => 'nullable|boolean', |         'database.is_log_drain_enabled' => 'nullable|boolean', | ||||||
|         'database.custom_docker_run_options' => 'nullable', |         'database.custom_docker_run_options' => 'nullable', | ||||||
|  |         'redis_username' => 'required', | ||||||
|  |         'redis_password' => 'required', | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     protected $validationAttributes = [ |     protected $validationAttributes = [ | ||||||
|         'database.name' => 'Name', |         'database.name' => 'Name', | ||||||
|         'database.description' => 'Description', |         'database.description' => 'Description', | ||||||
|         'database.redis_conf' => 'Redis Configuration', |         'database.redis_conf' => 'Redis Configuration', | ||||||
|         'database.redis_password' => 'Redis Password', |  | ||||||
|         'database.image' => 'Image', |         'database.image' => 'Image', | ||||||
|         'database.ports_mappings' => 'Port Mapping', |         'database.ports_mappings' => 'Port Mapping', | ||||||
|         'database.is_public' => 'Is Public', |         'database.is_public' => 'Is Public', | ||||||
|         'database.public_port' => 'Public Port', |         'database.public_port' => 'Public Port', | ||||||
|         'database.custom_docker_run_options' => 'Custom Docker Options', |         'database.custom_docker_run_options' => 'Custom Docker Options', | ||||||
|  |         'redis_username' => 'Redis Username', | ||||||
|  |         'redis_password' => 'Redis Password', | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     public function mount() |     public function mount() | ||||||
|     { |     { | ||||||
|         $this->db_url = $this->database->internal_db_url; |  | ||||||
|         $this->db_url_public = $this->database->external_db_url; |  | ||||||
|         $this->server = data_get($this->database, 'destination.server'); |         $this->server = data_get($this->database, 'destination.server'); | ||||||
| 
 |         $this->refreshView(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function instantSaveAdvanced() |     public function instantSaveAdvanced() | ||||||
| @@ -75,13 +84,24 @@ class General extends Component | |||||||
|     { |     { | ||||||
|         try { |         try { | ||||||
|             $this->validate(); |             $this->validate(); | ||||||
|             if ($this->database->redis_conf === '') { | 
 | ||||||
|                 $this->database->redis_conf = null; |             if (version_compare($this->redis_version, '6.0', '>=')) { | ||||||
|  |                 $this->database->runtime_environment_variables()->updateOrCreate( | ||||||
|  |                     ['key' => 'REDIS_USERNAME'], | ||||||
|  |                     ['value' => $this->redis_username, 'standalone_redis_id' => $this->database->id] | ||||||
|  |                 ); | ||||||
|             } |             } | ||||||
|  |             $this->database->runtime_environment_variables()->updateOrCreate( | ||||||
|  |                 ['key' => 'REDIS_PASSWORD'], | ||||||
|  |                 ['value' => $this->redis_password, 'standalone_redis_id' => $this->database->id] | ||||||
|  |             ); | ||||||
|  | 
 | ||||||
|             $this->database->save(); |             $this->database->save(); | ||||||
|             $this->dispatch('success', 'Database updated.'); |             $this->dispatch('success', 'Database updated.'); | ||||||
|         } catch (Exception $e) { |         } catch (Exception $e) { | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|  |         } finally { | ||||||
|  |             $this->dispatch('refreshEnvs'); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -119,10 +139,25 @@ class General extends Component | |||||||
|     public function refresh(): void |     public function refresh(): void | ||||||
|     { |     { | ||||||
|         $this->database->refresh(); |         $this->database->refresh(); | ||||||
|  |         $this->refreshView(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private function refreshView() | ||||||
|  |     { | ||||||
|  |         $this->db_url = $this->database->internal_db_url; | ||||||
|  |         $this->db_url_public = $this->database->external_db_url; | ||||||
|  |         $this->redis_version = $this->database->getRedisVersion(); | ||||||
|  |         $this->redis_username = $this->database->redis_username; | ||||||
|  |         $this->redis_password = $this->database->redis_password; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function render() |     public function render() | ||||||
|     { |     { | ||||||
|         return view('livewire.project.database.redis.general'); |         return view('livewire.project.database.redis.general'); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function isSharedVariable($name) | ||||||
|  |     { | ||||||
|  |         return $this->database->runtime_environment_variables()->where('key', $name)->where('is_shared', true)->exists(); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,18 +7,22 @@ use Livewire\Component; | |||||||
| 
 | 
 | ||||||
| class DeleteEnvironment extends Component | class DeleteEnvironment extends Component | ||||||
| { | { | ||||||
|     public array $parameters; |  | ||||||
| 
 |  | ||||||
|     public int $environment_id; |     public int $environment_id; | ||||||
| 
 | 
 | ||||||
|     public bool $disabled = false; |     public bool $disabled = false; | ||||||
| 
 | 
 | ||||||
|     public string $environmentName = ''; |     public string $environmentName = ''; | ||||||
| 
 | 
 | ||||||
|  |     public array $parameters; | ||||||
|  | 
 | ||||||
|     public function mount() |     public function mount() | ||||||
|     { |     { | ||||||
|         $this->parameters = get_route_parameters(); |         try { | ||||||
|             $this->environmentName = Environment::findOrFail($this->environment_id)->name; |             $this->environmentName = Environment::findOrFail($this->environment_id)->name; | ||||||
|  |             $this->parameters = get_route_parameters(); | ||||||
|  |         } catch (\Exception $e) { | ||||||
|  |             return handleError($e, $this); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function delete() |     public function delete() | ||||||
| @@ -30,7 +34,7 @@ class DeleteEnvironment extends Component | |||||||
|         if ($environment->isEmpty()) { |         if ($environment->isEmpty()) { | ||||||
|             $environment->delete(); |             $environment->delete(); | ||||||
| 
 | 
 | ||||||
|             return redirect()->route('project.show', ['project_uuid' => $this->parameters['project_uuid']]); |             return redirect()->route('project.show', parameters: ['project_uuid' => $this->parameters['project_uuid']]); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return $this->dispatch('error', 'Environment has defined resources, please delete them first.'); |         return $this->dispatch('error', 'Environment has defined resources, please delete them first.'); | ||||||
|   | |||||||
| @@ -18,7 +18,11 @@ class Index extends Component | |||||||
|     public function mount() |     public function mount() | ||||||
|     { |     { | ||||||
|         $this->private_keys = PrivateKey::ownedByCurrentTeam()->get(); |         $this->private_keys = PrivateKey::ownedByCurrentTeam()->get(); | ||||||
|         $this->projects = Project::ownedByCurrentTeam()->get(); |         $this->projects = Project::ownedByCurrentTeam()->get()->map(function ($project) { | ||||||
|  |             $project->settingsRoute = route('project.edit', ['project_uuid' => $project->uuid]); | ||||||
|  | 
 | ||||||
|  |             return $project; | ||||||
|  |         }); | ||||||
|         $this->servers = Server::ownedByCurrentTeam()->count(); |         $this->servers = Server::ownedByCurrentTeam()->count(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -317,6 +317,7 @@ class PublicGitRepository extends Component | |||||||
|                 //     $application->setConfig($config);
 |                 //     $application->setConfig($config);
 | ||||||
|                 // }
 |                 // }
 | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|             return redirect()->route('project.application.configuration', [ |             return redirect()->route('project.application.configuration', [ | ||||||
|                 'application_uuid' => $application->uuid, |                 'application_uuid' => $application->uuid, | ||||||
|                 'environment_name' => $environment->name, |                 'environment_name' => $environment->name, | ||||||
|   | |||||||
| @@ -32,8 +32,11 @@ class Index extends Component | |||||||
| 
 | 
 | ||||||
|     public $services = []; |     public $services = []; | ||||||
| 
 | 
 | ||||||
|  |     public array $parameters; | ||||||
|  | 
 | ||||||
|     public function mount() |     public function mount() | ||||||
|     { |     { | ||||||
|  |         $this->parameters = get_route_parameters(); | ||||||
|         $project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); |         $project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); | ||||||
|         if (! $project) { |         if (! $project) { | ||||||
|             return redirect()->route('dashboard'); |             return redirect()->route('dashboard'); | ||||||
| @@ -44,7 +47,6 @@ class Index extends Component | |||||||
|         } |         } | ||||||
|         $this->project = $project; |         $this->project = $project; | ||||||
|         $this->environment = $environment; |         $this->environment = $environment; | ||||||
| 
 |  | ||||||
|         $this->applications = $this->environment->applications->load(['tags']); |         $this->applications = $this->environment->applications->load(['tags']); | ||||||
|         $this->applications = $this->applications->map(function ($application) { |         $this->applications = $this->applications->map(function ($application) { | ||||||
|             if (data_get($application, 'environment.project.uuid')) { |             if (data_get($application, 'environment.project.uuid')) { | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ class EditDomain extends Component | |||||||
|     { |     { | ||||||
|         $this->application = ServiceApplication::find($this->applicationId); |         $this->application = ServiceApplication::find($this->applicationId); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     public function submit() |     public function submit() | ||||||
|     { |     { | ||||||
|         try { |         try { | ||||||
| @@ -28,9 +29,14 @@ class EditDomain extends Component | |||||||
|             $this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim(); |             $this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim(); | ||||||
|             $this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) { |             $this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) { | ||||||
|                 Url::fromString($domain, ['http', 'https']); |                 Url::fromString($domain, ['http', 'https']); | ||||||
|  | 
 | ||||||
|                 return str($domain)->trim()->lower(); |                 return str($domain)->trim()->lower(); | ||||||
|             }); |             }); | ||||||
|             $this->application->fqdn = $this->application->fqdn->unique()->implode(','); |             $this->application->fqdn = $this->application->fqdn->unique()->implode(','); | ||||||
|  |             $warning = sslipDomainWarning($this->application->fqdn); | ||||||
|  |             if ($warning) { | ||||||
|  |                 $this->dispatch('warning', __('warning.sslipdomain')); | ||||||
|  |             } | ||||||
|             check_domain_usage(resource: $this->application); |             check_domain_usage(resource: $this->application); | ||||||
|             $this->validate(); |             $this->validate(); | ||||||
|             $this->application->save(); |             $this->application->save(); | ||||||
| @@ -38,7 +44,7 @@ class EditDomain extends Component | |||||||
|             if (str($this->application->fqdn)->contains(',')) { |             if (str($this->application->fqdn)->contains(',')) { | ||||||
|                 $this->dispatch('warning', 'Some services do not support multiple domains, which can lead to problems and is NOT RECOMMENDED.<br><br>Only use multiple domains if you know what you are doing.'); |                 $this->dispatch('warning', 'Some services do not support multiple domains, which can lead to problems and is NOT RECOMMENDED.<br><br>Only use multiple domains if you know what you are doing.'); | ||||||
|             } else { |             } else { | ||||||
|                 $this->dispatch('success', 'Service saved.'); |                 ! $warning && $this->dispatch('success', 'Service saved.'); | ||||||
|             } |             } | ||||||
|             $this->application->service->parse(); |             $this->application->service->parse(); | ||||||
|             $this->dispatch('refresh'); |             $this->dispatch('refresh'); | ||||||
| @@ -48,6 +54,7 @@ class EditDomain extends Component | |||||||
|             if ($originalFqdn !== $this->application->fqdn) { |             if ($originalFqdn !== $this->application->fqdn) { | ||||||
|                 $this->application->fqdn = $originalFqdn; |                 $this->application->fqdn = $originalFqdn; | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -39,6 +39,7 @@ class Navbar extends Component | |||||||
| 
 | 
 | ||||||
|         return [ |         return [ | ||||||
|             "echo-private:user.{$userId},ServiceStatusChanged" => 'serviceStarted', |             "echo-private:user.{$userId},ServiceStatusChanged" => 'serviceStarted', | ||||||
|  |             'envsUpdated' => '$refresh', | ||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -30,11 +30,6 @@ class ServiceApplicationView extends Component | |||||||
|         'application.is_stripprefix_enabled' => 'nullable|boolean', |         'application.is_stripprefix_enabled' => 'nullable|boolean', | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     public function updatedApplicationFqdn() |  | ||||||
|     { |  | ||||||
| 
 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public function instantSave() |     public function instantSave() | ||||||
|     { |     { | ||||||
|         $this->submit(); |         $this->submit(); | ||||||
| @@ -82,10 +77,14 @@ class ServiceApplicationView extends Component | |||||||
|             $this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim(); |             $this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim(); | ||||||
|             $this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) { |             $this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) { | ||||||
|                 Url::fromString($domain, ['http', 'https']); |                 Url::fromString($domain, ['http', 'https']); | ||||||
|  | 
 | ||||||
|                 return str($domain)->trim()->lower(); |                 return str($domain)->trim()->lower(); | ||||||
|             }); |             }); | ||||||
|             $this->application->fqdn = $this->application->fqdn->unique()->implode(','); |             $this->application->fqdn = $this->application->fqdn->unique()->implode(','); | ||||||
| 
 |             $warning = sslipDomainWarning($this->application->fqdn); | ||||||
|  |             if ($warning) { | ||||||
|  |                 $this->dispatch('warning', __('warning.sslipdomain')); | ||||||
|  |             } | ||||||
|             check_domain_usage(resource: $this->application); |             check_domain_usage(resource: $this->application); | ||||||
|             $this->validate(); |             $this->validate(); | ||||||
|             $this->application->save(); |             $this->application->save(); | ||||||
| @@ -93,7 +92,7 @@ class ServiceApplicationView extends Component | |||||||
|             if (str($this->application->fqdn)->contains(',')) { |             if (str($this->application->fqdn)->contains(',')) { | ||||||
|                 $this->dispatch('warning', 'Some services do not support multiple domains, which can lead to problems and is NOT RECOMMENDED.<br><br>Only use multiple domains if you know what you are doing.'); |                 $this->dispatch('warning', 'Some services do not support multiple domains, which can lead to problems and is NOT RECOMMENDED.<br><br>Only use multiple domains if you know what you are doing.'); | ||||||
|             } else { |             } else { | ||||||
|                 $this->dispatch('success', 'Service saved.'); |                 ! $warning && $this->dispatch('success', 'Service saved.'); | ||||||
|             } |             } | ||||||
|             $this->dispatch('generateDockerCompose'); |             $this->dispatch('generateDockerCompose'); | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
| @@ -101,6 +100,7 @@ class ServiceApplicationView extends Component | |||||||
|             if ($originalFqdn !== $this->application->fqdn) { |             if ($originalFqdn !== $this->application->fqdn) { | ||||||
|                 $this->application->fqdn = $originalFqdn; |                 $this->application->fqdn = $originalFqdn; | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -37,6 +37,7 @@ class Show extends Component | |||||||
|         'env.is_literal' => 'required|boolean', |         'env.is_literal' => 'required|boolean', | ||||||
|         'env.is_shown_once' => 'required|boolean', |         'env.is_shown_once' => 'required|boolean', | ||||||
|         'env.real_value' => 'nullable', |         'env.real_value' => 'nullable', | ||||||
|  |         'env.is_required' => 'required|boolean', | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     protected $validationAttributes = [ |     protected $validationAttributes = [ | ||||||
| @@ -46,6 +47,7 @@ class Show extends Component | |||||||
|         'env.is_multiline' => 'Multiline', |         'env.is_multiline' => 'Multiline', | ||||||
|         'env.is_literal' => 'Literal', |         'env.is_literal' => 'Literal', | ||||||
|         'env.is_shown_once' => 'Shown Once', |         'env.is_shown_once' => 'Shown Once', | ||||||
|  |         'env.is_required' => 'Required', | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     public function refresh() |     public function refresh() | ||||||
| @@ -109,14 +111,14 @@ class Show extends Component | |||||||
|             } else { |             } else { | ||||||
|                 $this->validate(); |                 $this->validate(); | ||||||
|             } |             } | ||||||
|             // if (str($this->env->value)->startsWith('{{') && str($this->env->value)->endsWith('}}')) {
 |  | ||||||
|             //     $type = str($this->env->value)->after('{{')->before('.')->value;
 |  | ||||||
|             //     if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
 |  | ||||||
|             //         $this->dispatch('error', 'Invalid  shared variable type.', 'Valid types are: team, project, environment.');
 |  | ||||||
| 
 | 
 | ||||||
|             //         return;
 |             if ($this->env->is_required && str($this->env->real_value)->isEmpty()) { | ||||||
|             //     }
 |                 $oldValue = $this->env->getOriginal('value'); | ||||||
|             // }
 |                 $this->env->value = $oldValue; | ||||||
|  |                 $this->dispatch('error', 'Required environment variable cannot be empty.'); | ||||||
|  | 
 | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|             $this->serialize(); |             $this->serialize(); | ||||||
|             $this->env->save(); |             $this->env->save(); | ||||||
|             $this->dispatch('success', 'Environment variable updated.'); |             $this->dispatch('success', 'Environment variable updated.'); | ||||||
|   | |||||||
| @@ -31,13 +31,8 @@ class Metrics extends Component | |||||||
|     public function loadData() |     public function loadData() | ||||||
|     { |     { | ||||||
|         try { |         try { | ||||||
|             $metrics = $this->resource->getMetrics($this->interval); |             $cpuMetrics = $this->resource->getCpuMetrics($this->interval); | ||||||
|             $cpuMetrics = collect($metrics)->map(function ($metric) { |             $memoryMetrics = $this->resource->getMemoryMetrics($this->interval); | ||||||
|                 return [$metric[0], $metric[1]]; |  | ||||||
|             }); |  | ||||||
|             $memoryMetrics = collect($metrics)->map(function ($metric) { |  | ||||||
|                 return [$metric[0], $metric[2]]; |  | ||||||
|             }); |  | ||||||
|             $this->dispatch("refreshChartData-{$this->chartId}-cpu", [ |             $this->dispatch("refreshChartData-{$this->chartId}-cpu", [ | ||||||
|                 'seriesData' => $cpuMetrics, |                 'seriesData' => $cpuMetrics, | ||||||
|             ]); |             ]); | ||||||
|   | |||||||
| @@ -8,8 +8,11 @@ use Livewire\Component; | |||||||
| class UploadConfig extends Component | class UploadConfig extends Component | ||||||
| { | { | ||||||
|     public $config; |     public $config; | ||||||
|  | 
 | ||||||
|     public $applicationId; |     public $applicationId; | ||||||
|     public function mount() { | 
 | ||||||
|  |     public function mount() | ||||||
|  |     { | ||||||
|         if (isDev()) { |         if (isDev()) { | ||||||
|             $this->config = '{ |             $this->config = '{ | ||||||
|     "build_pack": "nixpacks", |     "build_pack": "nixpacks", | ||||||
| @@ -22,6 +25,7 @@ class UploadConfig extends Component | |||||||
| }'; | }'; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     public function uploadConfig() |     public function uploadConfig() | ||||||
|     { |     { | ||||||
|         try { |         try { | ||||||
| @@ -30,10 +34,12 @@ class UploadConfig extends Component | |||||||
|             $this->dispatch('success', 'Application settings updated'); |             $this->dispatch('success', 'Application settings updated'); | ||||||
|         } catch (\Exception $e) { |         } catch (\Exception $e) { | ||||||
|             $this->dispatch('error', $e->getMessage()); |             $this->dispatch('error', $e->getMessage()); | ||||||
|  | 
 | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     public function render() |     public function render() | ||||||
|     { |     { | ||||||
|         return view('livewire.project.shared.upload-config'); |         return view('livewire.project.shared.upload-config'); | ||||||
|   | |||||||
							
								
								
									
										77
									
								
								app/Livewire/Server/Advanced.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								app/Livewire/Server/Advanced.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Livewire\Server; | ||||||
|  | 
 | ||||||
|  | use App\Jobs\DockerCleanupJob; | ||||||
|  | use App\Models\Server; | ||||||
|  | use Livewire\Component; | ||||||
|  | 
 | ||||||
|  | class Advanced extends Component | ||||||
|  | { | ||||||
|  |     public Server $server; | ||||||
|  | 
 | ||||||
|  |     protected $rules = [ | ||||||
|  |         'server.settings.concurrent_builds' => 'required|integer|min:1', | ||||||
|  |         'server.settings.dynamic_timeout' => 'required|integer|min:1', | ||||||
|  |         'server.settings.force_docker_cleanup' => 'required|boolean', | ||||||
|  |         'server.settings.docker_cleanup_frequency' => 'required_if:server.settings.force_docker_cleanup,true|string', | ||||||
|  |         'server.settings.docker_cleanup_threshold' => 'required_if:server.settings.force_docker_cleanup,false|integer|min:1|max:100', | ||||||
|  |         'server.settings.delete_unused_volumes' => 'boolean', | ||||||
|  |         'server.settings.delete_unused_networks' => 'boolean', | ||||||
|  |     ]; | ||||||
|  | 
 | ||||||
|  |     protected $validationAttributes = [ | ||||||
|  | 
 | ||||||
|  |         'server.settings.concurrent_builds' => 'Concurrent Builds', | ||||||
|  |         'server.settings.dynamic_timeout' => 'Dynamic Timeout', | ||||||
|  |         'server.settings.force_docker_cleanup' => 'Force Docker Cleanup', | ||||||
|  |         'server.settings.docker_cleanup_frequency' => 'Docker Cleanup Frequency', | ||||||
|  |         'server.settings.docker_cleanup_threshold' => 'Docker Cleanup Threshold', | ||||||
|  |         'server.settings.delete_unused_volumes' => 'Delete Unused Volumes', | ||||||
|  |         'server.settings.delete_unused_networks' => 'Delete Unused Networks', | ||||||
|  |     ]; | ||||||
|  | 
 | ||||||
|  |     public function instantSave() | ||||||
|  |     { | ||||||
|  |         try { | ||||||
|  |             $this->validate(); | ||||||
|  |             $this->server->settings->save(); | ||||||
|  |             $this->dispatch('success', 'Server updated.'); | ||||||
|  |             $this->dispatch('refreshServerShow'); | ||||||
|  |         } catch (\Throwable $e) { | ||||||
|  |             $this->server->settings->refresh(); | ||||||
|  | 
 | ||||||
|  |             return handleError($e, $this); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function manualCleanup() | ||||||
|  |     { | ||||||
|  |         try { | ||||||
|  |             DockerCleanupJob::dispatch($this->server, true); | ||||||
|  |             $this->dispatch('success', 'Manual cleanup job started. Depending on the amount of data, this might take a while.'); | ||||||
|  |         } catch (\Throwable $e) { | ||||||
|  |             return handleError($e, $this); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function submit() | ||||||
|  |     { | ||||||
|  |         try { | ||||||
|  |             $frequency = $this->server->settings->docker_cleanup_frequency; | ||||||
|  |             if (empty($frequency) || ! validate_cron_expression($frequency)) { | ||||||
|  |                 $this->server->settings->docker_cleanup_frequency = '*/10 * * * *'; | ||||||
|  |                 throw new \Exception('Invalid Cron / Human expression for Docker Cleanup Frequency. Resetting to default 10 minutes.'); | ||||||
|  |             } | ||||||
|  |             $this->server->settings->save(); | ||||||
|  |             $this->dispatch('success', 'Server updated.'); | ||||||
|  |         } catch (\Throwable $e) { | ||||||
|  |             return handleError($e, $this); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function render() | ||||||
|  |     { | ||||||
|  |         return view('livewire.server.advanced'); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -34,12 +34,12 @@ class Charts extends Component | |||||||
|         try { |         try { | ||||||
|             $cpuMetrics = $this->server->getCpuMetrics($this->interval); |             $cpuMetrics = $this->server->getCpuMetrics($this->interval); | ||||||
|             $memoryMetrics = $this->server->getMemoryMetrics($this->interval); |             $memoryMetrics = $this->server->getMemoryMetrics($this->interval); | ||||||
|             $cpuMetrics = collect($cpuMetrics)->map(function ($metric) { |             // $cpuMetrics = collect($cpuMetrics)->map(function ($metric) {
 | ||||||
|                 return [$metric[0], $metric[1]]; |             //     return [$metric[0], $metric[1]];
 | ||||||
|             }); |             // });
 | ||||||
|             $memoryMetrics = collect($memoryMetrics)->map(function ($metric) { |             // $memoryMetrics = collect($memoryMetrics)->map(function ($metric) {
 | ||||||
|                 return [$metric[0], $metric[1]]; |             //     return [$metric[0], $metric[1]];
 | ||||||
|             }); |             // });
 | ||||||
|             $this->dispatch("refreshChartData-{$this->chartId}-cpu", [ |             $this->dispatch("refreshChartData-{$this->chartId}-cpu", [ | ||||||
|                 'seriesData' => $cpuMetrics, |                 'seriesData' => $cpuMetrics, | ||||||
|             ]); |             ]); | ||||||
|   | |||||||
							
								
								
									
										44
									
								
								app/Livewire/Server/CloudflareTunnels.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								app/Livewire/Server/CloudflareTunnels.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Livewire\Server; | ||||||
|  | 
 | ||||||
|  | use App\Models\Server; | ||||||
|  | use Livewire\Component; | ||||||
|  | 
 | ||||||
|  | class CloudflareTunnels extends Component | ||||||
|  | { | ||||||
|  |     public Server $server; | ||||||
|  | 
 | ||||||
|  |     protected $rules = [ | ||||||
|  |         'server.settings.is_cloudflare_tunnel' => 'required|boolean', | ||||||
|  |     ]; | ||||||
|  | 
 | ||||||
|  |     protected $validationAttributes = [ | ||||||
|  |         'server.settings.is_cloudflare_tunnel' => 'Cloudflare Tunnel', | ||||||
|  |     ]; | ||||||
|  | 
 | ||||||
|  |     public function instantSave() | ||||||
|  |     { | ||||||
|  |         try { | ||||||
|  |             $this->validate(); | ||||||
|  |             $this->server->settings->save(); | ||||||
|  |             $this->dispatch('success', 'Server updated.'); | ||||||
|  |             $this->dispatch('refreshServerShow'); | ||||||
|  |         } catch (\Throwable $e) { | ||||||
|  |             return handleError($e, $this); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function manualCloudflareConfig() | ||||||
|  |     { | ||||||
|  |         $this->server->settings->is_cloudflare_tunnel = true; | ||||||
|  |         $this->server->settings->save(); | ||||||
|  |         $this->server->refresh(); | ||||||
|  |         $this->dispatch('success', 'Cloudflare Tunnels enabled.'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function render() | ||||||
|  |     { | ||||||
|  |         return view('livewire.server.cloudflare-tunnels'); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -2,6 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Livewire\Server; | namespace App\Livewire\Server; | ||||||
| 
 | 
 | ||||||
|  | use App\Actions\Server\DeleteServer; | ||||||
| use Illuminate\Foundation\Auth\Access\AuthorizesRequests; | use Illuminate\Foundation\Auth\Access\AuthorizesRequests; | ||||||
| use Illuminate\Support\Facades\Auth; | use Illuminate\Support\Facades\Auth; | ||||||
| use Illuminate\Support\Facades\Hash; | use Illuminate\Support\Facades\Hash; | ||||||
| @@ -28,6 +29,7 @@ class Delete extends Component | |||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|             $this->server->delete(); |             $this->server->delete(); | ||||||
|  |             DeleteServer::dispatch($this->server); | ||||||
| 
 | 
 | ||||||
|             return redirect()->route('server.index'); |             return redirect()->route('server.index'); | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|   | |||||||
| @@ -4,8 +4,6 @@ namespace App\Livewire\Server; | |||||||
| 
 | 
 | ||||||
| use App\Actions\Server\StartSentinel; | use App\Actions\Server\StartSentinel; | ||||||
| use App\Actions\Server\StopSentinel; | use App\Actions\Server\StopSentinel; | ||||||
| use App\Jobs\DockerCleanupJob; |  | ||||||
| use App\Jobs\PullSentinelImageJob; |  | ||||||
| use App\Models\Server; | use App\Models\Server; | ||||||
| use Livewire\Component; | use Livewire\Component; | ||||||
| 
 | 
 | ||||||
| @@ -46,25 +44,19 @@ class Form extends Component | |||||||
|         'server.ip' => 'required', |         'server.ip' => 'required', | ||||||
|         'server.user' => 'required', |         'server.user' => 'required', | ||||||
|         'server.port' => 'required', |         'server.port' => 'required', | ||||||
|         'server.settings.is_cloudflare_tunnel' => 'required|boolean', |         'wildcard_domain' => 'nullable|url', | ||||||
|         'server.settings.is_reachable' => 'required', |         'server.settings.is_reachable' => 'required', | ||||||
|         'server.settings.is_swarm_manager' => 'required|boolean', |         'server.settings.is_swarm_manager' => 'required|boolean', | ||||||
|         'server.settings.is_swarm_worker' => 'required|boolean', |         'server.settings.is_swarm_worker' => 'required|boolean', | ||||||
|         'server.settings.is_build_server' => 'required|boolean', |         'server.settings.is_build_server' => 'required|boolean', | ||||||
|         'server.settings.concurrent_builds' => 'required|integer|min:1', |  | ||||||
|         'server.settings.dynamic_timeout' => 'required|integer|min:1', |  | ||||||
|         'server.settings.is_metrics_enabled' => 'required|boolean', |         'server.settings.is_metrics_enabled' => 'required|boolean', | ||||||
|         'server.settings.metrics_token' => 'required', |         'server.settings.sentinel_token' => 'required', | ||||||
|         'server.settings.metrics_refresh_rate_seconds' => 'required|integer|min:1', |         'server.settings.sentinel_metrics_refresh_rate_seconds' => 'required|integer|min:1', | ||||||
|         'server.settings.metrics_history_days' => 'required|integer|min:1', |         'server.settings.sentinel_metrics_history_days' => 'required|integer|min:1', | ||||||
|         'wildcard_domain' => 'nullable|url', |         'server.settings.sentinel_push_interval_seconds' => 'required|integer|min:10', | ||||||
|         'server.settings.is_server_api_enabled' => 'required|boolean', |         'server.settings.sentinel_custom_url' => 'nullable|url', | ||||||
|  |         'server.settings.is_sentinel_enabled' => 'required|boolean', | ||||||
|         'server.settings.server_timezone' => 'required|string|timezone', |         'server.settings.server_timezone' => 'required|string|timezone', | ||||||
|         'server.settings.force_docker_cleanup' => 'required|boolean', |  | ||||||
|         'server.settings.docker_cleanup_frequency' => 'required_if:server.settings.force_docker_cleanup,true|string', |  | ||||||
|         'server.settings.docker_cleanup_threshold' => 'required_if:server.settings.force_docker_cleanup,false|integer|min:1|max:100', |  | ||||||
|         'server.settings.delete_unused_volumes' => 'boolean', |  | ||||||
|         'server.settings.delete_unused_networks' => 'boolean', |  | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     protected $validationAttributes = [ |     protected $validationAttributes = [ | ||||||
| @@ -73,21 +65,18 @@ class Form extends Component | |||||||
|         'server.ip' => 'IP address/Domain', |         'server.ip' => 'IP address/Domain', | ||||||
|         'server.user' => 'User', |         'server.user' => 'User', | ||||||
|         'server.port' => 'Port', |         'server.port' => 'Port', | ||||||
|         'server.settings.is_cloudflare_tunnel' => 'Cloudflare Tunnel', |  | ||||||
|         'server.settings.is_reachable' => 'Is reachable', |         'server.settings.is_reachable' => 'Is reachable', | ||||||
|         'server.settings.is_swarm_manager' => 'Swarm Manager', |         'server.settings.is_swarm_manager' => 'Swarm Manager', | ||||||
|         'server.settings.is_swarm_worker' => 'Swarm Worker', |         'server.settings.is_swarm_worker' => 'Swarm Worker', | ||||||
|         'server.settings.is_build_server' => 'Build Server', |         'server.settings.is_build_server' => 'Build Server', | ||||||
|         'server.settings.concurrent_builds' => 'Concurrent Builds', |  | ||||||
|         'server.settings.dynamic_timeout' => 'Dynamic Timeout', |  | ||||||
|         'server.settings.is_metrics_enabled' => 'Metrics', |         'server.settings.is_metrics_enabled' => 'Metrics', | ||||||
|         'server.settings.metrics_token' => 'Metrics Token', |         'server.settings.sentinel_token' => 'Metrics Token', | ||||||
|         'server.settings.metrics_refresh_rate_seconds' => 'Metrics Interval', |         'server.settings.sentinel_metrics_refresh_rate_seconds' => 'Metrics Interval', | ||||||
|         'server.settings.metrics_history_days' => 'Metrics History', |         'server.settings.sentinel_metrics_history_days' => 'Metrics History', | ||||||
|         'server.settings.is_server_api_enabled' => 'Server API', |         'server.settings.sentinel_push_interval_seconds' => 'Push Interval', | ||||||
|  |         'server.settings.is_sentinel_enabled' => 'Server API', | ||||||
|  |         'server.settings.sentinel_custom_url' => 'Coolify URL', | ||||||
|         'server.settings.server_timezone' => 'Server Timezone', |         'server.settings.server_timezone' => 'Server Timezone', | ||||||
|         'server.settings.delete_unused_volumes' => 'Delete Unused Volumes', |  | ||||||
|         'server.settings.delete_unused_networks' => 'Delete Unused Networks', |  | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     public function mount(Server $server) |     public function mount(Server $server) | ||||||
| @@ -95,10 +84,24 @@ class Form extends Component | |||||||
|         $this->server = $server; |         $this->server = $server; | ||||||
|         $this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray(); |         $this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray(); | ||||||
|         $this->wildcard_domain = $this->server->settings->wildcard_domain; |         $this->wildcard_domain = $this->server->settings->wildcard_domain; | ||||||
|         $this->server->settings->docker_cleanup_threshold = $this->server->settings->docker_cleanup_threshold; |     } | ||||||
|         $this->server->settings->docker_cleanup_frequency = $this->server->settings->docker_cleanup_frequency; | 
 | ||||||
|         $this->server->settings->delete_unused_volumes = $server->settings->delete_unused_volumes; |     public function checkSyncStatus() | ||||||
|         $this->server->settings->delete_unused_networks = $server->settings->delete_unused_networks; |     { | ||||||
|  |         $this->server->refresh(); | ||||||
|  |         $this->server->settings->refresh(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function regenerateSentinelToken() | ||||||
|  |     { | ||||||
|  |         try { | ||||||
|  |             $this->server->settings->generateSentinelToken(); | ||||||
|  |             $this->server->settings->refresh(); | ||||||
|  |             $this->restartSentinel(notification: false); | ||||||
|  |             $this->dispatch('success', 'Token regenerated & Sentinel restarted.'); | ||||||
|  |         } catch (\Throwable $e) { | ||||||
|  |             return handleError($e, $this); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function updated($field) |     public function updated($field) | ||||||
| @@ -131,21 +134,35 @@ class Form extends Component | |||||||
|         $this->dispatch('proxyStatusUpdated'); |         $this->dispatch('proxyStatusUpdated'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function checkPortForServerApi() |     public function updatedServerSettingsIsSentinelEnabled($value) | ||||||
|     { |     { | ||||||
|  |         $this->validate(); | ||||||
|  |         $this->validate([ | ||||||
|  |             'server.settings.sentinel_custom_url' => 'required|url', | ||||||
|  |         ]); | ||||||
|  |         if ($value === false) { | ||||||
|  |             StopSentinel::dispatch($this->server); | ||||||
|  |             $this->server->settings->is_metrics_enabled = false; | ||||||
|  |             $this->server->settings->save(); | ||||||
|  |             $this->server->sentinelHeartbeat(isReset: true); | ||||||
|  |         } else { | ||||||
|             try { |             try { | ||||||
|             if ($this->server->settings->is_server_api_enabled === true) { |                 StartSentinel::run($this->server); | ||||||
|                 $this->server->checkServerApi(); |  | ||||||
|                 $this->dispatch('success', 'Server API is reachable.'); |  | ||||||
|             } |  | ||||||
|             } catch (\Throwable $e) { |             } catch (\Throwable $e) { | ||||||
|                 return handleError($e, $this); |                 return handleError($e, $this); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function updatedServerSettingsIsMetricsEnabled() | ||||||
|  |     { | ||||||
|  |         $this->restartSentinel(); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     public function instantSave() |     public function instantSave() | ||||||
|     { |     { | ||||||
|         try { |         try { | ||||||
|  |             $this->validate(); | ||||||
|             refresh_server_connection($this->server->privateKey); |             refresh_server_connection($this->server->privateKey); | ||||||
|             $this->validateServer(false); |             $this->validateServer(false); | ||||||
| 
 | 
 | ||||||
| @@ -153,33 +170,27 @@ class Form extends Component | |||||||
|             $this->server->save(); |             $this->server->save(); | ||||||
|             $this->dispatch('success', 'Server updated.'); |             $this->dispatch('success', 'Server updated.'); | ||||||
|             $this->dispatch('refreshServerShow'); |             $this->dispatch('refreshServerShow'); | ||||||
|             if ($this->server->isSentinelEnabled()) { |  | ||||||
|                 PullSentinelImageJob::dispatchSync($this->server); |  | ||||||
|                 ray('Sentinel is enabled'); |  | ||||||
|                 if ($this->server->settings->isDirty('is_metrics_enabled')) { |  | ||||||
|                     $this->dispatch('reloadWindow'); |  | ||||||
|                 } |  | ||||||
|                 if ($this->server->settings->isDirty('is_server_api_enabled') && $this->server->settings->is_server_api_enabled === true) { |  | ||||||
|                     ray('Starting sentinel'); |  | ||||||
|                 } |  | ||||||
|             } else { |  | ||||||
|                 ray('Sentinel is not enabled'); |  | ||||||
|                 StopSentinel::dispatch($this->server); |  | ||||||
|             } |  | ||||||
|             $this->server->settings->save(); |             $this->server->settings->save(); | ||||||
|             // $this->checkPortForServerApi();
 |  | ||||||
| 
 | 
 | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|  |             $this->server->settings->refresh(); | ||||||
|  | 
 | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function restartSentinel() |     public function restartSentinel($notification = true) | ||||||
|     { |     { | ||||||
|         try { |         try { | ||||||
|  |             $this->validate(); | ||||||
|  |             $this->validate([ | ||||||
|  |                 'server.settings.sentinel_custom_url' => 'required|url', | ||||||
|  |             ]); | ||||||
|             $version = get_latest_sentinel_version(); |             $version = get_latest_sentinel_version(); | ||||||
|             StartSentinel::run($this->server, $version, true); |             StartSentinel::run($this->server, $version, true); | ||||||
|             $this->dispatch('success', 'Sentinel restarted.'); |             if ($notification) { | ||||||
|  |                 $this->dispatch('success', 'Sentinel started.'); | ||||||
|  |             } | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|         } |         } | ||||||
| @@ -236,16 +247,15 @@ class Form extends Component | |||||||
|             } |             } | ||||||
|             refresh_server_connection($this->server->privateKey); |             refresh_server_connection($this->server->privateKey); | ||||||
|             $this->server->settings->wildcard_domain = $this->wildcard_domain; |             $this->server->settings->wildcard_domain = $this->wildcard_domain; | ||||||
|             if ($this->server->settings->force_docker_cleanup) { |             // if ($this->server->settings->force_docker_cleanup) {
 | ||||||
|                 $this->server->settings->docker_cleanup_frequency = $this->server->settings->docker_cleanup_frequency; |             //     $this->server->settings->docker_cleanup_frequency = $this->server->settings->docker_cleanup_frequency;
 | ||||||
|             } else { |             // } else {
 | ||||||
|                 $this->server->settings->docker_cleanup_threshold = $this->server->settings->docker_cleanup_threshold; |             //     $this->server->settings->docker_cleanup_threshold = $this->server->settings->docker_cleanup_threshold;
 | ||||||
|             } |             // }
 | ||||||
|             $currentTimezone = $this->server->settings->getOriginal('server_timezone'); |             $currentTimezone = $this->server->settings->getOriginal('server_timezone'); | ||||||
|             $newTimezone = $this->server->settings->server_timezone; |             $newTimezone = $this->server->settings->server_timezone; | ||||||
|             if ($currentTimezone !== $newTimezone || $currentTimezone === '') { |             if ($currentTimezone !== $newTimezone || $currentTimezone === '') { | ||||||
|                 $this->server->settings->server_timezone = $newTimezone; |                 $this->server->settings->server_timezone = $newTimezone; | ||||||
|                 $this->server->settings->save(); |  | ||||||
|             } |             } | ||||||
|             $this->server->settings->save(); |             $this->server->settings->save(); | ||||||
|             $this->server->save(); |             $this->server->save(); | ||||||
| @@ -255,29 +265,4 @@ class Form extends Component | |||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     public function updatedServerSettingsServerTimezone($value) |  | ||||||
|     { |  | ||||||
|         $this->server->settings->server_timezone = $value; |  | ||||||
|         $this->server->settings->save(); |  | ||||||
|         $this->dispatch('success', 'Server timezone updated.'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public function manualCleanup() |  | ||||||
|     { |  | ||||||
|         try { |  | ||||||
|             DockerCleanupJob::dispatch($this->server, true); |  | ||||||
|             $this->dispatch('success', 'Manual cleanup job started. Depending on the amount of data, this might take a while.'); |  | ||||||
|         } catch (\Throwable $e) { |  | ||||||
|             return handleError($e, $this); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public function manualCloudflareConfig() |  | ||||||
|     { |  | ||||||
|         $this->server->settings->is_cloudflare_tunnel = true; |  | ||||||
|         $this->server->settings->save(); |  | ||||||
|         $this->server->refresh(); |  | ||||||
|         $this->dispatch('success', 'Cloudflare Tunnels enabled.'); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,16 +0,0 @@ | |||||||
| <?php |  | ||||||
| 
 |  | ||||||
| namespace App\Livewire\Server\Proxy; |  | ||||||
| 
 |  | ||||||
| use App\Models\Server; |  | ||||||
| use Livewire\Component; |  | ||||||
| 
 |  | ||||||
| class Modal extends Component |  | ||||||
| { |  | ||||||
|     public Server $server; |  | ||||||
| 
 |  | ||||||
|     public function proxyStatusUpdated() |  | ||||||
|     { |  | ||||||
|         $this->dispatch('proxyStatusUpdated'); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -22,10 +22,7 @@ class Show extends Component | |||||||
|     { |     { | ||||||
|         $this->parameters = get_route_parameters(); |         $this->parameters = get_route_parameters(); | ||||||
|         try { |         try { | ||||||
|             $this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first(); |             $this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->firstOrFail(); | ||||||
|             if (is_null($this->server)) { |  | ||||||
|                 return redirect()->route('server.index'); |  | ||||||
|             } |  | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -15,7 +15,9 @@ class Resources extends Component | |||||||
| 
 | 
 | ||||||
|     public $parameters = []; |     public $parameters = []; | ||||||
| 
 | 
 | ||||||
|     public Collection $unmanagedContainers; |     public Collection $containers; | ||||||
|  | 
 | ||||||
|  |     public $activeTab = 'managed'; | ||||||
| 
 | 
 | ||||||
|     public function getListeners() |     public function getListeners() | ||||||
|     { |     { | ||||||
| @@ -50,14 +52,29 @@ class Resources extends Component | |||||||
|     public function refreshStatus() |     public function refreshStatus() | ||||||
|     { |     { | ||||||
|         $this->server->refresh(); |         $this->server->refresh(); | ||||||
|  |         if ($this->activeTab === 'managed') { | ||||||
|  |             $this->loadManagedContainers(); | ||||||
|  |         } else { | ||||||
|             $this->loadUnmanagedContainers(); |             $this->loadUnmanagedContainers(); | ||||||
|  |         } | ||||||
|         $this->dispatch('success', 'Resource statuses refreshed.'); |         $this->dispatch('success', 'Resource statuses refreshed.'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function loadManagedContainers() | ||||||
|  |     { | ||||||
|  |         try { | ||||||
|  |             $this->activeTab = 'managed'; | ||||||
|  |             $this->containers = $this->server->refresh()->definedResources(); | ||||||
|  |         } catch (\Throwable $e) { | ||||||
|  |             return handleError($e, $this); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function loadUnmanagedContainers() |     public function loadUnmanagedContainers() | ||||||
|     { |     { | ||||||
|  |         $this->activeTab = 'unmanaged'; | ||||||
|         try { |         try { | ||||||
|             $this->unmanagedContainers = $this->server->loadUnmanagedContainers(); |             $this->containers = $this->server->loadUnmanagedContainers(); | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|         } |         } | ||||||
| @@ -65,13 +82,14 @@ class Resources extends Component | |||||||
| 
 | 
 | ||||||
|     public function mount() |     public function mount() | ||||||
|     { |     { | ||||||
|         $this->unmanagedContainers = collect(); |         $this->containers = collect(); | ||||||
|         $this->parameters = get_route_parameters(); |         $this->parameters = get_route_parameters(); | ||||||
|         try { |         try { | ||||||
|             $this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first(); |             $this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first(); | ||||||
|             if (is_null($this->server)) { |             if (is_null($this->server)) { | ||||||
|                 return redirect()->route('server.index'); |                 return redirect()->route('server.index'); | ||||||
|             } |             } | ||||||
|  |             $this->loadManagedContainers(); | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -10,20 +10,17 @@ class Show extends Component | |||||||
| { | { | ||||||
|     use AuthorizesRequests; |     use AuthorizesRequests; | ||||||
| 
 | 
 | ||||||
|     public ?Server $server = null; |     public Server $server; | ||||||
| 
 | 
 | ||||||
|     public $parameters = []; |     public array $parameters; | ||||||
| 
 | 
 | ||||||
|     protected $listeners = ['refreshServerShow']; |     protected $listeners = ['refreshServerShow']; | ||||||
| 
 | 
 | ||||||
|     public function mount() |     public function mount() | ||||||
|     { |     { | ||||||
|         $this->parameters = get_route_parameters(); |  | ||||||
|         try { |         try { | ||||||
|             $this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first(); |             $this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->firstOrFail(); | ||||||
|             if (is_null($this->server)) { |             $this->parameters = get_route_parameters(); | ||||||
|                 return redirect()->route('server.index'); |  | ||||||
|             } |  | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -2,7 +2,6 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Livewire\Server; | namespace App\Livewire\Server; | ||||||
| 
 | 
 | ||||||
| use App\Models\PrivateKey; |  | ||||||
| use App\Models\Server; | use App\Models\Server; | ||||||
| use Livewire\Component; | use Livewire\Component; | ||||||
| 
 | 
 | ||||||
| @@ -14,15 +13,29 @@ class ShowPrivateKey extends Component | |||||||
| 
 | 
 | ||||||
|     public $parameters; |     public $parameters; | ||||||
| 
 | 
 | ||||||
|  |     public function mount() | ||||||
|  |     { | ||||||
|  |         $this->parameters = get_route_parameters(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function setPrivateKey($privateKeyId) |     public function setPrivateKey($privateKeyId) | ||||||
|     { |     { | ||||||
|  |         $originalPrivateKeyId = $this->server->getOriginal('private_key_id'); | ||||||
|         try { |         try { | ||||||
|             $privateKey = PrivateKey::findOrFail($privateKeyId); |             $this->server->update(['private_key_id' => $privateKeyId]); | ||||||
|             $this->server->update(['private_key_id' => $privateKey->id]); |             ['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection(); | ||||||
|             $this->server->refresh(); |             if ($uptime) { | ||||||
|                 $this->dispatch('success', 'Private key updated successfully.'); |                 $this->dispatch('success', 'Private key updated successfully.'); | ||||||
|  |             } else { | ||||||
|  |                 throw new \Exception('Server is not reachable.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.<br><br>Error: '.$error); | ||||||
|  |             } | ||||||
|         } catch (\Exception $e) { |         } catch (\Exception $e) { | ||||||
|  |             $this->server->update(['private_key_id' => $originalPrivateKeyId]); | ||||||
|  |             $this->server->validateConnection(); | ||||||
|             $this->dispatch('error', 'Failed to update private key: '.$e->getMessage()); |             $this->dispatch('error', 'Failed to update private key: '.$e->getMessage()); | ||||||
|  |         } finally { | ||||||
|  |             $this->dispatch('refreshServerShow'); | ||||||
|  |             $this->server->refresh(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -33,18 +46,15 @@ class ShowPrivateKey extends Component | |||||||
|             if ($uptime) { |             if ($uptime) { | ||||||
|                 $this->dispatch('success', 'Server is reachable.'); |                 $this->dispatch('success', 'Server is reachable.'); | ||||||
|             } else { |             } else { | ||||||
|                 ray($error); |  | ||||||
|                 $this->dispatch('error', 'Server is not reachable.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.<br><br>Error: '.$error); |                 $this->dispatch('error', 'Server is not reachable.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.<br><br>Error: '.$error); | ||||||
| 
 | 
 | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|  |         } finally { | ||||||
|  |             $this->dispatch('refreshServerShow'); | ||||||
|  |             $this->server->refresh(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     public function mount() |  | ||||||
|     { |  | ||||||
|         $this->parameters = get_route_parameters(); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -25,6 +25,10 @@ class Index extends Component | |||||||
| 
 | 
 | ||||||
|     public string $update_check_frequency; |     public string $update_check_frequency; | ||||||
| 
 | 
 | ||||||
|  |     public $timezones; | ||||||
|  | 
 | ||||||
|  |     public bool $disable_two_step_confirmation; | ||||||
|  | 
 | ||||||
|     protected string $dynamic_config_path = '/data/coolify/proxy/dynamic'; |     protected string $dynamic_config_path = '/data/coolify/proxy/dynamic'; | ||||||
| 
 | 
 | ||||||
|     protected Server $server; |     protected Server $server; | ||||||
| @@ -38,6 +42,8 @@ class Index extends Component | |||||||
|         'settings.instance_name' => 'nullable', |         'settings.instance_name' => 'nullable', | ||||||
|         'settings.allowed_ips' => 'nullable', |         'settings.allowed_ips' => 'nullable', | ||||||
|         'settings.is_auto_update_enabled' => 'boolean', |         'settings.is_auto_update_enabled' => 'boolean', | ||||||
|  |         'settings.public_ipv4' => 'nullable', | ||||||
|  |         'settings.public_ipv6' => 'nullable', | ||||||
|         'auto_update_frequency' => 'string', |         'auto_update_frequency' => 'string', | ||||||
|         'update_check_frequency' => 'string', |         'update_check_frequency' => 'string', | ||||||
|         'settings.instance_timezone' => 'required|string|timezone', |         'settings.instance_timezone' => 'required|string|timezone', | ||||||
| @@ -51,16 +57,18 @@ class Index extends Component | |||||||
|         'settings.custom_dns_servers' => 'Custom DNS servers', |         'settings.custom_dns_servers' => 'Custom DNS servers', | ||||||
|         'settings.allowed_ips' => 'Allowed IPs', |         'settings.allowed_ips' => 'Allowed IPs', | ||||||
|         'settings.is_auto_update_enabled' => 'Auto Update Enabled', |         'settings.is_auto_update_enabled' => 'Auto Update Enabled', | ||||||
|  |         'settings.public_ipv4' => 'IPv4', | ||||||
|  |         'settings.public_ipv6' => 'IPv6', | ||||||
|         'auto_update_frequency' => 'Auto Update Frequency', |         'auto_update_frequency' => 'Auto Update Frequency', | ||||||
|         'update_check_frequency' => 'Update Check Frequency', |         'update_check_frequency' => 'Update Check Frequency', | ||||||
|  |         'settings.instance_timezone' => 'Instance Timezone', | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     public $timezones; |  | ||||||
| 
 |  | ||||||
|     public function mount() |     public function mount() | ||||||
|     { |     { | ||||||
|         if (isInstanceAdmin()) { |         if (isInstanceAdmin()) { | ||||||
|             $this->settings = instanceSettings(); |             $this->settings = instanceSettings(); | ||||||
|  |             loggy($this->settings); | ||||||
|             $this->do_not_track = $this->settings->do_not_track; |             $this->do_not_track = $this->settings->do_not_track; | ||||||
|             $this->is_auto_update_enabled = $this->settings->is_auto_update_enabled; |             $this->is_auto_update_enabled = $this->settings->is_auto_update_enabled; | ||||||
|             $this->is_registration_enabled = $this->settings->is_registration_enabled; |             $this->is_registration_enabled = $this->settings->is_registration_enabled; | ||||||
| @@ -69,6 +77,7 @@ class Index extends Component | |||||||
|             $this->auto_update_frequency = $this->settings->auto_update_frequency; |             $this->auto_update_frequency = $this->settings->auto_update_frequency; | ||||||
|             $this->update_check_frequency = $this->settings->update_check_frequency; |             $this->update_check_frequency = $this->settings->update_check_frequency; | ||||||
|             $this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray(); |             $this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray(); | ||||||
|  |             $this->disable_two_step_confirmation = $this->settings->disable_two_step_confirmation; | ||||||
|         } else { |         } else { | ||||||
|             return redirect()->route('dashboard'); |             return redirect()->route('dashboard'); | ||||||
|         } |         } | ||||||
| @@ -83,6 +92,7 @@ class Index extends Component | |||||||
|         $this->settings->is_api_enabled = $this->is_api_enabled; |         $this->settings->is_api_enabled = $this->is_api_enabled; | ||||||
|         $this->settings->auto_update_frequency = $this->auto_update_frequency; |         $this->settings->auto_update_frequency = $this->auto_update_frequency; | ||||||
|         $this->settings->update_check_frequency = $this->update_check_frequency; |         $this->settings->update_check_frequency = $this->update_check_frequency; | ||||||
|  |         $this->settings->disable_two_step_confirmation = $this->disable_two_step_confirmation; | ||||||
|         $this->settings->save(); |         $this->settings->save(); | ||||||
|         $this->dispatch('success', 'Settings updated!'); |         $this->dispatch('success', 'Settings updated!'); | ||||||
|     } |     } | ||||||
| @@ -170,15 +180,16 @@ class Index extends Component | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function updatedSettingsInstanceTimezone($value) |  | ||||||
|     { |  | ||||||
|         $this->settings->instance_timezone = $value; |  | ||||||
|         $this->settings->save(); |  | ||||||
|         $this->dispatch('success', 'Instance timezone updated.'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public function render() |     public function render() | ||||||
|     { |     { | ||||||
|         return view('livewire.settings.index'); |         return view('livewire.settings.index'); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function toggleTwoStepConfirmation() | ||||||
|  |     { | ||||||
|  |         $this->settings->disable_two_step_confirmation = true; | ||||||
|  |         $this->settings->save(); | ||||||
|  |         $this->disable_two_step_confirmation = true; | ||||||
|  |         $this->dispatch('success', 'Two step confirmation has been disabled.'); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ class Create extends Component | |||||||
| 
 | 
 | ||||||
|     public function mount() |     public function mount() | ||||||
|     { |     { | ||||||
|         $this->name = generate_random_name(); |         $this->name = substr(generate_random_name(), 0, 34); // GitHub Apps names can only be 34 characters long
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function createGitHubApp() |     public function createGitHubApp() | ||||||
|   | |||||||
| @@ -1400,13 +1400,21 @@ class Application extends BaseModel | |||||||
|         return []; |         return []; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function getMetrics(int $mins = 5) |     public function getCpuMetrics(int $mins = 5) | ||||||
|     { |     { | ||||||
|         $server = $this->destination->server; |         $server = $this->destination->server; | ||||||
|         $container_name = $this->uuid; |         $container_name = $this->uuid; | ||||||
|         if ($server->isMetricsEnabled()) { |         if ($server->isMetricsEnabled()) { | ||||||
|             $from = now()->subMinutes($mins)->toIso8601ZuluString(); |             $from = now()->subMinutes($mins)->toIso8601ZuluString(); | ||||||
|             $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); |             if (isDev() && $server->id === 0) { | ||||||
|  |                 $process = Process::run("curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://host.docker.internal:8888/api/container/{$container_name}/cpu/history?from=$from"); | ||||||
|  |                 if ($process->failed()) { | ||||||
|  |                     throw new \Exception($process->errorOutput()); | ||||||
|  |                 } | ||||||
|  |                 $metrics = $process->output(); | ||||||
|  |             } else { | ||||||
|  |                 $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/history?from=$from'"], $server, false); | ||||||
|  |             } | ||||||
|             if (str($metrics)->contains('error')) { |             if (str($metrics)->contains('error')) { | ||||||
|                 $error = json_decode($metrics, true); |                 $error = json_decode($metrics, true); | ||||||
|                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); |                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); | ||||||
| @@ -1415,14 +1423,41 @@ class Application extends BaseModel | |||||||
|                 } |                 } | ||||||
|                 throw new \Exception($error); |                 throw new \Exception($error); | ||||||
|             } |             } | ||||||
|             $metrics = str($metrics)->explode("\n")->skip(1)->all(); |             $metrics = json_decode($metrics, true); | ||||||
|             $parsedCollection = collect($metrics)->flatMap(function ($item) { |             $parsedCollection = collect($metrics)->map(function ($metric) { | ||||||
|                 return collect(explode("\n", trim($item)))->map(function ($line) { |                 return [(int) $metric['time'], (float) $metric['percent']]; | ||||||
|                     [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); |  | ||||||
|                     $cpu_usage_percent = number_format($cpu_usage_percent, 2); |  | ||||||
| 
 |  | ||||||
|                     return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; |  | ||||||
|             }); |             }); | ||||||
|  | 
 | ||||||
|  |             return $parsedCollection->toArray(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function getMemoryMetrics(int $mins = 5) | ||||||
|  |     { | ||||||
|  |         $server = $this->destination->server; | ||||||
|  |         $container_name = $this->uuid; | ||||||
|  |         if ($server->isMetricsEnabled()) { | ||||||
|  |             $from = now()->subMinutes($mins)->toIso8601ZuluString(); | ||||||
|  |             if (isDev() && $server->id === 0) { | ||||||
|  |                 $process = Process::run("curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://host.docker.internal:8888/api/container/{$container_name}/memory/history?from=$from"); | ||||||
|  |                 if ($process->failed()) { | ||||||
|  |                     throw new \Exception($process->errorOutput()); | ||||||
|  |                 } | ||||||
|  |                 $metrics = $process->output(); | ||||||
|  |             } else { | ||||||
|  |                 $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/history?from=$from'"], $server, false); | ||||||
|  |             } | ||||||
|  |             if (str($metrics)->contains('error')) { | ||||||
|  |                 $error = json_decode($metrics, true); | ||||||
|  |                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); | ||||||
|  |                 if ($error == 'Unauthorized') { | ||||||
|  |                     $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; | ||||||
|  |                 } | ||||||
|  |                 throw new \Exception($error); | ||||||
|  |             } | ||||||
|  |             $metrics = json_decode($metrics, true); | ||||||
|  |             $parsedCollection = collect($metrics)->map(function ($metric) { | ||||||
|  |                 return [(int) $metric['time'], (float) $metric['used']]; | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|             return $parsedCollection->toArray(); |             return $parsedCollection->toArray(); | ||||||
| @@ -1459,7 +1494,9 @@ class Application extends BaseModel | |||||||
| 
 | 
 | ||||||
|         return $config; |         return $config; | ||||||
|     } |     } | ||||||
|     public function setConfig($config) { | 
 | ||||||
|  |     public function setConfig($config) | ||||||
|  |     { | ||||||
| 
 | 
 | ||||||
|         $config = $config; |         $config = $config; | ||||||
|         $validator = Validator::make(['config' => $config], [ |         $validator = Validator::make(['config' => $config], [ | ||||||
|   | |||||||
| @@ -44,7 +44,7 @@ class EnvironmentVariable extends Model | |||||||
|         'version' => 'string', |         'version' => 'string', | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     protected $appends = ['real_value', 'is_shared']; |     protected $appends = ['real_value', 'is_shared', 'is_really_required']; | ||||||
| 
 | 
 | ||||||
|     protected static function booted() |     protected static function booted() | ||||||
|     { |     { | ||||||
| @@ -74,6 +74,9 @@ class EnvironmentVariable extends Model | |||||||
|                 'version' => config('version'), |                 'version' => config('version'), | ||||||
|             ]); |             ]); | ||||||
|         }); |         }); | ||||||
|  |         static::saving(function (EnvironmentVariable $environmentVariable) { | ||||||
|  |             $environmentVariable->updateIsShared(); | ||||||
|  |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function service() |     public function service() | ||||||
| @@ -130,6 +133,13 @@ class EnvironmentVariable extends Model | |||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     protected function isReallyRequired(): Attribute | ||||||
|  |     { | ||||||
|  |         return Attribute::make( | ||||||
|  |             get: fn () => $this->is_required && str($this->real_value)->isEmpty(), | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     protected function isShared(): Attribute |     protected function isShared(): Attribute | ||||||
|     { |     { | ||||||
|         return Attribute::make( |         return Attribute::make( | ||||||
| @@ -210,4 +220,11 @@ class EnvironmentVariable extends Model | |||||||
|             set: fn (string $value) => str($value)->trim()->replace(' ', '_')->value, |             set: fn (string $value) => str($value)->trim()->replace(' ', '_')->value, | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     protected function updateIsShared(): void | ||||||
|  |     { | ||||||
|  |         $type = str($this->value)->after('{{')->before('.')->value; | ||||||
|  |         $isShared = str($this->value)->startsWith('{{'.$type) && str($this->value)->endsWith('}}'); | ||||||
|  |         $this->is_shared = $isShared; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Models; | namespace App\Models; | ||||||
| 
 | 
 | ||||||
|  | use App\Jobs\PullHelperImageJob; | ||||||
| use App\Notifications\Channels\SendsEmail; | use App\Notifications\Channels\SendsEmail; | ||||||
| use Illuminate\Database\Eloquent\Casts\Attribute; | use Illuminate\Database\Eloquent\Casts\Attribute; | ||||||
| use Illuminate\Database\Eloquent\Model; | use Illuminate\Database\Eloquent\Model; | ||||||
| @@ -21,8 +22,23 @@ class InstanceSettings extends Model implements SendsEmail | |||||||
|         'is_auto_update_enabled' => 'boolean', |         'is_auto_update_enabled' => 'boolean', | ||||||
|         'auto_update_frequency' => 'string', |         'auto_update_frequency' => 'string', | ||||||
|         'update_check_frequency' => 'string', |         'update_check_frequency' => 'string', | ||||||
|  |         'sentinel_token' => 'encrypted', | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|  |     protected static function booted(): void | ||||||
|  |     { | ||||||
|  |         static::updated(function ($settings) { | ||||||
|  |             if ($settings->isDirty('helper_version')) { | ||||||
|  |                 Server::chunkById(100, function ($servers) { | ||||||
|  |                     foreach ($servers as $server) { | ||||||
|  |                         PullHelperImageJob::dispatch($server); | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function fqdn(): Attribute |     public function fqdn(): Attribute | ||||||
|     { |     { | ||||||
|         return Attribute::make( |         return Attribute::make( | ||||||
| @@ -85,17 +101,4 @@ class InstanceSettings extends Model implements SendsEmail | |||||||
| 
 | 
 | ||||||
|         return "[{$instanceName}]"; |         return "[{$instanceName}]"; | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     public function helperVersion(): Attribute |  | ||||||
|     { |  | ||||||
|         return Attribute::make( |  | ||||||
|             get: function ($value) { |  | ||||||
|                 if (isDev()) { |  | ||||||
|                     return 'latest'; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 return $value; |  | ||||||
|             } |  | ||||||
|         ); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -51,7 +51,6 @@ class ScheduledDatabaseBackup extends BaseModel | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|         return null; |         return null; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,6 +7,8 @@ use App\Enums\ProxyTypes; | |||||||
| use App\Jobs\PullSentinelImageJob; | use App\Jobs\PullSentinelImageJob; | ||||||
| use Illuminate\Database\Eloquent\Builder; | use Illuminate\Database\Eloquent\Builder; | ||||||
| use Illuminate\Database\Eloquent\Casts\Attribute; | use Illuminate\Database\Eloquent\Casts\Attribute; | ||||||
|  | use Illuminate\Database\Eloquent\SoftDeletes; | ||||||
|  | use Illuminate\Support\Carbon; | ||||||
| use Illuminate\Support\Collection; | use Illuminate\Support\Collection; | ||||||
| use Illuminate\Support\Facades\DB; | use Illuminate\Support\Facades\DB; | ||||||
| use Illuminate\Support\Facades\Process; | use Illuminate\Support\Facades\Process; | ||||||
| @@ -43,7 +45,7 @@ use Symfony\Component\Yaml\Yaml; | |||||||
| 
 | 
 | ||||||
| class Server extends BaseModel | class Server extends BaseModel | ||||||
| { | { | ||||||
|     use SchemalessAttributesTrait; |     use SchemalessAttributesTrait,SoftDeletes; | ||||||
| 
 | 
 | ||||||
|     public static $batch_counter = 0; |     public static $batch_counter = 0; | ||||||
| 
 | 
 | ||||||
| @@ -95,7 +97,8 @@ class Server extends BaseModel | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|         static::deleting(function ($server) { | 
 | ||||||
|  |         static::forceDeleting(function ($server) { | ||||||
|             $server->destinations()->each(function ($destination) { |             $server->destinations()->each(function ($destination) { | ||||||
|                 $destination->delete(); |                 $destination->delete(); | ||||||
|             }); |             }); | ||||||
| @@ -525,9 +528,20 @@ $schema://$host { | |||||||
|         Storage::disk('ssh-mux')->delete($this->muxFilename()); |         Storage::disk('ssh-mux')->delete($this->muxFilename()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function sentinelHeartbeat(bool $isReset = false) | ||||||
|  |     { | ||||||
|  |         $this->sentinel_updated_at = $isReset ? now()->subMinutes(6000) : now(); | ||||||
|  |         $this->save(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function isSentinelLive() | ||||||
|  |     { | ||||||
|  |         return Carbon::parse($this->sentinel_updated_at)->isAfter(now()->subMinutes(4)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function isSentinelEnabled() |     public function isSentinelEnabled() | ||||||
|     { |     { | ||||||
|         return $this->isMetricsEnabled() || $this->isServerApiEnabled(); |         return ($this->isMetricsEnabled() || $this->isServerApiEnabled()) && ! $this->isBuildServer(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function isMetricsEnabled() |     public function isMetricsEnabled() | ||||||
| @@ -537,7 +551,7 @@ $schema://$host { | |||||||
| 
 | 
 | ||||||
|     public function isServerApiEnabled() |     public function isServerApiEnabled() | ||||||
|     { |     { | ||||||
|         return $this->settings->is_server_api_enabled; |         return $this->settings->is_sentinel_enabled; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function checkServerApi() |     public function checkServerApi() | ||||||
| @@ -555,7 +569,6 @@ $schema://$host { | |||||||
|                 ray($process->exitCode(), $process->output(), $process->errorOutput()); |                 ray($process->exitCode(), $process->output(), $process->errorOutput()); | ||||||
|                 throw new \Exception("Server API is not reachable on http://{$server_ip}:12172"); |                 throw new \Exception("Server API is not reachable on http://{$server_ip}:12172"); | ||||||
|             } |             } | ||||||
| 
 |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -579,7 +592,15 @@ $schema://$host { | |||||||
|     { |     { | ||||||
|         if ($this->isMetricsEnabled()) { |         if ($this->isMetricsEnabled()) { | ||||||
|             $from = now()->subMinutes($mins)->toIso8601ZuluString(); |             $from = now()->subMinutes($mins)->toIso8601ZuluString(); | ||||||
|             $cpu = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->metrics_token}\" http://localhost:8888/api/cpu/history?from=$from'"], $this, false); |             if (isDev() && $this->id === 0) { | ||||||
|  |                 $process = Process::run("curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://host.docker.internal:8888/api/cpu/history?from=$from"); | ||||||
|  |                 if ($process->failed()) { | ||||||
|  |                     throw new \Exception($process->errorOutput()); | ||||||
|  |                 } | ||||||
|  |                 $cpu = $process->output(); | ||||||
|  |             } else { | ||||||
|  |                 $cpu = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://localhost:8888/api/cpu/history?from=$from'"], $this, false); | ||||||
|  |             } | ||||||
|             if (str($cpu)->contains('error')) { |             if (str($cpu)->contains('error')) { | ||||||
|                 $error = json_decode($cpu, true); |                 $error = json_decode($cpu, true); | ||||||
|                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); |                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); | ||||||
| @@ -588,17 +609,13 @@ $schema://$host { | |||||||
|                 } |                 } | ||||||
|                 throw new \Exception($error); |                 throw new \Exception($error); | ||||||
|             } |             } | ||||||
|             $cpu = str($cpu)->explode("\n")->skip(1)->all(); |             $cpu = json_decode($cpu, true); | ||||||
|             $parsedCollection = collect($cpu)->flatMap(function ($item) { |             $parsedCollection = collect($cpu)->map(function ($metric) { | ||||||
|                 return collect(explode("\n", trim($item)))->map(function ($line) { |                 return [(int) $metric['time'], (float) $metric['percent']]; | ||||||
|                     [$time, $cpu_usage_percent] = explode(',', trim($line)); |  | ||||||
|                     $cpu_usage_percent = number_format($cpu_usage_percent, 0); |  | ||||||
| 
 |  | ||||||
|                     return [(int) $time, (float) $cpu_usage_percent]; |  | ||||||
|                 }); |  | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|             return $parsedCollection->toArray(); |             return $parsedCollection; | ||||||
|  | 
 | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -606,7 +623,15 @@ $schema://$host { | |||||||
|     { |     { | ||||||
|         if ($this->isMetricsEnabled()) { |         if ($this->isMetricsEnabled()) { | ||||||
|             $from = now()->subMinutes($mins)->toIso8601ZuluString(); |             $from = now()->subMinutes($mins)->toIso8601ZuluString(); | ||||||
|             $memory = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->metrics_token}\" http://localhost:8888/api/memory/history?from=$from'"], $this, false); |             if (isDev() && $this->id === 0) { | ||||||
|  |                 $process = Process::run("curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://host.docker.internal:8888/api/memory/history?from=$from"); | ||||||
|  |                 if ($process->failed()) { | ||||||
|  |                     throw new \Exception($process->errorOutput()); | ||||||
|  |                 } | ||||||
|  |                 $memory = $process->output(); | ||||||
|  |             } else { | ||||||
|  |                 $memory = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://localhost:8888/api/memory/history?from=$from'"], $this, false); | ||||||
|  |             } | ||||||
|             if (str($memory)->contains('error')) { |             if (str($memory)->contains('error')) { | ||||||
|                 $error = json_decode($memory, true); |                 $error = json_decode($memory, true); | ||||||
|                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); |                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); | ||||||
| @@ -615,14 +640,9 @@ $schema://$host { | |||||||
|                 } |                 } | ||||||
|                 throw new \Exception($error); |                 throw new \Exception($error); | ||||||
|             } |             } | ||||||
|             $memory = str($memory)->explode("\n")->skip(1)->all(); |             $memory = json_decode($memory, true); | ||||||
|             $parsedCollection = collect($memory)->flatMap(function ($item) { |             $parsedCollection = collect($memory)->map(function ($metric) { | ||||||
|                 return collect(explode("\n", trim($item)))->map(function ($line) { |                 return [(int) $metric['time'], (float) $metric['usedPercent']]; | ||||||
|                     [$time, $used, $free, $usedPercent] = explode(',', trim($line)); |  | ||||||
|                     $usedPercent = number_format($usedPercent, 0); |  | ||||||
| 
 |  | ||||||
|                     return [(int) $time, (float) $usedPercent]; |  | ||||||
|                 }); |  | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|             return $parsedCollection->toArray(); |             return $parsedCollection->toArray(); | ||||||
| @@ -977,7 +997,8 @@ $schema://$host { | |||||||
| 
 | 
 | ||||||
|     public function isProxyShouldRun() |     public function isProxyShouldRun() | ||||||
|     { |     { | ||||||
|         if ($this->proxyType() === ProxyTypes::NONE->value || $this->settings->is_build_server) { |         // TODO: Do we need "|| $this->proxy->force_stop" here?
 | ||||||
|  |         if ($this->proxyType() === ProxyTypes::NONE->value || $this->isBuildServer()) { | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @@ -1041,6 +1062,38 @@ $schema://$host { | |||||||
|         return data_get($this, 'settings.is_swarm_worker'); |         return data_get($this, 'settings.is_swarm_worker'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function status(): bool | ||||||
|  |     { | ||||||
|  |         ['uptime' => $uptime] = $this->validateConnection(false); | ||||||
|  |         if ($uptime) { | ||||||
|  |             if ($this->unreachable_notification_sent === true) { | ||||||
|  |                 $this->update(['unreachable_notification_sent' => false]); | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             // $this->server->team?->notify(new Unreachable($this->server));
 | ||||||
|  |             foreach ($this->applications as $application) { | ||||||
|  |                 $application->update(['status' => 'exited']); | ||||||
|  |             } | ||||||
|  |             foreach ($this->databases as $database) { | ||||||
|  |                 $database->update(['status' => 'exited']); | ||||||
|  |             } | ||||||
|  |             foreach ($this->services as $service) { | ||||||
|  |                 $apps = $service->applications()->get(); | ||||||
|  |                 $dbs = $service->databases()->get(); | ||||||
|  |                 foreach ($apps as $app) { | ||||||
|  |                     $app->update(['status' => 'exited']); | ||||||
|  |                 } | ||||||
|  |                 foreach ($dbs as $db) { | ||||||
|  |                     $db->update(['status' => 'exited']); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function validateConnection($isManualCheck = true) |     public function validateConnection($isManualCheck = true) | ||||||
|     { |     { | ||||||
|         config()->set('constants.ssh.mux_enabled', ! $isManualCheck); |         config()->set('constants.ssh.mux_enabled', ! $isManualCheck); | ||||||
|   | |||||||
| @@ -24,7 +24,7 @@ use OpenApi\Attributes as OA; | |||||||
|         'is_logdrain_newrelic_enabled' => ['type' => 'boolean'], |         'is_logdrain_newrelic_enabled' => ['type' => 'boolean'], | ||||||
|         'is_metrics_enabled' => ['type' => 'boolean'], |         'is_metrics_enabled' => ['type' => 'boolean'], | ||||||
|         'is_reachable' => ['type' => 'boolean'], |         'is_reachable' => ['type' => 'boolean'], | ||||||
|         'is_server_api_enabled' => ['type' => 'boolean'], |         'is_sentinel_enabled' => ['type' => 'boolean'], | ||||||
|         'is_swarm_manager' => ['type' => 'boolean'], |         'is_swarm_manager' => ['type' => 'boolean'], | ||||||
|         'is_swarm_worker' => ['type' => 'boolean'], |         'is_swarm_worker' => ['type' => 'boolean'], | ||||||
|         'is_usable' => ['type' => 'boolean'], |         'is_usable' => ['type' => 'boolean'], | ||||||
| @@ -35,9 +35,9 @@ use OpenApi\Attributes as OA; | |||||||
|         'logdrain_highlight_project_id' => ['type' => 'string'], |         'logdrain_highlight_project_id' => ['type' => 'string'], | ||||||
|         'logdrain_newrelic_base_uri' => ['type' => 'string'], |         'logdrain_newrelic_base_uri' => ['type' => 'string'], | ||||||
|         'logdrain_newrelic_license_key' => ['type' => 'string'], |         'logdrain_newrelic_license_key' => ['type' => 'string'], | ||||||
|         'metrics_history_days' => ['type' => 'integer'], |         'sentinel_metrics_history_days' => ['type' => 'integer'], | ||||||
|         'metrics_refresh_rate_seconds' => ['type' => 'integer'], |         'sentinel_metrics_refresh_rate_seconds' => ['type' => 'integer'], | ||||||
|         'metrics_token' => ['type' => 'string'], |         'sentinel_token' => ['type' => 'string'], | ||||||
|         'docker_cleanup_frequency' => ['type' => 'string'], |         'docker_cleanup_frequency' => ['type' => 'string'], | ||||||
|         'docker_cleanup_threshold' => ['type' => 'integer'], |         'docker_cleanup_threshold' => ['type' => 'integer'], | ||||||
|         'server_id' => ['type' => 'integer'], |         'server_id' => ['type' => 'integer'], | ||||||
| @@ -53,8 +53,66 @@ class ServerSetting extends Model | |||||||
|     protected $casts = [ |     protected $casts = [ | ||||||
|         'force_docker_cleanup' => 'boolean', |         'force_docker_cleanup' => 'boolean', | ||||||
|         'docker_cleanup_threshold' => 'integer', |         'docker_cleanup_threshold' => 'integer', | ||||||
|  |         'sentinel_token' => 'encrypted', | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|  |     protected static function booted() | ||||||
|  |     { | ||||||
|  |         static::creating(function ($setting) { | ||||||
|  |             try { | ||||||
|  |                 if (str($setting->sentinel_token)->isEmpty()) { | ||||||
|  |                     $setting->generateSentinelToken(save: false); | ||||||
|  |                 } | ||||||
|  |                 if (str($setting->sentinel_custom_url)->isEmpty()) { | ||||||
|  |                     $url = $setting->generateSentinelUrl(save: false); | ||||||
|  |                     if (str($url)->isEmpty()) { | ||||||
|  |                         $setting->is_sentinel_enabled = false; | ||||||
|  |                     } else { | ||||||
|  |                         $setting->is_sentinel_enabled = true; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } catch (\Throwable $e) { | ||||||
|  |                 loggy('Error creating server setting: '.$e->getMessage()); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function generateSentinelToken(bool $save = true) | ||||||
|  |     { | ||||||
|  |         $data = [ | ||||||
|  |             'server_uuid' => $this->server->uuid, | ||||||
|  |         ]; | ||||||
|  |         $token = json_encode($data); | ||||||
|  |         $encrypted = encrypt($token); | ||||||
|  |         $this->sentinel_token = $encrypted; | ||||||
|  |         if ($save) { | ||||||
|  |             $this->save(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return $encrypted; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function generateSentinelUrl(bool $save = true) | ||||||
|  |     { | ||||||
|  |         $domain = null; | ||||||
|  |         $settings = InstanceSettings::get(); | ||||||
|  |         if ($this->server->isLocalhost()) { | ||||||
|  |             $domain = 'http://host.docker.internal:8000'; | ||||||
|  |         } elseif ($settings->fqdn) { | ||||||
|  |             $domain = $settings->fqdn; | ||||||
|  |         } elseif ($settings->ipv4) { | ||||||
|  |             $domain = $settings->ipv4.':8000'; | ||||||
|  |         } elseif ($settings->ipv6) { | ||||||
|  |             $domain = $settings->ipv6.':8000'; | ||||||
|  |         } | ||||||
|  |         $this->sentinel_custom_url = $domain; | ||||||
|  |         if ($save) { | ||||||
|  |             $this->save(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return $domain; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function server() |     public function server() | ||||||
|     { |     { | ||||||
|         return $this->belongsTo(Server::class); |         return $this->belongsTo(Server::class); | ||||||
|   | |||||||
| @@ -297,7 +297,7 @@ class Service extends BaseModel | |||||||
|                                 'key' => 'CP_DISABLE_HTTPS', |                                 'key' => 'CP_DISABLE_HTTPS', | ||||||
|                                 'value' => data_get($disable_https, 'value'), |                                 'value' => data_get($disable_https, 'value'), | ||||||
|                                 'rules' => 'required', |                                 'rules' => 'required', | ||||||
|                                 'customHelper' => "If you want to use https, set this to 0. Variable name: CP_DISABLE_HTTPS", |                                 'customHelper' => 'If you want to use https, set this to 0. Variable name: CP_DISABLE_HTTPS', | ||||||
|                             ], |                             ], | ||||||
|                         ]); |                         ]); | ||||||
|                     } |                     } | ||||||
| @@ -1232,7 +1232,6 @@ class Service extends BaseModel | |||||||
| 
 | 
 | ||||||
|     public function environment_variables(): HasMany |     public function environment_variables(): HasMany | ||||||
|     { |     { | ||||||
| 
 |  | ||||||
|         return $this->hasMany(EnvironmentVariable::class)->orderByRaw("LOWER(key) LIKE LOWER('SERVICE%') DESC, LOWER(key) ASC"); |         return $this->hasMany(EnvironmentVariable::class)->orderByRaw("LOWER(key) LIKE LOWER('SERVICE%') DESC, LOWER(key) ASC"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -1316,4 +1315,20 @@ class Service extends BaseModel | |||||||
| 
 | 
 | ||||||
|         return $networks; |         return $networks; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     protected function isDeployable(): Attribute | ||||||
|  |     { | ||||||
|  |         return Attribute::make( | ||||||
|  |             get: function () { | ||||||
|  |                 $envs = $this->environment_variables()->where('is_required', true)->get(); | ||||||
|  |                 foreach ($envs as $env) { | ||||||
|  |                     if ($env->is_really_required) { | ||||||
|  |                         return false; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -272,7 +272,7 @@ class StandaloneClickhouse extends BaseModel | |||||||
|         $container_name = $this->uuid; |         $container_name = $this->uuid; | ||||||
|         if ($server->isMetricsEnabled()) { |         if ($server->isMetricsEnabled()) { | ||||||
|             $from = now()->subMinutes($mins)->toIso8601ZuluString(); |             $from = now()->subMinutes($mins)->toIso8601ZuluString(); | ||||||
|             $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); |             $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); | ||||||
|             if (str($metrics)->contains('error')) { |             if (str($metrics)->contains('error')) { | ||||||
|                 $error = json_decode($metrics, true); |                 $error = json_decode($metrics, true); | ||||||
|                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); |                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); | ||||||
|   | |||||||
| @@ -272,7 +272,7 @@ class StandaloneDragonfly extends BaseModel | |||||||
|         $container_name = $this->uuid; |         $container_name = $this->uuid; | ||||||
|         if ($server->isMetricsEnabled()) { |         if ($server->isMetricsEnabled()) { | ||||||
|             $from = now()->subMinutes($mins)->toIso8601ZuluString(); |             $from = now()->subMinutes($mins)->toIso8601ZuluString(); | ||||||
|             $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); |             $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); | ||||||
|             if (str($metrics)->contains('error')) { |             if (str($metrics)->contains('error')) { | ||||||
|                 $error = json_decode($metrics, true); |                 $error = json_decode($metrics, true); | ||||||
|                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); |                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); | ||||||
|   | |||||||
| @@ -272,7 +272,7 @@ class StandaloneKeydb extends BaseModel | |||||||
|         $container_name = $this->uuid; |         $container_name = $this->uuid; | ||||||
|         if ($server->isMetricsEnabled()) { |         if ($server->isMetricsEnabled()) { | ||||||
|             $from = now()->subMinutes($mins)->toIso8601ZuluString(); |             $from = now()->subMinutes($mins)->toIso8601ZuluString(); | ||||||
|             $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); |             $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); | ||||||
|             if (str($metrics)->contains('error')) { |             if (str($metrics)->contains('error')) { | ||||||
|                 $error = json_decode($metrics, true); |                 $error = json_decode($metrics, true); | ||||||
|                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); |                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); | ||||||
|   | |||||||
| @@ -272,7 +272,7 @@ class StandaloneMariadb extends BaseModel | |||||||
|         $container_name = $this->uuid; |         $container_name = $this->uuid; | ||||||
|         if ($server->isMetricsEnabled()) { |         if ($server->isMetricsEnabled()) { | ||||||
|             $from = now()->subMinutes($mins)->toIso8601ZuluString(); |             $from = now()->subMinutes($mins)->toIso8601ZuluString(); | ||||||
|             $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); |             $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); | ||||||
|             if (str($metrics)->contains('error')) { |             if (str($metrics)->contains('error')) { | ||||||
|                 $error = json_decode($metrics, true); |                 $error = json_decode($metrics, true); | ||||||
|                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); |                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); | ||||||
|   | |||||||
| @@ -292,7 +292,7 @@ class StandaloneMongodb extends BaseModel | |||||||
|         $container_name = $this->uuid; |         $container_name = $this->uuid; | ||||||
|         if ($server->isMetricsEnabled()) { |         if ($server->isMetricsEnabled()) { | ||||||
|             $from = now()->subMinutes($mins)->toIso8601ZuluString(); |             $from = now()->subMinutes($mins)->toIso8601ZuluString(); | ||||||
|             $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); |             $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); | ||||||
|             if (str($metrics)->contains('error')) { |             if (str($metrics)->contains('error')) { | ||||||
|                 $error = json_decode($metrics, true); |                 $error = json_decode($metrics, true); | ||||||
|                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); |                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); | ||||||
|   | |||||||
| @@ -273,7 +273,7 @@ class StandaloneMysql extends BaseModel | |||||||
|         $container_name = $this->uuid; |         $container_name = $this->uuid; | ||||||
|         if ($server->isMetricsEnabled()) { |         if ($server->isMetricsEnabled()) { | ||||||
|             $from = now()->subMinutes($mins)->toIso8601ZuluString(); |             $from = now()->subMinutes($mins)->toIso8601ZuluString(); | ||||||
|             $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); |             $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); | ||||||
|             if (str($metrics)->contains('error')) { |             if (str($metrics)->contains('error')) { | ||||||
|                 $error = json_decode($metrics, true); |                 $error = json_decode($metrics, true); | ||||||
|                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); |                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); | ||||||
|   | |||||||
| @@ -274,7 +274,7 @@ class StandalonePostgresql extends BaseModel | |||||||
|         $container_name = $this->uuid; |         $container_name = $this->uuid; | ||||||
|         if ($server->isMetricsEnabled()) { |         if ($server->isMetricsEnabled()) { | ||||||
|             $from = now()->subMinutes($mins)->toIso8601ZuluString(); |             $from = now()->subMinutes($mins)->toIso8601ZuluString(); | ||||||
|             $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); |             $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); | ||||||
|             if (str($metrics)->contains('error')) { |             if (str($metrics)->contains('error')) { | ||||||
|                 $error = json_decode($metrics, true); |                 $error = json_decode($metrics, true); | ||||||
|                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); |                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); | ||||||
|   | |||||||
| @@ -210,7 +210,12 @@ class StandaloneRedis extends BaseModel | |||||||
|     protected function internalDbUrl(): Attribute |     protected function internalDbUrl(): Attribute | ||||||
|     { |     { | ||||||
|         return new Attribute( |         return new Attribute( | ||||||
|             get: fn () => "redis://:{$this->redis_password}@{$this->uuid}:6379/0", |             get: function () { | ||||||
|  |                 $redis_version = $this->getRedisVersion(); | ||||||
|  |                 $username_part = version_compare($redis_version, '6.0', '>=') ? "{$this->redis_username}:" : ''; | ||||||
|  | 
 | ||||||
|  |                 return "redis://{$username_part}{$this->redis_password}@{$this->uuid}:6379/0"; | ||||||
|  |             } | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -219,7 +224,10 @@ class StandaloneRedis extends BaseModel | |||||||
|         return new Attribute( |         return new Attribute( | ||||||
|             get: function () { |             get: function () { | ||||||
|                 if ($this->is_public && $this->public_port) { |                 if ($this->is_public && $this->public_port) { | ||||||
|                     return "redis://:{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0"; |                     $redis_version = $this->getRedisVersion(); | ||||||
|  |                     $username_part = version_compare($redis_version, '6.0', '>=') ? "{$this->redis_username}:" : ''; | ||||||
|  | 
 | ||||||
|  |                     return "redis://{$username_part}{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0"; | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 return null; |                 return null; | ||||||
| @@ -227,6 +235,13 @@ class StandaloneRedis extends BaseModel | |||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function getRedisVersion() | ||||||
|  |     { | ||||||
|  |         $image_parts = explode(':', $this->image); | ||||||
|  | 
 | ||||||
|  |         return $image_parts[1] ?? '0.0'; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function environment() |     public function environment() | ||||||
|     { |     { | ||||||
|         return $this->belongsTo(Environment::class); |         return $this->belongsTo(Environment::class); | ||||||
| @@ -268,7 +283,7 @@ class StandaloneRedis extends BaseModel | |||||||
|         $container_name = $this->uuid; |         $container_name = $this->uuid; | ||||||
|         if ($server->isMetricsEnabled()) { |         if ($server->isMetricsEnabled()) { | ||||||
|             $from = now()->subMinutes($mins)->toIso8601ZuluString(); |             $from = now()->subMinutes($mins)->toIso8601ZuluString(); | ||||||
|             $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); |             $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); | ||||||
|             if (str($metrics)->contains('error')) { |             if (str($metrics)->contains('error')) { | ||||||
|                 $error = json_decode($metrics, true); |                 $error = json_decode($metrics, true); | ||||||
|                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); |                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); | ||||||
| @@ -295,4 +310,33 @@ class StandaloneRedis extends BaseModel | |||||||
|     { |     { | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function redisPassword(): Attribute | ||||||
|  |     { | ||||||
|  |         return new Attribute( | ||||||
|  |             get: function () { | ||||||
|  |                 $password = $this->runtime_environment_variables()->where('key', 'REDIS_PASSWORD')->first(); | ||||||
|  |                 if (! $password) { | ||||||
|  |                     return null; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 return $password->value; | ||||||
|  |             }, | ||||||
|  | 
 | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function redisUsername(): Attribute | ||||||
|  |     { | ||||||
|  |         return new Attribute( | ||||||
|  |             get: function () { | ||||||
|  |                 $username = $this->runtime_environment_variables()->where('key', 'REDIS_USERNAME')->first(); | ||||||
|  |                 if (! $username) { | ||||||
|  |                     return null; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 return $username->value; | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| <?php | <?php | ||||||
| 
 | 
 | ||||||
|  | use App\Models\EnvironmentVariable; | ||||||
| use App\Models\Server; | use App\Models\Server; | ||||||
| use App\Models\StandaloneClickhouse; | use App\Models\StandaloneClickhouse; | ||||||
| use App\Models\StandaloneDocker; | use App\Models\StandaloneDocker; | ||||||
| @@ -48,7 +49,7 @@ function create_standalone_redis($environment_id, $destination_uuid, ?array $oth | |||||||
|     } |     } | ||||||
|     $database = new StandaloneRedis; |     $database = new StandaloneRedis; | ||||||
|     $database->name = generate_database_name('redis'); |     $database->name = generate_database_name('redis'); | ||||||
|     $database->redis_password = \Illuminate\Support\Str::password(length: 64, symbols: false); |     $redis_password = \Illuminate\Support\Str::password(length: 64, symbols: false); | ||||||
|     $database->environment_id = $environment_id; |     $database->environment_id = $environment_id; | ||||||
|     $database->destination_id = $destination->id; |     $database->destination_id = $destination->id; | ||||||
|     $database->destination_type = $destination->getMorphClass(); |     $database->destination_type = $destination->getMorphClass(); | ||||||
| @@ -57,6 +58,20 @@ function create_standalone_redis($environment_id, $destination_uuid, ?array $oth | |||||||
|     } |     } | ||||||
|     $database->save(); |     $database->save(); | ||||||
| 
 | 
 | ||||||
|  |     EnvironmentVariable::create([ | ||||||
|  |         'key' => 'REDIS_PASSWORD', | ||||||
|  |         'value' => $redis_password, | ||||||
|  |         'standalone_redis_id' => $database->id, | ||||||
|  |         'is_shared' => false, | ||||||
|  |     ]); | ||||||
|  | 
 | ||||||
|  |     EnvironmentVariable::create([ | ||||||
|  |         'key' => 'REDIS_USERNAME', | ||||||
|  |         'value' => 'default', | ||||||
|  |         'standalone_redis_id' => $database->id, | ||||||
|  |         'is_shared' => false, | ||||||
|  |     ]); | ||||||
|  | 
 | ||||||
|     return $database; |     return $database; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -335,6 +335,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ | |||||||
|             if (preg_match('/coolify\.traefik\.middlewares=(.*)/', $item, $matches)) { |             if (preg_match('/coolify\.traefik\.middlewares=(.*)/', $item, $matches)) { | ||||||
|                 return explode(',', $matches[1]); |                 return explode(',', $matches[1]); | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|             return null; |             return null; | ||||||
|         })->flatten() |         })->flatten() | ||||||
|             ->filter() |             ->filter() | ||||||
|   | |||||||
| @@ -241,9 +241,11 @@ function generate_default_proxy_configuration(Server $server) | |||||||
|                     'ports' => [ |                     'ports' => [ | ||||||
|                         '80:80', |                         '80:80', | ||||||
|                         '443:443', |                         '443:443', | ||||||
|  |                         '443:443/udp', | ||||||
|                     ], |                     ], | ||||||
|                     'labels' => [ |                     'labels' => [ | ||||||
|                         'coolify.managed=true', |                         'coolify.managed=true', | ||||||
|  |                         'coolify.proxy=true', | ||||||
|                     ], |                     ], | ||||||
|                     'volumes' => [ |                     'volumes' => [ | ||||||
|                         '/var/run/docker.sock:/var/run/docker.sock:ro', |                         '/var/run/docker.sock:/var/run/docker.sock:ro', | ||||||
|   | |||||||
| @@ -126,7 +126,7 @@ function refreshSession(?Team $team = null): void | |||||||
| } | } | ||||||
| function handleError(?Throwable $error = null, ?Livewire\Component $livewire = null, ?string $customErrorMessage = null) | function handleError(?Throwable $error = null, ?Livewire\Component $livewire = null, ?string $customErrorMessage = null) | ||||||
| { | { | ||||||
|     ray($error); |     loggy($error); | ||||||
|     if ($error instanceof TooManyRequestsException) { |     if ($error instanceof TooManyRequestsException) { | ||||||
|         if (isset($livewire)) { |         if (isset($livewire)) { | ||||||
|             return $livewire->dispatch('error', "Too many requests. Please try again in {$error->secondsUntilAvailable} seconds."); |             return $livewire->dispatch('error', "Too many requests. Please try again in {$error->secondsUntilAvailable} seconds."); | ||||||
| @@ -142,6 +142,10 @@ function handleError(?Throwable $error = null, ?Livewire\Component $livewire = n | |||||||
|         return 'Duplicate entry found. Please use a different name.'; |         return 'Duplicate entry found. Please use a different name.'; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if ($error instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) { | ||||||
|  |         abort(404); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     if ($error instanceof Throwable) { |     if ($error instanceof Throwable) { | ||||||
|         $message = $error->getMessage(); |         $message = $error->getMessage(); | ||||||
|     } else { |     } else { | ||||||
| @@ -164,10 +168,10 @@ function get_route_parameters(): array | |||||||
| function get_latest_sentinel_version(): string | function get_latest_sentinel_version(): string | ||||||
| { | { | ||||||
|     try { |     try { | ||||||
|         $response = Http::get('https://cdn.coollabs.io/sentinel/versions.json'); |         $response = Http::get('https://cdn.coollabs.io/coolify/versions.json'); | ||||||
|         $versions = $response->json(); |         $versions = $response->json(); | ||||||
| 
 | 
 | ||||||
|         return data_get($versions, 'sentinel.version'); |         return data_get($versions, 'coolify.sentinel.version'); | ||||||
|     } catch (\Throwable $e) { |     } catch (\Throwable $e) { | ||||||
|         //throw $e;
 |         //throw $e;
 | ||||||
|         ray($e->getMessage()); |         ray($e->getMessage()); | ||||||
| @@ -1338,13 +1342,6 @@ function isAnyDeploymentInprogress() | |||||||
|     exit(0); |     exit(0); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function generateSentinelToken() |  | ||||||
| { |  | ||||||
|     $token = Str::random(64); |  | ||||||
| 
 |  | ||||||
|     return $token; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function isBase64Encoded($strValue) | function isBase64Encoded($strValue) | ||||||
| { | { | ||||||
|     return base64_encode(base64_decode($strValue, true)) === $strValue; |     return base64_encode(base64_decode($strValue, true)) === $strValue; | ||||||
| @@ -3569,6 +3566,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int | |||||||
|                 ]); |                 ]); | ||||||
|             } else { |             } else { | ||||||
|                 if ($value->startsWith('$')) { |                 if ($value->startsWith('$')) { | ||||||
|  |                     $isRequired = false; | ||||||
|                     if ($value->contains(':-')) { |                     if ($value->contains(':-')) { | ||||||
|                         $value = replaceVariables($value); |                         $value = replaceVariables($value); | ||||||
|                         $key = $value->before(':'); |                         $key = $value->before(':'); | ||||||
| @@ -3583,11 +3581,13 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int | |||||||
| 
 | 
 | ||||||
|                         $key = $value->before(':'); |                         $key = $value->before(':'); | ||||||
|                         $value = $value->after(':?'); |                         $value = $value->after(':?'); | ||||||
|  |                         $isRequired = true; | ||||||
|                     } elseif ($value->contains('?')) { |                     } elseif ($value->contains('?')) { | ||||||
|                         $value = replaceVariables($value); |                         $value = replaceVariables($value); | ||||||
| 
 | 
 | ||||||
|                         $key = $value->before('?'); |                         $key = $value->before('?'); | ||||||
|                         $value = $value->after('?'); |                         $value = $value->after('?'); | ||||||
|  |                         $isRequired = true; | ||||||
|                     } |                     } | ||||||
|                     if ($originalValue->value() === $value->value()) { |                     if ($originalValue->value() === $value->value()) { | ||||||
|                         // This means the variable does not have a default value, so it needs to be created in Coolify
 |                         // This means the variable does not have a default value, so it needs to be created in Coolify
 | ||||||
| @@ -3598,6 +3598,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int | |||||||
|                         ], [ |                         ], [ | ||||||
|                             'is_build_time' => false, |                             'is_build_time' => false, | ||||||
|                             'is_preview' => false, |                             'is_preview' => false, | ||||||
|  |                             'is_required' => $isRequired, | ||||||
|                         ]); |                         ]); | ||||||
|                         // Add the variable to the environment so it will be shown in the deployable compose file
 |                         // Add the variable to the environment so it will be shown in the deployable compose file
 | ||||||
|                         $environment[$parsedKeyValue->value()] = $resource->environment_variables()->where('key', $parsedKeyValue)->where($nameOfId, $resource->id)->first()->value; |                         $environment[$parsedKeyValue->value()] = $resource->environment_variables()->where('key', $parsedKeyValue)->where($nameOfId, $resource->id)->first()->value; | ||||||
| @@ -3611,6 +3612,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int | |||||||
|                         'value' => $value, |                         'value' => $value, | ||||||
|                         'is_build_time' => false, |                         'is_build_time' => false, | ||||||
|                         'is_preview' => false, |                         'is_preview' => false, | ||||||
|  |                         'is_required' => $isRequired, | ||||||
|                     ]); |                     ]); | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
| @@ -3787,7 +3789,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int | |||||||
|                     service_name: $serviceName, |                     service_name: $serviceName, | ||||||
|                     image: $image, |                     image: $image, | ||||||
|                     predefinedPort: $predefinedPort |                     predefinedPort: $predefinedPort | ||||||
| 
 |  | ||||||
|                 )); |                 )); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -3985,13 +3986,14 @@ function instanceSettings() | |||||||
|     return InstanceSettings::get(); |     return InstanceSettings::get(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function loadConfigFromGit(string $repository, string $branch, string $base_directory, int $server_id, int $team_id) { | function loadConfigFromGit(string $repository, string $branch, string $base_directory, int $server_id, int $team_id) | ||||||
|  | { | ||||||
| 
 | 
 | ||||||
|     $server = Server::find($server_id)->where('team_id', $team_id)->first(); |     $server = Server::find($server_id)->where('team_id', $team_id)->first(); | ||||||
|     if (! $server) { |     if (! $server) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     $uuid = new Cuid2(); |     $uuid = new Cuid2; | ||||||
|     $cloneCommand = "git clone --no-checkout -b $branch $repository ."; |     $cloneCommand = "git clone --no-checkout -b $branch $repository ."; | ||||||
|     $workdir = rtrim($base_directory, '/'); |     $workdir = rtrim($base_directory, '/'); | ||||||
|     $fileList = collect([".$workdir/coolify.json"]); |     $fileList = collect([".$workdir/coolify.json"]); | ||||||
| @@ -4012,3 +4014,30 @@ function loadConfigFromGit(string $repository, string $branch, string $base_dire | |||||||
|         // continue
 |         // continue
 | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | function loggy($message = null, array $context = []) | ||||||
|  | { | ||||||
|  |     if (! isDev()) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     if (function_exists('ray') && config('app.debug')) { | ||||||
|  |         ray($message, $context); | ||||||
|  |     } | ||||||
|  |     if (is_null($message)) { | ||||||
|  |         return app('log'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return app('log')->debug($message, $context); | ||||||
|  | } | ||||||
|  | function sslipDomainWarning(string $domains) | ||||||
|  | { | ||||||
|  |     $domains = str($domains)->trim()->explode(','); | ||||||
|  |     $showSslipHttpsWarning = false; | ||||||
|  |     $domains->each(function ($domain) use (&$showSslipHttpsWarning) { | ||||||
|  |         if (str($domain)->contains('https') && str($domain)->contains('sslip')) { | ||||||
|  |             $showSslipHttpsWarning = true; | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     return $showSslipHttpsWarning; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ | |||||||
|         "laravel/fortify": "^v1.16.0", |         "laravel/fortify": "^v1.16.0", | ||||||
|         "laravel/framework": "^v11", |         "laravel/framework": "^v11", | ||||||
|         "laravel/horizon": "^5.29.1", |         "laravel/horizon": "^5.29.1", | ||||||
|  |         "laravel/pail": "^1.1", | ||||||
|         "laravel/prompts": "^0.1.6", |         "laravel/prompts": "^0.1.6", | ||||||
|         "laravel/sanctum": "^v4.0", |         "laravel/sanctum": "^v4.0", | ||||||
|         "laravel/socialite": "^v5.14.0", |         "laravel/socialite": "^v5.14.0", | ||||||
|   | |||||||
							
								
								
									
										79
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										79
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							| @@ -4,7 +4,7 @@ | |||||||
|         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", |         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", | ||||||
|         "This file is @generated automatically" |         "This file is @generated automatically" | ||||||
|     ], |     ], | ||||||
|     "content-hash": "c47adf3684eb727e22503937435c0914", |     "content-hash": "943975ec232403b96a40d215253492d8", | ||||||
|     "packages": [ |     "packages": [ | ||||||
|         { |         { | ||||||
|             "name": "amphp/amp", |             "name": "amphp/amp", | ||||||
| @@ -3144,6 +3144,83 @@ | |||||||
|             }, |             }, | ||||||
|             "time": "2024-10-08T18:23:02+00:00" |             "time": "2024-10-08T18:23:02+00:00" | ||||||
|         }, |         }, | ||||||
|  |         { | ||||||
|  |             "name": "laravel/pail", | ||||||
|  |             "version": "v1.1.5", | ||||||
|  |             "source": { | ||||||
|  |                 "type": "git", | ||||||
|  |                 "url": "https://github.com/laravel/pail.git", | ||||||
|  |                 "reference": "b33ad8321416fe86efed7bf398f3306c47b4871b" | ||||||
|  |             }, | ||||||
|  |             "dist": { | ||||||
|  |                 "type": "zip", | ||||||
|  |                 "url": "https://api.github.com/repos/laravel/pail/zipball/b33ad8321416fe86efed7bf398f3306c47b4871b", | ||||||
|  |                 "reference": "b33ad8321416fe86efed7bf398f3306c47b4871b", | ||||||
|  |                 "shasum": "" | ||||||
|  |             }, | ||||||
|  |             "require": { | ||||||
|  |                 "ext-mbstring": "*", | ||||||
|  |                 "illuminate/console": "^10.24|^11.0", | ||||||
|  |                 "illuminate/contracts": "^10.24|^11.0", | ||||||
|  |                 "illuminate/log": "^10.24|^11.0", | ||||||
|  |                 "illuminate/process": "^10.24|^11.0", | ||||||
|  |                 "illuminate/support": "^10.24|^11.0", | ||||||
|  |                 "nunomaduro/termwind": "^1.15|^2.0", | ||||||
|  |                 "php": "^8.2", | ||||||
|  |                 "symfony/console": "^6.0|^7.0" | ||||||
|  |             }, | ||||||
|  |             "require-dev": { | ||||||
|  |                 "laravel/pint": "^1.13", | ||||||
|  |                 "orchestra/testbench": "^8.12|^9.0", | ||||||
|  |                 "pestphp/pest": "^2.20", | ||||||
|  |                 "pestphp/pest-plugin-type-coverage": "^2.3", | ||||||
|  |                 "phpstan/phpstan": "^1.10", | ||||||
|  |                 "symfony/var-dumper": "^6.3|^7.0" | ||||||
|  |             }, | ||||||
|  |             "type": "library", | ||||||
|  |             "extra": { | ||||||
|  |                 "branch-alias": { | ||||||
|  |                     "dev-main": "1.x-dev" | ||||||
|  |                 }, | ||||||
|  |                 "laravel": { | ||||||
|  |                     "providers": [ | ||||||
|  |                         "Laravel\\Pail\\PailServiceProvider" | ||||||
|  |                     ] | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             "autoload": { | ||||||
|  |                 "psr-4": { | ||||||
|  |                     "Laravel\\Pail\\": "src/" | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             "notification-url": "https://packagist.org/downloads/", | ||||||
|  |             "license": [ | ||||||
|  |                 "MIT" | ||||||
|  |             ], | ||||||
|  |             "authors": [ | ||||||
|  |                 { | ||||||
|  |                     "name": "Taylor Otwell", | ||||||
|  |                     "email": "taylor@laravel.com" | ||||||
|  |                 }, | ||||||
|  |                 { | ||||||
|  |                     "name": "Nuno Maduro", | ||||||
|  |                     "email": "enunomaduro@gmail.com" | ||||||
|  |                 } | ||||||
|  |             ], | ||||||
|  |             "description": "Easily delve into your Laravel application's log files directly from the command line.", | ||||||
|  |             "homepage": "https://github.com/laravel/pail", | ||||||
|  |             "keywords": [ | ||||||
|  |                 "laravel", | ||||||
|  |                 "logs", | ||||||
|  |                 "php", | ||||||
|  |                 "tail" | ||||||
|  |             ], | ||||||
|  |             "support": { | ||||||
|  |                 "issues": "https://github.com/laravel/pail/issues", | ||||||
|  |                 "source": "https://github.com/laravel/pail" | ||||||
|  |             }, | ||||||
|  |             "time": "2024-10-15T20:06:24+00:00" | ||||||
|  |         }, | ||||||
|         { |         { | ||||||
|             "name": "laravel/prompts", |             "name": "laravel/prompts", | ||||||
|             "version": "v0.1.25", |             "version": "v0.1.25", | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ return [ | |||||||
| 
 | 
 | ||||||
|     // The release version of your application
 |     // The release version of your application
 | ||||||
|     // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
 |     // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
 | ||||||
|     'release' => '4.0.0-beta.360', |     'release' => '4.0.0-beta.361', | ||||||
| 
 | 
 | ||||||
|     // When left empty or `null` the Laravel environment will be used
 |     // When left empty or `null` the Laravel environment will be used
 | ||||||
|     'environment' => config('app.env'), |     'environment' => config('app.env'), | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								config/testing.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								config/testing.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | return [ | ||||||
|  |     'dusk_test_email' => env('DUSK_TEST_EMAIL', 'test@example.com'), | ||||||
|  |     'dusk_test_password' => env('DUSK_TEST_PASSWORD', 'password'), | ||||||
|  | ]; | ||||||
| @@ -1,3 +1,3 @@ | |||||||
| <?php | <?php | ||||||
| 
 | 
 | ||||||
| return '4.0.0-beta.360'; | return '4.0.0-beta.361'; | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ return new class extends Migration | |||||||
|             $table->boolean('is_metrics_enabled')->default(false); |             $table->boolean('is_metrics_enabled')->default(false); | ||||||
|             $table->integer('metrics_refresh_rate_seconds')->default(5); |             $table->integer('metrics_refresh_rate_seconds')->default(5); | ||||||
|             $table->integer('metrics_history_days')->default(30); |             $table->integer('metrics_history_days')->default(30); | ||||||
|             $table->string('metrics_token')->default(generateSentinelToken()); |             $table->string('metrics_token')->nullable(); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ use App\Models\EnvironmentVariable; | |||||||
| use App\Models\Server; | use App\Models\Server; | ||||||
| use Illuminate\Database\Migrations\Migration; | use Illuminate\Database\Migrations\Migration; | ||||||
| use Illuminate\Database\Schema\Blueprint; | use Illuminate\Database\Schema\Blueprint; | ||||||
|  | use Illuminate\Support\Facades\DB; | ||||||
| use Illuminate\Support\Facades\Schema; | use Illuminate\Support\Facades\Schema; | ||||||
| use Visus\Cuid2\Cuid2; | use Visus\Cuid2\Cuid2; | ||||||
| 
 | 
 | ||||||
| @@ -14,6 +15,7 @@ return new class extends Migration | |||||||
|      */ |      */ | ||||||
|     public function up(): void |     public function up(): void | ||||||
|     { |     { | ||||||
|  |         try { | ||||||
|             Schema::table('applications', function (Blueprint $table) { |             Schema::table('applications', function (Blueprint $table) { | ||||||
|                 $table->dropColumn('docker_compose_pr_location'); |                 $table->dropColumn('docker_compose_pr_location'); | ||||||
|                 $table->dropColumn('docker_compose_pr'); |                 $table->dropColumn('docker_compose_pr'); | ||||||
| @@ -47,11 +49,11 @@ return new class extends Migration | |||||||
|             Schema::table('server_settings', function (Blueprint $table) { |             Schema::table('server_settings', function (Blueprint $table) { | ||||||
|                 $table->integer('metrics_history_days')->default(7)->change(); |                 $table->integer('metrics_history_days')->default(7)->change(); | ||||||
|             }); |             }); | ||||||
|         Server::all()->each(function (Server $server) { | 
 | ||||||
|             $server->settings->update([ |             DB::table('server_settings')->update(['metrics_history_days' => 7]); | ||||||
|                 'metrics_history_days' => 7, |         } catch (\Exception $e) { | ||||||
|             ]); |             loggy($e); | ||||||
|         }); |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ return new class extends Migration | |||||||
|     public function up(): void |     public function up(): void | ||||||
|     { |     { | ||||||
|         Schema::table('server_settings', function (Blueprint $table) { |         Schema::table('server_settings', function (Blueprint $table) { | ||||||
|             $table->boolean('is_force_cleanup_enabled')->default(false)->after('is_sentinel_enabled'); |             $table->boolean('is_force_cleanup_enabled')->default(false); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -0,0 +1,28 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | use Illuminate\Database\Migrations\Migration; | ||||||
|  | use Illuminate\Database\Schema\Blueprint; | ||||||
|  | use Illuminate\Support\Facades\Schema; | ||||||
|  | 
 | ||||||
|  | return new class extends Migration | ||||||
|  | { | ||||||
|  |     /** | ||||||
|  |      * Run the migrations. | ||||||
|  |      */ | ||||||
|  |     public function up(): void | ||||||
|  |     { | ||||||
|  |         Schema::table('environment_variables', function (Blueprint $table) { | ||||||
|  |             $table->boolean('is_required')->default(false); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Reverse the migrations. | ||||||
|  |      */ | ||||||
|  |     public function down(): void | ||||||
|  |     { | ||||||
|  |         Schema::table('environment_variables', function (Blueprint $table) { | ||||||
|  |             $table->dropColumn('is_required'); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | }; | ||||||
| @@ -0,0 +1,54 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | use Illuminate\Database\Migrations\Migration; | ||||||
|  | use Illuminate\Database\Schema\Blueprint; | ||||||
|  | use Illuminate\Support\Facades\Schema; | ||||||
|  | 
 | ||||||
|  | return new class extends Migration | ||||||
|  | { | ||||||
|  |     /** | ||||||
|  |      * Run the migrations. | ||||||
|  |      */ | ||||||
|  |     public function up(): void | ||||||
|  |     { | ||||||
|  |         Schema::table('server_settings', function (Blueprint $table) { | ||||||
|  |             $table->dropColumn('metrics_token'); | ||||||
|  |             $table->dropColumn('metrics_refresh_rate_seconds'); | ||||||
|  |             $table->dropColumn('metrics_history_days'); | ||||||
|  |             $table->dropColumn('is_server_api_enabled'); | ||||||
|  | 
 | ||||||
|  |             $table->boolean('is_sentinel_enabled')->default(false); | ||||||
|  |             $table->text('sentinel_token')->nullable(); | ||||||
|  |             $table->integer('sentinel_metrics_refresh_rate_seconds')->default(10); | ||||||
|  |             $table->integer('sentinel_metrics_history_days')->default(7); | ||||||
|  |             $table->integer('sentinel_push_interval_seconds')->default(60); | ||||||
|  |             $table->string('sentinel_custom_url')->nullable(); | ||||||
|  |         }); | ||||||
|  |         Schema::table('servers', function (Blueprint $table) { | ||||||
|  |             $table->dateTime('sentinel_updated_at')->default(now()); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Reverse the migrations. | ||||||
|  |      */ | ||||||
|  |     public function down(): void | ||||||
|  |     { | ||||||
|  |         Schema::table('server_settings', function (Blueprint $table) { | ||||||
|  |             $table->string('metrics_token')->nullable(); | ||||||
|  |             $table->integer('metrics_refresh_rate_seconds')->default(5); | ||||||
|  |             $table->integer('metrics_history_days')->default(30); | ||||||
|  |             $table->boolean('is_server_api_enabled')->default(false); | ||||||
|  | 
 | ||||||
|  |             $table->dropColumn('is_sentinel_enabled'); | ||||||
|  |             $table->dropColumn('sentinel_token'); | ||||||
|  |             $table->dropColumn('sentinel_metrics_refresh_rate_seconds'); | ||||||
|  |             $table->dropColumn('sentinel_metrics_history_days'); | ||||||
|  |             $table->dropColumn('sentinel_push_interval_seconds'); | ||||||
|  |             $table->dropColumn('sentinel_custom_url'); | ||||||
|  |         }); | ||||||
|  |         Schema::table('servers', function (Blueprint $table) { | ||||||
|  |             $table->dropColumn('sentinel_updated_at'); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | }; | ||||||
| @@ -0,0 +1,22 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | use Illuminate\Database\Migrations\Migration; | ||||||
|  | use Illuminate\Database\Schema\Blueprint; | ||||||
|  | use Illuminate\Support\Facades\Schema; | ||||||
|  | 
 | ||||||
|  | class AddIsSharedToEnvironmentVariables extends Migration | ||||||
|  | { | ||||||
|  |     public function up() | ||||||
|  |     { | ||||||
|  |         Schema::table('environment_variables', function (Blueprint $table) { | ||||||
|  |             $table->boolean('is_shared')->default(false); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function down() | ||||||
|  |     { | ||||||
|  |         Schema::table('environment_variables', function (Blueprint $table) { | ||||||
|  |             $table->dropColumn('is_shared'); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,41 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | use App\Models\EnvironmentVariable; | ||||||
|  | use App\Models\StandaloneRedis; | ||||||
|  | use Illuminate\Database\Migrations\Migration; | ||||||
|  | use Illuminate\Database\Schema\Blueprint; | ||||||
|  | use Illuminate\Support\Facades\DB; | ||||||
|  | use Illuminate\Support\Facades\Schema; | ||||||
|  | 
 | ||||||
|  | class MoveRedisPasswordToEnvs extends Migration | ||||||
|  | { | ||||||
|  |     /** | ||||||
|  |      * Run the migrations. | ||||||
|  |      */ | ||||||
|  |     public function up(): void | ||||||
|  |     { | ||||||
|  |         try { | ||||||
|  |             StandaloneRedis::chunkById(100, function ($redisInstances) { | ||||||
|  |                 foreach ($redisInstances as $redis) { | ||||||
|  |                     $redis_password = DB::table('standalone_redis')->where('id', $redis->id)->value('redis_password'); | ||||||
|  |                     EnvironmentVariable::create([ | ||||||
|  |                         'standalone_redis_id' => $redis->id, | ||||||
|  |                         'key' => 'REDIS_PASSWORD', | ||||||
|  |                         'value' => $redis_password, | ||||||
|  |                     ]); | ||||||
|  |                     EnvironmentVariable::create([ | ||||||
|  |                         'standalone_redis_id' => $redis->id, | ||||||
|  |                         'key' => 'REDIS_USERNAME', | ||||||
|  |                         'value' => 'default', | ||||||
|  |                     ]); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |             Schema::table('standalone_redis', function (Blueprint $table) { | ||||||
|  |                 $table->dropColumn('redis_password'); | ||||||
|  |             }); | ||||||
|  |         } catch (\Exception $e) { | ||||||
|  |             echo 'Moving Redis passwords to envs failed.'; | ||||||
|  |             echo $e->getMessage(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,22 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | use Illuminate\Database\Migrations\Migration; | ||||||
|  | use Illuminate\Database\Schema\Blueprint; | ||||||
|  | use Illuminate\Support\Facades\Schema; | ||||||
|  | 
 | ||||||
|  | return new class extends Migration | ||||||
|  | { | ||||||
|  |     public function up() | ||||||
|  |     { | ||||||
|  |         Schema::table('instance_settings', function (Blueprint $table) { | ||||||
|  |             $table->boolean('disable_two_step_confirmation')->default(false); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function down() | ||||||
|  |     { | ||||||
|  |         Schema::table('instance_settings', function (Blueprint $table) { | ||||||
|  |             $table->dropColumn('disable_two_step_confirmation'); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | }; | ||||||
| @@ -0,0 +1,28 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | use Illuminate\Database\Migrations\Migration; | ||||||
|  | use Illuminate\Database\Schema\Blueprint; | ||||||
|  | use Illuminate\Support\Facades\Schema; | ||||||
|  | 
 | ||||||
|  | return new class extends Migration | ||||||
|  | { | ||||||
|  |     /** | ||||||
|  |      * Run the migrations. | ||||||
|  |      */ | ||||||
|  |     public function up(): void | ||||||
|  |     { | ||||||
|  |         Schema::table('servers', function (Blueprint $table) { | ||||||
|  |             $table->softDeletes(); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Reverse the migrations. | ||||||
|  |      */ | ||||||
|  |     public function down(): void | ||||||
|  |     { | ||||||
|  |         Schema::table('servers', function (Blueprint $table) { | ||||||
|  |             $table->dropSoftDeletes(); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | }; | ||||||
| @@ -26,6 +26,8 @@ class DatabaseSeeder extends Seeder | |||||||
|             S3StorageSeeder::class, |             S3StorageSeeder::class, | ||||||
|             StandalonePostgresqlSeeder::class, |             StandalonePostgresqlSeeder::class, | ||||||
|             OauthSettingSeeder::class, |             OauthSettingSeeder::class, | ||||||
|  |             DisableTwoStepConfirmationSeeder::class, | ||||||
|  |             SentinelSeeder::class, | ||||||
|         ]); |         ]); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										20
									
								
								database/seeders/DisableTwoStepConfirmationSeeder.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								database/seeders/DisableTwoStepConfirmationSeeder.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace Database\Seeders; | ||||||
|  | 
 | ||||||
|  | use Illuminate\Database\Seeder; | ||||||
|  | use Illuminate\Support\Facades\DB; | ||||||
|  | 
 | ||||||
|  | class DisableTwoStepConfirmationSeeder extends Seeder | ||||||
|  | { | ||||||
|  |     /** | ||||||
|  |      * Run the database seeds. | ||||||
|  |      */ | ||||||
|  |     public function run(): void | ||||||
|  |     { | ||||||
|  |         DB::table('instance_settings')->updateOrInsert( | ||||||
|  |             [], | ||||||
|  |             ['disable_two_step_confirmation' => true] | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -186,6 +186,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== | |||||||
| 
 | 
 | ||||||
|         $this->call(OauthSettingSeeder::class); |         $this->call(OauthSettingSeeder::class); | ||||||
|         $this->call(PopulateSshKeysDirectorySeeder::class); |         $this->call(PopulateSshKeysDirectorySeeder::class); | ||||||
|  |         $this->call(SentinelSeeder::class); | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										31
									
								
								database/seeders/SentinelSeeder.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								database/seeders/SentinelSeeder.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace Database\Seeders; | ||||||
|  | 
 | ||||||
|  | use App\Models\Server; | ||||||
|  | use Illuminate\Database\Seeder; | ||||||
|  | 
 | ||||||
|  | class SentinelSeeder extends Seeder | ||||||
|  | { | ||||||
|  |     public function run() | ||||||
|  |     { | ||||||
|  |         Server::chunk(100, function ($servers) { | ||||||
|  |             foreach ($servers as $server) { | ||||||
|  |                 try { | ||||||
|  |                     if (str($server->settings->sentinel_token)->isEmpty()) { | ||||||
|  |                         $server->settings->generateSentinelToken(); | ||||||
|  |                     } | ||||||
|  |                     if (str($server->settings->sentinel_custom_url)->isEmpty()) { | ||||||
|  |                         $url = $server->settings->generateSentinelUrl(); | ||||||
|  |                         if (str($url)->isEmpty()) { | ||||||
|  |                             $server->settings->is_sentinel_enabled = false; | ||||||
|  |                             $server->settings->save(); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } catch (\Throwable $e) { | ||||||
|  |                     loggy("Error: {$e->getMessage()}\n"); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -4,7 +4,7 @@ | |||||||
|     "dependencies": { |     "dependencies": { | ||||||
|         "@xterm/addon-fit": "^0.10.0", |         "@xterm/addon-fit": "^0.10.0", | ||||||
|         "@xterm/xterm": "^5.5.0", |         "@xterm/xterm": "^5.5.0", | ||||||
|         "cookie": "^0.6.0", |         "cookie": "^0.7.0", | ||||||
|         "axios": "1.7.5", |         "axios": "1.7.5", | ||||||
|         "dotenv": "^16.4.5", |         "dotenv": "^16.4.5", | ||||||
|         "node-pty": "^1.0.0", |         "node-pty": "^1.0.0", | ||||||
|   | |||||||
| @@ -5,34 +5,38 @@ ARG TARGETPLATFORM | |||||||
| ARG CLOUDFLARED_VERSION=2024.4.1 | ARG CLOUDFLARED_VERSION=2024.4.1 | ||||||
|  |  | ||||||
| ARG POSTGRES_VERSION=15 | ARG POSTGRES_VERSION=15 | ||||||
| RUN apt-get update |  | ||||||
| # Postgres version requirements |  | ||||||
| RUN apt install dirmngr ca-certificates software-properties-common gnupg gnupg2 apt-transport-https curl -y |  | ||||||
| RUN curl -fSsL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/postgresql.gpg > /dev/null |  | ||||||
|  |  | ||||||
| RUN echo deb [arch=amd64,arm64,ppc64el signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt/ jammy-pgdg main | tee -a /etc/apt/sources.list.d/postgresql.list | # Use build arguments for caching | ||||||
|  | ARG BUILDTIME_DEPS="dirmngr ca-certificates software-properties-common gnupg gnupg2 apt-transport-https curl" | ||||||
|  | ARG RUNTIME_DEPS="postgresql-client-$POSTGRES_VERSION php8.2-pgsql openssh-client git git-lfs jq lsof" | ||||||
|  |  | ||||||
| RUN apt-get update | # Install dependencies | ||||||
| RUN apt-get install postgresql-client-$POSTGRES_VERSION -y | RUN --mount=type=cache,target=/var/cache/apt \ | ||||||
|  |     apt-get update && \ | ||||||
|  |     apt-get install -y $BUILDTIME_DEPS && \ | ||||||
|  |     curl -fSsL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/postgresql.gpg > /dev/null && \ | ||||||
|  |     echo deb [arch=amd64,arm64,ppc64el signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt/ jammy-pgdg main | tee -a /etc/apt/sources.list.d/postgresql.list && \ | ||||||
|  |     apt-get update && \ | ||||||
|  |     apt-get install -y $RUNTIME_DEPS && \ | ||||||
|  |     apt-get -y autoremove && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* | ||||||
|  |  | ||||||
| # Coolify requirements |  | ||||||
| RUN apt-get install -y php8.2-pgsql openssh-client git git-lfs jq lsof |  | ||||||
| RUN apt-get -y autoremove && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* |  | ||||||
| COPY --chmod=755 docker/dev/etc/s6-overlay/ /etc/s6-overlay/ | COPY --chmod=755 docker/dev/etc/s6-overlay/ /etc/s6-overlay/ | ||||||
|  |  | ||||||
| COPY docker/dev/nginx.conf /etc/nginx/conf.d/custom.conf | COPY docker/dev/nginx.conf /etc/nginx/conf.d/custom.conf | ||||||
|  |  | ||||||
| RUN echo "alias ll='ls -al'" >>/etc/bash.bashrc | RUN echo "alias ll='ls -al'" >>/etc/bash.bashrc && \ | ||||||
| RUN echo "alias a='php artisan'" >>/etc/bash.bashrc |     echo "alias a='php artisan'" >>/etc/bash.bashrc | ||||||
|  |  | ||||||
| RUN mkdir -p /usr/local/bin | RUN mkdir -p /usr/local/bin | ||||||
|  |  | ||||||
| RUN /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/amd64' ]]; then \ | RUN --mount=type=cache,target=/root/.cache \ | ||||||
|  |     /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/amd64' ]]; then \ | ||||||
|     echo 'amd64' && \ |     echo 'amd64' && \ | ||||||
|     curl -sSL https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \ |     curl -sSL https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \ | ||||||
|     ;fi" |     ;fi" | ||||||
|  |  | ||||||
| RUN /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \ | RUN --mount=type=cache,target=/root/.cache \ | ||||||
|  |     /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \ | ||||||
|     echo 'arm64' && \ |     echo 'arm64' && \ | ||||||
|     curl -L https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \ |     curl -L https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \ | ||||||
|     ;fi" |     ;fi" | ||||||
|   | |||||||
| @@ -26,5 +26,12 @@ | |||||||
|     "input.code": "الرمز لمرة واحدة", |     "input.code": "الرمز لمرة واحدة", | ||||||
|     "input.recovery_code": "رمز الاسترداد", |     "input.recovery_code": "رمز الاسترداد", | ||||||
|     "button.save": "حفظ", |     "button.save": "حفظ", | ||||||
|     "repository.url": "<span class='text-helper'>أمثلة</span><br>للمستودعات العامة، استخدم <span class='text-helper'>https://...</span>.<br>للمستودعات الخاصة، استخدم <span class='text-helper'>git@...</span>.<br><br>سيتم تحديد الفرع <span class='text-helper'>main</span> لـ <span class='text-helper'>https://github.com/coollabsio/coolify-examples</span><br>سيتم تحديد الفرع <span class='text-helper'>nodejs-fastify</span> لـ <span class='text-helper'>https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify</span><br>سيتم تحديد الفرع <span class='text-helper'>main</span> لـ <span class='text-helper'>https://gitea.com/sedlav/expressjs.git</span><br>سيتم تحديد الفرع <span class='text-helper'>main</span> لـ <span class='text-helper'>https://gitlab.com/andrasbacsai/nodejs-example.git</span>." |     "repository.url": "<span class='text-helper'>أمثلة</span><br>للمستودعات العامة، استخدم <span class='text-helper'>https://...</span>.<br>للمستودعات الخاصة، استخدم <span class='text-helper'>git@...</span>.<br><br>سيتم تحديد الفرع <span class='text-helper'>main</span> لـ <span class='text-helper'>https://github.com/coollabsio/coolify-examples</span><br>سيتم تحديد الفرع <span class='text-helper'>nodejs-fastify</span> لـ <span class='text-helper'>https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify</span><br>سيتم تحديد الفرع <span class='text-helper'>main</span> لـ <span class='text-helper'>https://gitea.com/sedlav/expressjs.git</span><br>سيتم تحديد الفرع <span class='text-helper'>main</span> لـ <span class='text-helper'>https://gitlab.com/andrasbacsai/nodejs-example.git</span>.", | ||||||
|  |     "service.stop": "سيتم إيقاف هذه الخدمة.", | ||||||
|  |     "resource.docker_cleanup": "قم بتشغيل Docker Cleanup (قم بإزالة الصور غير المستخدمة وذاكرة التخزين المؤقت للمنشئ).", | ||||||
|  |     "resource.non_persistent": "سيتم حذف جميع البيانات غير الدائمة.", | ||||||
|  |     "resource.delete_volumes": "حذف جميع المجلدات والملفات المرتبطة بهذا المورد بشكل دائم.", | ||||||
|  |     "resource.delete_connected_networks": "حذف جميع الشبكات غير المحددة مسبقًا والمرتبطة بهذا المورد بشكل دائم.", | ||||||
|  |     "resource.delete_configurations": "حذف جميع ملفات التعريف من الخادم بشكل دائم.", | ||||||
|  |     "database.delete_backups_locally": "حذف كافة النسخ الاحتياطية نهائيًا من التخزين المحلي." | ||||||
| } | } | ||||||
|   | |||||||
| @@ -33,5 +33,6 @@ | |||||||
|     "resource.delete_volumes": "Permanently delete all volumes associated with this resource.", |     "resource.delete_volumes": "Permanently delete all volumes associated with this resource.", | ||||||
|     "resource.delete_connected_networks": "Permanently delete all non-predefined networks associated with this resource.", |     "resource.delete_connected_networks": "Permanently delete all non-predefined networks associated with this resource.", | ||||||
|     "resource.delete_configurations": "Permanently delete all configuration files from the server.", |     "resource.delete_configurations": "Permanently delete all configuration files from the server.", | ||||||
|     "database.delete_backups_locally": "All backups will be permanently deleted from local storage." |     "database.delete_backups_locally": "All backups will be permanently deleted from local storage.", | ||||||
|  |     "warning.sslipdomain": "Your configuration is saved, but sslip domain with https is <span class='dark:text-red-500 text-red-500 font-bold'>NOT</span> recommended, because Let's Encrypt servers with this public domain are rate limited (SSL certificate validation will fail). <br><br>Use your own domain instead." | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										37
									
								
								lang/ro.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								lang/ro.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | { | ||||||
|  |     "auth.login": "Autentificare", | ||||||
|  |     "auth.login.azure": "Autentificare prin Microsoft", | ||||||
|  |     "auth.login.bitbucket": "Autentificare prin Bitbucket", | ||||||
|  |     "auth.login.github": "Autentificare prin GitHub", | ||||||
|  |     "auth.login.gitlab": "Autentificare prin Gitlab", | ||||||
|  |     "auth.login.google": "Autentificare prin Google", | ||||||
|  |     "auth.already_registered": "Sunteți deja înregistrat?", | ||||||
|  |     "auth.confirm_password": "Confirmați parola", | ||||||
|  |     "auth.forgot_password": "Ați uitat parola", | ||||||
|  |     "auth.forgot_password_send_email": "Trimiteți e-mail-ul pentru resetarea parolei", | ||||||
|  |     "auth.register_now": "Înregistrare", | ||||||
|  |     "auth.logout": "Deconectare", | ||||||
|  |     "auth.register": "Înregistrare", | ||||||
|  |     "auth.registration_disabled": "Înregistrarea este dezactivată. Vă rugăm să contactați administratorul site-ului.", | ||||||
|  |     "auth.reset_password": "Resetare parolă", | ||||||
|  |     "auth.failed": "Autentificare nereușită. Vă rugăm să verificați datele introduse.", | ||||||
|  |     "auth.failed.callback": "A apărut o eroare în timpul autentificării cu furnizorul extern.", | ||||||
|  |     "auth.failed.password": "Parola furnizată este incorectă.", | ||||||
|  |     "auth.failed.email": "Nu putem găsi un utilizator cu această adresă de e-mail.", | ||||||
|  |     "auth.throttle": "Prea multe încercări de autentificare. Vă rugăm să încercați din nou în :seconds secunde.", | ||||||
|  |     "input.name": "Nume", | ||||||
|  |     "input.email": "E-mail", | ||||||
|  |     "input.password": "Parolă", | ||||||
|  |     "input.password.again": "Repetați parola", | ||||||
|  |     "input.code": "Cod de unică folosință", | ||||||
|  |     "input.recovery_code": "Cod de recuperare", | ||||||
|  |     "button.save": "Salvare", | ||||||
|  |     "repository.url": "<span class='text-helper'>Exemple</span><br>Pentru depozite publice, utilizați <span class='text-helper'>https://...</span>.<br>Pentru depozite private, utilizați <span class='text-helper'>git@...</span>.<br><br>https://github.com/coollabsio/coolify-examples va fi selectată ramura <span class='text-helper'>main</span><br>https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify va fi selectată ramura <span class='text-helper'>nodejs-fastify</span>.<br>https://gitea.com/sedlav/expressjs.git va fi selectată ramura <span class='text-helper'>main</span>.<br>https://gitlab.com/andrasbacsai/nodejs-example.git va fi selectată ramura <span class='text-helper'>main</span>.", | ||||||
|  |     "service.stop": "Acest serviciu va fi oprit.", | ||||||
|  |     "resource.docker_cleanup": "Executați curățarea Docker (eliminați imaginile neutilizate și memoria cache a constructorului).", | ||||||
|  |     "resource.non_persistent": "Toate datele nepersistente vor fi șterse.", | ||||||
|  |     "resource.delete_volumes": "Ștergeți definitiv toate volumele asociate cu această resursă.", | ||||||
|  |     "resource.delete_connected_networks": "Ștergeți definitiv toate rețelele non-predefinite asociate cu această resursă.", | ||||||
|  |     "resource.delete_configurations": "Ștergeți definitiv toate fișierele de configurare de pe server.", | ||||||
|  |     "database.delete_backups_locally": "Toate copiile de rezervă vor fi șterse definitiv din stocarea locală." | ||||||
|  | } | ||||||
							
								
								
									
										22
									
								
								openapi.yaml
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								openapi.yaml
									
									
									
									
									
								
							| @@ -98,6 +98,10 @@ paths: | |||||||
|                 is_static: |                 is_static: | ||||||
|                   type: boolean |                   type: boolean | ||||||
|                   description: 'The flag to indicate if the application is static.' |                   description: 'The flag to indicate if the application is static.' | ||||||
|  |                 static_image: | ||||||
|  |                   type: string | ||||||
|  |                   enum: ['nginx:alpine'] | ||||||
|  |                   description: 'The static image.' | ||||||
|                 install_command: |                 install_command: | ||||||
|                   type: string |                   type: string | ||||||
|                   description: 'The install command.' |                   description: 'The install command.' | ||||||
| @@ -323,6 +327,10 @@ paths: | |||||||
|                 is_static: |                 is_static: | ||||||
|                   type: boolean |                   type: boolean | ||||||
|                   description: 'The flag to indicate if the application is static.' |                   description: 'The flag to indicate if the application is static.' | ||||||
|  |                 static_image: | ||||||
|  |                   type: string | ||||||
|  |                   enum: ['nginx:alpine'] | ||||||
|  |                   description: 'The static image.' | ||||||
|                 install_command: |                 install_command: | ||||||
|                   type: string |                   type: string | ||||||
|                   description: 'The install command.' |                   description: 'The install command.' | ||||||
| @@ -548,6 +556,10 @@ paths: | |||||||
|                 is_static: |                 is_static: | ||||||
|                   type: boolean |                   type: boolean | ||||||
|                   description: 'The flag to indicate if the application is static.' |                   description: 'The flag to indicate if the application is static.' | ||||||
|  |                 static_image: | ||||||
|  |                   type: string | ||||||
|  |                   enum: ['nginx:alpine'] | ||||||
|  |                   description: 'The static image.' | ||||||
|                 install_command: |                 install_command: | ||||||
|                   type: string |                   type: string | ||||||
|                   description: 'The install command.' |                   description: 'The install command.' | ||||||
| @@ -3093,7 +3105,7 @@ paths: | |||||||
|       security: |       security: | ||||||
|         - |         - | ||||||
|           bearerAuth: [] |           bearerAuth: [] | ||||||
|   /healthcheck: |   /health: | ||||||
|     get: |     get: | ||||||
|       summary: Healthcheck |       summary: Healthcheck | ||||||
|       description: 'Healthcheck endpoint.' |       description: 'Healthcheck endpoint.' | ||||||
| @@ -4959,7 +4971,7 @@ components: | |||||||
|           type: boolean |           type: boolean | ||||||
|         is_reachable: |         is_reachable: | ||||||
|           type: boolean |           type: boolean | ||||||
|         is_server_api_enabled: |         is_sentinel_enabled: | ||||||
|           type: boolean |           type: boolean | ||||||
|         is_swarm_manager: |         is_swarm_manager: | ||||||
|           type: boolean |           type: boolean | ||||||
| @@ -4981,11 +4993,11 @@ components: | |||||||
|           type: string |           type: string | ||||||
|         logdrain_newrelic_license_key: |         logdrain_newrelic_license_key: | ||||||
|           type: string |           type: string | ||||||
|         metrics_history_days: |         sentinel_metrics_history_days: | ||||||
|           type: integer |           type: integer | ||||||
|         metrics_refresh_rate_seconds: |         sentinel_metrics_refresh_rate_seconds: | ||||||
|           type: integer |           type: integer | ||||||
|         metrics_token: |         sentinel_token: | ||||||
|           type: string |           type: string | ||||||
|         docker_cleanup_frequency: |         docker_cleanup_frequency: | ||||||
|           type: string |           type: string | ||||||
|   | |||||||
							
								
								
									
										88
									
								
								public/svgs/affine.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								public/svgs/affine.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 500 500" width="500" height="500" style="width:100%;height:100%;transform:translate3d(0,0,0);content-visibility:visible"> | ||||||
|  |     <defs> | ||||||
|  |       <path d="M-6.816-205.027c-20.92 36.208-170.344 294.875-187.382 324.715-1.947 4.684 1.644 10.592 6.749 10.684 1.368.132 11.815.066 13.815.079 97.651.04 259.272-.026 357.187 0 1.526 0 3.79.013 3.895-.079.618.053 1.105-.198 1.658-.224 4.25-1.276 6.815-6.276 5.091-10.447-.408-.592-.237-.67-1.342-2.486C143.266 31.247 71.403-93.101 21.696-179.24c-3.052-5.052-11.868-20.735-14.881-25.774-2.96-5.144-10.684-5.21-13.644 0z" transform="translate(250 250)" style="display:block" id="a"/> | ||||||
|  |       <path d="M-6.816-205.027c-20.92 36.208-170.344 294.875-187.382 324.715-1.947 4.684 1.644 10.592 6.749 10.684 1.368.132 11.815.066 13.815.079 97.651.04 259.272-.026 357.187 0 1.526 0 3.79.013 3.895-.079.618.053 1.105-.198 1.658-.224 4.25-1.276 6.815-6.276 5.091-10.447-.408-.592-.237-.67-1.342-2.486C143.266 31.247 71.403-93.101 21.696-179.24c-3.052-5.052-11.868-20.735-14.881-25.774-2.96-5.144-10.684-5.21-13.644 0z" transform="translate(250 250)" style="display:block" id="g"/> | ||||||
|  |       <path d="M-6.816-205.027c-20.92 36.208-170.344 294.875-187.382 324.715-1.947 4.684 1.644 10.592 6.749 10.684 1.368.132 11.815.066 13.815.079 97.651.04 259.272-.026 357.187 0 1.526 0 3.79.013 3.895-.079.618.053 1.105-.198 1.658-.224 4.25-1.276 6.815-6.276 5.091-10.447-.408-.592-.237-.67-1.342-2.486C143.266 31.247 71.403-93.101 21.696-179.24c-3.052-5.052-11.868-20.735-14.881-25.774-2.96-5.144-10.684-5.21-13.644 0z" transform="translate(250 250)" style="display:block" id="b"/> | ||||||
|  |       <path d="M-6.816-205.027c-20.92 36.208-170.344 294.875-187.382 324.715-1.947 4.684 1.644 10.592 6.749 10.684 1.368.132 11.815.066 13.815.079 97.651.04 259.272-.026 357.187 0 1.526 0 3.79.013 3.895-.079.618.053 1.105-.198 1.658-.224 4.25-1.276 6.815-6.276 5.091-10.447-.408-.592-.237-.67-1.342-2.486C143.266 31.247 71.403-93.101 21.696-179.24c-3.052-5.052-11.868-20.735-14.881-25.774-2.96-5.144-10.684-5.21-13.644 0z" transform="translate(250 250)" style="display:block" id="c"/> | ||||||
|  |       <path d="M-6.816-205.027c-20.92 36.208-170.344 294.875-187.382 324.715-1.947 4.684 1.644 10.592 6.749 10.684 1.368.132 11.815.066 13.815.079 97.651.04 259.272-.026 357.187 0 1.526 0 3.79.013 3.895-.079.618.053 1.105-.198 1.658-.224 4.25-1.276 6.815-6.276 5.091-10.447-.408-.592-.237-.67-1.342-2.486C143.266 31.247 71.403-93.101 21.696-179.24c-3.052-5.052-11.868-20.735-14.881-25.774-2.96-5.144-10.684-5.21-13.644 0z" transform="translate(250 250)" style="display:block" id="d"/> | ||||||
|  |       <path d="M-6.816-205.027c-20.92 36.208-170.344 294.875-187.382 324.715-1.947 4.684 1.644 10.592 6.749 10.684 1.368.132 11.815.066 13.815.079 97.651.04 259.272-.026 357.187 0 1.526 0 3.79.013 3.895-.079.618.053 1.105-.198 1.658-.224 4.25-1.276 6.815-6.276 5.091-10.447-.408-.592-.237-.67-1.342-2.486C143.266 31.247 71.403-93.101 21.696-179.24c-3.052-5.052-11.868-20.735-14.881-25.774-2.96-5.144-10.684-5.21-13.644 0z" transform="translate(250 250)" style="display:block" id="e"/> | ||||||
|  |       <path d="M-6.816-205.027c-20.92 36.208-170.344 294.875-187.382 324.715-1.947 4.684 1.644 10.592 6.749 10.684 1.368.132 11.815.066 13.815.079 97.651.04 259.272-.026 357.187 0 1.526 0 3.79.013 3.895-.079.618.053 1.105-.198 1.658-.224 4.25-1.276 6.815-6.276 5.091-10.447-.408-.592-.237-.67-1.342-2.486C143.266 31.247 71.403-93.101 21.696-179.24c-3.052-5.052-11.868-20.735-14.881-25.774-2.96-5.144-10.684-5.21-13.644 0z" transform="translate(250 250)" style="display:block" id="f"/> | ||||||
|  |       <mask id="r" mask-type="alpha"> | ||||||
|  |         <use xlink:href="#a"/> | ||||||
|  |       </mask> | ||||||
|  |       <mask id="p" mask-type="alpha"> | ||||||
|  |         <use xlink:href="#b"/> | ||||||
|  |       </mask> | ||||||
|  |       <mask id="n" mask-type="alpha"> | ||||||
|  |         <use xlink:href="#c"/> | ||||||
|  |       </mask> | ||||||
|  |       <mask id="m" mask-type="alpha"> | ||||||
|  |         <use xlink:href="#d"/> | ||||||
|  |       </mask> | ||||||
|  |       <mask id="l" mask-type="alpha"> | ||||||
|  |         <use xlink:href="#e"/> | ||||||
|  |       </mask> | ||||||
|  |       <mask id="k" mask-type="alpha"> | ||||||
|  |         <use xlink:href="#f"/> | ||||||
|  |       </mask> | ||||||
|  |       <mask id="j" mask-type="alpha"> | ||||||
|  |         <use xlink:href="#g"/> | ||||||
|  |       </mask> | ||||||
|  |       <clipPath id="h"> | ||||||
|  |         <path d="M0 0h500v500H0z"/> | ||||||
|  |       </clipPath> | ||||||
|  |       <clipPath id="i"> | ||||||
|  |         <path d="M0 0h500v500H0z"/> | ||||||
|  |       </clipPath> | ||||||
|  |       <clipPath id="s"> | ||||||
|  |         <path d="M0 0h1920v1080H0z"/> | ||||||
|  |       </clipPath> | ||||||
|  |       <clipPath id="q"> | ||||||
|  |         <path d="M0 0h1920v1080H0z"/> | ||||||
|  |       </clipPath> | ||||||
|  |       <clipPath id="o"> | ||||||
|  |         <path d="M0 0h1920v1080H0z"/> | ||||||
|  |       </clipPath> | ||||||
|  |     </defs> | ||||||
|  |     <g clip-path="url(#h)"> | ||||||
|  |       <g clip-path="url(#i)" transform="translate(0 24)" style="display:block"> | ||||||
|  |         <path d="M-6.816-205.027c-20.92 36.208-170.344 294.875-187.382 324.715-1.947 4.684 1.644 10.592 6.749 10.684 1.368.132 11.815.066 13.815.079 97.651.04 259.272-.026 357.187 0 1.526 0 3.79.013 3.895-.079.618.053 1.105-.198 1.658-.224 4.25-1.276 6.815-6.276 5.091-10.447-.408-.592-.237-.67-1.342-2.486C143.266 31.247 71.403-93.101 21.696-179.24c-3.052-5.052-11.868-20.735-14.881-25.774-2.96-5.144-10.684-5.21-13.644 0zm-20.511-11.842c9.105-16.065 30.997-20.709 45.904-9.762 3.566 2.645 6.513 6.039 8.763 9.762l4.96 8.592 9.921 17.183L200.973 83.875l9.921 17.183c1.671 2.987 3.21 5.25 5.236 9.644 1.605 4.105 2.435 8.539 2.29 12.973-.382 13.381-9.974 25.656-22.881 29.248-4.184 1.184-9.144 1.237-11.986 1.184-98.112-.118-259.378.092-357.187 0-6.723-.237-14.512.763-21.906-1.197-17.788-4.96-27.683-25.183-20.578-42.234 17.486-31.524 167.371-290.153 188.791-327.545" transform="translate(250 250)" style="display:block"/> | ||||||
|  |         <g mask="url(#j)" style="display:block"> | ||||||
|  |           <path d="m202.134 244.025 41.827 72.456c2.684 4.658 9.407 4.658 12.091 0l41.826-72.456c2.684-4.658-.671-10.473-6.039-10.473h-83.652c-5.368 0-8.736 5.815-6.039 10.473zm44.182 47.642-21.525-37.274c-1.645-2.842.408-6.394 3.697-6.394h43.05c3.276 0 5.328 3.552 3.697 6.394l-21.525 37.274c-1.645 2.842-5.736 2.842-7.381 0z"/> | ||||||
|  |         </g> | ||||||
|  |         <g mask="url(#k)" style="display:block"> | ||||||
|  |           <path d="M230.369 47.341c-22.906 53.22-42.589 115.018-23.274 172.146 2.802 7.868 6.288 15.42 10.604 22.591l-12.486 7.302c-4.684-7.881-8.618-16.486-11.657-25.222-17.749-52.128-4.526-108.427 14.486-158.029 3.197-8.184 6.605-16.262 10.157-24.248l12.157 5.447z"/> | ||||||
|  |         </g> | ||||||
|  |         <g mask="url(#l)" style="display:block"> | ||||||
|  |           <path d="M448.039 356.347c-34.642-46.457-78.31-94.402-137.451-106.23-8.21-1.5-16.499-2.263-24.867-2.105l-.079-14.46c9.17-.118 18.578.776 27.669 2.513 54.01 10.697 96.165 50.286 129.61 91.56 5.486 6.855 10.776 13.855 15.92 20.92z"/> | ||||||
|  |         </g> | ||||||
|  |         <g mask="url(#m)" style="display:block"> | ||||||
|  |           <path d="M71.59 390.345c57.549-6.776 120.914-20.617 160.727-65.93 5.408-6.355 10.21-13.158 14.262-20.486l12.565 7.158c-4.487 7.999-9.973 15.709-16.012 22.709-36.274 41.432-91.626 58.141-144.096 66.469a586 586 0 0 1-26.077 3.329l-1.355-13.249z"/> | ||||||
|  |         </g> | ||||||
|  |         <g mask="url(#n)" style="display:block"> | ||||||
|  |           <g clip-path="url(#o)" transform="translate(-710 -290)"> | ||||||
|  |             <path d="m26.6-48.803-69.413 93.109-5.881-7.184 62.386-93.517z" transform="translate(990.142 584.502)" style="display:block"/> | ||||||
|  |             <path d="M-69.58 28.086 57.952-72.23l11.212 14.81-133.09 92.966z" transform="translate(988.235 608.382)" style="display:block"/> | ||||||
|  |             <path d="m-101.789 16.832 210.317-98.614 8.268 18.054-214.415 89.649z" transform="translate(981.847 640.913)" style="display:block"/> | ||||||
|  |             <path d="m-165.267 36.82 324.014-66.518 4.06 20.75-326.033 56.216z" transform="translate(981.847 640.913)" style="display:block"/> | ||||||
|  |           </g> | ||||||
|  |         </g> | ||||||
|  |         <g mask="url(#p)" style="display:block"> | ||||||
|  |           <g clip-path="url(#q)" transform="rotate(120 689.283 205.728)"> | ||||||
|  |             <path d="m26.6-48.803-69.413 93.109-5.881-7.184 62.386-93.517z" transform="translate(990.142 584.502)" style="display:block"/> | ||||||
|  |             <path d="M-69.58 28.086 57.952-72.23l11.212 14.81-133.09 92.966z" transform="translate(988.235 608.382)" style="display:block"/> | ||||||
|  |             <path d="m-101.789 16.832 210.317-98.614 8.268 18.054-214.415 89.649z" transform="translate(981.847 640.913)" style="display:block"/> | ||||||
|  |             <path d="m-165.267 36.82 324.014-66.518 4.06 20.75-326.033 56.216z" transform="translate(981.847 640.913)" style="display:block"/> | ||||||
|  |           </g> | ||||||
|  |         </g> | ||||||
|  |         <g mask="url(#r)" style="display:block"> | ||||||
|  |           <g clip-path="url(#s)" transform="rotate(-120 520.37 615.193)"> | ||||||
|  |             <path d="m26.6-48.803-69.413 93.109-5.881-7.184 62.386-93.517z" transform="translate(990.142 584.502)" style="display:block"/> | ||||||
|  |             <path d="M-69.58 28.086 57.952-72.23l11.212 14.81-133.09 92.966z" transform="translate(988.235 608.382)" style="display:block"/> | ||||||
|  |             <path d="m-101.789 16.832 210.317-98.614 8.268 18.054-214.415 89.649z" transform="translate(981.847 640.913)" style="display:block"/> | ||||||
|  |             <path d="m-165.267 36.82 324.014-66.518 4.06 20.75-326.033 56.216z" transform="translate(981.847 640.913)" style="display:block"/> | ||||||
|  |           </g> | ||||||
|  |         </g> | ||||||
|  |       </g> | ||||||
|  |     </g> | ||||||
|  |   </svg> | ||||||
| After Width: | Height: | Size: 9.0 KiB | 
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	 Andras Bacsai
					Andras Bacsai