Merge branch 'next' into feat--terminal-pty
This commit is contained in:
		@@ -1,16 +1,34 @@
 | 
			
		||||
APP_NAME=Coolify-localhost
 | 
			
		||||
APP_ID=development
 | 
			
		||||
# Coolify Configuration
 | 
			
		||||
APP_ENV=local
 | 
			
		||||
APP_NAME="Coolify Development"
 | 
			
		||||
APP_ID=development
 | 
			
		||||
APP_KEY=
 | 
			
		||||
APP_DEBUG=true
 | 
			
		||||
APP_URL=http://localhost
 | 
			
		||||
APP_PORT=8000
 | 
			
		||||
APP_DEBUG=true
 | 
			
		||||
MUX_ENABLED=false
 | 
			
		||||
 | 
			
		||||
# Enable Laravel Telescope for debugging
 | 
			
		||||
TELESCOPE_ENABLED=false
 | 
			
		||||
 | 
			
		||||
# Selenium Driver URL for Dusk
 | 
			
		||||
DUSK_DRIVER_URL=http://selenium:4444
 | 
			
		||||
 | 
			
		||||
## For Andras only
 | 
			
		||||
# To purge cache
 | 
			
		||||
# PostgreSQL Database Configuration
 | 
			
		||||
DB_DATABASE=coolify
 | 
			
		||||
DB_USERNAME=coolify
 | 
			
		||||
DB_PASSWORD=password
 | 
			
		||||
DB_HOST=host.docker.internal
 | 
			
		||||
DB_PORT=5432
 | 
			
		||||
 | 
			
		||||
# Ray Configuration
 | 
			
		||||
# Set to true to enable Ray
 | 
			
		||||
RAY_ENABLED=false
 | 
			
		||||
# Set custom ray port
 | 
			
		||||
RAY_PORT=
 | 
			
		||||
 | 
			
		||||
# Special Keys for Andras
 | 
			
		||||
# For cache purging
 | 
			
		||||
BUNNY_API_KEY=
 | 
			
		||||
# To upload assets
 | 
			
		||||
# For asset uploads
 | 
			
		||||
BUNNY_STORAGE_API_KEY=
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,16 @@
 | 
			
		||||
# Coolify Configuration
 | 
			
		||||
APP_ID=
 | 
			
		||||
APP_NAME=Coolify
 | 
			
		||||
APP_KEY=
 | 
			
		||||
 | 
			
		||||
# PostgreSQL Database Configuration
 | 
			
		||||
DB_USERNAME=coolify
 | 
			
		||||
DB_PASSWORD=
 | 
			
		||||
 | 
			
		||||
# Redis Configuration
 | 
			
		||||
REDIS_PASSWORD=
 | 
			
		||||
 | 
			
		||||
# Pusher Configuration
 | 
			
		||||
PUSHER_APP_ID=
 | 
			
		||||
PUSHER_APP_KEY=
 | 
			
		||||
PUSHER_APP_SECRET=
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										23
									
								
								.github/workflows/coolify-helper-next.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										23
									
								
								.github/workflows/coolify-helper-next.yml
									
									
									
									
										vendored
									
									
								
							@@ -25,6 +25,10 @@ jobs:
 | 
			
		||||
          registry: ${{ env.REGISTRY }}
 | 
			
		||||
          username: ${{ github.actor }}
 | 
			
		||||
          password: ${{ secrets.GITHUB_TOKEN  }}
 | 
			
		||||
      - name: Get Version
 | 
			
		||||
        id: version
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
 | 
			
		||||
      - name: Build image and push to registry
 | 
			
		||||
        uses: docker/build-push-action@v5
 | 
			
		||||
        with:
 | 
			
		||||
@@ -33,7 +37,9 @@ jobs:
 | 
			
		||||
          file: docker/coolify-helper/Dockerfile
 | 
			
		||||
          platforms: linux/amd64
 | 
			
		||||
          push: true
 | 
			
		||||
          tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
 | 
			
		||||
          tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
 | 
			
		||||
          labels: |
 | 
			
		||||
            coolify.managed=true
 | 
			
		||||
  aarch64:
 | 
			
		||||
    runs-on: [ self-hosted, arm64 ]
 | 
			
		||||
    permissions:
 | 
			
		||||
@@ -47,6 +53,10 @@ jobs:
 | 
			
		||||
          registry: ${{ env.REGISTRY }}
 | 
			
		||||
          username: ${{ github.actor }}
 | 
			
		||||
          password: ${{ secrets.GITHUB_TOKEN  }}
 | 
			
		||||
      - name: Get Version
 | 
			
		||||
        id: version
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
 | 
			
		||||
      - name: Build image and push to registry
 | 
			
		||||
        uses: docker/build-push-action@v5
 | 
			
		||||
        with:
 | 
			
		||||
@@ -55,7 +65,9 @@ jobs:
 | 
			
		||||
          file: docker/coolify-helper/Dockerfile
 | 
			
		||||
          platforms: linux/aarch64
 | 
			
		||||
          push: true
 | 
			
		||||
          tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64
 | 
			
		||||
          tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64
 | 
			
		||||
          labels: |
 | 
			
		||||
            coolify.managed=true
 | 
			
		||||
  merge-manifest:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    permissions:
 | 
			
		||||
@@ -75,10 +87,15 @@ jobs:
 | 
			
		||||
          registry: ${{ env.REGISTRY }}
 | 
			
		||||
          username: ${{ github.actor }}
 | 
			
		||||
          password: ${{ secrets.GITHUB_TOKEN  }}
 | 
			
		||||
      - name: Get Version
 | 
			
		||||
        id: version
 | 
			
		||||
        run: |
 | 
			
		||||
           echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
 | 
			
		||||
      - name: Create & publish manifest
 | 
			
		||||
        run: |
 | 
			
		||||
          docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
 | 
			
		||||
          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
 | 
			
		||||
      - uses: sarisia/actions-status-discord@v1
 | 
			
		||||
        if: always()
 | 
			
		||||
        with:
 | 
			
		||||
          webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL  }}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										24
									
								
								.github/workflows/coolify-helper.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								.github/workflows/coolify-helper.yml
									
									
									
									
										vendored
									
									
								
							@@ -2,7 +2,7 @@ name: Coolify Helper Image (v4)
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches: [ "main" ]
 | 
			
		||||
    branches: [ "main", "next" ]
 | 
			
		||||
    paths:
 | 
			
		||||
      - .github/workflows/coolify-helper.yml
 | 
			
		||||
      - docker/coolify-helper/Dockerfile
 | 
			
		||||
@@ -25,6 +25,10 @@ jobs:
 | 
			
		||||
          registry: ${{ env.REGISTRY }}
 | 
			
		||||
          username: ${{ github.actor }}
 | 
			
		||||
          password: ${{ secrets.GITHUB_TOKEN  }}
 | 
			
		||||
      - name: Get Version
 | 
			
		||||
        id: version
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
 | 
			
		||||
      - name: Build image and push to registry
 | 
			
		||||
        uses: docker/build-push-action@v5
 | 
			
		||||
        with:
 | 
			
		||||
@@ -33,7 +37,9 @@ jobs:
 | 
			
		||||
          file: docker/coolify-helper/Dockerfile
 | 
			
		||||
          platforms: linux/amd64
 | 
			
		||||
          push: true
 | 
			
		||||
          tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
 | 
			
		||||
          tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
 | 
			
		||||
          labels: |
 | 
			
		||||
            coolify.managed=true
 | 
			
		||||
  aarch64:
 | 
			
		||||
    runs-on: [ self-hosted, arm64 ]
 | 
			
		||||
    permissions:
 | 
			
		||||
@@ -47,6 +53,10 @@ jobs:
 | 
			
		||||
          registry: ${{ env.REGISTRY }}
 | 
			
		||||
          username: ${{ github.actor }}
 | 
			
		||||
          password: ${{ secrets.GITHUB_TOKEN  }}
 | 
			
		||||
      - name: Get Version
 | 
			
		||||
        id: version
 | 
			
		||||
        run: |
 | 
			
		||||
           echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
 | 
			
		||||
      - name: Build image and push to registry
 | 
			
		||||
        uses: docker/build-push-action@v5
 | 
			
		||||
        with:
 | 
			
		||||
@@ -55,7 +65,9 @@ jobs:
 | 
			
		||||
          file: docker/coolify-helper/Dockerfile
 | 
			
		||||
          platforms: linux/aarch64
 | 
			
		||||
          push: true
 | 
			
		||||
          tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64
 | 
			
		||||
          tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
 | 
			
		||||
          labels: |
 | 
			
		||||
            coolify.managed=true
 | 
			
		||||
  merge-manifest:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    permissions:
 | 
			
		||||
@@ -75,9 +87,13 @@ jobs:
 | 
			
		||||
          registry: ${{ env.REGISTRY }}
 | 
			
		||||
          username: ${{ github.actor }}
 | 
			
		||||
          password: ${{ secrets.GITHUB_TOKEN  }}
 | 
			
		||||
      - name: Get Version
 | 
			
		||||
        id: version
 | 
			
		||||
        run: |
 | 
			
		||||
           echo "VERSION=$(docker run  --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
 | 
			
		||||
      - name: Create & publish manifest
 | 
			
		||||
        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.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:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/production-build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/production-build.yml
									
									
									
									
										vendored
									
									
								
							@@ -4,6 +4,8 @@ on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches: ["main"]
 | 
			
		||||
    paths-ignore:
 | 
			
		||||
      - .github/workflows/coolify-helper.yml
 | 
			
		||||
      - docker/coolify-helper/Dockerfile
 | 
			
		||||
      - templates/service-templates.json
 | 
			
		||||
 | 
			
		||||
env:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										189
									
								
								CONTRIBUTING.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								CONTRIBUTING.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,189 @@
 | 
			
		||||
# Contributing
 | 
			
		||||
 | 
			
		||||
