Merge branch 'next' into feat--terminal-pty
This commit is contained in:
@@ -1,16 +1,34 @@
|
|||||||
APP_NAME=Coolify-localhost
|
# Coolify Configuration
|
||||||
APP_ID=development
|
|
||||||
APP_ENV=local
|
APP_ENV=local
|
||||||
|
APP_NAME="Coolify Development"
|
||||||
|
APP_ID=development
|
||||||
APP_KEY=
|
APP_KEY=
|
||||||
APP_DEBUG=true
|
|
||||||
APP_URL=http://localhost
|
APP_URL=http://localhost
|
||||||
APP_PORT=8000
|
APP_PORT=8000
|
||||||
|
APP_DEBUG=true
|
||||||
MUX_ENABLED=false
|
MUX_ENABLED=false
|
||||||
|
|
||||||
|
# Enable Laravel Telescope for debugging
|
||||||
|
TELESCOPE_ENABLED=false
|
||||||
|
|
||||||
|
# Selenium Driver URL for Dusk
|
||||||
DUSK_DRIVER_URL=http://selenium:4444
|
DUSK_DRIVER_URL=http://selenium:4444
|
||||||
|
|
||||||
## For Andras only
|
# PostgreSQL Database Configuration
|
||||||
# To purge cache
|
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=
|
BUNNY_API_KEY=
|
||||||
# To upload assets
|
# For asset uploads
|
||||||
BUNNY_STORAGE_API_KEY=
|
BUNNY_STORAGE_API_KEY=
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
|
# Coolify Configuration
|
||||||
APP_ID=
|
APP_ID=
|
||||||
APP_NAME=Coolify
|
APP_NAME=Coolify
|
||||||
APP_KEY=
|
APP_KEY=
|
||||||
|
|
||||||
|
# PostgreSQL Database Configuration
|
||||||
|
DB_USERNAME=coolify
|
||||||
DB_PASSWORD=
|
DB_PASSWORD=
|
||||||
|
|
||||||
|
# Redis Configuration
|
||||||
REDIS_PASSWORD=
|
REDIS_PASSWORD=
|
||||||
|
|
||||||
|
# Pusher Configuration
|
||||||
PUSHER_APP_ID=
|
PUSHER_APP_ID=
|
||||||
PUSHER_APP_KEY=
|
PUSHER_APP_KEY=
|
||||||
PUSHER_APP_SECRET=
|
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 }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
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
|
- name: Build image and push to registry
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
@@ -33,7 +37,9 @@ jobs:
|
|||||||
file: docker/coolify-helper/Dockerfile
|
file: docker/coolify-helper/Dockerfile
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
|
||||||
|
labels: |
|
||||||
|
coolify.managed=true
|
||||||
aarch64:
|
aarch64:
|
||||||
runs-on: [ self-hosted, arm64 ]
|
runs-on: [ self-hosted, arm64 ]
|
||||||
permissions:
|
permissions:
|
||||||
@@ -47,6 +53,10 @@ jobs:
|
|||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
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
|
- name: Build image and push to registry
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
@@ -55,7 +65,9 @@ jobs:
|
|||||||
file: docker/coolify-helper/Dockerfile
|
file: docker/coolify-helper/Dockerfile
|
||||||
platforms: linux/aarch64
|
platforms: linux/aarch64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64
|
||||||
|
labels: |
|
||||||
|
coolify.managed=true
|
||||||
merge-manifest:
|
merge-manifest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
@@ -75,10 +87,15 @@ jobs:
|
|||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
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
|
- name: Create & publish manifest
|
||||||
run: |
|
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
|
- uses: sarisia/actions-status-discord@v1
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
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:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "main" ]
|
branches: [ "main", "next" ]
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/coolify-helper.yml
|
- .github/workflows/coolify-helper.yml
|
||||||
- docker/coolify-helper/Dockerfile
|
- docker/coolify-helper/Dockerfile
|
||||||
@@ -25,6 +25,10 @@ jobs:
|
|||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
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
|
- name: Build image and push to registry
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
@@ -33,7 +37,9 @@ jobs:
|
|||||||
file: docker/coolify-helper/Dockerfile
|
file: docker/coolify-helper/Dockerfile
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
|
||||||
|
labels: |
|
||||||
|
coolify.managed=true
|
||||||
aarch64:
|
aarch64:
|
||||||
runs-on: [ self-hosted, arm64 ]
|
runs-on: [ self-hosted, arm64 ]
|
||||||
permissions:
|
permissions:
|
||||||
@@ -47,6 +53,10 @@ jobs:
|
|||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
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
|
- name: Build image and push to registry
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
@@ -55,7 +65,9 @@ jobs:
|
|||||||
file: docker/coolify-helper/Dockerfile
|
file: docker/coolify-helper/Dockerfile
|
||||||
platforms: linux/aarch64
|
platforms: linux/aarch64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
|
||||||
|
labels: |
|
||||||
|
coolify.managed=true
|
||||||
merge-manifest:
|
merge-manifest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
@@ -75,9 +87,13 @@ jobs:
|
|||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
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
|
- name: Create & publish manifest
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
docker buildx imagetools create --append ${{ env.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
|
- uses: sarisia/actions-status-discord@v1
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
|
|||||||
2
.github/workflows/production-build.yml
vendored
2
.github/workflows/production-build.yml
vendored
@@ -4,6 +4,8 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches: ["main"]
|
branches: ["main"]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
|
- .github/workflows/coolify-helper.yml
|
||||||
|
- docker/coolify-helper/Dockerfile
|
||||||
- templates/service-templates.json
|
- templates/service-templates.json
|
||||||
|
|
||||||
env:
|
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!
|
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>
|
### Special Sponsors
|
||||||
<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>
|
* [CCCareers](https://cccareers.org/) - A career development platform connecting coding bootcamp graduates with job opportunities in the tech industry.
|
||||||
<a href="https://arcjet.com/?ref=coolify.io" target="_blank"><img src="./other/logos/arcjet.svg" alt="arcjet logo" width="200"/></a>
|
* [Hetzner](http://htznr.li/CoolifyXHetzner) - A German web hosting company offering affordable dedicated servers, cloud services, and web hosting solutions.
|
||||||
<a href="https://supa.guide/?ref=coolify.io" target="_blank"><img src="./other/logos/supaguide.png" alt="supaguide logo" width="200"/></a>
|
* [Logto](https://logto.io/?ref=coolify) - An open-source authentication and authorization solution for building secure login systems and managing user identities.
|
||||||
<a href="https://tigrisdata.com/?ref=coolify.io" target="_blank"><img src="./other/logos/tigris.svg" alt="tigris logo" width="140"/></a>
|
* [BC Direct](https://bc.direct/?ref=coolify.io) - A digital marketing agency specializing in e-commerce solutions and online business growth strategies.
|
||||||
<a href="https://fractalnetworks.co/?ref=coolify.io" target="_blank"><img src="./other/logos/fractal.svg" alt="fractal logo" width="180"/></a>
|
* [QuantCDN](https://www.quantcdn.io/?ref=coolify.io) - A content delivery network (CDN) optimizing website performance through global content distribution.
|
||||||
<a href="https://coolify.ad.vin/?ref=coolify.io" target="_blank"><img src="./other/logos/advin.png" alt="advin logo" width="250"/></a>
|
* [Arcjet](https://arcjet.com/?ref=coolify.io) - A cloud-based platform providing real-time protection against API abuse and bot attacks.
|
||||||
<a href="https://trieve.ai/?ref=coolify.io" target="_blank"><img src="./other/logos/trieve_bg.png" alt="trieve logo" width="180"/></a>
|
* [SupaGuide](https://supa.guide/?ref=coolify.io) - A comprehensive resource hub offering guides and tutorials for web development using Supabase.
|
||||||
<a href="https://blacksmith.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/blacksmith.svg" alt="blacksmith logo" width="200"/></a>
|
* [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.
|
||||||
<a href="https://latitude.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/latitude.svg" alt="latitude logo" width="200"/></a>
|
* [Fractal Networks](https://fractalnetworks.co/?ref=coolify.io) - A decentralized network infrastructure company focusing on secure and private communication solutions.
|
||||||
<a href="https://brand.dev/?ref=coolify.io" target="_blank"><img src="./other/logos/branddev.png" alt="branddev logo" width="200"/></a>
|
* [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+)
|
## Github Sponsors ($40+)
|
||||||
<a href="https://serpapi.com/?ref=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a>
|
<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/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://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://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/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://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
|
## Organizations
|
||||||
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
|
<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);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
$docker_compose['services'][$container_name]['logging'] = [
|
$docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
|
||||||
'driver' => 'fluentd',
|
|
||||||
'options' => [
|
|
||||||
'fluentd-address' => 'tcp://127.0.0.1:24224',
|
|
||||||
'fluentd-async' => 'true',
|
|
||||||
'fluentd-sub-second-precision' => 'true',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
@@ -102,6 +95,11 @@ class StartClickhouse
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$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 = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | 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}");
|
$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();
|
return $environment_variables->all();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class StartDragonfly
|
|||||||
$startCommand = "dragonfly --requirepass {$this->database->dragonfly_password}";
|
$startCommand = "dragonfly --requirepass {$this->database->dragonfly_password}";
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -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);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
$docker_compose['services'][$container_name]['logging'] = [
|
$docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
|
||||||
'driver' => 'fluentd',
|
|
||||||
'options' => [
|
|
||||||
'fluentd-address' => 'tcp://127.0.0.1:24224',
|
|
||||||
'fluentd-async' => 'true',
|
|
||||||
'fluentd-sub-second-precision' => 'true',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
@@ -102,6 +95,11 @@ class StartDragonfly
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$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 = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
@@ -120,10 +118,10 @@ class StartDragonfly
|
|||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
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 {
|
} else {
|
||||||
$volume_name = $persistentStorage->name;
|
$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");
|
$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}");
|
$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";
|
$startCommand = "keydb-server --requirepass {$this->database->keydb_password} --appendonly yes";
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -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);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
$docker_compose['services'][$container_name]['logging'] = [
|
$docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
|
||||||
'driver' => 'fluentd',
|
|
||||||
'options' => [
|
|
||||||
'fluentd-address' => 'tcp://127.0.0.1:24224',
|
|
||||||
'fluentd-async' => 'true',
|
|
||||||
'fluentd-sub-second-precision' => 'true',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
@@ -101,15 +94,19 @@ class StartKeydb
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (! is_null($this->database->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'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir.'/keydb.conf',
|
'source' => $this->configuration_dir . '/keydb.conf',
|
||||||
'target' => '/etc/keydb/keydb.conf',
|
'target' => '/etc/keydb/keydb.conf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
$docker_compose['services'][$container_name]['command'] = "keydb-server /etc/keydb/keydb.conf --requirepass {$this->database->keydb_password} --appendonly yes";
|
$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 = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
@@ -128,10 +125,10 @@ class StartKeydb
|
|||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
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 {
|
} else {
|
||||||
$volume_name = $persistentStorage->name;
|
$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");
|
$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}");
|
$environment_variables->push("REDIS_PASSWORD={$this->database->keydb_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
|
||||||
|
|
||||||
return $environment_variables->all();
|
return $environment_variables->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class StartMariadb
|
|||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -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);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
$docker_compose['services'][$container_name]['logging'] = [
|
$docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
|
||||||
'driver' => 'fluentd',
|
|
||||||
'options' => [
|
|
||||||
'fluentd-address' => 'tcp://127.0.0.1:24224',
|
|
||||||
'fluentd-async' => 'true',
|
|
||||||
'fluentd-sub-second-precision' => 'true',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
@@ -96,14 +89,19 @@ class StartMariadb
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (! is_null($this->database->mariadb_conf) || ! empty($this->database->mariadb_conf)) {
|
if (!is_null($this->database->mariadb_conf) || !empty($this->database->mariadb_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir.'/custom-config.cnf',
|
'source' => $this->configuration_dir . '/custom-config.cnf',
|
||||||
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
@@ -122,10 +120,10 @@ class StartMariadb
|
|||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
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 {
|
} else {
|
||||||
$volume_name = $persistentStorage->name;
|
$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");
|
$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}");
|
$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}");
|
$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}");
|
$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}");
|
$environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
|
||||||
|
|
||||||
return $environment_variables->all();
|
return $environment_variables->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class StartMongodb
|
|||||||
$startCommand = 'mongod';
|
$startCommand = 'mongod';
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -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);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
$docker_compose['services'][$container_name]['logging'] = [
|
$docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
|
||||||
'driver' => 'fluentd',
|
|
||||||
'options' => [
|
|
||||||
'fluentd-address' => 'tcp://127.0.0.1:24224',
|
|
||||||
'fluentd-async' => 'true',
|
|
||||||
'fluentd-sub-second-precision' => 'true',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
@@ -104,23 +97,27 @@ class StartMongodb
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (! is_null($this->database->mongo_conf) || ! empty($this->database->mongo_conf)) {
|
if (!is_null($this->database->mongo_conf) || !empty($this->database->mongo_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir.'/mongod.conf',
|
'source' => $this->configuration_dir . '/mongod.conf',
|
||||||
'target' => '/etc/mongo/mongod.conf',
|
'target' => '/etc/mongo/mongod.conf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
$docker_compose['services'][$container_name]['command'] = $startCommand.' --config /etc/mongo/mongod.conf';
|
$docker_compose['services'][$container_name]['command'] = $startCommand . ' --config /etc/mongo/mongod.conf';
|
||||||
}
|
}
|
||||||
$this->add_default_database();
|
$this->add_default_database();
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir.'/docker-entrypoint-initdb.d',
|
'source' => $this->configuration_dir . '/docker-entrypoint-initdb.d',
|
||||||
'target' => '/docker-entrypoint-initdb.d',
|
'target' => '/docker-entrypoint-initdb.d',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 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 = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
@@ -139,10 +136,10 @@ class StartMongodb
|
|||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
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 {
|
} else {
|
||||||
$volume_name = $persistentStorage->name;
|
$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");
|
$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}");
|
$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}");
|
$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}");
|
$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();
|
return $environment_variables->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class StartMysql
|
|||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -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);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
$docker_compose['services'][$container_name]['logging'] = [
|
$docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
|
||||||
'driver' => 'fluentd',
|
|
||||||
'options' => [
|
|
||||||
'fluentd-address' => 'tcp://127.0.0.1:24224',
|
|
||||||
'fluentd-async' => 'true',
|
|
||||||
'fluentd-sub-second-precision' => 'true',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
@@ -96,14 +89,19 @@ class StartMysql
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (! is_null($this->database->mysql_conf) || ! empty($this->database->mysql_conf)) {
|
if (!is_null($this->database->mysql_conf) || !empty($this->database->mysql_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir.'/custom-config.cnf',
|
'source' => $this->configuration_dir . '/custom-config.cnf',
|
||||||
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
@@ -122,10 +120,10 @@ class StartMysql
|
|||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
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 {
|
} else {
|
||||||
$volume_name = $persistentStorage->name;
|
$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");
|
$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}");
|
$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}");
|
$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}");
|
$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}");
|
$environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
|
||||||
|
|
||||||
return $environment_variables->all();
|
return $environment_variables->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class StartPostgresql
|
|||||||
$this->generate_init_scripts();
|
$this->generate_init_scripts();
|
||||||
$this->add_custom_conf();
|
$this->add_custom_conf();
|
||||||
|
|
||||||
|
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'services' => [
|
'services' => [
|
||||||
$container_name => [
|
$container_name => [
|
||||||
@@ -80,14 +81,7 @@ class StartPostgresql
|
|||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
$docker_compose['services'][$container_name]['logging'] = [
|
$docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
|
||||||
'driver' => 'fluentd',
|
|
||||||
'options' => [
|
|
||||||
'fluentd-address' => 'tcp://127.0.0.1:24224',
|
|
||||||
'fluentd-async' => 'true',
|
|
||||||
'fluentd-sub-second-precision' => 'true',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
@@ -126,6 +120,10 @@ class StartPostgresql
|
|||||||
'config_file=/etc/postgresql/postgresql.conf',
|
'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 = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
@@ -193,6 +191,8 @@ class StartPostgresql
|
|||||||
$environment_variables->push("POSTGRES_DB={$this->database->postgres_db}");
|
$environment_variables->push("POSTGRES_DB={$this->database->postgres_db}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
|
||||||
|
|
||||||
return $environment_variables->all();
|
return $environment_variables->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class StartRedis
|
|||||||
$startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
|
$startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -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);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
$docker_compose['services'][$container_name]['logging'] = [
|
$docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
|
||||||
'driver' => 'fluentd',
|
|
||||||
'options' => [
|
|
||||||
'fluentd-address' => 'tcp://127.0.0.1:24224',
|
|
||||||
'fluentd-async' => 'true',
|
|
||||||
'fluentd-sub-second-precision' => 'true',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
@@ -105,15 +98,20 @@ class StartRedis
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (! is_null($this->database->redis_conf) || ! empty($this->database->redis_conf)) {
|
if (!is_null($this->database->redis_conf) || !empty($this->database->redis_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir.'/redis.conf',
|
'source' => $this->configuration_dir . '/redis.conf',
|
||||||
'target' => '/usr/local/etc/redis/redis.conf',
|
'target' => '/usr/local/etc/redis/redis.conf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
$docker_compose['services'][$container_name]['command'] = "redis-server /usr/local/etc/redis/redis.conf --requirepass {$this->database->redis_password} --appendonly yes";
|
$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 = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
@@ -132,10 +130,10 @@ class StartRedis
|
|||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
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 {
|
} else {
|
||||||
$volume_name = $persistentStorage->name;
|
$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");
|
$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}");
|
$environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
|
||||||
|
|
||||||
return $environment_variables->all();
|
return $environment_variables->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Actions\Server;
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
@@ -9,17 +10,30 @@ class CleanupDocker
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Server $server, bool $force = true)
|
public function handle(Server $server)
|
||||||
{
|
{
|
||||||
// cleanup docker images, containers, and builder caches
|
|
||||||
if ($force) {
|
$commands = $this->getCommands();
|
||||||
instant_remote_process(['docker image prune -af'], $server, false);
|
|
||||||
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
|
foreach ($commands as $command) {
|
||||||
instant_remote_process(['docker builder prune -af'], $server, false);
|
instant_remote_process([$command], $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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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]
|
[FILTER]
|
||||||
Name modify
|
Name modify
|
||||||
Match *
|
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]
|
[OUTPUT]
|
||||||
Name nrlogs
|
Name nrlogs
|
||||||
Match *
|
Match *
|
||||||
@@ -98,7 +102,11 @@ class InstallLogDrain
|
|||||||
[FILTER]
|
[FILTER]
|
||||||
Name modify
|
Name modify
|
||||||
Match *
|
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]
|
[OUTPUT]
|
||||||
Name http
|
Name http
|
||||||
Match *
|
Match *
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ namespace App\Actions\Server;
|
|||||||
|
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Support\Facades\File;
|
|
||||||
use Illuminate\Support\Facades\Http;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class UpdateCoolify
|
class UpdateCoolify
|
||||||
@@ -26,12 +24,7 @@ class UpdateCoolify
|
|||||||
if (! $this->server) {
|
if (! $this->server) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CleanupDocker::dispatch($this->server, false)->onQueue('high');
|
CleanupDocker::dispatch($this->server)->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));
|
|
||||||
}
|
|
||||||
$this->latestVersion = get_latest_version_of_coolify();
|
$this->latestVersion = get_latest_version_of_coolify();
|
||||||
$this->currentVersion = config('version');
|
$this->currentVersion = config('version');
|
||||||
if (! $manual_update) {
|
if (! $manual_update) {
|
||||||
@@ -62,10 +55,11 @@ class UpdateCoolify
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$this->latestVersion}"], $this->server, false);
|
||||||
|
|
||||||
remote_process([
|
remote_process([
|
||||||
'curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh',
|
'curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh',
|
||||||
"bash /data/coolify/source/upgrade.sh $this->latestVersion",
|
"bash /data/coolify/source/upgrade.sh $this->latestVersion",
|
||||||
], $this->server);
|
], $this->server);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,10 @@ class StartService
|
|||||||
$service->saveComposeConfigs();
|
$service->saveComposeConfigs();
|
||||||
$commands[] = 'cd '.$service->workdir();
|
$commands[] = 'cd '.$service->workdir();
|
||||||
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
|
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
|
||||||
|
if($service->networks()->count() > 0){
|
||||||
$commands[] = "echo 'Creating Docker network.'";
|
$commands[] = "echo 'Creating Docker network.'";
|
||||||
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid";
|
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid";
|
||||||
|
}
|
||||||
$commands[] = 'echo Starting service.';
|
$commands[] = 'echo Starting service.';
|
||||||
$commands[] = "echo 'Pulling images.'";
|
$commands[] = "echo 'Pulling images.'";
|
||||||
$commands[] = 'docker compose pull';
|
$commands[] = 'docker compose pull';
|
||||||
|
|||||||
@@ -19,14 +19,19 @@ class StopService
|
|||||||
ray('Stopping service: '.$service->name);
|
ray('Stopping service: '.$service->name);
|
||||||
$applications = $service->applications()->get();
|
$applications = $service->applications()->get();
|
||||||
foreach ($applications as $application) {
|
foreach ($applications as $application) {
|
||||||
instant_remote_process(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 {$application->name}-{$service->uuid}"], server: $server, throwError: false);
|
||||||
instant_remote_process(command: ["docker rm -f {$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']);
|
$application->update(['status' => 'exited']);
|
||||||
}
|
}
|
||||||
$dbs = $service->databases()->get();
|
$dbs = $service->databases()->get();
|
||||||
foreach ($dbs as $db) {
|
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 {$db->name}-{$service->uuid}"], server: $server, throwError: false);
|
||||||
instant_remote_process(command: ["docker rm -f {$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']);
|
$db->update(['status' => 'exited']);
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
|
use App\Models\ApplicationPreview;
|
||||||
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\ScheduledTask;
|
use App\Models\ScheduledTask;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
use App\Models\ServiceApplication;
|
use App\Models\ServiceApplication;
|
||||||
@@ -42,6 +44,17 @@ class CleanupStuckedResources extends Command
|
|||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
echo "Error in cleaning stuck application: {$e->getMessage()}\n";
|
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 {
|
try {
|
||||||
$postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get();
|
$postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
foreach ($postgresqls as $postgresql) {
|
foreach ($postgresqls as $postgresql) {
|
||||||
@@ -153,6 +166,18 @@ class CleanupStuckedResources extends Command
|
|||||||
echo "Error in cleaning stuck scheduledtasks: {$e->getMessage()}\n";
|
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
|
// Cleanup any resources that are not attached to any environment or destination or server
|
||||||
try {
|
try {
|
||||||
$applications = Application::all();
|
$applications = Application::all();
|
||||||
|
|||||||
@@ -132,6 +132,9 @@ class Init extends Command
|
|||||||
|
|
||||||
private function cleanup_unused_network_from_coolify_proxy()
|
private function cleanup_unused_network_from_coolify_proxy()
|
||||||
{
|
{
|
||||||
|
if (isCloud()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
foreach ($this->servers as $server) {
|
foreach ($this->servers as $server) {
|
||||||
if (! $server->isFunctional()) {
|
if (! $server->isFunctional()) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class SyncBunny extends Command
|
|||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'sync:bunny {--templates} {--release}';
|
protected $signature = 'sync:bunny {--templates} {--release} {--nightly}';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
@@ -33,6 +33,7 @@ class SyncBunny extends Command
|
|||||||
$that = $this;
|
$that = $this;
|
||||||
$only_template = $this->option('templates');
|
$only_template = $this->option('templates');
|
||||||
$only_version = $this->option('release');
|
$only_version = $this->option('release');
|
||||||
|
$nightly = $this->option('nightly');
|
||||||
$bunny_cdn = 'https://cdn.coollabs.io';
|
$bunny_cdn = 'https://cdn.coollabs.io';
|
||||||
$bunny_cdn_path = 'coolify';
|
$bunny_cdn_path = 'coolify';
|
||||||
$bunny_cdn_storage_name = 'coolcdn';
|
$bunny_cdn_storage_name = 'coolcdn';
|
||||||
@@ -45,9 +46,15 @@ class SyncBunny extends Command
|
|||||||
$upgrade_script = 'upgrade.sh';
|
$upgrade_script = 'upgrade.sh';
|
||||||
$production_env = '.env.production';
|
$production_env = '.env.production';
|
||||||
$service_template = 'service-templates.json';
|
$service_template = 'service-templates.json';
|
||||||
|
|
||||||
$versions = 'versions.json';
|
$versions = 'versions.json';
|
||||||
|
|
||||||
|
$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) {
|
PendingRequest::macro('storage', function ($fileName) use ($that) {
|
||||||
$headers = [
|
$headers = [
|
||||||
'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
|
'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
|
||||||
@@ -73,8 +80,26 @@ class SyncBunny extends Command
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
try {
|
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) {
|
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) {
|
if ($only_template) {
|
||||||
$this->info('About to sync service-templates.json to BunnyCDN.');
|
$this->info('About to sync service-templates.json to BunnyCDN.');
|
||||||
@@ -90,8 +115,12 @@ class SyncBunny extends Command
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
} elseif ($only_version) {
|
} elseif ($only_version) {
|
||||||
$this->info('About to sync versions.json to BunnyCDN.');
|
if ($nightly) {
|
||||||
$file = file_get_contents("$parent_dir/$versions");
|
$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);
|
$json = json_decode($file, true);
|
||||||
$actual_version = data_get($json, 'coolify.v4.version');
|
$actual_version = data_get($json, 'coolify.v4.version');
|
||||||
|
|
||||||
@@ -100,7 +129,7 @@ class SyncBunny extends Command
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Http::pool(fn (Pool $pool) => [
|
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"),
|
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
|
||||||
]);
|
]);
|
||||||
$this->info('versions.json uploaded & purged...');
|
$this->info('versions.json uploaded & purged...');
|
||||||
@@ -109,11 +138,11 @@ class SyncBunny extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
Http::pool(fn (Pool $pool) => [
|
Http::pool(fn (Pool $pool) => [
|
||||||
$pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
|
$pool->storage(fileName: "$compose_file_location")->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: "$compose_file_prod_location")->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: "$production_env_location")->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: "$upgrade_script_location")->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: "$install_script_location")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$install_script"),
|
||||||
]);
|
]);
|
||||||
Http::pool(fn (Pool $pool) => [
|
Http::pool(fn (Pool $pool) => [
|
||||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$compose_file"),
|
$pool->purge("$bunny_cdn/$bunny_cdn_path/$compose_file"),
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ namespace App\Console;
|
|||||||
|
|
||||||
use App\Jobs\CheckForUpdatesJob;
|
use App\Jobs\CheckForUpdatesJob;
|
||||||
use App\Jobs\CleanupInstanceStuffsJob;
|
use App\Jobs\CleanupInstanceStuffsJob;
|
||||||
|
use App\Jobs\CleanupStaleMultiplexedConnections;
|
||||||
use App\Jobs\DatabaseBackupJob;
|
use App\Jobs\DatabaseBackupJob;
|
||||||
use App\Jobs\DockerCleanupJob;
|
use App\Jobs\DockerCleanupJob;
|
||||||
use App\Jobs\PullCoolifyImageJob;
|
|
||||||
use App\Jobs\PullHelperImageJob;
|
use App\Jobs\PullHelperImageJob;
|
||||||
use App\Jobs\PullSentinelImageJob;
|
use App\Jobs\PullSentinelImageJob;
|
||||||
use App\Jobs\PullTemplatesFromCDN;
|
use App\Jobs\PullTemplatesFromCDN;
|
||||||
@@ -30,22 +30,24 @@ class Kernel extends ConsoleKernel
|
|||||||
$this->all_servers = Server::all();
|
$this->all_servers = Server::all();
|
||||||
$settings = InstanceSettings::get();
|
$settings = InstanceSettings::get();
|
||||||
|
|
||||||
|
$schedule->job(new CleanupStaleMultiplexedConnections)->hourly();
|
||||||
|
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
// Instance Jobs
|
// Instance Jobs
|
||||||
$schedule->command('horizon:snapshot')->everyMinute();
|
$schedule->command('horizon:snapshot')->everyMinute();
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
||||||
|
|
||||||
// Server Jobs
|
// Server Jobs
|
||||||
$this->check_scheduled_backups($schedule);
|
$this->check_scheduled_backups($schedule);
|
||||||
$this->check_resources($schedule);
|
$this->check_resources($schedule);
|
||||||
$this->check_scheduled_tasks($schedule);
|
$this->check_scheduled_tasks($schedule);
|
||||||
$schedule->command('uploads:clear')->everyTwoMinutes();
|
$schedule->command('uploads:clear')->everyTwoMinutes();
|
||||||
|
|
||||||
|
$schedule->command('telescope:prune')->daily();
|
||||||
} else {
|
} else {
|
||||||
// Instance Jobs
|
// Instance Jobs
|
||||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||||
$schedule->command('cleanup:unreachable-servers')->daily();
|
$schedule->command('cleanup:unreachable-servers')->daily()->onOneServer();
|
||||||
$schedule->job(new PullCoolifyImageJob)->cron($settings->update_check_frequency)->onOneServer();
|
$schedule->job(new PullTemplatesFromCDN)->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
|
||||||
$schedule->job(new PullTemplatesFromCDN)->cron($settings->update_check_frequency)->onOneServer();
|
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||||
$this->schedule_updates($schedule);
|
$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');
|
$servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
if ($server->isSentinelEnabled()) {
|
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();
|
$settings = InstanceSettings::get();
|
||||||
|
|
||||||
$updateCheckFrequency = $settings->update_check_frequency;
|
$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) {
|
if ($settings->is_auto_update_enabled) {
|
||||||
$autoUpdateFrequency = $settings->auto_update_frequency;
|
$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) {
|
foreach ($servers as $server) {
|
||||||
$schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
|
$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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$server = $scheduled_backup->server();
|
||||||
|
|
||||||
|
if (! $server) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$serverTimezone = $server->settings->server_timezone;
|
||||||
|
|
||||||
if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
|
if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
|
||||||
$scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency];
|
$scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency];
|
||||||
}
|
}
|
||||||
$schedule->job(new DatabaseBackupJob(
|
$schedule->job(new DatabaseBackupJob(
|
||||||
backup: $scheduled_backup
|
backup: $scheduled_backup
|
||||||
))->cron($scheduled_backup->frequency)->onOneServer();
|
))->cron($scheduled_backup->frequency)->timezone($serverTimezone)->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,12 +185,19 @@ class Kernel extends ConsoleKernel
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$server = $scheduled_task->server();
|
||||||
|
if (! $server) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$serverTimezone = $server->settings->server_timezone ?: config('app.timezone');
|
||||||
|
|
||||||
if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) {
|
if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) {
|
||||||
$scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency];
|
$scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency];
|
||||||
}
|
}
|
||||||
$schedule->job(new ScheduledTaskJob(
|
$schedule->job(new ScheduledTaskJob(
|
||||||
task: $scheduled_task
|
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',
|
summary: 'List',
|
||||||
description: 'List all applications.',
|
description: 'List all applications.',
|
||||||
path: '/applications',
|
path: '/applications',
|
||||||
|
operationId: 'list-applications',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -101,6 +102,7 @@ class ApplicationsController extends Controller
|
|||||||
summary: 'Create (Public)',
|
summary: 'Create (Public)',
|
||||||
description: 'Create new application based on a public git repository.',
|
description: 'Create new application based on a public git repository.',
|
||||||
path: '/applications/public',
|
path: '/applications/public',
|
||||||
|
operationId: 'create-public-application',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -202,6 +204,7 @@ class ApplicationsController extends Controller
|
|||||||
summary: 'Create (Private - GH App)',
|
summary: 'Create (Private - GH App)',
|
||||||
description: 'Create new application based on a private repository through a Github App.',
|
description: 'Create new application based on a private repository through a Github App.',
|
||||||
path: '/applications/private-github-app',
|
path: '/applications/private-github-app',
|
||||||
|
operationId: 'create-private-github-app-application',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -303,6 +306,7 @@ class ApplicationsController extends Controller
|
|||||||
summary: 'Create (Private - Deploy Key)',
|
summary: 'Create (Private - Deploy Key)',
|
||||||
description: 'Create new application based on a private repository through a Deploy Key.',
|
description: 'Create new application based on a private repository through a Deploy Key.',
|
||||||
path: '/applications/private-deploy-key',
|
path: '/applications/private-deploy-key',
|
||||||
|
operationId: 'create-private-deploy-key-application',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -404,6 +408,7 @@ class ApplicationsController extends Controller
|
|||||||
summary: 'Create (Dockerfile)',
|
summary: 'Create (Dockerfile)',
|
||||||
description: 'Create new application based on a simple Dockerfile.',
|
description: 'Create new application based on a simple Dockerfile.',
|
||||||
path: '/applications/dockerfile',
|
path: '/applications/dockerfile',
|
||||||
|
operationId: 'create-dockerfile-application',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -490,6 +495,7 @@ class ApplicationsController extends Controller
|
|||||||
summary: 'Create (Docker Image)',
|
summary: 'Create (Docker Image)',
|
||||||
description: 'Create new application based on a prebuilt docker image',
|
description: 'Create new application based on a prebuilt docker image',
|
||||||
path: '/applications/dockerimage',
|
path: '/applications/dockerimage',
|
||||||
|
operationId: 'create-dockerimage-application',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -573,6 +579,7 @@ class ApplicationsController extends Controller
|
|||||||
summary: 'Create (Docker Compose)',
|
summary: 'Create (Docker Compose)',
|
||||||
description: 'Create new application based on a docker-compose file.',
|
description: 'Create new application based on a docker-compose file.',
|
||||||
path: '/applications/dockercompose',
|
path: '/applications/dockercompose',
|
||||||
|
operationId: 'create-dockercompose-application',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -1171,6 +1178,7 @@ class ApplicationsController extends Controller
|
|||||||
summary: 'Get',
|
summary: 'Get',
|
||||||
description: 'Get application by UUID.',
|
description: 'Get application by UUID.',
|
||||||
path: '/applications/{uuid}',
|
path: '/applications/{uuid}',
|
||||||
|
operationId: 'get-application-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -1235,6 +1243,7 @@ class ApplicationsController extends Controller
|
|||||||
summary: 'Delete',
|
summary: 'Delete',
|
||||||
description: 'Delete application by UUID.',
|
description: 'Delete application by UUID.',
|
||||||
path: '/applications/{uuid}',
|
path: '/applications/{uuid}',
|
||||||
|
operationId: 'delete-application-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -1321,6 +1330,7 @@ class ApplicationsController extends Controller
|
|||||||
summary: 'Update',
|
summary: 'Update',
|
||||||
description: 'Update application by UUID.',
|
description: 'Update application by UUID.',
|
||||||
path: '/applications/{uuid}',
|
path: '/applications/{uuid}',
|
||||||
|
operationId: 'update-application-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -1557,6 +1567,7 @@ class ApplicationsController extends Controller
|
|||||||
summary: 'List Envs',
|
summary: 'List Envs',
|
||||||
description: 'List all envs by application UUID.',
|
description: 'List all envs by application UUID.',
|
||||||
path: '/applications/{uuid}/envs',
|
path: '/applications/{uuid}/envs',
|
||||||
|
operationId: 'list-envs-by-application-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -1639,6 +1650,7 @@ class ApplicationsController extends Controller
|
|||||||
summary: 'Update Env',
|
summary: 'Update Env',
|
||||||
description: 'Update env by application UUID.',
|
description: 'Update env by application UUID.',
|
||||||
path: '/applications/{uuid}/envs',
|
path: '/applications/{uuid}/envs',
|
||||||
|
operationId: 'update-env-by-application-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -1821,6 +1833,7 @@ class ApplicationsController extends Controller
|
|||||||
summary: 'Update Envs (Bulk)',
|
summary: 'Update Envs (Bulk)',
|
||||||
description: 'Update multiple envs by application UUID.',
|
description: 'Update multiple envs by application UUID.',
|
||||||
path: '/applications/{uuid}/envs/bulk',
|
path: '/applications/{uuid}/envs/bulk',
|
||||||
|
operationId: 'update-envs-by-application-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -2012,6 +2025,7 @@ class ApplicationsController extends Controller
|
|||||||
summary: 'Create Env',
|
summary: 'Create Env',
|
||||||
description: 'Create env by application UUID.',
|
description: 'Create env by application UUID.',
|
||||||
path: '/applications/{uuid}/envs',
|
path: '/applications/{uuid}/envs',
|
||||||
|
operationId: 'create-env-by-application-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -2171,6 +2185,7 @@ class ApplicationsController extends Controller
|
|||||||
summary: 'Delete Env',
|
summary: 'Delete Env',
|
||||||
description: 'Delete env by UUID.',
|
description: 'Delete env by UUID.',
|
||||||
path: '/applications/{uuid}/envs/{env_uuid}',
|
path: '/applications/{uuid}/envs/{env_uuid}',
|
||||||
|
operationId: 'delete-env-by-application-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -2256,6 +2271,7 @@ class ApplicationsController extends Controller
|
|||||||
summary: 'Start',
|
summary: 'Start',
|
||||||
description: 'Start application. `Post` request is also accepted.',
|
description: 'Start application. `Post` request is also accepted.',
|
||||||
path: '/applications/{uuid}/start',
|
path: '/applications/{uuid}/start',
|
||||||
|
operationId: 'start-application-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -2359,6 +2375,7 @@ class ApplicationsController extends Controller
|
|||||||
summary: 'Stop',
|
summary: 'Stop',
|
||||||
description: 'Stop application. `Post` request is also accepted.',
|
description: 'Stop application. `Post` request is also accepted.',
|
||||||
path: '/applications/{uuid}/stop',
|
path: '/applications/{uuid}/stop',
|
||||||
|
operationId: 'stop-application-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -2431,6 +2448,7 @@ class ApplicationsController extends Controller
|
|||||||
summary: 'Restart',
|
summary: 'Restart',
|
||||||
description: 'Restart application. `Post` request is also accepted.',
|
description: 'Restart application. `Post` request is also accepted.',
|
||||||
path: '/applications/{uuid}/restart',
|
path: '/applications/{uuid}/restart',
|
||||||
|
operationId: 'restart-application-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ class DatabasesController extends Controller
|
|||||||
summary: 'List',
|
summary: 'List',
|
||||||
description: 'List all databases.',
|
description: 'List all databases.',
|
||||||
path: '/databases',
|
path: '/databases',
|
||||||
|
operationId: 'list-databases',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -91,6 +92,7 @@ class DatabasesController extends Controller
|
|||||||
summary: 'Get',
|
summary: 'Get',
|
||||||
description: 'Get database by UUID.',
|
description: 'Get database by UUID.',
|
||||||
path: '/databases/{uuid}',
|
path: '/databases/{uuid}',
|
||||||
|
operationId: 'get-database-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -151,6 +153,7 @@ class DatabasesController extends Controller
|
|||||||
summary: 'Update',
|
summary: 'Update',
|
||||||
description: 'Update database by UUID.',
|
description: 'Update database by UUID.',
|
||||||
path: '/databases/{uuid}',
|
path: '/databases/{uuid}',
|
||||||
|
operationId: 'update-database-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -510,6 +513,7 @@ class DatabasesController extends Controller
|
|||||||
summary: 'Create (PostgreSQL)',
|
summary: 'Create (PostgreSQL)',
|
||||||
description: 'Create a new PostgreSQL database.',
|
description: 'Create a new PostgreSQL database.',
|
||||||
path: '/databases/postgresql',
|
path: '/databases/postgresql',
|
||||||
|
operationId: 'create-database-postgresql',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -575,6 +579,7 @@ class DatabasesController extends Controller
|
|||||||
summary: 'Create (Clickhouse)',
|
summary: 'Create (Clickhouse)',
|
||||||
description: 'Create a new Clickhouse database.',
|
description: 'Create a new Clickhouse database.',
|
||||||
path: '/databases/clickhouse',
|
path: '/databases/clickhouse',
|
||||||
|
operationId: 'create-database-clickhouse',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -636,6 +641,7 @@ class DatabasesController extends Controller
|
|||||||
summary: 'Create (DragonFly)',
|
summary: 'Create (DragonFly)',
|
||||||
description: 'Create a new DragonFly database.',
|
description: 'Create a new DragonFly database.',
|
||||||
path: '/databases/dragonfly',
|
path: '/databases/dragonfly',
|
||||||
|
operationId: 'create-database-dragonfly',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -696,6 +702,7 @@ class DatabasesController extends Controller
|
|||||||
summary: 'Create (Redis)',
|
summary: 'Create (Redis)',
|
||||||
description: 'Create a new Redis database.',
|
description: 'Create a new Redis database.',
|
||||||
path: '/databases/redis',
|
path: '/databases/redis',
|
||||||
|
operationId: 'create-database-redis',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -757,6 +764,7 @@ class DatabasesController extends Controller
|
|||||||
summary: 'Create (KeyDB)',
|
summary: 'Create (KeyDB)',
|
||||||
description: 'Create a new KeyDB database.',
|
description: 'Create a new KeyDB database.',
|
||||||
path: '/databases/keydb',
|
path: '/databases/keydb',
|
||||||
|
operationId: 'create-database-keydb',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -818,6 +826,7 @@ class DatabasesController extends Controller
|
|||||||
summary: 'Create (MariaDB)',
|
summary: 'Create (MariaDB)',
|
||||||
description: 'Create a new MariaDB database.',
|
description: 'Create a new MariaDB database.',
|
||||||
path: '/databases/mariadb',
|
path: '/databases/mariadb',
|
||||||
|
operationId: 'create-database-mariadb',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -882,6 +891,7 @@ class DatabasesController extends Controller
|
|||||||
summary: 'Create (MySQL)',
|
summary: 'Create (MySQL)',
|
||||||
description: 'Create a new MySQL database.',
|
description: 'Create a new MySQL database.',
|
||||||
path: '/databases/mysql',
|
path: '/databases/mysql',
|
||||||
|
operationId: 'create-database-mysql',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -945,6 +955,7 @@ class DatabasesController extends Controller
|
|||||||
summary: 'Create (MongoDB)',
|
summary: 'Create (MongoDB)',
|
||||||
description: 'Create a new MongoDB database.',
|
description: 'Create a new MongoDB database.',
|
||||||
path: '/databases/mongodb',
|
path: '/databases/mongodb',
|
||||||
|
operationId: 'create-database-mongodb',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -1514,6 +1525,7 @@ class DatabasesController extends Controller
|
|||||||
summary: 'Delete',
|
summary: 'Delete',
|
||||||
description: 'Delete database by UUID.',
|
description: 'Delete database by UUID.',
|
||||||
path: '/databases/{uuid}',
|
path: '/databases/{uuid}',
|
||||||
|
operationId: 'delete-database-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -1597,6 +1609,7 @@ class DatabasesController extends Controller
|
|||||||
summary: 'Start',
|
summary: 'Start',
|
||||||
description: 'Start database. `Post` request is also accepted.',
|
description: 'Start database. `Post` request is also accepted.',
|
||||||
path: '/databases/{uuid}/start',
|
path: '/databases/{uuid}/start',
|
||||||
|
operationId: 'start-database-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -1672,6 +1685,7 @@ class DatabasesController extends Controller
|
|||||||
summary: 'Stop',
|
summary: 'Stop',
|
||||||
description: 'Stop database. `Post` request is also accepted.',
|
description: 'Stop database. `Post` request is also accepted.',
|
||||||
path: '/databases/{uuid}/stop',
|
path: '/databases/{uuid}/stop',
|
||||||
|
operationId: 'stop-database-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -1747,6 +1761,7 @@ class DatabasesController extends Controller
|
|||||||
summary: 'Restart',
|
summary: 'Restart',
|
||||||
description: 'Restart database. `Post` request is also accepted.',
|
description: 'Restart database. `Post` request is also accepted.',
|
||||||
path: '/databases/{uuid}/restart',
|
path: '/databases/{uuid}/restart',
|
||||||
|
operationId: 'restart-database-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class DeployController extends Controller
|
|||||||
summary: 'List',
|
summary: 'List',
|
||||||
description: 'List currently running deployments',
|
description: 'List currently running deployments',
|
||||||
path: '/deployments',
|
path: '/deployments',
|
||||||
|
operationId: 'list-deployments',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -79,12 +80,13 @@ class DeployController extends Controller
|
|||||||
summary: 'Get',
|
summary: 'Get',
|
||||||
description: 'Get deployment by UUID.',
|
description: 'Get deployment by UUID.',
|
||||||
path: '/deployments/{uuid}',
|
path: '/deployments/{uuid}',
|
||||||
|
operationId: 'get-deployment-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
tags: ['Deployments'],
|
tags: ['Deployments'],
|
||||||
parameters: [
|
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: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
@@ -134,6 +136,7 @@ class DeployController extends Controller
|
|||||||
summary: 'Deploy',
|
summary: 'Deploy',
|
||||||
description: 'Deploy by tag or uuid. `Post` request also accepted.',
|
description: 'Deploy by tag or uuid. `Post` request also accepted.',
|
||||||
path: '/deploy',
|
path: '/deploy',
|
||||||
|
operationId: 'deploy-by-tag-or-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -147,7 +150,7 @@ class DeployController extends Controller
|
|||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 200,
|
response: 200,
|
||||||
description: 'Get deployment(s) Uuid\'s',
|
description: 'Get deployment(s) UUID\'s',
|
||||||
content: [
|
content: [
|
||||||
new OA\MediaType(
|
new OA\MediaType(
|
||||||
mediaType: 'application/json',
|
mediaType: 'application/json',
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class OtherController extends Controller
|
|||||||
summary: 'Version',
|
summary: 'Version',
|
||||||
description: 'Get Coolify version.',
|
description: 'Get Coolify version.',
|
||||||
path: '/version',
|
path: '/version',
|
||||||
|
operationId: 'version',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -43,6 +44,7 @@ class OtherController extends Controller
|
|||||||
summary: 'Enable API',
|
summary: 'Enable API',
|
||||||
description: 'Enable API (only with root permissions).',
|
description: 'Enable API (only with root permissions).',
|
||||||
path: '/enable',
|
path: '/enable',
|
||||||
|
operationId: 'enable-api',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -94,6 +96,7 @@ class OtherController extends Controller
|
|||||||
summary: 'Disable API',
|
summary: 'Disable API',
|
||||||
description: 'Disable API (only with root permissions).',
|
description: 'Disable API (only with root permissions).',
|
||||||
path: '/disable',
|
path: '/disable',
|
||||||
|
operationId: 'disable-api',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -158,6 +161,7 @@ class OtherController extends Controller
|
|||||||
summary: 'Healthcheck',
|
summary: 'Healthcheck',
|
||||||
description: 'Healthcheck endpoint.',
|
description: 'Healthcheck endpoint.',
|
||||||
path: '/healthcheck',
|
path: '/healthcheck',
|
||||||
|
operationId: 'healthcheck',
|
||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 200,
|
response: 200,
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ class ProjectController extends Controller
|
|||||||
{
|
{
|
||||||
#[OA\Get(
|
#[OA\Get(
|
||||||
summary: 'List',
|
summary: 'List',
|
||||||
description: 'list projects.',
|
description: 'List projects.',
|
||||||
path: '/projects',
|
path: '/projects',
|
||||||
|
operationId: 'list-projects',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -46,7 +47,7 @@ class ProjectController extends Controller
|
|||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
return invalidTokenResponse();
|
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),
|
return response()->json(serializeApiResponse($projects),
|
||||||
);
|
);
|
||||||
@@ -54,8 +55,9 @@ class ProjectController extends Controller
|
|||||||
|
|
||||||
#[OA\Get(
|
#[OA\Get(
|
||||||
summary: 'Get',
|
summary: 'Get',
|
||||||
description: 'Get project by Uuid.',
|
description: 'Get project by UUID.',
|
||||||
path: '/projects/{uuid}',
|
path: '/projects/{uuid}',
|
||||||
|
operationId: 'get-project-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -102,6 +104,7 @@ class ProjectController extends Controller
|
|||||||
summary: 'Environment',
|
summary: 'Environment',
|
||||||
description: 'Get environment by name.',
|
description: 'Get environment by name.',
|
||||||
path: '/projects/{uuid}/{environment_name}',
|
path: '/projects/{uuid}/{environment_name}',
|
||||||
|
operationId: 'get-environment-by-name',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -136,12 +139,15 @@ class ProjectController extends Controller
|
|||||||
return invalidTokenResponse();
|
return invalidTokenResponse();
|
||||||
}
|
}
|
||||||
if (! $request->uuid) {
|
if (! $request->uuid) {
|
||||||
return response()->json(['message' => 'Uuid is required.'], 422);
|
return response()->json(['message' => 'UUID is required.'], 422);
|
||||||
}
|
}
|
||||||
if (! $request->environment_name) {
|
if (! $request->environment_name) {
|
||||||
return response()->json(['message' => 'Environment name is required.'], 422);
|
return response()->json(['message' => 'Environment name is required.'], 422);
|
||||||
}
|
}
|
||||||
$project = Project::whereTeamId($teamId)->whereUuid($request->uuid)->first();
|
$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();
|
$environment = $project->environments()->whereName($request->environment_name)->first();
|
||||||
if (! $environment) {
|
if (! $environment) {
|
||||||
return response()->json(['message' => 'Environment not found.'], 404);
|
return response()->json(['message' => 'Environment not found.'], 404);
|
||||||
@@ -155,6 +161,7 @@ class ProjectController extends Controller
|
|||||||
summary: 'Create',
|
summary: 'Create',
|
||||||
description: 'Create Project.',
|
description: 'Create Project.',
|
||||||
path: '/projects',
|
path: '/projects',
|
||||||
|
operationId: 'create-project',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -167,7 +174,7 @@ class ProjectController extends Controller
|
|||||||
schema: new OA\Schema(
|
schema: new OA\Schema(
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: [
|
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.'],
|
'description' => ['type' => 'string', 'description' => 'The description of the project.'],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -250,6 +257,7 @@ class ProjectController extends Controller
|
|||||||
summary: 'Update',
|
summary: 'Update',
|
||||||
description: 'Update Project.',
|
description: 'Update Project.',
|
||||||
path: '/projects/{uuid}',
|
path: '/projects/{uuid}',
|
||||||
|
operationId: 'update-project-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -333,7 +341,7 @@ class ProjectController extends Controller
|
|||||||
}
|
}
|
||||||
$uuid = $request->uuid;
|
$uuid = $request->uuid;
|
||||||
if (! $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();
|
$project = Project::whereTeamId($teamId)->whereUuid($uuid)->first();
|
||||||
@@ -355,6 +363,7 @@ class ProjectController extends Controller
|
|||||||
summary: 'Delete',
|
summary: 'Delete',
|
||||||
description: 'Delete project by UUID.',
|
description: 'Delete project by UUID.',
|
||||||
path: '/projects/{uuid}',
|
path: '/projects/{uuid}',
|
||||||
|
operationId: 'delete-project-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -408,7 +417,7 @@ class ProjectController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (! $request->uuid) {
|
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();
|
$project = Project::whereTeamId($teamId)->whereUuid($request->uuid)->first();
|
||||||
if (! $project) {
|
if (! $project) {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class ResourcesController extends Controller
|
|||||||
summary: 'List',
|
summary: 'List',
|
||||||
description: 'Get all resources.',
|
description: 'Get all resources.',
|
||||||
path: '/resources',
|
path: '/resources',
|
||||||
|
operationId: 'list-resources',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class SecurityController extends Controller
|
|||||||
summary: 'List',
|
summary: 'List',
|
||||||
description: 'List all private keys.',
|
description: 'List all private keys.',
|
||||||
path: '/security/keys',
|
path: '/security/keys',
|
||||||
|
operationId: 'list-private-keys',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -68,12 +69,13 @@ class SecurityController extends Controller
|
|||||||
summary: 'Get',
|
summary: 'Get',
|
||||||
description: 'Get key by UUID.',
|
description: 'Get key by UUID.',
|
||||||
path: '/security/keys/{uuid}',
|
path: '/security/keys/{uuid}',
|
||||||
|
operationId: 'get-private-key-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
tags: ['Private Keys'],
|
tags: ['Private Keys'],
|
||||||
parameters: [
|
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: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
@@ -124,6 +126,7 @@ class SecurityController extends Controller
|
|||||||
summary: 'Create',
|
summary: 'Create',
|
||||||
description: 'Create a new private key.',
|
description: 'Create a new private key.',
|
||||||
path: '/security/keys',
|
path: '/security/keys',
|
||||||
|
operationId: 'create-private-key',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -217,6 +220,7 @@ class SecurityController extends Controller
|
|||||||
summary: 'Update',
|
summary: 'Update',
|
||||||
description: 'Update a private key.',
|
description: 'Update a private key.',
|
||||||
path: '/security/keys',
|
path: '/security/keys',
|
||||||
|
operationId: 'update-private-key',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -313,12 +317,13 @@ class SecurityController extends Controller
|
|||||||
summary: 'Delete',
|
summary: 'Delete',
|
||||||
description: 'Delete a private key.',
|
description: 'Delete a private key.',
|
||||||
path: '/security/keys/{uuid}',
|
path: '/security/keys/{uuid}',
|
||||||
|
operationId: 'delete-private-key-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
tags: ['Private Keys'],
|
tags: ['Private Keys'],
|
||||||
parameters: [
|
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: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ class ServersController extends Controller
|
|||||||
summary: 'List',
|
summary: 'List',
|
||||||
description: 'List all servers.',
|
description: 'List all servers.',
|
||||||
path: '/servers',
|
path: '/servers',
|
||||||
|
operationId: 'list-servers',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -100,12 +101,13 @@ class ServersController extends Controller
|
|||||||
summary: 'Get',
|
summary: 'Get',
|
||||||
description: 'Get server by UUID.',
|
description: 'Get server by UUID.',
|
||||||
path: '/servers/{uuid}',
|
path: '/servers/{uuid}',
|
||||||
|
operationId: 'get-server-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
tags: ['Servers'],
|
tags: ['Servers'],
|
||||||
parameters: [
|
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: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
@@ -177,12 +179,13 @@ class ServersController extends Controller
|
|||||||
summary: 'Resources',
|
summary: 'Resources',
|
||||||
description: 'Get resources by server.',
|
description: 'Get resources by server.',
|
||||||
path: '/servers/{uuid}/resources',
|
path: '/servers/{uuid}/resources',
|
||||||
|
operationId: 'get-resources-by-server-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
tags: ['Servers'],
|
tags: ['Servers'],
|
||||||
parameters: [
|
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: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
@@ -254,12 +257,13 @@ class ServersController extends Controller
|
|||||||
summary: 'Domains',
|
summary: 'Domains',
|
||||||
description: 'Get domains by server.',
|
description: 'Get domains by server.',
|
||||||
path: '/servers/{uuid}/domains',
|
path: '/servers/{uuid}/domains',
|
||||||
|
operationId: 'get-domains-by-server-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
tags: ['Servers'],
|
tags: ['Servers'],
|
||||||
parameters: [
|
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: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
@@ -401,6 +405,7 @@ class ServersController extends Controller
|
|||||||
summary: 'Create',
|
summary: 'Create',
|
||||||
description: 'Create Server.',
|
description: 'Create Server.',
|
||||||
path: '/servers',
|
path: '/servers',
|
||||||
|
operationId: 'create-server',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -545,6 +550,7 @@ class ServersController extends Controller
|
|||||||
summary: 'Update',
|
summary: 'Update',
|
||||||
description: 'Update Server.',
|
description: 'Update Server.',
|
||||||
path: '/servers/{uuid}',
|
path: '/servers/{uuid}',
|
||||||
|
operationId: 'update-server-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -655,6 +661,7 @@ class ServersController extends Controller
|
|||||||
summary: 'Delete',
|
summary: 'Delete',
|
||||||
description: 'Delete server by UUID.',
|
description: 'Delete server by UUID.',
|
||||||
path: '/servers/{uuid}',
|
path: '/servers/{uuid}',
|
||||||
|
operationId: 'delete-server-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -727,6 +734,7 @@ class ServersController extends Controller
|
|||||||
summary: 'Validate',
|
summary: 'Validate',
|
||||||
description: 'Validate server by UUID.',
|
description: 'Validate server by UUID.',
|
||||||
path: '/servers/{uuid}/validate',
|
path: '/servers/{uuid}/validate',
|
||||||
|
operationId: 'validate-server-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class ServicesController extends Controller
|
|||||||
summary: 'List',
|
summary: 'List',
|
||||||
description: 'List all services.',
|
description: 'List all services.',
|
||||||
path: '/services',
|
path: '/services',
|
||||||
|
operationId: 'list-services',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -88,6 +89,7 @@ class ServicesController extends Controller
|
|||||||
summary: 'Create',
|
summary: 'Create',
|
||||||
description: 'Create a one-click service',
|
description: 'Create a one-click service',
|
||||||
path: '/services',
|
path: '/services',
|
||||||
|
operationId: 'create-service',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -365,6 +367,7 @@ class ServicesController extends Controller
|
|||||||
summary: 'Get',
|
summary: 'Get',
|
||||||
description: 'Get service by UUID.',
|
description: 'Get service by UUID.',
|
||||||
path: '/services/{uuid}',
|
path: '/services/{uuid}',
|
||||||
|
operationId: 'get-service-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -375,7 +378,7 @@ class ServicesController extends Controller
|
|||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 200,
|
response: 200,
|
||||||
description: 'Get a service by Uuid.',
|
description: 'Get a service by UUID.',
|
||||||
content: [
|
content: [
|
||||||
new OA\MediaType(
|
new OA\MediaType(
|
||||||
mediaType: 'application/json',
|
mediaType: 'application/json',
|
||||||
@@ -422,6 +425,7 @@ class ServicesController extends Controller
|
|||||||
summary: 'Delete',
|
summary: 'Delete',
|
||||||
description: 'Delete service by UUID.',
|
description: 'Delete service by UUID.',
|
||||||
path: '/services/{uuid}',
|
path: '/services/{uuid}',
|
||||||
|
operationId: 'delete-service-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -432,7 +436,7 @@ class ServicesController extends Controller
|
|||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 200,
|
response: 200,
|
||||||
description: 'Delete a service by Uuid',
|
description: 'Delete a service by UUID',
|
||||||
content: [
|
content: [
|
||||||
new OA\MediaType(
|
new OA\MediaType(
|
||||||
mediaType: 'application/json',
|
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(
|
#[OA\Get(
|
||||||
summary: 'Start',
|
summary: 'Start',
|
||||||
description: 'Start service. `Post` request is also accepted.',
|
description: 'Start service. `Post` request is also accepted.',
|
||||||
path: '/services/{uuid}/start',
|
path: '/services/{uuid}/start',
|
||||||
|
operationId: 'start-service-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -558,6 +1073,7 @@ class ServicesController extends Controller
|
|||||||
summary: 'Stop',
|
summary: 'Stop',
|
||||||
description: 'Stop service. `Post` request is also accepted.',
|
description: 'Stop service. `Post` request is also accepted.',
|
||||||
path: '/services/{uuid}/stop',
|
path: '/services/{uuid}/stop',
|
||||||
|
operationId: 'stop-service-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -633,6 +1149,7 @@ class ServicesController extends Controller
|
|||||||
summary: 'Restart',
|
summary: 'Restart',
|
||||||
description: 'Restart service. `Post` request is also accepted.',
|
description: 'Restart service. `Post` request is also accepted.',
|
||||||
path: '/services/{uuid}/restart',
|
path: '/services/{uuid}/restart',
|
||||||
|
operationId: 'restart-service-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class TeamController extends Controller
|
|||||||
summary: 'List',
|
summary: 'List',
|
||||||
description: 'Get all teams.',
|
description: 'Get all teams.',
|
||||||
path: '/teams',
|
path: '/teams',
|
||||||
|
operationId: 'list-teams',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -79,6 +80,7 @@ class TeamController extends Controller
|
|||||||
summary: 'Get',
|
summary: 'Get',
|
||||||
description: 'Get team by TeamId.',
|
description: 'Get team by TeamId.',
|
||||||
path: '/teams/{id}',
|
path: '/teams/{id}',
|
||||||
|
operationId: 'get-team-by-id',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -129,6 +131,7 @@ class TeamController extends Controller
|
|||||||
summary: 'Members',
|
summary: 'Members',
|
||||||
description: 'Get members by TeamId.',
|
description: 'Get members by TeamId.',
|
||||||
path: '/teams/{id}/members',
|
path: '/teams/{id}/members',
|
||||||
|
operationId: 'get-members-by-team-id',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -189,6 +192,7 @@ class TeamController extends Controller
|
|||||||
summary: 'Authenticated Team',
|
summary: 'Authenticated Team',
|
||||||
description: 'Get currently authenticated team.',
|
description: 'Get currently authenticated team.',
|
||||||
path: '/teams/current',
|
path: '/teams/current',
|
||||||
|
operationId: 'get-current-team',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -225,6 +229,7 @@ class TeamController extends Controller
|
|||||||
summary: 'Authenticated Team Members',
|
summary: 'Authenticated Team Members',
|
||||||
description: 'Get currently authenticated team members.',
|
description: 'Get currently authenticated team members.',
|
||||||
path: '/teams/current/members',
|
path: '/teams/current/members',
|
||||||
|
operationId: 'get-current-team-members',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use App\Models\ApplicationPreview;
|
|||||||
use App\Models\EnvironmentVariable;
|
use App\Models\EnvironmentVariable;
|
||||||
use App\Models\GithubApp;
|
use App\Models\GithubApp;
|
||||||
use App\Models\GitlabApp;
|
use App\Models\GitlabApp;
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\StandaloneDocker;
|
use App\Models\StandaloneDocker;
|
||||||
use App\Models\SwarmDocker;
|
use App\Models\SwarmDocker;
|
||||||
@@ -109,10 +110,12 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
private bool $is_debug_enabled;
|
private bool $is_debug_enabled;
|
||||||
|
|
||||||
private $build_args;
|
private Collection|string $build_args;
|
||||||
|
|
||||||
private $env_args;
|
private $env_args;
|
||||||
|
|
||||||
|
private $environment_variables;
|
||||||
|
|
||||||
private $env_nixpacks_args;
|
private $env_nixpacks_args;
|
||||||
|
|
||||||
private $docker_compose;
|
private $docker_compose;
|
||||||
@@ -166,6 +169,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->application_deployment_queue = ApplicationDeploymentQueue::find($application_deployment_queue_id);
|
$this->application_deployment_queue = ApplicationDeploymentQueue::find($application_deployment_queue_id);
|
||||||
$this->application = Application::find($this->application_deployment_queue->application_id);
|
$this->application = Application::find($this->application_deployment_queue->application_id);
|
||||||
$this->build_pack = data_get($this->application, 'build_pack');
|
$this->build_pack = data_get($this->application, 'build_pack');
|
||||||
|
$this->build_args = collect([]);
|
||||||
|
|
||||||
$this->application_deployment_queue_id = $application_deployment_queue_id;
|
$this->application_deployment_queue_id = $application_deployment_queue_id;
|
||||||
$this->deployment_uuid = $this->application_deployment_queue->deployment_uuid;
|
$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}";
|
$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);
|
savePrivateKeyToFs($this->server);
|
||||||
$this->saved_outputs = collect();
|
$this->saved_outputs = collect();
|
||||||
@@ -419,15 +423,42 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->prepare_builder_image();
|
$this->prepare_builder_image();
|
||||||
$this->check_git_if_build_needed();
|
$this->check_git_if_build_needed();
|
||||||
$this->clone_repository();
|
$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->generate_image_names();
|
||||||
$this->cleanup_git();
|
$this->cleanup_git();
|
||||||
$this->application->loadComposeFile(isInit: false);
|
$this->application->loadComposeFile(isInit: false);
|
||||||
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
||||||
$this->application->parseRawCompose();
|
$this->application->oldRawParser();
|
||||||
$yaml = $composeFile = $this->application->docker_compose_raw;
|
$yaml = $composeFile = $this->application->docker_compose_raw;
|
||||||
$this->save_environment_variables();
|
$this->save_environment_variables();
|
||||||
} else {
|
} 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();
|
$this->save_environment_variables();
|
||||||
if (! is_null($this->env_filename)) {
|
if (! is_null($this->env_filename)) {
|
||||||
$services = collect($composeFile['services']);
|
$services = collect($composeFile['services']);
|
||||||
@@ -444,11 +475,12 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$yaml = Yaml::dump($composeFile->toArray(), 10);
|
$yaml = Yaml::dump(convertToArray($composeFile), 10);
|
||||||
}
|
}
|
||||||
$this->docker_compose_base64 = base64_encode($yaml);
|
$this->docker_compose_base64 = base64_encode($yaml);
|
||||||
$this->execute_remote_command([
|
$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.
|
// Build new container to limit downtime.
|
||||||
$this->application_deployment_queue->addLogEntry('Pulling & building required images.');
|
$this->application_deployment_queue->addLogEntry('Pulling & building required images.');
|
||||||
@@ -478,9 +510,13 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
// TODO
|
// TODO
|
||||||
} else {
|
} else {
|
||||||
$this->execute_remote_command([
|
$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(
|
$this->execute_remote_command(
|
||||||
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
|
[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",
|
"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}",
|
"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) {
|
if (! $fileStorage->is_based_on_git && ! $fileStorage->is_directory) {
|
||||||
$fileStorage->saveStorageOnServer();
|
$fileStorage->saveStorageOnServer();
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
if ($this->use_build_server) {
|
if ($this->use_build_server) {
|
||||||
$this->server = $this->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->application_deployment_queue->addLogEntry("Pushing image to docker registry ({$this->production_image_name}).");
|
||||||
$this->execute_remote_command(
|
$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) {
|
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->application_deployment_queue->addLogEntry("Tagging and pushing image with {$this->application->docker_registry_image_tag} tag.");
|
||||||
$this->execute_remote_command(
|
$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()
|
private function check_image_locally_or_remotely()
|
||||||
{
|
{
|
||||||
$this->execute_remote_command([
|
$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) {
|
if (str($this->saved_outputs->get('local_image_found'))->isEmpty() && $this->application->docker_registry_image_name) {
|
||||||
$this->execute_remote_command([
|
$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([
|
$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()) {
|
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_FQDN')->isEmpty()) {
|
||||||
$envs->push("COOLIFY_FQDN={$this->preview->fqdn}");
|
$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()) {
|
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_URL')->isEmpty()) {
|
||||||
$url = str($this->preview->fqdn)->replace('http://', '')->replace('https://', '');
|
$url = str($this->preview->fqdn)->replace('http://', '')->replace('https://', '');
|
||||||
$envs->push("COOLIFY_URL={$url}");
|
$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()) {
|
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||||
$envs->push("COOLIFY_BRANCH={$local_branch}");
|
$envs->push("COOLIFY_BRANCH={$local_branch}");
|
||||||
}
|
}
|
||||||
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
||||||
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
|
$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) {
|
foreach ($sorted_environment_variables_preview as $env) {
|
||||||
$real_value = $env->real_value;
|
$real_value = $env->real_value;
|
||||||
if ($env->version === '4.0.0-beta.239') {
|
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->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}");
|
$envs->push("COOLIFY_FQDN={$this->application->fqdn}");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if ($this->application->environment_variables->where('key', 'COOLIFY_URL')->isEmpty()) {
|
if ($this->application->environment_variables->where('key', 'COOLIFY_URL')->isEmpty()) {
|
||||||
$url = str($this->application->fqdn)->replace('http://', '')->replace('https://', '');
|
$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}");
|
$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()) {
|
if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||||
$envs->push("COOLIFY_BRANCH={$local_branch}");
|
$envs->push("COOLIFY_BRANCH={$local_branch}");
|
||||||
}
|
}
|
||||||
if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
||||||
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
|
$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) {
|
foreach ($sorted_environment_variables as $env) {
|
||||||
$real_value = $env->real_value;
|
$real_value = $env->real_value;
|
||||||
if ($env->version === '4.0.0-beta.239') {
|
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()
|
private function laravel_finetunes()
|
||||||
{
|
{
|
||||||
if ($this->pull_request_id === 0) {
|
if ($this->pull_request_id === 0) {
|
||||||
$nixpacks_php_fallback_path = $this->application->environment_variables->where('key', 'NIXPACKS_PHP_FALLBACK_PATH')->first();
|
$envType = 'environment_variables';
|
||||||
$nixpacks_php_root_dir = $this->application->environment_variables->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first();
|
|
||||||
} else {
|
} else {
|
||||||
$nixpacks_php_fallback_path = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_FALLBACK_PATH')->first();
|
$envType = 'environment_variables_preview';
|
||||||
$nixpacks_php_root_dir = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first();
|
|
||||||
}
|
}
|
||||||
|
$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) {
|
if (! $nixpacks_php_fallback_path) {
|
||||||
$nixpacks_php_fallback_path = new EnvironmentVariable;
|
$nixpacks_php_fallback_path = new EnvironmentVariable;
|
||||||
$nixpacks_php_fallback_path->key = 'NIXPACKS_PHP_FALLBACK_PATH';
|
$nixpacks_php_fallback_path->key = 'NIXPACKS_PHP_FALLBACK_PATH';
|
||||||
@@ -1230,7 +1334,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
private function prepare_builder_image()
|
private function prepare_builder_image()
|
||||||
{
|
{
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
$helperImage = config('coolify.helper_image');
|
$helperImage = config('coolify.helper_image');
|
||||||
|
$helperImage = "{$helperImage}:{$settings->helper_version}";
|
||||||
// Get user home directory
|
// Get user home directory
|
||||||
$this->serverUserHomeDir = instant_remote_process(['echo $HOME'], $this->server);
|
$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);
|
$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(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
$importCommands, 'hidden' => true,
|
$importCommands,
|
||||||
|
'hidden' => true,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
$this->create_workdir();
|
$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_FALLBACK_PATH', $variables[0]->value);
|
||||||
data_set($parsed, 'variables.NIXPACKS_PHP_ROOT_DIR', $variables[1]->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->nixpacks_plan = json_encode($parsed, JSON_PRETTY_PRINT);
|
||||||
$this->application_deployment_queue->addLogEntry("Final Nixpacks plan: {$this->nixpacks_plan}", hidden: true);
|
$this->application_deployment_queue->addLogEntry("Final Nixpacks plan: {$this->nixpacks_plan}", hidden: true);
|
||||||
if ($this->nixpacks_type === 'rust') {
|
if ($this->nixpacks_type === 'rust') {
|
||||||
@@ -1592,7 +1702,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
// Check for custom HEALTHCHECK
|
// Check for custom HEALTHCHECK
|
||||||
if ($this->application->build_pack === 'dockerfile' || $this->application->dockerfile) {
|
if ($this->application->build_pack === 'dockerfile' || $this->application->dockerfile) {
|
||||||
$this->execute_remote_command([
|
$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"));
|
$dockerfile = collect(str($this->saved_outputs->get('dockerfile_from_repo'))->trim()->explode("\n"));
|
||||||
$this->application->parseHealthcheckFromDockerfile($dockerfile);
|
$this->application->parseHealthcheckFromDockerfile($dockerfile);
|
||||||
@@ -1695,14 +1808,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$docker_compose['services'][$this->container_name]['labels'] = $labels;
|
$docker_compose['services'][$this->container_name]['labels'] = $labels;
|
||||||
}
|
}
|
||||||
if ($this->server->isLogDrainEnabled() && $this->application->isLogDrainEnabled()) {
|
if ($this->server->isLogDrainEnabled() && $this->application->isLogDrainEnabled()) {
|
||||||
$docker_compose['services'][$this->container_name]['logging'] = [
|
$docker_compose['services'][$this->container_name]['logging'] = generate_fluentd_configuration();
|
||||||
'driver' => 'fluentd',
|
|
||||||
'options' => [
|
|
||||||
'fluentd-address' => 'tcp://127.0.0.1:24224',
|
|
||||||
'fluentd-async' => 'true',
|
|
||||||
'fluentd-sub-second-precision' => 'true',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if ($this->application->settings->is_gpu_enabled) {
|
if ($this->application->settings->is_gpu_enabled) {
|
||||||
$docker_compose['services'][$this->container_name]['deploy']['resources']['reservations']['devices'] = [
|
$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->application_deployment_queue->addLogEntry("Pulling latest image ($image) from the registry.");
|
||||||
$this->execute_remote_command(
|
$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()
|
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('----------------------------------------');
|
$this->application_deployment_queue->addLogEntry('----------------------------------------');
|
||||||
if ($this->application->build_pack === 'static') {
|
if ($this->application->build_pack === 'static') {
|
||||||
$this->application_deployment_queue->addLogEntry('Static deployment. Copying static assets to the image.');
|
$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]);
|
$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) {
|
if ($this->force_rebuild) {
|
||||||
$this->execute_remote_command([
|
$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}";
|
$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 {
|
} else {
|
||||||
$this->execute_remote_command([
|
$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}";
|
$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);
|
$base64_build_command = base64_encode($build_command);
|
||||||
$this->execute_remote_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]);
|
$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(
|
$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 '{$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 {
|
} else {
|
||||||
@@ -2002,10 +2126,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
$base64_build_command = base64_encode($build_command);
|
$base64_build_command = base64_encode($build_command);
|
||||||
$this->execute_remote_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 {
|
} 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]);
|
$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) {
|
if ($this->force_rebuild) {
|
||||||
$this->execute_remote_command([
|
$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}";
|
$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 {
|
} else {
|
||||||
$this->execute_remote_command([
|
$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}";
|
$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);
|
$base64_build_command = base64_encode($build_command);
|
||||||
$this->execute_remote_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]);
|
$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(
|
$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(
|
$this->execute_remote_command(
|
||||||
["docker rm -f $containerName", 'hidden' => true, 'ignore_errors' => true]
|
["docker rm -f $containerName", 'hidden' => true, 'ignore_errors' => true]
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function stop_running_container(bool $force = false)
|
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->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()
|
private function add_build_env_variables_to_dockerfile()
|
||||||
{
|
{
|
||||||
$this->execute_remote_command([
|
$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"));
|
$dockerfile = collect(str($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
|
||||||
if ($this->pull_request_id === 0) {
|
if ($this->pull_request_id === 0) {
|
||||||
@@ -2168,7 +2298,6 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
} else {
|
} else {
|
||||||
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}");
|
$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"));
|
$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}";
|
$exec = "docker exec {$containerName} {$cmd}";
|
||||||
$this->execute_remote_command(
|
$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 {
|
try {
|
||||||
$this->execute_remote_command(
|
$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) {
|
} 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\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
|
||||||
class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue
|
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');
|
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
||||||
if ($response->successful()) {
|
if ($response->successful()) {
|
||||||
$versions = $response->json();
|
$versions = $response->json();
|
||||||
|
|
||||||
$latest_version = data_get($versions, 'coolify.v4.version');
|
$latest_version = data_get($versions, 'coolify.v4.version');
|
||||||
$current_version = config('version');
|
$current_version = config('version');
|
||||||
|
|
||||||
if (version_compare($latest_version, $current_version, '>')) {
|
if (version_compare($latest_version, $current_version, '>')) {
|
||||||
// New version available
|
// New version available
|
||||||
$settings->update(['new_version_available' => true]);
|
$settings->update(['new_version_available' => true]);
|
||||||
|
File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT));
|
||||||
} else {
|
} else {
|
||||||
$settings->update(['new_version_available' => false]);
|
$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\Middleware\WithoutOverlapping;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
|
|
||||||
class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
{
|
{
|
||||||
@@ -56,6 +57,8 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public ?string $backup_output = null;
|
public ?string $backup_output = null;
|
||||||
|
|
||||||
|
public ?string $postgres_password = null;
|
||||||
|
|
||||||
public ?S3Storage $s3 = null;
|
public ?S3Storage $s3 = null;
|
||||||
|
|
||||||
public function __construct($backup)
|
public function __construct($backup)
|
||||||
@@ -89,8 +92,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
BackupCreated::dispatch($this->team->id);
|
|
||||||
|
|
||||||
// Check if team is exists
|
// Check if team is exists
|
||||||
if (is_null($this->team)) {
|
if (is_null($this->team)) {
|
||||||
$this->backup->update(['status' => 'failed']);
|
$this->backup->update(['status' => 'failed']);
|
||||||
@@ -99,6 +100,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BackupCreated::dispatch($this->team->id);
|
||||||
|
|
||||||
$status = str(data_get($this->database, 'status'));
|
$status = str(data_get($this->database, 'status'));
|
||||||
if (! $status->startsWith('running') && $this->database->id !== 0) {
|
if (! $status->startsWith('running') && $this->database->id !== 0) {
|
||||||
ray('database not running');
|
ray('database not running');
|
||||||
@@ -134,6 +138,13 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
} else {
|
} else {
|
||||||
$databasesToBackup = $this->database->postgres_user;
|
$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')) {
|
} elseif (str($databaseType)->contains('mysql')) {
|
||||||
$this->container_name = "{$this->database->name}-$serviceUuid";
|
$this->container_name = "{$this->database->name}-$serviceUuid";
|
||||||
$this->directory_name = $serviceName.'-'.$this->container_name;
|
$this->directory_name = $serviceName.'-'.$this->container_name;
|
||||||
@@ -381,7 +392,13 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$commands[] = 'mkdir -p '.$this->backup_dir;
|
$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 = instant_remote_process($commands, $this->server);
|
||||||
$this->backup_output = trim($this->backup_output);
|
$this->backup_output = trim($this->backup_output);
|
||||||
if ($this->backup_output === '') {
|
if ($this->backup_output === '') {
|
||||||
@@ -452,7 +469,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if ($this->backup->number_of_backups_locally === 0) {
|
if ($this->backup->number_of_backups_locally === 0) {
|
||||||
$deletable = $this->backup->executions()->where('status', 'success');
|
$deletable = $this->backup->executions()->where('status', 'success');
|
||||||
} else {
|
} 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) {
|
foreach ($deletable->get() as $execution) {
|
||||||
delete_backup_locally($execution->filename, $this->server);
|
delete_backup_locally($execution->filename, $this->server);
|
||||||
@@ -477,12 +494,15 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
} else {
|
} else {
|
||||||
$network = $this->database->destination->network;
|
$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 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}/";
|
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/";
|
||||||
instant_remote_process($commands, $this->server);
|
instant_remote_process($commands, $this->server);
|
||||||
$this->add_to_backup_output('Uploaded to S3.');
|
$this->add_to_backup_output('Uploaded to S3.');
|
||||||
ray('Uploaded to S3. '.$this->backup_location.' to s3://'.$bucket.$this->backup_dir);
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->add_to_backup_output($e->getMessage());
|
$this->add_to_backup_output($e->getMessage());
|
||||||
throw $e;
|
throw $e;
|
||||||
@@ -491,4 +511,40 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
instant_remote_process([$command], $this->server);
|
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\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
@@ -17,21 +18,33 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
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 __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
|
public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (! $this->server->isFunctional()) {
|
if (! $this->server->isFunctional()) {
|
||||||
return;
|
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);
|
Log::info('DockerCleanupJob force cleanup on '.$this->server->name);
|
||||||
CleanupDocker::run(server: $this->server, force: true);
|
CleanupDocker::run(server: $this->server);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -39,12 +52,12 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->usageBefore = $this->server->getDiskUsage();
|
$this->usageBefore = $this->server->getDiskUsage();
|
||||||
if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) {
|
if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) {
|
||||||
Log::info('DockerCleanupJob force cleanup on '.$this->server->name);
|
Log::info('DockerCleanupJob force cleanup on '.$this->server->name);
|
||||||
CleanupDocker::run(server: $this->server, force: true);
|
CleanupDocker::run(server: $this->server);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) {
|
if ($this->usageBefore >= $this->server->settings->docker_cleanup_threshold) {
|
||||||
CleanupDocker::run(server: $this->server, force: false);
|
CleanupDocker::run(server: $this->server);
|
||||||
$usageAfter = $this->server->getDiskUsage();
|
$usageAfter = $this->server->getDiskUsage();
|
||||||
if ($usageAfter < $this->usageBefore) {
|
if ($usageAfter < $this->usageBefore) {
|
||||||
$this->server->team?->notify(new DockerCleanup($this->server, 'Saved '.($this->usageBefore - $usageAfter).'% disk space.'));
|
$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);
|
Log::info('No need to clean up '.$this->server->name);
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
ray($e->getMessage());
|
CleanupDocker::run(server: $this->server);
|
||||||
|
Log::error('DockerCleanupJob failed: '.$e->getMessage());
|
||||||
throw $e;
|
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;
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
@@ -10,6 +11,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
|||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
|
class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
{
|
{
|
||||||
@@ -32,10 +34,20 @@ class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
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');
|
$helperImage = config('coolify.helper_image');
|
||||||
ray("Pulling {$helperImage}");
|
instant_remote_process(["docker pull -q {$helperImage}:{$latest_version}"], $this->server);
|
||||||
instant_remote_process(["docker pull -q {$helperImage}"], $this->server, false);
|
$settings->update(['helper_version' => $latest_version]);
|
||||||
ray('PullHelperImageJob done');
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
send_internal_notification('PullHelperImageJob failed with: '.$e->getMessage());
|
send_internal_notification('PullHelperImageJob failed with: '.$e->getMessage());
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ class ScheduledTaskJob implements ShouldQueue
|
|||||||
|
|
||||||
public array $containers = [];
|
public array $containers = [];
|
||||||
|
|
||||||
|
public string $server_timezone;
|
||||||
|
|
||||||
public function __construct($task)
|
public function __construct($task)
|
||||||
{
|
{
|
||||||
$this->task = $task;
|
$this->task = $task;
|
||||||
@@ -47,6 +49,19 @@ class ScheduledTaskJob implements ShouldQueue
|
|||||||
throw new \RuntimeException('ScheduledTaskJob failed: No resource found.');
|
throw new \RuntimeException('ScheduledTaskJob failed: No resource found.');
|
||||||
}
|
}
|
||||||
$this->team = Team::find($task->team_id);
|
$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
|
public function middleware(): array
|
||||||
@@ -61,6 +76,7 @@ class ScheduledTaskJob implements ShouldQueue
|
|||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->task_log = ScheduledTaskExecution::create([
|
$this->task_log = ScheduledTaskExecution::create([
|
||||||
'scheduled_task_id' => $this->task->id,
|
'scheduled_task_id' => $this->task->id,
|
||||||
@@ -78,12 +94,12 @@ class ScheduledTaskJob implements ShouldQueue
|
|||||||
} elseif ($this->resource->type() == 'service') {
|
} elseif ($this->resource->type() == 'service') {
|
||||||
$this->resource->applications()->get()->each(function ($application) {
|
$this->resource->applications()->get()->each(function ($application) {
|
||||||
if (str(data_get($application, 'status'))->contains('running')) {
|
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) {
|
$this->resource->databases()->get()->each(function ($database) {
|
||||||
if (str(data_get($database, 'status'))->contains('running')) {
|
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) {
|
foreach ($this->containers as $containerName) {
|
||||||
if (count($this->containers) == 1 || str_starts_with($containerName, $this->task->container.'-'.$this->resource->uuid)) {
|
if (count($this->containers) == 1 || str_starts_with($containerName, $this->task->container . '-' . $this->resource->uuid)) {
|
||||||
$cmd = "sh -c '".str_replace("'", "'\''", $this->task->command)."'";
|
$cmd = "sh -c '" . str_replace("'", "'\''", $this->task->command) . "'";
|
||||||
$exec = "docker exec {$containerName} {$cmd}";
|
$exec = "docker exec {$containerName} {$cmd}";
|
||||||
$this->task_output = instant_remote_process([$exec], $this->server, true);
|
$this->task_output = instant_remote_process([$exec], $this->server, true);
|
||||||
$this->task_log->update([
|
$this->task_log->update([
|
||||||
@@ -121,6 +137,7 @@ class ScheduledTaskJob implements ShouldQueue
|
|||||||
$this->team?->notify(new TaskFailed($this->task, $e->getMessage()));
|
$this->team?->notify(new TaskFailed($this->task, $e->getMessage()));
|
||||||
// send_internal_notification('ScheduledTaskJob failed with: ' . $e->getMessage());
|
// send_internal_notification('ScheduledTaskJob failed with: ' . $e->getMessage());
|
||||||
throw $e;
|
throw $e;
|
||||||
|
} finally {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public $tries = 3;
|
public $tries = 3;
|
||||||
|
|
||||||
|
public $timeout = 60;
|
||||||
|
|
||||||
public $containers;
|
public $containers;
|
||||||
|
|
||||||
public $applications;
|
public $applications;
|
||||||
@@ -43,15 +45,15 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public function __construct(public Server $server) {}
|
public function __construct(public Server $server) {}
|
||||||
|
|
||||||
// public function middleware(): array
|
public function middleware(): array
|
||||||
// {
|
{
|
||||||
// return [(new WithoutOverlapping($this->server->uuid))];
|
return [(new WithoutOverlapping($this->server->uuid))];
|
||||||
// }
|
}
|
||||||
|
|
||||||
// public function uniqueId(): int
|
public function uniqueId(): int
|
||||||
// {
|
{
|
||||||
// return $this->server->uuid;
|
return $this->server->uuid;
|
||||||
// }
|
}
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
@@ -79,7 +81,6 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
}
|
}
|
||||||
GetContainersStatus::run($this->server, $this->containers, $containerReplicates);
|
GetContainersStatus::run($this->server, $this->containers, $containerReplicates);
|
||||||
$this->checkLogDrainContainer();
|
$this->checkLogDrainContainer();
|
||||||
$this->checkSentinel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
} 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()
|
private function serverStatus()
|
||||||
{
|
{
|
||||||
['uptime' => $uptime] = $this->server->validateConnection();
|
['uptime' => $uptime] = $this->server->validateConnection();
|
||||||
@@ -140,6 +126,9 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
private function checkLogDrainContainer()
|
private function checkLogDrainContainer()
|
||||||
{
|
{
|
||||||
|
if (! $this->server->isLogDrainEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
$foundLogDrainContainer = $this->containers->filter(function ($value, $key) {
|
$foundLogDrainContainer = $this->containers->filter(function ($value, $key) {
|
||||||
return data_get($value, 'Name') === '/coolify-log-drain';
|
return data_get($value, 'Name') === '/coolify-log-drain';
|
||||||
})->first();
|
})->first();
|
||||||
|
|||||||
@@ -73,6 +73,8 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
$this->privateKeyName = generate_random_name();
|
$this->privateKeyName = generate_random_name();
|
||||||
$this->remoteServerName = generate_random_name();
|
$this->remoteServerName = generate_random_name();
|
||||||
|
$this->remoteServerPort = $this->remoteServerPort;
|
||||||
|
$this->remoteServerUser = $this->remoteServerUser;
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
$this->privateKey = '-----BEGIN OPENSSH PRIVATE KEY-----
|
$this->privateKey = '-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||||
@@ -154,6 +156,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
$this->servers = Server::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
|
$this->servers = Server::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
|
||||||
if ($this->servers->count() > 0) {
|
if ($this->servers->count() > 0) {
|
||||||
$this->selectedExistingServer = $this->servers->first()->id;
|
$this->selectedExistingServer = $this->servers->first()->id;
|
||||||
|
$this->updateServerDetails();
|
||||||
$this->currentState = 'select-existing-server';
|
$this->currentState = 'select-existing-server';
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -173,9 +176,18 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
}
|
}
|
||||||
$this->selectedExistingPrivateKey = $this->createdServer->privateKey->id;
|
$this->selectedExistingPrivateKey = $this->createdServer->privateKey->id;
|
||||||
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
|
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
|
||||||
|
$this->updateServerDetails();
|
||||||
$this->currentState = 'validate-server';
|
$this->currentState = 'validate-server';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function updateServerDetails()
|
||||||
|
{
|
||||||
|
if ($this->createdServer) {
|
||||||
|
$this->remoteServerPort = $this->createdServer->port;
|
||||||
|
$this->remoteServerUser = $this->createdServer->user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function getProxyType()
|
public function getProxyType()
|
||||||
{
|
{
|
||||||
// Set Default Proxy Type
|
// Set Default Proxy Type
|
||||||
@@ -235,11 +247,12 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
public function saveServer()
|
public function saveServer()
|
||||||
{
|
{
|
||||||
$this->validate([
|
$this->validate([
|
||||||
'remoteServerName' => 'required',
|
'remoteServerName' => 'required|string',
|
||||||
'remoteServerHost' => 'required',
|
'remoteServerHost' => 'required|string',
|
||||||
'remoteServerPort' => 'required|integer',
|
'remoteServerPort' => 'required|integer',
|
||||||
'remoteServerUser' => 'required',
|
'remoteServerUser' => 'required|string',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->privateKey = formatPrivateKey($this->privateKey);
|
$this->privateKey = formatPrivateKey($this->privateKey);
|
||||||
$foundServer = Server::whereIp($this->remoteServerHost)->first();
|
$foundServer = Server::whereIp($this->remoteServerHost)->first();
|
||||||
if ($foundServer) {
|
if ($foundServer) {
|
||||||
@@ -277,9 +290,12 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
$this->createdServer->settings()->update([
|
$this->createdServer->settings()->update([
|
||||||
'is_reachable' => true,
|
'is_reachable' => true,
|
||||||
]);
|
]);
|
||||||
|
$this->serverReachable = true;
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->serverReachable = false;
|
$this->serverReachable = false;
|
||||||
$this->createdServer->delete();
|
$this->createdServer->settings()->update([
|
||||||
|
'is_reachable' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
return handleError(error: $e, livewire: $this);
|
return handleError(error: $e, livewire: $this);
|
||||||
}
|
}
|
||||||
@@ -296,6 +312,10 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
]);
|
]);
|
||||||
$this->getProxyType();
|
$this->getProxyType();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
$this->createdServer->settings()->update([
|
||||||
|
'is_usable' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
return handleError(error: $e, livewire: $this);
|
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()
|
private function createNewPrivateKey()
|
||||||
{
|
{
|
||||||
$this->privateKeyName = generate_random_name();
|
$this->privateKeyName = generate_random_name();
|
||||||
|
|||||||
@@ -50,15 +50,6 @@ class Dashboard extends Component
|
|||||||
])->sortBy('id')->groupBy('server_name')->toArray();
|
])->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()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.dashboard');
|
return view('livewire.dashboard');
|
||||||
|
|||||||
@@ -66,9 +66,9 @@ class Advanced extends Component
|
|||||||
$this->dispatch('resetDefaultLabels', false);
|
$this->dispatch('resetDefaultLabels', false);
|
||||||
}
|
}
|
||||||
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
||||||
$this->application->parseRawCompose();
|
$this->application->oldRawParser();
|
||||||
} else {
|
} else {
|
||||||
$this->application->parseCompose();
|
$this->application->parse();
|
||||||
}
|
}
|
||||||
$this->application->settings->save();
|
$this->application->settings->save();
|
||||||
$this->dispatch('success', 'Settings saved.');
|
$this->dispatch('success', 'Settings saved.');
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Application\Deployment;
|
|||||||
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Show extends 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()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.application.deployment.show');
|
return view('livewire.project.application.deployment.show');
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ class General extends Component
|
|||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->parsedServices = $this->application->parseCompose();
|
$this->parsedServices = $this->application->parse();
|
||||||
if (is_null($this->parsedServices) || empty($this->parsedServices)) {
|
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.');
|
$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
|
// Must reload the application to get the latest database changes
|
||||||
// Why? Not sure, but it works.
|
// Why? Not sure, but it works.
|
||||||
$this->application->refresh();
|
// $this->application->refresh();
|
||||||
|
|
||||||
['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation] = $this->application->loadComposeFile($isInit);
|
['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation] = $this->application->loadComposeFile($isInit);
|
||||||
if (is_null($this->parsedServices)) {
|
if (is_null($this->parsedServices)) {
|
||||||
@@ -204,7 +204,7 @@ class General extends Component
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$compose = $this->application->parseCompose();
|
$this->application->parse();
|
||||||
$this->dispatch('success', 'Docker compose file loaded.');
|
$this->dispatch('success', 'Docker compose file loaded.');
|
||||||
$this->dispatch('compose_loaded');
|
$this->dispatch('compose_loaded');
|
||||||
$this->dispatch('refreshStorages');
|
$this->dispatch('refreshStorages');
|
||||||
|
|||||||
@@ -79,8 +79,15 @@ class Previews extends Component
|
|||||||
|
|
||||||
return;
|
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);
|
$url = Url::fromString($fqdn);
|
||||||
$template = $this->application->preview_url_template;
|
$template = $this->application->preview_url_template;
|
||||||
$host = $url->getHost();
|
$host = $url->getHost();
|
||||||
|
|||||||
@@ -8,9 +8,8 @@ use Livewire\Component;
|
|||||||
class BackupExecutions extends Component
|
class BackupExecutions extends Component
|
||||||
{
|
{
|
||||||
public ?ScheduledDatabaseBackup $backup = null;
|
public ?ScheduledDatabaseBackup $backup = null;
|
||||||
|
public $database;
|
||||||
public $executions = [];
|
public $executions = [];
|
||||||
|
|
||||||
public $setDeletableBackup;
|
public $setDeletableBackup;
|
||||||
|
|
||||||
public function getListeners()
|
public function getListeners()
|
||||||
@@ -58,7 +57,53 @@ class BackupExecutions extends Component
|
|||||||
public function refreshBackupExecutions(): void
|
public function refreshBackupExecutions(): void
|
||||||
{
|
{
|
||||||
if ($this->backup) {
|
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.is_public' => 'nullable|boolean',
|
||||||
'database.public_port' => 'nullable|integer',
|
'database.public_port' => 'nullable|integer',
|
||||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||||
|
'database.custom_docker_run_options' => 'nullable',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
@@ -42,6 +43,7 @@ class General extends Component
|
|||||||
'database.ports_mappings' => 'Port Mapping',
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
'database.is_public' => 'Is Public',
|
'database.is_public' => 'Is Public',
|
||||||
'database.public_port' => 'Public Port',
|
'database.public_port' => 'Public Port',
|
||||||
|
'database.custom_docker_run_options' => 'Custom Docker Run Options',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -54,7 +56,7 @@ class General extends Component
|
|||||||
public function instantSaveAdvanced()
|
public function instantSaveAdvanced()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (! $this->server->isLogDrainEnabled()) {
|
if (!$this->server->isLogDrainEnabled()) {
|
||||||
$this->database->is_log_drain_enabled = false;
|
$this->database->is_log_drain_enabled = false;
|
||||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
$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()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
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->dispatch('error', 'Public port is required.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
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->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
@@ -93,7 +95,7 @@ class General extends Component
|
|||||||
$this->db_url_public = $this->database->external_db_url;
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
$this->database->is_public = !$this->database->is_public;
|
||||||
|
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ class General extends Component
|
|||||||
'database.is_public' => 'nullable|boolean',
|
'database.is_public' => 'nullable|boolean',
|
||||||
'database.public_port' => 'nullable|integer',
|
'database.public_port' => 'nullable|integer',
|
||||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||||
|
'database.custom_docker_run_options' => 'nullable',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
@@ -40,6 +41,7 @@ class General extends Component
|
|||||||
'database.ports_mappings' => 'Port Mapping',
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
'database.is_public' => 'Is Public',
|
'database.is_public' => 'Is Public',
|
||||||
'database.public_port' => 'Public Port',
|
'database.public_port' => 'Public Port',
|
||||||
|
'database.custom_docker_run_options' => 'Custom Docker Run Options',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -52,7 +54,7 @@ class General extends Component
|
|||||||
public function instantSaveAdvanced()
|
public function instantSaveAdvanced()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (! $this->server->isLogDrainEnabled()) {
|
if (!$this->server->isLogDrainEnabled()) {
|
||||||
$this->database->is_log_drain_enabled = false;
|
$this->database->is_log_drain_enabled = false;
|
||||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
$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()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
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->dispatch('error', 'Public port is required.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
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->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
@@ -108,7 +110,7 @@ class General extends Component
|
|||||||
$this->db_url_public = $this->database->external_db_url;
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
$this->database->is_public = !$this->database->is_public;
|
||||||
|
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class General extends Component
|
|||||||
'database.is_public' => 'nullable|boolean',
|
'database.is_public' => 'nullable|boolean',
|
||||||
'database.public_port' => 'nullable|integer',
|
'database.public_port' => 'nullable|integer',
|
||||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||||
|
'database.custom_docker_run_options' => 'nullable',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
@@ -42,6 +43,7 @@ class General extends Component
|
|||||||
'database.ports_mappings' => 'Port Mapping',
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
'database.is_public' => 'Is Public',
|
'database.is_public' => 'Is Public',
|
||||||
'database.public_port' => 'Public Port',
|
'database.public_port' => 'Public Port',
|
||||||
|
'database.custom_docker_run_options' => 'Custom Docker Run Options',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -55,7 +57,7 @@ class General extends Component
|
|||||||
public function instantSaveAdvanced()
|
public function instantSaveAdvanced()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (! $this->server->isLogDrainEnabled()) {
|
if (!$this->server->isLogDrainEnabled()) {
|
||||||
$this->database->is_log_drain_enabled = false;
|
$this->database->is_log_drain_enabled = false;
|
||||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
$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()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
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->dispatch('error', 'Public port is required.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
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->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
@@ -114,7 +116,7 @@ class General extends Component
|
|||||||
$this->db_url_public = $this->database->external_db_url;
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
$this->database->is_public = !$this->database->is_public;
|
||||||
|
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class General extends Component
|
|||||||
'database.is_public' => 'nullable|boolean',
|
'database.is_public' => 'nullable|boolean',
|
||||||
'database.public_port' => 'nullable|integer',
|
'database.public_port' => 'nullable|integer',
|
||||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||||
|
'database.custom_docker_run_options' => 'nullable',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
@@ -48,6 +49,7 @@ class General extends Component
|
|||||||
'database.ports_mappings' => 'Port Mapping',
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
'database.is_public' => 'Is Public',
|
'database.is_public' => 'Is Public',
|
||||||
'database.public_port' => 'Public Port',
|
'database.public_port' => 'Public Port',
|
||||||
|
'database.custom_docker_run_options' => 'Custom Docker Options',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -61,7 +63,7 @@ class General extends Component
|
|||||||
public function instantSaveAdvanced()
|
public function instantSaveAdvanced()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (! $this->server->isLogDrainEnabled()) {
|
if (!$this->server->isLogDrainEnabled()) {
|
||||||
$this->database->is_log_drain_enabled = false;
|
$this->database->is_log_drain_enabled = false;
|
||||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
$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()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
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->dispatch('error', 'Public port is required.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
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->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
@@ -120,7 +122,7 @@ class General extends Component
|
|||||||
$this->db_url_public = $this->database->external_db_url;
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
$this->database->is_public = !$this->database->is_public;
|
||||||
|
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class General extends Component
|
|||||||
'database.is_public' => 'nullable|boolean',
|
'database.is_public' => 'nullable|boolean',
|
||||||
'database.public_port' => 'nullable|integer',
|
'database.public_port' => 'nullable|integer',
|
||||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||||
|
'database.custom_docker_run_options' => 'nullable',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
@@ -46,6 +47,7 @@ class General extends Component
|
|||||||
'database.ports_mappings' => 'Port Mapping',
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
'database.is_public' => 'Is Public',
|
'database.is_public' => 'Is Public',
|
||||||
'database.public_port' => 'Public Port',
|
'database.public_port' => 'Public Port',
|
||||||
|
'database.custom_docker_run_options' => 'Custom Docker Run Options',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -59,7 +61,7 @@ class General extends Component
|
|||||||
public function instantSaveAdvanced()
|
public function instantSaveAdvanced()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (! $this->server->isLogDrainEnabled()) {
|
if (!$this->server->isLogDrainEnabled()) {
|
||||||
$this->database->is_log_drain_enabled = false;
|
$this->database->is_log_drain_enabled = false;
|
||||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
$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()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
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->dispatch('error', 'Public port is required.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
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->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
@@ -121,7 +123,7 @@ class General extends Component
|
|||||||
$this->db_url_public = $this->database->external_db_url;
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
$this->database->is_public = !$this->database->is_public;
|
||||||
|
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class General extends Component
|
|||||||
'database.is_public' => 'nullable|boolean',
|
'database.is_public' => 'nullable|boolean',
|
||||||
'database.public_port' => 'nullable|integer',
|
'database.public_port' => 'nullable|integer',
|
||||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||||
|
'database.custom_docker_run_options' => 'nullable',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
@@ -48,6 +49,7 @@ class General extends Component
|
|||||||
'database.ports_mappings' => 'Port Mapping',
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
'database.is_public' => 'Is Public',
|
'database.is_public' => 'Is Public',
|
||||||
'database.public_port' => 'Public Port',
|
'database.public_port' => 'Public Port',
|
||||||
|
'database.custom_docker_run_options' => 'Custom Docker Run Options',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -60,7 +62,7 @@ class General extends Component
|
|||||||
public function instantSaveAdvanced()
|
public function instantSaveAdvanced()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (! $this->server->isLogDrainEnabled()) {
|
if (!$this->server->isLogDrainEnabled()) {
|
||||||
$this->database->is_log_drain_enabled = false;
|
$this->database->is_log_drain_enabled = false;
|
||||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
$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()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
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->dispatch('error', 'Public port is required.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
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->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
@@ -119,7 +121,7 @@ class General extends Component
|
|||||||
$this->db_url_public = $this->database->external_db_url;
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
$this->database->is_public = !$this->database->is_public;
|
||||||
|
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ class General extends Component
|
|||||||
'database.is_public' => 'nullable|boolean',
|
'database.is_public' => 'nullable|boolean',
|
||||||
'database.public_port' => 'nullable|integer',
|
'database.public_port' => 'nullable|integer',
|
||||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||||
|
'database.custom_docker_run_options' => 'nullable',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
@@ -65,6 +66,7 @@ class General extends Component
|
|||||||
'database.ports_mappings' => 'Port Mapping',
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
'database.is_public' => 'Is Public',
|
'database.is_public' => 'Is Public',
|
||||||
'database.public_port' => 'Public Port',
|
'database.public_port' => 'Public Port',
|
||||||
|
'database.custom_docker_run_options' => 'Custom Docker Run Options',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class General extends Component
|
|||||||
'database.is_public' => 'nullable|boolean',
|
'database.is_public' => 'nullable|boolean',
|
||||||
'database.public_port' => 'nullable|integer',
|
'database.public_port' => 'nullable|integer',
|
||||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||||
|
'database.custom_docker_run_options' => 'nullable',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
@@ -42,6 +43,7 @@ class General extends Component
|
|||||||
'database.ports_mappings' => 'Port Mapping',
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
'database.is_public' => 'Is Public',
|
'database.is_public' => 'Is Public',
|
||||||
'database.public_port' => 'Public Port',
|
'database.public_port' => 'Public Port',
|
||||||
|
'database.custom_docker_run_options' => 'Custom Docker Options',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -55,7 +57,7 @@ class General extends Component
|
|||||||
public function instantSaveAdvanced()
|
public function instantSaveAdvanced()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (! $this->server->isLogDrainEnabled()) {
|
if (!$this->server->isLogDrainEnabled()) {
|
||||||
$this->database->is_log_drain_enabled = false;
|
$this->database->is_log_drain_enabled = false;
|
||||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
$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()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
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->dispatch('error', 'Public port is required.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
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->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
@@ -108,7 +110,7 @@ class General extends Component
|
|||||||
$this->db_url_public = $this->database->external_db_url;
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
$this->database->is_public = !$this->database->is_public;
|
||||||
|
|
||||||
return handleError($e, $this);
|
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()
|
public function updatedBuildPack()
|
||||||
{
|
{
|
||||||
if ($this->build_pack === 'nixpacks') {
|
if ($this->build_pack === 'nixpacks') {
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ class Select extends Component
|
|||||||
|
|
||||||
public ?string $selectedEnvironment = null;
|
public ?string $selectedEnvironment = null;
|
||||||
|
|
||||||
|
public string $postgresql_type = 'postgres:16-alpine';
|
||||||
|
|
||||||
public ?string $existingPostgresqlUrl = null;
|
public ?string $existingPostgresqlUrl = null;
|
||||||
|
|
||||||
public ?string $search = null;
|
public ?string $search = null;
|
||||||
@@ -202,6 +204,8 @@ class Select extends Component
|
|||||||
$docker = $this->standaloneDockers->first() ?? $this->swarmDockers->first();
|
$docker = $this->standaloneDockers->first() ?? $this->swarmDockers->first();
|
||||||
if ($docker) {
|
if ($docker) {
|
||||||
$this->setDestination($docker->uuid);
|
$this->setDestination($docker->uuid);
|
||||||
|
|
||||||
|
return $this->whatToDoNext();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->current_step = 'destinations';
|
$this->current_step = 'destinations';
|
||||||
@@ -211,6 +215,28 @@ class Select extends Component
|
|||||||
{
|
{
|
||||||
$this->destination_uuid = $destination_uuid;
|
$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', [
|
return redirect()->route('project.resource.create', [
|
||||||
'project_uuid' => $this->parameters['project_uuid'],
|
'project_uuid' => $this->parameters['project_uuid'],
|
||||||
'environment_name' => $this->parameters['environment_name'],
|
'environment_name' => $this->parameters['environment_name'],
|
||||||
@@ -219,6 +245,7 @@ class Select extends Component
|
|||||||
'server_id' => $this->server_id,
|
'server_id' => $this->server_id,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function loadServers()
|
public function loadServers()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ class Create extends Component
|
|||||||
$type = str(request()->query('type'));
|
$type = str(request()->query('type'));
|
||||||
$destination_uuid = request()->query('destination');
|
$destination_uuid = request()->query('destination');
|
||||||
$server_id = request()->query('server_id');
|
$server_id = request()->query('server_id');
|
||||||
|
$database_image = request()->query('database_image');
|
||||||
|
|
||||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||||
if (! $project) {
|
if (! $project) {
|
||||||
@@ -33,7 +34,11 @@ class Create extends Component
|
|||||||
|
|
||||||
if (in_array($type, DATABASE_TYPES)) {
|
if (in_array($type, DATABASE_TYPES)) {
|
||||||
if ($type->value() === 'postgresql') {
|
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') {
|
} elseif ($type->value() === 'redis') {
|
||||||
$database = create_standalone_redis($environment->id, $destination_uuid);
|
$database = create_standalone_redis($environment->id, $destination_uuid);
|
||||||
} elseif ($type->value() === 'mongodb') {
|
} elseif ($type->value() === 'mongodb') {
|
||||||
@@ -86,18 +91,16 @@ class Create extends Component
|
|||||||
$oneClickDotEnvs->each(function ($value) use ($service) {
|
$oneClickDotEnvs->each(function ($value) use ($service) {
|
||||||
$key = str()->before($value, '=');
|
$key = str()->before($value, '=');
|
||||||
$value = str(str()->after($value, '='));
|
$value = str(str()->after($value, '='));
|
||||||
$generatedValue = $value;
|
if ($value) {
|
||||||
if ($value->contains('SERVICE_')) {
|
|
||||||
$command = $value->after('SERVICE_')->beforeLast('_');
|
|
||||||
$generatedValue = generateEnvValue($command->value(), $service);
|
|
||||||
}
|
|
||||||
EnvironmentVariable::create([
|
EnvironmentVariable::create([
|
||||||
'key' => $key,
|
'key' => $key,
|
||||||
'value' => $generatedValue,
|
'value' => $value,
|
||||||
'service_id' => $service->id,
|
'service_id' => $service->id,
|
||||||
'is_build_time' => false,
|
'is_build_time' => false,
|
||||||
'is_preview' => false,
|
'is_preview' => false,
|
||||||
]);
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
$service->parse(isNew: true);
|
$service->parse(isNew: true);
|
||||||
|
|||||||
@@ -76,6 +76,12 @@ class Configuration extends Component
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
GetContainersStatus::run($this->service->server);
|
GetContainersStatus::run($this->service->server);
|
||||||
|
$this->service->applications->each(function ($application) {
|
||||||
|
$application->refresh();
|
||||||
|
});
|
||||||
|
$this->service->databases->each(function ($database) {
|
||||||
|
$database->refresh();
|
||||||
|
});
|
||||||
$this->dispatch('$refresh');
|
$this->dispatch('$refresh');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class EditCompose extends Component
|
|||||||
{
|
{
|
||||||
$this->dispatch('info', 'Saving new docker compose...');
|
$this->dispatch('info', 'Saving new docker compose...');
|
||||||
$this->dispatch('saveCompose', $this->service->docker_compose_raw);
|
$this->dispatch('saveCompose', $this->service->docker_compose_raw);
|
||||||
|
$this->dispatch('refreshStorages');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ class FileStorage extends Component
|
|||||||
$this->fileStorage->deleteStorageOnServer();
|
$this->fileStorage->deleteStorageOnServer();
|
||||||
$this->fileStorage->is_directory = true;
|
$this->fileStorage->is_directory = true;
|
||||||
$this->fileStorage->content = null;
|
$this->fileStorage->content = null;
|
||||||
|
$this->fileStorage->is_based_on_git = false;
|
||||||
$this->fileStorage->save();
|
$this->fileStorage->save();
|
||||||
$this->fileStorage->saveStorageOnServer();
|
$this->fileStorage->saveStorageOnServer();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ class All extends Component
|
|||||||
|
|
||||||
protected $listeners = [
|
protected $listeners = [
|
||||||
'saveKey' => 'submit',
|
'saveKey' => 'submit',
|
||||||
|
'refreshEnvs',
|
||||||
'environmentVariableDeleted' => 'refreshEnvs',
|
'environmentVariableDeleted' => 'refreshEnvs',
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -52,11 +53,18 @@ class All extends Component
|
|||||||
|
|
||||||
public function sortEnvironmentVariables()
|
public function sortEnvironmentVariables()
|
||||||
{
|
{
|
||||||
|
if ($this->resource->type() === 'application') {
|
||||||
$this->resource->load(['environment_variables', 'environment_variables_preview']);
|
$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';
|
$sortBy = data_get($this->resource, 'settings.is_env_sorting_enabled') ? 'key' : 'order';
|
||||||
|
|
||||||
$sortFunction = function ($variables) use ($sortBy) {
|
$sortFunction = function ($variables) use ($sortBy) {
|
||||||
|
if (! $variables) {
|
||||||
|
return $variables;
|
||||||
|
}
|
||||||
if ($sortBy === 'key') {
|
if ($sortBy === 'key') {
|
||||||
return $variables->sortBy(function ($item) {
|
return $variables->sortBy(function ($item) {
|
||||||
return strtolower($item->key);
|
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-'))) {
|
if (! $refresh && ($this->resource?->getMorphClass() === 'App\Models\Service' || str($this->container)->contains('-pr-'))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (! $this->numberOfLines) {
|
if ($this->numberOfLines <= 0) {
|
||||||
$this->numberOfLines = 1000;
|
$this->numberOfLines = 1000;
|
||||||
}
|
}
|
||||||
if ($this->container) {
|
if ($this->container) {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class All extends Component
|
|||||||
$this->containerNames = $this->containerNames->merge($this->resource->databases()->pluck('name'));
|
$this->containerNames = $this->containerNames->merge($this->resource->databases()->pluck('name'));
|
||||||
} elseif ($this->resource->type() == 'application') {
|
} elseif ($this->resource->type() == 'application') {
|
||||||
if ($this->resource->build_pack === 'dockercompose') {
|
if ($this->resource->build_pack === 'dockercompose') {
|
||||||
$parsed = $this->resource->parseCompose();
|
$parsed = $this->resource->parse();
|
||||||
$containers = collect(data_get($parsed, 'services'))->keys();
|
$containers = collect(data_get($parsed, 'services'))->keys();
|
||||||
$this->containerNames = $containers;
|
$this->containerNames = $containers;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ use Livewire\Component;
|
|||||||
class Executions extends Component
|
class Executions extends Component
|
||||||
{
|
{
|
||||||
public $executions = [];
|
public $executions = [];
|
||||||
|
|
||||||
public $selectedKey;
|
public $selectedKey;
|
||||||
|
public $task;
|
||||||
|
|
||||||
public function getListeners()
|
public function getListeners()
|
||||||
{
|
{
|
||||||
@@ -26,4 +26,44 @@ class Executions extends Component
|
|||||||
}
|
}
|
||||||
$this->selectedKey = $key;
|
$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 ?string $wildcard_domain = null;
|
||||||
|
|
||||||
public int $cleanup_after_percentage;
|
|
||||||
|
|
||||||
public bool $dockerInstallationStarted = false;
|
public bool $dockerInstallationStarted = false;
|
||||||
|
|
||||||
public bool $revalidate = false;
|
public bool $revalidate = false;
|
||||||
|
|
||||||
|
public $timezones;
|
||||||
|
|
||||||
protected $listeners = ['serverInstalled', 'revalidate' => '$refresh'];
|
protected $listeners = ['serverInstalled', 'revalidate' => '$refresh'];
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
@@ -37,7 +37,6 @@ class Form extends Component
|
|||||||
'server.settings.is_swarm_manager' => 'required|boolean',
|
'server.settings.is_swarm_manager' => 'required|boolean',
|
||||||
'server.settings.is_swarm_worker' => 'required|boolean',
|
'server.settings.is_swarm_worker' => 'required|boolean',
|
||||||
'server.settings.is_build_server' => 'required|boolean',
|
'server.settings.is_build_server' => 'required|boolean',
|
||||||
'server.settings.is_force_cleanup_enabled' => 'required|boolean',
|
|
||||||
'server.settings.concurrent_builds' => 'required|integer|min:1',
|
'server.settings.concurrent_builds' => 'required|integer|min:1',
|
||||||
'server.settings.dynamic_timeout' => 'required|integer|min:1',
|
'server.settings.dynamic_timeout' => 'required|integer|min:1',
|
||||||
'server.settings.is_metrics_enabled' => 'required|boolean',
|
'server.settings.is_metrics_enabled' => 'required|boolean',
|
||||||
@@ -46,6 +45,10 @@ class Form extends Component
|
|||||||
'server.settings.metrics_history_days' => 'required|integer|min:1',
|
'server.settings.metrics_history_days' => 'required|integer|min:1',
|
||||||
'wildcard_domain' => 'nullable|url',
|
'wildcard_domain' => 'nullable|url',
|
||||||
'server.settings.is_server_api_enabled' => 'required|boolean',
|
'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 = [
|
protected $validationAttributes = [
|
||||||
@@ -66,12 +69,27 @@ class Form extends Component
|
|||||||
'server.settings.metrics_refresh_rate_seconds' => 'Metrics Interval',
|
'server.settings.metrics_refresh_rate_seconds' => 'Metrics Interval',
|
||||||
'server.settings.metrics_history_days' => 'Metrics History',
|
'server.settings.metrics_history_days' => 'Metrics History',
|
||||||
'server.settings.is_server_api_enabled' => 'Server API',
|
'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->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()
|
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) {
|
if ($this->server->settings->isDirty('is_server_api_enabled') && $this->server->settings->is_server_api_enabled === true) {
|
||||||
ray('Starting sentinel');
|
ray('Starting sentinel');
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ray('Sentinel is not enabled');
|
ray('Sentinel is not enabled');
|
||||||
@@ -172,6 +189,7 @@ class Form extends Component
|
|||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
if (isCloud() && ! isDev()) {
|
if (isCloud() && ! isDev()) {
|
||||||
$this->validate();
|
$this->validate();
|
||||||
$this->validate([
|
$this->validate([
|
||||||
@@ -190,9 +208,30 @@ class Form extends Component
|
|||||||
}
|
}
|
||||||
refresh_server_connection($this->server->privateKey);
|
refresh_server_connection($this->server->privateKey);
|
||||||
$this->server->settings->wildcard_domain = $this->wildcard_domain;
|
$this->server->settings->wildcard_domain = $this->wildcard_domain;
|
||||||
$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->settings->save();
|
||||||
$this->server->save();
|
$this->server->save();
|
||||||
$this->dispatch('success', 'Server updated.');
|
$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',
|
'settings.is_auto_update_enabled' => 'boolean',
|
||||||
'auto_update_frequency' => 'string',
|
'auto_update_frequency' => 'string',
|
||||||
'update_check_frequency' => 'string',
|
'update_check_frequency' => 'string',
|
||||||
|
'settings.instance_timezone' => 'required|string|timezone',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
@@ -54,6 +55,8 @@ class Index extends Component
|
|||||||
'update_check_frequency' => 'Update Check Frequency',
|
'update_check_frequency' => 'Update Check Frequency',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public $timezones;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
if (isInstanceAdmin()) {
|
if (isInstanceAdmin()) {
|
||||||
@@ -65,6 +68,7 @@ class Index extends Component
|
|||||||
$this->is_api_enabled = $this->settings->is_api_enabled;
|
$this->is_api_enabled = $this->settings->is_api_enabled;
|
||||||
$this->auto_update_frequency = $this->settings->auto_update_frequency;
|
$this->auto_update_frequency = $this->settings->auto_update_frequency;
|
||||||
$this->update_check_frequency = $this->settings->update_check_frequency;
|
$this->update_check_frequency = $this->settings->update_check_frequency;
|
||||||
|
$this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray();
|
||||||
} else {
|
} else {
|
||||||
return redirect()->route('dashboard');
|
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()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.settings.index');
|
return view('livewire.settings.index');
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace App\Livewire;
|
|||||||
|
|
||||||
use App\Actions\Server\UpdateCoolify;
|
use App\Actions\Server\UpdateCoolify;
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use Illuminate\Support\Facades\Http;
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Upgrade extends Component
|
class Upgrade extends Component
|
||||||
@@ -22,13 +21,8 @@ class Upgrade extends Component
|
|||||||
public function checkUpdate()
|
public function checkUpdate()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$settings = InstanceSettings::get();
|
$this->latestVersion = get_latest_version_of_coolify();
|
||||||
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
$this->isUpgradeAvailable = data_get(InstanceSettings::get(), 'new_version_available', false);
|
||||||
if ($response->successful()) {
|
|
||||||
$versions = $response->json();
|
|
||||||
$this->latestVersion = data_get($versions, 'coolify.v4.version');
|
|
||||||
}
|
|
||||||
$this->isUpgradeAvailable = $settings->new_version_available;
|
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
|||||||
@@ -102,6 +102,8 @@ class Application extends BaseModel
|
|||||||
{
|
{
|
||||||
use SoftDeletes;
|
use SoftDeletes;
|
||||||
|
|
||||||
|
private static $parserVersion = '3';
|
||||||
|
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
|
||||||
protected $appends = ['server_status'];
|
protected $appends = ['server_status'];
|
||||||
@@ -125,7 +127,7 @@ class Application extends BaseModel
|
|||||||
ApplicationSetting::create([
|
ApplicationSetting::create([
|
||||||
'application_id' => $application->id,
|
'application_id' => $application->id,
|
||||||
]);
|
]);
|
||||||
$application->compose_parsing_version = '2';
|
$application->compose_parsing_version = self::$parserVersion;
|
||||||
$application->save();
|
$application->save();
|
||||||
});
|
});
|
||||||
static::forceDeleting(function ($application) {
|
static::forceDeleting(function ($application) {
|
||||||
@@ -138,6 +140,7 @@ class Application extends BaseModel
|
|||||||
$task->delete();
|
$task->delete();
|
||||||
}
|
}
|
||||||
$application->tags()->detach();
|
$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
|
public function baseDirectory(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@@ -1040,7 +1026,7 @@ class Application extends BaseModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function parseRawCompose()
|
public function oldRawParser()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$yaml = Yaml::parse($this->docker_compose_raw);
|
$yaml = Yaml::parse($this->docker_compose_raw);
|
||||||
@@ -1100,9 +1086,11 @@ class Application extends BaseModel
|
|||||||
instant_remote_process($commands, $this->destination->server, false);
|
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);
|
return parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, preview_id: $preview_id);
|
||||||
} else {
|
} else {
|
||||||
return collect([]);
|
return collect([]);
|
||||||
@@ -1154,7 +1142,7 @@ class Application extends BaseModel
|
|||||||
if ($composeFileContent) {
|
if ($composeFileContent) {
|
||||||
$this->docker_compose_raw = $composeFileContent;
|
$this->docker_compose_raw = $composeFileContent;
|
||||||
$this->save();
|
$this->save();
|
||||||
$parsedServices = $this->parseCompose();
|
$parsedServices = $this->parse();
|
||||||
if ($this->docker_compose_domains) {
|
if ($this->docker_compose_domains) {
|
||||||
$json = collect(json_decode($this->docker_compose_domains));
|
$json = collect(json_decode($this->docker_compose_domains));
|
||||||
$names = collect(data_get($parsedServices, 'services'))->keys()->toArray();
|
$names = collect(data_get($parsedServices, 'services'))->keys()->toArray();
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ class ApplicationPreview extends BaseModel
|
|||||||
protected static function booted()
|
protected static function booted()
|
||||||
{
|
{
|
||||||
static::deleting(function ($preview) {
|
static::deleting(function ($preview) {
|
||||||
if ($preview->application->build_pack === 'dockercompose') {
|
if (data_get($preview, 'application.build_pack') === 'dockercompose') {
|
||||||
$server = $preview->application->destination->server;
|
$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');
|
$volumes = data_get($composeFile, 'volumes');
|
||||||
$networks = data_get($composeFile, 'networks');
|
$networks = data_get($composeFile, 'networks');
|
||||||
$networkKeys = collect($networks)->keys();
|
$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()
|
public static function get()
|
||||||
{
|
{
|
||||||
return InstanceSettings::findOrFail(0);
|
return InstanceSettings::findOrFail(0);
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ class LocalFileVolume extends BaseModel
|
|||||||
|
|
||||||
public function deleteStorageOnServer()
|
public function deleteStorageOnServer()
|
||||||
{
|
{
|
||||||
|
$this->load(['service']);
|
||||||
$isService = data_get($this->resource, 'service');
|
$isService = data_get($this->resource, 'service');
|
||||||
if ($isService) {
|
if ($isService) {
|
||||||
$workdir = $this->resource->service->workdir();
|
$workdir = $this->resource->service->workdir();
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use OpenApi\Attributes as OA;
|
|||||||
'id' => ['type' => 'integer'],
|
'id' => ['type' => 'integer'],
|
||||||
'uuid' => ['type' => 'string'],
|
'uuid' => ['type' => 'string'],
|
||||||
'name' => ['type' => 'string'],
|
'name' => ['type' => 'string'],
|
||||||
|
'description' => ['type' => 'string'],
|
||||||
'environments' => new OA\Property(
|
'environments' => new OA\Property(
|
||||||
property: 'environments',
|
property: 'environments',
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ class ScheduledDatabaseBackup extends BaseModel
|
|||||||
|
|
||||||
public function executions(): HasMany
|
public function executions(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(ScheduledDatabaseBackupExecution::class);
|
// Last execution first
|
||||||
|
return $this->hasMany(ScheduledDatabaseBackupExecution::class)->orderBy('created_at', 'desc');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function s3()
|
public function s3()
|
||||||
@@ -34,4 +35,14 @@ class ScheduledDatabaseBackup extends BaseModel
|
|||||||
{
|
{
|
||||||
return $this->hasMany(ScheduledDatabaseBackupExecution::class)->where('created_at', '>=', now()->subDays($days))->get();
|
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\HasMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
|
use App\Models\Service;
|
||||||
|
use App\Models\Application;
|
||||||
|
|
||||||
class ScheduledTask extends BaseModel
|
class ScheduledTask extends BaseModel
|
||||||
{
|
{
|
||||||
@@ -26,6 +28,28 @@ class ScheduledTask extends BaseModel
|
|||||||
|
|
||||||
public function executions(): HasMany
|
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',
|
'proxy',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'ip',
|
||||||
|
'port',
|
||||||
|
'user',
|
||||||
|
'description',
|
||||||
|
'private_key_id',
|
||||||
|
'team_id',
|
||||||
|
];
|
||||||
|
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
|
||||||
public static function isReachable()
|
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);
|
return instant_remote_process(["df /| tail -1 | awk '{ print $5}' | sed 's/%//g'"], $this, false);
|
||||||
}
|
}
|
||||||
@@ -909,7 +919,7 @@ $schema://$host {
|
|||||||
|
|
||||||
public function muxFilename()
|
public function muxFilename()
|
||||||
{
|
{
|
||||||
return "{$this->ip}_{$this->port}_{$this->user}";
|
return $this->uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function team()
|
public function team()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use OpenApi\Attributes as OA;
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
@@ -10,10 +11,10 @@ use OpenApi\Attributes as OA;
|
|||||||
type: 'object',
|
type: 'object',
|
||||||
properties: [
|
properties: [
|
||||||
'id' => ['type' => 'integer'],
|
'id' => ['type' => 'integer'],
|
||||||
'cleanup_after_percentage' => ['type' => 'integer'],
|
|
||||||
'concurrent_builds' => ['type' => 'integer'],
|
'concurrent_builds' => ['type' => 'integer'],
|
||||||
'dynamic_timeout' => ['type' => 'integer'],
|
'dynamic_timeout' => ['type' => 'integer'],
|
||||||
'force_disabled' => ['type' => 'boolean'],
|
'force_disabled' => ['type' => 'boolean'],
|
||||||
|
'force_server_cleanup' => ['type' => 'boolean'],
|
||||||
'is_build_server' => ['type' => 'boolean'],
|
'is_build_server' => ['type' => 'boolean'],
|
||||||
'is_cloudflare_tunnel' => ['type' => 'boolean'],
|
'is_cloudflare_tunnel' => ['type' => 'boolean'],
|
||||||
'is_jump_server' => ['type' => 'boolean'],
|
'is_jump_server' => ['type' => 'boolean'],
|
||||||
@@ -37,6 +38,8 @@ use OpenApi\Attributes as OA;
|
|||||||
'metrics_history_days' => ['type' => 'integer'],
|
'metrics_history_days' => ['type' => 'integer'],
|
||||||
'metrics_refresh_rate_seconds' => ['type' => 'integer'],
|
'metrics_refresh_rate_seconds' => ['type' => 'integer'],
|
||||||
'metrics_token' => ['type' => 'string'],
|
'metrics_token' => ['type' => 'string'],
|
||||||
|
'docker_cleanup_frequency' => ['type' => 'string'],
|
||||||
|
'docker_cleanup_threshold' => ['type' => 'integer'],
|
||||||
'server_id' => ['type' => 'integer'],
|
'server_id' => ['type' => 'integer'],
|
||||||
'wildcard_domain' => ['type' => 'string'],
|
'wildcard_domain' => ['type' => 'string'],
|
||||||
'created_at' => ['type' => 'string'],
|
'created_at' => ['type' => 'string'],
|
||||||
@@ -47,8 +50,25 @@ class ServerSetting extends Model
|
|||||||
{
|
{
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'force_docker_cleanup' => 'boolean',
|
||||||
|
'docker_cleanup_threshold' => 'integer',
|
||||||
|
];
|
||||||
|
|
||||||
public function server()
|
public function server()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Server::class);
|
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\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
use OpenApi\Attributes as OA;
|
use OpenApi\Attributes as OA;
|
||||||
use Spatie\Url\Url;
|
use Spatie\Url\Url;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
#[OA\Schema(
|
#[OA\Schema(
|
||||||
description: 'Service model',
|
description: 'Service model',
|
||||||
@@ -23,7 +24,7 @@ use Symfony\Component\Yaml\Yaml;
|
|||||||
'description' => ['type' => 'string', 'description' => 'The description of the service.'],
|
'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_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.'],
|
'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.'],
|
'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.'],
|
'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.'],
|
'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;
|
use HasFactory, SoftDeletes;
|
||||||
|
|
||||||
|
private static $parserVersion = '3';
|
||||||
|
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
|
||||||
protected $appends = ['server_status'];
|
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)
|
public function isConfigurationChanged(bool $save = false)
|
||||||
{
|
{
|
||||||
$domains = $this->applications()->get()->pluck('fqdn')->sort()->toArray();
|
$domains = $this->applications()->get()->pluck('fqdn')->sort()->toArray();
|
||||||
@@ -665,6 +676,32 @@ class Service extends BaseModel
|
|||||||
|
|
||||||
$fields->put('GitLab', $data->toArray());
|
$fields->put('GitLab', $data->toArray());
|
||||||
break;
|
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();
|
$databases = $this->databases()->get();
|
||||||
@@ -711,8 +748,8 @@ class Service extends BaseModel
|
|||||||
$fields->put('PostgreSQL', $data->toArray());
|
$fields->put('PostgreSQL', $data->toArray());
|
||||||
break;
|
break;
|
||||||
case str($image)->contains('mysql'):
|
case str($image)->contains('mysql'):
|
||||||
$userVariables = ['SERVICE_USER_MYSQL', 'SERVICE_USER_WORDPRESS'];
|
$userVariables = ['SERVICE_USER_MYSQL', 'SERVICE_USER_WORDPRESS', 'MYSQL_USER'];
|
||||||
$passwordVariables = ['SERVICE_PASSWORD_MYSQL', 'SERVICE_PASSWORD_WORDPRESS'];
|
$passwordVariables = ['SERVICE_PASSWORD_MYSQL', 'SERVICE_PASSWORD_WORDPRESS', 'MYSQL_PASSWORD'];
|
||||||
$rootPasswordVariables = ['SERVICE_PASSWORD_MYSQLROOT', 'SERVICE_PASSWORD_ROOT'];
|
$rootPasswordVariables = ['SERVICE_PASSWORD_MYSQLROOT', 'SERVICE_PASSWORD_ROOT'];
|
||||||
$dbNameVariables = ['MYSQL_DATABASE'];
|
$dbNameVariables = ['MYSQL_DATABASE'];
|
||||||
$mysql_user = $this->environment_variables()->whereIn('key', $userVariables)->first();
|
$mysql_user = $this->environment_variables()->whereIn('key', $userVariables)->first();
|
||||||
@@ -761,10 +798,10 @@ class Service extends BaseModel
|
|||||||
$fields->put('MySQL', $data->toArray());
|
$fields->put('MySQL', $data->toArray());
|
||||||
break;
|
break;
|
||||||
case str($image)->contains('mariadb'):
|
case str($image)->contains('mariadb'):
|
||||||
$userVariables = ['SERVICE_USER_MARIADB', 'SERVICE_USER_WORDPRESS', '_APP_DB_USER'];
|
$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'];
|
$passwordVariables = ['SERVICE_PASSWORD_MARIADB', 'SERVICE_PASSWORD_WORDPRESS', '_APP_DB_PASS', 'MYSQL_PASSWORD'];
|
||||||
$rootPasswordVariables = ['SERVICE_PASSWORD_MARIADBROOT', 'SERVICE_PASSWORD_ROOT', '_APP_DB_ROOT_PASS'];
|
$rootPasswordVariables = ['SERVICE_PASSWORD_MARIADBROOT', 'SERVICE_PASSWORD_ROOT', '_APP_DB_ROOT_PASS', 'MYSQL_ROOT_PASSWORD'];
|
||||||
$dbNameVariables = ['SERVICE_DATABASE_MARIADB', 'SERVICE_DATABASE_WORDPRESS', '_APP_DB_SCHEMA'];
|
$dbNameVariables = ['SERVICE_DATABASE_MARIADB', 'SERVICE_DATABASE_WORDPRESS', '_APP_DB_SCHEMA', 'MYSQL_DATABASE'];
|
||||||
$mariadb_user = $this->environment_variables()->whereIn('key', $userVariables)->first();
|
$mariadb_user = $this->environment_variables()->whereIn('key', $userVariables)->first();
|
||||||
$mariadb_password = $this->environment_variables()->whereIn('key', $passwordVariables)->first();
|
$mariadb_password = $this->environment_variables()->whereIn('key', $passwordVariables)->first();
|
||||||
$mariadb_root_password = $this->environment_variables()->whereIn('key', $rootPasswordVariables)->first();
|
$mariadb_root_password = $this->environment_variables()->whereIn('key', $rootPasswordVariables)->first();
|
||||||
@@ -811,6 +848,7 @@ class Service extends BaseModel
|
|||||||
}
|
}
|
||||||
$fields->put('MariaDB', $data->toArray());
|
$fields->put('MariaDB', $data->toArray());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -945,7 +983,8 @@ class Service extends BaseModel
|
|||||||
|
|
||||||
public function environment_variables(): HasMany
|
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
|
public function environment_variables_preview(): HasMany
|
||||||
@@ -961,21 +1000,36 @@ class Service extends BaseModel
|
|||||||
public function saveComposeConfigs()
|
public function saveComposeConfigs()
|
||||||
{
|
{
|
||||||
$workdir = $this->workdir();
|
$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";
|
$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';
|
$commands[] = 'rm -f .env || true';
|
||||||
|
|
||||||
$envs_from_coolify = $this->environment_variables()->get();
|
$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";
|
$commands[] = "echo '{$env->key}={$env->real_value}' >> .env";
|
||||||
}
|
}
|
||||||
if ($envs_from_coolify->count() === 0) {
|
if ($sorted->count() === 0) {
|
||||||
$commands[] = 'touch .env';
|
$commands[] = 'touch .env';
|
||||||
}
|
}
|
||||||
instant_remote_process($commands, $this->server);
|
instant_remote_process($commands, $this->server);
|
||||||
@@ -983,7 +1037,14 @@ class Service extends BaseModel
|
|||||||
|
|
||||||
public function parse(bool $isNew = false): Collection
|
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);
|
return parseDockerComposeFile($this, $isNew);
|
||||||
|
} else {
|
||||||
|
return collect([]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function networks()
|
public function networks()
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class DockerCleanup extends Notification implements ShouldQueue
|
|||||||
// $mail->view('emails.high-disk-usage', [
|
// $mail->view('emails.high-disk-usage', [
|
||||||
// 'name' => $this->server->name,
|
// 'name' => $this->server->name,
|
||||||
// 'disk_usage' => $this->disk_usage,
|
// 'disk_usage' => $this->disk_usage,
|
||||||
// 'threshold' => $this->cleanup_after_percentage,
|
// 'threshold' => $this->docker_cleanup_threshold,
|
||||||
// ]);
|
// ]);
|
||||||
// return $mail;
|
// return $mail;
|
||||||
// }
|
// }
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class HighDiskUsage extends Notification implements ShouldQueue
|
|||||||
|
|
||||||
public $tries = 1;
|
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
|
public function via(object $notifiable): array
|
||||||
{
|
{
|
||||||
@@ -46,7 +46,7 @@ class HighDiskUsage extends Notification implements ShouldQueue
|
|||||||
$mail->view('emails.high-disk-usage', [
|
$mail->view('emails.high-disk-usage', [
|
||||||
'name' => $this->server->name,
|
'name' => $this->server->name,
|
||||||
'disk_usage' => $this->disk_usage,
|
'disk_usage' => $this->disk_usage,
|
||||||
'threshold' => $this->cleanup_after_percentage,
|
'threshold' => $this->docker_cleanup_threshold,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $mail;
|
return $mail;
|
||||||
@@ -54,7 +54,7 @@ class HighDiskUsage extends Notification implements ShouldQueue
|
|||||||
|
|
||||||
public function toDiscord(): string
|
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;
|
return $message;
|
||||||
}
|
}
|
||||||
@@ -62,7 +62,7 @@ class HighDiskUsage extends Notification implements ShouldQueue
|
|||||||
public function toTelegram(): array
|
public function toTelegram(): array
|
||||||
{
|
{
|
||||||
return [
|
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);
|
$server = Server::find($server_id);
|
||||||
$concurrent_builds = $server->settings->concurrent_builds;
|
$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) {
|
if ($deployments->count() > $concurrent_builds) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ const SUPPORTED_OS = [
|
|||||||
'centos fedora rhel ol rocky amzn almalinux',
|
'centos fedora rhel ol rocky amzn almalinux',
|
||||||
'sles opensuse-leap opensuse-tumbleweed',
|
'sles opensuse-leap opensuse-tumbleweed',
|
||||||
'arch',
|
'arch',
|
||||||
|
'alpine',
|
||||||
];
|
];
|
||||||
|
|
||||||
const SHARED_VARIABLE_TYPES = ['team', 'project', 'environment'];
|
const SHARED_VARIABLE_TYPES = ['team', 'project', 'environment'];
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ function generate_database_name(string $type): string
|
|||||||
return $type.'-database-'.$cuid;
|
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();
|
$destination = StandaloneDocker::where('uuid', $destinationUuid)->first();
|
||||||
if (! $destination) {
|
if (! $destination) {
|
||||||
@@ -27,6 +27,7 @@ function create_standalone_postgresql($environmentId, $destinationUuid, ?array $
|
|||||||
}
|
}
|
||||||
$database = new StandalonePostgresql;
|
$database = new StandalonePostgresql;
|
||||||
$database->name = generate_database_name('postgresql');
|
$database->name = generate_database_name('postgresql');
|
||||||
|
$database->image = $databaseImage;
|
||||||
$database->postgres_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
|
$database->postgres_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
|
||||||
$database->environment_id = $environmentId;
|
$database->environment_id = $environmentId;
|
||||||
$database->destination_id = $destination->id;
|
$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)
|
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;
|
$consistent_container_name = $application->settings->is_consistent_container_name_enabled;
|
||||||
$now = now()->format('Hisu');
|
$now = now()->format('Hisu');
|
||||||
if ($pull_request_id !== 0 && $pull_request_id !== null) {
|
if ($pull_request_id !== 0 && $pull_request_id !== null) {
|
||||||
@@ -251,7 +253,7 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
|
|||||||
|
|
||||||
return $payload;
|
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([]);
|
$labels = collect([]);
|
||||||
if ($serviceLabels) {
|
if ($serviceLabels) {
|
||||||
@@ -270,6 +272,9 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
|
|||||||
if (is_null($port) && ! is_null($onlyPort)) {
|
if (is_null($port) && ! is_null($onlyPort)) {
|
||||||
$port = $onlyPort;
|
$port = $onlyPort;
|
||||||
}
|
}
|
||||||
|
if (is_null($port) && $predefinedPort) {
|
||||||
|
$port = $predefinedPort;
|
||||||
|
}
|
||||||
$labels->push("caddy_{$loop}={$schema}://{$host}");
|
$labels->push("caddy_{$loop}={$schema}://{$host}");
|
||||||
$labels->push("caddy_{$loop}.header=-Server");
|
$labels->push("caddy_{$loop}.header=-Server");
|
||||||
$labels->push("caddy_{$loop}.try_files={path} /index.html /index.php");
|
$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',
|
'--sysctl',
|
||||||
'--ulimit',
|
'--ulimit',
|
||||||
'--device',
|
'--device',
|
||||||
|
'--shm-size',
|
||||||
]);
|
]);
|
||||||
$mapping = collect([
|
$mapping = collect([
|
||||||
'--cap-add' => 'cap_add',
|
'--cap-add' => 'cap_add',
|
||||||
'--cap-drop' => 'cap_drop',
|
'--cap-drop' => 'cap_drop',
|
||||||
'--security-opt' => 'security_opt',
|
'--security-opt' => 'security_opt',
|
||||||
'--sysctl' => 'sysctls',
|
'--sysctl' => 'sysctls',
|
||||||
'--ulimit' => 'ulimits',
|
|
||||||
'--device' => 'devices',
|
'--device' => 'devices',
|
||||||
'--init' => 'init',
|
'--init' => 'init',
|
||||||
'--ulimit' => 'ulimits',
|
'--ulimit' => 'ulimits',
|
||||||
'--privileged' => 'privileged',
|
'--privileged' => 'privileged',
|
||||||
'--ip' => 'ip',
|
'--ip' => 'ip',
|
||||||
|
'--shm-size' => 'shm_size',
|
||||||
]);
|
]);
|
||||||
foreach ($matches as $match) {
|
foreach ($matches as $match) {
|
||||||
$option = $match[1];
|
$option = $match[1];
|
||||||
@@ -704,6 +710,7 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
|
|||||||
$options = collect($options);
|
$options = collect($options);
|
||||||
// Easily get mappings from https://github.com/composerize/composerize/blob/master/packages/composerize/src/mappings.js
|
// Easily get mappings from https://github.com/composerize/composerize/blob/master/packages/composerize/src/mappings.js
|
||||||
foreach ($options as $option => $value) {
|
foreach ($options as $option => $value) {
|
||||||
|
// ray($option,$value);
|
||||||
if (! data_get($mapping, $option)) {
|
if (! data_get($mapping, $option)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -728,6 +735,10 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
$compose_options->put($mapping[$option], $ulimits);
|
$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 {
|
} else {
|
||||||
if ($list_options->contains($option)) {
|
if ($list_options->contains($option)) {
|
||||||
if ($compose_options->has($mapping[$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();
|
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
|
function validateComposeFile(string $compose, int $server_id): string|Throwable
|
||||||
{
|
{
|
||||||
return 'OK';
|
return 'OK';
|
||||||
|
|||||||
@@ -146,12 +146,11 @@ function generate_default_proxy_configuration(Server $server)
|
|||||||
'coolify.managed=true',
|
'coolify.managed=true',
|
||||||
];
|
];
|
||||||
$config = [
|
$config = [
|
||||||
'version' => '3.8',
|
|
||||||
'networks' => $array_of_networks->toArray(),
|
'networks' => $array_of_networks->toArray(),
|
||||||
'services' => [
|
'services' => [
|
||||||
'traefik' => [
|
'traefik' => [
|
||||||
'container_name' => 'coolify-proxy',
|
'container_name' => 'coolify-proxy',
|
||||||
'image' => 'traefik:v2.11',
|
'image' => 'traefik:v3.1',
|
||||||
'restart' => RESTART_MODE,
|
'restart' => RESTART_MODE,
|
||||||
'extra_hosts' => [
|
'extra_hosts' => [
|
||||||
'host.docker.internal:host-gateway',
|
'host.docker.internal:host-gateway',
|
||||||
|
|||||||
@@ -95,8 +95,26 @@ function generateScpCommand(Server $server, string $source, string $dest)
|
|||||||
$timeout = config('constants.ssh.command_timeout');
|
$timeout = config('constants.ssh.command_timeout');
|
||||||
$connectionTimeout = config('constants.ssh.connection_timeout');
|
$connectionTimeout = config('constants.ssh.connection_timeout');
|
||||||
$serverInterval = config('constants.ssh.server_interval');
|
$serverInterval = config('constants.ssh.server_interval');
|
||||||
|
$muxPersistTime = config('constants.ssh.mux_persist_time');
|
||||||
|
|
||||||
$scp_command = "timeout $timeout scp ";
|
$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} "
|
$scp_command .= "-i {$privateKeyLocation} "
|
||||||
.'-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null '
|
.'-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null '
|
||||||
.'-o PasswordAuthentication=no '
|
.'-o PasswordAuthentication=no '
|
||||||
@@ -145,9 +163,20 @@ function generateSshCommand(Server $server, string $command)
|
|||||||
|
|
||||||
$ssh_command = "timeout $timeout ssh ";
|
$ssh_command = "timeout $timeout ssh ";
|
||||||
|
|
||||||
if (config('coolify.mux_enabled') && config('coolify.is_windows_docker_desktop') == false) {
|
// Check if multiplexing is enabled
|
||||||
$ssh_command .= "-o ControlMaster=auto -o ControlPersist={$muxPersistTime} -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r ";
|
$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')) {
|
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
||||||
$ssh_command .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" ';
|
$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
|
.$command.PHP_EOL
|
||||||
.$delimiter;
|
.$delimiter;
|
||||||
|
|
||||||
// ray($ssh_command);
|
|
||||||
return $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');
|
$timeout = config('constants.ssh.command_timeout');
|
||||||
if ($command instanceof Collection) {
|
if ($command instanceof Collection) {
|
||||||
$command = $command->toArray();
|
$command = $command->toArray();
|
||||||
@@ -180,10 +298,18 @@ function instant_remote_process(Collection|array $command, Server $server, bool
|
|||||||
$command = parseCommandsByLineForSudo(collect($command), $server);
|
$command = parseCommandsByLineForSudo(collect($command), $server);
|
||||||
}
|
}
|
||||||
$command_string = implode("\n", $command);
|
$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());
|
$output = trim($process->output());
|
||||||
$exitCode = $process->exitCode();
|
$exitCode = $process->exitCode();
|
||||||
|
|
||||||
if ($exitCode !== 0) {
|
if ($exitCode !== 0) {
|
||||||
if (! $throwError) {
|
if (! $throwError) {
|
||||||
return null;
|
return null;
|
||||||
@@ -223,7 +349,6 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d
|
|||||||
if (is_null($application_deployment_queue)) {
|
if (is_null($application_deployment_queue)) {
|
||||||
return collect([]);
|
return collect([]);
|
||||||
}
|
}
|
||||||
// ray(data_get($application_deployment_queue, 'logs'));
|
|
||||||
try {
|
try {
|
||||||
$decoded = json_decode(
|
$decoded = json_decode(
|
||||||
data_get($application_deployment_queue, 'logs'),
|
data_get($application_deployment_queue, 'logs'),
|
||||||
@@ -233,7 +358,7 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d
|
|||||||
} catch (\JsonException $exception) {
|
} catch (\JsonException $exception) {
|
||||||
return collect([]);
|
return collect([]);
|
||||||
}
|
}
|
||||||
// ray($decoded );
|
$seenCommands = collect();
|
||||||
$formatted = collect($decoded);
|
$formatted = collect($decoded);
|
||||||
if (! $is_debug_enabled) {
|
if (! $is_debug_enabled) {
|
||||||
$formatted = $formatted->filter(fn ($i) => $i['hidden'] === false ?? false);
|
$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'));
|
data_set($i, 'timestamp', Carbon::parse(data_get($i, 'timestamp'))->format('Y-M-d H:i:s.u'));
|
||||||
|
|
||||||
return $i;
|
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;
|
return $formatted;
|
||||||
}
|
}
|
||||||
function remove_iip($text)
|
function remove_iip($text)
|
||||||
@@ -258,6 +418,10 @@ function remove_mux_and_private_key(Server $server)
|
|||||||
{
|
{
|
||||||
$muxFilename = $server->muxFilename();
|
$muxFilename = $server->muxFilename();
|
||||||
$privateKeyLocation = savePrivateKeyToFs($server);
|
$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-mux')->delete($muxFilename);
|
||||||
Storage::disk('ssh-keys')->delete($privateKeyLocation);
|
Storage::disk('ssh-keys')->delete($privateKeyLocation);
|
||||||
}
|
}
|
||||||
@@ -267,7 +431,10 @@ function refresh_server_connection(?PrivateKey $private_key = null)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
foreach ($private_key->servers as $server) {
|
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) {
|
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);
|
$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) {
|
if ($commandFound) {
|
||||||
ray($command.' found');
|
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
try {
|
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);
|
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) {
|
} catch (\Throwable $e) {
|
||||||
ray('could not install '.$command);
|
|
||||||
ray($e);
|
|
||||||
break;
|
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);
|
$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) {
|
if ($commandFound) {
|
||||||
ray($command.' found');
|
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ray('could not install '.$command);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use App\Models\Application;
|
|||||||
use App\Models\EnvironmentVariable;
|
use App\Models\EnvironmentVariable;
|
||||||
use App\Models\ServiceApplication;
|
use App\Models\ServiceApplication;
|
||||||
use App\Models\ServiceDatabase;
|
use App\Models\ServiceDatabase;
|
||||||
|
use Illuminate\Support\Stringable;
|
||||||
use Spatie\Url\Url;
|
use Spatie\Url\Url;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
@@ -15,9 +16,9 @@ function collectRegex(string $name)
|
|||||||
{
|
{
|
||||||
return "/{$name}\w+/";
|
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)
|
function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Application $oneService, bool $isInit = false)
|
||||||
@@ -53,7 +54,9 @@ function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Appli
|
|||||||
if ($isFile == 'OK') {
|
if ($isFile == 'OK') {
|
||||||
// If its a file & exists
|
// If its a file & exists
|
||||||
$filesystemContent = instant_remote_process(["cat $fileLocation"], $server);
|
$filesystemContent = instant_remote_process(["cat $fileLocation"], $server);
|
||||||
|
if ($fileVolume->is_based_on_git) {
|
||||||
$fileVolume->content = $filesystemContent;
|
$fileVolume->content = $filesystemContent;
|
||||||
|
}
|
||||||
$fileVolume->is_directory = false;
|
$fileVolume->is_directory = false;
|
||||||
$fileVolume->save();
|
$fileVolume->save();
|
||||||
} elseif ($isDir == 'OK') {
|
} elseif ($isDir == 'OK') {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -14,10 +14,11 @@
|
|||||||
"guzzlehttp/guzzle": "^7.5.0",
|
"guzzlehttp/guzzle": "^7.5.0",
|
||||||
"laravel/fortify": "^v1.16.0",
|
"laravel/fortify": "^v1.16.0",
|
||||||
"laravel/framework": "^v11",
|
"laravel/framework": "^v11",
|
||||||
"laravel/horizon": "^5.23.1",
|
"laravel/horizon": "^5.27.1",
|
||||||
"laravel/prompts": "^0.1.6",
|
"laravel/prompts": "^0.1.6",
|
||||||
"laravel/sanctum": "^v4.0",
|
"laravel/sanctum": "^v4.0",
|
||||||
"laravel/socialite": "^v5.14.0",
|
"laravel/socialite": "^v5.14.0",
|
||||||
|
"laravel/telescope": "^5.2",
|
||||||
"laravel/tinker": "^v2.8.1",
|
"laravel/tinker": "^v2.8.1",
|
||||||
"laravel/ui": "^4.2",
|
"laravel/ui": "^4.2",
|
||||||
"lcobucci/jwt": "^5.0.0",
|
"lcobucci/jwt": "^5.0.0",
|
||||||
@@ -93,7 +94,9 @@
|
|||||||
},
|
},
|
||||||
"extra": {
|
"extra": {
|
||||||
"laravel": {
|
"laravel": {
|
||||||
"dont-discover": []
|
"dont-discover": [
|
||||||
|
"laravel/telescope"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"config": {
|
"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\EventServiceProvider::class,
|
||||||
App\Providers\HorizonServiceProvider::class,
|
App\Providers\HorizonServiceProvider::class,
|
||||||
App\Providers\RouteServiceProvider::class,
|
App\Providers\RouteServiceProvider::class,
|
||||||
|
App\Providers\TelescopeServiceProvider::class,
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ return [
|
|||||||
'contact' => 'https://coolify.io/docs/contact',
|
'contact' => 'https://coolify.io/docs/contact',
|
||||||
],
|
],
|
||||||
'ssh' => [
|
'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,
|
'connection_timeout' => 10,
|
||||||
'server_interval' => 20,
|
'server_interval' => 20,
|
||||||
'command_timeout' => 7200,
|
'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