> "First, thanks for considering contributing to my project. It really means a lot!" - [@andrasbacsai](https://github.com/andrasbacsai)
 | 
			
		||||
 | 
			
		||||
You can ask for guidance anytime on our [Discord server](https://coollabs.io/discord) in the `#contribute` channel.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Code Contribution
 | 
			
		||||
 | 
			
		||||
## 1. Setup your development environment 
 | 
			
		||||
 | 
			
		||||
Follow the steps below for your operating system:
 | 
			
		||||
 | 
			
		||||
### Windows
 | 
			
		||||
 | 
			
		||||
1. Install `docker-ce`, Docker Desktop (or similar):
 | 
			
		||||
   - Docker CE (recommended):
 | 
			
		||||
     - Install Windows Subsystem for Linux v2 (WSL2) by following this guide: [Install WSL](https://learn.microsoft.com/en-us/windows/wsl/install)
 | 
			
		||||
     - After installing WSL2, install Docker CE for your Linux distribution by following this guide: [Install Docker Engine](https://docs.docker.com/engine/install/)
 | 
			
		||||
     - Make sure to choose the appropriate Linux distribution (e.g., Ubuntu) when following the Docker installation guide
 | 
			
		||||
   - Install Docker Desktop (easier):
 | 
			
		||||
     - Download and install [Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/)
 | 
			
		||||
     - Ensure WSL2 backend is enabled in Docker Desktop settings
 | 
			
		||||
 | 
			
		||||
2. Install Spin:
 | 
			
		||||
   - Follow the instructions to install Spin on Windows from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-windows#download-and-install-spin-into-wsl2)
 | 
			
		||||
 | 
			
		||||
### MacOS
 | 
			
		||||
 | 
			
		||||
1. Install Orbstack, Docker Desktop (or similar):
 | 
			
		||||
   - Orbstack (recommended, as it is a faster and lighter alternative to Docker Desktop):
 | 
			
		||||
     - Download and install [Orbstack](https://docs.orbstack.dev/quick-start#installation)
 | 
			
		||||
   - Docker Desktop:
 | 
			
		||||
     - Download and install [Docker Desktop for Mac](https://docs.docker.com/desktop/install/mac-install/)
 | 
			
		||||
 | 
			
		||||
2. Install Spin:
 | 
			
		||||
   - Follow the instructions to install Spin on MacOS from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-macos/#download-and-install-spin)
 | 
			
		||||
 | 
			
		||||
### Linux
 | 
			
		||||
 | 
			
		||||
1. Install Docker Engine, Docker Desktop (or similar):
 | 
			
		||||
   - Docker Engine (recommended, as there is no VM overhead):
 | 
			
		||||
     - Follow the official [Docker Engine installation guide](https://docs.docker.com/engine/install/) for your Linux distribution
 | 
			
		||||
   - Docker Desktop:
 | 
			
		||||
     - If you want a GUI, you can use [Docker Desktop for Linux](https://docs.docker.com/desktop/install/linux-install/)
 | 
			
		||||
 | 
			
		||||
2. Install Spin:
 | 
			
		||||
   - Follow the instructions to install Spin on Linux from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-linux#configure-docker-permissions)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 2. Verify installation (optional)
 | 
			
		||||
 | 
			
		||||
After installing Docker (or Orbstack) and Spin, verify the installation:
 | 
			
		||||
 | 
			
		||||
1. Open a terminal or command prompt
 | 
			
		||||
2. Run the following commands:
 | 
			
		||||
   ```bash
 | 
			
		||||
   docker --version
 | 
			
		||||
   spin --version
 | 
			
		||||
   ```
 | 
			
		||||
   You should see version information for both Docker and Spin.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 3. Fork the Coolify repository and setup your local repository
 | 
			
		||||
 | 
			
		||||
1. Fork the [Coolify](https://github.com/coollabsio/coolify) repository to your GitHub account.
 | 
			
		||||
 | 
			
		||||
2. Install a code editor on your machine (below are some popular choices, choose one):
 | 
			
		||||
 | 
			
		||||
   - Visual Studio Code (recommended free):
 | 
			
		||||
     - Windows/macOS/Linux: Download and install from [https://code.visualstudio.com/download](https://code.visualstudio.com/download)
 | 
			
		||||
 | 
			
		||||
   - Cursor (recommended but paid for getting the full benefits):
 | 
			
		||||
     - Windows/macOS/Linux: Download and install from [https://www.cursor.com/](https://www.cursor.com/)
 | 
			
		||||
 | 
			
		||||
   - Zed (very fast code editor):
 | 
			
		||||
     - macOS/Linux: Download and install from [https://zed.dev/download](https://zed.dev/download)
 | 
			
		||||
     - Windows: Not available yet
 | 
			
		||||
 | 
			
		||||
3. Clone the Coolify Repository from your fork to your local machine
 | 
			
		||||
   - Use `git clone` in the command line
 | 
			
		||||
   - Use GitHub Desktop (recommended):
 | 
			
		||||
     - Download and install from [https://desktop.github.com/](https://desktop.github.com/)
 | 
			
		||||
     - Open GitHub Desktop and login with your GitHub account
 | 
			
		||||
     - Click on `File` -> `Clone Repository` select `github.com` as the repository location, then select your forked Coolify repository, choose the local path and then click `Clone`
 | 
			
		||||
 | 
			
		||||
4. Open the cloned Coolify Repository in your chosen code editor.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 4. Set up Environment Variables
 | 
			
		||||
 | 
			
		||||
1. In the Code Editor, locate the `.env.development.example` file in the root directory of your local Coolify repository.
 | 
			
		||||
 | 
			
		||||
2. Duplicate the `.env.development.example` file and rename the copy to `.env`.
 | 
			
		||||
 | 
			
		||||
3. Open the new `.env` file and review its contents. Adjust any environment variables as needed for your development setup.
 | 
			
		||||
 | 
			
		||||
4. If you encounter errors during database migrations, update the database connection settings in your `.env` file. Use the IP address or hostname of your PostgreSQL database container. You can find this information by running `docker ps` after executing `spin up`.
 | 
			
		||||
 | 
			
		||||
5. Save the changes to your `.env` file.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 5. Start Coolify
 | 
			
		||||
 | 
			
		||||
1. Open a terminal in the local Coolify directory.
 | 
			
		||||
 | 
			
		||||
2. Run the following command in the terminal (leave that terminal open):
 | 
			
		||||
   ```
 | 
			
		||||
   spin up
 | 
			
		||||
   ```
 | 
			
		||||
   Note: You may see some errors, but don't worry; this is expected.
 | 
			
		||||
 | 
			
		||||
3. If you encounter permission errors, especially on macOS, use:
 | 
			
		||||
   ```
 | 
			
		||||
   sudo spin up
 | 
			
		||||
   ```
 | 
			
		||||
 | 
			
		||||
Note: If you change environment variables afterwards or anything seems broken, press Ctrl + C to stop the process and run `spin up` again.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 6. Start Development
 | 
			
		||||
 | 
			
		||||
1. Access your Coolify instance:
 | 
			
		||||
   - URL: `http://localhost:8000`
 | 
			
		||||
   - Login: `test@example.com`
 | 
			
		||||
   - Password: `password`
 | 
			
		||||
 | 
			
		||||
2. Additional development tools:
 | 
			
		||||
   - Laravel Horizon (scheduler): `http://localhost:8000/horizon`
 | 
			
		||||
     Note: Only accessible when logged in as root user
 | 
			
		||||
   - Mailpit (email catcher): `http://localhost:8025`
 | 
			
		||||
   - Telescope (debugging tool): `http://localhost:8000/telescope` 
 | 
			
		||||
     Note: Disabled by default (so the database is not overloaded), enable by adding the following environment variable to your `.env` file:
 | 
			
		||||
     ```env
 | 
			
		||||
     TELESCOPE_ENABLED=true
 | 
			
		||||
     ```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 7. Development Notes
 | 
			
		||||
 | 
			
		||||
When working on Coolify, keep the following in mind:
 | 
			
		||||
 | 
			
		||||
1. **Database Migrations**: After switching branches or making changes to the database structure, always run migrations:
 | 
			
		||||
   ```bash
 | 
			
		||||
   docker exec -it coolify php artisan migrate
 | 
			
		||||
   ```
 | 
			
		||||
 | 
			
		||||
2. **Resetting Development Setup**: To reset your development setup to a clean database with default values:
 | 
			
		||||
   ```bash
 | 
			
		||||
   docker exec -it coolify php artisan migrate:fresh --seed
 | 
			
		||||
   ```
 | 
			
		||||
 | 
			
		||||
3. **Troubleshooting**: If you encounter unexpected behavior, ensure your database is up-to-date with the latest migrations and if possible reset the development setup to eliminate any envrionement specific issues.
 | 
			
		||||
 | 
			
		||||
Remember, forgetting to migrate the database can cause problems, so make it a habit to run migrations after pulling changes or switching branches.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 8. Contributing a New Service
 | 
			
		||||
 | 
			
		||||
To add a new service to Coolify, please refer to our documentation:
 | 
			
		||||
[Adding a New Service](https://coolify.io/docs/knowledge-base/add-a-service)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 9. Create a Pull Request
 | 
			
		||||
 | 
			
		||||
1. After making changes or adding a new service:
 | 
			
		||||
   - Commit your changes to your forked repository.
 | 
			
		||||
   - Push the changes to your GitHub account.
 | 
			
		||||
 | 
			
		||||
2. Creating the Pull Request (PR):
 | 
			
		||||
   - Navigate to the main Coolify repository on GitHub.
 | 
			
		||||
   - Click the "Pull requests" tab.
 | 
			
		||||
   - Click the green "New pull request" button.
 | 
			
		||||
   - Choose your fork and branch as the compare branch.
 | 
			
		||||
   - Click "Create pull request".
 | 
			
		||||
 | 
			
		||||
3. Filling out the PR details:
 | 
			
		||||
   - Give your PR a descriptive title.
 | 
			
		||||
   - In the description, explain the changes you've made.
 | 
			
		||||
   - Reference any related issues by using keywords like "Fixes #123" or "Closes #456".
 | 
			
		||||
 | 
			
		||||
4. Important note:
 | 
			
		||||
   Always set the base branch for your PR to the `next` branch of the Coolify repository, not the `main` branch.
 | 
			
		||||
 | 
			
		||||
5. Submit your PR:
 | 
			
		||||
   - Review your changes one last time.
 | 
			
		||||
   - Click "Create pull request" to submit.
 | 
			
		||||
 | 
			
		||||
After submission, maintainers will review your PR and may request changes or provide feedback.
 | 
			
		||||
@@ -1,34 +0,0 @@
 | 
			
		||||
# Contributing
 | 
			
		||||
 | 
			
		||||
> "First, thanks for considering to contribute to my project. 
 | 
			
		||||
  It really means a lot!" - [@andrasbacsai](https://github.com/andrasbacsai)
 | 
			
		||||
 | 
			
		||||
You can ask for guidance anytime on our 
 | 
			
		||||
[Discord server](https://coollabs.io/discord) in the `#contribution` channel.
 | 
			
		||||
 | 
			
		||||
## Code Contribution
 | 
			
		||||
 | 
			
		||||
### 1) Setup your development environment
 | 
			
		||||
 | 
			
		||||
- You need to have Docker Engine (or equivalent) [installed](https://docs.docker.com/engine/install/) on your system.
 | 
			
		||||
- For better DX, install [Spin](https://serversideup.net/open-source/spin/).
 | 
			
		||||
 | 
			
		||||
### 2) Set your environment variables
 | 
			
		||||
 | 
			
		||||
- Copy [.env.development.example](./.env.development.example) to .env.
 | 
			
		||||
 | 
			
		||||
## 3) Start & setup Coolify
 | 
			
		||||
 | 
			
		||||
- Run `spin up` - You can notice that errors will be thrown. Don't worry.
 | 
			
		||||
  - If you see weird permission errors, especially on Mac, run `sudo spin up` instead. 
 | 
			
		||||
 | 
			
		||||
### 4) Start development
 | 
			
		||||
You can login your Coolify instance at `localhost:8000` with `test@example.com` and `password`.
 | 
			
		||||
 | 
			
		||||
Your horizon (Laravel scheduler): `localhost:8000/horizon` - Only reachable if you logged in with root user.
 | 
			
		||||
 | 
			
		||||
Mails are caught by Mailpit: `localhost:8025`
 | 
			
		||||
 | 
			
		||||
## New Service Contribution
 | 
			
		||||
Check out the docs [here](https://coolify.io/docs/knowledge-base/add-a-service).
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										43
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								README.md
									
									
									
									
									
								
							@@ -35,20 +35,32 @@ Thank you so much!
 | 
			
		||||
 | 
			
		||||
Special thanks to our biggest sponsors!
 | 
			
		||||
 | 
			
		||||
<a href="https://cccareers.org/" target="_blank"><img src="./other/logos/ccc-logo.webp" alt="cccareers logo" width="200"/></a>
 | 
			
		||||
<a href="http://htznr.li/CoolifyXHetzner" target="_blank"><img src="./other/logos/hetzner.jpg" alt="hetzner logo" width="150"/></a>
 | 
			
		||||
<a href="https://logto.io/?ref=coolify" target="_blank"><img src="./other/logos/logto.webp" alt="logto logo" width="150"/></a>
 | 
			
		||||
<a href="https://bc.direct/?ref=coolify.io" target="_blank"><img src="./other/logos/bc.png" alt="bc direct logo" width="200"/></a>
 | 
			
		||||
<a href="https://www.quantcdn.io/?ref=coolify.io" target="_blank"><img src="./other/logos/quant.svg" alt="quantcdn logo" width="150"/></a>
 | 
			
		||||
<a href="https://arcjet.com/?ref=coolify.io" target="_blank"><img src="./other/logos/arcjet.svg" alt="arcjet logo" width="200"/></a>
 | 
			
		||||
<a href="https://supa.guide/?ref=coolify.io" target="_blank"><img src="./other/logos/supaguide.png" alt="supaguide logo" width="200"/></a>
 | 
			
		||||
<a href="https://tigrisdata.com/?ref=coolify.io" target="_blank"><img src="./other/logos/tigris.svg" alt="tigris logo" width="140"/></a>
 | 
			
		||||
<a href="https://fractalnetworks.co/?ref=coolify.io" target="_blank"><img src="./other/logos/fractal.svg" alt="fractal logo" width="180"/></a>
 | 
			
		||||
<a href="https://coolify.ad.vin/?ref=coolify.io" target="_blank"><img src="./other/logos/advin.png" alt="advin logo" width="250"/></a>
 | 
			
		||||
<a href="https://trieve.ai/?ref=coolify.io" target="_blank"><img src="./other/logos/trieve_bg.png" alt="trieve logo" width="180"/></a>
 | 
			
		||||
<a href="https://blacksmith.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/blacksmith.svg" alt="blacksmith logo" width="200"/></a>
 | 
			
		||||
<a href="https://latitude.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/latitude.svg" alt="latitude logo" width="200"/></a>
 | 
			
		||||
<a href="https://brand.dev/?ref=coolify.io" target="_blank"><img src="./other/logos/branddev.png" alt="branddev logo" width="200"/></a>
 | 
			
		||||
### Special Sponsors
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
* [CCCareers](https://cccareers.org/) - A career development platform connecting coding bootcamp graduates with job opportunities in the tech industry.
 | 
			
		||||
* [Hetzner](http://htznr.li/CoolifyXHetzner) - A German web hosting company offering affordable dedicated servers, cloud services, and web hosting solutions.
 | 
			
		||||
* [Logto](https://logto.io/?ref=coolify) - An open-source authentication and authorization solution for building secure login systems and managing user identities.
 | 
			
		||||
* [BC Direct](https://bc.direct/?ref=coolify.io) - A digital marketing agency specializing in e-commerce solutions and online business growth strategies.
 | 
			
		||||
* [QuantCDN](https://www.quantcdn.io/?ref=coolify.io) - A content delivery network (CDN) optimizing website performance through global content distribution.
 | 
			
		||||
* [Arcjet](https://arcjet.com/?ref=coolify.io) - A cloud-based platform providing real-time protection against API abuse and bot attacks.
 | 
			
		||||
* [SupaGuide](https://supa.guide/?ref=coolify.io) - A comprehensive resource hub offering guides and tutorials for web development using Supabase.
 | 
			
		||||
* [Tigris](https://tigrisdata.com/?ref=coolify.io) - A fully managed serverless object storage service compatible with Amazon S3 API. Offers high performance, scalability, and built-in search capabilities for efficient data management.
 | 
			
		||||
* [Fractal Networks](https://fractalnetworks.co/?ref=coolify.io) - A decentralized network infrastructure company focusing on secure and private communication solutions.
 | 
			
		||||
* [Advin](https://coolify.ad.vin/?ref=coolify.io) - A digital advertising agency specializing in programmatic advertising and data-driven marketing strategies.
 | 
			
		||||
* [Treive](https://trieve.ai/?ref=coolify.io) - An AI-powered search and discovery platform for enhancing information retrieval in large datasets.
 | 
			
		||||
* [Blacksmith](https://blacksmith.sh/?ref=coolify.io) - A cloud-native platform for automating infrastructure provisioning and management across multiple cloud providers.
 | 
			
		||||
* [Latitude](https://latitude.sh/?ref=coolify.io) - A cloud computing platform offering bare metal servers and cloud instances for developers and businesses.
 | 
			
		||||
* [Brand Dev](https://brand.dev/?ref=coolify.io) - A web development agency specializing in creating custom digital experiences and brand identities.
 | 
			
		||||
* [Jobscollider](https://jobscollider.com/remote-jobs?ref=coolify.io) - A job search platform connecting professionals with remote work opportunities across various industries.
 | 
			
		||||
* [Hostinger](https://www.hostinger.com/vps/coolify-hosting?ref=coolify.io) - A web hosting provider offering affordable hosting solutions, domain registration, and website building tools.
 | 
			
		||||
* [Glueops](https://www.glueops.dev/?ref=coolify.io) - A DevOps consulting company providing infrastructure automation and cloud optimization services.
 | 
			
		||||
* [Ubicloud](https://ubicloud.com/?ref=coolify.io) - An open-source alternative to hyperscale cloud providers, offering high-performance cloud computing services.
 | 
			
		||||
* [Juxtdigital](https://juxtdigital.dev/?ref=coolify.io) - A digital agency offering web development, design, and digital marketing services for businesses.
 | 
			
		||||
* [Saasykit](https://saasykit.com/?ref=coolify.io) - A Laravel-based boilerplate providing essential components and features for building SaaS applications quickly.
 | 
			
		||||
* [Massivegrid](https://massivegrid.com/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Github Sponsors ($40+)
 | 
			
		||||
<a href="https://serpapi.com/?ref=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a>
 | 
			
		||||
@@ -71,8 +83,11 @@ Special thanks to our biggest sponsors!
 | 
			
		||||
<a href="https://github.com/aniftyco"><img src="https://github.com/aniftyco.png" width="60px" alt="NiftyCo" /></a>
 | 
			
		||||
<a href="https://github.com/iujlaki"><img src="https://github.com/iujlaki.png" width="60px" alt="Imre Ujlaki" /></a>
 | 
			
		||||
<a href="https://il.ly"><img src="https://github.com/Illyism.png" width="60px" alt="Ilias Ism" /></a>
 | 
			
		||||
<a href="https://www.breakcold.com/?utm_source=coolify.io"><img src="https://github.com/breakcold.png" width="60px" alt="Breakcold" /></a>
 | 
			
		||||
<a href="https://github.com/urtho"><img src="https://github.com/urtho.png" width="60px" alt="Paweł Pierścionek" /></a>
 | 
			
		||||
<a href="https://github.com/monocursive"><img src="https://github.com/monocursive.png" width="60px" alt="Michael Mazurczak" /></a>
 | 
			
		||||
<a href="https://formbricks.com/?utm_source=coolify.io"><img src="https://github.com/formbricks.png" width="60px" alt="Formbricks" /></a>
 | 
			
		||||
<a href="https://x.com/adithsuhas17?utm_source=coolify.io"><img src="https://github.com/adith-suhas-sv.png" width="60px" alt="Adith Suhas" /></a>
 | 
			
		||||
 | 
			
		||||
## Organizations
 | 
			
		||||
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
 | 
			
		||||
 
 | 
			
		||||
@@ -79,14 +79,7 @@ class StartClickhouse
 | 
			
		||||
            data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
 | 
			
		||||
        }
 | 
			
		||||
        if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
 | 
			
		||||
            $docker_compose['services'][$container_name]['logging'] = [
 | 
			
		||||
                'driver' => 'fluentd',
 | 
			
		||||
                'options' => [
 | 
			
		||||
                    'fluentd-address' => 'tcp://127.0.0.1:24224',
 | 
			
		||||
                    'fluentd-async' => 'true',
 | 
			
		||||
                    'fluentd-sub-second-precision' => 'true',
 | 
			
		||||
                ],
 | 
			
		||||
            ];
 | 
			
		||||
            $docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
 | 
			
		||||
        }
 | 
			
		||||
        if (count($this->database->ports_mappings_array) > 0) {
 | 
			
		||||
            $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
 | 
			
		||||
@@ -102,6 +95,11 @@ class StartClickhouse
 | 
			
		||||
        if (count($volume_names) > 0) {
 | 
			
		||||
            $docker_compose['volumes'] = $volume_names;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Add custom docker run options
 | 
			
		||||
        $docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
 | 
			
		||||
        $docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
 | 
			
		||||
 | 
			
		||||
        $docker_compose = Yaml::dump($docker_compose, 10);
 | 
			
		||||
        $docker_compose_base64 = base64_encode($docker_compose);
 | 
			
		||||
        $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
 | 
			
		||||
@@ -162,6 +160,8 @@ class StartClickhouse
 | 
			
		||||
            $environment_variables->push("CLICKHOUSE_ADMIN_PASSWORD={$this->database->clickhouse_admin_password}");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
 | 
			
		||||
 | 
			
		||||
        return $environment_variables->all();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ class StartDragonfly
 | 
			
		||||
        $startCommand = "dragonfly --requirepass {$this->database->dragonfly_password}";
 | 
			
		||||
 | 
			
		||||
        $container_name = $this->database->uuid;
 | 
			
		||||
        $this->configuration_dir = database_configuration_dir().'/'.$container_name;
 | 
			
		||||
        $this->configuration_dir = database_configuration_dir() . '/' . $container_name;
 | 
			
		||||
 | 
			
		||||
        $this->commands = [
 | 
			
		||||
            "echo 'Starting {$database->name}.'",
 | 
			
		||||
@@ -75,18 +75,11 @@ class StartDragonfly
 | 
			
		||||
                ],
 | 
			
		||||
            ],
 | 
			
		||||
        ];
 | 
			
		||||
        if (! is_null($this->database->limits_cpuset)) {
 | 
			
		||||
        if (!is_null($this->database->limits_cpuset)) {
 | 
			
		||||
            data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
 | 
			
		||||
        }
 | 
			
		||||
        if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
 | 
			
		||||
            $docker_compose['services'][$container_name]['logging'] = [
 | 
			
		||||
                'driver' => 'fluentd',
 | 
			
		||||
                'options' => [
 | 
			
		||||
                    'fluentd-address' => 'tcp://127.0.0.1:24224',
 | 
			
		||||
                    'fluentd-async' => 'true',
 | 
			
		||||
                    'fluentd-sub-second-precision' => 'true',
 | 
			
		||||
                ],
 | 
			
		||||
            ];
 | 
			
		||||
            $docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
 | 
			
		||||
        }
 | 
			
		||||
        if (count($this->database->ports_mappings_array) > 0) {
 | 
			
		||||
            $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
 | 
			
		||||
@@ -102,6 +95,11 @@ class StartDragonfly
 | 
			
		||||
        if (count($volume_names) > 0) {
 | 
			
		||||
            $docker_compose['volumes'] = $volume_names;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Add custom docker run options
 | 
			
		||||
        $docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
 | 
			
		||||
        $docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
 | 
			
		||||
 | 
			
		||||
        $docker_compose = Yaml::dump($docker_compose, 10);
 | 
			
		||||
        $docker_compose_base64 = base64_encode($docker_compose);
 | 
			
		||||
        $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
 | 
			
		||||
@@ -120,10 +118,10 @@ class StartDragonfly
 | 
			
		||||
        $local_persistent_volumes = [];
 | 
			
		||||
        foreach ($this->database->persistentStorages as $persistentStorage) {
 | 
			
		||||
            if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
 | 
			
		||||
                $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
 | 
			
		||||
                $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
 | 
			
		||||
            } else {
 | 
			
		||||
                $volume_name = $persistentStorage->name;
 | 
			
		||||
                $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
 | 
			
		||||
                $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -154,7 +152,7 @@ class StartDragonfly
 | 
			
		||||
            $environment_variables->push("$env->key=$env->real_value");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
 | 
			
		||||
        if ($environment_variables->filter(fn($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
 | 
			
		||||
            $environment_variables->push("REDIS_PASSWORD={$this->database->dragonfly_password}");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ class StartKeydb
 | 
			
		||||
        $startCommand = "keydb-server --requirepass {$this->database->keydb_password} --appendonly yes";
 | 
			
		||||
 | 
			
		||||
        $container_name = $this->database->uuid;
 | 
			
		||||
        $this->configuration_dir = database_configuration_dir().'/'.$container_name;
 | 
			
		||||
        $this->configuration_dir = database_configuration_dir() . '/' . $container_name;
 | 
			
		||||
 | 
			
		||||
        $this->commands = [
 | 
			
		||||
            "echo 'Starting {$database->name}.'",
 | 
			
		||||
@@ -74,18 +74,11 @@ class StartKeydb
 | 
			
		||||
                ],
 | 
			
		||||
            ],
 | 
			
		||||
        ];
 | 
			
		||||
        if (! is_null($this->database->limits_cpuset)) {
 | 
			
		||||
        if (!is_null($this->database->limits_cpuset)) {
 | 
			
		||||
            data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
 | 
			
		||||
        }
 | 
			
		||||
        if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
 | 
			
		||||
            $docker_compose['services'][$container_name]['logging'] = [
 | 
			
		||||
                'driver' => 'fluentd',
 | 
			
		||||
                'options' => [
 | 
			
		||||
                    'fluentd-address' => 'tcp://127.0.0.1:24224',
 | 
			
		||||
                    'fluentd-async' => 'true',
 | 
			
		||||
                    'fluentd-sub-second-precision' => 'true',
 | 
			
		||||
                ],
 | 
			
		||||
            ];
 | 
			
		||||
            $docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
 | 
			
		||||
        }
 | 
			
		||||
        if (count($this->database->ports_mappings_array) > 0) {
 | 
			
		||||
            $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
 | 
			
		||||
@@ -101,15 +94,19 @@ class StartKeydb
 | 
			
		||||
        if (count($volume_names) > 0) {
 | 
			
		||||
            $docker_compose['volumes'] = $volume_names;
 | 
			
		||||
        }
 | 
			
		||||
        if (! is_null($this->database->keydb_conf) || ! empty($this->database->keydb_conf)) {
 | 
			
		||||
        if (!is_null($this->database->keydb_conf) || !empty($this->database->keydb_conf)) {
 | 
			
		||||
            $docker_compose['services'][$container_name]['volumes'][] = [
 | 
			
		||||
                'type' => 'bind',
 | 
			
		||||
                'source' => $this->configuration_dir.'/keydb.conf',
 | 
			
		||||
                'source' => $this->configuration_dir . '/keydb.conf',
 | 
			
		||||
                'target' => '/etc/keydb/keydb.conf',
 | 
			
		||||
                'read_only' => true,
 | 
			
		||||
            ];
 | 
			
		||||
            $docker_compose['services'][$container_name]['command'] = "keydb-server /etc/keydb/keydb.conf --requirepass {$this->database->keydb_password} --appendonly yes";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Add custom docker run options
 | 
			
		||||
        $docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
 | 
			
		||||
        $docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
 | 
			
		||||
        $docker_compose = Yaml::dump($docker_compose, 10);
 | 
			
		||||
        $docker_compose_base64 = base64_encode($docker_compose);
 | 
			
		||||
        $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
 | 
			
		||||
@@ -128,10 +125,10 @@ class StartKeydb
 | 
			
		||||
        $local_persistent_volumes = [];
 | 
			
		||||
        foreach ($this->database->persistentStorages as $persistentStorage) {
 | 
			
		||||
            if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
 | 
			
		||||
                $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
 | 
			
		||||
                $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
 | 
			
		||||
            } else {
 | 
			
		||||
                $volume_name = $persistentStorage->name;
 | 
			
		||||
                $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
 | 
			
		||||
                $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -162,10 +159,12 @@ class StartKeydb
 | 
			
		||||
            $environment_variables->push("$env->key=$env->real_value");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
 | 
			
		||||
        if ($environment_variables->filter(fn($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
 | 
			
		||||
            $environment_variables->push("REDIS_PASSWORD={$this->database->keydb_password}");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
 | 
			
		||||
 | 
			
		||||
        return $environment_variables->all();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ class StartMariadb
 | 
			
		||||
        $this->database = $database;
 | 
			
		||||
 | 
			
		||||
        $container_name = $this->database->uuid;
 | 
			
		||||
        $this->configuration_dir = database_configuration_dir().'/'.$container_name;
 | 
			
		||||
        $this->configuration_dir = database_configuration_dir() . '/' . $container_name;
 | 
			
		||||
 | 
			
		||||
        $this->commands = [
 | 
			
		||||
            "echo 'Starting {$database->name}.'",
 | 
			
		||||
@@ -69,18 +69,11 @@ class StartMariadb
 | 
			
		||||
                ],
 | 
			
		||||
            ],
 | 
			
		||||
        ];
 | 
			
		||||
        if (! is_null($this->database->limits_cpuset)) {
 | 
			
		||||
        if (!is_null($this->database->limits_cpuset)) {
 | 
			
		||||
            data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
 | 
			
		||||
        }
 | 
			
		||||
        if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
 | 
			
		||||
            $docker_compose['services'][$container_name]['logging'] = [
 | 
			
		||||
                'driver' => 'fluentd',
 | 
			
		||||
                'options' => [
 | 
			
		||||
                    'fluentd-address' => 'tcp://127.0.0.1:24224',
 | 
			
		||||
                    'fluentd-async' => 'true',
 | 
			
		||||
                    'fluentd-sub-second-precision' => 'true',
 | 
			
		||||
                ],
 | 
			
		||||
            ];
 | 
			
		||||
            $docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
 | 
			
		||||
        }
 | 
			
		||||
        if (count($this->database->ports_mappings_array) > 0) {
 | 
			
		||||
            $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
 | 
			
		||||
@@ -96,14 +89,19 @@ class StartMariadb
 | 
			
		||||
        if (count($volume_names) > 0) {
 | 
			
		||||
            $docker_compose['volumes'] = $volume_names;
 | 
			
		||||
        }
 | 
			
		||||
        if (! is_null($this->database->mariadb_conf) || ! empty($this->database->mariadb_conf)) {
 | 
			
		||||
        if (!is_null($this->database->mariadb_conf) || !empty($this->database->mariadb_conf)) {
 | 
			
		||||
            $docker_compose['services'][$container_name]['volumes'][] = [
 | 
			
		||||
                'type' => 'bind',
 | 
			
		||||
                'source' => $this->configuration_dir.'/custom-config.cnf',
 | 
			
		||||
                'source' => $this->configuration_dir . '/custom-config.cnf',
 | 
			
		||||
                'target' => '/etc/mysql/conf.d/custom-config.cnf',
 | 
			
		||||
                'read_only' => true,
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Add custom docker run options
 | 
			
		||||
        $docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
 | 
			
		||||
        $docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
 | 
			
		||||
 | 
			
		||||
        $docker_compose = Yaml::dump($docker_compose, 10);
 | 
			
		||||
        $docker_compose_base64 = base64_encode($docker_compose);
 | 
			
		||||
        $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
 | 
			
		||||
@@ -122,10 +120,10 @@ class StartMariadb
 | 
			
		||||
        $local_persistent_volumes = [];
 | 
			
		||||
        foreach ($this->database->persistentStorages as $persistentStorage) {
 | 
			
		||||
            if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
 | 
			
		||||
                $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
 | 
			
		||||
                $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
 | 
			
		||||
            } else {
 | 
			
		||||
                $volume_name = $persistentStorage->name;
 | 
			
		||||
                $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
 | 
			
		||||
                $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -156,21 +154,23 @@ class StartMariadb
 | 
			
		||||
            $environment_variables->push("$env->key=$env->real_value");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) {
 | 
			
		||||
        if ($environment_variables->filter(fn($env) => str($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) {
 | 
			
		||||
            $environment_variables->push("MARIADB_ROOT_PASSWORD={$this->database->mariadb_root_password}");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_DATABASE'))->isEmpty()) {
 | 
			
		||||
        if ($environment_variables->filter(fn($env) => str($env)->contains('MARIADB_DATABASE'))->isEmpty()) {
 | 
			
		||||
            $environment_variables->push("MARIADB_DATABASE={$this->database->mariadb_database}");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_USER'))->isEmpty()) {
 | 
			
		||||
        if ($environment_variables->filter(fn($env) => str($env)->contains('MARIADB_USER'))->isEmpty()) {
 | 
			
		||||
            $environment_variables->push("MARIADB_USER={$this->database->mariadb_user}");
 | 
			
		||||
        }
 | 
			
		||||
        if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_PASSWORD'))->isEmpty()) {
 | 
			
		||||
        if ($environment_variables->filter(fn($env) => str($env)->contains('MARIADB_PASSWORD'))->isEmpty()) {
 | 
			
		||||
            $environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
 | 
			
		||||
 | 
			
		||||
        return $environment_variables->all();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ class StartMongodb
 | 
			
		||||
        $startCommand = 'mongod';
 | 
			
		||||
 | 
			
		||||
        $container_name = $this->database->uuid;
 | 
			
		||||
        $this->configuration_dir = database_configuration_dir().'/'.$container_name;
 | 
			
		||||
        $this->configuration_dir = database_configuration_dir() . '/' . $container_name;
 | 
			
		||||
 | 
			
		||||
        $this->commands = [
 | 
			
		||||
            "echo 'Starting {$database->name}.'",
 | 
			
		||||
@@ -77,18 +77,11 @@ class StartMongodb
 | 
			
		||||
                ],
 | 
			
		||||
            ],
 | 
			
		||||
        ];
 | 
			
		||||
        if (! is_null($this->database->limits_cpuset)) {
 | 
			
		||||
        if (!is_null($this->database->limits_cpuset)) {
 | 
			
		||||
            data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
 | 
			
		||||
        }
 | 
			
		||||
        if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
 | 
			
		||||
            $docker_compose['services'][$container_name]['logging'] = [
 | 
			
		||||
                'driver' => 'fluentd',
 | 
			
		||||
                'options' => [
 | 
			
		||||
                    'fluentd-address' => 'tcp://127.0.0.1:24224',
 | 
			
		||||
                    'fluentd-async' => 'true',
 | 
			
		||||
                    'fluentd-sub-second-precision' => 'true',
 | 
			
		||||
                ],
 | 
			
		||||
            ];
 | 
			
		||||
            $docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
 | 
			
		||||
        }
 | 
			
		||||
        if (count($this->database->ports_mappings_array) > 0) {
 | 
			
		||||
            $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
 | 
			
		||||
@@ -104,23 +97,27 @@ class StartMongodb
 | 
			
		||||
        if (count($volume_names) > 0) {
 | 
			
		||||
            $docker_compose['volumes'] = $volume_names;
 | 
			
		||||
        }
 | 
			
		||||
        if (! is_null($this->database->mongo_conf) || ! empty($this->database->mongo_conf)) {
 | 
			
		||||
        if (!is_null($this->database->mongo_conf) || !empty($this->database->mongo_conf)) {
 | 
			
		||||
            $docker_compose['services'][$container_name]['volumes'][] = [
 | 
			
		||||
                'type' => 'bind',
 | 
			
		||||
                'source' => $this->configuration_dir.'/mongod.conf',
 | 
			
		||||
                'source' => $this->configuration_dir . '/mongod.conf',
 | 
			
		||||
                'target' => '/etc/mongo/mongod.conf',
 | 
			
		||||
                'read_only' => true,
 | 
			
		||||
            ];
 | 
			
		||||
            $docker_compose['services'][$container_name]['command'] = $startCommand.' --config /etc/mongo/mongod.conf';
 | 
			
		||||
            $docker_compose['services'][$container_name]['command'] = $startCommand . ' --config /etc/mongo/mongod.conf';
 | 
			
		||||
        }
 | 
			
		||||
        $this->add_default_database();
 | 
			
		||||
        $docker_compose['services'][$container_name]['volumes'][] = [
 | 
			
		||||
            'type' => 'bind',
 | 
			
		||||
            'source' => $this->configuration_dir.'/docker-entrypoint-initdb.d',
 | 
			
		||||
            'source' => $this->configuration_dir . '/docker-entrypoint-initdb.d',
 | 
			
		||||
            'target' => '/docker-entrypoint-initdb.d',
 | 
			
		||||
            'read_only' => true,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        // Add custom docker run options
 | 
			
		||||
        $docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
 | 
			
		||||
        $docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
 | 
			
		||||
 | 
			
		||||
        $docker_compose = Yaml::dump($docker_compose, 10);
 | 
			
		||||
        $docker_compose_base64 = base64_encode($docker_compose);
 | 
			
		||||
        $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
 | 
			
		||||
@@ -139,10 +136,10 @@ class StartMongodb
 | 
			
		||||
        $local_persistent_volumes = [];
 | 
			
		||||
        foreach ($this->database->persistentStorages as $persistentStorage) {
 | 
			
		||||
            if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
 | 
			
		||||
                $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
 | 
			
		||||
                $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
 | 
			
		||||
            } else {
 | 
			
		||||
                $volume_name = $persistentStorage->name;
 | 
			
		||||
                $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
 | 
			
		||||
                $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -173,18 +170,20 @@ class StartMongodb
 | 
			
		||||
            $environment_variables->push("$env->key=$env->real_value");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) {
 | 
			
		||||
        if ($environment_variables->filter(fn($env) => str($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) {
 | 
			
		||||
            $environment_variables->push("MONGO_INITDB_ROOT_USERNAME={$this->database->mongo_initdb_root_username}");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_ROOT_PASSWORD'))->isEmpty()) {
 | 
			
		||||
        if ($environment_variables->filter(fn($env) => str($env)->contains('MONGO_INITDB_ROOT_PASSWORD'))->isEmpty()) {
 | 
			
		||||
            $environment_variables->push("MONGO_INITDB_ROOT_PASSWORD={$this->database->mongo_initdb_root_password}");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) {
 | 
			
		||||
        if ($environment_variables->filter(fn($env) => str($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) {
 | 
			
		||||
            $environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
 | 
			
		||||
 | 
			
		||||
        return $environment_variables->all();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ class StartMysql
 | 
			
		||||
        $this->database = $database;
 | 
			
		||||
 | 
			
		||||
        $container_name = $this->database->uuid;
 | 
			
		||||
        $this->configuration_dir = database_configuration_dir().'/'.$container_name;
 | 
			
		||||
        $this->configuration_dir = database_configuration_dir() . '/' . $container_name;
 | 
			
		||||
 | 
			
		||||
        $this->commands = [
 | 
			
		||||
            "echo 'Starting {$database->name}.'",
 | 
			
		||||
@@ -69,18 +69,11 @@ class StartMysql
 | 
			
		||||
                ],
 | 
			
		||||
            ],
 | 
			
		||||
        ];
 | 
			
		||||
        if (! is_null($this->database->limits_cpuset)) {
 | 
			
		||||
        if (!is_null($this->database->limits_cpuset)) {
 | 
			
		||||
            data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
 | 
			
		||||
        }
 | 
			
		||||
        if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
 | 
			
		||||
            $docker_compose['services'][$container_name]['logging'] = [
 | 
			
		||||
                'driver' => 'fluentd',
 | 
			
		||||
                'options' => [
 | 
			
		||||
                    'fluentd-address' => 'tcp://127.0.0.1:24224',
 | 
			
		||||
                    'fluentd-async' => 'true',
 | 
			
		||||
                    'fluentd-sub-second-precision' => 'true',
 | 
			
		||||
                ],
 | 
			
		||||
            ];
 | 
			
		||||
            $docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
 | 
			
		||||
        }
 | 
			
		||||
        if (count($this->database->ports_mappings_array) > 0) {
 | 
			
		||||
            $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
 | 
			
		||||
@@ -96,14 +89,19 @@ class StartMysql
 | 
			
		||||
        if (count($volume_names) > 0) {
 | 
			
		||||
            $docker_compose['volumes'] = $volume_names;
 | 
			
		||||
        }
 | 
			
		||||
        if (! is_null($this->database->mysql_conf) || ! empty($this->database->mysql_conf)) {
 | 
			
		||||
        if (!is_null($this->database->mysql_conf) || !empty($this->database->mysql_conf)) {
 | 
			
		||||
            $docker_compose['services'][$container_name]['volumes'][] = [
 | 
			
		||||
                'type' => 'bind',
 | 
			
		||||
                'source' => $this->configuration_dir.'/custom-config.cnf',
 | 
			
		||||
                'source' => $this->configuration_dir . '/custom-config.cnf',
 | 
			
		||||
                'target' => '/etc/mysql/conf.d/custom-config.cnf',
 | 
			
		||||
                'read_only' => true,
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Add custom docker run options
 | 
			
		||||
        $docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
 | 
			
		||||
        $docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
 | 
			
		||||
 | 
			
		||||
        $docker_compose = Yaml::dump($docker_compose, 10);
 | 
			
		||||
        $docker_compose_base64 = base64_encode($docker_compose);
 | 
			
		||||
        $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
 | 
			
		||||
@@ -122,10 +120,10 @@ class StartMysql
 | 
			
		||||
        $local_persistent_volumes = [];
 | 
			
		||||
        foreach ($this->database->persistentStorages as $persistentStorage) {
 | 
			
		||||
            if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
 | 
			
		||||
                $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
 | 
			
		||||
                $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
 | 
			
		||||
            } else {
 | 
			
		||||
                $volume_name = $persistentStorage->name;
 | 
			
		||||
                $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
 | 
			
		||||
                $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -156,21 +154,23 @@ class StartMysql
 | 
			
		||||
            $environment_variables->push("$env->key=$env->real_value");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) {
 | 
			
		||||
        if ($environment_variables->filter(fn($env) => str($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) {
 | 
			
		||||
            $environment_variables->push("MYSQL_ROOT_PASSWORD={$this->database->mysql_root_password}");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_DATABASE'))->isEmpty()) {
 | 
			
		||||
        if ($environment_variables->filter(fn($env) => str($env)->contains('MYSQL_DATABASE'))->isEmpty()) {
 | 
			
		||||
            $environment_variables->push("MYSQL_DATABASE={$this->database->mysql_database}");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_USER'))->isEmpty()) {
 | 
			
		||||
        if ($environment_variables->filter(fn($env) => str($env)->contains('MYSQL_USER'))->isEmpty()) {
 | 
			
		||||
            $environment_variables->push("MYSQL_USER={$this->database->mysql_user}");
 | 
			
		||||
        }
 | 
			
		||||
        if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_PASSWORD'))->isEmpty()) {
 | 
			
		||||
        if ($environment_variables->filter(fn($env) => str($env)->contains('MYSQL_PASSWORD'))->isEmpty()) {
 | 
			
		||||
            $environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
 | 
			
		||||
 | 
			
		||||
        return $environment_variables->all();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -37,6 +37,7 @@ class StartPostgresql
 | 
			
		||||
        $this->generate_init_scripts();
 | 
			
		||||
        $this->add_custom_conf();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        $docker_compose = [
 | 
			
		||||
            'services' => [
 | 
			
		||||
                $container_name => [
 | 
			
		||||
@@ -80,14 +81,7 @@ class StartPostgresql
 | 
			
		||||
            data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
 | 
			
		||||
        }
 | 
			
		||||
        if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
 | 
			
		||||
            $docker_compose['services'][$container_name]['logging'] = [
 | 
			
		||||
                'driver' => 'fluentd',
 | 
			
		||||
                'options' => [
 | 
			
		||||
                    'fluentd-address' => 'tcp://127.0.0.1:24224',
 | 
			
		||||
                    'fluentd-async' => 'true',
 | 
			
		||||
                    'fluentd-sub-second-precision' => 'true',
 | 
			
		||||
                ],
 | 
			
		||||
            ];
 | 
			
		||||
            $docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
 | 
			
		||||
        }
 | 
			
		||||
        if (count($this->database->ports_mappings_array) > 0) {
 | 
			
		||||
            $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
 | 
			
		||||
@@ -126,6 +120,10 @@ class StartPostgresql
 | 
			
		||||
                'config_file=/etc/postgresql/postgresql.conf',
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
        // Add custom docker run options
 | 
			
		||||
        $docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
 | 
			
		||||
        $docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
 | 
			
		||||
 | 
			
		||||
        $docker_compose = Yaml::dump($docker_compose, 10);
 | 
			
		||||
        $docker_compose_base64 = base64_encode($docker_compose);
 | 
			
		||||
        $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
 | 
			
		||||
@@ -193,6 +191,8 @@ class StartPostgresql
 | 
			
		||||
            $environment_variables->push("POSTGRES_DB={$this->database->postgres_db}");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
 | 
			
		||||
 | 
			
		||||
        return $environment_variables->all();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ class StartRedis
 | 
			
		||||
        $startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
 | 
			
		||||
 | 
			
		||||
        $container_name = $this->database->uuid;
 | 
			
		||||
        $this->configuration_dir = database_configuration_dir().'/'.$container_name;
 | 
			
		||||
        $this->configuration_dir = database_configuration_dir() . '/' . $container_name;
 | 
			
		||||
 | 
			
		||||
        $this->commands = [
 | 
			
		||||
            "echo 'Starting {$database->name}.'",
 | 
			
		||||
@@ -78,18 +78,11 @@ class StartRedis
 | 
			
		||||
                ],
 | 
			
		||||
            ],
 | 
			
		||||
        ];
 | 
			
		||||
        if (! is_null($this->database->limits_cpuset)) {
 | 
			
		||||
        if (!is_null($this->database->limits_cpuset)) {
 | 
			
		||||
            data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
 | 
			
		||||
        }
 | 
			
		||||
        if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
 | 
			
		||||
            $docker_compose['services'][$container_name]['logging'] = [
 | 
			
		||||
                'driver' => 'fluentd',
 | 
			
		||||
                'options' => [
 | 
			
		||||
                    'fluentd-address' => 'tcp://127.0.0.1:24224',
 | 
			
		||||
                    'fluentd-async' => 'true',
 | 
			
		||||
                    'fluentd-sub-second-precision' => 'true',
 | 
			
		||||
                ],
 | 
			
		||||
            ];
 | 
			
		||||
            $docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
 | 
			
		||||
        }
 | 
			
		||||
        if (count($this->database->ports_mappings_array) > 0) {
 | 
			
		||||
            $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
 | 
			
		||||
@@ -105,15 +98,20 @@ class StartRedis
 | 
			
		||||
        if (count($volume_names) > 0) {
 | 
			
		||||
            $docker_compose['volumes'] = $volume_names;
 | 
			
		||||
        }
 | 
			
		||||
        if (! is_null($this->database->redis_conf) || ! empty($this->database->redis_conf)) {
 | 
			
		||||
        if (!is_null($this->database->redis_conf) || !empty($this->database->redis_conf)) {
 | 
			
		||||
            $docker_compose['services'][$container_name]['volumes'][] = [
 | 
			
		||||
                'type' => 'bind',
 | 
			
		||||
                'source' => $this->configuration_dir.'/redis.conf',
 | 
			
		||||
                'source' => $this->configuration_dir . '/redis.conf',
 | 
			
		||||
                'target' => '/usr/local/etc/redis/redis.conf',
 | 
			
		||||
                'read_only' => true,
 | 
			
		||||
            ];
 | 
			
		||||
            $docker_compose['services'][$container_name]['command'] = "redis-server /usr/local/etc/redis/redis.conf --requirepass {$this->database->redis_password} --appendonly yes";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Add custom docker run options
 | 
			
		||||
        $docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
 | 
			
		||||
        $docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
 | 
			
		||||
 | 
			
		||||
        $docker_compose = Yaml::dump($docker_compose, 10);
 | 
			
		||||
        $docker_compose_base64 = base64_encode($docker_compose);
 | 
			
		||||
        $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
 | 
			
		||||
@@ -132,10 +130,10 @@ class StartRedis
 | 
			
		||||
        $local_persistent_volumes = [];
 | 
			
		||||
        foreach ($this->database->persistentStorages as $persistentStorage) {
 | 
			
		||||
            if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
 | 
			
		||||
                $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
 | 
			
		||||
                $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
 | 
			
		||||
            } else {
 | 
			
		||||
                $volume_name = $persistentStorage->name;
 | 
			
		||||
                $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
 | 
			
		||||
                $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -166,10 +164,12 @@ class StartRedis
 | 
			
		||||
            $environment_variables->push("$env->key=$env->real_value");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
 | 
			
		||||
        if ($environment_variables->filter(fn($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
 | 
			
		||||
            $environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
 | 
			
		||||
 | 
			
		||||
        return $environment_variables->all();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
 | 
			
		||||
namespace App\Actions\Server;
 | 
			
		||||
 | 
			
		||||
use App\Models\InstanceSettings;
 | 
			
		||||
use App\Models\Server;
 | 
			
		||||
use Lorisleiva\Actions\Concerns\AsAction;
 | 
			
		||||
 | 
			
		||||
@@ -9,17 +10,30 @@ class CleanupDocker
 | 
			
		||||
{
 | 
			
		||||
    use AsAction;
 | 
			
		||||
 | 
			
		||||
    public function handle(Server $server, bool $force = true)
 | 
			
		||||
    public function handle(Server $server)
 | 
			
		||||
    {
 | 
			
		||||
        // cleanup docker images, containers, and builder caches
 | 
			
		||||
        if ($force) {
 | 
			
		||||
            instant_remote_process(['docker image prune -af'], $server, false);
 | 
			
		||||
            instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
 | 
			
		||||
            instant_remote_process(['docker builder prune -af'], $server, false);
 | 
			
		||||
        } else {
 | 
			
		||||
            instant_remote_process(['docker image prune -f'], $server, false);
 | 
			
		||||
            instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
 | 
			
		||||
            instant_remote_process(['docker builder prune -f'], $server, false);
 | 
			
		||||
 | 
			
		||||
        $commands = $this->getCommands();
 | 
			
		||||
 | 
			
		||||
        foreach ($commands as $command) {
 | 
			
		||||
            instant_remote_process([$command], $server, false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getCommands(): array
 | 
			
		||||
    {
 | 
			
		||||
        $settings = InstanceSettings::get();
 | 
			
		||||
        $helperImageVersion = data_get($settings, 'helper_version');
 | 
			
		||||
        $helperImage = config('coolify.helper_image');
 | 
			
		||||
        $helperImageWithVersion = config('coolify.helper_image').':'.$helperImageVersion;
 | 
			
		||||
 | 
			
		||||
        $commonCommands = [
 | 
			
		||||
            'docker container prune -f --filter "label=coolify.managed=true"',
 | 
			
		||||
            'docker image prune -af --filter "label!=coolify.managed=true"',
 | 
			
		||||
            'docker builder prune -af',
 | 
			
		||||
            "docker images --filter before=$helperImageWithVersion --filter reference=$helperImage | grep $helperImage | awk '{print $3}' | xargs -r docker rmi",
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        return $commonCommands;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,11 @@ class InstallLogDrain
 | 
			
		||||
[FILTER]
 | 
			
		||||
    Name                modify
 | 
			
		||||
    Match               *
 | 
			
		||||
    Set                 server_name {$server->name}
 | 
			
		||||
    Set                 coolify.server_name {$server->name}
 | 
			
		||||
    Rename              COOLIFY_APP_NAME coolify.app_name
 | 
			
		||||
    Rename              COOLIFY_PROJECT_NAME coolify.project_name
 | 
			
		||||
    Rename              COOLIFY_SERVER_IP coolify.server_ip
 | 
			
		||||
    Rename              COOLIFY_ENVIRONMENT_NAME coolify.environment_name
 | 
			
		||||
[OUTPUT]
 | 
			
		||||
    Name nrlogs
 | 
			
		||||
    Match *
 | 
			
		||||
@@ -98,7 +102,11 @@ class InstallLogDrain
 | 
			
		||||
[FILTER]
 | 
			
		||||
    Name                modify
 | 
			
		||||
    Match               *
 | 
			
		||||
    Set                 server_name {$server->name}
 | 
			
		||||
    Set                 coolify.server_name {$server->name}
 | 
			
		||||
    Rename              COOLIFY_APP_NAME coolify.app_name
 | 
			
		||||
    Rename              COOLIFY_PROJECT_NAME coolify.project_name
 | 
			
		||||
    Rename              COOLIFY_SERVER_IP coolify.server_ip
 | 
			
		||||
    Rename              COOLIFY_ENVIRONMENT_NAME coolify.environment_name
 | 
			
		||||
[OUTPUT]
 | 
			
		||||
    Name            http
 | 
			
		||||
    Match           *
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ namespace App\Actions\Server;
 | 
			
		||||
 | 
			
		||||
use App\Models\InstanceSettings;
 | 
			
		||||
use App\Models\Server;
 | 
			
		||||
use Illuminate\Support\Facades\File;
 | 
			
		||||
use Illuminate\Support\Facades\Http;
 | 
			
		||||
use Lorisleiva\Actions\Concerns\AsAction;
 | 
			
		||||
 | 
			
		||||
class UpdateCoolify
 | 
			
		||||
@@ -26,12 +24,7 @@ class UpdateCoolify
 | 
			
		||||
            if (! $this->server) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            CleanupDocker::dispatch($this->server, false)->onQueue('high');
 | 
			
		||||
            $response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
 | 
			
		||||
            if ($response->successful()) {
 | 
			
		||||
                $versions = $response->json();
 | 
			
		||||
                File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT));
 | 
			
		||||
            }
 | 
			
		||||
            CleanupDocker::dispatch($this->server)->onQueue('high');
 | 
			
		||||
            $this->latestVersion = get_latest_version_of_coolify();
 | 
			
		||||
            $this->currentVersion = config('version');
 | 
			
		||||
            if (! $manual_update) {
 | 
			
		||||
@@ -62,10 +55,11 @@ class UpdateCoolify
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$this->latestVersion}"], $this->server, false);
 | 
			
		||||
 | 
			
		||||
        remote_process([
 | 
			
		||||
            'curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh',
 | 
			
		||||
            "bash /data/coolify/source/upgrade.sh $this->latestVersion",
 | 
			
		||||
        ], $this->server);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -16,8 +16,10 @@ class StartService
 | 
			
		||||
        $service->saveComposeConfigs();
 | 
			
		||||
        $commands[] = 'cd '.$service->workdir();
 | 
			
		||||
        $commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
 | 
			
		||||
        if($service->networks()->count() > 0){
 | 
			
		||||
            $commands[] = "echo 'Creating Docker network.'";
 | 
			
		||||
            $commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid";
 | 
			
		||||
        }
 | 
			
		||||
        $commands[] = 'echo Starting service.';
 | 
			
		||||
        $commands[] = "echo 'Pulling images.'";
 | 
			
		||||
        $commands[] = 'docker compose pull';
 | 
			
		||||
 
 | 
			
		||||
@@ -19,14 +19,19 @@ class StopService
 | 
			
		||||
            ray('Stopping service: '.$service->name);
 | 
			
		||||
            $applications = $service->applications()->get();
 | 
			
		||||
            foreach ($applications as $application) {
 | 
			
		||||
                instant_remote_process(command: ["docker stop --time=30 {$application->name}-{$service->uuid}"], server: $server, throwError: false);
 | 
			
		||||
                if ($applications->count() < 6) {
 | 
			
		||||
                    instant_remote_process(command: ["docker stop --time=10 {$application->name}-{$service->uuid}"], server: $server, throwError: false);
 | 
			
		||||
                }
 | 
			
		||||
                instant_remote_process(command: ["docker rm {$application->name}-{$service->uuid}"], server: $server, throwError: false);
 | 
			
		||||
                instant_remote_process(command: ["docker rm -f {$application->name}-{$service->uuid}"], server: $server, throwError: false);
 | 
			
		||||
                $application->update(['status' => 'exited']);
 | 
			
		||||
            }
 | 
			
		||||
            $dbs = $service->databases()->get();
 | 
			
		||||
            foreach ($dbs as $db) {
 | 
			
		||||
                instant_remote_process(command: ["docker stop --time=30 {$db->name}-{$service->uuid}"], server: $server, throwError: false);
 | 
			
		||||
                if ($dbs->count() < 6) {
 | 
			
		||||
 | 
			
		||||
                    instant_remote_process(command: ["docker stop --time=10 {$db->name}-{$service->uuid}"], server: $server, throwError: false);
 | 
			
		||||
                }
 | 
			
		||||
                instant_remote_process(command: ["docker rm {$db->name}-{$service->uuid}"], server: $server, throwError: false);
 | 
			
		||||
                instant_remote_process(command: ["docker rm -f {$db->name}-{$service->uuid}"], server: $server, throwError: false);
 | 
			
		||||
                $db->update(['status' => 'exited']);
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@
 | 
			
		||||
namespace App\Console\Commands;
 | 
			
		||||
 | 
			
		||||
use App\Models\Application;
 | 
			
		||||
use App\Models\ApplicationPreview;
 | 
			
		||||
use App\Models\ScheduledDatabaseBackup;
 | 
			
		||||
use App\Models\ScheduledTask;
 | 
			
		||||
use App\Models\Service;
 | 
			
		||||
use App\Models\ServiceApplication;
 | 
			
		||||
@@ -42,6 +44,17 @@ class CleanupStuckedResources extends Command
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            echo "Error in cleaning stuck application: {$e->getMessage()}\n";
 | 
			
		||||
        }
 | 
			
		||||
        try {
 | 
			
		||||
            $applicationsPreviews = ApplicationPreview::get();
 | 
			
		||||
            foreach ($applicationsPreviews as $applicationPreview) {
 | 
			
		||||
                if (! data_get($applicationPreview, 'application')) {
 | 
			
		||||
                    echo "Deleting stuck application preview: {$applicationPreview->uuid}\n";
 | 
			
		||||
                    $applicationPreview->delete();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            echo "Error in cleaning stuck application: {$e->getMessage()}\n";
 | 
			
		||||
        }
 | 
			
		||||
        try {
 | 
			
		||||
            $postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get();
 | 
			
		||||
            foreach ($postgresqls as $postgresql) {
 | 
			
		||||
@@ -153,6 +166,18 @@ class CleanupStuckedResources extends Command
 | 
			
		||||
            echo "Error in cleaning stuck scheduledtasks: {$e->getMessage()}\n";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            $scheduled_backups = ScheduledDatabaseBackup::all();
 | 
			
		||||
            foreach ($scheduled_backups as $scheduled_backup) {
 | 
			
		||||
                if (! $scheduled_backup->server()) {
 | 
			
		||||
                    echo "Deleting stuck scheduledbackup: {$scheduled_backup->name}\n";
 | 
			
		||||
                    $scheduled_backup->delete();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            echo "Error in cleaning stuck scheduledbackups: {$e->getMessage()}\n";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Cleanup any resources that are not attached to any environment or destination or server
 | 
			
		||||
        try {
 | 
			
		||||
            $applications = Application::all();
 | 
			
		||||
 
 | 
			
		||||
@@ -132,6 +132,9 @@ class Init extends Command
 | 
			
		||||
 | 
			
		||||
    private function cleanup_unused_network_from_coolify_proxy()
 | 
			
		||||
    {
 | 
			
		||||
        if (isCloud()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        foreach ($this->servers as $server) {
 | 
			
		||||
            if (! $server->isFunctional()) {
 | 
			
		||||
                continue;
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ class SyncBunny extends Command
 | 
			
		||||
     *
 | 
			
		||||
     * @var string
 | 
			
		||||
     */
 | 
			
		||||
    protected $signature = 'sync:bunny {--templates} {--release}';
 | 
			
		||||
    protected $signature = 'sync:bunny {--templates} {--release} {--nightly}';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The console command description.
 | 
			
		||||
@@ -33,6 +33,7 @@ class SyncBunny extends Command
 | 
			
		||||
        $that = $this;
 | 
			
		||||
        $only_template = $this->option('templates');
 | 
			
		||||
        $only_version = $this->option('release');
 | 
			
		||||
        $nightly = $this->option('nightly');
 | 
			
		||||
        $bunny_cdn = 'https://cdn.coollabs.io';
 | 
			
		||||
        $bunny_cdn_path = 'coolify';
 | 
			
		||||
        $bunny_cdn_storage_name = 'coolcdn';
 | 
			
		||||
@@ -45,9 +46,15 @@ class SyncBunny extends Command
 | 
			
		||||
        $upgrade_script = 'upgrade.sh';
 | 
			
		||||
        $production_env = '.env.production';
 | 
			
		||||
        $service_template = 'service-templates.json';
 | 
			
		||||
 | 
			
		||||
        $versions = 'versions.json';
 | 
			
		||||
 | 
			
		||||
        $compose_file_location = "$parent_dir/$compose_file";
 | 
			
		||||
        $compose_file_prod_location = "$parent_dir/$compose_file_prod";
 | 
			
		||||
        $install_script_location = "$parent_dir/scripts/install.sh";
 | 
			
		||||
        $upgrade_script_location = "$parent_dir/scripts/upgrade.sh";
 | 
			
		||||
        $production_env_location = "$parent_dir/.env.production";
 | 
			
		||||
        $versions_location = "$parent_dir/$versions";
 | 
			
		||||
 | 
			
		||||
        PendingRequest::macro('storage', function ($fileName) use ($that) {
 | 
			
		||||
            $headers = [
 | 
			
		||||
                'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
 | 
			
		||||
@@ -73,8 +80,26 @@ class SyncBunny extends Command
 | 
			
		||||
            ]);
 | 
			
		||||
        });
 | 
			
		||||
        try {
 | 
			
		||||
            if ($nightly) {
 | 
			
		||||
                $bunny_cdn_path = 'coolify-nightly';
 | 
			
		||||
 | 
			
		||||
                $compose_file_location = "$parent_dir/other/nightly/$compose_file";
 | 
			
		||||
                $compose_file_prod_location = "$parent_dir/other/nightly/$compose_file_prod";
 | 
			
		||||
                $production_env_location = "$parent_dir/other/nightly/$production_env";
 | 
			
		||||
                $upgrade_script_location = "$parent_dir/other/nightly/$upgrade_script";
 | 
			
		||||
                $install_script_location = "$parent_dir/other/nightly/$install_script";
 | 
			
		||||
                $versions_location = "$parent_dir/other/nightly/$versions";
 | 
			
		||||
            }
 | 
			
		||||
            if (! $only_template && ! $only_version) {
 | 
			
		||||
                $this->info('About to sync files (docker-compose.prod.yaml, upgrade.sh, install.sh, etc) to BunnyCDN.');
 | 
			
		||||
                if ($nightly) {
 | 
			
		||||
                    $this->info('About to sync files NIGHTLY (docker-compose.prod.yaml, upgrade.sh, install.sh, etc) to BunnyCDN.');
 | 
			
		||||
                } else {
 | 
			
		||||
                    $this->info('About to sync files PRODUCTION (docker-compose.yml, docker-compose.prod.yml, upgrade.sh, install.sh, etc) to BunnyCDN.');
 | 
			
		||||
                }
 | 
			
		||||
                $confirmed = confirm('Are you sure you want to sync?');
 | 
			
		||||
                if (! $confirmed) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if ($only_template) {
 | 
			
		||||
                $this->info('About to sync service-templates.json to BunnyCDN.');
 | 
			
		||||
@@ -90,8 +115,12 @@ class SyncBunny extends Command
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            } elseif ($only_version) {
 | 
			
		||||
                $this->info('About to sync versions.json to BunnyCDN.');
 | 
			
		||||
                $file = file_get_contents("$parent_dir/$versions");
 | 
			
		||||
                if ($nightly) {
 | 
			
		||||
                    $this->info('About to sync NIGHLTY versions.json to BunnyCDN.');
 | 
			
		||||
                } else {
 | 
			
		||||
                    $this->info('About to sync PRODUCTION versions.json to BunnyCDN.');
 | 
			
		||||
                }
 | 
			
		||||
                $file = file_get_contents($versions_location);
 | 
			
		||||
                $json = json_decode($file, true);
 | 
			
		||||
                $actual_version = data_get($json, 'coolify.v4.version');
 | 
			
		||||
 | 
			
		||||
@@ -100,7 +129,7 @@ class SyncBunny extends Command
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                Http::pool(fn (Pool $pool) => [
 | 
			
		||||
                    $pool->storage(fileName: "$parent_dir/$versions")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"),
 | 
			
		||||
                    $pool->storage(fileName: $versions_location)->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"),
 | 
			
		||||
                    $pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
 | 
			
		||||
                ]);
 | 
			
		||||
                $this->info('versions.json uploaded & purged...');
 | 
			
		||||
@@ -109,11 +138,11 @@ class SyncBunny extends Command
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Http::pool(fn (Pool $pool) => [
 | 
			
		||||
                $pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
 | 
			
		||||
                $pool->storage(fileName: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
 | 
			
		||||
                $pool->storage(fileName: "$parent_dir/$production_env")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$production_env"),
 | 
			
		||||
                $pool->storage(fileName: "$parent_dir/scripts/$upgrade_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$upgrade_script"),
 | 
			
		||||
                $pool->storage(fileName: "$parent_dir/scripts/$install_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$install_script"),
 | 
			
		||||
                $pool->storage(fileName: "$compose_file_location")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
 | 
			
		||||
                $pool->storage(fileName: "$compose_file_prod_location")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
 | 
			
		||||
                $pool->storage(fileName: "$production_env_location")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$production_env"),
 | 
			
		||||
                $pool->storage(fileName: "$upgrade_script_location")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$upgrade_script"),
 | 
			
		||||
                $pool->storage(fileName: "$install_script_location")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$install_script"),
 | 
			
		||||
            ]);
 | 
			
		||||
            Http::pool(fn (Pool $pool) => [
 | 
			
		||||
                $pool->purge("$bunny_cdn/$bunny_cdn_path/$compose_file"),
 | 
			
		||||
 
 | 
			
		||||
@@ -4,9 +4,9 @@ namespace App\Console;
 | 
			
		||||
 | 
			
		||||
use App\Jobs\CheckForUpdatesJob;
 | 
			
		||||
use App\Jobs\CleanupInstanceStuffsJob;
 | 
			
		||||
use App\Jobs\CleanupStaleMultiplexedConnections;
 | 
			
		||||
use App\Jobs\DatabaseBackupJob;
 | 
			
		||||
use App\Jobs\DockerCleanupJob;
 | 
			
		||||
use App\Jobs\PullCoolifyImageJob;
 | 
			
		||||
use App\Jobs\PullHelperImageJob;
 | 
			
		||||
use App\Jobs\PullSentinelImageJob;
 | 
			
		||||
use App\Jobs\PullTemplatesFromCDN;
 | 
			
		||||
@@ -30,22 +30,24 @@ class Kernel extends ConsoleKernel
 | 
			
		||||
        $this->all_servers = Server::all();
 | 
			
		||||
        $settings = InstanceSettings::get();
 | 
			
		||||
 | 
			
		||||
        $schedule->job(new CleanupStaleMultiplexedConnections)->hourly();
 | 
			
		||||
 | 
			
		||||
        if (isDev()) {
 | 
			
		||||
            // Instance Jobs
 | 
			
		||||
            $schedule->command('horizon:snapshot')->everyMinute();
 | 
			
		||||
            $schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
 | 
			
		||||
 | 
			
		||||
            // Server Jobs
 | 
			
		||||
            $this->check_scheduled_backups($schedule);
 | 
			
		||||
            $this->check_resources($schedule);
 | 
			
		||||
            $this->check_scheduled_tasks($schedule);
 | 
			
		||||
            $schedule->command('uploads:clear')->everyTwoMinutes();
 | 
			
		||||
 | 
			
		||||
            $schedule->command('telescope:prune')->daily();
 | 
			
		||||
        } else {
 | 
			
		||||
            // Instance Jobs
 | 
			
		||||
            $schedule->command('horizon:snapshot')->everyFiveMinutes();
 | 
			
		||||
            $schedule->command('cleanup:unreachable-servers')->daily();
 | 
			
		||||
            $schedule->job(new PullCoolifyImageJob)->cron($settings->update_check_frequency)->onOneServer();
 | 
			
		||||
            $schedule->job(new PullTemplatesFromCDN)->cron($settings->update_check_frequency)->onOneServer();
 | 
			
		||||
            $schedule->command('cleanup:unreachable-servers')->daily()->onOneServer();
 | 
			
		||||
            $schedule->job(new PullTemplatesFromCDN)->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
 | 
			
		||||
            $schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
 | 
			
		||||
            $this->schedule_updates($schedule);
 | 
			
		||||
 | 
			
		||||
@@ -66,9 +68,19 @@ class Kernel extends ConsoleKernel
 | 
			
		||||
        $servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
 | 
			
		||||
        foreach ($servers as $server) {
 | 
			
		||||
            if ($server->isSentinelEnabled()) {
 | 
			
		||||
                $schedule->job(new PullSentinelImageJob($server))->cron($settings->update_check_frequency)->onOneServer();
 | 
			
		||||
                $schedule->job(function () use ($server) {
 | 
			
		||||
                    $sentinel_found = instant_remote_process(['docker inspect coolify-sentinel'], $server, false);
 | 
			
		||||
                    $sentinel_found = json_decode($sentinel_found, true);
 | 
			
		||||
                    $status = data_get($sentinel_found, '0.State.Status', 'exited');
 | 
			
		||||
                    if ($status !== 'running') {
 | 
			
		||||
                        PullSentinelImageJob::dispatch($server);
 | 
			
		||||
                    }
 | 
			
		||||
            $schedule->job(new PullHelperImageJob($server))->cron($settings->update_check_frequency)->onOneServer();
 | 
			
		||||
                })->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
 | 
			
		||||
            }
 | 
			
		||||
            $schedule->job(new PullHelperImageJob($server))
 | 
			
		||||
                ->cron($settings->update_check_frequency)
 | 
			
		||||
                ->timezone($settings->instance_timezone)
 | 
			
		||||
                ->onOneServer();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -77,11 +89,17 @@ class Kernel extends ConsoleKernel
 | 
			
		||||
        $settings = InstanceSettings::get();
 | 
			
		||||
 | 
			
		||||
        $updateCheckFrequency = $settings->update_check_frequency;
 | 
			
		||||
        $schedule->job(new CheckForUpdatesJob)->cron($updateCheckFrequency)->onOneServer();
 | 
			
		||||
        $schedule->job(new CheckForUpdatesJob)
 | 
			
		||||
            ->cron($updateCheckFrequency)
 | 
			
		||||
            ->timezone($settings->instance_timezone)
 | 
			
		||||
            ->onOneServer();
 | 
			
		||||
 | 
			
		||||
        if ($settings->is_auto_update_enabled) {
 | 
			
		||||
            $autoUpdateFrequency = $settings->auto_update_frequency;
 | 
			
		||||
            $schedule->job(new UpdateCoolifyJob)->cron($autoUpdateFrequency)->onOneServer();
 | 
			
		||||
            $schedule->job(new UpdateCoolifyJob)
 | 
			
		||||
                ->cron($autoUpdateFrequency)
 | 
			
		||||
                ->timezone($settings->instance_timezone)
 | 
			
		||||
                ->onOneServer();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -96,7 +114,12 @@ class Kernel extends ConsoleKernel
 | 
			
		||||
        }
 | 
			
		||||
        foreach ($servers as $server) {
 | 
			
		||||
            $schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
 | 
			
		||||
            $schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->onOneServer();
 | 
			
		||||
            $serverTimezone = $server->settings->server_timezone;
 | 
			
		||||
            if ($server->settings->force_docker_cleanup) {
 | 
			
		||||
                $schedule->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer();
 | 
			
		||||
            } else {
 | 
			
		||||
                $schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->timezone($serverTimezone)->onOneServer();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -117,12 +140,19 @@ class Kernel extends ConsoleKernel
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $server = $scheduled_backup->server();
 | 
			
		||||
 | 
			
		||||
            if (! $server) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            $serverTimezone = $server->settings->server_timezone;
 | 
			
		||||
 | 
			
		||||
            if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
 | 
			
		||||
                $scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency];
 | 
			
		||||
            }
 | 
			
		||||
            $schedule->job(new DatabaseBackupJob(
 | 
			
		||||
                backup: $scheduled_backup
 | 
			
		||||
            ))->cron($scheduled_backup->frequency)->onOneServer();
 | 
			
		||||
            ))->cron($scheduled_backup->frequency)->timezone($serverTimezone)->onOneServer();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -155,12 +185,19 @@ class Kernel extends ConsoleKernel
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $server = $scheduled_task->server();
 | 
			
		||||
            if (! $server) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            $serverTimezone = $server->settings->server_timezone ?: config('app.timezone');
 | 
			
		||||
 | 
			
		||||
            if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) {
 | 
			
		||||
                $scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency];
 | 
			
		||||
            }
 | 
			
		||||
            $schedule->job(new ScheduledTaskJob(
 | 
			
		||||
                task: $scheduled_task
 | 
			
		||||
            ))->cron($scheduled_task->frequency)->onOneServer();
 | 
			
		||||
            ))->cron($scheduled_task->frequency)->timezone($serverTimezone)->onOneServer();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -53,6 +53,7 @@ class ApplicationsController extends Controller
 | 
			
		||||
        summary: 'List',
 | 
			
		||||
        description: 'List all applications.',
 | 
			
		||||
        path: '/applications',
 | 
			
		||||
        operationId: 'list-applications',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -101,6 +102,7 @@ class ApplicationsController extends Controller
 | 
			
		||||
        summary: 'Create (Public)',
 | 
			
		||||
        description: 'Create new application based on a public git repository.',
 | 
			
		||||
        path: '/applications/public',
 | 
			
		||||
        operationId: 'create-public-application',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -202,6 +204,7 @@ class ApplicationsController extends Controller
 | 
			
		||||
        summary: 'Create (Private - GH App)',
 | 
			
		||||
        description: 'Create new application based on a private repository through a Github App.',
 | 
			
		||||
        path: '/applications/private-github-app',
 | 
			
		||||
        operationId: 'create-private-github-app-application',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -303,6 +306,7 @@ class ApplicationsController extends Controller
 | 
			
		||||
        summary: 'Create (Private - Deploy Key)',
 | 
			
		||||
        description: 'Create new application based on a private repository through a Deploy Key.',
 | 
			
		||||
        path: '/applications/private-deploy-key',
 | 
			
		||||
        operationId: 'create-private-deploy-key-application',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -404,6 +408,7 @@ class ApplicationsController extends Controller
 | 
			
		||||
        summary: 'Create (Dockerfile)',
 | 
			
		||||
        description: 'Create new application based on a simple Dockerfile.',
 | 
			
		||||
        path: '/applications/dockerfile',
 | 
			
		||||
        operationId: 'create-dockerfile-application',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -490,6 +495,7 @@ class ApplicationsController extends Controller
 | 
			
		||||
        summary: 'Create (Docker Image)',
 | 
			
		||||
        description: 'Create new application based on a prebuilt docker image',
 | 
			
		||||
        path: '/applications/dockerimage',
 | 
			
		||||
        operationId: 'create-dockerimage-application',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -573,6 +579,7 @@ class ApplicationsController extends Controller
 | 
			
		||||
        summary: 'Create (Docker Compose)',
 | 
			
		||||
        description: 'Create new application based on a docker-compose file.',
 | 
			
		||||
        path: '/applications/dockercompose',
 | 
			
		||||
        operationId: 'create-dockercompose-application',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -1171,6 +1178,7 @@ class ApplicationsController extends Controller
 | 
			
		||||
        summary: 'Get',
 | 
			
		||||
        description: 'Get application by UUID.',
 | 
			
		||||
        path: '/applications/{uuid}',
 | 
			
		||||
        operationId: 'get-application-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -1235,6 +1243,7 @@ class ApplicationsController extends Controller
 | 
			
		||||
        summary: 'Delete',
 | 
			
		||||
        description: 'Delete application by UUID.',
 | 
			
		||||
        path: '/applications/{uuid}',
 | 
			
		||||
        operationId: 'delete-application-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -1321,6 +1330,7 @@ class ApplicationsController extends Controller
 | 
			
		||||
        summary: 'Update',
 | 
			
		||||
        description: 'Update application by UUID.',
 | 
			
		||||
        path: '/applications/{uuid}',
 | 
			
		||||
        operationId: 'update-application-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -1557,6 +1567,7 @@ class ApplicationsController extends Controller
 | 
			
		||||
        summary: 'List Envs',
 | 
			
		||||
        description: 'List all envs by application UUID.',
 | 
			
		||||
        path: '/applications/{uuid}/envs',
 | 
			
		||||
        operationId: 'list-envs-by-application-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -1639,6 +1650,7 @@ class ApplicationsController extends Controller
 | 
			
		||||
        summary: 'Update Env',
 | 
			
		||||
        description: 'Update env by application UUID.',
 | 
			
		||||
        path: '/applications/{uuid}/envs',
 | 
			
		||||
        operationId: 'update-env-by-application-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -1821,6 +1833,7 @@ class ApplicationsController extends Controller
 | 
			
		||||
        summary: 'Update Envs (Bulk)',
 | 
			
		||||
        description: 'Update multiple envs by application UUID.',
 | 
			
		||||
        path: '/applications/{uuid}/envs/bulk',
 | 
			
		||||
        operationId: 'update-envs-by-application-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -2012,6 +2025,7 @@ class ApplicationsController extends Controller
 | 
			
		||||
        summary: 'Create Env',
 | 
			
		||||
        description: 'Create env by application UUID.',
 | 
			
		||||
        path: '/applications/{uuid}/envs',
 | 
			
		||||
        operationId: 'create-env-by-application-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -2171,6 +2185,7 @@ class ApplicationsController extends Controller
 | 
			
		||||
        summary: 'Delete Env',
 | 
			
		||||
        description: 'Delete env by UUID.',
 | 
			
		||||
        path: '/applications/{uuid}/envs/{env_uuid}',
 | 
			
		||||
        operationId: 'delete-env-by-application-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -2256,6 +2271,7 @@ class ApplicationsController extends Controller
 | 
			
		||||
        summary: 'Start',
 | 
			
		||||
        description: 'Start application. `Post` request is also accepted.',
 | 
			
		||||
        path: '/applications/{uuid}/start',
 | 
			
		||||
        operationId: 'start-application-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -2359,6 +2375,7 @@ class ApplicationsController extends Controller
 | 
			
		||||
        summary: 'Stop',
 | 
			
		||||
        description: 'Stop application. `Post` request is also accepted.',
 | 
			
		||||
        path: '/applications/{uuid}/stop',
 | 
			
		||||
        operationId: 'stop-application-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -2431,6 +2448,7 @@ class ApplicationsController extends Controller
 | 
			
		||||
        summary: 'Restart',
 | 
			
		||||
        description: 'Restart application. `Post` request is also accepted.',
 | 
			
		||||
        path: '/applications/{uuid}/restart',
 | 
			
		||||
        operationId: 'restart-application-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,7 @@ class DatabasesController extends Controller
 | 
			
		||||
        summary: 'List',
 | 
			
		||||
        description: 'List all databases.',
 | 
			
		||||
        path: '/databases',
 | 
			
		||||
        operationId: 'list-databases',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -91,6 +92,7 @@ class DatabasesController extends Controller
 | 
			
		||||
        summary: 'Get',
 | 
			
		||||
        description: 'Get database by UUID.',
 | 
			
		||||
        path: '/databases/{uuid}',
 | 
			
		||||
        operationId: 'get-database-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -151,6 +153,7 @@ class DatabasesController extends Controller
 | 
			
		||||
        summary: 'Update',
 | 
			
		||||
        description: 'Update database by UUID.',
 | 
			
		||||
        path: '/databases/{uuid}',
 | 
			
		||||
        operationId: 'update-database-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -510,6 +513,7 @@ class DatabasesController extends Controller
 | 
			
		||||
        summary: 'Create (PostgreSQL)',
 | 
			
		||||
        description: 'Create a new PostgreSQL database.',
 | 
			
		||||
        path: '/databases/postgresql',
 | 
			
		||||
        operationId: 'create-database-postgresql',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -575,6 +579,7 @@ class DatabasesController extends Controller
 | 
			
		||||
        summary: 'Create (Clickhouse)',
 | 
			
		||||
        description: 'Create a new Clickhouse database.',
 | 
			
		||||
        path: '/databases/clickhouse',
 | 
			
		||||
        operationId: 'create-database-clickhouse',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -636,6 +641,7 @@ class DatabasesController extends Controller
 | 
			
		||||
        summary: 'Create (DragonFly)',
 | 
			
		||||
        description: 'Create a new DragonFly database.',
 | 
			
		||||
        path: '/databases/dragonfly',
 | 
			
		||||
        operationId: 'create-database-dragonfly',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -696,6 +702,7 @@ class DatabasesController extends Controller
 | 
			
		||||
        summary: 'Create (Redis)',
 | 
			
		||||
        description: 'Create a new Redis database.',
 | 
			
		||||
        path: '/databases/redis',
 | 
			
		||||
        operationId: 'create-database-redis',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -757,6 +764,7 @@ class DatabasesController extends Controller
 | 
			
		||||
        summary: 'Create (KeyDB)',
 | 
			
		||||
        description: 'Create a new KeyDB database.',
 | 
			
		||||
        path: '/databases/keydb',
 | 
			
		||||
        operationId: 'create-database-keydb',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -818,6 +826,7 @@ class DatabasesController extends Controller
 | 
			
		||||
        summary: 'Create (MariaDB)',
 | 
			
		||||
        description: 'Create a new MariaDB database.',
 | 
			
		||||
        path: '/databases/mariadb',
 | 
			
		||||
        operationId: 'create-database-mariadb',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -882,6 +891,7 @@ class DatabasesController extends Controller
 | 
			
		||||
        summary: 'Create (MySQL)',
 | 
			
		||||
        description: 'Create a new MySQL database.',
 | 
			
		||||
        path: '/databases/mysql',
 | 
			
		||||
        operationId: 'create-database-mysql',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -945,6 +955,7 @@ class DatabasesController extends Controller
 | 
			
		||||
        summary: 'Create (MongoDB)',
 | 
			
		||||
        description: 'Create a new MongoDB database.',
 | 
			
		||||
        path: '/databases/mongodb',
 | 
			
		||||
        operationId: 'create-database-mongodb',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -1514,6 +1525,7 @@ class DatabasesController extends Controller
 | 
			
		||||
        summary: 'Delete',
 | 
			
		||||
        description: 'Delete database by UUID.',
 | 
			
		||||
        path: '/databases/{uuid}',
 | 
			
		||||
        operationId: 'delete-database-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -1597,6 +1609,7 @@ class DatabasesController extends Controller
 | 
			
		||||
        summary: 'Start',
 | 
			
		||||
        description: 'Start database. `Post` request is also accepted.',
 | 
			
		||||
        path: '/databases/{uuid}/start',
 | 
			
		||||
        operationId: 'start-database-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -1672,6 +1685,7 @@ class DatabasesController extends Controller
 | 
			
		||||
        summary: 'Stop',
 | 
			
		||||
        description: 'Stop database. `Post` request is also accepted.',
 | 
			
		||||
        path: '/databases/{uuid}/stop',
 | 
			
		||||
        operationId: 'stop-database-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -1747,6 +1761,7 @@ class DatabasesController extends Controller
 | 
			
		||||
        summary: 'Restart',
 | 
			
		||||
        description: 'Restart database. `Post` request is also accepted.',
 | 
			
		||||
        path: '/databases/{uuid}/restart',
 | 
			
		||||
        operationId: 'restart-database-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,7 @@ class DeployController extends Controller
 | 
			
		||||
        summary: 'List',
 | 
			
		||||
        description: 'List currently running deployments',
 | 
			
		||||
        path: '/deployments',
 | 
			
		||||
        operationId: 'list-deployments',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -79,12 +80,13 @@ class DeployController extends Controller
 | 
			
		||||
        summary: 'Get',
 | 
			
		||||
        description: 'Get deployment by UUID.',
 | 
			
		||||
        path: '/deployments/{uuid}',
 | 
			
		||||
        operationId: 'get-deployment-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
        tags: ['Deployments'],
 | 
			
		||||
        parameters: [
 | 
			
		||||
            new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Deployment Uuid', schema: new OA\Schema(type: 'string')),
 | 
			
		||||
            new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Deployment UUID', schema: new OA\Schema(type: 'string')),
 | 
			
		||||
        ],
 | 
			
		||||
        responses: [
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
@@ -134,6 +136,7 @@ class DeployController extends Controller
 | 
			
		||||
        summary: 'Deploy',
 | 
			
		||||
        description: 'Deploy by tag or uuid. `Post` request also accepted.',
 | 
			
		||||
        path: '/deploy',
 | 
			
		||||
        operationId: 'deploy-by-tag-or-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -147,7 +150,7 @@ class DeployController extends Controller
 | 
			
		||||
        responses: [
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
                response: 200,
 | 
			
		||||
                description: 'Get deployment(s) Uuid\'s',
 | 
			
		||||
                description: 'Get deployment(s) UUID\'s',
 | 
			
		||||
                content: [
 | 
			
		||||
                    new OA\MediaType(
 | 
			
		||||
                        mediaType: 'application/json',
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ class OtherController extends Controller
 | 
			
		||||
        summary: 'Version',
 | 
			
		||||
        description: 'Get Coolify version.',
 | 
			
		||||
        path: '/version',
 | 
			
		||||
        operationId: 'version',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -43,6 +44,7 @@ class OtherController extends Controller
 | 
			
		||||
        summary: 'Enable API',
 | 
			
		||||
        description: 'Enable API (only with root permissions).',
 | 
			
		||||
        path: '/enable',
 | 
			
		||||
        operationId: 'enable-api',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -94,6 +96,7 @@ class OtherController extends Controller
 | 
			
		||||
        summary: 'Disable API',
 | 
			
		||||
        description: 'Disable API (only with root permissions).',
 | 
			
		||||
        path: '/disable',
 | 
			
		||||
        operationId: 'disable-api',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -158,6 +161,7 @@ class OtherController extends Controller
 | 
			
		||||
        summary: 'Healthcheck',
 | 
			
		||||
        description: 'Healthcheck endpoint.',
 | 
			
		||||
        path: '/healthcheck',
 | 
			
		||||
        operationId: 'healthcheck',
 | 
			
		||||
        responses: [
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
                response: 200,
 | 
			
		||||
 
 | 
			
		||||
@@ -11,8 +11,9 @@ class ProjectController extends Controller
 | 
			
		||||
{
 | 
			
		||||
    #[OA\Get(
 | 
			
		||||
        summary: 'List',
 | 
			
		||||
        description: 'list projects.',
 | 
			
		||||
        description: 'List projects.',
 | 
			
		||||
        path: '/projects',
 | 
			
		||||
        operationId: 'list-projects',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -46,7 +47,7 @@ class ProjectController extends Controller
 | 
			
		||||
        if (is_null($teamId)) {
 | 
			
		||||
            return invalidTokenResponse();
 | 
			
		||||
        }
 | 
			
		||||
        $projects = Project::whereTeamId($teamId)->select('id', 'name', 'uuid')->get();
 | 
			
		||||
        $projects = Project::whereTeamId($teamId)->select('id', 'name', 'description', 'uuid')->get();
 | 
			
		||||
 | 
			
		||||
        return response()->json(serializeApiResponse($projects),
 | 
			
		||||
        );
 | 
			
		||||
@@ -54,8 +55,9 @@ class ProjectController extends Controller
 | 
			
		||||
 | 
			
		||||
    #[OA\Get(
 | 
			
		||||
        summary: 'Get',
 | 
			
		||||
        description: 'Get project by Uuid.',
 | 
			
		||||
        description: 'Get project by UUID.',
 | 
			
		||||
        path: '/projects/{uuid}',
 | 
			
		||||
        operationId: 'get-project-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -102,6 +104,7 @@ class ProjectController extends Controller
 | 
			
		||||
        summary: 'Environment',
 | 
			
		||||
        description: 'Get environment by name.',
 | 
			
		||||
        path: '/projects/{uuid}/{environment_name}',
 | 
			
		||||
        operationId: 'get-environment-by-name',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -136,12 +139,15 @@ class ProjectController extends Controller
 | 
			
		||||
            return invalidTokenResponse();
 | 
			
		||||
        }
 | 
			
		||||
        if (! $request->uuid) {
 | 
			
		||||
            return response()->json(['message' => 'Uuid is required.'], 422);
 | 
			
		||||
            return response()->json(['message' => 'UUID is required.'], 422);
 | 
			
		||||
        }
 | 
			
		||||
        if (! $request->environment_name) {
 | 
			
		||||
            return response()->json(['message' => 'Environment name is required.'], 422);
 | 
			
		||||
        }
 | 
			
		||||
        $project = Project::whereTeamId($teamId)->whereUuid($request->uuid)->first();
 | 
			
		||||
        if (! $project) {
 | 
			
		||||
            return response()->json(['message' => 'Project not found.'], 404);
 | 
			
		||||
        }
 | 
			
		||||
        $environment = $project->environments()->whereName($request->environment_name)->first();
 | 
			
		||||
        if (! $environment) {
 | 
			
		||||
            return response()->json(['message' => 'Environment not found.'], 404);
 | 
			
		||||
@@ -155,6 +161,7 @@ class ProjectController extends Controller
 | 
			
		||||
        summary: 'Create',
 | 
			
		||||
        description: 'Create Project.',
 | 
			
		||||
        path: '/projects',
 | 
			
		||||
        operationId: 'create-project',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -167,7 +174,7 @@ class ProjectController extends Controller
 | 
			
		||||
                schema: new OA\Schema(
 | 
			
		||||
                    type: 'object',
 | 
			
		||||
                    properties: [
 | 
			
		||||
                        'uuid' => ['type' => 'string', 'description' => 'The name of the project.'],
 | 
			
		||||
                        'name' => ['type' => 'string', 'description' => 'The name of the project.'],
 | 
			
		||||
                        'description' => ['type' => 'string', 'description' => 'The description of the project.'],
 | 
			
		||||
                    ],
 | 
			
		||||
                ),
 | 
			
		||||
@@ -250,6 +257,7 @@ class ProjectController extends Controller
 | 
			
		||||
        summary: 'Update',
 | 
			
		||||
        description: 'Update Project.',
 | 
			
		||||
        path: '/projects/{uuid}',
 | 
			
		||||
        operationId: 'update-project-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -333,7 +341,7 @@ class ProjectController extends Controller
 | 
			
		||||
        }
 | 
			
		||||
        $uuid = $request->uuid;
 | 
			
		||||
        if (! $uuid) {
 | 
			
		||||
            return response()->json(['message' => 'Uuid is required.'], 422);
 | 
			
		||||
            return response()->json(['message' => 'UUID is required.'], 422);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $project = Project::whereTeamId($teamId)->whereUuid($uuid)->first();
 | 
			
		||||
@@ -355,6 +363,7 @@ class ProjectController extends Controller
 | 
			
		||||
        summary: 'Delete',
 | 
			
		||||
        description: 'Delete project by UUID.',
 | 
			
		||||
        path: '/projects/{uuid}',
 | 
			
		||||
        operationId: 'delete-project-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -408,7 +417,7 @@ class ProjectController extends Controller
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (! $request->uuid) {
 | 
			
		||||
            return response()->json(['message' => 'Uuid is required.'], 422);
 | 
			
		||||
            return response()->json(['message' => 'UUID is required.'], 422);
 | 
			
		||||
        }
 | 
			
		||||
        $project = Project::whereTeamId($teamId)->whereUuid($request->uuid)->first();
 | 
			
		||||
        if (! $project) {
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ class ResourcesController extends Controller
 | 
			
		||||
        summary: 'List',
 | 
			
		||||
        description: 'Get all resources.',
 | 
			
		||||
        path: '/resources',
 | 
			
		||||
        operationId: 'list-resources',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,7 @@ class SecurityController extends Controller
 | 
			
		||||
        summary: 'List',
 | 
			
		||||
        description: 'List all private keys.',
 | 
			
		||||
        path: '/security/keys',
 | 
			
		||||
        operationId: 'list-private-keys',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -68,12 +69,13 @@ class SecurityController extends Controller
 | 
			
		||||
        summary: 'Get',
 | 
			
		||||
        description: 'Get key by UUID.',
 | 
			
		||||
        path: '/security/keys/{uuid}',
 | 
			
		||||
        operationId: 'get-private-key-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
        tags: ['Private Keys'],
 | 
			
		||||
        parameters: [
 | 
			
		||||
            new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key Uuid', schema: new OA\Schema(type: 'string')),
 | 
			
		||||
            new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key UUID', schema: new OA\Schema(type: 'string')),
 | 
			
		||||
        ],
 | 
			
		||||
        responses: [
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
@@ -124,6 +126,7 @@ class SecurityController extends Controller
 | 
			
		||||
        summary: 'Create',
 | 
			
		||||
        description: 'Create a new private key.',
 | 
			
		||||
        path: '/security/keys',
 | 
			
		||||
        operationId: 'create-private-key',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -217,6 +220,7 @@ class SecurityController extends Controller
 | 
			
		||||
        summary: 'Update',
 | 
			
		||||
        description: 'Update a private key.',
 | 
			
		||||
        path: '/security/keys',
 | 
			
		||||
        operationId: 'update-private-key',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -313,12 +317,13 @@ class SecurityController extends Controller
 | 
			
		||||
        summary: 'Delete',
 | 
			
		||||
        description: 'Delete a private key.',
 | 
			
		||||
        path: '/security/keys/{uuid}',
 | 
			
		||||
        operationId: 'delete-private-key-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
        tags: ['Private Keys'],
 | 
			
		||||
        parameters: [
 | 
			
		||||
            new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key Uuid', schema: new OA\Schema(type: 'string')),
 | 
			
		||||
            new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key UUID', schema: new OA\Schema(type: 'string')),
 | 
			
		||||
        ],
 | 
			
		||||
        responses: [
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,7 @@ class ServersController extends Controller
 | 
			
		||||
        summary: 'List',
 | 
			
		||||
        description: 'List all servers.',
 | 
			
		||||
        path: '/servers',
 | 
			
		||||
        operationId: 'list-servers',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -100,12 +101,13 @@ class ServersController extends Controller
 | 
			
		||||
        summary: 'Get',
 | 
			
		||||
        description: 'Get server by UUID.',
 | 
			
		||||
        path: '/servers/{uuid}',
 | 
			
		||||
        operationId: 'get-server-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
        tags: ['Servers'],
 | 
			
		||||
        parameters: [
 | 
			
		||||
            new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'string')),
 | 
			
		||||
            new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s UUID', schema: new OA\Schema(type: 'string')),
 | 
			
		||||
        ],
 | 
			
		||||
        responses: [
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
@@ -177,12 +179,13 @@ class ServersController extends Controller
 | 
			
		||||
        summary: 'Resources',
 | 
			
		||||
        description: 'Get resources by server.',
 | 
			
		||||
        path: '/servers/{uuid}/resources',
 | 
			
		||||
        operationId: 'get-resources-by-server-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
        tags: ['Servers'],
 | 
			
		||||
        parameters: [
 | 
			
		||||
            new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'string')),
 | 
			
		||||
            new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s UUID', schema: new OA\Schema(type: 'string')),
 | 
			
		||||
        ],
 | 
			
		||||
        responses: [
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
@@ -254,12 +257,13 @@ class ServersController extends Controller
 | 
			
		||||
        summary: 'Domains',
 | 
			
		||||
        description: 'Get domains by server.',
 | 
			
		||||
        path: '/servers/{uuid}/domains',
 | 
			
		||||
        operationId: 'get-domains-by-server-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
        tags: ['Servers'],
 | 
			
		||||
        parameters: [
 | 
			
		||||
            new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'string')),
 | 
			
		||||
            new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s UUID', schema: new OA\Schema(type: 'string')),
 | 
			
		||||
        ],
 | 
			
		||||
        responses: [
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
@@ -401,6 +405,7 @@ class ServersController extends Controller
 | 
			
		||||
        summary: 'Create',
 | 
			
		||||
        description: 'Create Server.',
 | 
			
		||||
        path: '/servers',
 | 
			
		||||
        operationId: 'create-server',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -545,6 +550,7 @@ class ServersController extends Controller
 | 
			
		||||
        summary: 'Update',
 | 
			
		||||
        description: 'Update Server.',
 | 
			
		||||
        path: '/servers/{uuid}',
 | 
			
		||||
        operationId: 'update-server-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -655,6 +661,7 @@ class ServersController extends Controller
 | 
			
		||||
        summary: 'Delete',
 | 
			
		||||
        description: 'Delete server by UUID.',
 | 
			
		||||
        path: '/servers/{uuid}',
 | 
			
		||||
        operationId: 'delete-server-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -727,6 +734,7 @@ class ServersController extends Controller
 | 
			
		||||
        summary: 'Validate',
 | 
			
		||||
        description: 'Validate server by UUID.',
 | 
			
		||||
        path: '/servers/{uuid}/validate',
 | 
			
		||||
        operationId: 'validate-server-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,7 @@ class ServicesController extends Controller
 | 
			
		||||
        summary: 'List',
 | 
			
		||||
        description: 'List all services.',
 | 
			
		||||
        path: '/services',
 | 
			
		||||
        operationId: 'list-services',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -88,6 +89,7 @@ class ServicesController extends Controller
 | 
			
		||||
        summary: 'Create',
 | 
			
		||||
        description: 'Create a one-click service',
 | 
			
		||||
        path: '/services',
 | 
			
		||||
        operationId: 'create-service',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -365,6 +367,7 @@ class ServicesController extends Controller
 | 
			
		||||
        summary: 'Get',
 | 
			
		||||
        description: 'Get service by UUID.',
 | 
			
		||||
        path: '/services/{uuid}',
 | 
			
		||||
        operationId: 'get-service-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -375,7 +378,7 @@ class ServicesController extends Controller
 | 
			
		||||
        responses: [
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
                response: 200,
 | 
			
		||||
                description: 'Get a service by Uuid.',
 | 
			
		||||
                description: 'Get a service by UUID.',
 | 
			
		||||
                content: [
 | 
			
		||||
                    new OA\MediaType(
 | 
			
		||||
                        mediaType: 'application/json',
 | 
			
		||||
@@ -422,6 +425,7 @@ class ServicesController extends Controller
 | 
			
		||||
        summary: 'Delete',
 | 
			
		||||
        description: 'Delete service by UUID.',
 | 
			
		||||
        path: '/services/{uuid}',
 | 
			
		||||
        operationId: 'delete-service-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -432,7 +436,7 @@ class ServicesController extends Controller
 | 
			
		||||
        responses: [
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
                response: 200,
 | 
			
		||||
                description: 'Delete a service by Uuid',
 | 
			
		||||
                description: 'Delete a service by UUID',
 | 
			
		||||
                content: [
 | 
			
		||||
                    new OA\MediaType(
 | 
			
		||||
                        mediaType: 'application/json',
 | 
			
		||||
@@ -479,10 +483,521 @@ class ServicesController extends Controller
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[OA\Get(
 | 
			
		||||
        summary: 'List Envs',
 | 
			
		||||
        description: 'List all envs by service UUID.',
 | 
			
		||||
        path: '/services/{uuid}/envs',
 | 
			
		||||
        operationId: 'list-envs-by-service-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
        tags: ['Services'],
 | 
			
		||||
        parameters: [
 | 
			
		||||
            new OA\Parameter(
 | 
			
		||||
                name: 'uuid',
 | 
			
		||||
                in: 'path',
 | 
			
		||||
                description: 'UUID of the service.',
 | 
			
		||||
                required: true,
 | 
			
		||||
                schema: new OA\Schema(
 | 
			
		||||
                    type: 'string',
 | 
			
		||||
                    format: 'uuid',
 | 
			
		||||
                )
 | 
			
		||||
            ),
 | 
			
		||||
        ],
 | 
			
		||||
        responses: [
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
                response: 200,
 | 
			
		||||
                description: 'All environment variables by service UUID.',
 | 
			
		||||
                content: [
 | 
			
		||||
                    new OA\MediaType(
 | 
			
		||||
                        mediaType: 'application/json',
 | 
			
		||||
                        schema: new OA\Schema(
 | 
			
		||||
                            type: 'array',
 | 
			
		||||
                            items: new OA\Items(ref: '#/components/schemas/EnvironmentVariable')
 | 
			
		||||
                        )
 | 
			
		||||
                    ),
 | 
			
		||||
                ]),
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
                response: 401,
 | 
			
		||||
                ref: '#/components/responses/401',
 | 
			
		||||
            ),
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
                response: 400,
 | 
			
		||||
                ref: '#/components/responses/400',
 | 
			
		||||
            ),
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
                response: 404,
 | 
			
		||||
                ref: '#/components/responses/404',
 | 
			
		||||
            ),
 | 
			
		||||
        ]
 | 
			
		||||
    )]
 | 
			
		||||
    public function envs(Request $request)
 | 
			
		||||
    {
 | 
			
		||||
        $teamId = getTeamIdFromToken();
 | 
			
		||||
        if (is_null($teamId)) {
 | 
			
		||||
            return invalidTokenResponse();
 | 
			
		||||
        }
 | 
			
		||||
        $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
 | 
			
		||||
        if (! $service) {
 | 
			
		||||
            return response()->json(['message' => 'Service not found.'], 404);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $envs = $service->environment_variables->map(function ($env) {
 | 
			
		||||
            $env->makeHidden([
 | 
			
		||||
                'application_id',
 | 
			
		||||
                'standalone_clickhouse_id',
 | 
			
		||||
                'standalone_dragonfly_id',
 | 
			
		||||
                'standalone_keydb_id',
 | 
			
		||||
                'standalone_mariadb_id',
 | 
			
		||||
                'standalone_mongodb_id',
 | 
			
		||||
                'standalone_mysql_id',
 | 
			
		||||
                'standalone_postgresql_id',
 | 
			
		||||
                'standalone_redis_id',
 | 
			
		||||
            ]);
 | 
			
		||||
            $env = $this->removeSensitiveData($env);
 | 
			
		||||
 | 
			
		||||
            return $env;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return response()->json($envs);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[OA\Patch(
 | 
			
		||||
        summary: 'Update Env',
 | 
			
		||||
        description: 'Update env by service UUID.',
 | 
			
		||||
        path: '/services/{uuid}/envs',
 | 
			
		||||
        operationId: 'update-env-by-service-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
        tags: ['Services'],
 | 
			
		||||
        parameters: [
 | 
			
		||||
            new OA\Parameter(
 | 
			
		||||
                name: 'uuid',
 | 
			
		||||
                in: 'path',
 | 
			
		||||
                description: 'UUID of the service.',
 | 
			
		||||
                required: true,
 | 
			
		||||
                schema: new OA\Schema(
 | 
			
		||||
                    type: 'string',
 | 
			
		||||
                    format: 'uuid',
 | 
			
		||||
                )
 | 
			
		||||
            ),
 | 
			
		||||
        ],
 | 
			
		||||
        requestBody: new OA\RequestBody(
 | 
			
		||||
            description: 'Env updated.',
 | 
			
		||||
            required: true,
 | 
			
		||||
            content: [
 | 
			
		||||
                new OA\MediaType(
 | 
			
		||||
                    mediaType: 'application/json',
 | 
			
		||||
                    schema: new OA\Schema(
 | 
			
		||||
                        type: 'object',
 | 
			
		||||
                        required: ['key', 'value'],
 | 
			
		||||
                        properties: [
 | 
			
		||||
                            'key' => ['type' => 'string', 'description' => 'The key of the environment variable.'],
 | 
			
		||||
                            'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'],
 | 
			
		||||
                            'is_preview' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in preview deployments.'],
 | 
			
		||||
                            'is_build_time' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in build time.'],
 | 
			
		||||
                            'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
 | 
			
		||||
                            'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
 | 
			
		||||
                            'is_shown_once' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable\'s value is shown on the UI.'],
 | 
			
		||||
                        ],
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
            ],
 | 
			
		||||
        ),
 | 
			
		||||
        responses: [
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
                response: 201,
 | 
			
		||||
                description: 'Environment variable updated.',
 | 
			
		||||
                content: [
 | 
			
		||||
                    new OA\MediaType(
 | 
			
		||||
                        mediaType: 'application/json',
 | 
			
		||||
                        schema: new OA\Schema(
 | 
			
		||||
                            type: 'object',
 | 
			
		||||
                            properties: [
 | 
			
		||||
                                'message' => ['type' => 'string', 'example' => 'Environment variable updated.'],
 | 
			
		||||
                            ]
 | 
			
		||||
                        )
 | 
			
		||||
                    ),
 | 
			
		||||
                ]),
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
                response: 401,
 | 
			
		||||
                ref: '#/components/responses/401',
 | 
			
		||||
            ),
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
                response: 400,
 | 
			
		||||
                ref: '#/components/responses/400',
 | 
			
		||||
            ),
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
                response: 404,
 | 
			
		||||
                ref: '#/components/responses/404',
 | 
			
		||||
            ),
 | 
			
		||||
        ]
 | 
			
		||||
    )]
 | 
			
		||||
    public function update_env_by_uuid(Request $request)
 | 
			
		||||
    {
 | 
			
		||||
        $teamId = getTeamIdFromToken();
 | 
			
		||||
        if (is_null($teamId)) {
 | 
			
		||||
            return invalidTokenResponse();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
 | 
			
		||||
        if (! $service) {
 | 
			
		||||
            return response()->json(['message' => 'Service not found.'], 404);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $validator = customApiValidator($request->all(), [
 | 
			
		||||
            'key' => 'string|required',
 | 
			
		||||
            'value' => 'string|nullable',
 | 
			
		||||
            'is_build_time' => 'boolean',
 | 
			
		||||
            'is_literal' => 'boolean',
 | 
			
		||||
            'is_multiline' => 'boolean',
 | 
			
		||||
            'is_shown_once' => 'boolean',
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        if ($validator->fails()) {
 | 
			
		||||
            return response()->json([
 | 
			
		||||
                'message' => 'Validation failed.',
 | 
			
		||||
                'errors' => $validator->errors(),
 | 
			
		||||
            ], 422);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $env = $service->environment_variables()->where('key', $request->key)->first();
 | 
			
		||||
        if (! $env) {
 | 
			
		||||
            return response()->json(['message' => 'Environment variable not found.'], 404);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $env->fill($request->all());
 | 
			
		||||
        $env->save();
 | 
			
		||||
 | 
			
		||||
        return response()->json($this->removeSensitiveData($env))->setStatusCode(201);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[OA\Patch(
 | 
			
		||||
        summary: 'Update Envs (Bulk)',
 | 
			
		||||
        description: 'Update multiple envs by service UUID.',
 | 
			
		||||
        path: '/services/{uuid}/envs/bulk',
 | 
			
		||||
        operationId: 'update-envs-by-service-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
        tags: ['Services'],
 | 
			
		||||
        parameters: [
 | 
			
		||||
            new OA\Parameter(
 | 
			
		||||
                name: 'uuid',
 | 
			
		||||
                in: 'path',
 | 
			
		||||
                description: 'UUID of the service.',
 | 
			
		||||
                required: true,
 | 
			
		||||
                schema: new OA\Schema(
 | 
			
		||||
                    type: 'string',
 | 
			
		||||
                    format: 'uuid',
 | 
			
		||||
                )
 | 
			
		||||
            ),
 | 
			
		||||
        ],
 | 
			
		||||
        requestBody: new OA\RequestBody(
 | 
			
		||||
            description: 'Bulk envs updated.',
 | 
			
		||||
            required: true,
 | 
			
		||||
            content: [
 | 
			
		||||
                new OA\MediaType(
 | 
			
		||||
                    mediaType: 'application/json',
 | 
			
		||||
                    schema: new OA\Schema(
 | 
			
		||||
                        type: 'object',
 | 
			
		||||
                        required: ['data'],
 | 
			
		||||
                        properties: [
 | 
			
		||||
                            'data' => [
 | 
			
		||||
                                'type' => 'array',
 | 
			
		||||
                                'items' => new OA\Schema(
 | 
			
		||||
                                    type: 'object',
 | 
			
		||||
                                    properties: [
 | 
			
		||||
                                        'key' => ['type' => 'string', 'description' => 'The key of the environment variable.'],
 | 
			
		||||
                                        'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'],
 | 
			
		||||
                                        'is_preview' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in preview deployments.'],
 | 
			
		||||
                                        'is_build_time' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in build time.'],
 | 
			
		||||
                                        'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
 | 
			
		||||
                                        'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
 | 
			
		||||
                                        'is_shown_once' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable\'s value is shown on the UI.'],
 | 
			
		||||
                                    ],
 | 
			
		||||
                                ),
 | 
			
		||||
                            ],
 | 
			
		||||
                        ],
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
            ],
 | 
			
		||||
        ),
 | 
			
		||||
        responses: [
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
                response: 201,
 | 
			
		||||
                description: 'Environment variables updated.',
 | 
			
		||||
                content: [
 | 
			
		||||
                    new OA\MediaType(
 | 
			
		||||
                        mediaType: 'application/json',
 | 
			
		||||
                        schema: new OA\Schema(
 | 
			
		||||
                            type: 'object',
 | 
			
		||||
                            properties: [
 | 
			
		||||
                                'message' => ['type' => 'string', 'example' => 'Environment variables updated.'],
 | 
			
		||||
                            ]
 | 
			
		||||
                        )
 | 
			
		||||
                    ),
 | 
			
		||||
                ]),
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
                response: 401,
 | 
			
		||||
                ref: '#/components/responses/401',
 | 
			
		||||
            ),
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
                response: 400,
 | 
			
		||||
                ref: '#/components/responses/400',
 | 
			
		||||
            ),
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
                response: 404,
 | 
			
		||||
                ref: '#/components/responses/404',
 | 
			
		||||
            ),
 | 
			
		||||
        ]
 | 
			
		||||
    )]
 | 
			
		||||
    public function create_bulk_envs(Request $request)
 | 
			
		||||
    {
 | 
			
		||||
        $teamId = getTeamIdFromToken();
 | 
			
		||||
        if (is_null($teamId)) {
 | 
			
		||||
            return invalidTokenResponse();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
 | 
			
		||||
        if (! $service) {
 | 
			
		||||
            return response()->json(['message' => 'Service not found.'], 404);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $bulk_data = $request->get('data');
 | 
			
		||||
        if (! $bulk_data) {
 | 
			
		||||
            return response()->json(['message' => 'Bulk data is required.'], 400);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $updatedEnvs = collect();
 | 
			
		||||
        foreach ($bulk_data as $item) {
 | 
			
		||||
            $validator = customApiValidator($item, [
 | 
			
		||||
                'key' => 'string|required',
 | 
			
		||||
                'value' => 'string|nullable',
 | 
			
		||||
                'is_build_time' => 'boolean',
 | 
			
		||||
                'is_literal' => 'boolean',
 | 
			
		||||
                'is_multiline' => 'boolean',
 | 
			
		||||
                'is_shown_once' => 'boolean',
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            if ($validator->fails()) {
 | 
			
		||||
                return response()->json([
 | 
			
		||||
                    'message' => 'Validation failed.',
 | 
			
		||||
                    'errors' => $validator->errors(),
 | 
			
		||||
                ], 422);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $env = $service->environment_variables()->updateOrCreate(
 | 
			
		||||
                ['key' => $item['key']],
 | 
			
		||||
                $item
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            $updatedEnvs->push($this->removeSensitiveData($env));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return response()->json($updatedEnvs)->setStatusCode(201);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[OA\Post(
 | 
			
		||||
        summary: 'Create Env',
 | 
			
		||||
        description: 'Create env by service UUID.',
 | 
			
		||||
        path: '/services/{uuid}/envs',
 | 
			
		||||
        operationId: 'create-env-by-service-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
        tags: ['Services'],
 | 
			
		||||
        parameters: [
 | 
			
		||||
            new OA\Parameter(
 | 
			
		||||
                name: 'uuid',
 | 
			
		||||
                in: 'path',
 | 
			
		||||
                description: 'UUID of the service.',
 | 
			
		||||
                required: true,
 | 
			
		||||
                schema: new OA\Schema(
 | 
			
		||||
                    type: 'string',
 | 
			
		||||
                    format: 'uuid',
 | 
			
		||||
                )
 | 
			
		||||
            ),
 | 
			
		||||
        ],
 | 
			
		||||
        requestBody: new OA\RequestBody(
 | 
			
		||||
            required: true,
 | 
			
		||||
            description: 'Env created.',
 | 
			
		||||
            content: new OA\MediaType(
 | 
			
		||||
                mediaType: 'application/json',
 | 
			
		||||
                schema: new OA\Schema(
 | 
			
		||||
                    type: 'object',
 | 
			
		||||
                    properties: [
 | 
			
		||||
                        'key' => ['type' => 'string', 'description' => 'The key of the environment variable.'],
 | 
			
		||||
                        'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'],
 | 
			
		||||
                        'is_preview' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in preview deployments.'],
 | 
			
		||||
                        'is_build_time' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in build time.'],
 | 
			
		||||
                        'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
 | 
			
		||||
                        'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
 | 
			
		||||
                        'is_shown_once' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable\'s value is shown on the UI.'],
 | 
			
		||||
                    ],
 | 
			
		||||
                ),
 | 
			
		||||
            ),
 | 
			
		||||
        ),
 | 
			
		||||
        responses: [
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
                response: 201,
 | 
			
		||||
                description: 'Environment variable created.',
 | 
			
		||||
                content: [
 | 
			
		||||
                    new OA\MediaType(
 | 
			
		||||
                        mediaType: 'application/json',
 | 
			
		||||
                        schema: new OA\Schema(
 | 
			
		||||
                            type: 'object',
 | 
			
		||||
                            properties: [
 | 
			
		||||
                                'uuid' => ['type' => 'string', 'example' => 'nc0k04gk8g0cgsk440g0koko'],
 | 
			
		||||
                            ]
 | 
			
		||||
                        )
 | 
			
		||||
                    ),
 | 
			
		||||
                ]),
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
                response: 401,
 | 
			
		||||
                ref: '#/components/responses/401',
 | 
			
		||||
            ),
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
                response: 400,
 | 
			
		||||
                ref: '#/components/responses/400',
 | 
			
		||||
            ),
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
                response: 404,
 | 
			
		||||
                ref: '#/components/responses/404',
 | 
			
		||||
            ),
 | 
			
		||||
        ]
 | 
			
		||||
    )]
 | 
			
		||||
    public function create_env(Request $request)
 | 
			
		||||
    {
 | 
			
		||||
        $teamId = getTeamIdFromToken();
 | 
			
		||||
        if (is_null($teamId)) {
 | 
			
		||||
            return invalidTokenResponse();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
 | 
			
		||||
        if (! $service) {
 | 
			
		||||
            return response()->json(['message' => 'Service not found.'], 404);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $validator = customApiValidator($request->all(), [
 | 
			
		||||
            'key' => 'string|required',
 | 
			
		||||
            'value' => 'string|nullable',
 | 
			
		||||
            'is_build_time' => 'boolean',
 | 
			
		||||
            'is_literal' => 'boolean',
 | 
			
		||||
            'is_multiline' => 'boolean',
 | 
			
		||||
            'is_shown_once' => 'boolean',
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        if ($validator->fails()) {
 | 
			
		||||
            return response()->json([
 | 
			
		||||
                'message' => 'Validation failed.',
 | 
			
		||||
                'errors' => $validator->errors(),
 | 
			
		||||
            ], 422);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $existingEnv = $service->environment_variables()->where('key', $request->key)->first();
 | 
			
		||||
        if ($existingEnv) {
 | 
			
		||||
            return response()->json([
 | 
			
		||||
                'message' => 'Environment variable already exists. Use PATCH request to update it.',
 | 
			
		||||
            ], 409);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $env = $service->environment_variables()->create($request->all());
 | 
			
		||||
 | 
			
		||||
        return response()->json($this->removeSensitiveData($env))->setStatusCode(201);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[OA\Delete(
 | 
			
		||||
        summary: 'Delete Env',
 | 
			
		||||
        description: 'Delete env by UUID.',
 | 
			
		||||
        path: '/services/{uuid}/envs/{env_uuid}',
 | 
			
		||||
        operationId: 'delete-env-by-service-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
        tags: ['Services'],
 | 
			
		||||
        parameters: [
 | 
			
		||||
            new OA\Parameter(
 | 
			
		||||
                name: 'uuid',
 | 
			
		||||
                in: 'path',
 | 
			
		||||
                description: 'UUID of the service.',
 | 
			
		||||
                required: true,
 | 
			
		||||
                schema: new OA\Schema(
 | 
			
		||||
                    type: 'string',
 | 
			
		||||
                    format: 'uuid',
 | 
			
		||||
                )
 | 
			
		||||
            ),
 | 
			
		||||
            new OA\Parameter(
 | 
			
		||||
                name: 'env_uuid',
 | 
			
		||||
                in: 'path',
 | 
			
		||||
                description: 'UUID of the environment variable.',
 | 
			
		||||
                required: true,
 | 
			
		||||
                schema: new OA\Schema(
 | 
			
		||||
                    type: 'string',
 | 
			
		||||
                    format: 'uuid',
 | 
			
		||||
                )
 | 
			
		||||
            ),
 | 
			
		||||
        ],
 | 
			
		||||
        responses: [
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
                response: 200,
 | 
			
		||||
                description: 'Environment variable deleted.',
 | 
			
		||||
                content: [
 | 
			
		||||
                    new OA\MediaType(
 | 
			
		||||
                        mediaType: 'application/json',
 | 
			
		||||
                        schema: new OA\Schema(
 | 
			
		||||
                            type: 'object',
 | 
			
		||||
                            properties: [
 | 
			
		||||
                                'message' => ['type' => 'string', 'example' => 'Environment variable deleted.'],
 | 
			
		||||
                            ]
 | 
			
		||||
                        )
 | 
			
		||||
                    ),
 | 
			
		||||
                ]),
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
                response: 401,
 | 
			
		||||
                ref: '#/components/responses/401',
 | 
			
		||||
            ),
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
                response: 400,
 | 
			
		||||
                ref: '#/components/responses/400',
 | 
			
		||||
            ),
 | 
			
		||||
            new OA\Response(
 | 
			
		||||
                response: 404,
 | 
			
		||||
                ref: '#/components/responses/404',
 | 
			
		||||
            ),
 | 
			
		||||
        ]
 | 
			
		||||
    )]
 | 
			
		||||
    public function delete_env_by_uuid(Request $request)
 | 
			
		||||
    {
 | 
			
		||||
        $teamId = getTeamIdFromToken();
 | 
			
		||||
        if (is_null($teamId)) {
 | 
			
		||||
            return invalidTokenResponse();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
 | 
			
		||||
        if (! $service) {
 | 
			
		||||
            return response()->json(['message' => 'Service not found.'], 404);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $env = EnvironmentVariable::where('uuid', $request->env_uuid)
 | 
			
		||||
            ->where('service_id', $service->id)
 | 
			
		||||
            ->first();
 | 
			
		||||
 | 
			
		||||
        if (! $env) {
 | 
			
		||||
            return response()->json(['message' => 'Environment variable not found.'], 404);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $env->forceDelete();
 | 
			
		||||
 | 
			
		||||
        return response()->json(['message' => 'Environment variable deleted.']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[OA\Get(
 | 
			
		||||
        summary: 'Start',
 | 
			
		||||
        description: 'Start service. `Post` request is also accepted.',
 | 
			
		||||
        path: '/services/{uuid}/start',
 | 
			
		||||
        operationId: 'start-service-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -558,6 +1073,7 @@ class ServicesController extends Controller
 | 
			
		||||
        summary: 'Stop',
 | 
			
		||||
        description: 'Stop service. `Post` request is also accepted.',
 | 
			
		||||
        path: '/services/{uuid}/stop',
 | 
			
		||||
        operationId: 'stop-service-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -633,6 +1149,7 @@ class ServicesController extends Controller
 | 
			
		||||
        summary: 'Restart',
 | 
			
		||||
        description: 'Restart service. `Post` request is also accepted.',
 | 
			
		||||
        path: '/services/{uuid}/restart',
 | 
			
		||||
        operationId: 'restart-service-by-uuid',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,7 @@ class TeamController extends Controller
 | 
			
		||||
        summary: 'List',
 | 
			
		||||
        description: 'Get all teams.',
 | 
			
		||||
        path: '/teams',
 | 
			
		||||
        operationId: 'list-teams',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -79,6 +80,7 @@ class TeamController extends Controller
 | 
			
		||||
        summary: 'Get',
 | 
			
		||||
        description: 'Get team by TeamId.',
 | 
			
		||||
        path: '/teams/{id}',
 | 
			
		||||
        operationId: 'get-team-by-id',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -129,6 +131,7 @@ class TeamController extends Controller
 | 
			
		||||
        summary: 'Members',
 | 
			
		||||
        description: 'Get members by TeamId.',
 | 
			
		||||
        path: '/teams/{id}/members',
 | 
			
		||||
        operationId: 'get-members-by-team-id',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -189,6 +192,7 @@ class TeamController extends Controller
 | 
			
		||||
        summary: 'Authenticated Team',
 | 
			
		||||
        description: 'Get currently authenticated team.',
 | 
			
		||||
        path: '/teams/current',
 | 
			
		||||
        operationId: 'get-current-team',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
@@ -225,6 +229,7 @@ class TeamController extends Controller
 | 
			
		||||
        summary: 'Authenticated Team Members',
 | 
			
		||||
        description: 'Get currently authenticated team members.',
 | 
			
		||||
        path: '/teams/current/members',
 | 
			
		||||
        operationId: 'get-current-team-members',
 | 
			
		||||
        security: [
 | 
			
		||||
            ['bearerAuth' => []],
 | 
			
		||||
        ],
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ use App\Models\ApplicationPreview;
 | 
			
		||||
use App\Models\EnvironmentVariable;
 | 
			
		||||
use App\Models\GithubApp;
 | 
			
		||||
use App\Models\GitlabApp;
 | 
			
		||||
use App\Models\InstanceSettings;
 | 
			
		||||
use App\Models\Server;
 | 
			
		||||
use App\Models\StandaloneDocker;
 | 
			
		||||
use App\Models\SwarmDocker;
 | 
			
		||||
@@ -109,10 +110,12 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
 | 
			
		||||
    private bool $is_debug_enabled;
 | 
			
		||||
 | 
			
		||||
    private $build_args;
 | 
			
		||||
    private Collection|string $build_args;
 | 
			
		||||
 | 
			
		||||
    private $env_args;
 | 
			
		||||
 | 
			
		||||
    private $environment_variables;
 | 
			
		||||
 | 
			
		||||
    private $env_nixpacks_args;
 | 
			
		||||
 | 
			
		||||
    private $docker_compose;
 | 
			
		||||
@@ -166,6 +169,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
        $this->application_deployment_queue = ApplicationDeploymentQueue::find($application_deployment_queue_id);
 | 
			
		||||
        $this->application = Application::find($this->application_deployment_queue->application_id);
 | 
			
		||||
        $this->build_pack = data_get($this->application, 'build_pack');
 | 
			
		||||
        $this->build_args = collect([]);
 | 
			
		||||
 | 
			
		||||
        $this->application_deployment_queue_id = $application_deployment_queue_id;
 | 
			
		||||
        $this->deployment_uuid = $this->application_deployment_queue->deployment_uuid;
 | 
			
		||||
@@ -204,7 +208,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
                $this->container_name = "{$this->application->settings->custom_internal_name}-pr-{$this->pull_request_id}";
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        ray('New container name: ', $this->container_name);
 | 
			
		||||
        ray('New container name: ', $this->container_name)->green();
 | 
			
		||||
 | 
			
		||||
        savePrivateKeyToFs($this->server);
 | 
			
		||||
        $this->saved_outputs = collect();
 | 
			
		||||
@@ -419,15 +423,42 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
        $this->prepare_builder_image();
 | 
			
		||||
        $this->check_git_if_build_needed();
 | 
			
		||||
        $this->clone_repository();
 | 
			
		||||
        if ($this->preserveRepository) {
 | 
			
		||||
            foreach ($this->application->fileStorages as $fileStorage) {
 | 
			
		||||
                $path = $fileStorage->fs_path;
 | 
			
		||||
                $saveName = 'file_stat_'.$fileStorage->id;
 | 
			
		||||
                $realPathInGit = str($path)->replace($this->application->workdir(), $this->workdir)->value();
 | 
			
		||||
                // check if the file is a directory or a file inside the repository
 | 
			
		||||
                $this->execute_remote_command(
 | 
			
		||||
                    [executeInDocker($this->deployment_uuid, "stat -c '%F' {$realPathInGit}"), 'hidden' => true, 'ignore_errors' => true, 'save' => $saveName]
 | 
			
		||||
                );
 | 
			
		||||
                if ($this->saved_outputs->has($saveName)) {
 | 
			
		||||
                    $fileStat = $this->saved_outputs->get($saveName);
 | 
			
		||||
                    if ($fileStat->value() === 'directory' && ! $fileStorage->is_directory) {
 | 
			
		||||
                        $fileStorage->is_directory = true;
 | 
			
		||||
                        $fileStorage->content = null;
 | 
			
		||||
                        $fileStorage->save();
 | 
			
		||||
                        $fileStorage->deleteStorageOnServer();
 | 
			
		||||
                        $fileStorage->saveStorageOnServer();
 | 
			
		||||
                    } elseif ($fileStat->value() === 'regular file' && $fileStorage->is_directory) {
 | 
			
		||||
                        $fileStorage->is_directory = false;
 | 
			
		||||
                        $fileStorage->is_based_on_git = true;
 | 
			
		||||
                        $fileStorage->save();
 | 
			
		||||
                        $fileStorage->deleteStorageOnServer();
 | 
			
		||||
                        $fileStorage->saveStorageOnServer();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $this->generate_image_names();
 | 
			
		||||
        $this->cleanup_git();
 | 
			
		||||
        $this->application->loadComposeFile(isInit: false);
 | 
			
		||||
        if ($this->application->settings->is_raw_compose_deployment_enabled) {
 | 
			
		||||
            $this->application->parseRawCompose();
 | 
			
		||||
            $this->application->oldRawParser();
 | 
			
		||||
            $yaml = $composeFile = $this->application->docker_compose_raw;
 | 
			
		||||
            $this->save_environment_variables();
 | 
			
		||||
        } else {
 | 
			
		||||
            $composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id, preview_id: data_get($this, 'preview.id'));
 | 
			
		||||
            $composeFile = $this->application->parse(pull_request_id: $this->pull_request_id, preview_id: data_get($this->preview, 'id'));
 | 
			
		||||
            $this->save_environment_variables();
 | 
			
		||||
            if (! is_null($this->env_filename)) {
 | 
			
		||||
                $services = collect($composeFile['services']);
 | 
			
		||||
@@ -444,11 +475,12 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            $yaml = Yaml::dump($composeFile->toArray(), 10);
 | 
			
		||||
            $yaml = Yaml::dump(convertToArray($composeFile), 10);
 | 
			
		||||
        }
 | 
			
		||||
        $this->docker_compose_base64 = base64_encode($yaml);
 | 
			
		||||
        $this->execute_remote_command([
 | 
			
		||||
            executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}{$this->docker_compose_location} > /dev/null"), 'hidden' => true,
 | 
			
		||||
            executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}{$this->docker_compose_location} > /dev/null"),
 | 
			
		||||
            'hidden' => true,
 | 
			
		||||
        ]);
 | 
			
		||||
        // Build new container to limit downtime.
 | 
			
		||||
        $this->application_deployment_queue->addLogEntry('Pulling & building required images.');
 | 
			
		||||
@@ -478,9 +510,13 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
            // TODO
 | 
			
		||||
        } else {
 | 
			
		||||
            $this->execute_remote_command([
 | 
			
		||||
                "docker network inspect '{$networkId}' >/dev/null 2>&1 || docker network create --attachable '{$networkId}' >/dev/null || true", 'hidden' => true, 'ignore_errors' => true,
 | 
			
		||||
                "docker network inspect '{$networkId}' >/dev/null 2>&1 || docker network create --attachable '{$networkId}' >/dev/null || true",
 | 
			
		||||
                'hidden' => true,
 | 
			
		||||
                'ignore_errors' => true,
 | 
			
		||||
            ], [
 | 
			
		||||
                "docker network connect {$networkId} coolify-proxy || true", 'hidden' => true, 'ignore_errors' => true,
 | 
			
		||||
                "docker network connect {$networkId} coolify-proxy || true",
 | 
			
		||||
                'hidden' => true,
 | 
			
		||||
                'ignore_errors' => true,
 | 
			
		||||
            ]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -531,8 +567,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
                    $this->execute_remote_command(
 | 
			
		||||
                        [executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
 | 
			
		||||
                    );
 | 
			
		||||
                    $this->write_deployment_configurations();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -626,20 +662,16 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
                    [
 | 
			
		||||
                        "mkdir -p $this->configuration_dir",
 | 
			
		||||
                    ],
 | 
			
		||||
                    // removing this now as we are using docker cp
 | 
			
		||||
                    // [
 | 
			
		||||
                    //     "rm -rf $this->configuration_dir/{*,.*}",
 | 
			
		||||
                    // ],
 | 
			
		||||
                    [
 | 
			
		||||
                        "docker cp {$this->deployment_uuid}:{$this->workdir}/. {$this->configuration_dir}",
 | 
			
		||||
                    ],
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            $this->application->fileStorages()->each(function ($fileStorage) {
 | 
			
		||||
            foreach ($this->application->fileStorages as $fileStorage) {
 | 
			
		||||
                if (! $fileStorage->is_based_on_git && ! $fileStorage->is_directory) {
 | 
			
		||||
                    $fileStorage->saveStorageOnServer();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            }
 | 
			
		||||
            if ($this->use_build_server) {
 | 
			
		||||
                $this->server = $this->build_server;
 | 
			
		||||
            }
 | 
			
		||||
@@ -719,7 +751,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
            $this->application_deployment_queue->addLogEntry("Pushing image to docker registry ({$this->production_image_name}).");
 | 
			
		||||
            $this->execute_remote_command(
 | 
			
		||||
                [
 | 
			
		||||
                    executeInDocker($this->deployment_uuid, "docker push {$this->production_image_name}"), 'hidden' => true,
 | 
			
		||||
                    executeInDocker($this->deployment_uuid, "docker push {$this->production_image_name}"),
 | 
			
		||||
                    'hidden' => true,
 | 
			
		||||
                ],
 | 
			
		||||
            );
 | 
			
		||||
            if ($this->application->docker_registry_image_tag) {
 | 
			
		||||
@@ -727,10 +760,14 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
                $this->application_deployment_queue->addLogEntry("Tagging and pushing image with {$this->application->docker_registry_image_tag} tag.");
 | 
			
		||||
                $this->execute_remote_command(
 | 
			
		||||
                    [
 | 
			
		||||
                        executeInDocker($this->deployment_uuid, "docker tag {$this->production_image_name} {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true,
 | 
			
		||||
                        executeInDocker($this->deployment_uuid, "docker tag {$this->production_image_name} {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"),
 | 
			
		||||
                        'ignore_errors' => true,
 | 
			
		||||
                        'hidden' => true,
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        executeInDocker($this->deployment_uuid, "docker push {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true,
 | 
			
		||||
                        executeInDocker($this->deployment_uuid, "docker push {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"),
 | 
			
		||||
                        'ignore_errors' => true,
 | 
			
		||||
                        'hidden' => true,
 | 
			
		||||
                    ],
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
@@ -827,14 +864,20 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
    private function check_image_locally_or_remotely()
 | 
			
		||||
    {
 | 
			
		||||
        $this->execute_remote_command([
 | 
			
		||||
            "docker images -q {$this->production_image_name} 2>/dev/null", 'hidden' => true, 'save' => 'local_image_found',
 | 
			
		||||
            "docker images -q {$this->production_image_name} 2>/dev/null",
 | 
			
		||||
            'hidden' => true,
 | 
			
		||||
            'save' => 'local_image_found',
 | 
			
		||||
        ]);
 | 
			
		||||
        if (str($this->saved_outputs->get('local_image_found'))->isEmpty() && $this->application->docker_registry_image_name) {
 | 
			
		||||
            $this->execute_remote_command([
 | 
			
		||||
                "docker pull {$this->production_image_name} 2>/dev/null", 'ignore_errors' => true, 'hidden' => true,
 | 
			
		||||
                "docker pull {$this->production_image_name} 2>/dev/null",
 | 
			
		||||
                'ignore_errors' => true,
 | 
			
		||||
                'hidden' => true,
 | 
			
		||||
            ]);
 | 
			
		||||
            $this->execute_remote_command([
 | 
			
		||||
                "docker images -q {$this->production_image_name} 2>/dev/null", 'hidden' => true, 'save' => 'local_image_found',
 | 
			
		||||
                "docker images -q {$this->production_image_name} 2>/dev/null",
 | 
			
		||||
                'hidden' => true,
 | 
			
		||||
                'save' => 'local_image_found',
 | 
			
		||||
            ]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -867,17 +910,24 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
            }
 | 
			
		||||
            if ($this->application->environment_variables_preview->where('key', 'COOLIFY_FQDN')->isEmpty()) {
 | 
			
		||||
                $envs->push("COOLIFY_FQDN={$this->preview->fqdn}");
 | 
			
		||||
                $envs->push("COOLIFY_DOMAIN_URL={$this->preview->fqdn}");
 | 
			
		||||
            }
 | 
			
		||||
            if ($this->application->environment_variables_preview->where('key', 'COOLIFY_URL')->isEmpty()) {
 | 
			
		||||
                $url = str($this->preview->fqdn)->replace('http://', '')->replace('https://', '');
 | 
			
		||||
                $envs->push("COOLIFY_URL={$url}");
 | 
			
		||||
                $envs->push("COOLIFY_DOMAIN_FQDN={$url}");
 | 
			
		||||
            }
 | 
			
		||||
            if ($this->application->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
 | 
			
		||||
                if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
 | 
			
		||||
                    $envs->push("COOLIFY_BRANCH={$local_branch}");
 | 
			
		||||
                }
 | 
			
		||||
                if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
 | 
			
		||||
                    $envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            add_coolify_default_environment_variables($this->application, $envs, $this->application->environment_variables_preview);
 | 
			
		||||
 | 
			
		||||
            foreach ($sorted_environment_variables_preview as $env) {
 | 
			
		||||
                $real_value = $env->real_value;
 | 
			
		||||
                if ($env->version === '4.0.0-beta.239') {
 | 
			
		||||
@@ -912,18 +962,31 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if ($this->application->environment_variables->where('key', 'COOLIFY_FQDN')->isEmpty()) {
 | 
			
		||||
                if ($this->application->compose_parsing_version === '3') {
 | 
			
		||||
                    $envs->push("COOLIFY_URL={$this->application->fqdn}");
 | 
			
		||||
                } else {
 | 
			
		||||
                    $envs->push("COOLIFY_FQDN={$this->application->fqdn}");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if ($this->application->environment_variables->where('key', 'COOLIFY_URL')->isEmpty()) {
 | 
			
		||||
                $url = str($this->application->fqdn)->replace('http://', '')->replace('https://', '');
 | 
			
		||||
                if ($this->application->compose_parsing_version === '3') {
 | 
			
		||||
                    $envs->push("COOLIFY_FQDN={$url}");
 | 
			
		||||
                } else {
 | 
			
		||||
                    $envs->push("COOLIFY_URL={$url}");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if ($this->application->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
 | 
			
		||||
                if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
 | 
			
		||||
                    $envs->push("COOLIFY_BRANCH={$local_branch}");
 | 
			
		||||
                }
 | 
			
		||||
                if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
 | 
			
		||||
                    $envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            add_coolify_default_environment_variables($this->application, $envs, $this->application->environment_variables);
 | 
			
		||||
 | 
			
		||||
            foreach ($sorted_environment_variables as $env) {
 | 
			
		||||
                $real_value = $env->real_value;
 | 
			
		||||
                if ($env->version === '4.0.0-beta.239') {
 | 
			
		||||
@@ -1000,17 +1063,58 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $this->environment_variables = $envs;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function elixir_finetunes()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->pull_request_id === 0) {
 | 
			
		||||
            $envType = 'environment_variables';
 | 
			
		||||
        } else {
 | 
			
		||||
            $envType = 'environment_variables_preview';
 | 
			
		||||
        }
 | 
			
		||||
        $mix_env = $this->application->{$envType}->where('key', 'MIX_ENV')->first();
 | 
			
		||||
        if ($mix_env) {
 | 
			
		||||
            if ($mix_env->is_build_time === false) {
 | 
			
		||||
                $this->application_deployment_queue->addLogEntry('MIX_ENV environment variable is not set as build time.', type: 'error');
 | 
			
		||||
                $this->application_deployment_queue->addLogEntry('Please set MIX_ENV environment variable to be build time variable if you facing any issues with the deployment.', type: 'error');
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            $this->application_deployment_queue->addLogEntry('MIX_ENV environment variable not found.', type: 'error');
 | 
			
		||||
            $this->application_deployment_queue->addLogEntry('Please add MIX_ENV environment variable and set it to be build time variable if you facing any issues with the deployment.', type: 'error');
 | 
			
		||||
        }
 | 
			
		||||
        $secret_key_base = $this->application->{$envType}->where('key', 'SECRET_KEY_BASE')->first();
 | 
			
		||||
        if ($secret_key_base) {
 | 
			
		||||
            if ($secret_key_base->is_build_time === false) {
 | 
			
		||||
                $this->application_deployment_queue->addLogEntry('SECRET_KEY_BASE environment variable is not set as build time.', type: 'error');
 | 
			
		||||
                $this->application_deployment_queue->addLogEntry('Please set SECRET_KEY_BASE environment variable to be build time variable if you facing any issues with the deployment.', type: 'error');
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            $this->application_deployment_queue->addLogEntry('SECRET_KEY_BASE environment variable not found.', type: 'error');
 | 
			
		||||
            $this->application_deployment_queue->addLogEntry('Please add SECRET_KEY_BASE environment variable and set it to be build time variable if you facing any issues with the deployment.', type: 'error');
 | 
			
		||||
        }
 | 
			
		||||
        $database_url = $this->application->{$envType}->where('key', 'DATABASE_URL')->first();
 | 
			
		||||
        if ($database_url) {
 | 
			
		||||
            if ($database_url->is_build_time === false) {
 | 
			
		||||
                $this->application_deployment_queue->addLogEntry('DATABASE_URL environment variable is not set as build time.', type: 'error');
 | 
			
		||||
                $this->application_deployment_queue->addLogEntry('Please set DATABASE_URL environment variable to be build time variable if you facing any issues with the deployment.', type: 'error');
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            $this->application_deployment_queue->addLogEntry('DATABASE_URL environment variable not found.', type: 'error');
 | 
			
		||||
            $this->application_deployment_queue->addLogEntry('Please add DATABASE_URL environment variable and set it to be build time variable if you facing any issues with the deployment.', type: 'error');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function laravel_finetunes()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->pull_request_id === 0) {
 | 
			
		||||
            $nixpacks_php_fallback_path = $this->application->environment_variables->where('key', 'NIXPACKS_PHP_FALLBACK_PATH')->first();
 | 
			
		||||
            $nixpacks_php_root_dir = $this->application->environment_variables->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first();
 | 
			
		||||
            $envType = 'environment_variables';
 | 
			
		||||
        } else {
 | 
			
		||||
            $nixpacks_php_fallback_path = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_FALLBACK_PATH')->first();
 | 
			
		||||
            $nixpacks_php_root_dir = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first();
 | 
			
		||||
            $envType = 'environment_variables_preview';
 | 
			
		||||
        }
 | 
			
		||||
        $nixpacks_php_fallback_path = $this->application->{$envType}->where('key', 'NIXPACKS_PHP_FALLBACK_PATH')->first();
 | 
			
		||||
        $nixpacks_php_root_dir = $this->application->{$envType}->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first();
 | 
			
		||||
 | 
			
		||||
        if (! $nixpacks_php_fallback_path) {
 | 
			
		||||
            $nixpacks_php_fallback_path = new EnvironmentVariable;
 | 
			
		||||
            $nixpacks_php_fallback_path->key = 'NIXPACKS_PHP_FALLBACK_PATH';
 | 
			
		||||
@@ -1230,7 +1334,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
 | 
			
		||||
    private function prepare_builder_image()
 | 
			
		||||
    {
 | 
			
		||||
        $settings = InstanceSettings::get();
 | 
			
		||||
        $helperImage = config('coolify.helper_image');
 | 
			
		||||
        $helperImage = "{$helperImage}:{$settings->helper_version}";
 | 
			
		||||
        // Get user home directory
 | 
			
		||||
        $this->serverUserHomeDir = instant_remote_process(['echo $HOME'], $this->server);
 | 
			
		||||
        $this->dockerConfigFileExists = instant_remote_process(["test -f {$this->serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $this->server);
 | 
			
		||||
@@ -1382,7 +1488,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
        }
 | 
			
		||||
        $this->execute_remote_command(
 | 
			
		||||
            [
 | 
			
		||||
                $importCommands, 'hidden' => true,
 | 
			
		||||
                $importCommands,
 | 
			
		||||
                'hidden' => true,
 | 
			
		||||
            ]
 | 
			
		||||
        );
 | 
			
		||||
        $this->create_workdir();
 | 
			
		||||
@@ -1466,6 +1573,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
                    data_set($parsed, 'variables.NIXPACKS_PHP_FALLBACK_PATH', $variables[0]->value);
 | 
			
		||||
                    data_set($parsed, 'variables.NIXPACKS_PHP_ROOT_DIR', $variables[1]->value);
 | 
			
		||||
                }
 | 
			
		||||
                if ($this->nixpacks_type === 'elixir') {
 | 
			
		||||
                    $this->elixir_finetunes();
 | 
			
		||||
                }
 | 
			
		||||
                $this->nixpacks_plan = json_encode($parsed, JSON_PRETTY_PRINT);
 | 
			
		||||
                $this->application_deployment_queue->addLogEntry("Final Nixpacks plan: {$this->nixpacks_plan}", hidden: true);
 | 
			
		||||
                if ($this->nixpacks_type === 'rust') {
 | 
			
		||||
@@ -1592,7 +1702,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
        // Check for custom HEALTHCHECK
 | 
			
		||||
        if ($this->application->build_pack === 'dockerfile' || $this->application->dockerfile) {
 | 
			
		||||
            $this->execute_remote_command([
 | 
			
		||||
                executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), 'hidden' => true, 'save' => 'dockerfile_from_repo', 'ignore_errors' => true,
 | 
			
		||||
                executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"),
 | 
			
		||||
                'hidden' => true,
 | 
			
		||||
                'save' => 'dockerfile_from_repo',
 | 
			
		||||
                'ignore_errors' => true,
 | 
			
		||||
            ]);
 | 
			
		||||
            $dockerfile = collect(str($this->saved_outputs->get('dockerfile_from_repo'))->trim()->explode("\n"));
 | 
			
		||||
            $this->application->parseHealthcheckFromDockerfile($dockerfile);
 | 
			
		||||
@@ -1695,14 +1808,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
            $docker_compose['services'][$this->container_name]['labels'] = $labels;
 | 
			
		||||
        }
 | 
			
		||||
        if ($this->server->isLogDrainEnabled() && $this->application->isLogDrainEnabled()) {
 | 
			
		||||
            $docker_compose['services'][$this->container_name]['logging'] = [
 | 
			
		||||
                'driver' => 'fluentd',
 | 
			
		||||
                'options' => [
 | 
			
		||||
                    'fluentd-address' => 'tcp://127.0.0.1:24224',
 | 
			
		||||
                    'fluentd-async' => 'true',
 | 
			
		||||
                    'fluentd-sub-second-precision' => 'true',
 | 
			
		||||
                ],
 | 
			
		||||
            ];
 | 
			
		||||
            $docker_compose['services'][$this->container_name]['logging'] = generate_fluentd_configuration();
 | 
			
		||||
        }
 | 
			
		||||
        if ($this->application->settings->is_gpu_enabled) {
 | 
			
		||||
            $docker_compose['services'][$this->container_name]['deploy']['resources']['reservations']['devices'] = [
 | 
			
		||||
@@ -1865,13 +1971,23 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
        $this->application_deployment_queue->addLogEntry("Pulling latest image ($image) from the registry.");
 | 
			
		||||
        $this->execute_remote_command(
 | 
			
		||||
            [
 | 
			
		||||
                executeInDocker($this->deployment_uuid, "docker pull {$image}"), 'hidden' => true,
 | 
			
		||||
                executeInDocker($this->deployment_uuid, "docker pull {$image}"),
 | 
			
		||||
                'hidden' => true,
 | 
			
		||||
            ]
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function build_image()
 | 
			
		||||
    {
 | 
			
		||||
        // Add Coolify related variables to the build args
 | 
			
		||||
        $this->environment_variables->filter(function ($key, $value) {
 | 
			
		||||
            return str($key)->startsWith('COOLIFY_');
 | 
			
		||||
        })->each(function ($key, $value) {
 | 
			
		||||
            $this->build_args->push("--build-arg '{$key}'");
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        $this->build_args = $this->build_args->implode(' ');
 | 
			
		||||
 | 
			
		||||
        $this->application_deployment_queue->addLogEntry('----------------------------------------');
 | 
			
		||||
        if ($this->application->build_pack === 'static') {
 | 
			
		||||
            $this->application_deployment_queue->addLogEntry('Static deployment. Copying static assets to the image.');
 | 
			
		||||
@@ -1915,12 +2031,14 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
 | 
			
		||||
                    $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), 'hidden' => true]);
 | 
			
		||||
                    if ($this->force_rebuild) {
 | 
			
		||||
                        $this->execute_remote_command([
 | 
			
		||||
                            executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true,
 | 
			
		||||
                            executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"),
 | 
			
		||||
                            'hidden' => true,
 | 
			
		||||
                        ]);
 | 
			
		||||
                        $build_command = "docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->build_image_name} {$this->workdir}";
 | 
			
		||||
                    } else {
 | 
			
		||||
                        $this->execute_remote_command([
 | 
			
		||||
                            executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true,
 | 
			
		||||
                            executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"),
 | 
			
		||||
                            'hidden' => true,
 | 
			
		||||
                        ]);
 | 
			
		||||
                        $build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->build_image_name} {$this->workdir}";
 | 
			
		||||
                    }
 | 
			
		||||
@@ -1928,10 +2046,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
 | 
			
		||||
                    $base64_build_command = base64_encode($build_command);
 | 
			
		||||
                    $this->execute_remote_command(
 | 
			
		||||
                        [
 | 
			
		||||
                            executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true,
 | 
			
		||||
                            executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
 | 
			
		||||
                            'hidden' => true,
 | 
			
		||||
                        ],
 | 
			
		||||
                        [
 | 
			
		||||
                            executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true,
 | 
			
		||||
                            executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
 | 
			
		||||
                            'hidden' => true,
 | 
			
		||||
                        ]
 | 
			
		||||
                    );
 | 
			
		||||
                    $this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm /artifacts/thegameplan.json'), 'hidden' => true]);
 | 
			
		||||
@@ -1945,10 +2065,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
 | 
			
		||||
                    }
 | 
			
		||||
                    $this->execute_remote_command(
 | 
			
		||||
                        [
 | 
			
		||||
                            executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true,
 | 
			
		||||
                            executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
 | 
			
		||||
                            'hidden' => true,
 | 
			
		||||
                        ],
 | 
			
		||||
                        [
 | 
			
		||||
                            executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true,
 | 
			
		||||
                            executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
 | 
			
		||||
                            'hidden' => true,
 | 
			
		||||
                        ]
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
@@ -1985,10 +2107,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
 | 
			
		||||
                    executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d | tee {$this->workdir}/nginx.conf > /dev/null"),
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true,
 | 
			
		||||
                    executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
 | 
			
		||||
                    'hidden' => true,
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true,
 | 
			
		||||
                    executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
 | 
			
		||||
                    'hidden' => true,
 | 
			
		||||
                ]
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
@@ -2002,10 +2126,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
 | 
			
		||||
                $base64_build_command = base64_encode($build_command);
 | 
			
		||||
                $this->execute_remote_command(
 | 
			
		||||
                    [
 | 
			
		||||
                        executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true,
 | 
			
		||||
                        executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
 | 
			
		||||
                        'hidden' => true,
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true,
 | 
			
		||||
                        executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
 | 
			
		||||
                        'hidden' => true,
 | 
			
		||||
                    ]
 | 
			
		||||
                );
 | 
			
		||||
            } else {
 | 
			
		||||
@@ -2014,22 +2140,26 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
 | 
			
		||||
                    $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), 'hidden' => true]);
 | 
			
		||||
                    if ($this->force_rebuild) {
 | 
			
		||||
                        $this->execute_remote_command([
 | 
			
		||||
                            executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->production_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true,
 | 
			
		||||
                            executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->production_image_name} {$this->workdir} -o {$this->workdir}"),
 | 
			
		||||
                            'hidden' => true,
 | 
			
		||||
                        ]);
 | 
			
		||||
                        $build_command = "docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
 | 
			
		||||
                    } else {
 | 
			
		||||
                        $this->execute_remote_command([
 | 
			
		||||
                            executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->production_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true,
 | 
			
		||||
                            executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->production_image_name} {$this->workdir} -o {$this->workdir}"),
 | 
			
		||||
                            'hidden' => true,
 | 
			
		||||
                        ]);
 | 
			
		||||
                        $build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
 | 
			
		||||
                    }
 | 
			
		||||
                    $base64_build_command = base64_encode($build_command);
 | 
			
		||||
                    $this->execute_remote_command(
 | 
			
		||||
                        [
 | 
			
		||||
                            executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true,
 | 
			
		||||
                            executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
 | 
			
		||||
                            'hidden' => true,
 | 
			
		||||
                        ],
 | 
			
		||||
                        [
 | 
			
		||||
                            executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true,
 | 
			
		||||
                            executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
 | 
			
		||||
                            'hidden' => true,
 | 
			
		||||
                        ]
 | 
			
		||||
                    );
 | 
			
		||||
                    $this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm /artifacts/thegameplan.json'), 'hidden' => true]);
 | 
			
		||||
@@ -2043,10 +2173,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
 | 
			
		||||
                    }
 | 
			
		||||
                    $this->execute_remote_command(
 | 
			
		||||
                        [
 | 
			
		||||
                            executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true,
 | 
			
		||||
                            executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
 | 
			
		||||
                            'hidden' => true,
 | 
			
		||||
                        ],
 | 
			
		||||
                        [
 | 
			
		||||
                            executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true,
 | 
			
		||||
                            executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
 | 
			
		||||
                            'hidden' => true,
 | 
			
		||||
                        ]
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
@@ -2072,7 +2204,6 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
 | 
			
		||||
        $this->execute_remote_command(
 | 
			
		||||
            ["docker rm -f $containerName", 'hidden' => true, 'ignore_errors' => true]
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function stop_running_container(bool $force = false)
 | 
			
		||||
@@ -2142,15 +2273,14 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
 | 
			
		||||
                $this->build_args->push("--build-arg {$env->key}={$value}");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->build_args = $this->build_args->implode(' ');
 | 
			
		||||
        ray($this->build_args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function add_build_env_variables_to_dockerfile()
 | 
			
		||||
    {
 | 
			
		||||
        $this->execute_remote_command([
 | 
			
		||||
            executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), 'hidden' => true, 'save' => 'dockerfile',
 | 
			
		||||
            executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"),
 | 
			
		||||
            'hidden' => true,
 | 
			
		||||
            'save' => 'dockerfile',
 | 
			
		||||
        ]);
 | 
			
		||||
        $dockerfile = collect(str($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
 | 
			
		||||
        if ($this->pull_request_id === 0) {
 | 
			
		||||
@@ -2168,7 +2298,6 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
 | 
			
		||||
                } else {
 | 
			
		||||
                    $dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}");
 | 
			
		||||
                }
 | 
			
		||||
                $dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $dockerfile_base64 = base64_encode($dockerfile->implode("\n"));
 | 
			
		||||
@@ -2196,7 +2325,8 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
 | 
			
		||||
                $exec = "docker exec {$containerName} {$cmd}";
 | 
			
		||||
                $this->execute_remote_command(
 | 
			
		||||
                    [
 | 
			
		||||
                        'command' => $exec, 'hidden' => true,
 | 
			
		||||
                        'command' => $exec,
 | 
			
		||||
                        'hidden' => true,
 | 
			
		||||
                    ],
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
@@ -2223,7 +2353,9 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
 | 
			
		||||
                try {
 | 
			
		||||
                    $this->execute_remote_command(
 | 
			
		||||
                        [
 | 
			
		||||
                            'command' => $exec, 'hidden' => true, 'save' => 'post-deployment-command-output',
 | 
			
		||||
                            'command' => $exec,
 | 
			
		||||
                            'hidden' => true,
 | 
			
		||||
                            'save' => 'post-deployment-command-output',
 | 
			
		||||
                        ],
 | 
			
		||||
                    );
 | 
			
		||||
                } catch (Exception $e) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,32 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Jobs;
 | 
			
		||||
 | 
			
		||||
use App\Traits\ExecuteRemoteCommand;
 | 
			
		||||
use Illuminate\Bus\Queueable;
 | 
			
		||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 | 
			
		||||
use Illuminate\Contracts\Queue\ShouldQueue;
 | 
			
		||||
use Illuminate\Foundation\Bus\Dispatchable;
 | 
			
		||||
use Illuminate\Queue\InteractsWithQueue;
 | 
			
		||||
use Illuminate\Queue\SerializesModels;
 | 
			
		||||
 | 
			
		||||
class ApplicationRestartJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
{
 | 
			
		||||
    use Dispatchable, ExecuteRemoteCommand, InteractsWithQueue, Queueable, SerializesModels;
 | 
			
		||||
 | 
			
		||||
    public $timeout = 3600;
 | 
			
		||||
 | 
			
		||||
    public $tries = 1;
 | 
			
		||||
 | 
			
		||||
    public string $applicationDeploymentQueueId;
 | 
			
		||||
 | 
			
		||||
    public function __construct(string $applicationDeploymentQueueId)
 | 
			
		||||
    {
 | 
			
		||||
        $this->applicationDeploymentQueueId = $applicationDeploymentQueueId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function handle()
 | 
			
		||||
    {
 | 
			
		||||
        ray('Restarting application');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -10,6 +10,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
 | 
			
		||||
use Illuminate\Queue\InteractsWithQueue;
 | 
			
		||||
use Illuminate\Queue\SerializesModels;
 | 
			
		||||
use Illuminate\Support\Facades\Http;
 | 
			
		||||
use Illuminate\Support\Facades\File;
 | 
			
		||||
 | 
			
		||||
class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
{
 | 
			
		||||
@@ -25,12 +26,14 @@ class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
            $response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
 | 
			
		||||
            if ($response->successful()) {
 | 
			
		||||
                $versions = $response->json();
 | 
			
		||||
 | 
			
		||||
                $latest_version = data_get($versions, 'coolify.v4.version');
 | 
			
		||||
                $current_version = config('version');
 | 
			
		||||
 | 
			
		||||
                if (version_compare($latest_version, $current_version, '>')) {
 | 
			
		||||
                    // New version available
 | 
			
		||||
                    $settings->update(['new_version_available' => true]);
 | 
			
		||||
                    File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT));
 | 
			
		||||
                } else {
 | 
			
		||||
                    $settings->update(['new_version_available' => false]);
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,93 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Jobs;
 | 
			
		||||
 | 
			
		||||
use App\Actions\Server\InstallLogDrain;
 | 
			
		||||
use App\Models\Server;
 | 
			
		||||
use App\Notifications\Container\ContainerRestarted;
 | 
			
		||||
use App\Notifications\Container\ContainerStopped;
 | 
			
		||||
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\Middleware\WithoutOverlapping;
 | 
			
		||||
use Illuminate\Queue\SerializesModels;
 | 
			
		||||
use Illuminate\Support\Sleep;
 | 
			
		||||
 | 
			
		||||
class CheckLogDrainContainerJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
{
 | 
			
		||||
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 | 
			
		||||
 | 
			
		||||
    public function __construct(public Server $server) {}
 | 
			
		||||
 | 
			
		||||
    public function middleware(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [(new WithoutOverlapping($this->server->id))->dontRelease()];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function uniqueId(): int
 | 
			
		||||
    {
 | 
			
		||||
        return $this->server->id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function healthcheck()
 | 
			
		||||
    {
 | 
			
		||||
        $status = instant_remote_process(["docker inspect --format='{{json .State.Status}}' coolify-log-drain"], $this->server, false);
 | 
			
		||||
        if (str($status)->contains('running')) {
 | 
			
		||||
            return true;
 | 
			
		||||
        } else {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function handle()
 | 
			
		||||
    {
 | 
			
		||||
        // ray("checking log drain statuses for {$this->server->id}");
 | 
			
		||||
        try {
 | 
			
		||||
            if (! $this->server->isFunctional()) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            $containers = instant_remote_process(['docker container ls -q'], $this->server, false);
 | 
			
		||||
            if (! $containers) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            $containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server);
 | 
			
		||||
            $containers = format_docker_command_output_to_json($containers);
 | 
			
		||||
 | 
			
		||||
            $foundLogDrainContainer = $containers->filter(function ($value, $key) {
 | 
			
		||||
                return data_get($value, 'Name') === '/coolify-log-drain';
 | 
			
		||||
            })->first();
 | 
			
		||||
            if (! $foundLogDrainContainer || ! $this->healthcheck()) {
 | 
			
		||||
                ray('Log drain container not found or unhealthy. Restarting...');
 | 
			
		||||
                InstallLogDrain::run($this->server);
 | 
			
		||||
                Sleep::for(10)->seconds();
 | 
			
		||||
                if ($this->healthcheck()) {
 | 
			
		||||
                    if ($this->server->log_drain_notification_sent) {
 | 
			
		||||
                        $this->server->team?->notify(new ContainerRestarted('Coolify Log Drainer', $this->server));
 | 
			
		||||
                        $this->server->update(['log_drain_notification_sent' => false]);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                if (! $this->server->log_drain_notification_sent) {
 | 
			
		||||
                    ray('Log drain container still unhealthy. Sending notification...');
 | 
			
		||||
                    // $this->server->team?->notify(new ContainerStopped('Coolify Log Drainer', $this->server, null));
 | 
			
		||||
                    $this->server->update(['log_drain_notification_sent' => true]);
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                if ($this->server->log_drain_notification_sent) {
 | 
			
		||||
                    $this->server->team?->notify(new ContainerRestarted('Coolify Log Drainer', $this->server));
 | 
			
		||||
                    $this->server->update(['log_drain_notification_sent' => false]);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            if (! isCloud()) {
 | 
			
		||||
                send_internal_notification("CheckLogDrainContainerJob failed on ({$this->server->id}) with: ".$e->getMessage());
 | 
			
		||||
            }
 | 
			
		||||
            ray($e->getMessage());
 | 
			
		||||
 | 
			
		||||
            return handleError($e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								app/Jobs/CleanupStaleMultiplexedConnections.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								app/Jobs/CleanupStaleMultiplexedConnections.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Jobs;
 | 
			
		||||
 | 
			
		||||
use App\Models\Server;
 | 
			
		||||
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\Facades\Process;
 | 
			
		||||
 | 
			
		||||
class CleanupStaleMultiplexedConnections implements ShouldQueue
 | 
			
		||||
{
 | 
			
		||||
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 | 
			
		||||
 | 
			
		||||
    public function handle()
 | 
			
		||||
    {
 | 
			
		||||
        Server::chunk(100, function ($servers) {
 | 
			
		||||
            foreach ($servers as $server) {
 | 
			
		||||
                $this->cleanupStaleConnection($server);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function cleanupStaleConnection(Server $server)
 | 
			
		||||
    {
 | 
			
		||||
        $muxSocket = "/tmp/mux_{$server->id}";
 | 
			
		||||
        $checkCommand = "ssh -O check -o ControlPath=$muxSocket {$server->user}@{$server->ip} 2>/dev/null";
 | 
			
		||||
        $checkProcess = Process::run($checkCommand);
 | 
			
		||||
 | 
			
		||||
        if ($checkProcess->exitCode() !== 0) {
 | 
			
		||||
            $closeCommand = "ssh -O exit -o ControlPath=$muxSocket {$server->user}@{$server->ip} 2>/dev/null";
 | 
			
		||||
            Process::run($closeCommand);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -25,6 +25,7 @@ use Illuminate\Queue\InteractsWithQueue;
 | 
			
		||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
 | 
			
		||||
use Illuminate\Queue\SerializesModels;
 | 
			
		||||
use Illuminate\Support\Str;
 | 
			
		||||
use App\Models\InstanceSettings;
 | 
			
		||||
 | 
			
		||||
class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
{
 | 
			
		||||
@@ -56,6 +57,8 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
 | 
			
		||||
    public ?string $backup_output = null;
 | 
			
		||||
 | 
			
		||||
    public ?string $postgres_password = null;
 | 
			
		||||
 | 
			
		||||
    public ?S3Storage $s3 = null;
 | 
			
		||||
 | 
			
		||||
    public function __construct($backup)
 | 
			
		||||
@@ -89,8 +92,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
    public function handle(): void
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            BackupCreated::dispatch($this->team->id);
 | 
			
		||||
 | 
			
		||||
            // Check if team is exists
 | 
			
		||||
            if (is_null($this->team)) {
 | 
			
		||||
                $this->backup->update(['status' => 'failed']);
 | 
			
		||||
@@ -99,6 +100,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            BackupCreated::dispatch($this->team->id);
 | 
			
		||||
 | 
			
		||||
            $status = str(data_get($this->database, 'status'));
 | 
			
		||||
            if (! $status->startsWith('running') && $this->database->id !== 0) {
 | 
			
		||||
                ray('database not running');
 | 
			
		||||
@@ -134,6 +138,13 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
                    } else {
 | 
			
		||||
                        $databasesToBackup = $this->database->postgres_user;
 | 
			
		||||
                    }
 | 
			
		||||
                    $this->postgres_password = $envs->filter(function ($env) {
 | 
			
		||||
                        return str($env)->startsWith('POSTGRES_PASSWORD=');
 | 
			
		||||
                    })->first();
 | 
			
		||||
                    if ($this->postgres_password) {
 | 
			
		||||
                        $this->postgres_password = str($this->postgres_password)->after('POSTGRES_PASSWORD=')->value();
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                } elseif (str($databaseType)->contains('mysql')) {
 | 
			
		||||
                    $this->container_name = "{$this->database->name}-$serviceUuid";
 | 
			
		||||
                    $this->directory_name = $serviceName.'-'.$this->container_name;
 | 
			
		||||
@@ -381,7 +392,13 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $commands[] = 'mkdir -p '.$this->backup_dir;
 | 
			
		||||
            $commands[] = "docker exec $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
 | 
			
		||||
            $backupCommand = 'docker exec';
 | 
			
		||||
            if ($this->postgres_password) {
 | 
			
		||||
                $backupCommand .= " -e PGPASSWORD=$this->postgres_password";
 | 
			
		||||
            }
 | 
			
		||||
            $backupCommand .= " $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
 | 
			
		||||
 | 
			
		||||
            $commands[] = $backupCommand;
 | 
			
		||||
            $this->backup_output = instant_remote_process($commands, $this->server);
 | 
			
		||||
            $this->backup_output = trim($this->backup_output);
 | 
			
		||||
            if ($this->backup_output === '') {
 | 
			
		||||
@@ -452,7 +469,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
        if ($this->backup->number_of_backups_locally === 0) {
 | 
			
		||||
            $deletable = $this->backup->executions()->where('status', 'success');
 | 
			
		||||
        } else {
 | 
			
		||||
            $deletable = $this->backup->executions()->where('status', 'success')->orderByDesc('created_at')->skip($this->backup->number_of_backups_locally - 1);
 | 
			
		||||
            $deletable = $this->backup->executions()->where('status', 'success')->skip($this->backup->number_of_backups_locally - 1);
 | 
			
		||||
        }
 | 
			
		||||
        foreach ($deletable->get() as $execution) {
 | 
			
		||||
            delete_backup_locally($execution->filename, $this->server);
 | 
			
		||||
@@ -477,12 +494,15 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
            } else {
 | 
			
		||||
                $network = $this->database->destination->network;
 | 
			
		||||
            }
 | 
			
		||||
            $commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro ghcr.io/coollabsio/coolify-helper";
 | 
			
		||||
 | 
			
		||||
            $this->ensureHelperImageAvailable();
 | 
			
		||||
 | 
			
		||||
            $fullImageName = $this->getFullImageName();
 | 
			
		||||
            $commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}";
 | 
			
		||||
            $commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret";
 | 
			
		||||
            $commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/";
 | 
			
		||||
            instant_remote_process($commands, $this->server);
 | 
			
		||||
            $this->add_to_backup_output('Uploaded to S3.');
 | 
			
		||||
            ray('Uploaded to S3. '.$this->backup_location.' to s3://'.$bucket.$this->backup_dir);
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            $this->add_to_backup_output($e->getMessage());
 | 
			
		||||
            throw $e;
 | 
			
		||||
@@ -491,4 +511,40 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
            instant_remote_process([$command], $this->server);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
    {
 | 
			
		||||
        $settings = InstanceSettings::get();
 | 
			
		||||
        $helperImage = config('coolify.helper_image');
 | 
			
		||||
        $latestVersion = $settings->helper_version;
 | 
			
		||||
        return "{$helperImage}:{$latestVersion}";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 | 
			
		||||
use Illuminate\Contracts\Queue\ShouldQueue;
 | 
			
		||||
use Illuminate\Foundation\Bus\Dispatchable;
 | 
			
		||||
use Illuminate\Queue\InteractsWithQueue;
 | 
			
		||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
 | 
			
		||||
use Illuminate\Queue\SerializesModels;
 | 
			
		||||
use Illuminate\Support\Facades\Log;
 | 
			
		||||
 | 
			
		||||
@@ -17,21 +18,33 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
{
 | 
			
		||||
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 | 
			
		||||
 | 
			
		||||
    public $timeout = 300;
 | 
			
		||||
    public $timeout = 600;
 | 
			
		||||
 | 
			
		||||
    public int|string|null $usageBefore = null;
 | 
			
		||||
    public $tries = 1;
 | 
			
		||||
 | 
			
		||||
    public ?string $usageBefore = null;
 | 
			
		||||
 | 
			
		||||
    public function __construct(public Server $server) {}
 | 
			
		||||
 | 
			
		||||
    public function middleware(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [new WithoutOverlapping($this->server->id)];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function uniqueId(): int
 | 
			
		||||
    {
 | 
			
		||||
        return $this->server->id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function handle(): void
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            if (! $this->server->isFunctional()) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if ($this->server->settings->is_force_cleanup_enabled) {
 | 
			
		||||
            if ($this->server->settings->force_docker_cleanup) {
 | 
			
		||||
                Log::info('DockerCleanupJob force cleanup on '.$this->server->name);
 | 
			
		||||
                CleanupDocker::run(server: $this->server, force: true);
 | 
			
		||||
                CleanupDocker::run(server: $this->server);
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
@@ -39,12 +52,12 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
            $this->usageBefore = $this->server->getDiskUsage();
 | 
			
		||||
            if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) {
 | 
			
		||||
                Log::info('DockerCleanupJob force cleanup on '.$this->server->name);
 | 
			
		||||
                CleanupDocker::run(server: $this->server, force: true);
 | 
			
		||||
                CleanupDocker::run(server: $this->server);
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) {
 | 
			
		||||
                CleanupDocker::run(server: $this->server, force: false);
 | 
			
		||||
            if ($this->usageBefore >= $this->server->settings->docker_cleanup_threshold) {
 | 
			
		||||
                CleanupDocker::run(server: $this->server);
 | 
			
		||||
                $usageAfter = $this->server->getDiskUsage();
 | 
			
		||||
                if ($usageAfter < $this->usageBefore) {
 | 
			
		||||
                    $this->server->team?->notify(new DockerCleanup($this->server, 'Saved '.($this->usageBefore - $usageAfter).'% disk space.'));
 | 
			
		||||
@@ -56,7 +69,8 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
                Log::info('No need to clean up '.$this->server->name);
 | 
			
		||||
            }
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            ray($e->getMessage());
 | 
			
		||||
            CleanupDocker::run(server: $this->server);
 | 
			
		||||
            Log::error('DockerCleanupJob failed: '.$e->getMessage());
 | 
			
		||||
            throw $e;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,28 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Jobs;
 | 
			
		||||
 | 
			
		||||
use App\Actions\Server\UpdateCoolify;
 | 
			
		||||
use Illuminate\Bus\Queueable;
 | 
			
		||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 | 
			
		||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
 | 
			
		||||
use Illuminate\Contracts\Queue\ShouldQueue;
 | 
			
		||||
use Illuminate\Foundation\Bus\Dispatchable;
 | 
			
		||||
use Illuminate\Queue\InteractsWithQueue;
 | 
			
		||||
use Illuminate\Queue\SerializesModels;
 | 
			
		||||
 | 
			
		||||
class InstanceAutoUpdateJob implements ShouldBeEncrypted, ShouldBeUnique, ShouldQueue
 | 
			
		||||
{
 | 
			
		||||
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 | 
			
		||||
 | 
			
		||||
    public $timeout = 600;
 | 
			
		||||
 | 
			
		||||
    public $tries = 1;
 | 
			
		||||
 | 
			
		||||
    public function __construct() {}
 | 
			
		||||
 | 
			
		||||
    public function handle(): void
 | 
			
		||||
    {
 | 
			
		||||
        UpdateCoolify::run();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,50 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Jobs;
 | 
			
		||||
 | 
			
		||||
use App\Models\InstanceSettings;
 | 
			
		||||
use App\Models\Server;
 | 
			
		||||
use Illuminate\Bus\Queueable;
 | 
			
		||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 | 
			
		||||
use Illuminate\Contracts\Queue\ShouldQueue;
 | 
			
		||||
use Illuminate\Foundation\Bus\Dispatchable;
 | 
			
		||||
use Illuminate\Queue\InteractsWithQueue;
 | 
			
		||||
use Illuminate\Queue\SerializesModels;
 | 
			
		||||
use Illuminate\Support\Facades\File;
 | 
			
		||||
use Illuminate\Support\Facades\Http;
 | 
			
		||||
 | 
			
		||||
class PullCoolifyImageJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
{
 | 
			
		||||
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 | 
			
		||||
 | 
			
		||||
    public function handle(): void
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            if (isDev() || isCloud()) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            $settings = InstanceSettings::get();
 | 
			
		||||
            $server = Server::findOrFail(0);
 | 
			
		||||
            $response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
 | 
			
		||||
            if ($response->successful()) {
 | 
			
		||||
                $versions = $response->json();
 | 
			
		||||
                File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT));
 | 
			
		||||
            }
 | 
			
		||||
            $latest_version = get_latest_version_of_coolify();
 | 
			
		||||
            instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$latest_version}"], $server, false);
 | 
			
		||||
 | 
			
		||||
            $current_version = config('version');
 | 
			
		||||
            if (! $settings->is_auto_update_enabled) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if ($latest_version === $current_version) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if (version_compare($latest_version, $current_version, '<')) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            throw $e;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
 | 
			
		||||
namespace App\Jobs;
 | 
			
		||||
 | 
			
		||||
use App\Models\InstanceSettings;
 | 
			
		||||
use App\Models\Server;
 | 
			
		||||
use Illuminate\Bus\Queueable;
 | 
			
		||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 | 
			
		||||
@@ -10,6 +11,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
 | 
			
		||||
use Illuminate\Queue\InteractsWithQueue;
 | 
			
		||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
 | 
			
		||||
use Illuminate\Queue\SerializesModels;
 | 
			
		||||
use Illuminate\Support\Facades\Http;
 | 
			
		||||
 | 
			
		||||
class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
{
 | 
			
		||||
@@ -32,10 +34,20 @@ class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
    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::get();
 | 
			
		||||
                $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');
 | 
			
		||||
            ray("Pulling {$helperImage}");
 | 
			
		||||
            instant_remote_process(["docker pull -q {$helperImage}"], $this->server, false);
 | 
			
		||||
            ray('PullHelperImageJob done');
 | 
			
		||||
                    instant_remote_process(["docker pull -q {$helperImage}:{$latest_version}"], $this->server);
 | 
			
		||||
                    $settings->update(['helper_version' => $latest_version]);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            send_internal_notification('PullHelperImageJob failed with: '.$e->getMessage());
 | 
			
		||||
            ray($e->getMessage());
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,8 @@ class ScheduledTaskJob implements ShouldQueue
 | 
			
		||||
 | 
			
		||||
    public array $containers = [];
 | 
			
		||||
 | 
			
		||||
    public string $server_timezone;
 | 
			
		||||
 | 
			
		||||
    public function __construct($task)
 | 
			
		||||
    {
 | 
			
		||||
        $this->task = $task;
 | 
			
		||||
@@ -47,6 +49,19 @@ class ScheduledTaskJob implements ShouldQueue
 | 
			
		||||
            throw new \RuntimeException('ScheduledTaskJob failed: No resource found.');
 | 
			
		||||
        }
 | 
			
		||||
        $this->team = Team::find($task->team_id);
 | 
			
		||||
        $this->server_timezone = $this->getServerTimezone();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getServerTimezone(): string
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->resource instanceof Application) {
 | 
			
		||||
            $timezone = $this->resource->destination->server->settings->server_timezone;
 | 
			
		||||
            return $timezone;
 | 
			
		||||
        } elseif ($this->resource instanceof Service) {
 | 
			
		||||
            $timezone = $this->resource->server->settings->server_timezone;
 | 
			
		||||
            return $timezone;
 | 
			
		||||
        }
 | 
			
		||||
        return 'UTC';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function middleware(): array
 | 
			
		||||
@@ -61,6 +76,7 @@ class ScheduledTaskJob implements ShouldQueue
 | 
			
		||||
 | 
			
		||||
    public function handle(): void
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            $this->task_log = ScheduledTaskExecution::create([
 | 
			
		||||
                'scheduled_task_id' => $this->task->id,
 | 
			
		||||
@@ -78,12 +94,12 @@ class ScheduledTaskJob implements ShouldQueue
 | 
			
		||||
            } elseif ($this->resource->type() == 'service') {
 | 
			
		||||
                $this->resource->applications()->get()->each(function ($application) {
 | 
			
		||||
                    if (str(data_get($application, 'status'))->contains('running')) {
 | 
			
		||||
                        $this->containers[] = data_get($application, 'name').'-'.data_get($this->resource, 'uuid');
 | 
			
		||||
                        $this->containers[] = data_get($application, 'name') . '-' . data_get($this->resource, 'uuid');
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                $this->resource->databases()->get()->each(function ($database) {
 | 
			
		||||
                    if (str(data_get($database, 'status'))->contains('running')) {
 | 
			
		||||
                        $this->containers[] = data_get($database, 'name').'-'.data_get($this->resource, 'uuid');
 | 
			
		||||
                        $this->containers[] = data_get($database, 'name') . '-' . data_get($this->resource, 'uuid');
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
@@ -96,8 +112,8 @@ class ScheduledTaskJob implements ShouldQueue
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            foreach ($this->containers as $containerName) {
 | 
			
		||||
                if (count($this->containers) == 1 || str_starts_with($containerName, $this->task->container.'-'.$this->resource->uuid)) {
 | 
			
		||||
                    $cmd = "sh -c '".str_replace("'", "'\''", $this->task->command)."'";
 | 
			
		||||
                if (count($this->containers) == 1 || str_starts_with($containerName, $this->task->container . '-' . $this->resource->uuid)) {
 | 
			
		||||
                    $cmd = "sh -c '" . str_replace("'", "'\''", $this->task->command) . "'";
 | 
			
		||||
                    $exec = "docker exec {$containerName} {$cmd}";
 | 
			
		||||
                    $this->task_output = instant_remote_process([$exec], $this->server, true);
 | 
			
		||||
                    $this->task_log->update([
 | 
			
		||||
@@ -121,6 +137,7 @@ class ScheduledTaskJob implements ShouldQueue
 | 
			
		||||
            $this->team?->notify(new TaskFailed($this->task, $e->getMessage()));
 | 
			
		||||
            // send_internal_notification('ScheduledTaskJob failed with: ' . $e->getMessage());
 | 
			
		||||
            throw $e;
 | 
			
		||||
        } finally {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,8 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
 | 
			
		||||
    public $tries = 3;
 | 
			
		||||
 | 
			
		||||
    public $timeout = 60;
 | 
			
		||||
 | 
			
		||||
    public $containers;
 | 
			
		||||
 | 
			
		||||
    public $applications;
 | 
			
		||||
@@ -43,15 +45,15 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
 | 
			
		||||
    public function __construct(public Server $server) {}
 | 
			
		||||
 | 
			
		||||
    // public function middleware(): array
 | 
			
		||||
    // {
 | 
			
		||||
    //     return [(new WithoutOverlapping($this->server->uuid))];
 | 
			
		||||
    // }
 | 
			
		||||
    public function middleware(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [(new WithoutOverlapping($this->server->uuid))];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // public function uniqueId(): int
 | 
			
		||||
    // {
 | 
			
		||||
    //     return $this->server->uuid;
 | 
			
		||||
    // }
 | 
			
		||||
    public function uniqueId(): int
 | 
			
		||||
    {
 | 
			
		||||
        return $this->server->uuid;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function handle()
 | 
			
		||||
    {
 | 
			
		||||
@@ -79,7 +81,6 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
                }
 | 
			
		||||
                GetContainersStatus::run($this->server, $this->containers, $containerReplicates);
 | 
			
		||||
                $this->checkLogDrainContainer();
 | 
			
		||||
                $this->checkSentinel();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
@@ -90,21 +91,6 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function checkSentinel()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->server->isSentinelEnabled()) {
 | 
			
		||||
            $sentinelContainerFound = $this->containers->filter(function ($value, $key) {
 | 
			
		||||
                return data_get($value, 'Name') === '/coolify-sentinel';
 | 
			
		||||
            })->first();
 | 
			
		||||
            if ($sentinelContainerFound) {
 | 
			
		||||
                $status = data_get($sentinelContainerFound, 'State.Status');
 | 
			
		||||
                if ($status !== 'running') {
 | 
			
		||||
                    PullSentinelImageJob::dispatch($this);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function serverStatus()
 | 
			
		||||
    {
 | 
			
		||||
        ['uptime' => $uptime] = $this->server->validateConnection();
 | 
			
		||||
@@ -140,6 +126,9 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
 | 
			
		||||
    private function checkLogDrainContainer()
 | 
			
		||||
    {
 | 
			
		||||
        if (! $this->server->isLogDrainEnabled()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        $foundLogDrainContainer = $this->containers->filter(function ($value, $key) {
 | 
			
		||||
            return data_get($value, 'Name') === '/coolify-log-drain';
 | 
			
		||||
        })->first();
 | 
			
		||||
 
 | 
			
		||||
@@ -73,6 +73,8 @@ class Index extends Component
 | 
			
		||||
        }
 | 
			
		||||
        $this->privateKeyName = generate_random_name();
 | 
			
		||||
        $this->remoteServerName = generate_random_name();
 | 
			
		||||
        $this->remoteServerPort = $this->remoteServerPort;
 | 
			
		||||
        $this->remoteServerUser = $this->remoteServerUser;
 | 
			
		||||
        if (isDev()) {
 | 
			
		||||
            $this->privateKey = '-----BEGIN OPENSSH PRIVATE KEY-----
 | 
			
		||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
 | 
			
		||||
@@ -154,6 +156,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
 | 
			
		||||
            $this->servers = Server::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
 | 
			
		||||
            if ($this->servers->count() > 0) {
 | 
			
		||||
                $this->selectedExistingServer = $this->servers->first()->id;
 | 
			
		||||
                $this->updateServerDetails();
 | 
			
		||||
                $this->currentState = 'select-existing-server';
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
@@ -173,9 +176,18 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
 | 
			
		||||
        }
 | 
			
		||||
        $this->selectedExistingPrivateKey = $this->createdServer->privateKey->id;
 | 
			
		||||
        $this->serverPublicKey = $this->createdServer->privateKey->publicKey();
 | 
			
		||||
        $this->updateServerDetails();
 | 
			
		||||
        $this->currentState = 'validate-server';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function updateServerDetails()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->createdServer) {
 | 
			
		||||
            $this->remoteServerPort = $this->createdServer->port;
 | 
			
		||||
            $this->remoteServerUser = $this->createdServer->user;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getProxyType()
 | 
			
		||||
    {
 | 
			
		||||
        // Set Default Proxy Type
 | 
			
		||||
@@ -235,11 +247,12 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
 | 
			
		||||
    public function saveServer()
 | 
			
		||||
    {
 | 
			
		||||
        $this->validate([
 | 
			
		||||
            'remoteServerName' => 'required',
 | 
			
		||||
            'remoteServerHost' => 'required',
 | 
			
		||||
            'remoteServerName' => 'required|string',
 | 
			
		||||
            'remoteServerHost' => 'required|string',
 | 
			
		||||
            'remoteServerPort' => 'required|integer',
 | 
			
		||||
            'remoteServerUser' => 'required',
 | 
			
		||||
            'remoteServerUser' => 'required|string',
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        $this->privateKey = formatPrivateKey($this->privateKey);
 | 
			
		||||
        $foundServer = Server::whereIp($this->remoteServerHost)->first();
 | 
			
		||||
        if ($foundServer) {
 | 
			
		||||
@@ -277,9 +290,12 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
 | 
			
		||||
            $this->createdServer->settings()->update([
 | 
			
		||||
                'is_reachable' => true,
 | 
			
		||||
            ]);
 | 
			
		||||
            $this->serverReachable = true;
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            $this->serverReachable = false;
 | 
			
		||||
            $this->createdServer->delete();
 | 
			
		||||
            $this->createdServer->settings()->update([
 | 
			
		||||
                'is_reachable' => false,
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            return handleError(error: $e, livewire: $this);
 | 
			
		||||
        }
 | 
			
		||||
@@ -296,6 +312,10 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
 | 
			
		||||
            ]);
 | 
			
		||||
            $this->getProxyType();
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            $this->createdServer->settings()->update([
 | 
			
		||||
                'is_usable' => false,
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            return handleError(error: $e, livewire: $this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -349,6 +369,21 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function saveAndValidateServer()
 | 
			
		||||
    {
 | 
			
		||||
        $this->validate([
 | 
			
		||||
            'remoteServerPort' => 'required|integer|min:1|max:65535',
 | 
			
		||||
            'remoteServerUser' => 'required|string',
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        $this->createdServer->update([
 | 
			
		||||
            'port' => $this->remoteServerPort,
 | 
			
		||||
            'user' => $this->remoteServerUser,
 | 
			
		||||
            'timezone' => 'UTC',
 | 
			
		||||
        ]);
 | 
			
		||||
        $this->validateServer();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function createNewPrivateKey()
 | 
			
		||||
    {
 | 
			
		||||
        $this->privateKeyName = generate_random_name();
 | 
			
		||||
 
 | 
			
		||||
@@ -50,15 +50,6 @@ class Dashboard extends Component
 | 
			
		||||
        ])->sortBy('id')->groupBy('server_name')->toArray();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // public function getIptables()
 | 
			
		||||
    // {
 | 
			
		||||
    //     $servers = Server::ownedByCurrentTeam()->get();
 | 
			
		||||
    //     foreach ($servers as $server) {
 | 
			
		||||
    //         checkRequiredCommands($server);
 | 
			
		||||
    //         $iptables = instant_remote_process(['docker run --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c "iptables -L -n | jc --iptables"'], $server);
 | 
			
		||||
    //         ray($iptables);
 | 
			
		||||
    //     }
 | 
			
		||||
    // }
 | 
			
		||||
    public function render()
 | 
			
		||||
    {
 | 
			
		||||
        return view('livewire.dashboard');
 | 
			
		||||
 
 | 
			
		||||
@@ -66,9 +66,9 @@ class Advanced extends Component
 | 
			
		||||
            $this->dispatch('resetDefaultLabels', false);
 | 
			
		||||
        }
 | 
			
		||||
        if ($this->application->settings->is_raw_compose_deployment_enabled) {
 | 
			
		||||
            $this->application->parseRawCompose();
 | 
			
		||||
            $this->application->oldRawParser();
 | 
			
		||||
        } else {
 | 
			
		||||
            $this->application->parseCompose();
 | 
			
		||||
            $this->application->parse();
 | 
			
		||||
        }
 | 
			
		||||
        $this->application->settings->save();
 | 
			
		||||
        $this->dispatch('success', 'Settings saved.');
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Application\Deployment;
 | 
			
		||||
 | 
			
		||||
use App\Models\Application;
 | 
			
		||||
use App\Models\ApplicationDeploymentQueue;
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
use Livewire\Component;
 | 
			
		||||
 | 
			
		||||
class Show extends Component
 | 
			
		||||
@@ -69,6 +70,20 @@ class Show extends Component
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getLogLinesProperty()
 | 
			
		||||
    {
 | 
			
		||||
        return decode_remote_command_output($this->application_deployment_queue)->map(function ($logLine) {
 | 
			
		||||
            $logLine['line'] = e($logLine['line']);
 | 
			
		||||
            $logLine['line'] = preg_replace(
 | 
			
		||||
                '/(https?:\/\/[^\s]+)/',
 | 
			
		||||
                '<a href="$1" target="_blank" rel="noopener noreferrer" class="underline text-neutral-400">$1</a>',
 | 
			
		||||
                $logLine['line'],
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            return $logLine;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function render()
 | 
			
		||||
    {
 | 
			
		||||
        return view('livewire.project.application.deployment.show');
 | 
			
		||||
 
 | 
			
		||||
@@ -131,7 +131,7 @@ class General extends Component
 | 
			
		||||
    public function mount()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $this->parsedServices = $this->application->parseCompose();
 | 
			
		||||
            $this->parsedServices = $this->application->parse();
 | 
			
		||||
            if (is_null($this->parsedServices) || empty($this->parsedServices)) {
 | 
			
		||||
                $this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.');
 | 
			
		||||
 | 
			
		||||
@@ -196,7 +196,7 @@ class General extends Component
 | 
			
		||||
 | 
			
		||||
            // Must reload the application to get the latest database changes
 | 
			
		||||
            // Why? Not sure, but it works.
 | 
			
		||||
            $this->application->refresh();
 | 
			
		||||
            // $this->application->refresh();
 | 
			
		||||
 | 
			
		||||
            ['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation] = $this->application->loadComposeFile($isInit);
 | 
			
		||||
            if (is_null($this->parsedServices)) {
 | 
			
		||||
@@ -204,7 +204,7 @@ class General extends Component
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            $compose = $this->application->parseCompose();
 | 
			
		||||
            $this->application->parse();
 | 
			
		||||
            $this->dispatch('success', 'Docker compose file loaded.');
 | 
			
		||||
            $this->dispatch('compose_loaded');
 | 
			
		||||
            $this->dispatch('refreshStorages');
 | 
			
		||||
 
 | 
			
		||||
@@ -79,8 +79,15 @@ class Previews extends Component
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        $fqdn = generateFqdn($this->application->destination->server, $this->application->uuid);
 | 
			
		||||
        if ($this->application->build_pack === 'dockercompose') {
 | 
			
		||||
            $preview->generate_preview_fqdn_compose();
 | 
			
		||||
            $this->application->refresh();
 | 
			
		||||
            $this->dispatch('success', 'Domain generated.');
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $fqdn = generateFqdn($this->application->destination->server, $this->application->uuid);
 | 
			
		||||
        $url = Url::fromString($fqdn);
 | 
			
		||||
        $template = $this->application->preview_url_template;
 | 
			
		||||
        $host = $url->getHost();
 | 
			
		||||
 
 | 
			
		||||
@@ -8,9 +8,8 @@ use Livewire\Component;
 | 
			
		||||
class BackupExecutions extends Component
 | 
			
		||||
{
 | 
			
		||||
    public ?ScheduledDatabaseBackup $backup = null;
 | 
			
		||||
 | 
			
		||||
    public $database;
 | 
			
		||||
    public $executions = [];
 | 
			
		||||
 | 
			
		||||
    public $setDeletableBackup;
 | 
			
		||||
 | 
			
		||||
    public function getListeners()
 | 
			
		||||
@@ -58,7 +57,53 @@ class BackupExecutions extends Component
 | 
			
		||||
    public function refreshBackupExecutions(): void
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->backup) {
 | 
			
		||||
            $this->executions = $this->backup->executions()->get()->sortBy('created_at');
 | 
			
		||||
            $this->executions = $this->backup->executions()->get();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function mount(ScheduledDatabaseBackup $backup)
 | 
			
		||||
    {
 | 
			
		||||
        $this->backup = $backup;
 | 
			
		||||
        $this->database = $backup->database;
 | 
			
		||||
        $this->refreshBackupExecutions();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function server()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->database) {
 | 
			
		||||
            $server = null;
 | 
			
		||||
 | 
			
		||||
            if ($this->database instanceof \App\Models\ServiceDatabase) {
 | 
			
		||||
                $server = $this->database->service->destination->server;
 | 
			
		||||
            } elseif ($this->database->destination && $this->database->destination->server) {
 | 
			
		||||
                $server = $this->database->destination->server;
 | 
			
		||||
            }
 | 
			
		||||
            if ($server) {
 | 
			
		||||
                return $server;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getServerTimezone()
 | 
			
		||||
    {
 | 
			
		||||
        $server = $this->server();
 | 
			
		||||
        if (!$server) {
 | 
			
		||||
            return 'UTC';
 | 
			
		||||
        }
 | 
			
		||||
        $serverTimezone = $server->settings->server_timezone;
 | 
			
		||||
        return $serverTimezone;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function formatDateInServerTimezone($date)
 | 
			
		||||
    {
 | 
			
		||||
        $serverTimezone = $this->getServerTimezone();
 | 
			
		||||
        $dateObj = new \DateTime($date);
 | 
			
		||||
        try {
 | 
			
		||||
            $dateObj->setTimezone(new \DateTimeZone($serverTimezone));
 | 
			
		||||
        } catch (\Exception $e) {
 | 
			
		||||
            $dateObj->setTimezone(new \DateTimeZone('UTC'));
 | 
			
		||||
        }
 | 
			
		||||
        return $dateObj->format('Y-m-d H:i:s T');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,7 @@ class General extends Component
 | 
			
		||||
        'database.is_public' => 'nullable|boolean',
 | 
			
		||||
        'database.public_port' => 'nullable|integer',
 | 
			
		||||
        'database.is_log_drain_enabled' => 'nullable|boolean',
 | 
			
		||||
        'database.custom_docker_run_options' => 'nullable',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    protected $validationAttributes = [
 | 
			
		||||
@@ -42,6 +43,7 @@ class General extends Component
 | 
			
		||||
        'database.ports_mappings' => 'Port Mapping',
 | 
			
		||||
        'database.is_public' => 'Is Public',
 | 
			
		||||
        'database.public_port' => 'Public Port',
 | 
			
		||||
        'database.custom_docker_run_options' => 'Custom Docker Run Options',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public function mount()
 | 
			
		||||
@@ -54,7 +56,7 @@ class General extends Component
 | 
			
		||||
    public function instantSaveAdvanced()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            if (! $this->server->isLogDrainEnabled()) {
 | 
			
		||||
            if (!$this->server->isLogDrainEnabled()) {
 | 
			
		||||
                $this->database->is_log_drain_enabled = false;
 | 
			
		||||
                $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
 | 
			
		||||
 | 
			
		||||
@@ -71,14 +73,14 @@ class General extends Component
 | 
			
		||||
    public function instantSave()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            if ($this->database->is_public && ! $this->database->public_port) {
 | 
			
		||||
            if ($this->database->is_public && !$this->database->public_port) {
 | 
			
		||||
                $this->dispatch('error', 'Public port is required.');
 | 
			
		||||
                $this->database->is_public = false;
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if ($this->database->is_public) {
 | 
			
		||||
                if (! str($this->database->status)->startsWith('running')) {
 | 
			
		||||
                if (!str($this->database->status)->startsWith('running')) {
 | 
			
		||||
                    $this->dispatch('error', 'Database must be started to be publicly accessible.');
 | 
			
		||||
                    $this->database->is_public = false;
 | 
			
		||||
 | 
			
		||||
@@ -93,7 +95,7 @@ class General extends Component
 | 
			
		||||
            $this->db_url_public = $this->database->external_db_url;
 | 
			
		||||
            $this->database->save();
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            $this->database->is_public = ! $this->database->is_public;
 | 
			
		||||
            $this->database->is_public = !$this->database->is_public;
 | 
			
		||||
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -30,6 +30,7 @@ class General extends Component
 | 
			
		||||
        'database.is_public' => 'nullable|boolean',
 | 
			
		||||
        'database.public_port' => 'nullable|integer',
 | 
			
		||||
        'database.is_log_drain_enabled' => 'nullable|boolean',
 | 
			
		||||
        'database.custom_docker_run_options' => 'nullable',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    protected $validationAttributes = [
 | 
			
		||||
@@ -40,6 +41,7 @@ class General extends Component
 | 
			
		||||
        'database.ports_mappings' => 'Port Mapping',
 | 
			
		||||
        'database.is_public' => 'Is Public',
 | 
			
		||||
        'database.public_port' => 'Public Port',
 | 
			
		||||
        'database.custom_docker_run_options' => 'Custom Docker Run Options',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public function mount()
 | 
			
		||||
@@ -52,7 +54,7 @@ class General extends Component
 | 
			
		||||
    public function instantSaveAdvanced()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            if (! $this->server->isLogDrainEnabled()) {
 | 
			
		||||
            if (!$this->server->isLogDrainEnabled()) {
 | 
			
		||||
                $this->database->is_log_drain_enabled = false;
 | 
			
		||||
                $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
 | 
			
		||||
 | 
			
		||||
@@ -86,14 +88,14 @@ class General extends Component
 | 
			
		||||
    public function instantSave()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            if ($this->database->is_public && ! $this->database->public_port) {
 | 
			
		||||
            if ($this->database->is_public && !$this->database->public_port) {
 | 
			
		||||
                $this->dispatch('error', 'Public port is required.');
 | 
			
		||||
                $this->database->is_public = false;
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if ($this->database->is_public) {
 | 
			
		||||
                if (! str($this->database->status)->startsWith('running')) {
 | 
			
		||||
                if (!str($this->database->status)->startsWith('running')) {
 | 
			
		||||
                    $this->dispatch('error', 'Database must be started to be publicly accessible.');
 | 
			
		||||
                    $this->database->is_public = false;
 | 
			
		||||
 | 
			
		||||
@@ -108,7 +110,7 @@ class General extends Component
 | 
			
		||||
            $this->db_url_public = $this->database->external_db_url;
 | 
			
		||||
            $this->database->save();
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            $this->database->is_public = ! $this->database->is_public;
 | 
			
		||||
            $this->database->is_public = !$this->database->is_public;
 | 
			
		||||
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,7 @@ class General extends Component
 | 
			
		||||
        'database.is_public' => 'nullable|boolean',
 | 
			
		||||
        'database.public_port' => 'nullable|integer',
 | 
			
		||||
        'database.is_log_drain_enabled' => 'nullable|boolean',
 | 
			
		||||
        'database.custom_docker_run_options' => 'nullable',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    protected $validationAttributes = [
 | 
			
		||||
@@ -42,6 +43,7 @@ class General extends Component
 | 
			
		||||
        'database.ports_mappings' => 'Port Mapping',
 | 
			
		||||
        'database.is_public' => 'Is Public',
 | 
			
		||||
        'database.public_port' => 'Public Port',
 | 
			
		||||
        'database.custom_docker_run_options' => 'Custom Docker Run Options',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public function mount()
 | 
			
		||||
@@ -55,7 +57,7 @@ class General extends Component
 | 
			
		||||
    public function instantSaveAdvanced()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            if (! $this->server->isLogDrainEnabled()) {
 | 
			
		||||
            if (!$this->server->isLogDrainEnabled()) {
 | 
			
		||||
                $this->database->is_log_drain_enabled = false;
 | 
			
		||||
                $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
 | 
			
		||||
 | 
			
		||||
@@ -92,14 +94,14 @@ class General extends Component
 | 
			
		||||
    public function instantSave()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            if ($this->database->is_public && ! $this->database->public_port) {
 | 
			
		||||
            if ($this->database->is_public && !$this->database->public_port) {
 | 
			
		||||
                $this->dispatch('error', 'Public port is required.');
 | 
			
		||||
                $this->database->is_public = false;
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if ($this->database->is_public) {
 | 
			
		||||
                if (! str($this->database->status)->startsWith('running')) {
 | 
			
		||||
                if (!str($this->database->status)->startsWith('running')) {
 | 
			
		||||
                    $this->dispatch('error', 'Database must be started to be publicly accessible.');
 | 
			
		||||
                    $this->database->is_public = false;
 | 
			
		||||
 | 
			
		||||
@@ -114,7 +116,7 @@ class General extends Component
 | 
			
		||||
            $this->db_url_public = $this->database->external_db_url;
 | 
			
		||||
            $this->database->save();
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            $this->database->is_public = ! $this->database->is_public;
 | 
			
		||||
            $this->database->is_public = !$this->database->is_public;
 | 
			
		||||
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,7 @@ class General extends Component
 | 
			
		||||
        'database.is_public' => 'nullable|boolean',
 | 
			
		||||
        'database.public_port' => 'nullable|integer',
 | 
			
		||||
        'database.is_log_drain_enabled' => 'nullable|boolean',
 | 
			
		||||
        'database.custom_docker_run_options' => 'nullable',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    protected $validationAttributes = [
 | 
			
		||||
@@ -48,6 +49,7 @@ class General extends Component
 | 
			
		||||
        'database.ports_mappings' => 'Port Mapping',
 | 
			
		||||
        'database.is_public' => 'Is Public',
 | 
			
		||||
        'database.public_port' => 'Public Port',
 | 
			
		||||
        'database.custom_docker_run_options' => 'Custom Docker Options',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public function mount()
 | 
			
		||||
@@ -61,7 +63,7 @@ class General extends Component
 | 
			
		||||
    public function instantSaveAdvanced()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            if (! $this->server->isLogDrainEnabled()) {
 | 
			
		||||
            if (!$this->server->isLogDrainEnabled()) {
 | 
			
		||||
                $this->database->is_log_drain_enabled = false;
 | 
			
		||||
                $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
 | 
			
		||||
 | 
			
		||||
@@ -98,14 +100,14 @@ class General extends Component
 | 
			
		||||
    public function instantSave()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            if ($this->database->is_public && ! $this->database->public_port) {
 | 
			
		||||
            if ($this->database->is_public && !$this->database->public_port) {
 | 
			
		||||
                $this->dispatch('error', 'Public port is required.');
 | 
			
		||||
                $this->database->is_public = false;
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if ($this->database->is_public) {
 | 
			
		||||
                if (! str($this->database->status)->startsWith('running')) {
 | 
			
		||||
                if (!str($this->database->status)->startsWith('running')) {
 | 
			
		||||
                    $this->dispatch('error', 'Database must be started to be publicly accessible.');
 | 
			
		||||
                    $this->database->is_public = false;
 | 
			
		||||
 | 
			
		||||
@@ -120,7 +122,7 @@ class General extends Component
 | 
			
		||||
            $this->db_url_public = $this->database->external_db_url;
 | 
			
		||||
            $this->database->save();
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            $this->database->is_public = ! $this->database->is_public;
 | 
			
		||||
            $this->database->is_public = !$this->database->is_public;
 | 
			
		||||
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,7 @@ class General extends Component
 | 
			
		||||
        'database.is_public' => 'nullable|boolean',
 | 
			
		||||
        'database.public_port' => 'nullable|integer',
 | 
			
		||||
        'database.is_log_drain_enabled' => 'nullable|boolean',
 | 
			
		||||
        'database.custom_docker_run_options' => 'nullable',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    protected $validationAttributes = [
 | 
			
		||||
@@ -46,6 +47,7 @@ class General extends Component
 | 
			
		||||
        'database.ports_mappings' => 'Port Mapping',
 | 
			
		||||
        'database.is_public' => 'Is Public',
 | 
			
		||||
        'database.public_port' => 'Public Port',
 | 
			
		||||
        'database.custom_docker_run_options' => 'Custom Docker Run Options',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public function mount()
 | 
			
		||||
@@ -59,7 +61,7 @@ class General extends Component
 | 
			
		||||
    public function instantSaveAdvanced()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            if (! $this->server->isLogDrainEnabled()) {
 | 
			
		||||
            if (!$this->server->isLogDrainEnabled()) {
 | 
			
		||||
                $this->database->is_log_drain_enabled = false;
 | 
			
		||||
                $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
 | 
			
		||||
 | 
			
		||||
@@ -99,14 +101,14 @@ class General extends Component
 | 
			
		||||
    public function instantSave()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            if ($this->database->is_public && ! $this->database->public_port) {
 | 
			
		||||
            if ($this->database->is_public && !$this->database->public_port) {
 | 
			
		||||
                $this->dispatch('error', 'Public port is required.');
 | 
			
		||||
                $this->database->is_public = false;
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if ($this->database->is_public) {
 | 
			
		||||
                if (! str($this->database->status)->startsWith('running')) {
 | 
			
		||||
                if (!str($this->database->status)->startsWith('running')) {
 | 
			
		||||
                    $this->dispatch('error', 'Database must be started to be publicly accessible.');
 | 
			
		||||
                    $this->database->is_public = false;
 | 
			
		||||
 | 
			
		||||
@@ -121,7 +123,7 @@ class General extends Component
 | 
			
		||||
            $this->db_url_public = $this->database->external_db_url;
 | 
			
		||||
            $this->database->save();
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            $this->database->is_public = ! $this->database->is_public;
 | 
			
		||||
            $this->database->is_public = !$this->database->is_public;
 | 
			
		||||
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,7 @@ class General extends Component
 | 
			
		||||
        'database.is_public' => 'nullable|boolean',
 | 
			
		||||
        'database.public_port' => 'nullable|integer',
 | 
			
		||||
        'database.is_log_drain_enabled' => 'nullable|boolean',
 | 
			
		||||
        'database.custom_docker_run_options' => 'nullable',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    protected $validationAttributes = [
 | 
			
		||||
@@ -48,6 +49,7 @@ class General extends Component
 | 
			
		||||
        'database.ports_mappings' => 'Port Mapping',
 | 
			
		||||
        'database.is_public' => 'Is Public',
 | 
			
		||||
        'database.public_port' => 'Public Port',
 | 
			
		||||
        'database.custom_docker_run_options' => 'Custom Docker Run Options',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public function mount()
 | 
			
		||||
@@ -60,7 +62,7 @@ class General extends Component
 | 
			
		||||
    public function instantSaveAdvanced()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            if (! $this->server->isLogDrainEnabled()) {
 | 
			
		||||
            if (!$this->server->isLogDrainEnabled()) {
 | 
			
		||||
                $this->database->is_log_drain_enabled = false;
 | 
			
		||||
                $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
 | 
			
		||||
 | 
			
		||||
@@ -97,14 +99,14 @@ class General extends Component
 | 
			
		||||
    public function instantSave()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            if ($this->database->is_public && ! $this->database->public_port) {
 | 
			
		||||
            if ($this->database->is_public && !$this->database->public_port) {
 | 
			
		||||
                $this->dispatch('error', 'Public port is required.');
 | 
			
		||||
                $this->database->is_public = false;
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if ($this->database->is_public) {
 | 
			
		||||
                if (! str($this->database->status)->startsWith('running')) {
 | 
			
		||||
                if (!str($this->database->status)->startsWith('running')) {
 | 
			
		||||
                    $this->dispatch('error', 'Database must be started to be publicly accessible.');
 | 
			
		||||
                    $this->database->is_public = false;
 | 
			
		||||
 | 
			
		||||
@@ -119,7 +121,7 @@ class General extends Component
 | 
			
		||||
            $this->db_url_public = $this->database->external_db_url;
 | 
			
		||||
            $this->database->save();
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            $this->database->is_public = ! $this->database->is_public;
 | 
			
		||||
            $this->database->is_public = !$this->database->is_public;
 | 
			
		||||
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -49,6 +49,7 @@ class General extends Component
 | 
			
		||||
        'database.is_public' => 'nullable|boolean',
 | 
			
		||||
        'database.public_port' => 'nullable|integer',
 | 
			
		||||
        'database.is_log_drain_enabled' => 'nullable|boolean',
 | 
			
		||||
        'database.custom_docker_run_options' => 'nullable',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    protected $validationAttributes = [
 | 
			
		||||
@@ -65,6 +66,7 @@ class General extends Component
 | 
			
		||||
        'database.ports_mappings' => 'Port Mapping',
 | 
			
		||||
        'database.is_public' => 'Is Public',
 | 
			
		||||
        'database.public_port' => 'Public Port',
 | 
			
		||||
        'database.custom_docker_run_options' => 'Custom Docker Run Options',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public function mount()
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,7 @@ class General extends Component
 | 
			
		||||
        'database.is_public' => 'nullable|boolean',
 | 
			
		||||
        'database.public_port' => 'nullable|integer',
 | 
			
		||||
        'database.is_log_drain_enabled' => 'nullable|boolean',
 | 
			
		||||
        'database.custom_docker_run_options' => 'nullable',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    protected $validationAttributes = [
 | 
			
		||||
@@ -42,6 +43,7 @@ class General extends Component
 | 
			
		||||
        'database.ports_mappings' => 'Port Mapping',
 | 
			
		||||
        'database.is_public' => 'Is Public',
 | 
			
		||||
        'database.public_port' => 'Public Port',
 | 
			
		||||
        'database.custom_docker_run_options' => 'Custom Docker Options',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public function mount()
 | 
			
		||||
@@ -55,7 +57,7 @@ class General extends Component
 | 
			
		||||
    public function instantSaveAdvanced()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            if (! $this->server->isLogDrainEnabled()) {
 | 
			
		||||
            if (!$this->server->isLogDrainEnabled()) {
 | 
			
		||||
                $this->database->is_log_drain_enabled = false;
 | 
			
		||||
                $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
 | 
			
		||||
 | 
			
		||||
@@ -86,14 +88,14 @@ class General extends Component
 | 
			
		||||
    public function instantSave()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            if ($this->database->is_public && ! $this->database->public_port) {
 | 
			
		||||
            if ($this->database->is_public && !$this->database->public_port) {
 | 
			
		||||
                $this->dispatch('error', 'Public port is required.');
 | 
			
		||||
                $this->database->is_public = false;
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if ($this->database->is_public) {
 | 
			
		||||
                if (! str($this->database->status)->startsWith('running')) {
 | 
			
		||||
                if (!str($this->database->status)->startsWith('running')) {
 | 
			
		||||
                    $this->dispatch('error', 'Database must be started to be publicly accessible.');
 | 
			
		||||
                    $this->database->is_public = false;
 | 
			
		||||
 | 
			
		||||
@@ -108,7 +110,7 @@ class General extends Component
 | 
			
		||||
            $this->db_url_public = $this->database->external_db_url;
 | 
			
		||||
            $this->database->save();
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            $this->database->is_public = ! $this->database->is_public;
 | 
			
		||||
            $this->database->is_public = !$this->database->is_public;
 | 
			
		||||
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -99,6 +99,16 @@ class PublicGitRepository extends Component
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function updatedDockerComposeLocation()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->docker_compose_location) {
 | 
			
		||||
            $this->docker_compose_location = rtrim($this->docker_compose_location, '/');
 | 
			
		||||
            if (! str($this->docker_compose_location)->startsWith('/')) {
 | 
			
		||||
                $this->docker_compose_location = '/'.$this->docker_compose_location;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function updatedBuildPack()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->build_pack === 'nixpacks') {
 | 
			
		||||
 
 | 
			
		||||
@@ -45,6 +45,8 @@ class Select extends Component
 | 
			
		||||
 | 
			
		||||
    public ?string $selectedEnvironment = null;
 | 
			
		||||
 | 
			
		||||
    public string $postgresql_type = 'postgres:16-alpine';
 | 
			
		||||
 | 
			
		||||
    public ?string $existingPostgresqlUrl = null;
 | 
			
		||||
 | 
			
		||||
    public ?string $search = null;
 | 
			
		||||
@@ -202,6 +204,8 @@ class Select extends Component
 | 
			
		||||
            $docker = $this->standaloneDockers->first() ?? $this->swarmDockers->first();
 | 
			
		||||
            if ($docker) {
 | 
			
		||||
                $this->setDestination($docker->uuid);
 | 
			
		||||
 | 
			
		||||
                return $this->whatToDoNext();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $this->current_step = 'destinations';
 | 
			
		||||
@@ -211,6 +215,28 @@ class Select extends Component
 | 
			
		||||
    {
 | 
			
		||||
        $this->destination_uuid = $destination_uuid;
 | 
			
		||||
 | 
			
		||||
        return $this->whatToDoNext();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setPostgresqlType(string $type)
 | 
			
		||||
    {
 | 
			
		||||
        $this->postgresql_type = $type;
 | 
			
		||||
 | 
			
		||||
        return redirect()->route('project.resource.create', [
 | 
			
		||||
            'project_uuid' => $this->parameters['project_uuid'],
 | 
			
		||||
            'environment_name' => $this->parameters['environment_name'],
 | 
			
		||||
            'type' => $this->type,
 | 
			
		||||
            'destination' => $this->destination_uuid,
 | 
			
		||||
            'server_id' => $this->server_id,
 | 
			
		||||
            'database_image' => $this->postgresql_type,
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function whatToDoNext()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->type === 'postgresql') {
 | 
			
		||||
            $this->current_step = 'select-postgresql-type';
 | 
			
		||||
        } else {
 | 
			
		||||
            return redirect()->route('project.resource.create', [
 | 
			
		||||
                'project_uuid' => $this->parameters['project_uuid'],
 | 
			
		||||
                'environment_name' => $this->parameters['environment_name'],
 | 
			
		||||
@@ -219,6 +245,7 @@ class Select extends Component
 | 
			
		||||
                'server_id' => $this->server_id,
 | 
			
		||||
            ]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function loadServers()
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@ class Create extends Component
 | 
			
		||||
        $type = str(request()->query('type'));
 | 
			
		||||
        $destination_uuid = request()->query('destination');
 | 
			
		||||
        $server_id = request()->query('server_id');
 | 
			
		||||
        $database_image = request()->query('database_image');
 | 
			
		||||
 | 
			
		||||
        $project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
 | 
			
		||||
        if (! $project) {
 | 
			
		||||
@@ -33,7 +34,11 @@ class Create extends Component
 | 
			
		||||
 | 
			
		||||
            if (in_array($type, DATABASE_TYPES)) {
 | 
			
		||||
                if ($type->value() === 'postgresql') {
 | 
			
		||||
                    $database = create_standalone_postgresql($environment->id, $destination_uuid);
 | 
			
		||||
                    $database = create_standalone_postgresql(
 | 
			
		||||
                        environmentId: $environment->id,
 | 
			
		||||
                        destinationUuid: $destination_uuid,
 | 
			
		||||
                        databaseImage: $database_image
 | 
			
		||||
                    );
 | 
			
		||||
                } elseif ($type->value() === 'redis') {
 | 
			
		||||
                    $database = create_standalone_redis($environment->id, $destination_uuid);
 | 
			
		||||
                } elseif ($type->value() === 'mongodb') {
 | 
			
		||||
@@ -86,18 +91,16 @@ class Create extends Component
 | 
			
		||||
                        $oneClickDotEnvs->each(function ($value) use ($service) {
 | 
			
		||||
                            $key = str()->before($value, '=');
 | 
			
		||||
                            $value = str(str()->after($value, '='));
 | 
			
		||||
                            $generatedValue = $value;
 | 
			
		||||
                            if ($value->contains('SERVICE_')) {
 | 
			
		||||
                                $command = $value->after('SERVICE_')->beforeLast('_');
 | 
			
		||||
                                $generatedValue = generateEnvValue($command->value(), $service);
 | 
			
		||||
                            }
 | 
			
		||||
                            if ($value) {
 | 
			
		||||
                                EnvironmentVariable::create([
 | 
			
		||||
                                    'key' => $key,
 | 
			
		||||
                                'value' => $generatedValue,
 | 
			
		||||
                                    'value' => $value,
 | 
			
		||||
                                    'service_id' => $service->id,
 | 
			
		||||
                                    'is_build_time' => false,
 | 
			
		||||
                                    'is_preview' => false,
 | 
			
		||||
                                ]);
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                    $service->parse(isNew: true);
 | 
			
		||||
 
 | 
			
		||||
@@ -76,6 +76,12 @@ class Configuration extends Component
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            GetContainersStatus::run($this->service->server);
 | 
			
		||||
            $this->service->applications->each(function ($application) {
 | 
			
		||||
                $application->refresh();
 | 
			
		||||
            });
 | 
			
		||||
            $this->service->databases->each(function ($database) {
 | 
			
		||||
                $database->refresh();
 | 
			
		||||
            });
 | 
			
		||||
            $this->dispatch('$refresh');
 | 
			
		||||
        } catch (\Exception $e) {
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,7 @@ class EditCompose extends Component
 | 
			
		||||
    {
 | 
			
		||||
        $this->dispatch('info', 'Saving new docker compose...');
 | 
			
		||||
        $this->dispatch('saveCompose', $this->service->docker_compose_raw);
 | 
			
		||||
        $this->dispatch('refreshStorages');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function instantSave()
 | 
			
		||||
 
 | 
			
		||||
@@ -55,6 +55,7 @@ class FileStorage extends Component
 | 
			
		||||
            $this->fileStorage->deleteStorageOnServer();
 | 
			
		||||
            $this->fileStorage->is_directory = true;
 | 
			
		||||
            $this->fileStorage->content = null;
 | 
			
		||||
            $this->fileStorage->is_based_on_git = false;
 | 
			
		||||
            $this->fileStorage->save();
 | 
			
		||||
            $this->fileStorage->saveStorageOnServer();
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,7 @@ class All extends Component
 | 
			
		||||
 | 
			
		||||
    protected $listeners = [
 | 
			
		||||
        'saveKey' => 'submit',
 | 
			
		||||
        'refreshEnvs',
 | 
			
		||||
        'environmentVariableDeleted' => 'refreshEnvs',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
@@ -52,11 +53,18 @@ class All extends Component
 | 
			
		||||
 | 
			
		||||
    public function sortEnvironmentVariables()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->resource->type() === 'application') {
 | 
			
		||||
            $this->resource->load(['environment_variables', 'environment_variables_preview']);
 | 
			
		||||
        } else {
 | 
			
		||||
            $this->resource->load(['environment_variables']);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $sortBy = data_get($this->resource, 'settings.is_env_sorting_enabled') ? 'key' : 'order';
 | 
			
		||||
 | 
			
		||||
        $sortFunction = function ($variables) use ($sortBy) {
 | 
			
		||||
            if (! $variables) {
 | 
			
		||||
                return $variables;
 | 
			
		||||
            }
 | 
			
		||||
            if ($sortBy === 'key') {
 | 
			
		||||
                return $variables->sortBy(function ($item) {
 | 
			
		||||
                    return strtolower($item->key);
 | 
			
		||||
 
 | 
			
		||||
@@ -97,7 +97,7 @@ class GetLogs extends Component
 | 
			
		||||
        if (! $refresh && ($this->resource?->getMorphClass() === 'App\Models\Service' || str($this->container)->contains('-pr-'))) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (! $this->numberOfLines) {
 | 
			
		||||
        if ($this->numberOfLines <= 0) {
 | 
			
		||||
            $this->numberOfLines = 1000;
 | 
			
		||||
        }
 | 
			
		||||
        if ($this->container) {
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@ class All extends Component
 | 
			
		||||
            $this->containerNames = $this->containerNames->merge($this->resource->databases()->pluck('name'));
 | 
			
		||||
        } elseif ($this->resource->type() == 'application') {
 | 
			
		||||
            if ($this->resource->build_pack === 'dockercompose') {
 | 
			
		||||
                $parsed = $this->resource->parseCompose();
 | 
			
		||||
                $parsed = $this->resource->parse();
 | 
			
		||||
                $containers = collect(data_get($parsed, 'services'))->keys();
 | 
			
		||||
                $this->containerNames = $containers;
 | 
			
		||||
            } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -7,8 +7,8 @@ use Livewire\Component;
 | 
			
		||||
class Executions extends Component
 | 
			
		||||
{
 | 
			
		||||
    public $executions = [];
 | 
			
		||||
 | 
			
		||||
    public $selectedKey;
 | 
			
		||||
    public $task;
 | 
			
		||||
 | 
			
		||||
    public function getListeners()
 | 
			
		||||
    {
 | 
			
		||||
@@ -26,4 +26,44 @@ class Executions extends Component
 | 
			
		||||
        }
 | 
			
		||||
        $this->selectedKey = $key;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function server()
 | 
			
		||||
    {
 | 
			
		||||
        if (!$this->task) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($this->task->application) {
 | 
			
		||||
            if ($this->task->application->destination && $this->task->application->destination->server) {
 | 
			
		||||
                return $this->task->application->destination->server;
 | 
			
		||||
            }
 | 
			
		||||
        } elseif ($this->task->service) {
 | 
			
		||||
            if ($this->task->service->destination && $this->task->service->destination->server) {
 | 
			
		||||
                return $this->task->service->destination->server;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getServerTimezone()
 | 
			
		||||
    {
 | 
			
		||||
        $server = $this->server();
 | 
			
		||||
        if (!$server) {
 | 
			
		||||
            return 'UTC';
 | 
			
		||||
        }
 | 
			
		||||
        $serverTimezone = $server->settings->server_timezone;
 | 
			
		||||
        return $serverTimezone;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function formatDateInServerTimezone($date)
 | 
			
		||||
    {
 | 
			
		||||
        $serverTimezone = $this->getServerTimezone();
 | 
			
		||||
        $dateObj = new \DateTime($date);
 | 
			
		||||
        try {
 | 
			
		||||
            $dateObj->setTimezone(new \DateTimeZone($serverTimezone));
 | 
			
		||||
        } catch (\Exception $e) {
 | 
			
		||||
            $dateObj->setTimezone(new \DateTimeZone('UTC'));
 | 
			
		||||
        }
 | 
			
		||||
        return $dateObj->format('Y-m-d H:i:s T');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,12 +18,12 @@ class Form extends Component
 | 
			
		||||
 | 
			
		||||
    public ?string $wildcard_domain = null;
 | 
			
		||||
 | 
			
		||||
    public int $cleanup_after_percentage;
 | 
			
		||||
 | 
			
		||||
    public bool $dockerInstallationStarted = false;
 | 
			
		||||
 | 
			
		||||
    public bool $revalidate = false;
 | 
			
		||||
 | 
			
		||||
    public $timezones;
 | 
			
		||||
 | 
			
		||||
    protected $listeners = ['serverInstalled', 'revalidate' => '$refresh'];
 | 
			
		||||
 | 
			
		||||
    protected $rules = [
 | 
			
		||||
@@ -37,7 +37,6 @@ class Form extends Component
 | 
			
		||||
        'server.settings.is_swarm_manager' => 'required|boolean',
 | 
			
		||||
        'server.settings.is_swarm_worker' => 'required|boolean',
 | 
			
		||||
        'server.settings.is_build_server' => 'required|boolean',
 | 
			
		||||
        'server.settings.is_force_cleanup_enabled' => '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',
 | 
			
		||||
@@ -46,6 +45,10 @@ class Form extends Component
 | 
			
		||||
        'server.settings.metrics_history_days' => 'required|integer|min:1',
 | 
			
		||||
        'wildcard_domain' => 'nullable|url',
 | 
			
		||||
        'server.settings.is_server_api_enabled' => 'required|boolean',
 | 
			
		||||
        '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',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    protected $validationAttributes = [
 | 
			
		||||
@@ -66,12 +69,27 @@ class Form extends Component
 | 
			
		||||
        'server.settings.metrics_refresh_rate_seconds' => 'Metrics Interval',
 | 
			
		||||
        'server.settings.metrics_history_days' => 'Metrics History',
 | 
			
		||||
        'server.settings.is_server_api_enabled' => 'Server API',
 | 
			
		||||
        'server.settings.server_timezone' => 'Server Timezone',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public function mount()
 | 
			
		||||
    public function mount(Server $server)
 | 
			
		||||
    {
 | 
			
		||||
        $this->server = $server;
 | 
			
		||||
        $this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray();
 | 
			
		||||
        $this->wildcard_domain = $this->server->settings->wildcard_domain;
 | 
			
		||||
        $this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
 | 
			
		||||
        $this->server->settings->docker_cleanup_threshold = $this->server->settings->docker_cleanup_threshold;
 | 
			
		||||
        $this->server->settings->docker_cleanup_frequency = $this->server->settings->docker_cleanup_frequency;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function updated($field)
 | 
			
		||||
    {
 | 
			
		||||
        if ($field === 'server.settings.docker_cleanup_frequency') {
 | 
			
		||||
            $frequency = $this->server->settings->docker_cleanup_frequency;
 | 
			
		||||
            if (empty($frequency) || ! validate_cron_expression($frequency)) {
 | 
			
		||||
                $this->dispatch('error', 'Invalid Cron / Human expression for Docker Cleanup Frequency. Resetting to default 10 minutes.');
 | 
			
		||||
                $this->server->settings->docker_cleanup_frequency = '*/10 * * * *';
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function serverInstalled()
 | 
			
		||||
@@ -116,7 +134,6 @@ class Form extends Component
 | 
			
		||||
                }
 | 
			
		||||
                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');
 | 
			
		||||
@@ -172,6 +189,7 @@ class Form extends Component
 | 
			
		||||
 | 
			
		||||
    public function submit()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            if (isCloud() && ! isDev()) {
 | 
			
		||||
                $this->validate();
 | 
			
		||||
                $this->validate([
 | 
			
		||||
@@ -190,9 +208,30 @@ class Form extends Component
 | 
			
		||||
            }
 | 
			
		||||
            refresh_server_connection($this->server->privateKey);
 | 
			
		||||
            $this->server->settings->wildcard_domain = $this->wildcard_domain;
 | 
			
		||||
        $this->server->settings->cleanup_after_percentage = $this->cleanup_after_percentage;
 | 
			
		||||
            if ($this->server->settings->force_docker_cleanup) {
 | 
			
		||||
                $this->server->settings->docker_cleanup_frequency = $this->server->settings->docker_cleanup_frequency;
 | 
			
		||||
            } else {
 | 
			
		||||
                $this->server->settings->docker_cleanup_threshold = $this->server->settings->docker_cleanup_threshold;
 | 
			
		||||
            }
 | 
			
		||||
            $currentTimezone = $this->server->settings->getOriginal('server_timezone');
 | 
			
		||||
            $newTimezone = $this->server->settings->server_timezone;
 | 
			
		||||
            if ($currentTimezone !== $newTimezone || $currentTimezone === '') {
 | 
			
		||||
                $this->server->settings->server_timezone = $newTimezone;
 | 
			
		||||
                $this->server->settings->save();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $this->server->settings->save();
 | 
			
		||||
            $this->server->save();
 | 
			
		||||
            $this->dispatch('success', 'Server updated.');
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function updatedServerSettingsServerTimezone($value)
 | 
			
		||||
    {
 | 
			
		||||
        $this->server->settings->server_timezone = $value;
 | 
			
		||||
        $this->server->settings->save();
 | 
			
		||||
        $this->dispatch('success', 'Server timezone updated.');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -40,6 +40,7 @@ class Index extends Component
 | 
			
		||||
        'settings.is_auto_update_enabled' => 'boolean',
 | 
			
		||||
        'auto_update_frequency' => 'string',
 | 
			
		||||
        'update_check_frequency' => 'string',
 | 
			
		||||
        'settings.instance_timezone' => 'required|string|timezone',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    protected $validationAttributes = [
 | 
			
		||||
@@ -54,6 +55,8 @@ class Index extends Component
 | 
			
		||||
        'update_check_frequency' => 'Update Check Frequency',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public $timezones;
 | 
			
		||||
 | 
			
		||||
    public function mount()
 | 
			
		||||
    {
 | 
			
		||||
        if (isInstanceAdmin()) {
 | 
			
		||||
@@ -65,6 +68,7 @@ class Index extends Component
 | 
			
		||||
            $this->is_api_enabled = $this->settings->is_api_enabled;
 | 
			
		||||
            $this->auto_update_frequency = $this->settings->auto_update_frequency;
 | 
			
		||||
            $this->update_check_frequency = $this->settings->update_check_frequency;
 | 
			
		||||
            $this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray();
 | 
			
		||||
        } else {
 | 
			
		||||
            return redirect()->route('dashboard');
 | 
			
		||||
        }
 | 
			
		||||
@@ -166,6 +170,13 @@ 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()
 | 
			
		||||
    {
 | 
			
		||||
        return view('livewire.settings.index');
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@ namespace App\Livewire;
 | 
			
		||||
 | 
			
		||||
use App\Actions\Server\UpdateCoolify;
 | 
			
		||||
use App\Models\InstanceSettings;
 | 
			
		||||
use Illuminate\Support\Facades\Http;
 | 
			
		||||
use Livewire\Component;
 | 
			
		||||
 | 
			
		||||
class Upgrade extends Component
 | 
			
		||||
@@ -22,13 +21,8 @@ class Upgrade extends Component
 | 
			
		||||
    public function checkUpdate()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $settings = InstanceSettings::get();
 | 
			
		||||
            $response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
 | 
			
		||||
            if ($response->successful()) {
 | 
			
		||||
                $versions = $response->json();
 | 
			
		||||
                $this->latestVersion = data_get($versions, 'coolify.v4.version');
 | 
			
		||||
            }
 | 
			
		||||
            $this->isUpgradeAvailable = $settings->new_version_available;
 | 
			
		||||
            $this->latestVersion = get_latest_version_of_coolify();
 | 
			
		||||
            $this->isUpgradeAvailable = data_get(InstanceSettings::get(), 'new_version_available', false);
 | 
			
		||||
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
 
 | 
			
		||||
@@ -102,6 +102,8 @@ class Application extends BaseModel
 | 
			
		||||
{
 | 
			
		||||
    use SoftDeletes;
 | 
			
		||||
 | 
			
		||||
    private static $parserVersion = '3';
 | 
			
		||||
 | 
			
		||||
    protected $guarded = [];
 | 
			
		||||
 | 
			
		||||
    protected $appends = ['server_status'];
 | 
			
		||||
@@ -125,7 +127,7 @@ class Application extends BaseModel
 | 
			
		||||
            ApplicationSetting::create([
 | 
			
		||||
                'application_id' => $application->id,
 | 
			
		||||
            ]);
 | 
			
		||||
            $application->compose_parsing_version = '2';
 | 
			
		||||
            $application->compose_parsing_version = self::$parserVersion;
 | 
			
		||||
            $application->save();
 | 
			
		||||
        });
 | 
			
		||||
        static::forceDeleting(function ($application) {
 | 
			
		||||
@@ -138,6 +140,7 @@ class Application extends BaseModel
 | 
			
		||||
                $task->delete();
 | 
			
		||||
            }
 | 
			
		||||
            $application->tags()->detach();
 | 
			
		||||
            $application->previews()->delete();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -412,23 +415,6 @@ class Application extends BaseModel
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function dockerComposePrLocation(): Attribute
 | 
			
		||||
    {
 | 
			
		||||
        return Attribute::make(
 | 
			
		||||
            set: function ($value) {
 | 
			
		||||
                if (is_null($value) || $value === '') {
 | 
			
		||||
                    return '/docker-compose.yaml';
 | 
			
		||||
                } else {
 | 
			
		||||
                    if ($value !== '/') {
 | 
			
		||||
                        return Str::start(Str::replaceEnd('/', '', $value), '/');
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return Str::start($value, '/');
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function baseDirectory(): Attribute
 | 
			
		||||
    {
 | 
			
		||||
        return Attribute::make(
 | 
			
		||||
@@ -1040,7 +1026,7 @@ class Application extends BaseModel
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function parseRawCompose()
 | 
			
		||||
    public function oldRawParser()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $yaml = Yaml::parse($this->docker_compose_raw);
 | 
			
		||||
@@ -1100,9 +1086,11 @@ class Application extends BaseModel
 | 
			
		||||
        instant_remote_process($commands, $this->destination->server, false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function parseCompose(int $pull_request_id = 0, ?int $preview_id = null)
 | 
			
		||||
    public function parse(int $pull_request_id = 0, ?int $preview_id = null)
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->docker_compose_raw) {
 | 
			
		||||
        if ($this->compose_parsing_version === '3') {
 | 
			
		||||
            return newParser($this, $pull_request_id, $preview_id);
 | 
			
		||||
        } elseif ($this->docker_compose_raw) {
 | 
			
		||||
            return parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, preview_id: $preview_id);
 | 
			
		||||
        } else {
 | 
			
		||||
            return collect([]);
 | 
			
		||||
@@ -1154,7 +1142,7 @@ class Application extends BaseModel
 | 
			
		||||
        if ($composeFileContent) {
 | 
			
		||||
            $this->docker_compose_raw = $composeFileContent;
 | 
			
		||||
            $this->save();
 | 
			
		||||
            $parsedServices = $this->parseCompose();
 | 
			
		||||
            $parsedServices = $this->parse();
 | 
			
		||||
            if ($this->docker_compose_domains) {
 | 
			
		||||
                $json = collect(json_decode($this->docker_compose_domains));
 | 
			
		||||
                $names = collect(data_get($parsedServices, 'services'))->keys()->toArray();
 | 
			
		||||
 
 | 
			
		||||
@@ -12,9 +12,9 @@ class ApplicationPreview extends BaseModel
 | 
			
		||||
    protected static function booted()
 | 
			
		||||
    {
 | 
			
		||||
        static::deleting(function ($preview) {
 | 
			
		||||
            if ($preview->application->build_pack === 'dockercompose') {
 | 
			
		||||
            if (data_get($preview, 'application.build_pack') === 'dockercompose') {
 | 
			
		||||
                $server = $preview->application->destination->server;
 | 
			
		||||
                $composeFile = $preview->application->parseCompose(pull_request_id: $preview->pull_request_id);
 | 
			
		||||
                $composeFile = $preview->application->parse(pull_request_id: $preview->pull_request_id);
 | 
			
		||||
                $volumes = data_get($composeFile, 'volumes');
 | 
			
		||||
                $networks = data_get($composeFile, 'networks');
 | 
			
		||||
                $networkKeys = collect($networks)->keys();
 | 
			
		||||
 
 | 
			
		||||
@@ -37,6 +37,30 @@ class InstanceSettings extends Model implements SendsEmail
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function updateCheckFrequency(): Attribute
 | 
			
		||||
    {
 | 
			
		||||
        return Attribute::make(
 | 
			
		||||
            set: function ($value) {
 | 
			
		||||
                return translate_cron_expression($value);
 | 
			
		||||
            },
 | 
			
		||||
            get: function ($value) {
 | 
			
		||||
                return translate_cron_expression($value);
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function autoUpdateFrequency(): Attribute
 | 
			
		||||
    {
 | 
			
		||||
        return Attribute::make(
 | 
			
		||||
            set: function ($value) {
 | 
			
		||||
                return translate_cron_expression($value);
 | 
			
		||||
            },
 | 
			
		||||
            get: function ($value) {
 | 
			
		||||
                return translate_cron_expression($value);
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function get()
 | 
			
		||||
    {
 | 
			
		||||
        return InstanceSettings::findOrFail(0);
 | 
			
		||||
 
 | 
			
		||||
@@ -52,6 +52,7 @@ class LocalFileVolume extends BaseModel
 | 
			
		||||
 | 
			
		||||
    public function deleteStorageOnServer()
 | 
			
		||||
    {
 | 
			
		||||
        $this->load(['service']);
 | 
			
		||||
        $isService = data_get($this->resource, 'service');
 | 
			
		||||
        if ($isService) {
 | 
			
		||||
            $workdir = $this->resource->service->workdir();
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ use OpenApi\Attributes as OA;
 | 
			
		||||
        'id' => ['type' => 'integer'],
 | 
			
		||||
        'uuid' => ['type' => 'string'],
 | 
			
		||||
        'name' => ['type' => 'string'],
 | 
			
		||||
        'description' => ['type' => 'string'],
 | 
			
		||||
        'environments' => new OA\Property(
 | 
			
		||||
            property: 'environments',
 | 
			
		||||
            type: 'array',
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,8 @@ class ScheduledDatabaseBackup extends BaseModel
 | 
			
		||||
 | 
			
		||||
    public function executions(): HasMany
 | 
			
		||||
    {
 | 
			
		||||
        return $this->hasMany(ScheduledDatabaseBackupExecution::class);
 | 
			
		||||
        // Last execution first
 | 
			
		||||
        return $this->hasMany(ScheduledDatabaseBackupExecution::class)->orderBy('created_at', 'desc');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function s3()
 | 
			
		||||
@@ -34,4 +35,14 @@ class ScheduledDatabaseBackup extends BaseModel
 | 
			
		||||
    {
 | 
			
		||||
        return $this->hasMany(ScheduledDatabaseBackupExecution::class)->where('created_at', '>=', now()->subDays($days))->get();
 | 
			
		||||
    }
 | 
			
		||||
    public function server()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->database) {
 | 
			
		||||
            if ($this->database->destination && $this->database->destination->server) {
 | 
			
		||||
                $server = $this->database->destination->server;
 | 
			
		||||
                return $server;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,8 @@ namespace App\Models;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Database\Eloquent\Relations\HasMany;
 | 
			
		||||
use Illuminate\Database\Eloquent\Relations\HasOne;
 | 
			
		||||
use App\Models\Service;
 | 
			
		||||
use App\Models\Application;
 | 
			
		||||
 | 
			
		||||
class ScheduledTask extends BaseModel
 | 
			
		||||
{
 | 
			
		||||
@@ -26,6 +28,28 @@ class ScheduledTask extends BaseModel
 | 
			
		||||
 | 
			
		||||
    public function executions(): HasMany
 | 
			
		||||
    {
 | 
			
		||||
        return $this->hasMany(ScheduledTaskExecution::class);
 | 
			
		||||
        // Last execution first
 | 
			
		||||
        return $this->hasMany(ScheduledTaskExecution::class)->orderBy('created_at', 'desc');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function server()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->application) {
 | 
			
		||||
            if ($this->application->destination && $this->application->destination->server) {
 | 
			
		||||
                $server = $this->application->destination->server;
 | 
			
		||||
                return $server;
 | 
			
		||||
            }
 | 
			
		||||
        } elseif ($this->service) {
 | 
			
		||||
            if ($this->service->destination && $this->service->destination->server) {
 | 
			
		||||
                $server = $this->service->destination->server;
 | 
			
		||||
                return $server;
 | 
			
		||||
            }
 | 
			
		||||
        } elseif ($this->database) {
 | 
			
		||||
            if ($this->database->destination && $this->database->destination->server) {
 | 
			
		||||
                $server = $this->database->destination->server;
 | 
			
		||||
                return $server;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -112,6 +112,16 @@ class Server extends BaseModel
 | 
			
		||||
        'proxy',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    protected $fillable = [
 | 
			
		||||
        'name',
 | 
			
		||||
        'ip',
 | 
			
		||||
        'port',
 | 
			
		||||
        'user',
 | 
			
		||||
        'description',
 | 
			
		||||
        'private_key_id',
 | 
			
		||||
        'team_id',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    protected $guarded = [];
 | 
			
		||||
 | 
			
		||||
    public static function isReachable()
 | 
			
		||||
@@ -678,7 +688,7 @@ $schema://$host {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getDiskUsage()
 | 
			
		||||
    public function getDiskUsage(): ?string
 | 
			
		||||
    {
 | 
			
		||||
        return instant_remote_process(["df /| tail -1 | awk '{ print $5}' | sed 's/%//g'"], $this, false);
 | 
			
		||||
    }
 | 
			
		||||
@@ -909,7 +919,7 @@ $schema://$host {
 | 
			
		||||
 | 
			
		||||
    public function muxFilename()
 | 
			
		||||
    {
 | 
			
		||||
        return "{$this->ip}_{$this->port}_{$this->user}";
 | 
			
		||||
        return $this->uuid;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function team()
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
 | 
			
		||||
namespace App\Models;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Database\Eloquent\Casts\Attribute;
 | 
			
		||||
use Illuminate\Database\Eloquent\Model;
 | 
			
		||||
use OpenApi\Attributes as OA;
 | 
			
		||||
 | 
			
		||||
@@ -10,10 +11,10 @@ use OpenApi\Attributes as OA;
 | 
			
		||||
    type: 'object',
 | 
			
		||||
    properties: [
 | 
			
		||||
        'id' => ['type' => 'integer'],
 | 
			
		||||
        'cleanup_after_percentage' => ['type' => 'integer'],
 | 
			
		||||
        'concurrent_builds' => ['type' => 'integer'],
 | 
			
		||||
        'dynamic_timeout' => ['type' => 'integer'],
 | 
			
		||||
        'force_disabled' => ['type' => 'boolean'],
 | 
			
		||||
        'force_server_cleanup' => ['type' => 'boolean'],
 | 
			
		||||
        'is_build_server' => ['type' => 'boolean'],
 | 
			
		||||
        'is_cloudflare_tunnel' => ['type' => 'boolean'],
 | 
			
		||||
        'is_jump_server' => ['type' => 'boolean'],
 | 
			
		||||
@@ -37,6 +38,8 @@ use OpenApi\Attributes as OA;
 | 
			
		||||
        'metrics_history_days' => ['type' => 'integer'],
 | 
			
		||||
        'metrics_refresh_rate_seconds' => ['type' => 'integer'],
 | 
			
		||||
        'metrics_token' => ['type' => 'string'],
 | 
			
		||||
        'docker_cleanup_frequency' => ['type' => 'string'],
 | 
			
		||||
        'docker_cleanup_threshold' => ['type' => 'integer'],
 | 
			
		||||
        'server_id' => ['type' => 'integer'],
 | 
			
		||||
        'wildcard_domain' => ['type' => 'string'],
 | 
			
		||||
        'created_at' => ['type' => 'string'],
 | 
			
		||||
@@ -47,8 +50,25 @@ class ServerSetting extends Model
 | 
			
		||||
{
 | 
			
		||||
    protected $guarded = [];
 | 
			
		||||
 | 
			
		||||
    protected $casts = [
 | 
			
		||||
        'force_docker_cleanup' => 'boolean',
 | 
			
		||||
        'docker_cleanup_threshold' => 'integer',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public function server()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->belongsTo(Server::class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function dockerCleanupFrequency(): Attribute
 | 
			
		||||
    {
 | 
			
		||||
        return Attribute::make(
 | 
			
		||||
            set: function ($value) {
 | 
			
		||||
                return translate_cron_expression($value);
 | 
			
		||||
            },
 | 
			
		||||
            get: function ($value) {
 | 
			
		||||
                return translate_cron_expression($value);
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,9 +7,10 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
 | 
			
		||||
use Illuminate\Database\Eloquent\Relations\HasMany;
 | 
			
		||||
use Illuminate\Database\Eloquent\SoftDeletes;
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
use Illuminate\Support\Facades\Storage;
 | 
			
		||||
use OpenApi\Attributes as OA;
 | 
			
		||||
use Spatie\Url\Url;
 | 
			
		||||
use Symfony\Component\Yaml\Yaml;
 | 
			
		||||
use Visus\Cuid2\Cuid2;
 | 
			
		||||
 | 
			
		||||
#[OA\Schema(
 | 
			
		||||
    description: 'Service model',
 | 
			
		||||
@@ -23,7 +24,7 @@ use Symfony\Component\Yaml\Yaml;
 | 
			
		||||
        'description' => ['type' => 'string', 'description' => 'The description of the service.'],
 | 
			
		||||
        'docker_compose_raw' => ['type' => 'string', 'description' => 'The raw docker-compose.yml file of the service.'],
 | 
			
		||||
        'docker_compose' => ['type' => 'string', 'description' => 'The docker-compose.yml file that is parsed and modified by Coolify.'],
 | 
			
		||||
        'destination_type' => ['type' => 'integer', 'description' => 'The unique identifier of the destination where the service is running.'],
 | 
			
		||||
        'destination_type' => ['type' => 'string', 'description' => 'Destination type.'],
 | 
			
		||||
        'destination_id' => ['type' => 'integer', 'description' => 'The unique identifier of the destination where the service is running.'],
 | 
			
		||||
        'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
 | 
			
		||||
        'is_container_label_escape_enabled' => ['type' => 'boolean', 'description' => 'The flag to enable the container label escape.'],
 | 
			
		||||
@@ -39,10 +40,20 @@ class Service extends BaseModel
 | 
			
		||||
{
 | 
			
		||||
    use HasFactory, SoftDeletes;
 | 
			
		||||
 | 
			
		||||
    private static $parserVersion = '3';
 | 
			
		||||
 | 
			
		||||
    protected $guarded = [];
 | 
			
		||||
 | 
			
		||||
    protected $appends = ['server_status'];
 | 
			
		||||
 | 
			
		||||
    protected static function booted()
 | 
			
		||||
    {
 | 
			
		||||
        static::created(function ($service) {
 | 
			
		||||
            $service->compose_parsing_version = self::$parserVersion;
 | 
			
		||||
            $service->save();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function isConfigurationChanged(bool $save = false)
 | 
			
		||||
    {
 | 
			
		||||
        $domains = $this->applications()->get()->pluck('fqdn')->sort()->toArray();
 | 
			
		||||
@@ -665,6 +676,32 @@ class Service extends BaseModel
 | 
			
		||||
 | 
			
		||||
                    $fields->put('GitLab', $data->toArray());
 | 
			
		||||
                    break;
 | 
			
		||||
                case str($image)->contains('code-server'):
 | 
			
		||||
                    $data = collect([]);
 | 
			
		||||
                    $password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_64_PASSWORDCODESERVER')->first();
 | 
			
		||||
                    if ($password) {
 | 
			
		||||
                        $data = $data->merge([
 | 
			
		||||
                            'Password' => [
 | 
			
		||||
                                'key' => data_get($password, 'key'),
 | 
			
		||||
                                'value' => data_get($password, 'value'),
 | 
			
		||||
                                'rules' => 'required',
 | 
			
		||||
                                'isPassword' => true,
 | 
			
		||||
                            ],
 | 
			
		||||
                        ]);
 | 
			
		||||
                    }
 | 
			
		||||
                    $sudoPassword = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_SUDOCODESERVER')->first();
 | 
			
		||||
                    if ($sudoPassword) {
 | 
			
		||||
                        $data = $data->merge([
 | 
			
		||||
                            'Sudo Password' => [
 | 
			
		||||
                                'key' => data_get($sudoPassword, 'key'),
 | 
			
		||||
                                'value' => data_get($sudoPassword, 'value'),
 | 
			
		||||
                                'rules' => 'required',
 | 
			
		||||
                                'isPassword' => true,
 | 
			
		||||
                            ],
 | 
			
		||||
                        ]);
 | 
			
		||||
                    }
 | 
			
		||||
                    $fields->put('Code Server', $data->toArray());
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $databases = $this->databases()->get();
 | 
			
		||||
@@ -711,8 +748,8 @@ class Service extends BaseModel
 | 
			
		||||
                    $fields->put('PostgreSQL', $data->toArray());
 | 
			
		||||
                    break;
 | 
			
		||||
                case str($image)->contains('mysql'):
 | 
			
		||||
                    $userVariables = ['SERVICE_USER_MYSQL', 'SERVICE_USER_WORDPRESS'];
 | 
			
		||||
                    $passwordVariables = ['SERVICE_PASSWORD_MYSQL', 'SERVICE_PASSWORD_WORDPRESS'];
 | 
			
		||||
                    $userVariables = ['SERVICE_USER_MYSQL', 'SERVICE_USER_WORDPRESS', 'MYSQL_USER'];
 | 
			
		||||
                    $passwordVariables = ['SERVICE_PASSWORD_MYSQL', 'SERVICE_PASSWORD_WORDPRESS', 'MYSQL_PASSWORD'];
 | 
			
		||||
                    $rootPasswordVariables = ['SERVICE_PASSWORD_MYSQLROOT', 'SERVICE_PASSWORD_ROOT'];
 | 
			
		||||
                    $dbNameVariables = ['MYSQL_DATABASE'];
 | 
			
		||||
                    $mysql_user = $this->environment_variables()->whereIn('key', $userVariables)->first();
 | 
			
		||||
@@ -761,10 +798,10 @@ class Service extends BaseModel
 | 
			
		||||
                    $fields->put('MySQL', $data->toArray());
 | 
			
		||||
                    break;
 | 
			
		||||
                case str($image)->contains('mariadb'):
 | 
			
		||||
                    $userVariables = ['SERVICE_USER_MARIADB', 'SERVICE_USER_WORDPRESS', '_APP_DB_USER'];
 | 
			
		||||
                    $passwordVariables = ['SERVICE_PASSWORD_MARIADB', 'SERVICE_PASSWORD_WORDPRESS', '_APP_DB_PASS'];
 | 
			
		||||
                    $rootPasswordVariables = ['SERVICE_PASSWORD_MARIADBROOT', 'SERVICE_PASSWORD_ROOT', '_APP_DB_ROOT_PASS'];
 | 
			
		||||
                    $dbNameVariables = ['SERVICE_DATABASE_MARIADB', 'SERVICE_DATABASE_WORDPRESS', '_APP_DB_SCHEMA'];
 | 
			
		||||
                    $userVariables = ['SERVICE_USER_MARIADB', 'SERVICE_USER_WORDPRESS', '_APP_DB_USER', 'SERVICE_USER_MYSQL', 'MYSQL_USER'];
 | 
			
		||||
                    $passwordVariables = ['SERVICE_PASSWORD_MARIADB', 'SERVICE_PASSWORD_WORDPRESS', '_APP_DB_PASS', 'MYSQL_PASSWORD'];
 | 
			
		||||
                    $rootPasswordVariables = ['SERVICE_PASSWORD_MARIADBROOT', 'SERVICE_PASSWORD_ROOT', '_APP_DB_ROOT_PASS', 'MYSQL_ROOT_PASSWORD'];
 | 
			
		||||
                    $dbNameVariables = ['SERVICE_DATABASE_MARIADB', 'SERVICE_DATABASE_WORDPRESS', '_APP_DB_SCHEMA', 'MYSQL_DATABASE'];
 | 
			
		||||
                    $mariadb_user = $this->environment_variables()->whereIn('key', $userVariables)->first();
 | 
			
		||||
                    $mariadb_password = $this->environment_variables()->whereIn('key', $passwordVariables)->first();
 | 
			
		||||
                    $mariadb_root_password = $this->environment_variables()->whereIn('key', $rootPasswordVariables)->first();
 | 
			
		||||
@@ -811,6 +848,7 @@ class Service extends BaseModel
 | 
			
		||||
                    }
 | 
			
		||||
                    $fields->put('MariaDB', $data->toArray());
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -945,7 +983,8 @@ class Service extends BaseModel
 | 
			
		||||
 | 
			
		||||
    public function environment_variables(): HasMany
 | 
			
		||||
    {
 | 
			
		||||
        return $this->hasMany(EnvironmentVariable::class)->orderBy('key', 'asc');
 | 
			
		||||
 | 
			
		||||
        return $this->hasMany(EnvironmentVariable::class)->orderByRaw("key LIKE 'SERVICE%' DESC, value ASC");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function environment_variables_preview(): HasMany
 | 
			
		||||
@@ -961,21 +1000,36 @@ class Service extends BaseModel
 | 
			
		||||
    public function saveComposeConfigs()
 | 
			
		||||
    {
 | 
			
		||||
        $workdir = $this->workdir();
 | 
			
		||||
        $commands[] = "mkdir -p $workdir";
 | 
			
		||||
 | 
			
		||||
        instant_remote_process([
 | 
			
		||||
            "mkdir -p $workdir",
 | 
			
		||||
            "cd $workdir",
 | 
			
		||||
        ], $this->server);
 | 
			
		||||
 | 
			
		||||
        $filename = new Cuid2.'-docker-compose.yml';
 | 
			
		||||
        Storage::disk('local')->put("tmp/{$filename}", $this->docker_compose);
 | 
			
		||||
        $path = Storage::path("tmp/{$filename}");
 | 
			
		||||
        instant_scp($path, "{$workdir}/docker-compose.yml", $this->server);
 | 
			
		||||
        Storage::disk('local')->delete("tmp/{$filename}");
 | 
			
		||||
 | 
			
		||||
        $commands[] = "cd $workdir";
 | 
			
		||||
 | 
			
		||||
        $json = Yaml::parse($this->docker_compose);
 | 
			
		||||
        $this->docker_compose = Yaml::dump($json, 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
 | 
			
		||||
        $docker_compose_base64 = base64_encode($this->docker_compose);
 | 
			
		||||
 | 
			
		||||
        $commands[] = "echo $docker_compose_base64 | base64 -d | tee docker-compose.yml > /dev/null";
 | 
			
		||||
        $commands[] = 'rm -f .env || true';
 | 
			
		||||
 | 
			
		||||
        $envs_from_coolify = $this->environment_variables()->get();
 | 
			
		||||
        foreach ($envs_from_coolify as $env) {
 | 
			
		||||
        $sorted = $envs_from_coolify->sortBy(function ($env) {
 | 
			
		||||
            if (str($env->key)->startsWith('SERVICE_')) {
 | 
			
		||||
                return 1;
 | 
			
		||||
            }
 | 
			
		||||
            if (str($env->value)->startsWith('$SERVICE_') || str($env->value)->startsWith('${SERVICE_')) {
 | 
			
		||||
                return 2;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return 3;
 | 
			
		||||
        });
 | 
			
		||||
        foreach ($sorted as $env) {
 | 
			
		||||
            $commands[] = "echo '{$env->key}={$env->real_value}' >> .env";
 | 
			
		||||
        }
 | 
			
		||||
        if ($envs_from_coolify->count() === 0) {
 | 
			
		||||
        if ($sorted->count() === 0) {
 | 
			
		||||
            $commands[] = 'touch .env';
 | 
			
		||||
        }
 | 
			
		||||
        instant_remote_process($commands, $this->server);
 | 
			
		||||
@@ -983,7 +1037,14 @@ class Service extends BaseModel
 | 
			
		||||
 | 
			
		||||
    public function parse(bool $isNew = false): Collection
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->compose_parsing_version === '3') {
 | 
			
		||||
            return newParser($this);
 | 
			
		||||
        } elseif ($this->docker_compose_raw) {
 | 
			
		||||
            return parseDockerComposeFile($this, $isNew);
 | 
			
		||||
        } else {
 | 
			
		||||
            return collect([]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function networks()
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,7 @@ class DockerCleanup extends Notification implements ShouldQueue
 | 
			
		||||
    //     $mail->view('emails.high-disk-usage', [
 | 
			
		||||
    //         'name' => $this->server->name,
 | 
			
		||||
    //         'disk_usage' => $this->disk_usage,
 | 
			
		||||
    //         'threshold' => $this->cleanup_after_percentage,
 | 
			
		||||
    //         'threshold' => $this->docker_cleanup_threshold,
 | 
			
		||||
    //     ]);
 | 
			
		||||
    //     return $mail;
 | 
			
		||||
    // }
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ class HighDiskUsage extends Notification implements ShouldQueue
 | 
			
		||||
 | 
			
		||||
    public $tries = 1;
 | 
			
		||||
 | 
			
		||||
    public function __construct(public Server $server, public int $disk_usage, public int $cleanup_after_percentage) {}
 | 
			
		||||
    public function __construct(public Server $server, public int $disk_usage, public int $docker_cleanup_threshold) {}
 | 
			
		||||
 | 
			
		||||
    public function via(object $notifiable): array
 | 
			
		||||
    {
 | 
			
		||||
@@ -46,7 +46,7 @@ class HighDiskUsage extends Notification implements ShouldQueue
 | 
			
		||||
        $mail->view('emails.high-disk-usage', [
 | 
			
		||||
            'name' => $this->server->name,
 | 
			
		||||
            'disk_usage' => $this->disk_usage,
 | 
			
		||||
            'threshold' => $this->cleanup_after_percentage,
 | 
			
		||||
            'threshold' => $this->docker_cleanup_threshold,
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        return $mail;
 | 
			
		||||
@@ -54,7 +54,7 @@ class HighDiskUsage extends Notification implements ShouldQueue
 | 
			
		||||
 | 
			
		||||
    public function toDiscord(): string
 | 
			
		||||
    {
 | 
			
		||||
        $message = "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->cleanup_after_percentage}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.";
 | 
			
		||||
        $message = "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->docker_cleanup_threshold}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.";
 | 
			
		||||
 | 
			
		||||
        return $message;
 | 
			
		||||
    }
 | 
			
		||||
@@ -62,7 +62,7 @@ class HighDiskUsage extends Notification implements ShouldQueue
 | 
			
		||||
    public function toTelegram(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
            'message' => "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->cleanup_after_percentage}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.",
 | 
			
		||||
            'message' => "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->docker_cleanup_threshold}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.",
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										67
									
								
								app/Providers/TelescopeServiceProvider.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								app/Providers/TelescopeServiceProvider.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Providers;
 | 
			
		||||
 | 
			
		||||
use App\Models\User;
 | 
			
		||||
use Illuminate\Support\Facades\Gate;
 | 
			
		||||
use Laravel\Telescope\IncomingEntry;
 | 
			
		||||
use Laravel\Telescope\Telescope;
 | 
			
		||||
use Laravel\Telescope\TelescopeApplicationServiceProvider;
 | 
			
		||||
 | 
			
		||||
class TelescopeServiceProvider extends TelescopeApplicationServiceProvider
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Register any application services.
 | 
			
		||||
     */
 | 
			
		||||
    public function register(): void
 | 
			
		||||
    {
 | 
			
		||||
        // Telescope::night();
 | 
			
		||||
 | 
			
		||||
        $this->hideSensitiveRequestDetails();
 | 
			
		||||
 | 
			
		||||
        $isLocal = $this->app->environment('local');
 | 
			
		||||
 | 
			
		||||
        Telescope::filter(function (IncomingEntry $entry) use ($isLocal) {
 | 
			
		||||
            return $isLocal ||
 | 
			
		||||
                   $entry->isReportableException() ||
 | 
			
		||||
                   $entry->isFailedRequest() ||
 | 
			
		||||
                   $entry->isFailedJob() ||
 | 
			
		||||
                   $entry->isScheduledTask() ||
 | 
			
		||||
                   $entry->hasMonitoredTag();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Prevent sensitive request details from being logged by Telescope.
 | 
			
		||||
     */
 | 
			
		||||
    protected function hideSensitiveRequestDetails(): void
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->app->environment('local')) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Telescope::hideRequestParameters(['_token']);
 | 
			
		||||
 | 
			
		||||
        Telescope::hideRequestHeaders([
 | 
			
		||||
            'cookie',
 | 
			
		||||
            'x-csrf-token',
 | 
			
		||||
            'x-xsrf-token',
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register the Telescope gate.
 | 
			
		||||
     *
 | 
			
		||||
     * This gate determines who can access Telescope in non-local environments.
 | 
			
		||||
     */
 | 
			
		||||
    protected function gate(): void
 | 
			
		||||
    {
 | 
			
		||||
        Gate::define('viewTelescope', function ($user) {
 | 
			
		||||
            $root_user = User::find(0);
 | 
			
		||||
 | 
			
		||||
            return in_array($user->email, [
 | 
			
		||||
                $root_user->email,
 | 
			
		||||
            ]);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -91,7 +91,7 @@ function next_queuable(string $server_id, string $application_id): bool
 | 
			
		||||
    $server = Server::find($server_id);
 | 
			
		||||
    $concurrent_builds = $server->settings->concurrent_builds;
 | 
			
		||||
 | 
			
		||||
    ray("serverId:{$server->id}", "concurrentBuilds:{$concurrent_builds}", "deployments:{$deployments->count()}", "sameApplicationDeployments:{$same_application_deployments->count()}");
 | 
			
		||||
    ray("serverId:{$server->id}", "concurrentBuilds:{$concurrent_builds}", "deployments:{$deployments->count()}", "sameApplicationDeployments:{$same_application_deployments->count()}")->green();
 | 
			
		||||
 | 
			
		||||
    if ($deployments->count() > $concurrent_builds) {
 | 
			
		||||
        return false;
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,7 @@ const SUPPORTED_OS = [
 | 
			
		||||
    'centos fedora rhel ol rocky amzn almalinux',
 | 
			
		||||
    'sles opensuse-leap opensuse-tumbleweed',
 | 
			
		||||
    'arch',
 | 
			
		||||
    'alpine',
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const SHARED_VARIABLE_TYPES = ['team', 'project', 'environment'];
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@ function generate_database_name(string $type): string
 | 
			
		||||
    return $type.'-database-'.$cuid;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function create_standalone_postgresql($environmentId, $destinationUuid, ?array $otherData = null): StandalonePostgresql
 | 
			
		||||
function create_standalone_postgresql($environmentId, $destinationUuid, ?array $otherData = null, string $databaseImage = 'postgres:16-alpine'): StandalonePostgresql
 | 
			
		||||
{
 | 
			
		||||
    $destination = StandaloneDocker::where('uuid', $destinationUuid)->first();
 | 
			
		||||
    if (! $destination) {
 | 
			
		||||
@@ -27,6 +27,7 @@ function create_standalone_postgresql($environmentId, $destinationUuid, ?array $
 | 
			
		||||
    }
 | 
			
		||||
    $database = new StandalonePostgresql;
 | 
			
		||||
    $database->name = generate_database_name('postgresql');
 | 
			
		||||
    $database->image = $databaseImage;
 | 
			
		||||
    $database->postgres_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
 | 
			
		||||
    $database->environment_id = $environmentId;
 | 
			
		||||
    $database->destination_id = $destination->id;
 | 
			
		||||
 
 | 
			
		||||
@@ -140,6 +140,8 @@ function getContainerStatus(Server $server, string $container_id, bool $all_data
 | 
			
		||||
 | 
			
		||||
function generateApplicationContainerName(Application $application, $pull_request_id = 0)
 | 
			
		||||
{
 | 
			
		||||
    // TODO: refactor generateApplicationContainerName, we do not need $application and $pull_request_id
 | 
			
		||||
 | 
			
		||||
    $consistent_container_name = $application->settings->is_consistent_container_name_enabled;
 | 
			
		||||
    $now = now()->format('Hisu');
 | 
			
		||||
    if ($pull_request_id !== 0 && $pull_request_id !== null) {
 | 
			
		||||
@@ -251,7 +253,7 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
 | 
			
		||||
 | 
			
		||||
    return $payload;
 | 
			
		||||
}
 | 
			
		||||
function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, ?string $image = null, string $redirect_direction = 'both')
 | 
			
		||||
function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, ?string $image = null, string $redirect_direction = 'both', ?string $predefinedPort = null)
 | 
			
		||||
{
 | 
			
		||||
    $labels = collect([]);
 | 
			
		||||
    if ($serviceLabels) {
 | 
			
		||||
@@ -270,6 +272,9 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
 | 
			
		||||
        if (is_null($port) && ! is_null($onlyPort)) {
 | 
			
		||||
            $port = $onlyPort;
 | 
			
		||||
        }
 | 
			
		||||
        if (is_null($port) && $predefinedPort) {
 | 
			
		||||
            $port = $predefinedPort;
 | 
			
		||||
        }
 | 
			
		||||
        $labels->push("caddy_{$loop}={$schema}://{$host}");
 | 
			
		||||
        $labels->push("caddy_{$loop}.header=-Server");
 | 
			
		||||
        $labels->push("caddy_{$loop}.try_files={path} /index.html /index.php");
 | 
			
		||||
@@ -677,18 +682,19 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
 | 
			
		||||
        '--sysctl',
 | 
			
		||||
        '--ulimit',
 | 
			
		||||
        '--device',
 | 
			
		||||
        '--shm-size',
 | 
			
		||||
    ]);
 | 
			
		||||
    $mapping = collect([
 | 
			
		||||
        '--cap-add' => 'cap_add',
 | 
			
		||||
        '--cap-drop' => 'cap_drop',
 | 
			
		||||
        '--security-opt' => 'security_opt',
 | 
			
		||||
        '--sysctl' => 'sysctls',
 | 
			
		||||
        '--ulimit' => 'ulimits',
 | 
			
		||||
        '--device' => 'devices',
 | 
			
		||||
        '--init' => 'init',
 | 
			
		||||
        '--ulimit' => 'ulimits',
 | 
			
		||||
        '--privileged' => 'privileged',
 | 
			
		||||
        '--ip' => 'ip',
 | 
			
		||||
        '--shm-size' => 'shm_size',
 | 
			
		||||
    ]);
 | 
			
		||||
    foreach ($matches as $match) {
 | 
			
		||||
        $option = $match[1];
 | 
			
		||||
@@ -704,6 +710,7 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
 | 
			
		||||
    $options = collect($options);
 | 
			
		||||
    // Easily get mappings from https://github.com/composerize/composerize/blob/master/packages/composerize/src/mappings.js
 | 
			
		||||
    foreach ($options as $option => $value) {
 | 
			
		||||
        // ray($option,$value);
 | 
			
		||||
        if (! data_get($mapping, $option)) {
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
@@ -728,6 +735,10 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            $compose_options->put($mapping[$option], $ulimits);
 | 
			
		||||
        } elseif ($option === '--shm-size') {
 | 
			
		||||
            if (! is_null($value) && is_array($value) && count($value) > 0) {
 | 
			
		||||
                $compose_options->put($mapping[$option], $value[0]);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            if ($list_options->contains($option)) {
 | 
			
		||||
                if ($compose_options->has($mapping[$option])) {
 | 
			
		||||
@@ -749,6 +760,26 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
 | 
			
		||||
    return $compose_options->toArray();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $network)
 | 
			
		||||
{
 | 
			
		||||
    $ipv4 = data_get($docker_run_options, 'ip.0');
 | 
			
		||||
    $ipv6 = data_get($docker_run_options, 'ip6.0');
 | 
			
		||||
    data_forget($docker_run_options, 'ip');
 | 
			
		||||
    data_forget($docker_run_options, 'ip6');
 | 
			
		||||
    if ($ipv4 || $ipv6) {
 | 
			
		||||
        data_forget($docker_compose['services'][$container_name], 'networks');
 | 
			
		||||
    }
 | 
			
		||||
    if ($ipv4) {
 | 
			
		||||
        $docker_compose['services'][$container_name]['networks'][$network]['ipv4_address'] = $ipv4;
 | 
			
		||||
    }
 | 
			
		||||
    if ($ipv6) {
 | 
			
		||||
        $docker_compose['services'][$container_name]['networks'][$network]['ipv6_address'] = $ipv6;
 | 
			
		||||
    }
 | 
			
		||||
    $docker_compose['services'][$container_name] = array_merge_recursive($docker_compose['services'][$container_name], $docker_run_options);
 | 
			
		||||
 | 
			
		||||
    return $docker_compose;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function validateComposeFile(string $compose, int $server_id): string|Throwable
 | 
			
		||||
{
 | 
			
		||||
    return 'OK';
 | 
			
		||||
 
 | 
			
		||||
@@ -146,12 +146,11 @@ function generate_default_proxy_configuration(Server $server)
 | 
			
		||||
            'coolify.managed=true',
 | 
			
		||||
        ];
 | 
			
		||||
        $config = [
 | 
			
		||||
            'version' => '3.8',
 | 
			
		||||
            'networks' => $array_of_networks->toArray(),
 | 
			
		||||
            'services' => [
 | 
			
		||||
                'traefik' => [
 | 
			
		||||
                    'container_name' => 'coolify-proxy',
 | 
			
		||||
                    'image' => 'traefik:v2.11',
 | 
			
		||||
                    'image' => 'traefik:v3.1',
 | 
			
		||||
                    'restart' => RESTART_MODE,
 | 
			
		||||
                    'extra_hosts' => [
 | 
			
		||||
                        'host.docker.internal:host-gateway',
 | 
			
		||||
 
 | 
			
		||||
@@ -95,8 +95,26 @@ function generateScpCommand(Server $server, string $source, string $dest)
 | 
			
		||||
    $timeout = config('constants.ssh.command_timeout');
 | 
			
		||||
    $connectionTimeout = config('constants.ssh.connection_timeout');
 | 
			
		||||
    $serverInterval = config('constants.ssh.server_interval');
 | 
			
		||||
    $muxPersistTime = config('constants.ssh.mux_persist_time');
 | 
			
		||||
 | 
			
		||||
    $scp_command = "timeout $timeout scp ";
 | 
			
		||||
    // Check if multiplexing is enabled
 | 
			
		||||
    $muxEnabled = config('constants.ssh.mux_enabled', true);
 | 
			
		||||
    // ray('SSH Multiplexing Enabled:', $muxEnabled)->blue();
 | 
			
		||||
 | 
			
		||||
    if ($muxEnabled) {
 | 
			
		||||
        // Always use multiplexing when enabled
 | 
			
		||||
        $muxSocket = "/var/www/html/storage/app/ssh/mux/{$server->muxFilename()}";
 | 
			
		||||
        $scp_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
 | 
			
		||||
        ensureMultiplexedConnection($server);
 | 
			
		||||
        // ray('Using SSH Multiplexing')->green();
 | 
			
		||||
    } else {
 | 
			
		||||
        // ray('Not using SSH Multiplexing')->red();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (data_get($server, 'settings.is_cloudflare_tunnel')) {
 | 
			
		||||
        $scp_command .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" ';
 | 
			
		||||
    }
 | 
			
		||||
    $scp_command .= "-i {$privateKeyLocation} "
 | 
			
		||||
        .'-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null '
 | 
			
		||||
        .'-o PasswordAuthentication=no '
 | 
			
		||||
@@ -145,9 +163,20 @@ function generateSshCommand(Server $server, string $command)
 | 
			
		||||
 | 
			
		||||
    $ssh_command = "timeout $timeout ssh ";
 | 
			
		||||
 | 
			
		||||
    if (config('coolify.mux_enabled') && config('coolify.is_windows_docker_desktop') == false) {
 | 
			
		||||
        $ssh_command .= "-o ControlMaster=auto -o ControlPersist={$muxPersistTime} -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r ";
 | 
			
		||||
    // Check if multiplexing is enabled
 | 
			
		||||
    $muxEnabled = config('constants.ssh.mux_enabled', true);
 | 
			
		||||
    // ray('SSH Multiplexing Enabled:', $muxEnabled)->blue();
 | 
			
		||||
 | 
			
		||||
    if ($muxEnabled) {
 | 
			
		||||
        // Always use multiplexing when enabled
 | 
			
		||||
        $muxSocket = "/var/www/html/storage/app/ssh/mux/{$server->muxFilename()}";
 | 
			
		||||
        $ssh_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
 | 
			
		||||
        ensureMultiplexedConnection($server);
 | 
			
		||||
        // ray('Using SSH Multiplexing')->green();
 | 
			
		||||
    } else {
 | 
			
		||||
        // ray('Not using SSH Multiplexing')->red();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (data_get($server, 'settings.is_cloudflare_tunnel')) {
 | 
			
		||||
        $ssh_command .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" ';
 | 
			
		||||
    }
 | 
			
		||||
@@ -167,11 +196,100 @@ function generateSshCommand(Server $server, string $command)
 | 
			
		||||
        .$command.PHP_EOL
 | 
			
		||||
        .$delimiter;
 | 
			
		||||
 | 
			
		||||
    // ray($ssh_command);
 | 
			
		||||
    return $ssh_command;
 | 
			
		||||
}
 | 
			
		||||
function instant_remote_process(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false)
 | 
			
		||||
 | 
			
		||||
function ensureMultiplexedConnection(Server $server)
 | 
			
		||||
{
 | 
			
		||||
    static $ensuredConnections = [];
 | 
			
		||||
 | 
			
		||||
    if (isset($ensuredConnections[$server->id])) {
 | 
			
		||||
        if (! shouldResetMultiplexedConnection($server)) {
 | 
			
		||||
            // ray('Using Existing Multiplexed Connection')->green();
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $muxSocket = "/var/www/html/storage/app/ssh/mux/{$server->muxFilename()}";
 | 
			
		||||
    $checkCommand = "ssh -O check -o ControlPath=$muxSocket {$server->user}@{$server->ip} 2>/dev/null";
 | 
			
		||||
 | 
			
		||||
    $process = Process::run($checkCommand);
 | 
			
		||||
 | 
			
		||||
    if ($process->exitCode() === 0) {
 | 
			
		||||
        // ray('Existing Multiplexed Connection is Valid')->green();
 | 
			
		||||
        $ensuredConnections[$server->id] = [
 | 
			
		||||
            'timestamp' => now(),
 | 
			
		||||
            'muxSocket' => $muxSocket,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ray('Establishing New Multiplexed Connection')->orange();
 | 
			
		||||
 | 
			
		||||
    $privateKeyLocation = savePrivateKeyToFs($server);
 | 
			
		||||
    $connectionTimeout = config('constants.ssh.connection_timeout');
 | 
			
		||||
    $serverInterval = config('constants.ssh.server_interval');
 | 
			
		||||
    $muxPersistTime = config('constants.ssh.mux_persist_time');
 | 
			
		||||
 | 
			
		||||
    $establishCommand = "ssh -fNM -o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} "
 | 
			
		||||
        ."-i {$privateKeyLocation} "
 | 
			
		||||
        .'-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null '
 | 
			
		||||
        .'-o PasswordAuthentication=no '
 | 
			
		||||
        ."-o ConnectTimeout=$connectionTimeout "
 | 
			
		||||
        ."-o ServerAliveInterval=$serverInterval "
 | 
			
		||||
        .'-o RequestTTY=no '
 | 
			
		||||
        .'-o LogLevel=ERROR '
 | 
			
		||||
        ."-p {$server->port} "
 | 
			
		||||
        ."{$server->user}@{$server->ip}";
 | 
			
		||||
 | 
			
		||||
    $establishProcess = Process::run($establishCommand);
 | 
			
		||||
 | 
			
		||||
    if ($establishProcess->exitCode() !== 0) {
 | 
			
		||||
        throw new \RuntimeException('Failed to establish multiplexed connection: '.$establishProcess->errorOutput());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $ensuredConnections[$server->id] = [
 | 
			
		||||
        'timestamp' => now(),
 | 
			
		||||
        'muxSocket' => $muxSocket,
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // ray('Established New Multiplexed Connection')->green();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function shouldResetMultiplexedConnection(Server $server)
 | 
			
		||||
{
 | 
			
		||||
    static $ensuredConnections = [];
 | 
			
		||||
 | 
			
		||||
    if (! isset($ensuredConnections[$server->id])) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $lastEnsured = $ensuredConnections[$server->id]['timestamp'];
 | 
			
		||||
    $muxPersistTime = config('constants.ssh.mux_persist_time');
 | 
			
		||||
    $resetInterval = strtotime($muxPersistTime) - time();
 | 
			
		||||
 | 
			
		||||
    return $lastEnsured->addSeconds($resetInterval)->isPast();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function resetMultiplexedConnection(Server $server)
 | 
			
		||||
{
 | 
			
		||||
    static $ensuredConnections = [];
 | 
			
		||||
 | 
			
		||||
    if (isset($ensuredConnections[$server->id])) {
 | 
			
		||||
        $muxSocket = $ensuredConnections[$server->id]['muxSocket'];
 | 
			
		||||
        $closeCommand = "ssh -O exit -o ControlPath=$muxSocket {$server->user}@{$server->ip}";
 | 
			
		||||
        Process::run($closeCommand);
 | 
			
		||||
        unset($ensuredConnections[$server->id]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function instant_remote_process(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false): ?string
 | 
			
		||||
{
 | 
			
		||||
    static $processCount = 0;
 | 
			
		||||
    $processCount++;
 | 
			
		||||
 | 
			
		||||
    $timeout = config('constants.ssh.command_timeout');
 | 
			
		||||
    if ($command instanceof Collection) {
 | 
			
		||||
        $command = $command->toArray();
 | 
			
		||||
@@ -180,10 +298,18 @@ function instant_remote_process(Collection|array $command, Server $server, bool
 | 
			
		||||
        $command = parseCommandsByLineForSudo(collect($command), $server);
 | 
			
		||||
    }
 | 
			
		||||
    $command_string = implode("\n", $command);
 | 
			
		||||
    $ssh_command = generateSshCommand($server, $command_string, $no_sudo);
 | 
			
		||||
    $process = Process::timeout($timeout)->run($ssh_command);
 | 
			
		||||
 | 
			
		||||
    $start_time = microtime(true);
 | 
			
		||||
    $sshCommand = generateSshCommand($server, $command_string);
 | 
			
		||||
    $process = Process::timeout($timeout)->run($sshCommand);
 | 
			
		||||
    $end_time = microtime(true);
 | 
			
		||||
 | 
			
		||||
    $execution_time = ($end_time - $start_time) * 1000; // Convert to milliseconds
 | 
			
		||||
    // ray('SSH command execution time:', $execution_time.' ms')->orange();
 | 
			
		||||
 | 
			
		||||
    $output = trim($process->output());
 | 
			
		||||
    $exitCode = $process->exitCode();
 | 
			
		||||
 | 
			
		||||
    if ($exitCode !== 0) {
 | 
			
		||||
        if (! $throwError) {
 | 
			
		||||
            return null;
 | 
			
		||||
@@ -223,7 +349,6 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d
 | 
			
		||||
    if (is_null($application_deployment_queue)) {
 | 
			
		||||
        return collect([]);
 | 
			
		||||
    }
 | 
			
		||||
    // ray(data_get($application_deployment_queue, 'logs'));
 | 
			
		||||
    try {
 | 
			
		||||
        $decoded = json_decode(
 | 
			
		||||
            data_get($application_deployment_queue, 'logs'),
 | 
			
		||||
@@ -233,7 +358,7 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d
 | 
			
		||||
    } catch (\JsonException $exception) {
 | 
			
		||||
        return collect([]);
 | 
			
		||||
    }
 | 
			
		||||
    // ray($decoded );
 | 
			
		||||
    $seenCommands = collect();
 | 
			
		||||
    $formatted = collect($decoded);
 | 
			
		||||
    if (! $is_debug_enabled) {
 | 
			
		||||
        $formatted = $formatted->filter(fn ($i) => $i['hidden'] === false ?? false);
 | 
			
		||||
@@ -244,8 +369,43 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d
 | 
			
		||||
            data_set($i, 'timestamp', Carbon::parse(data_get($i, 'timestamp'))->format('Y-M-d H:i:s.u'));
 | 
			
		||||
 | 
			
		||||
            return $i;
 | 
			
		||||
        })
 | 
			
		||||
        ->reduce(function ($deploymentLogLines, $logItem) use ($seenCommands) {
 | 
			
		||||
            $command = data_get($logItem, 'command');
 | 
			
		||||
            $isStderr = data_get($logItem, 'type') === 'stderr';
 | 
			
		||||
            $isNewCommand = ! is_null($command) && ! $seenCommands->first(function ($seenCommand) use ($logItem) {
 | 
			
		||||
                return data_get($seenCommand, 'command') === data_get($logItem, 'command') && data_get($seenCommand, 'batch') === data_get($logItem, 'batch');
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if ($isNewCommand) {
 | 
			
		||||
                $deploymentLogLines->push([
 | 
			
		||||
                    'line' => $command,
 | 
			
		||||
                    'timestamp' => data_get($logItem, 'timestamp'),
 | 
			
		||||
                    'stderr' => $isStderr,
 | 
			
		||||
                    'hidden' => data_get($logItem, 'hidden'),
 | 
			
		||||
                    'command' => true,
 | 
			
		||||
                ]);
 | 
			
		||||
 | 
			
		||||
                $seenCommands->push([
 | 
			
		||||
                    'command' => $command,
 | 
			
		||||
                    'batch' => data_get($logItem, 'batch'),
 | 
			
		||||
                ]);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $lines = explode(PHP_EOL, data_get($logItem, 'output'));
 | 
			
		||||
 | 
			
		||||
            foreach ($lines as $line) {
 | 
			
		||||
                $deploymentLogLines->push([
 | 
			
		||||
                    'line' => $line,
 | 
			
		||||
                    'timestamp' => data_get($logItem, 'timestamp'),
 | 
			
		||||
                    'stderr' => $isStderr,
 | 
			
		||||
                    'hidden' => data_get($logItem, 'hidden'),
 | 
			
		||||
                ]);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return $deploymentLogLines;
 | 
			
		||||
        }, collect());
 | 
			
		||||
 | 
			
		||||
    return $formatted;
 | 
			
		||||
}
 | 
			
		||||
function remove_iip($text)
 | 
			
		||||
@@ -258,6 +418,10 @@ function remove_mux_and_private_key(Server $server)
 | 
			
		||||
{
 | 
			
		||||
    $muxFilename = $server->muxFilename();
 | 
			
		||||
    $privateKeyLocation = savePrivateKeyToFs($server);
 | 
			
		||||
 | 
			
		||||
    $closeCommand = "ssh -O exit -o ControlPath=/var/www/html/storage/app/ssh/mux/{$muxFilename} {$server->user}@{$server->ip}";
 | 
			
		||||
    Process::run($closeCommand);
 | 
			
		||||
 | 
			
		||||
    Storage::disk('ssh-mux')->delete($muxFilename);
 | 
			
		||||
    Storage::disk('ssh-keys')->delete($privateKeyLocation);
 | 
			
		||||
}
 | 
			
		||||
@@ -267,7 +431,10 @@ function refresh_server_connection(?PrivateKey $private_key = null)
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    foreach ($private_key->servers as $server) {
 | 
			
		||||
        Storage::disk('ssh-mux')->delete($server->muxFilename());
 | 
			
		||||
        $muxFilename = $server->muxFilename();
 | 
			
		||||
        $closeCommand = "ssh -O exit -o ControlPath=/var/www/html/storage/app/ssh/mux/{$muxFilename} {$server->user}@{$server->ip}";
 | 
			
		||||
        Process::run($closeCommand);
 | 
			
		||||
        Storage::disk('ssh-mux')->delete($muxFilename);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -277,24 +444,17 @@ function checkRequiredCommands(Server $server)
 | 
			
		||||
    foreach ($commands as $command) {
 | 
			
		||||
        $commandFound = instant_remote_process(["docker run --rm --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c 'command -v {$command}'"], $server, false);
 | 
			
		||||
        if ($commandFound) {
 | 
			
		||||
            ray($command.' found');
 | 
			
		||||
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        try {
 | 
			
		||||
            instant_remote_process(["docker run --rm --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c 'apt update && apt install -y {$command}'"], $server);
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            ray('could not install '.$command);
 | 
			
		||||
            ray($e);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        $commandFound = instant_remote_process(["docker run --rm --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c 'command -v {$command}'"], $server, false);
 | 
			
		||||
        if ($commandFound) {
 | 
			
		||||
            ray($command.' found');
 | 
			
		||||
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        ray('could not install '.$command);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ use App\Models\Application;
 | 
			
		||||
use App\Models\EnvironmentVariable;
 | 
			
		||||
use App\Models\ServiceApplication;
 | 
			
		||||
use App\Models\ServiceDatabase;
 | 
			
		||||
use Illuminate\Support\Stringable;
 | 
			
		||||
use Spatie\Url\Url;
 | 
			
		||||
use Symfony\Component\Yaml\Yaml;
 | 
			
		||||
 | 
			
		||||
@@ -15,9 +16,9 @@ function collectRegex(string $name)
 | 
			
		||||
{
 | 
			
		||||
    return "/{$name}\w+/";
 | 
			
		||||
}
 | 
			
		||||
function replaceVariables($variable)
 | 
			
		||||
function replaceVariables(string $variable): Stringable
 | 
			
		||||
{
 | 
			
		||||
    return $variable->before('}')->replaceFirst('$', '')->replaceFirst('{', '');
 | 
			
		||||
    return str($variable)->before('}')->replaceFirst('$', '')->replaceFirst('{', '');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Application $oneService, bool $isInit = false)
 | 
			
		||||
@@ -53,7 +54,9 @@ function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Appli
 | 
			
		||||
            if ($isFile == 'OK') {
 | 
			
		||||
                // If its a file & exists
 | 
			
		||||
                $filesystemContent = instant_remote_process(["cat $fileLocation"], $server);
 | 
			
		||||
                if ($fileVolume->is_based_on_git) {
 | 
			
		||||
                    $fileVolume->content = $filesystemContent;
 | 
			
		||||
                }
 | 
			
		||||
                $fileVolume->is_directory = false;
 | 
			
		||||
                $fileVolume->save();
 | 
			
		||||
            } elseif ($isDir == 'OK') {
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -14,10 +14,11 @@
 | 
			
		||||
        "guzzlehttp/guzzle": "^7.5.0",
 | 
			
		||||
        "laravel/fortify": "^v1.16.0",
 | 
			
		||||
        "laravel/framework": "^v11",
 | 
			
		||||
        "laravel/horizon": "^5.23.1",
 | 
			
		||||
        "laravel/horizon": "^5.27.1",
 | 
			
		||||
        "laravel/prompts": "^0.1.6",
 | 
			
		||||
        "laravel/sanctum": "^v4.0",
 | 
			
		||||
        "laravel/socialite": "^v5.14.0",
 | 
			
		||||
        "laravel/telescope": "^5.2",
 | 
			
		||||
        "laravel/tinker": "^v2.8.1",
 | 
			
		||||
        "laravel/ui": "^4.2",
 | 
			
		||||
        "lcobucci/jwt": "^5.0.0",
 | 
			
		||||
@@ -93,7 +94,9 @@
 | 
			
		||||
    },
 | 
			
		||||
    "extra": {
 | 
			
		||||
        "laravel": {
 | 
			
		||||
            "dont-discover": []
 | 
			
		||||
            "dont-discover": [
 | 
			
		||||
                "laravel/telescope"
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "config": {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										854
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										854
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -199,6 +199,7 @@ return [
 | 
			
		||||
        App\Providers\EventServiceProvider::class,
 | 
			
		||||
        App\Providers\HorizonServiceProvider::class,
 | 
			
		||||
        App\Providers\RouteServiceProvider::class,
 | 
			
		||||
        App\Providers\TelescopeServiceProvider::class,
 | 
			
		||||
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,8 @@ return [
 | 
			
		||||
        'contact' => 'https://coolify.io/docs/contact',
 | 
			
		||||
    ],
 | 
			
		||||
    'ssh' => [
 | 
			
		||||
        'mux_persist_time' => env('SSH_MUX_PERSIST_TIME', '1m'),
 | 
			
		||||
        'mux_enabled' => env('SSH_MUX_ENABLED', true),
 | 
			
		||||
        'mux_persist_time' => env('SSH_MUX_PERSIST_TIME', '1h'),
 | 
			
		||||
        'connection_timeout' => 10,
 | 
			
		||||
        'server_interval' => 20,
 | 
			
		||||
        'command_timeout' => 7200,
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user