Merge branch 'next' into feat-forgejo
This commit is contained in:
commit
3a352e832b
450 changed files with 19078 additions and 7158 deletions
|
|
@ -1,16 +1,38 @@
|
||||||
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
|
||||||
MUX_ENABLED=false
|
APP_DEBUG=true
|
||||||
|
SSH_MUX_ENABLED=true
|
||||||
|
|
||||||
|
# PostgreSQL Database Configuration
|
||||||
|
DB_DATABASE=coolify
|
||||||
|
DB_USERNAME=coolify
|
||||||
|
DB_PASSWORD=password
|
||||||
|
DB_HOST=host.docker.internal
|
||||||
|
DB_PORT=5432
|
||||||
|
|
||||||
|
# Ray Configuration
|
||||||
|
# Set to true to enable Ray
|
||||||
|
RAY_ENABLED=false
|
||||||
|
# Set custom ray port
|
||||||
|
RAY_PORT=
|
||||||
|
|
||||||
|
# Clockwork Configuration (remove?)
|
||||||
|
CLOCKWORK_ENABLED=false
|
||||||
|
CLOCKWORK_QUEUE_COLLECT=true
|
||||||
|
|
||||||
|
# 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
|
# Special Keys for Andras
|
||||||
# To purge cache
|
# 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=
|
||||||
|
|
|
||||||
72
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
72
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
|
|
@ -1,37 +1,65 @@
|
||||||
name: Bug report
|
name: 🐞 Bug Report
|
||||||
description: 'Create a new bug report.'
|
description: "File a new bug report."
|
||||||
title: '[Bug]: '
|
title: "[Bug]: "
|
||||||
|
labels: ["🐛 Bug", "🔍 Triage"]
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: >-
|
value: |
|
||||||
# 💎 Bounty program (with
|
> [!IMPORTANT]
|
||||||
[algora.io](https://console.algora.io/org/coollabsio/bounties/new))
|
> **Please ensure you are using the latest version of Coolify before submitting an issue, as the bug may have already been fixed in a recent update.** (Of course, if you're experiencing an issue on the latest version that wasn't present in a previous version, please let us know.)
|
||||||
|
|
||||||
|
# 💎 Bounty Program (with [algora.io](https://console.algora.io/org/coollabsio/bounties/new))
|
||||||
|
- If you would like to prioritize the issue resolution, consider adding a bounty to this issue through our [Bounty Program](https://console.algora.io/org/coollabsio/bounties/new).
|
||||||
|
|
||||||
If you would like to prioritize the issue resolution, you can add bounty
|
|
||||||
to this issue.
|
|
||||||
|
|
||||||
|
|
||||||
Click [here](https://console.algora.io/org/coollabsio/bounties/new) to
|
|
||||||
get started.
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Description
|
label: Error Message and Logs
|
||||||
description: A clear and concise description of the problem
|
description: Provide a detailed description of the error or exception you encountered, along with any relevant log output.
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Minimal Reproduction (if possible, example repository)
|
|
||||||
description: Please provide a step by step guide to reproduce the issue.
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Exception or Error
|
label: Steps to Reproduce
|
||||||
description: Please provide error logs if possible.
|
description: Please provide a step-by-step guide to reproduce the issue. Be as detailed as possible, otherwise we may not be able to assist you.
|
||||||
|
value: |
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
4.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: Version
|
label: Example Repository URL
|
||||||
description: Coolify's version (see top of your screen).
|
description: If applicable, provide a URL to a repository demonstrating the issue.
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Coolify Version
|
||||||
|
description: Please provide the Coolify version you are using. This can be found in the top left corner of your Coolify dashboard.
|
||||||
|
placeholder: "v4.0.0-beta.335"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Are you using Coolify Cloud?
|
||||||
|
options:
|
||||||
|
- "No (self-hosted)"
|
||||||
|
- "Yes (Coolify Cloud)"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Operating System and Version (self-hosted)
|
||||||
|
description: Run `cat /etc/os-release` or `lsb_release -a` in your terminal and provide the operating system and version.
|
||||||
|
placeholder: "Ubuntu 22.04"
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Additional Information
|
||||||
|
description: Any other relevant details about the issue.
|
||||||
|
|
|
||||||
31
.github/ISSUE_TEMPLATE/ENHANCEMENT_BOUNTY.yml
vendored
Normal file
31
.github/ISSUE_TEMPLATE/ENHANCEMENT_BOUNTY.yml
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
name: 💎 Enhancement Bounty
|
||||||
|
description: "Propose a new feature, service, or improvement with an attached bounty."
|
||||||
|
title: "[Enhancement]: "
|
||||||
|
labels: ["✨ Enhancement", "🔍 Triage"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> **This issue template is exclusively for proposing new features, services, or improvements with an attached bounty.** Enhancements without a bounty can be discussed in the appropriate category of [Github Discussions](https://github.com/coollabsio/coolify/discussions).
|
||||||
|
|
||||||
|
# 💎 Add a Bounty (with [algora.io](https://console.algora.io/org/coollabsio/bounties/new))
|
||||||
|
- [Click here to add the required bounty](https://console.algora.io/org/coollabsio/bounties/new)
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Request Type
|
||||||
|
description: Select the type of request you are making.
|
||||||
|
options:
|
||||||
|
- New Feature
|
||||||
|
- New Service
|
||||||
|
- Improvement
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Description
|
||||||
|
description: Provide a detailed description of the feature, improvement, or service you are proposing.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
20
.github/ISSUE_TEMPLATE/config.yml
vendored
20
.github/ISSUE_TEMPLATE/config.yml
vendored
|
|
@ -1,8 +1,18 @@
|
||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
|
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: 🤔 Community Support (Chat)
|
- name: 🤔 Questions and Community Support
|
||||||
url: https://coollabs.io/discord
|
url: https://coollabs.io/discord
|
||||||
about: Reach out to us on Discord.
|
about: If you have any questions, reach out to us on Discord inside the "#support" channel.
|
||||||
- name: 🙋♂️ Feature Requests
|
|
||||||
url: https://github.com/coollabsio/coolify/discussions/categories/feature-requests-ideas
|
- name: 💡 Feature Request
|
||||||
about: All feature requests will be discussed here.
|
url: https://github.com/coollabsio/coolify/discussions/categories/feature-requests
|
||||||
|
about: Suggest a new feature for Coolify.
|
||||||
|
|
||||||
|
- name: ⚙️ Service Request
|
||||||
|
url: https://github.com/coollabsio/coolify/discussions/categories/service-requests
|
||||||
|
about: Request a new service integration for Coolify.
|
||||||
|
|
||||||
|
- name: 🔧 Improvements
|
||||||
|
url: https://github.com/coollabsio/coolify/discussions/categories/improvements
|
||||||
|
about: Suggest improvements to existing features for Coolify.
|
||||||
|
|
|
||||||
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 }}
|
||||||
|
|
||||||
|
|
|
||||||
22
.github/workflows/coolify-helper.yml
vendored
22
.github/workflows/coolify-helper.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 }}: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:
|
||||||
|
|
|
||||||
103
.github/workflows/coolify-realtime.yml
vendored
Normal file
103
.github/workflows/coolify-realtime.yml
vendored
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
name: Coolify Realtime (v4)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main", "next" ]
|
||||||
|
paths:
|
||||||
|
- .github/workflows/coolify-realtime.yml
|
||||||
|
- docker/coolify-realtime/Dockerfile
|
||||||
|
- docker/coolify-realtime/terminal-server.js
|
||||||
|
- docker/coolify-realtime/package.json
|
||||||
|
- docker/coolify-realtime/soketi-entrypoint.sh
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: "coollabsio/coolify-realtime"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
amd64:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Get Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
- name: Build image and push to registry
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
no-cache: true
|
||||||
|
context: .
|
||||||
|
file: docker/coolify-realtime/Dockerfile
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
|
||||||
|
labels: |
|
||||||
|
coolify.managed=true
|
||||||
|
aarch64:
|
||||||
|
runs-on: [ self-hosted, arm64 ]
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Get Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
- name: Build image and push to registry
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
no-cache: true
|
||||||
|
context: .
|
||||||
|
file: docker/coolify-realtime/Dockerfile
|
||||||
|
platforms: linux/aarch64
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
|
||||||
|
labels: |
|
||||||
|
coolify.managed=true
|
||||||
|
merge-manifest:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
needs: [ amd64, aarch64 ]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Get Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
- name: Create & publish manifest
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
- uses: sarisia/actions-status-discord@v1
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}
|
||||||
93
.github/workflows/pr-build.yml
vendored
Normal file
93
.github/workflows/pr-build.yml
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
name: PR Build (v4)
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
branches-ignore: ["main", "v3"]
|
||||||
|
paths-ignore:
|
||||||
|
- .github/workflows/coolify-helper.yml
|
||||||
|
- docker/coolify-helper/Dockerfile
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: "coollabsio/coolify"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
amd64:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
attestations: write
|
||||||
|
id-token: write
|
||||||
|
actions: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build image and push to registry
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: docker/prod/Dockerfile
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}
|
||||||
|
aarch64:
|
||||||
|
runs-on: [self-hosted, arm64]
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
attestations: write
|
||||||
|
id-token: write
|
||||||
|
actions: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build image and push to registry
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: docker/prod/Dockerfile
|
||||||
|
platforms: linux/aarch64
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}-aarch64
|
||||||
|
merge-manifest:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
attestations: write
|
||||||
|
id-token: write
|
||||||
|
actions: write
|
||||||
|
needs: [amd64, aarch64]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Create & publish manifest
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}
|
||||||
|
- uses: sarisia/actions-status-discord@v1
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||||
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:
|
||||||
|
|
|
||||||
78
.github/workflows/remove-labels-and-assignees-on-close.yml
vendored
Normal file
78
.github/workflows/remove-labels-and-assignees-on-close.yml
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
name: Remove Labels and Assignees on Issue Close
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [closed]
|
||||||
|
pull_request:
|
||||||
|
types: [closed]
|
||||||
|
pull_request_target:
|
||||||
|
types: [closed]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
remove-labels-and-assignees:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Remove labels and assignees
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
script: |
|
||||||
|
const { owner, repo } = context.repo;
|
||||||
|
|
||||||
|
async function processIssue(issueNumber) {
|
||||||
|
try {
|
||||||
|
const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: issueNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
const labelsToKeep = currentLabels
|
||||||
|
.filter(label => label.name === '⏱︎ Stale')
|
||||||
|
.map(label => label.name);
|
||||||
|
|
||||||
|
await github.rest.issues.setLabels({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
labels: labelsToKeep
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: issue } = await github.rest.issues.get({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: issueNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
if (issue.assignees && issue.assignees.length > 0) {
|
||||||
|
await github.rest.issues.removeAssignees({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
assignees: issue.assignees.map(assignee => assignee.login)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.status !== 404) {
|
||||||
|
console.error(`Error processing issue ${issueNumber}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.eventName === 'issues' || context.eventName === 'pull_request' || context.eventName === 'pull_request_target') {
|
||||||
|
const issue = context.payload.issue || context.payload.pull_request;
|
||||||
|
await processIssue(issue.number);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.eventName === 'pull_request' || context.eventName === 'pull_request_target') {
|
||||||
|
const pr = context.payload.pull_request;
|
||||||
|
if (pr.body) {
|
||||||
|
const issueReferences = pr.body.match(/#(\d+)/g);
|
||||||
|
if (issueReferences) {
|
||||||
|
for (const reference of issueReferences) {
|
||||||
|
const issueNumber = parseInt(reference.substring(1));
|
||||||
|
await processIssue(issueNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
207
CONTRIBUTING.md
Normal file
207
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,207 @@
|
||||||
|
# Contributing to Coolify
|
||||||
|
|
||||||
|
> "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.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Setup Development Environment](#1-setup-development-environment)
|
||||||
|
2. [Verify Installation](#2-verify-installation-optional)
|
||||||
|
3. [Fork and Setup Local Repository](#3-fork-and-setup-local-repository)
|
||||||
|
4. [Set up Environment Variables](#4-set-up-environment-variables)
|
||||||
|
5. [Start Coolify](#5-start-coolify)
|
||||||
|
6. [Start Development](#6-start-development)
|
||||||
|
7. [Development Notes](#7-development-notes)
|
||||||
|
8. [Create a Pull Request](#8-create-a-pull-request)
|
||||||
|
9. [Additional Contribution Guidelines](#additional-contribution-guidelines)
|
||||||
|
|
||||||
|
## 1. Setup Development Environment
|
||||||
|
|
||||||
|
Follow the steps below for your operating system:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>Windows</strong></summary>
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>MacOS</strong></summary>
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>Linux</strong></summary>
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## 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 and Setup Local Repository
|
||||||
|
|
||||||
|
1. Fork the [Coolify](https://github.com/coollabsio/coolify) repository to your GitHub account.
|
||||||
|
|
||||||
|
2. Install a code editor on your machine (choose one):
|
||||||
|
|
||||||
|
| Editor | Platform | Download Link |
|
||||||
|
|--------|----------|---------------|
|
||||||
|
| Visual Studio Code (recommended free) | Windows/macOS/Linux | [Download](https://code.visualstudio.com/download) |
|
||||||
|
| Cursor (recommended but paid) | Windows/macOS/Linux | [Download](https://www.cursor.com/) |
|
||||||
|
| Zed (very fast) | macOS/Linux | [Download](https://zed.dev/download) |
|
||||||
|
|
||||||
|
3. Clone the Coolify Repository from your fork to your local machine
|
||||||
|
- Use `git clone` in the command line, or
|
||||||
|
- 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):
|
||||||
|
```bash
|
||||||
|
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:
|
||||||
|
```bash
|
||||||
|
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:
|
||||||
|
| Tool | URL | Note |
|
||||||
|
|------|-----|------|
|
||||||
|
| Laravel Horizon (scheduler) | `http://localhost:8000/horizon` | Only accessible when logged in as root user |
|
||||||
|
| Mailpit (email catcher) | `http://localhost:8025` | |
|
||||||
|
| Telescope (debugging tool) | `http://localhost:8000/telescope` | Disabled by default |
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> To enable Telescope, add the following 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 environment-specific issues.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Forgetting to migrate the database can cause problems, so make it a habit to run migrations after pulling changes or switching branches.
|
||||||
|
|
||||||
|
## 8. 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".
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Always set the base branch for your PR to the `next` branch of the Coolify repository, not the `main` branch.
|
||||||
|
|
||||||
|
4. Submit your PR:
|
||||||
|
- Review your changes one last time.
|
||||||
|
- Click "Create pull request" to submit.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Make sure your PR is out of draft mode as soon as it's ready for review. PRs that are in draft mode for a long time may be closed by maintainers.
|
||||||
|
|
||||||
|
After submission, maintainers will review your PR and may request changes or provide feedback.
|
||||||
|
|
||||||
|
## Additional Contribution Guidelines
|
||||||
|
|
||||||
|
### 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/contribute/service)
|
||||||
|
|
||||||
|
### Contributing to Documentation
|
||||||
|
|
||||||
|
To contribute to the Coolify documentation, please refer to this guide:
|
||||||
|
[Contributing to the Coolify Documentation](https://github.com/coollabsio/documentation-coolify/blob/main/CONTRIBUTING.md)
|
||||||
|
|
@ -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).
|
|
||||||
|
|
||||||
42
README.md
42
README.md
|
|
@ -35,19 +35,32 @@ # Donations
|
||||||
|
|
||||||
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.
|
||||||
|
* [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>
|
||||||
|
|
@ -70,8 +83,11 @@ ## Github Sponsors ($40+)
|
||||||
<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>
|
||||||
|
|
|
||||||
45
RELEASE.md
Normal file
45
RELEASE.md
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Coolify Release Guide
|
||||||
|
|
||||||
|
This guide outlines the release process for Coolify, intended for developers and those interested in understanding how releases are managed and deployed.
|
||||||
|
|
||||||
|
## Release Process
|
||||||
|
|
||||||
|
1. **Development on `next` or separate branches**
|
||||||
|
- Changes, fixes and new features are developed on the `next` or even separate branches.
|
||||||
|
|
||||||
|
2. **Merging to `main`**
|
||||||
|
- Once changes are ready, they are merged from `next` into the `main` branch.
|
||||||
|
|
||||||
|
3. **Building the release**
|
||||||
|
- After merging to `main`, a new release is built.
|
||||||
|
- Note: A push to `main` does not automatically mean a new version is released.
|
||||||
|
|
||||||
|
4. **Creating a GitHub release**
|
||||||
|
- A new release is created on GitHub with the new version details.
|
||||||
|
|
||||||
|
5. **Updating the CDN**
|
||||||
|
- The final step is updating the version information on the CDN:
|
||||||
|
[https://cdn.coollabs.io/coolify/versions.json](https://cdn.coollabs.io/coolify/versions.json)
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> The CDN update may not occur immediately after the GitHub release. It can happen hours or even days later due to additional testing, stability checks, or potential hotfixes.
|
||||||
|
|
||||||
|
|
||||||
|
## Version Availability
|
||||||
|
|
||||||
|
It's important to understand that a new version released on GitHub may not immediately become available for users to update (through manual or auto-update).
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> If you see a new release on GitHub but haven't received the update, it's likely because the CDN hasn't been updated yet. This is intentional and ensures stability and allows for hotfixes before the new version is officially released.
|
||||||
|
|
||||||
|
## Manually Update to Specific Versions
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> Updating to unreleased versions is not recommended and may cause issues. Use at your own risk!
|
||||||
|
|
||||||
|
To update your Coolify instance to a specific (unreleased) version, use the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash -s <version>
|
||||||
|
```
|
||||||
|
-> Replace `<version>` with the version you want to update to (for example `4.0.0-beta.332`).
|
||||||
|
|
@ -3,47 +3,40 @@
|
||||||
namespace App\Actions\Application;
|
namespace App\Actions\Application;
|
||||||
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
|
use App\Actions\Server\CleanupDocker;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class StopApplication
|
class StopApplication
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Application $application)
|
public function handle(Application $application, bool $previewDeployments = false, bool $dockerCleanup = true)
|
||||||
{
|
{
|
||||||
if ($application->destination->server->isSwarm()) {
|
try {
|
||||||
instant_remote_process(["docker stack rm {$application->uuid}"], $application->destination->server);
|
$server = $application->destination->server;
|
||||||
|
if (!$server->isFunctional()) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$servers = collect([]);
|
|
||||||
$servers->push($application->destination->server);
|
|
||||||
$application->additional_servers->map(function ($server) use ($servers) {
|
|
||||||
$servers->push($server);
|
|
||||||
});
|
|
||||||
foreach ($servers as $server) {
|
|
||||||
if (! $server->isFunctional()) {
|
|
||||||
return 'Server is not functional';
|
return 'Server is not functional';
|
||||||
}
|
}
|
||||||
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
ray('Stopping application: ' . $application->name);
|
||||||
if ($containers->count() > 0) {
|
|
||||||
foreach ($containers as $container) {
|
if ($server->isSwarm()) {
|
||||||
$containerName = data_get($container, 'Names');
|
instant_remote_process(["docker stack rm {$application->uuid}"], $server);
|
||||||
if ($containerName) {
|
return;
|
||||||
instant_remote_process(
|
|
||||||
["docker rm -f {$containerName}"],
|
|
||||||
$server
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$containersToStop = $application->getContainersToStop($previewDeployments);
|
||||||
|
$application->stopContainers($containersToStop, $server);
|
||||||
|
|
||||||
if ($application->build_pack === 'dockercompose') {
|
if ($application->build_pack === 'dockercompose') {
|
||||||
// remove network
|
$application->delete_connected_networks($application->uuid);
|
||||||
$uuid = $application->uuid;
|
|
||||||
instant_remote_process(["docker network disconnect {$uuid} coolify-proxy"], $server, false);
|
|
||||||
instant_remote_process(["docker network rm {$uuid}"], $server, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($dockerCleanup) {
|
||||||
|
CleanupDocker::run($server, true);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
ray($e->getMessage());
|
||||||
|
return $e->getMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Process;
|
use Illuminate\Support\Facades\Process;
|
||||||
use Spatie\Activitylog\Models\Activity;
|
use Spatie\Activitylog\Models\Activity;
|
||||||
|
use App\Helpers\SshMultiplexingHelper;
|
||||||
|
|
||||||
class RunRemoteProcess
|
class RunRemoteProcess
|
||||||
{
|
{
|
||||||
|
|
@ -137,7 +138,7 @@ protected function getCommand(): string
|
||||||
$command = $this->activity->getExtraProperty('command');
|
$command = $this->activity->getExtraProperty('command');
|
||||||
$server = Server::whereUuid($server_uuid)->firstOrFail();
|
$server = Server::whereUuid($server_uuid)->firstOrFail();
|
||||||
|
|
||||||
return generateSshCommand($server, $command);
|
return SshMultiplexingHelper::generateSshCommand($server, $command);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function handleOutput(string $type, string $output)
|
protected function handleOutput(string $type, string $output)
|
||||||
|
|
|
||||||
|
|
@ -79,14 +79,7 @@ public function handle(StandaloneClickhouse $database)
|
||||||
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 @@ public function handle(StandaloneClickhouse $database)
|
||||||
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 @@ private function generate_environment_variables()
|
||||||
$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 @@ public function handle(StandaloneDragonfly $database)
|
||||||
$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 @@ public function handle(StandaloneDragonfly $database)
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
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 @@ public function handle(StandaloneDragonfly $database)
|
||||||
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 @@ private function generate_local_persistent_volumes()
|
||||||
$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 @@ private function generate_environment_variables()
|
||||||
$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 @@ public function handle(StandaloneKeydb $database)
|
||||||
$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 @@ public function handle(StandaloneKeydb $database)
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
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 @@ public function handle(StandaloneKeydb $database)
|
||||||
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 @@ private function generate_local_persistent_volumes()
|
||||||
$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 @@ private function generate_environment_variables()
|
||||||
$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 @@ public function handle(StandaloneMariadb $database)
|
||||||
$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 @@ public function handle(StandaloneMariadb $database)
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
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 @@ public function handle(StandaloneMariadb $database)
|
||||||
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 @@ private function generate_local_persistent_volumes()
|
||||||
$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 @@ private function generate_environment_variables()
|
||||||
$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 @@ public function handle(StandaloneMongodb $database)
|
||||||
$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 @@ public function handle(StandaloneMongodb $database)
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
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 @@ public function handle(StandaloneMongodb $database)
|
||||||
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 @@ private function generate_local_persistent_volumes()
|
||||||
$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 @@ private function generate_environment_variables()
|
||||||
$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 @@ public function handle(StandaloneMysql $database)
|
||||||
$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 @@ public function handle(StandaloneMysql $database)
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
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 @@ public function handle(StandaloneMysql $database)
|
||||||
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 @@ private function generate_local_persistent_volumes()
|
||||||
$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 @@ private function generate_environment_variables()
|
||||||
$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 @@ public function handle(StandalonePostgresql $database)
|
||||||
$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 @@ public function handle(StandalonePostgresql $database)
|
||||||
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 @@ public function handle(StandalonePostgresql $database)
|
||||||
'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 @@ private function generate_environment_variables()
|
||||||
$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 @@ public function handle(StandaloneRedis $database)
|
||||||
$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 @@ public function handle(StandaloneRedis $database)
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
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 @@ public function handle(StandaloneRedis $database)
|
||||||
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 @@ private function generate_local_persistent_volumes()
|
||||||
$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 @@ private function generate_environment_variables()
|
||||||
$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\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Actions\Server\CleanupDocker;
|
||||||
use App\Models\StandaloneClickhouse;
|
use App\Models\StandaloneClickhouse;
|
||||||
use App\Models\StandaloneDragonfly;
|
use App\Models\StandaloneDragonfly;
|
||||||
use App\Models\StandaloneKeydb;
|
use App\Models\StandaloneKeydb;
|
||||||
|
|
@ -10,24 +11,65 @@
|
||||||
use App\Models\StandaloneMysql;
|
use App\Models\StandaloneMysql;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\StandaloneRedis;
|
use App\Models\StandaloneRedis;
|
||||||
|
use Illuminate\Support\Facades\Process;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class StopDatabase
|
class StopDatabase
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database)
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database, bool $isDeleteOperation = false, bool $dockerCleanup = true)
|
||||||
{
|
{
|
||||||
$server = $database->destination->server;
|
$server = $database->destination->server;
|
||||||
if (! $server->isFunctional()) {
|
if (! $server->isFunctional()) {
|
||||||
return 'Server is not functional';
|
return 'Server is not functional';
|
||||||
}
|
}
|
||||||
instant_remote_process(
|
|
||||||
["docker rm -f {$database->uuid}"],
|
$this->stopContainer($database, $database->uuid, 300);
|
||||||
$server
|
if (! $isDeleteOperation) {
|
||||||
);
|
if ($dockerCleanup) {
|
||||||
|
CleanupDocker::run($server, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($database->is_public) {
|
if ($database->is_public) {
|
||||||
StopDatabaseProxy::run($database);
|
StopDatabaseProxy::run($database);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return 'Database stopped successfully';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function stopContainer($database, string $containerName, int $timeout = 300): void
|
||||||
|
{
|
||||||
|
$server = $database->destination->server;
|
||||||
|
|
||||||
|
$process = Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
|
||||||
|
|
||||||
|
$startTime = time();
|
||||||
|
while ($process->running()) {
|
||||||
|
if (time() - $startTime >= $timeout) {
|
||||||
|
$this->forceStopContainer($containerName, $server);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
usleep(100000);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->removeContainer($containerName, $server);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function forceStopContainer(string $containerName, $server): void
|
||||||
|
{
|
||||||
|
instant_remote_process(command: ["docker kill $containerName"], server: $server, throwError: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function removeContainer(string $containerName, $server): void
|
||||||
|
{
|
||||||
|
instant_remote_process(command: ["docker rm -f $containerName"], server: $server, throwError: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deleteConnectedNetworks($uuid, $server)
|
||||||
|
{
|
||||||
|
instant_remote_process(["docker network disconnect {$uuid} coolify-proxy"], $server, false);
|
||||||
|
instant_remote_process(["docker network rm {$uuid}"], $server, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
use App\Notifications\Container\ContainerRestarted;
|
use App\Notifications\Container\ContainerRestarted;
|
||||||
use App\Notifications\Container\ContainerStopped;
|
use App\Notifications\Container\ContainerStopped;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class GetContainersStatus
|
class GetContainersStatus
|
||||||
|
|
@ -20,13 +21,16 @@ class GetContainersStatus
|
||||||
|
|
||||||
public $applications;
|
public $applications;
|
||||||
|
|
||||||
|
public ?Collection $containers;
|
||||||
|
|
||||||
|
public ?Collection $containerReplicates;
|
||||||
|
|
||||||
public $server;
|
public $server;
|
||||||
|
|
||||||
public function handle(Server $server)
|
public function handle(Server $server, ?Collection $containers = null, ?Collection $containerReplicates = null)
|
||||||
{
|
{
|
||||||
// if (isDev()) {
|
$this->containers = $containers;
|
||||||
// $server = Server::find(0);
|
$this->containerReplicates = $containerReplicates;
|
||||||
// }
|
|
||||||
$this->server = $server;
|
$this->server = $server;
|
||||||
if (! $this->server->isFunctional()) {
|
if (! $this->server->isFunctional()) {
|
||||||
return 'Server is not ready.';
|
return 'Server is not ready.';
|
||||||
|
|
@ -66,322 +70,312 @@ public function handle(Server $server)
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
private function sentinel()
|
// private function sentinel()
|
||||||
{
|
// {
|
||||||
try {
|
// try {
|
||||||
$containers = $this->server->getContainers();
|
// $this->containers = $this->server->getContainersWithSentinel();
|
||||||
if ($containers->count() === 0) {
|
// if ($this->containers->count() === 0) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
$databases = $this->server->databases();
|
// $databases = $this->server->databases();
|
||||||
$services = $this->server->services()->get();
|
// $services = $this->server->services()->get();
|
||||||
$previews = $this->server->previews();
|
// $previews = $this->server->previews();
|
||||||
$foundApplications = [];
|
// $foundApplications = [];
|
||||||
$foundApplicationPreviews = [];
|
// $foundApplicationPreviews = [];
|
||||||
$foundDatabases = [];
|
// $foundDatabases = [];
|
||||||
$foundServices = [];
|
// $foundServices = [];
|
||||||
|
|
||||||
foreach ($containers as $container) {
|
// foreach ($this->containers as $container) {
|
||||||
$labels = Arr::undot(data_get($container, 'labels'));
|
// $labels = Arr::undot(data_get($container, 'labels'));
|
||||||
$containerStatus = data_get($container, 'state');
|
// $containerStatus = data_get($container, 'state');
|
||||||
$containerHealth = data_get($container, 'health_status', 'unhealthy');
|
// $containerHealth = data_get($container, 'health_status', 'unhealthy');
|
||||||
$containerStatus = "$containerStatus ($containerHealth)";
|
// $containerStatus = "$containerStatus ($containerHealth)";
|
||||||
$applicationId = data_get($labels, 'coolify.applicationId');
|
// $applicationId = data_get($labels, 'coolify.applicationId');
|
||||||
if ($applicationId) {
|
// if ($applicationId) {
|
||||||
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
// $pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
||||||
if ($pullRequestId) {
|
// if ($pullRequestId) {
|
||||||
if (str($applicationId)->contains('-')) {
|
// if (str($applicationId)->contains('-')) {
|
||||||
$applicationId = str($applicationId)->before('-');
|
// $applicationId = str($applicationId)->before('-');
|
||||||
}
|
// }
|
||||||
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
// $preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
||||||
if ($preview) {
|
// if ($preview) {
|
||||||
$foundApplicationPreviews[] = $preview->id;
|
// $foundApplicationPreviews[] = $preview->id;
|
||||||
$statusFromDb = $preview->status;
|
// $statusFromDb = $preview->status;
|
||||||
if ($statusFromDb !== $containerStatus) {
|
// if ($statusFromDb !== $containerStatus) {
|
||||||
$preview->update(['status' => $containerStatus]);
|
// $preview->update(['status' => $containerStatus]);
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
//Notify user that this container should not be there.
|
// //Notify user that this container should not be there.
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
$application = $this->applications->where('id', $applicationId)->first();
|
// $application = $this->applications->where('id', $applicationId)->first();
|
||||||
if ($application) {
|
// if ($application) {
|
||||||
$foundApplications[] = $application->id;
|
// $foundApplications[] = $application->id;
|
||||||
$statusFromDb = $application->status;
|
// $statusFromDb = $application->status;
|
||||||
if ($statusFromDb !== $containerStatus) {
|
// if ($statusFromDb !== $containerStatus) {
|
||||||
$application->update(['status' => $containerStatus]);
|
// $application->update(['status' => $containerStatus]);
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
//Notify user that this container should not be there.
|
// //Notify user that this container should not be there.
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
$uuid = data_get($labels, 'com.docker.compose.service');
|
// $uuid = data_get($labels, 'com.docker.compose.service');
|
||||||
$type = data_get($labels, 'coolify.type');
|
// $type = data_get($labels, 'coolify.type');
|
||||||
if ($uuid) {
|
// if ($uuid) {
|
||||||
if ($type === 'service') {
|
// if ($type === 'service') {
|
||||||
$database_id = data_get($labels, 'coolify.service.subId');
|
// $database_id = data_get($labels, 'coolify.service.subId');
|
||||||
if ($database_id) {
|
// if ($database_id) {
|
||||||
$service_db = ServiceDatabase::where('id', $database_id)->first();
|
// $service_db = ServiceDatabase::where('id', $database_id)->first();
|
||||||
if ($service_db) {
|
// if ($service_db) {
|
||||||
$uuid = $service_db->service->uuid;
|
// $uuid = $service_db->service->uuid;
|
||||||
$isPublic = data_get($service_db, 'is_public');
|
// $isPublic = data_get($service_db, 'is_public');
|
||||||
if ($isPublic) {
|
// if ($isPublic) {
|
||||||
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
// $foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
|
||||||
if ($this->server->isSwarm()) {
|
// if ($this->server->isSwarm()) {
|
||||||
// TODO: fix this with sentinel
|
// // TODO: fix this with sentinel
|
||||||
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
// return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
} else {
|
// } else {
|
||||||
return data_get($value, 'name') === "$uuid-proxy";
|
// return data_get($value, 'name') === "$uuid-proxy";
|
||||||
}
|
// }
|
||||||
})->first();
|
// })->first();
|
||||||
if (! $foundTcpProxy) {
|
// if (! $foundTcpProxy) {
|
||||||
StartDatabaseProxy::run($service_db);
|
// StartDatabaseProxy::run($service_db);
|
||||||
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
|
// // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
$database = $databases->where('uuid', $uuid)->first();
|
// $database = $databases->where('uuid', $uuid)->first();
|
||||||
if ($database) {
|
// if ($database) {
|
||||||
$isPublic = data_get($database, 'is_public');
|
// $isPublic = data_get($database, 'is_public');
|
||||||
$foundDatabases[] = $database->id;
|
// $foundDatabases[] = $database->id;
|
||||||
$statusFromDb = $database->status;
|
// $statusFromDb = $database->status;
|
||||||
if ($statusFromDb !== $containerStatus) {
|
// if ($statusFromDb !== $containerStatus) {
|
||||||
$database->update(['status' => $containerStatus]);
|
// $database->update(['status' => $containerStatus]);
|
||||||
}
|
// }
|
||||||
if ($isPublic) {
|
// if ($isPublic) {
|
||||||
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
// $foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
|
||||||
if ($this->server->isSwarm()) {
|
// if ($this->server->isSwarm()) {
|
||||||
// TODO: fix this with sentinel
|
// // TODO: fix this with sentinel
|
||||||
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
// return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
} else {
|
// } else {
|
||||||
return data_get($value, 'name') === "$uuid-proxy";
|
// return data_get($value, 'name') === "$uuid-proxy";
|
||||||
}
|
// }
|
||||||
})->first();
|
// })->first();
|
||||||
if (! $foundTcpProxy) {
|
// if (! $foundTcpProxy) {
|
||||||
StartDatabaseProxy::run($database);
|
// StartDatabaseProxy::run($database);
|
||||||
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
// Notify user that this container should not be there.
|
// // Notify user that this container should not be there.
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
if (data_get($container, 'name') === 'coolify-db') {
|
// if (data_get($container, 'name') === 'coolify-db') {
|
||||||
$foundDatabases[] = 0;
|
// $foundDatabases[] = 0;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
$serviceLabelId = data_get($labels, 'coolify.serviceId');
|
// $serviceLabelId = data_get($labels, 'coolify.serviceId');
|
||||||
if ($serviceLabelId) {
|
// if ($serviceLabelId) {
|
||||||
$subType = data_get($labels, 'coolify.service.subType');
|
// $subType = data_get($labels, 'coolify.service.subType');
|
||||||
$subId = data_get($labels, 'coolify.service.subId');
|
// $subId = data_get($labels, 'coolify.service.subId');
|
||||||
$service = $services->where('id', $serviceLabelId)->first();
|
// $service = $services->where('id', $serviceLabelId)->first();
|
||||||
if (! $service) {
|
// if (! $service) {
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
if ($subType === 'application') {
|
// if ($subType === 'application') {
|
||||||
$service = $service->applications()->where('id', $subId)->first();
|
// $service = $service->applications()->where('id', $subId)->first();
|
||||||
} else {
|
// } else {
|
||||||
$service = $service->databases()->where('id', $subId)->first();
|
// $service = $service->databases()->where('id', $subId)->first();
|
||||||
}
|
// }
|
||||||
if ($service) {
|
// if ($service) {
|
||||||
$foundServices[] = "$service->id-$service->name";
|
// $foundServices[] = "$service->id-$service->name";
|
||||||
$statusFromDb = $service->status;
|
// $statusFromDb = $service->status;
|
||||||
if ($statusFromDb !== $containerStatus) {
|
// if ($statusFromDb !== $containerStatus) {
|
||||||
// ray('Updating status: ' . $containerStatus);
|
// // ray('Updating status: ' . $containerStatus);
|
||||||
$service->update(['status' => $containerStatus]);
|
// $service->update(['status' => $containerStatus]);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
$exitedServices = collect([]);
|
// $exitedServices = collect([]);
|
||||||
foreach ($services as $service) {
|
// foreach ($services as $service) {
|
||||||
$apps = $service->applications()->get();
|
// $apps = $service->applications()->get();
|
||||||
$dbs = $service->databases()->get();
|
// $dbs = $service->databases()->get();
|
||||||
foreach ($apps as $app) {
|
// foreach ($apps as $app) {
|
||||||
if (in_array("$app->id-$app->name", $foundServices)) {
|
// if (in_array("$app->id-$app->name", $foundServices)) {
|
||||||
continue;
|
// continue;
|
||||||
} else {
|
// } else {
|
||||||
$exitedServices->push($app);
|
// $exitedServices->push($app);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
foreach ($dbs as $db) {
|
// foreach ($dbs as $db) {
|
||||||
if (in_array("$db->id-$db->name", $foundServices)) {
|
// if (in_array("$db->id-$db->name", $foundServices)) {
|
||||||
continue;
|
// continue;
|
||||||
} else {
|
// } else {
|
||||||
$exitedServices->push($db);
|
// $exitedServices->push($db);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
$exitedServices = $exitedServices->unique('id');
|
// $exitedServices = $exitedServices->unique('id');
|
||||||
foreach ($exitedServices as $exitedService) {
|
// foreach ($exitedServices as $exitedService) {
|
||||||
if (str($exitedService->status)->startsWith('exited')) {
|
// if (str($exitedService->status)->startsWith('exited')) {
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
$name = data_get($exitedService, 'name');
|
// $name = data_get($exitedService, 'name');
|
||||||
$fqdn = data_get($exitedService, 'fqdn');
|
// $fqdn = data_get($exitedService, 'fqdn');
|
||||||
if ($name) {
|
// if ($name) {
|
||||||
if ($fqdn) {
|
// if ($fqdn) {
|
||||||
$containerName = "$name, available at $fqdn";
|
// $containerName = "$name, available at $fqdn";
|
||||||
} else {
|
// } else {
|
||||||
$containerName = $name;
|
// $containerName = $name;
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
if ($fqdn) {
|
// if ($fqdn) {
|
||||||
$containerName = $fqdn;
|
// $containerName = $fqdn;
|
||||||
} else {
|
// } else {
|
||||||
$containerName = null;
|
// $containerName = null;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
$projectUuid = data_get($service, 'environment.project.uuid');
|
// $projectUuid = data_get($service, 'environment.project.uuid');
|
||||||
$serviceUuid = data_get($service, 'uuid');
|
// $serviceUuid = data_get($service, 'uuid');
|
||||||
$environmentName = data_get($service, 'environment.name');
|
// $environmentName = data_get($service, 'environment.name');
|
||||||
|
|
||||||
if ($projectUuid && $serviceUuid && $environmentName) {
|
// if ($projectUuid && $serviceUuid && $environmentName) {
|
||||||
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/service/'.$serviceUuid;
|
// $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/service/'.$serviceUuid;
|
||||||
} else {
|
// } else {
|
||||||
$url = null;
|
// $url = null;
|
||||||
}
|
// }
|
||||||
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
// // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
$exitedService->update(['status' => 'exited']);
|
// $exitedService->update(['status' => 'exited']);
|
||||||
}
|
// }
|
||||||
|
|
||||||
$notRunningApplications = $this->applications->pluck('id')->diff($foundApplications);
|
// $notRunningApplications = $this->applications->pluck('id')->diff($foundApplications);
|
||||||
foreach ($notRunningApplications as $applicationId) {
|
// foreach ($notRunningApplications as $applicationId) {
|
||||||
$application = $this->applications->where('id', $applicationId)->first();
|
// $application = $this->applications->where('id', $applicationId)->first();
|
||||||
if (str($application->status)->startsWith('exited')) {
|
// if (str($application->status)->startsWith('exited')) {
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
$application->update(['status' => 'exited']);
|
// $application->update(['status' => 'exited']);
|
||||||
|
|
||||||
$name = data_get($application, 'name');
|
// $name = data_get($application, 'name');
|
||||||
$fqdn = data_get($application, 'fqdn');
|
// $fqdn = data_get($application, 'fqdn');
|
||||||
|
|
||||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
// $containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||||
|
|
||||||
$projectUuid = data_get($application, 'environment.project.uuid');
|
// $projectUuid = data_get($application, 'environment.project.uuid');
|
||||||
$applicationUuid = data_get($application, 'uuid');
|
// $applicationUuid = data_get($application, 'uuid');
|
||||||
$environment = data_get($application, 'environment.name');
|
// $environment = data_get($application, 'environment.name');
|
||||||
|
|
||||||
if ($projectUuid && $applicationUuid && $environment) {
|
// if ($projectUuid && $applicationUuid && $environment) {
|
||||||
$url = base_url().'/project/'.$projectUuid.'/'.$environment.'/application/'.$applicationUuid;
|
// $url = base_url().'/project/'.$projectUuid.'/'.$environment.'/application/'.$applicationUuid;
|
||||||
} else {
|
// } else {
|
||||||
$url = null;
|
// $url = null;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
// // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
}
|
// }
|
||||||
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
// $notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
||||||
foreach ($notRunningApplicationPreviews as $previewId) {
|
// foreach ($notRunningApplicationPreviews as $previewId) {
|
||||||
$preview = $previews->where('id', $previewId)->first();
|
// $preview = $previews->where('id', $previewId)->first();
|
||||||
if (str($preview->status)->startsWith('exited')) {
|
// if (str($preview->status)->startsWith('exited')) {
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
$preview->update(['status' => 'exited']);
|
// $preview->update(['status' => 'exited']);
|
||||||
|
|
||||||
$name = data_get($preview, 'name');
|
// $name = data_get($preview, 'name');
|
||||||
$fqdn = data_get($preview, 'fqdn');
|
// $fqdn = data_get($preview, 'fqdn');
|
||||||
|
|
||||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
// $containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||||
|
|
||||||
$projectUuid = data_get($preview, 'application.environment.project.uuid');
|
// $projectUuid = data_get($preview, 'application.environment.project.uuid');
|
||||||
$environmentName = data_get($preview, 'application.environment.name');
|
// $environmentName = data_get($preview, 'application.environment.name');
|
||||||
$applicationUuid = data_get($preview, 'application.uuid');
|
// $applicationUuid = data_get($preview, 'application.uuid');
|
||||||
|
|
||||||
if ($projectUuid && $applicationUuid && $environmentName) {
|
// if ($projectUuid && $applicationUuid && $environmentName) {
|
||||||
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/application/'.$applicationUuid;
|
// $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/application/'.$applicationUuid;
|
||||||
} else {
|
// } else {
|
||||||
$url = null;
|
// $url = null;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
// // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
}
|
// }
|
||||||
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
// $notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
||||||
foreach ($notRunningDatabases as $database) {
|
// foreach ($notRunningDatabases as $database) {
|
||||||
$database = $databases->where('id', $database)->first();
|
// $database = $databases->where('id', $database)->first();
|
||||||
if (str($database->status)->startsWith('exited')) {
|
// if (str($database->status)->startsWith('exited')) {
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
$database->update(['status' => 'exited']);
|
// $database->update(['status' => 'exited']);
|
||||||
|
|
||||||
$name = data_get($database, 'name');
|
// $name = data_get($database, 'name');
|
||||||
$fqdn = data_get($database, 'fqdn');
|
// $fqdn = data_get($database, 'fqdn');
|
||||||
|
|
||||||
$containerName = $name;
|
// $containerName = $name;
|
||||||
|
|
||||||
$projectUuid = data_get($database, 'environment.project.uuid');
|
// $projectUuid = data_get($database, 'environment.project.uuid');
|
||||||
$environmentName = data_get($database, 'environment.name');
|
// $environmentName = data_get($database, 'environment.name');
|
||||||
$databaseUuid = data_get($database, 'uuid');
|
// $databaseUuid = data_get($database, 'uuid');
|
||||||
|
|
||||||
if ($projectUuid && $databaseUuid && $environmentName) {
|
// if ($projectUuid && $databaseUuid && $environmentName) {
|
||||||
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/database/'.$databaseUuid;
|
// $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/database/'.$databaseUuid;
|
||||||
} else {
|
// } else {
|
||||||
$url = null;
|
// $url = null;
|
||||||
}
|
// }
|
||||||
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
// // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Check if proxy is running
|
// // Check if proxy is running
|
||||||
$this->server->proxyType();
|
// $this->server->proxyType();
|
||||||
$foundProxyContainer = $containers->filter(function ($value, $key) {
|
// $foundProxyContainer = $this->containers->filter(function ($value, $key) {
|
||||||
if ($this->server->isSwarm()) {
|
// if ($this->server->isSwarm()) {
|
||||||
// TODO: fix this with sentinel
|
// // TODO: fix this with sentinel
|
||||||
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
// return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
||||||
} else {
|
// } else {
|
||||||
return data_get($value, 'name') === 'coolify-proxy';
|
// return data_get($value, 'name') === 'coolify-proxy';
|
||||||
}
|
// }
|
||||||
})->first();
|
// })->first();
|
||||||
if (! $foundProxyContainer) {
|
// if (! $foundProxyContainer) {
|
||||||
try {
|
// try {
|
||||||
$shouldStart = CheckProxy::run($this->server);
|
// $shouldStart = CheckProxy::run($this->server);
|
||||||
if ($shouldStart) {
|
// if ($shouldStart) {
|
||||||
StartProxy::run($this->server, false);
|
// StartProxy::run($this->server, false);
|
||||||
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
// $this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||||
}
|
// }
|
||||||
} catch (\Throwable $e) {
|
// } catch (\Throwable $e) {
|
||||||
ray($e);
|
// ray($e);
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
$this->server->proxy->status = data_get($foundProxyContainer, 'state');
|
// $this->server->proxy->status = data_get($foundProxyContainer, 'state');
|
||||||
$this->server->save();
|
// $this->server->save();
|
||||||
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
// $connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
||||||
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
// instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||||
}
|
// }
|
||||||
} catch (\Exception $e) {
|
// } catch (\Exception $e) {
|
||||||
// send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
|
// // send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
|
||||||
ray($e->getMessage());
|
// ray($e->getMessage());
|
||||||
|
|
||||||
return handleError($e);
|
// return handleError($e);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
private function old_way()
|
private function old_way()
|
||||||
{
|
{
|
||||||
if ($this->server->isSwarm()) {
|
if ($this->containers === null) {
|
||||||
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
|
['containers' => $this->containers,'containerReplicates' => $this->containerReplicates] = $this->server->getContainers();
|
||||||
$containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
|
|
||||||
} else {
|
|
||||||
// Precheck for containers
|
|
||||||
$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, false);
|
|
||||||
$containerReplicates = null;
|
|
||||||
}
|
}
|
||||||
if (is_null($containers)) {
|
|
||||||
|
if (is_null($this->containers)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$containers = format_docker_command_output_to_json($containers);
|
if ($this->containerReplicates) {
|
||||||
if ($containerReplicates) {
|
foreach ($this->containerReplicates as $containerReplica) {
|
||||||
$containerReplicates = format_docker_command_output_to_json($containerReplicates);
|
|
||||||
foreach ($containerReplicates as $containerReplica) {
|
|
||||||
$name = data_get($containerReplica, 'Name');
|
$name = data_get($containerReplica, 'Name');
|
||||||
$containers = $containers->map(function ($container) use ($name, $containerReplica) {
|
$this->containers = $this->containers->map(function ($container) use ($name, $containerReplica) {
|
||||||
if (data_get($container, 'Spec.Name') === $name) {
|
if (data_get($container, 'Spec.Name') === $name) {
|
||||||
$replicas = data_get($containerReplica, 'Replicas');
|
$replicas = data_get($containerReplica, 'Replicas');
|
||||||
$running = str($replicas)->explode('/')[0];
|
$running = str($replicas)->explode('/')[0];
|
||||||
|
|
@ -407,7 +401,7 @@ private function old_way()
|
||||||
$foundDatabases = [];
|
$foundDatabases = [];
|
||||||
$foundServices = [];
|
$foundServices = [];
|
||||||
|
|
||||||
foreach ($containers as $container) {
|
foreach ($this->containers as $container) {
|
||||||
if ($this->server->isSwarm()) {
|
if ($this->server->isSwarm()) {
|
||||||
$labels = data_get($container, 'Spec.Labels');
|
$labels = data_get($container, 'Spec.Labels');
|
||||||
$uuid = data_get($labels, 'coolify.name');
|
$uuid = data_get($labels, 'coolify.name');
|
||||||
|
|
@ -461,7 +455,7 @@ private function old_way()
|
||||||
if ($uuid) {
|
if ($uuid) {
|
||||||
$isPublic = data_get($service_db, 'is_public');
|
$isPublic = data_get($service_db, 'is_public');
|
||||||
if ($isPublic) {
|
if ($isPublic) {
|
||||||
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
|
||||||
if ($this->server->isSwarm()) {
|
if ($this->server->isSwarm()) {
|
||||||
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -486,7 +480,7 @@ private function old_way()
|
||||||
$database->update(['status' => $containerStatus]);
|
$database->update(['status' => $containerStatus]);
|
||||||
}
|
}
|
||||||
if ($isPublic) {
|
if ($isPublic) {
|
||||||
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
|
||||||
if ($this->server->isSwarm()) {
|
if ($this->server->isSwarm()) {
|
||||||
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -659,7 +653,7 @@ private function old_way()
|
||||||
|
|
||||||
// Check if proxy is running
|
// Check if proxy is running
|
||||||
$this->server->proxyType();
|
$this->server->proxyType();
|
||||||
$foundProxyContainer = $containers->filter(function ($value, $key) {
|
$foundProxyContainer = $this->containers->filter(function ($value, $key) {
|
||||||
if ($this->server->isSwarm()) {
|
if ($this->server->isSwarm()) {
|
||||||
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ public function handle(Server $server, bool $reset = false)
|
||||||
"cat $proxy_path/docker-compose.yml",
|
"cat $proxy_path/docker-compose.yml",
|
||||||
];
|
];
|
||||||
$proxy_configuration = instant_remote_process($payload, $server, false);
|
$proxy_configuration = instant_remote_process($payload, $server, false);
|
||||||
|
|
||||||
if ($reset || ! $proxy_configuration || is_null($proxy_configuration)) {
|
if ($reset || ! $proxy_configuration || is_null($proxy_configuration)) {
|
||||||
$proxy_configuration = str(generate_default_proxy_configuration($server))->trim()->value;
|
$proxy_configuration = str(generate_default_proxy_configuration($server))->trim()->value;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ public function handle(Server $server, $fromUI = false)
|
||||||
if (is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop) {
|
if (is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
['uptime' => $uptime, 'error' => $error] = $server->validateConnection();
|
['uptime' => $uptime, 'error' => $error] = $server->validateConnection(false);
|
||||||
if (! $uptime) {
|
if (! $uptime) {
|
||||||
throw new \Exception($error);
|
throw new \Exception($error);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,28 +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
|
$commands = $this->getCommands();
|
||||||
if ($force) {
|
|
||||||
instant_remote_process(['docker image prune -af'], $server, false);
|
foreach ($commands as $command) {
|
||||||
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
|
instant_remote_process([$command], $server, false);
|
||||||
instant_remote_process(['docker builder prune -af'], $server, false);
|
|
||||||
} else {
|
|
||||||
instant_remote_process(['docker image prune -f'], $server, false);
|
|
||||||
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
|
|
||||||
instant_remote_process(['docker builder prune -f'], $server, false);
|
|
||||||
}
|
}
|
||||||
// cleanup networks
|
}
|
||||||
// $networks = collectDockerNetworksByServer($server);
|
|
||||||
// $proxyNetworks = collectProxyDockerNetworksByServer($server);
|
private function getCommands(): array
|
||||||
// $diff = $proxyNetworks->diff($networks);
|
{
|
||||||
// if ($diff->count() > 0) {
|
$settings = InstanceSettings::get();
|
||||||
// $diff->map(function ($network) use ($server) {
|
$helperImageVersion = data_get($settings, 'helper_version');
|
||||||
// instant_remote_process(["docker network disconnect $network coolify-proxy"], $server);
|
$helperImage = config('coolify.helper_image');
|
||||||
// instant_remote_process(["docker network rm $network"], $server);
|
$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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,7 @@ public function handle(Server $server)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if ($type === 'none') {
|
if ($type === 'none') {
|
||||||
$command = [
|
return 'No log drain is enabled.';
|
||||||
"echo 'Stopping old Fluent Bit'",
|
|
||||||
'docker rm -f coolify-log-drain || true',
|
|
||||||
];
|
|
||||||
|
|
||||||
return instant_remote_process($command, $server);
|
|
||||||
} elseif ($type === 'newrelic') {
|
} elseif ($type === 'newrelic') {
|
||||||
if (! $server->settings->is_logdrain_newrelic_enabled) {
|
if (! $server->settings->is_logdrain_newrelic_enabled) {
|
||||||
throw new \Exception('New Relic log drain is not enabled.');
|
throw new \Exception('New Relic log drain is not enabled.');
|
||||||
|
|
@ -52,7 +47,11 @@ public function handle(Server $server)
|
||||||
[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 *
|
||||||
|
|
@ -103,7 +102,11 @@ public function handle(Server $server)
|
||||||
[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 *
|
||||||
|
|
|
||||||
20
app/Actions/Server/StopLogDrain.php
Normal file
20
app/Actions/Server/StopLogDrain.php
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class StopLogDrain
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(Server $server)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return instant_remote_process(['docker rm -f coolify-log-drain || true'], $server);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace App\Actions\Server;
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Jobs\PullHelperImageJob;
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
@ -20,12 +21,11 @@ public function handle($manual_update = false)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$settings = InstanceSettings::get();
|
$settings = InstanceSettings::get();
|
||||||
ray('Running InstanceAutoUpdateJob');
|
|
||||||
$this->server = Server::find(0);
|
$this->server = Server::find(0);
|
||||||
if (! $this->server) {
|
if (! $this->server) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CleanupDocker::dispatch($this->server, false)->onQueue('high');
|
CleanupDocker::dispatch($this->server)->onQueue('high');
|
||||||
$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) {
|
||||||
|
|
@ -40,6 +40,8 @@ public function handle($manual_update = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->update();
|
$this->update();
|
||||||
|
$settings->new_version_available = false;
|
||||||
|
$settings->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|
@ -48,17 +50,24 @@ public function handle($manual_update = false)
|
||||||
private function update()
|
private function update()
|
||||||
{
|
{
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
ray('Running in dev mode');
|
|
||||||
remote_process([
|
remote_process([
|
||||||
'sleep 10',
|
'sleep 10',
|
||||||
], $this->server);
|
], $this->server);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$all_servers = Server::all();
|
||||||
|
$servers = $all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
|
||||||
|
foreach ($servers as $server) {
|
||||||
|
PullHelperImageJob::dispatch($server);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
67
app/Actions/Server/ValidateServer.php
Normal file
67
app/Actions/Server/ValidateServer.php
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class ValidateServer
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public ?string $uptime = null;
|
||||||
|
|
||||||
|
public ?string $error = null;
|
||||||
|
|
||||||
|
public ?string $supported_os_type = null;
|
||||||
|
|
||||||
|
public ?string $docker_installed = null;
|
||||||
|
|
||||||
|
public ?string $docker_compose_installed = null;
|
||||||
|
|
||||||
|
public ?string $docker_version = null;
|
||||||
|
|
||||||
|
public function handle(Server $server)
|
||||||
|
{
|
||||||
|
$server->update([
|
||||||
|
'validation_logs' => null,
|
||||||
|
]);
|
||||||
|
['uptime' => $this->uptime, 'error' => $error] = $server->validateConnection();
|
||||||
|
if (! $this->uptime) {
|
||||||
|
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br>Check this <a target="_blank" class="text-black underline dark:text-white" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br><div class="text-error">Error: '.$error.'</div>';
|
||||||
|
$server->update([
|
||||||
|
'validation_logs' => $this->error,
|
||||||
|
]);
|
||||||
|
throw new \Exception($this->error);
|
||||||
|
}
|
||||||
|
$this->supported_os_type = $server->validateOS();
|
||||||
|
if (! $this->supported_os_type) {
|
||||||
|
$this->error = 'Server OS type is not supported. Please install Docker manually before continuing: <a target="_blank" class="text-black underline dark:text-white" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||||
|
$server->update([
|
||||||
|
'validation_logs' => $this->error,
|
||||||
|
]);
|
||||||
|
throw new \Exception($this->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->docker_installed = $server->validateDockerEngine();
|
||||||
|
$this->docker_compose_installed = $server->validateDockerCompose();
|
||||||
|
if (! $this->docker_installed || ! $this->docker_compose_installed) {
|
||||||
|
$this->error = 'Docker Engine is not installed. Please install Docker manually before continuing: <a target="_blank" class="text-black underline dark:text-white" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||||
|
$server->update([
|
||||||
|
'validation_logs' => $this->error,
|
||||||
|
]);
|
||||||
|
throw new \Exception($this->error);
|
||||||
|
}
|
||||||
|
$this->docker_version = $server->validateDockerEngineVersion();
|
||||||
|
|
||||||
|
if ($this->docker_version) {
|
||||||
|
return 'OK';
|
||||||
|
} else {
|
||||||
|
$this->error = 'Docker Engine is not installed. Please install Docker manually before continuing: <a target="_blank" class="text-black underline dark:text-white" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||||
|
$server->update([
|
||||||
|
'validation_logs' => $this->error,
|
||||||
|
]);
|
||||||
|
throw new \Exception($this->error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,17 +3,18 @@
|
||||||
namespace App\Actions\Service;
|
namespace App\Actions\Service;
|
||||||
|
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
|
use App\Actions\Server\CleanupDocker;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class DeleteService
|
class DeleteService
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Service $service)
|
public function handle(Service $service, bool $deleteConfigurations, bool $deleteVolumes, bool $dockerCleanup, bool $deleteConnectedNetworks)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$server = data_get($service, 'server');
|
$server = data_get($service, 'server');
|
||||||
if ($server->isFunctional()) {
|
if ($deleteVolumes && $server->isFunctional()) {
|
||||||
$storagesToDelete = collect([]);
|
$storagesToDelete = collect([]);
|
||||||
|
|
||||||
$service->environment_variables()->delete();
|
$service->environment_variables()->delete();
|
||||||
|
|
@ -33,13 +34,29 @@ public function handle(Service $service)
|
||||||
foreach ($storagesToDelete as $storage) {
|
foreach ($storagesToDelete as $storage) {
|
||||||
$commands[] = "docker volume rm -f $storage->name";
|
$commands[] = "docker volume rm -f $storage->name";
|
||||||
}
|
}
|
||||||
$commands[] = "docker rm -f $service->uuid";
|
|
||||||
|
|
||||||
instant_remote_process($commands, $server, false);
|
// Execute volume deletion first, this must be done first otherwise volumes will not be deleted.
|
||||||
|
if (!empty($commands)) {
|
||||||
|
foreach ($commands as $command) {
|
||||||
|
$result = instant_remote_process([$command], $server, false);
|
||||||
|
if ($result !== 0) {
|
||||||
|
ray("Failed to execute: $command");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($deleteConnectedNetworks) {
|
||||||
|
$service->delete_connected_networks($service->uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
instant_remote_process(["docker rm -f $service->uuid"], $server, throwError: false);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
throw new \Exception($e->getMessage());
|
throw new \Exception($e->getMessage());
|
||||||
} finally {
|
} finally {
|
||||||
|
if ($deleteConfigurations) {
|
||||||
|
$service->delete_configurations();
|
||||||
|
}
|
||||||
foreach ($service->applications()->get() as $application) {
|
foreach ($service->applications()->get() as $application) {
|
||||||
$application->forceDelete();
|
$application->forceDelete();
|
||||||
}
|
}
|
||||||
|
|
@ -50,6 +67,11 @@ public function handle(Service $service)
|
||||||
$task->delete();
|
$task->delete();
|
||||||
}
|
}
|
||||||
$service->tags()->detach();
|
$service->tags()->detach();
|
||||||
|
$service->forceDelete();
|
||||||
|
|
||||||
|
if ($dockerCleanup) {
|
||||||
|
CleanupDocker::run($server, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,10 @@ public function handle(Service $service)
|
||||||
$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()}.'";
|
||||||
$commands[] = "echo 'Creating Docker network.'";
|
if ($service->networks()->count() > 0) {
|
||||||
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid";
|
$commands[] = "echo 'Creating Docker network.'";
|
||||||
|
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid";
|
||||||
|
}
|
||||||
$commands[] = 'echo Starting service.';
|
$commands[] = 'echo Starting service.';
|
||||||
$commands[] = "echo 'Pulling images.'";
|
$commands[] = "echo 'Pulling images.'";
|
||||||
$commands[] = 'docker compose pull';
|
$commands[] = 'docker compose pull';
|
||||||
|
|
@ -29,7 +31,7 @@ public function handle(Service $service)
|
||||||
$network = $service->destination->network;
|
$network = $service->destination->network;
|
||||||
$serviceNames = data_get(Yaml::parse($compose), 'services', []);
|
$serviceNames = data_get(Yaml::parse($compose), 'services', []);
|
||||||
foreach ($serviceNames as $serviceName => $serviceConfig) {
|
foreach ($serviceNames as $serviceName => $serviceConfig) {
|
||||||
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} || true";
|
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} >/dev/null 2>&1 || true";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$activity = remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged');
|
$activity = remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged');
|
||||||
|
|
|
||||||
|
|
@ -3,38 +3,33 @@
|
||||||
namespace App\Actions\Service;
|
namespace App\Actions\Service;
|
||||||
|
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
|
use App\Actions\Server\CleanupDocker;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class StopService
|
class StopService
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Service $service)
|
public function handle(Service $service, bool $isDeleteOperation = false, bool $dockerCleanup = true)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$server = $service->destination->server;
|
$server = $service->destination->server;
|
||||||
if (! $server->isFunctional()) {
|
if (!$server->isFunctional()) {
|
||||||
return 'Server is not functional';
|
return 'Server is not functional';
|
||||||
}
|
}
|
||||||
ray('Stopping service: '.$service->name);
|
|
||||||
$applications = $service->applications()->get();
|
|
||||||
foreach ($applications as $application) {
|
|
||||||
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server, false);
|
|
||||||
$application->update(['status' => 'exited']);
|
|
||||||
}
|
|
||||||
$dbs = $service->databases()->get();
|
|
||||||
foreach ($dbs as $db) {
|
|
||||||
instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server, false);
|
|
||||||
$db->update(['status' => 'exited']);
|
|
||||||
}
|
|
||||||
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy"], $service->server);
|
|
||||||
instant_remote_process(["docker network rm {$service->uuid}"], $service->server);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
echo $e->getMessage();
|
|
||||||
ray($e->getMessage());
|
|
||||||
|
|
||||||
|
$containersToStop = $service->getContainersToStop();
|
||||||
|
$service->stopContainers($containersToStop, $server);
|
||||||
|
|
||||||
|
if (!$isDeleteOperation) {
|
||||||
|
$service->delete_connected_networks($service->uuid);
|
||||||
|
if ($dockerCleanup) {
|
||||||
|
CleanupDocker::run($server, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
ray($e->getMessage());
|
||||||
return $e->getMessage();
|
return $e->getMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,12 @@ public function handle()
|
||||||
} else {
|
} else {
|
||||||
echo "Running database cleanup in dry-run mode...\n";
|
echo "Running database cleanup in dry-run mode...\n";
|
||||||
}
|
}
|
||||||
$keep_days = 60;
|
if (isCloud()) {
|
||||||
|
// Later on we can increase this to 180 days or dynamically set
|
||||||
|
$keep_days = 60;
|
||||||
|
} else {
|
||||||
|
$keep_days = 60;
|
||||||
|
}
|
||||||
echo "Keep days: $keep_days\n";
|
echo "Keep days: $keep_days\n";
|
||||||
// Cleanup failed jobs table
|
// Cleanup failed jobs table
|
||||||
$failed_jobs = DB::table('failed_jobs')->where('failed_at', '<', now()->subDays(1));
|
$failed_jobs = DB::table('failed_jobs')->where('failed_at', '<', now()->subDays(1));
|
||||||
|
|
|
||||||
|
|
@ -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 @@ private function cleanup_stucked_resources()
|
||||||
} 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 @@ private function cleanup_stucked_resources()
|
||||||
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();
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ public function handle()
|
||||||
if ($servers->count() > 0) {
|
if ($servers->count() > 0) {
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
echo "Cleanup unreachable server ($server->id) with name $server->name";
|
echo "Cleanup unreachable server ($server->id) with name $server->name";
|
||||||
send_internal_notification("Server $server->name is unreachable for 7 days. Cleaning up...");
|
// send_internal_notification("Server $server->name is unreachable for 7 days. Cleaning up...");
|
||||||
$server->update([
|
$server->update([
|
||||||
'ip' => '1.2.3.4',
|
'ip' => '1.2.3.4',
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ public function handle()
|
||||||
}
|
}
|
||||||
set_transanctional_email_settings();
|
set_transanctional_email_settings();
|
||||||
|
|
||||||
$this->mail = new MailMessage();
|
$this->mail = new MailMessage;
|
||||||
$this->mail->subject('Test Email');
|
$this->mail->subject('Test Email');
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
case 'updates':
|
case 'updates':
|
||||||
|
|
@ -107,7 +107,7 @@ public function handle()
|
||||||
$confirmed = confirm('Are you sure?');
|
$confirmed = confirm('Are you sure?');
|
||||||
if ($confirmed) {
|
if ($confirmed) {
|
||||||
foreach ($emails as $email) {
|
foreach ($emails as $email) {
|
||||||
$this->mail = new MailMessage();
|
$this->mail = new MailMessage;
|
||||||
$this->mail->subject('One-click Services, Docker Compose support');
|
$this->mail->subject('One-click Services, Docker Compose support');
|
||||||
$unsubscribeUrl = route('unsubscribe.marketing.emails', [
|
$unsubscribeUrl = route('unsubscribe.marketing.emails', [
|
||||||
'token' => encrypt($email),
|
'token' => encrypt($email),
|
||||||
|
|
@ -118,7 +118,7 @@ public function handle()
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'emails-test':
|
case 'emails-test':
|
||||||
$this->mail = (new Test())->toMail();
|
$this->mail = (new Test)->toMail();
|
||||||
$this->sendEmail();
|
$this->sendEmail();
|
||||||
break;
|
break;
|
||||||
case 'database-backup-statuses-daily':
|
case 'database-backup-statuses-daily':
|
||||||
|
|
@ -224,7 +224,7 @@ public function handle()
|
||||||
// $this->sendEmail();
|
// $this->sendEmail();
|
||||||
// break;
|
// break;
|
||||||
case 'waitlist-invitation-link':
|
case 'waitlist-invitation-link':
|
||||||
$this->mail = new MailMessage();
|
$this->mail = new MailMessage;
|
||||||
$this->mail->view('emails.waitlist-invitation', [
|
$this->mail->view('emails.waitlist-invitation', [
|
||||||
'loginLink' => 'https://coolify.io',
|
'loginLink' => 'https://coolify.io',
|
||||||
]);
|
]);
|
||||||
|
|
@ -241,7 +241,7 @@ public function handle()
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'realusers-before-trial':
|
case 'realusers-before-trial':
|
||||||
$this->mail = new MailMessage();
|
$this->mail = new MailMessage;
|
||||||
$this->mail->view('emails.before-trial-conversion');
|
$this->mail->view('emails.before-trial-conversion');
|
||||||
$this->mail->subject('Trial period has been added for all subscription plans.');
|
$this->mail->subject('Trial period has been added for all subscription plans.');
|
||||||
$teams = Team::doesntHave('subscription')->where('id', '!=', 0)->get();
|
$teams = Team::doesntHave('subscription')->where('id', '!=', 0)->get();
|
||||||
|
|
@ -287,7 +287,7 @@ public function handle()
|
||||||
foreach ($admins as $admin) {
|
foreach ($admins as $admin) {
|
||||||
$this->info($admin);
|
$this->info($admin);
|
||||||
}
|
}
|
||||||
$this->mail = new MailMessage();
|
$this->mail = new MailMessage;
|
||||||
$this->mail->view('emails.server-lost-connection', [
|
$this->mail->view('emails.server-lost-connection', [
|
||||||
'name' => $server->name,
|
'name' => $server->name,
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use App\Actions\Server\StopSentinel;
|
use App\Actions\Server\StopSentinel;
|
||||||
|
use App\Enums\ActivityTypes;
|
||||||
use App\Enums\ApplicationDeploymentStatus;
|
use App\Enums\ApplicationDeploymentStatus;
|
||||||
use App\Jobs\CleanupHelperContainersJob;
|
use App\Jobs\CleanupHelperContainersJob;
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
|
|
@ -12,22 +13,27 @@
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
class Init extends Command
|
class Init extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments}';
|
protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments} {--cleanup-proxy-networks}';
|
||||||
|
|
||||||
protected $description = 'Cleanup instance related stuffs';
|
protected $description = 'Cleanup instance related stuffs';
|
||||||
|
|
||||||
|
public $servers = null;
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
$this->servers = Server::all();
|
||||||
$this->alive();
|
$this->alive();
|
||||||
get_public_ips();
|
get_public_ips();
|
||||||
if (version_compare('4.0.0-beta.312', config('version'), '<=')) {
|
if (version_compare('4.0.0-beta.312', config('version'), '<=')) {
|
||||||
$servers = Server::all();
|
foreach ($this->servers as $server) {
|
||||||
foreach ($servers as $server) {
|
if ($server->settings->is_metrics_enabled === true) {
|
||||||
$server->settings->update(['is_metrics_enabled' => false]);
|
$server->settings->update(['is_metrics_enabled' => false]);
|
||||||
|
}
|
||||||
if ($server->isFunctional()) {
|
if ($server->isFunctional()) {
|
||||||
StopSentinel::dispatch($server);
|
StopSentinel::dispatch($server);
|
||||||
}
|
}
|
||||||
|
|
@ -36,7 +42,7 @@ public function handle()
|
||||||
|
|
||||||
$full_cleanup = $this->option('full-cleanup');
|
$full_cleanup = $this->option('full-cleanup');
|
||||||
$cleanup_deployments = $this->option('cleanup-deployments');
|
$cleanup_deployments = $this->option('cleanup-deployments');
|
||||||
|
$cleanup_proxy_networks = $this->option('cleanup-proxy-networks');
|
||||||
$this->replace_slash_in_environment_name();
|
$this->replace_slash_in_environment_name();
|
||||||
if ($cleanup_deployments) {
|
if ($cleanup_deployments) {
|
||||||
echo "Running cleanup deployments.\n";
|
echo "Running cleanup deployments.\n";
|
||||||
|
|
@ -44,17 +50,26 @@ public function handle()
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if ($cleanup_proxy_networks) {
|
||||||
|
echo "Running cleanup proxy networks.\n";
|
||||||
|
$this->cleanup_unused_network_from_coolify_proxy();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
if ($full_cleanup) {
|
if ($full_cleanup) {
|
||||||
// Required for falsely deleted coolify db
|
// Required for falsely deleted coolify db
|
||||||
$this->restore_coolify_db_backup();
|
$this->restore_coolify_db_backup();
|
||||||
|
$this->update_traefik_labels();
|
||||||
|
$this->cleanup_unused_network_from_coolify_proxy();
|
||||||
|
$this->cleanup_unnecessary_dynamic_proxy_configuration();
|
||||||
$this->cleanup_in_progress_application_deployments();
|
$this->cleanup_in_progress_application_deployments();
|
||||||
$this->cleanup_stucked_helper_containers();
|
$this->cleanup_stucked_helper_containers();
|
||||||
$this->call('cleanup:queue');
|
$this->call('cleanup:queue');
|
||||||
$this->call('cleanup:stucked-resources');
|
$this->call('cleanup:stucked-resources');
|
||||||
if (! isCloud()) {
|
if (! isCloud()) {
|
||||||
try {
|
try {
|
||||||
$server = Server::find(0)->first();
|
$localhost = $this->servers->where('id', 0)->first();
|
||||||
$server->setupDynamicProxyConfiguration();
|
$localhost->setupDynamicProxyConfiguration();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
|
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
|
|
@ -68,6 +83,13 @@ public function handle()
|
||||||
$settings->update(['is_auto_update_enabled' => false]);
|
$settings->update(['is_auto_update_enabled' => false]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (isCloud()) {
|
||||||
|
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
|
||||||
|
if ($response->successful()) {
|
||||||
|
$services = $response->json();
|
||||||
|
File::put(base_path('templates/service-templates.json'), json_encode($services));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -75,6 +97,82 @@ public function handle()
|
||||||
$this->call('cleanup:stucked-resources');
|
$this->call('cleanup:stucked-resources');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function update_traefik_labels()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Server::where('proxy->type', 'TRAEFIK_V2')->update(['proxy->type' => 'TRAEFIK']);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in updating traefik labels: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cleanup_unnecessary_dynamic_proxy_configuration()
|
||||||
|
{
|
||||||
|
if (isCloud()) {
|
||||||
|
foreach ($this->servers as $server) {
|
||||||
|
try {
|
||||||
|
if (! $server->isFunctional()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($server->id === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$file = $server->proxyPath().'/dynamic/coolify.yaml';
|
||||||
|
|
||||||
|
return instant_remote_process([
|
||||||
|
"rm -f $file",
|
||||||
|
], $server, false);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning up unnecessary dynamic proxy configuration: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cleanup_unused_network_from_coolify_proxy()
|
||||||
|
{
|
||||||
|
if (isCloud()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach ($this->servers as $server) {
|
||||||
|
if (! $server->isFunctional()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (! $server->isProxyShouldRun()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
['networks' => $networks, 'allNetworks' => $allNetworks] = collectDockerNetworksByServer($server);
|
||||||
|
$removeNetworks = $allNetworks->diff($networks);
|
||||||
|
$commands = collect();
|
||||||
|
foreach ($removeNetworks as $network) {
|
||||||
|
$out = instant_remote_process(["docker network inspect -f json $network | jq '.[].Containers | if . == {} then null else . end'"], $server, false);
|
||||||
|
if (empty($out)) {
|
||||||
|
$commands->push("docker network disconnect $network coolify-proxy >/dev/null 2>&1 || true");
|
||||||
|
$commands->push("docker network rm $network >/dev/null 2>&1 || true");
|
||||||
|
} else {
|
||||||
|
$data = collect(json_decode($out, true));
|
||||||
|
if ($data->count() === 1) {
|
||||||
|
// If only coolify-proxy itself is connected to that network (it should not be possible, but who knows)
|
||||||
|
$isCoolifyProxyItself = data_get($data->first(), 'Name') === 'coolify-proxy';
|
||||||
|
if ($isCoolifyProxyItself) {
|
||||||
|
$commands->push("docker network disconnect $network coolify-proxy >/dev/null 2>&1 || true");
|
||||||
|
$commands->push("docker network rm $network >/dev/null 2>&1 || true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($commands->isNotEmpty()) {
|
||||||
|
echo "Cleaning up unused networks from coolify proxy\n";
|
||||||
|
remote_process(command: $commands, type: ActivityTypes::INLINE->value, server: $server, ignore_errors: false);
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning up unused networks from coolify proxy: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function restore_coolify_db_backup()
|
private function restore_coolify_db_backup()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|
@ -102,8 +200,7 @@ private function restore_coolify_db_backup()
|
||||||
|
|
||||||
private function cleanup_stucked_helper_containers()
|
private function cleanup_stucked_helper_containers()
|
||||||
{
|
{
|
||||||
$servers = Server::all();
|
foreach ($this->servers as $server) {
|
||||||
foreach ($servers as $server) {
|
|
||||||
if ($server->isFunctional()) {
|
if ($server->isFunctional()) {
|
||||||
CleanupHelperContainersJob::dispatch($server);
|
CleanupHelperContainersJob::dispatch($server);
|
||||||
}
|
}
|
||||||
|
|
@ -148,7 +245,6 @@ private function alive()
|
||||||
private function cleanup_in_progress_application_deployments()
|
private function cleanup_in_progress_application_deployments()
|
||||||
{
|
{
|
||||||
// Cleanup any failed deployments
|
// Cleanup any failed deployments
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isCloud()) {
|
if (isCloud()) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
26
app/Console/Commands/OpenApi.php
Normal file
26
app/Console/Commands/OpenApi.php
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Process;
|
||||||
|
|
||||||
|
class OpenApi extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'openapi';
|
||||||
|
|
||||||
|
protected $description = 'Generate OpenApi file.';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
// Generate OpenAPI documentation
|
||||||
|
echo "Generating OpenAPI documentation.\n";
|
||||||
|
$process = Process::run(['/var/www/html/vendor/bin/openapi', 'app', '-o', 'openapi.yaml']);
|
||||||
|
$error = $process->errorOutput();
|
||||||
|
$error = preg_replace('/^.*an object literal,.*$/m', '', $error);
|
||||||
|
$error = preg_replace('/^\h*\v+/m', '', $error);
|
||||||
|
echo $error;
|
||||||
|
echo $process->output();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 @@ public function handle()
|
||||||
$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 @@ public function handle()
|
||||||
$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 @@ public function handle()
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
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 @@ public function handle()
|
||||||
|
|
||||||
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 @@ public function handle()
|
||||||
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 @@ public function handle()
|
||||||
}
|
}
|
||||||
|
|
||||||
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"),
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ private function send_email()
|
||||||
{
|
{
|
||||||
$token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password");
|
$token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password");
|
||||||
$loginLink = route('auth.link', ['token' => $token]);
|
$loginLink = route('auth.link', ['token' => $token]);
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage;
|
||||||
$mail->view('emails.waitlist-invitation', [
|
$mail->view('emails.waitlist-invitation', [
|
||||||
'loginLink' => $loginLink,
|
'loginLink' => $loginLink,
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,18 @@
|
||||||
|
|
||||||
namespace App\Console;
|
namespace App\Console;
|
||||||
|
|
||||||
use App\Jobs\CheckLogDrainContainerJob;
|
use App\Jobs\CheckForUpdatesJob;
|
||||||
use App\Jobs\CleanupInstanceStuffsJob;
|
use App\Jobs\CleanupInstanceStuffsJob;
|
||||||
use App\Jobs\ContainerStatusJob;
|
use App\Jobs\CleanupStaleMultiplexedConnections;
|
||||||
use App\Jobs\DatabaseBackupJob;
|
use App\Jobs\DatabaseBackupJob;
|
||||||
use App\Jobs\PullCoolifyImageJob;
|
use App\Jobs\DockerCleanupJob;
|
||||||
use App\Jobs\PullHelperImageJob;
|
use App\Jobs\PullHelperImageJob;
|
||||||
use App\Jobs\PullSentinelImageJob;
|
use App\Jobs\PullSentinelImageJob;
|
||||||
use App\Jobs\PullTemplatesFromCDN;
|
use App\Jobs\PullTemplatesFromCDN;
|
||||||
use App\Jobs\ScheduledTaskJob;
|
use App\Jobs\ScheduledTaskJob;
|
||||||
use App\Jobs\ServerStatusJob;
|
use App\Jobs\ServerCheckJob;
|
||||||
|
use App\Jobs\UpdateCoolifyJob;
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\ScheduledTask;
|
use App\Models\ScheduledTask;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
|
@ -26,25 +28,30 @@ class Kernel extends ConsoleKernel
|
||||||
protected function schedule(Schedule $schedule): void
|
protected function schedule(Schedule $schedule): void
|
||||||
{
|
{
|
||||||
$this->all_servers = Server::all();
|
$this->all_servers = Server::all();
|
||||||
|
$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();
|
||||||
$schedule->job(new PullTemplatesFromCDN)->everyTwoHours()->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_backups($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();
|
||||||
|
|
||||||
|
$schedule->job(new PullHelperImageJob)->everyFiveMinutes()->onOneServer();
|
||||||
} 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)->everyTenMinutes()->onOneServer();
|
$schedule->job(new PullTemplatesFromCDN)->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
|
||||||
$schedule->job(new PullTemplatesFromCDN)->everyThirtyMinutes()->onOneServer();
|
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||||
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
$this->schedule_updates($schedule);
|
||||||
|
|
||||||
// Server Jobs
|
// Server Jobs
|
||||||
$this->check_scheduled_backups($schedule);
|
$this->check_scheduled_backups($schedule);
|
||||||
|
|
@ -59,12 +66,42 @@ protected function schedule(Schedule $schedule): void
|
||||||
|
|
||||||
private function pull_images($schedule)
|
private function pull_images($schedule)
|
||||||
{
|
{
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
$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))->everyFiveMinutes()->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);
|
||||||
|
}
|
||||||
|
})->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
|
||||||
}
|
}
|
||||||
$schedule->job(new PullHelperImageJob($server))->everyFiveMinutes()->onOneServer();
|
}
|
||||||
|
$schedule->job(new PullHelperImageJob)
|
||||||
|
->cron($settings->update_check_frequency)
|
||||||
|
->timezone($settings->instance_timezone)
|
||||||
|
->onOneServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function schedule_updates($schedule)
|
||||||
|
{
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
|
||||||
|
$updateCheckFrequency = $settings->update_check_frequency;
|
||||||
|
$schedule->job(new CheckForUpdatesJob)
|
||||||
|
->cron($updateCheckFrequency)
|
||||||
|
->timezone($settings->instance_timezone)
|
||||||
|
->onOneServer();
|
||||||
|
|
||||||
|
if ($settings->is_auto_update_enabled) {
|
||||||
|
$autoUpdateFrequency = $settings->auto_update_frequency;
|
||||||
|
$schedule->job(new UpdateCoolifyJob)
|
||||||
|
->cron($autoUpdateFrequency)
|
||||||
|
->timezone($settings->instance_timezone)
|
||||||
|
->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,19 +111,17 @@ private function check_resources($schedule)
|
||||||
$servers = $this->all_servers->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false)->where('ip', '!=', '1.2.3.4');
|
$servers = $this->all_servers->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false)->where('ip', '!=', '1.2.3.4');
|
||||||
$own = Team::find(0)->servers;
|
$own = Team::find(0)->servers;
|
||||||
$servers = $servers->merge($own);
|
$servers = $servers->merge($own);
|
||||||
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
|
|
||||||
} else {
|
} else {
|
||||||
$servers = $this->all_servers->where('ip', '!=', '1.2.3.4');
|
$servers = $this->all_servers->where('ip', '!=', '1.2.3.4');
|
||||||
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
|
|
||||||
}
|
|
||||||
foreach ($containerServers as $server) {
|
|
||||||
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
|
|
||||||
if ($server->isLogDrainEnabled()) {
|
|
||||||
$schedule->job(new CheckLogDrainContainerJob($server))->everyMinute()->onOneServer();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
$schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer();
|
$schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
|
||||||
|
$serverTimezone = $server->settings->server_timezone;
|
||||||
|
if ($server->settings->force_docker_cleanup) {
|
||||||
|
$schedule->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer();
|
||||||
|
} else {
|
||||||
|
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->timezone($serverTimezone)->onOneServer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,12 +142,19 @@ private function check_scheduled_backups($schedule)
|
||||||
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -145,12 +187,19 @@ private function check_scheduled_tasks($schedule)
|
||||||
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
enum ProxyTypes: string
|
enum ProxyTypes: string
|
||||||
{
|
{
|
||||||
case NONE = 'NONE';
|
case NONE = 'NONE';
|
||||||
case TRAEFIK_V2 = 'TRAEFIK_V2';
|
case TRAEFIK = 'TRAEFIK';
|
||||||
case NGINX = 'NGINX';
|
case NGINX = 'NGINX';
|
||||||
case CADDY = 'CADDY';
|
case CADDY = 'CADDY';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
32
app/Events/FileStorageChanged.php
Normal file
32
app/Events/FileStorageChanged.php
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class FileStorageChanged implements ShouldBroadcast
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
|
public $teamId;
|
||||||
|
|
||||||
|
public function __construct($teamId = null)
|
||||||
|
{
|
||||||
|
ray($teamId);
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
throw new \Exception('Team id is null');
|
||||||
|
}
|
||||||
|
$this->teamId = $teamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function broadcastOn(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new PrivateChannel("team.{$this->teamId}"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
212
app/Helpers/SshMultiplexingHelper.php
Normal file
212
app/Helpers/SshMultiplexingHelper.php
Normal file
|
|
@ -0,0 +1,212 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Helpers;
|
||||||
|
|
||||||
|
use App\Models\PrivateKey;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Facades\Process;
|
||||||
|
|
||||||
|
class SshMultiplexingHelper
|
||||||
|
{
|
||||||
|
public static function serverSshConfiguration(Server $server)
|
||||||
|
{
|
||||||
|
$privateKey = PrivateKey::findOrFail($server->private_key_id);
|
||||||
|
$sshKeyLocation = $privateKey->getKeyLocation();
|
||||||
|
$muxFilename = '/var/www/html/storage/app/ssh/mux/mux_'.$server->uuid;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'sshKeyLocation' => $sshKeyLocation,
|
||||||
|
'muxFilename' => $muxFilename,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function ensureMultiplexedConnection(Server $server)
|
||||||
|
{
|
||||||
|
if (! self::isMultiplexingEnabled()) {
|
||||||
|
// ray('SSH Multiplexing: DISABLED')->red();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ray('SSH Multiplexing: ENABLED')->green();
|
||||||
|
// ray('Ensuring multiplexed connection for server:', $server);
|
||||||
|
|
||||||
|
$sshConfig = self::serverSshConfiguration($server);
|
||||||
|
$muxSocket = $sshConfig['muxFilename'];
|
||||||
|
$sshKeyLocation = $sshConfig['sshKeyLocation'];
|
||||||
|
|
||||||
|
self::validateSshKey($sshKeyLocation);
|
||||||
|
|
||||||
|
$checkCommand = "ssh -O check -o ControlPath=$muxSocket {$server->user}@{$server->ip}";
|
||||||
|
$process = Process::run($checkCommand);
|
||||||
|
|
||||||
|
if ($process->exitCode() !== 0) {
|
||||||
|
// ray('SSH Multiplexing: Existing connection check failed or not found')->orange();
|
||||||
|
// ray('Establishing new connection');
|
||||||
|
self::establishNewMultiplexedConnection($server);
|
||||||
|
} else {
|
||||||
|
// ray('SSH Multiplexing: Existing connection is valid')->green();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function establishNewMultiplexedConnection(Server $server)
|
||||||
|
{
|
||||||
|
$sshConfig = self::serverSshConfiguration($server);
|
||||||
|
$sshKeyLocation = $sshConfig['sshKeyLocation'];
|
||||||
|
$muxSocket = $sshConfig['muxFilename'];
|
||||||
|
|
||||||
|
// ray('Establishing new multiplexed connection')->blue();
|
||||||
|
// ray('SSH Key Location:', $sshKeyLocation);
|
||||||
|
// ray('Mux Socket:', $muxSocket);
|
||||||
|
|
||||||
|
$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} "
|
||||||
|
.self::getCommonSshOptions($server, $sshKeyLocation, $connectionTimeout, $serverInterval)
|
||||||
|
."{$server->user}@{$server->ip}";
|
||||||
|
|
||||||
|
// ray('Establish Command:', $establishCommand);
|
||||||
|
|
||||||
|
$establishProcess = Process::run($establishCommand);
|
||||||
|
|
||||||
|
// ray('Establish Process Exit Code:', $establishProcess->exitCode());
|
||||||
|
// ray('Establish Process Output:', $establishProcess->output());
|
||||||
|
// ray('Establish Process Error Output:', $establishProcess->errorOutput());
|
||||||
|
|
||||||
|
if ($establishProcess->exitCode() !== 0) {
|
||||||
|
// ray('Failed to establish multiplexed connection')->red();
|
||||||
|
throw new \RuntimeException('Failed to establish multiplexed connection: '.$establishProcess->errorOutput());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ray('Successfully established multiplexed connection')->green();
|
||||||
|
|
||||||
|
// Check if the mux socket file was created
|
||||||
|
if (! file_exists($muxSocket)) {
|
||||||
|
// ray('Mux socket file not found after connection establishment')->orange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function removeMuxFile(Server $server)
|
||||||
|
{
|
||||||
|
$sshConfig = self::serverSshConfiguration($server);
|
||||||
|
$muxSocket = $sshConfig['muxFilename'];
|
||||||
|
|
||||||
|
$closeCommand = "ssh -O exit -o ControlPath=$muxSocket {$server->user}@{$server->ip}";
|
||||||
|
$process = Process::run($closeCommand);
|
||||||
|
|
||||||
|
// ray('Closing multiplexed connection')->blue();
|
||||||
|
// ray('Close command:', $closeCommand);
|
||||||
|
// ray('Close process exit code:', $process->exitCode());
|
||||||
|
// ray('Close process output:', $process->output());
|
||||||
|
// ray('Close process error output:', $process->errorOutput());
|
||||||
|
|
||||||
|
if ($process->exitCode() !== 0) {
|
||||||
|
// ray('Failed to close multiplexed connection')->orange();
|
||||||
|
} else {
|
||||||
|
// ray('Successfully closed multiplexed connection')->green();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function generateScpCommand(Server $server, string $source, string $dest)
|
||||||
|
{
|
||||||
|
$sshConfig = self::serverSshConfiguration($server);
|
||||||
|
$sshKeyLocation = $sshConfig['sshKeyLocation'];
|
||||||
|
$muxSocket = $sshConfig['muxFilename'];
|
||||||
|
|
||||||
|
$timeout = config('constants.ssh.command_timeout');
|
||||||
|
|
||||||
|
$scp_command = "timeout $timeout scp ";
|
||||||
|
|
||||||
|
if (self::isMultiplexingEnabled()) {
|
||||||
|
$muxPersistTime = config('constants.ssh.mux_persist_time');
|
||||||
|
$scp_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
|
||||||
|
self::ensureMultiplexedConnection($server);
|
||||||
|
}
|
||||||
|
|
||||||
|
self::addCloudflareProxyCommand($scp_command, $server);
|
||||||
|
|
||||||
|
$scp_command .= self::getCommonSshOptions($server, $sshKeyLocation, config('constants.ssh.connection_timeout'), config('constants.ssh.server_interval'), isScp: true);
|
||||||
|
$scp_command .= "{$source} {$server->user}@{$server->ip}:{$dest}";
|
||||||
|
|
||||||
|
return $scp_command;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function generateSshCommand(Server $server, string $command)
|
||||||
|
{
|
||||||
|
if ($server->settings->force_disabled) {
|
||||||
|
throw new \RuntimeException('Server is disabled.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$sshConfig = self::serverSshConfiguration($server);
|
||||||
|
$sshKeyLocation = $sshConfig['sshKeyLocation'];
|
||||||
|
$muxSocket = $sshConfig['muxFilename'];
|
||||||
|
|
||||||
|
$timeout = config('constants.ssh.command_timeout');
|
||||||
|
|
||||||
|
$ssh_command = "timeout $timeout ssh ";
|
||||||
|
|
||||||
|
if (self::isMultiplexingEnabled()) {
|
||||||
|
$muxPersistTime = config('constants.ssh.mux_persist_time');
|
||||||
|
$ssh_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
|
||||||
|
self::ensureMultiplexedConnection($server);
|
||||||
|
}
|
||||||
|
|
||||||
|
self::addCloudflareProxyCommand($ssh_command, $server);
|
||||||
|
|
||||||
|
$ssh_command .= self::getCommonSshOptions($server, $sshKeyLocation, config('constants.ssh.connection_timeout'), config('constants.ssh.server_interval'));
|
||||||
|
|
||||||
|
$command = "PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin && $command";
|
||||||
|
$delimiter = Hash::make($command);
|
||||||
|
$command = str_replace($delimiter, '', $command);
|
||||||
|
|
||||||
|
$ssh_command .= "{$server->user}@{$server->ip} 'bash -se' << \\$delimiter".PHP_EOL
|
||||||
|
.$command.PHP_EOL
|
||||||
|
.$delimiter;
|
||||||
|
|
||||||
|
return $ssh_command;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function isMultiplexingEnabled(): bool
|
||||||
|
{
|
||||||
|
return config('constants.ssh.mux_enabled') && ! config('coolify.is_windows_docker_desktop');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function validateSshKey(string $sshKeyLocation): void
|
||||||
|
{
|
||||||
|
$checkKeyCommand = "ls $sshKeyLocation 2>/dev/null";
|
||||||
|
$keyCheckProcess = Process::run($checkKeyCommand);
|
||||||
|
|
||||||
|
if ($keyCheckProcess->exitCode() !== 0) {
|
||||||
|
throw new \RuntimeException("SSH key file not accessible: $sshKeyLocation");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function addCloudflareProxyCommand(string &$command, Server $server): void
|
||||||
|
{
|
||||||
|
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
||||||
|
$command .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getCommonSshOptions(Server $server, string $sshKeyLocation, int $connectionTimeout, int $serverInterval, bool $isScp = false): string
|
||||||
|
{
|
||||||
|
$options = "-i {$sshKeyLocation} "
|
||||||
|
.'-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null '
|
||||||
|
.'-o PasswordAuthentication=no '
|
||||||
|
."-o ConnectTimeout=$connectionTimeout "
|
||||||
|
."-o ServerAliveInterval=$serverInterval "
|
||||||
|
.'-o RequestTTY=no '
|
||||||
|
.'-o LogLevel=ERROR ';
|
||||||
|
|
||||||
|
// Bruh
|
||||||
|
if ($isScp) {
|
||||||
|
$options .= "-P {$server->port} ";
|
||||||
|
} else {
|
||||||
|
$options .= "-p {$server->port} ";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -53,6 +53,7 @@ private function removeSensitiveData($application)
|
||||||
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 @@ public function applications(Request $request)
|
||||||
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' => []],
|
||||||
],
|
],
|
||||||
|
|
@ -201,7 +203,8 @@ public function create_public_application(Request $request)
|
||||||
#[OA\Post(
|
#[OA\Post(
|
||||||
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-gh-app',
|
path: '/applications/private-github-app',
|
||||||
|
operationId: 'create-private-github-app-application',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
|
|
@ -303,6 +306,7 @@ public function create_private_gh_app_application(Request $request)
|
||||||
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 @@ public function create_private_deploy_key_application(Request $request)
|
||||||
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 @@ public function create_dockerfile_application(Request $request)
|
||||||
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 @@ public function create_dockerimage_application(Request $request)
|
||||||
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' => []],
|
||||||
],
|
],
|
||||||
|
|
@ -620,7 +627,7 @@ public function create_dockercompose_application(Request $request)
|
||||||
|
|
||||||
private function create_application(Request $request, $type)
|
private function create_application(Request $request, $type)
|
||||||
{
|
{
|
||||||
$allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths'];
|
$allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths'];
|
||||||
$teamId = getTeamIdFromToken();
|
$teamId = getTeamIdFromToken();
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
return invalidTokenResponse();
|
return invalidTokenResponse();
|
||||||
|
|
@ -708,7 +715,7 @@ private function create_application(Request $request, $type)
|
||||||
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
return $return;
|
return $return;
|
||||||
}
|
}
|
||||||
$application = new Application();
|
$application = new Application;
|
||||||
removeUnnecessaryFieldsFromRequest($request);
|
removeUnnecessaryFieldsFromRequest($request);
|
||||||
|
|
||||||
$application->fill($request->all());
|
$application->fill($request->all());
|
||||||
|
|
@ -739,7 +746,7 @@ private function create_application(Request $request, $type)
|
||||||
$application->isConfigurationChanged(true);
|
$application->isConfigurationChanged(true);
|
||||||
|
|
||||||
if ($instantDeploy) {
|
if ($instantDeploy) {
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
|
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
|
|
@ -796,7 +803,7 @@ private function create_application(Request $request, $type)
|
||||||
if (str($gitRepository)->startsWith('http') || str($gitRepository)->contains('github.com')) {
|
if (str($gitRepository)->startsWith('http') || str($gitRepository)->contains('github.com')) {
|
||||||
$gitRepository = str($gitRepository)->replace('https://', '')->replace('http://', '')->replace('github.com/', '');
|
$gitRepository = str($gitRepository)->replace('https://', '')->replace('http://', '')->replace('github.com/', '');
|
||||||
}
|
}
|
||||||
$application = new Application();
|
$application = new Application;
|
||||||
removeUnnecessaryFieldsFromRequest($request);
|
removeUnnecessaryFieldsFromRequest($request);
|
||||||
|
|
||||||
$application->fill($request->all());
|
$application->fill($request->all());
|
||||||
|
|
@ -835,7 +842,7 @@ private function create_application(Request $request, $type)
|
||||||
$application->isConfigurationChanged(true);
|
$application->isConfigurationChanged(true);
|
||||||
|
|
||||||
if ($instantDeploy) {
|
if ($instantDeploy) {
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
|
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
|
|
@ -890,7 +897,7 @@ private function create_application(Request $request, $type)
|
||||||
return response()->json(['message' => 'Private Key not found.'], 404);
|
return response()->json(['message' => 'Private Key not found.'], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$application = new Application();
|
$application = new Application;
|
||||||
removeUnnecessaryFieldsFromRequest($request);
|
removeUnnecessaryFieldsFromRequest($request);
|
||||||
|
|
||||||
$application->fill($request->all());
|
$application->fill($request->all());
|
||||||
|
|
@ -927,7 +934,7 @@ private function create_application(Request $request, $type)
|
||||||
$application->isConfigurationChanged(true);
|
$application->isConfigurationChanged(true);
|
||||||
|
|
||||||
if ($instantDeploy) {
|
if ($instantDeploy) {
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
|
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
|
|
@ -947,7 +954,7 @@ private function create_application(Request $request, $type)
|
||||||
]));
|
]));
|
||||||
} elseif ($type === 'dockerfile') {
|
} elseif ($type === 'dockerfile') {
|
||||||
if (! $request->has('name')) {
|
if (! $request->has('name')) {
|
||||||
$request->offsetSet('name', 'dockerfile-'.new Cuid2(7));
|
$request->offsetSet('name', 'dockerfile-'.new Cuid2);
|
||||||
}
|
}
|
||||||
$validator = customApiValidator($request->all(), [
|
$validator = customApiValidator($request->all(), [
|
||||||
sharedDataApplications(),
|
sharedDataApplications(),
|
||||||
|
|
@ -988,7 +995,7 @@ private function create_application(Request $request, $type)
|
||||||
$port = 80;
|
$port = 80;
|
||||||
}
|
}
|
||||||
|
|
||||||
$application = new Application();
|
$application = new Application;
|
||||||
$application->fill($request->all());
|
$application->fill($request->all());
|
||||||
$application->fqdn = $fqdn;
|
$application->fqdn = $fqdn;
|
||||||
$application->ports_exposes = $port;
|
$application->ports_exposes = $port;
|
||||||
|
|
@ -1009,7 +1016,7 @@ private function create_application(Request $request, $type)
|
||||||
$application->isConfigurationChanged(true);
|
$application->isConfigurationChanged(true);
|
||||||
|
|
||||||
if ($instantDeploy) {
|
if ($instantDeploy) {
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
|
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
|
|
@ -1025,7 +1032,7 @@ private function create_application(Request $request, $type)
|
||||||
]));
|
]));
|
||||||
} elseif ($type === 'dockerimage') {
|
} elseif ($type === 'dockerimage') {
|
||||||
if (! $request->has('name')) {
|
if (! $request->has('name')) {
|
||||||
$request->offsetSet('name', 'docker-image-'.new Cuid2(7));
|
$request->offsetSet('name', 'docker-image-'.new Cuid2);
|
||||||
}
|
}
|
||||||
$validator = customApiValidator($request->all(), [
|
$validator = customApiValidator($request->all(), [
|
||||||
sharedDataApplications(),
|
sharedDataApplications(),
|
||||||
|
|
@ -1046,7 +1053,7 @@ private function create_application(Request $request, $type)
|
||||||
if (! $request->docker_registry_image_tag) {
|
if (! $request->docker_registry_image_tag) {
|
||||||
$request->offsetSet('docker_registry_image_tag', 'latest');
|
$request->offsetSet('docker_registry_image_tag', 'latest');
|
||||||
}
|
}
|
||||||
$application = new Application();
|
$application = new Application;
|
||||||
removeUnnecessaryFieldsFromRequest($request);
|
removeUnnecessaryFieldsFromRequest($request);
|
||||||
|
|
||||||
$application->fill($request->all());
|
$application->fill($request->all());
|
||||||
|
|
@ -1067,7 +1074,7 @@ private function create_application(Request $request, $type)
|
||||||
$application->isConfigurationChanged(true);
|
$application->isConfigurationChanged(true);
|
||||||
|
|
||||||
if ($instantDeploy) {
|
if ($instantDeploy) {
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
|
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
|
|
@ -1099,7 +1106,7 @@ private function create_application(Request $request, $type)
|
||||||
], 422);
|
], 422);
|
||||||
}
|
}
|
||||||
if (! $request->has('name')) {
|
if (! $request->has('name')) {
|
||||||
$request->offsetSet('name', 'service'.new Cuid2(7));
|
$request->offsetSet('name', 'service'.new Cuid2);
|
||||||
}
|
}
|
||||||
$validator = customApiValidator($request->all(), [
|
$validator = customApiValidator($request->all(), [
|
||||||
sharedDataApplications(),
|
sharedDataApplications(),
|
||||||
|
|
@ -1140,7 +1147,7 @@ private function create_application(Request $request, $type)
|
||||||
// return $this->dispatch('error', "Invalid docker-compose file.\n$isValid");
|
// return $this->dispatch('error', "Invalid docker-compose file.\n$isValid");
|
||||||
// }
|
// }
|
||||||
|
|
||||||
$service = new Service();
|
$service = new Service;
|
||||||
removeUnnecessaryFieldsFromRequest($request);
|
removeUnnecessaryFieldsFromRequest($request);
|
||||||
$service->fill($request->all());
|
$service->fill($request->all());
|
||||||
|
|
||||||
|
|
@ -1171,6 +1178,7 @@ private function create_application(Request $request, $type)
|
||||||
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 @@ public function application_by_uuid(Request $request)
|
||||||
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' => []],
|
||||||
],
|
],
|
||||||
|
|
@ -1320,7 +1329,8 @@ public function delete_by_uuid(Request $request)
|
||||||
#[OA\Patch(
|
#[OA\Patch(
|
||||||
summary: 'Update',
|
summary: 'Update',
|
||||||
description: 'Update application by UUID.',
|
description: 'Update application by UUID.',
|
||||||
path: '/applications',
|
path: '/applications/{uuid}',
|
||||||
|
operationId: 'update-application-by-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
|
|
@ -1450,7 +1460,7 @@ public function update_by_uuid(Request $request)
|
||||||
], 404);
|
], 404);
|
||||||
}
|
}
|
||||||
$server = $application->destination->server;
|
$server = $application->destination->server;
|
||||||
$allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect'];
|
$allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy'];
|
||||||
|
|
||||||
$validator = customApiValidator($request->all(), [
|
$validator = customApiValidator($request->all(), [
|
||||||
sharedDataApplications(),
|
sharedDataApplications(),
|
||||||
|
|
@ -1526,6 +1536,10 @@ public function update_by_uuid(Request $request)
|
||||||
}
|
}
|
||||||
$request->offsetUnset('docker_compose_domains');
|
$request->offsetUnset('docker_compose_domains');
|
||||||
}
|
}
|
||||||
|
$instantDeploy = $request->instant_deploy;
|
||||||
|
|
||||||
|
removeUnnecessaryFieldsFromRequest($request);
|
||||||
|
|
||||||
$data = $request->all();
|
$data = $request->all();
|
||||||
data_set($data, 'fqdn', $domains);
|
data_set($data, 'fqdn', $domains);
|
||||||
if ($dockerComposeDomainsJson->count() > 0) {
|
if ($dockerComposeDomainsJson->count() > 0) {
|
||||||
|
|
@ -1534,6 +1548,16 @@ public function update_by_uuid(Request $request)
|
||||||
$application->fill($data);
|
$application->fill($data);
|
||||||
$application->save();
|
$application->save();
|
||||||
|
|
||||||
|
if ($instantDeploy) {
|
||||||
|
$deployment_uuid = new Cuid2;
|
||||||
|
|
||||||
|
queue_application_deployment(
|
||||||
|
application: $application,
|
||||||
|
deployment_uuid: $deployment_uuid,
|
||||||
|
is_api: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'uuid' => $application->uuid,
|
'uuid' => $application->uuid,
|
||||||
]);
|
]);
|
||||||
|
|
@ -1543,6 +1567,7 @@ public function update_by_uuid(Request $request)
|
||||||
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' => []],
|
||||||
],
|
],
|
||||||
|
|
@ -1625,6 +1650,7 @@ public function envs(Request $request)
|
||||||
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' => []],
|
||||||
],
|
],
|
||||||
|
|
@ -1807,6 +1833,7 @@ public function update_env_by_uuid(Request $request)
|
||||||
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' => []],
|
||||||
],
|
],
|
||||||
|
|
@ -1998,6 +2025,7 @@ public function create_bulk_envs(Request $request)
|
||||||
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' => []],
|
||||||
],
|
],
|
||||||
|
|
@ -2157,6 +2185,7 @@ public function create_env(Request $request)
|
||||||
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' => []],
|
||||||
],
|
],
|
||||||
|
|
@ -2242,6 +2271,7 @@ public function delete_env_by_uuid(Request $request)
|
||||||
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' => []],
|
||||||
],
|
],
|
||||||
|
|
@ -2322,7 +2352,7 @@ public function action_deploy(Request $request)
|
||||||
return response()->json(['message' => 'Application not found.'], 404);
|
return response()->json(['message' => 'Application not found.'], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
|
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
|
|
@ -2345,6 +2375,7 @@ public function action_deploy(Request $request)
|
||||||
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' => []],
|
||||||
],
|
],
|
||||||
|
|
@ -2417,6 +2448,7 @@ public function action_stop(Request $request)
|
||||||
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' => []],
|
||||||
],
|
],
|
||||||
|
|
@ -2479,7 +2511,7 @@ public function action_restart(Request $request)
|
||||||
return response()->json(['message' => 'Application not found.'], 404);
|
return response()->json(['message' => 'Application not found.'], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
|
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ private function removeSensitiveData($database)
|
||||||
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 @@ public function databases(Request $request)
|
||||||
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 @@ public function database_by_uuid(Request $request)
|
||||||
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 @@ public function update_by_uuid(Request $request)
|
||||||
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 @@ public function create_database_postgresql(Request $request)
|
||||||
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 @@ public function create_database_clickhouse(Request $request)
|
||||||
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 @@ public function create_database_dragonfly(Request $request)
|
||||||
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 @@ public function create_database_redis(Request $request)
|
||||||
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 @@ public function create_database_keydb(Request $request)
|
||||||
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 @@ public function create_database_mariadb(Request $request)
|
||||||
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 @@ public function create_database_mysql(Request $request)
|
||||||
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 @@ public function create_database(Request $request, NewDatabaseTypes $type)
|
||||||
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 @@ public function delete_by_uuid(Request $request)
|
||||||
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 @@ public function action_deploy(Request $request)
|
||||||
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 @@ public function action_stop(Request $request)
|
||||||
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 @@ private function removeSensitiveData($deployment)
|
||||||
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 @@ public function deployments(Request $request)
|
||||||
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: 'integer')),
|
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 @@ public function deployment_by_uuid(Request $request)
|
||||||
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 @@ public function deployment_by_uuid(Request $request)
|
||||||
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',
|
||||||
|
|
@ -290,7 +293,7 @@ public function deploy_resource($resource, bool $force = false): array
|
||||||
}
|
}
|
||||||
switch ($resource?->getMorphClass()) {
|
switch ($resource?->getMorphClass()) {
|
||||||
case 'App\Models\Application':
|
case 'App\Models\Application':
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $resource,
|
application: $resource,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
|
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\EnvironmentVariable;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class EnvironmentVariablesController extends Controller
|
|
||||||
{
|
|
||||||
public function delete_env_by_uuid(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = getTeamIdFromToken();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalidTokenResponse();
|
|
||||||
}
|
|
||||||
$env = EnvironmentVariable::where('uuid', $request->env_uuid)->first();
|
|
||||||
if (! $env) {
|
|
||||||
return response()->json([
|
|
||||||
'message' => 'Environment variable not found.',
|
|
||||||
], 404);
|
|
||||||
}
|
|
||||||
$found_app = $env->resource()->whereRelation('environment.project.team', 'id', $teamId)->first();
|
|
||||||
if (! $found_app) {
|
|
||||||
return response()->json([
|
|
||||||
'message' => 'Environment variable not found.',
|
|
||||||
], 404);
|
|
||||||
}
|
|
||||||
$env->delete();
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'message' => 'Environment variable deleted.',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
use OpenApi\Attributes as OA;
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
#[OA\Info(title: 'Coolify', version: '0.1')]
|
#[OA\Info(title: 'Coolify', version: '0.1')]
|
||||||
#[OA\Server(url: 'https://app.coolify.io/api/v1')]
|
#[OA\Server(url: 'https://app.coolify.io/api/v1', description: 'Coolify Cloud API. Change the host to your own instance if you are self-hosting.')]
|
||||||
#[OA\SecurityScheme(
|
#[OA\SecurityScheme(
|
||||||
type: 'http',
|
type: 'http',
|
||||||
scheme: 'bearer',
|
scheme: 'bearer',
|
||||||
|
|
|
||||||
|
|
@ -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 @@ public function version(Request $request)
|
||||||
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 @@ public function enable_api(Request $request)
|
||||||
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 @@ public function feedback(Request $request)
|
||||||
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 @@ public function projects(Request $request)
|
||||||
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,14 +55,15 @@ public function projects(Request $request)
|
||||||
|
|
||||||
#[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' => []],
|
||||||
],
|
],
|
||||||
tags: ['Projects'],
|
tags: ['Projects'],
|
||||||
parameters: [
|
parameters: [
|
||||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'integer')),
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'string')),
|
||||||
],
|
],
|
||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
|
|
@ -102,12 +104,13 @@ public function project_by_uuid(Request $request)
|
||||||
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' => []],
|
||||||
],
|
],
|
||||||
tags: ['Projects'],
|
tags: ['Projects'],
|
||||||
parameters: [
|
parameters: [
|
||||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'integer')),
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'string')),
|
||||||
new OA\Parameter(name: 'environment_name', in: 'path', required: true, description: 'Environment name', schema: new OA\Schema(type: 'string')),
|
new OA\Parameter(name: 'environment_name', in: 'path', required: true, description: 'Environment name', schema: new OA\Schema(type: 'string')),
|
||||||
],
|
],
|
||||||
responses: [
|
responses: [
|
||||||
|
|
@ -135,8 +138,17 @@ public function environment_details(Request $request)
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
return invalidTokenResponse();
|
return invalidTokenResponse();
|
||||||
}
|
}
|
||||||
$project = Project::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
if (! $request->uuid) {
|
||||||
$environment = $project->environments()->whereName(request()->environment_name)->first();
|
return response()->json(['message' => 'UUID is required.'], 422);
|
||||||
|
}
|
||||||
|
if (! $request->environment_name) {
|
||||||
|
return response()->json(['message' => 'Environment name is required.'], 422);
|
||||||
|
}
|
||||||
|
$project = Project::whereTeamId($teamId)->whereUuid($request->uuid)->first();
|
||||||
|
if (! $project) {
|
||||||
|
return response()->json(['message' => 'Project not found.'], 404);
|
||||||
|
}
|
||||||
|
$environment = $project->environments()->whereName($request->environment_name)->first();
|
||||||
if (! $environment) {
|
if (! $environment) {
|
||||||
return response()->json(['message' => 'Environment not found.'], 404);
|
return response()->json(['message' => 'Environment not found.'], 404);
|
||||||
}
|
}
|
||||||
|
|
@ -144,4 +156,279 @@ public function environment_details(Request $request)
|
||||||
|
|
||||||
return response()->json(serializeApiResponse($environment));
|
return response()->json(serializeApiResponse($environment));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[OA\Post(
|
||||||
|
summary: 'Create',
|
||||||
|
description: 'Create Project.',
|
||||||
|
path: '/projects',
|
||||||
|
operationId: 'create-project',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Projects'],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
required: true,
|
||||||
|
description: 'Project created.',
|
||||||
|
content: new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'name' => ['type' => 'string', 'description' => 'The name of the project.'],
|
||||||
|
'description' => ['type' => 'string', 'description' => 'The description of the project.'],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 201,
|
||||||
|
description: 'Project created.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'uuid' => ['type' => 'string', 'example' => 'og888os', 'description' => 'The UUID of the project.'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
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_project(Request $request)
|
||||||
|
{
|
||||||
|
$allowedFields = ['name', 'description'];
|
||||||
|
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$return = validateIncomingRequest($request);
|
||||||
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
$validator = customApiValidator($request->all(), [
|
||||||
|
'name' => 'string|max:255|required',
|
||||||
|
'description' => 'string|nullable',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||||
|
if ($validator->fails() || ! empty($extraFields)) {
|
||||||
|
$errors = $validator->errors();
|
||||||
|
if (! empty($extraFields)) {
|
||||||
|
foreach ($extraFields as $field) {
|
||||||
|
$errors->add($field, 'This field is not allowed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => $errors,
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
$project = Project::create([
|
||||||
|
'name' => $request->name,
|
||||||
|
'description' => $request->description,
|
||||||
|
'team_id' => $teamId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'uuid' => $project->uuid,
|
||||||
|
])->setStatusCode(201);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Patch(
|
||||||
|
summary: 'Update',
|
||||||
|
description: 'Update Project.',
|
||||||
|
path: '/projects/{uuid}',
|
||||||
|
operationId: 'update-project-by-uuid',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Projects'],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
required: true,
|
||||||
|
description: 'Project updated.',
|
||||||
|
content: new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'name' => ['type' => 'string', 'description' => 'The name of the project.'],
|
||||||
|
'description' => ['type' => 'string', 'description' => 'The description of the project.'],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 201,
|
||||||
|
description: 'Project updated.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'uuid' => ['type' => 'string', 'example' => 'og888os'],
|
||||||
|
'name' => ['type' => 'string', 'example' => 'Project Name'],
|
||||||
|
'description' => ['type' => 'string', 'example' => 'Project Description'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
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_project(Request $request)
|
||||||
|
{
|
||||||
|
$allowedFields = ['name', 'description'];
|
||||||
|
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$return = validateIncomingRequest($request);
|
||||||
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
$validator = customApiValidator($request->all(), [
|
||||||
|
'name' => 'string|max:255|nullable',
|
||||||
|
'description' => 'string|nullable',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||||
|
if ($validator->fails() || ! empty($extraFields)) {
|
||||||
|
$errors = $validator->errors();
|
||||||
|
if (! empty($extraFields)) {
|
||||||
|
foreach ($extraFields as $field) {
|
||||||
|
$errors->add($field, 'This field is not allowed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => $errors,
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
$uuid = $request->uuid;
|
||||||
|
if (! $uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
$project = Project::whereTeamId($teamId)->whereUuid($uuid)->first();
|
||||||
|
if (! $project) {
|
||||||
|
return response()->json(['message' => 'Project not found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$project->update($request->only($allowedFields));
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'uuid' => $project->uuid,
|
||||||
|
'name' => $project->name,
|
||||||
|
'description' => $project->description,
|
||||||
|
])->setStatusCode(201);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Delete(
|
||||||
|
summary: 'Delete',
|
||||||
|
description: 'Delete project by UUID.',
|
||||||
|
path: '/projects/{uuid}',
|
||||||
|
operationId: 'delete-project-by-uuid',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Projects'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'uuid',
|
||||||
|
in: 'path',
|
||||||
|
description: 'UUID of the application.',
|
||||||
|
required: true,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'string',
|
||||||
|
format: 'uuid',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Project deleted.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string', 'example' => 'Project 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_project(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $request->uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 422);
|
||||||
|
}
|
||||||
|
$project = Project::whereTeamId($teamId)->whereUuid($request->uuid)->first();
|
||||||
|
if (! $project) {
|
||||||
|
return response()->json(['message' => 'Project not found.'], 404);
|
||||||
|
}
|
||||||
|
if ($project->resource_count() > 0) {
|
||||||
|
return response()->json(['message' => 'Project has resources, so it cannot be deleted.'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$project->delete();
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Project deleted.']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 @@ private function removeSensitiveData($team)
|
||||||
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 @@ public function keys(Request $request)
|
||||||
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: 'integer')),
|
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 @@ public function key_by_uuid(Request $request)
|
||||||
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 @@ public function create_key(Request $request)
|
||||||
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 @@ public function update_key(Request $request)
|
||||||
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: 'integer')),
|
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(
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,12 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Actions\Server\ValidateServer;
|
||||||
|
use App\Enums\ProxyStatus;
|
||||||
|
use App\Enums\ProxyTypes;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
|
use App\Models\PrivateKey;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\Server as ModelsServer;
|
use App\Models\Server as ModelsServer;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
@ -42,6 +46,7 @@ private function removeSensitiveData($server)
|
||||||
summary: 'List',
|
summary: 'List',
|
||||||
description: 'List all servers.',
|
description: 'List all servers.',
|
||||||
path: '/servers',
|
path: '/servers',
|
||||||
|
operationId: 'list-servers',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
|
|
@ -75,7 +80,7 @@ public function servers(Request $request)
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
return invalidTokenResponse();
|
return invalidTokenResponse();
|
||||||
}
|
}
|
||||||
$servers = ModelsServer::whereTeamId($teamId)->select('id', 'name', 'uuid', 'ip', 'user', 'port')->get()->load(['settings'])->map(function ($server) {
|
$servers = ModelsServer::whereTeamId($teamId)->select('id', 'name', 'uuid', 'ip', 'user', 'port', 'description')->get()->load(['settings'])->map(function ($server) {
|
||||||
$server['is_reachable'] = $server->settings->is_reachable;
|
$server['is_reachable'] = $server->settings->is_reachable;
|
||||||
$server['is_usable'] = $server->settings->is_usable;
|
$server['is_usable'] = $server->settings->is_usable;
|
||||||
|
|
||||||
|
|
@ -96,12 +101,13 @@ public function servers(Request $request)
|
||||||
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: 'integer')),
|
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(
|
||||||
|
|
@ -173,12 +179,13 @@ public function server_by_uuid(Request $request)
|
||||||
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: 'integer')),
|
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(
|
||||||
|
|
@ -250,12 +257,13 @@ public function resources_by_server(Request $request)
|
||||||
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: 'integer')),
|
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(
|
||||||
|
|
@ -392,4 +400,394 @@ public function domains_by_server(Request $request)
|
||||||
|
|
||||||
return response()->json(serializeApiResponse($domains));
|
return response()->json(serializeApiResponse($domains));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[OA\Post(
|
||||||
|
summary: 'Create',
|
||||||
|
description: 'Create Server.',
|
||||||
|
path: '/servers',
|
||||||
|
operationId: 'create-server',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Servers'],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
required: true,
|
||||||
|
description: 'Server created.',
|
||||||
|
content: new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'name' => ['type' => 'string', 'example' => 'My Server', 'description' => 'The name of the server.'],
|
||||||
|
'description' => ['type' => 'string', 'example' => 'My Server Description', 'description' => 'The description of the server.'],
|
||||||
|
'ip' => ['type' => 'string', 'example' => '127.0.0.1', 'description' => 'The IP of the server.'],
|
||||||
|
'port' => ['type' => 'integer', 'example' => 22, 'description' => 'The port of the server.'],
|
||||||
|
'user' => ['type' => 'string', 'example' => 'root', 'description' => 'The user of the server.'],
|
||||||
|
'private_key_uuid' => ['type' => 'string', 'example' => 'og888os', 'description' => 'The UUID of the private key.'],
|
||||||
|
'is_build_server' => ['type' => 'boolean', 'example' => false, 'description' => 'Is build server.'],
|
||||||
|
'instant_validate' => ['type' => 'boolean', 'example' => false, 'description' => 'Instant validate.'],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 201,
|
||||||
|
description: 'Server created.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'uuid' => ['type' => 'string', 'example' => 'og888os', 'description' => 'The UUID of the server.'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
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_server(Request $request)
|
||||||
|
{
|
||||||
|
$allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate'];
|
||||||
|
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$return = validateIncomingRequest($request);
|
||||||
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
$validator = customApiValidator($request->all(), [
|
||||||
|
'name' => 'string|max:255',
|
||||||
|
'description' => 'string|nullable',
|
||||||
|
'ip' => 'string|required',
|
||||||
|
'port' => 'integer|nullable',
|
||||||
|
'private_key_uuid' => 'string|required',
|
||||||
|
'user' => 'string|nullable',
|
||||||
|
'is_build_server' => 'boolean|nullable',
|
||||||
|
'instant_validate' => 'boolean|nullable',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||||
|
if ($validator->fails() || ! empty($extraFields)) {
|
||||||
|
$errors = $validator->errors();
|
||||||
|
if (! empty($extraFields)) {
|
||||||
|
foreach ($extraFields as $field) {
|
||||||
|
$errors->add($field, 'This field is not allowed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => $errors,
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
if (! $request->name) {
|
||||||
|
$request->offsetSet('name', generate_random_name());
|
||||||
|
}
|
||||||
|
if (! $request->user) {
|
||||||
|
$request->offsetSet('user', 'root');
|
||||||
|
}
|
||||||
|
if (is_null($request->port)) {
|
||||||
|
$request->offsetSet('port', 22);
|
||||||
|
}
|
||||||
|
if (is_null($request->is_build_server)) {
|
||||||
|
$request->offsetSet('is_build_server', false);
|
||||||
|
}
|
||||||
|
if (is_null($request->instant_validate)) {
|
||||||
|
$request->offsetSet('instant_validate', false);
|
||||||
|
}
|
||||||
|
$privateKey = PrivateKey::whereTeamId($teamId)->whereUuid($request->private_key_uuid)->first();
|
||||||
|
if (! $privateKey) {
|
||||||
|
return response()->json(['message' => 'Private key not found.'], 404);
|
||||||
|
}
|
||||||
|
$allServers = ModelsServer::whereIp($request->ip)->get();
|
||||||
|
if ($allServers->count() > 0) {
|
||||||
|
return response()->json(['message' => 'Server with this IP already exists.'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$server = ModelsServer::create([
|
||||||
|
'name' => $request->name,
|
||||||
|
'description' => $request->description,
|
||||||
|
'ip' => $request->ip,
|
||||||
|
'port' => $request->port,
|
||||||
|
'user' => $request->user,
|
||||||
|
'private_key_id' => $privateKey->id,
|
||||||
|
'team_id' => $teamId,
|
||||||
|
'proxy' => [
|
||||||
|
'type' => ProxyTypes::TRAEFIK->value,
|
||||||
|
'status' => ProxyStatus::EXITED->value,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$server->settings()->update([
|
||||||
|
'is_build_server' => $request->is_build_server,
|
||||||
|
]);
|
||||||
|
if ($request->instant_validate) {
|
||||||
|
ValidateServer::dispatch($server);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'uuid' => $server->uuid,
|
||||||
|
])->setStatusCode(201);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Patch(
|
||||||
|
summary: 'Update',
|
||||||
|
description: 'Update Server.',
|
||||||
|
path: '/servers/{uuid}',
|
||||||
|
operationId: 'update-server-by-uuid',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Servers'],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
required: true,
|
||||||
|
description: 'Server updated.',
|
||||||
|
content: new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'name' => ['type' => 'string', 'description' => 'The name of the server.'],
|
||||||
|
'description' => ['type' => 'string', 'description' => 'The description of the server.'],
|
||||||
|
'ip' => ['type' => 'string', 'description' => 'The IP of the server.'],
|
||||||
|
'port' => ['type' => 'integer', 'description' => 'The port of the server.'],
|
||||||
|
'user' => ['type' => 'string', 'description' => 'The user of the server.'],
|
||||||
|
'private_key_uuid' => ['type' => 'string', 'description' => 'The UUID of the private key.'],
|
||||||
|
'is_build_server' => ['type' => 'boolean', 'description' => 'Is build server.'],
|
||||||
|
'instant_validate' => ['type' => 'boolean', 'description' => 'Instant validate.'],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 201,
|
||||||
|
description: 'Server updated.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/Server')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
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_server(Request $request)
|
||||||
|
{
|
||||||
|
$allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate'];
|
||||||
|
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$return = validateIncomingRequest($request);
|
||||||
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
$validator = customApiValidator($request->all(), [
|
||||||
|
'name' => 'string|max:255|nullable',
|
||||||
|
'description' => 'string|nullable',
|
||||||
|
'ip' => 'string|nullable',
|
||||||
|
'port' => 'integer|nullable',
|
||||||
|
'private_key_uuid' => 'string|nullable',
|
||||||
|
'user' => 'string|nullable',
|
||||||
|
'is_build_server' => 'boolean|nullable',
|
||||||
|
'instant_validate' => 'boolean|nullable',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||||
|
if ($validator->fails() || ! empty($extraFields)) {
|
||||||
|
$errors = $validator->errors();
|
||||||
|
if (! empty($extraFields)) {
|
||||||
|
foreach ($extraFields as $field) {
|
||||||
|
$errors->add($field, 'This field is not allowed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => $errors,
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
$server = ModelsServer::whereTeamId($teamId)->whereUuid($request->uuid)->first();
|
||||||
|
if (! $server) {
|
||||||
|
return response()->json(['message' => 'Server not found.'], 404);
|
||||||
|
}
|
||||||
|
$server->update($request->only(['name', 'description', 'ip', 'port', 'user']));
|
||||||
|
if ($request->is_build_server) {
|
||||||
|
$server->settings()->update([
|
||||||
|
'is_build_server' => $request->is_build_server,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($request->instant_validate) {
|
||||||
|
ValidateServer::dispatch($server);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse($server))->setStatusCode(201);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Delete(
|
||||||
|
summary: 'Delete',
|
||||||
|
description: 'Delete server by UUID.',
|
||||||
|
path: '/servers/{uuid}',
|
||||||
|
operationId: 'delete-server-by-uuid',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Servers'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'uuid',
|
||||||
|
in: 'path',
|
||||||
|
description: 'UUID of the server.',
|
||||||
|
required: true,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'string',
|
||||||
|
format: 'uuid',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Server deleted.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string', 'example' => 'Server 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_server(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $request->uuid) {
|
||||||
|
return response()->json(['message' => 'Uuid is required.'], 422);
|
||||||
|
}
|
||||||
|
$server = ModelsServer::whereTeamId($teamId)->whereUuid($request->uuid)->first();
|
||||||
|
|
||||||
|
if (! $server) {
|
||||||
|
return response()->json(['message' => 'Server not found.'], 404);
|
||||||
|
}
|
||||||
|
if ($server->definedResources()->count() > 0) {
|
||||||
|
return response()->json(['message' => 'Server has resources, so you need to delete them before.'], 400);
|
||||||
|
}
|
||||||
|
$server->delete();
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Server deleted.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Validate',
|
||||||
|
description: 'Validate server by UUID.',
|
||||||
|
path: '/servers/{uuid}/validate',
|
||||||
|
operationId: 'validate-server-by-uuid',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Servers'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server UUID', schema: new OA\Schema(type: 'string')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 201,
|
||||||
|
description: 'Server validation started.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string', 'example' => 'Validation started.'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
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 validate_server(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $request->uuid) {
|
||||||
|
return response()->json(['message' => 'Uuid is required.'], 422);
|
||||||
|
}
|
||||||
|
$server = ModelsServer::whereTeamId($teamId)->whereUuid($request->uuid)->first();
|
||||||
|
|
||||||
|
if (! $server) {
|
||||||
|
return response()->json(['message' => 'Server not found.'], 404);
|
||||||
|
}
|
||||||
|
ValidateServer::dispatch($server);
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Validation started.']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ private function removeSensitiveData($service)
|
||||||
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 @@ public function services(Request $request)
|
||||||
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 @@ public function create_service(Request $request)
|
||||||
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 @@ public function create_service(Request $request)
|
||||||
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',
|
||||||
|
|
@ -413,6 +416,8 @@ public function service_by_uuid(Request $request)
|
||||||
return response()->json(['message' => 'Service not found.'], 404);
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$service = $service->load(['applications', 'databases']);
|
||||||
|
|
||||||
return response()->json($this->removeSensitiveData($service));
|
return response()->json($this->removeSensitiveData($service));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -420,6 +425,7 @@ public function service_by_uuid(Request $request)
|
||||||
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' => []],
|
||||||
],
|
],
|
||||||
|
|
@ -430,7 +436,7 @@ public function service_by_uuid(Request $request)
|
||||||
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',
|
||||||
|
|
@ -477,10 +483,521 @@ public function delete_by_uuid(Request $request)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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' => []],
|
||||||
],
|
],
|
||||||
|
|
@ -556,6 +1073,7 @@ public function action_deploy(Request $request)
|
||||||
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' => []],
|
||||||
],
|
],
|
||||||
|
|
@ -631,6 +1149,7 @@ public function action_stop(Request $request)
|
||||||
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 @@ private function removeSensitiveData($team)
|
||||||
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 @@ public function teams(Request $request)
|
||||||
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 @@ public function team_by_id(Request $request)
|
||||||
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 @@ public function members_by_id(Request $request)
|
||||||
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 @@ public function current_team(Request $request)
|
||||||
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' => []],
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ public function upload(Request $request)
|
||||||
$receiver = new FileReceiver('file', $request, HandlerFactory::classFromRequest($request));
|
$receiver = new FileReceiver('file', $request, HandlerFactory::classFromRequest($request));
|
||||||
|
|
||||||
if ($receiver->isUploaded() === false) {
|
if ($receiver->isUploaded() === false) {
|
||||||
throw new UploadMissingFileException();
|
throw new UploadMissingFileException;
|
||||||
}
|
}
|
||||||
|
|
||||||
$save = $receiver->receive();
|
$save = $receiver->receive();
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ public function manual(Request $request)
|
||||||
if ($x_bitbucket_event === 'repo:push') {
|
if ($x_bitbucket_event === 'repo:push') {
|
||||||
if ($application->isDeployable()) {
|
if ($application->isDeployable()) {
|
||||||
ray('Deploying '.$application->name.' with branch '.$branch);
|
ray('Deploying '.$application->name.' with branch '.$branch);
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
|
|
@ -127,7 +127,7 @@ public function manual(Request $request)
|
||||||
if ($x_bitbucket_event === 'pullrequest:created') {
|
if ($x_bitbucket_event === 'pullrequest:created') {
|
||||||
if ($application->isPRDeployable()) {
|
if ($application->isPRDeployable()) {
|
||||||
ray('Deploying preview for '.$application->name.' with branch '.$branch.' and base branch '.$base_branch.' and pull request id '.$pull_request_id);
|
ray('Deploying preview for '.$application->name.' with branch '.$branch.' and base branch '.$base_branch.' and pull request id '.$pull_request_id);
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||||
if (! $found) {
|
if (! $found) {
|
||||||
if ($application->build_pack === 'dockercompose') {
|
if ($application->build_pack === 'dockercompose') {
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ public function manual(Request $request)
|
||||||
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
||||||
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
||||||
ray('Deploying '.$application->name.' with branch '.$branch);
|
ray('Deploying '.$application->name.' with branch '.$branch);
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
|
|
@ -162,7 +162,7 @@ public function manual(Request $request)
|
||||||
if ($x_gitea_event === 'pull_request') {
|
if ($x_gitea_event === 'pull_request') {
|
||||||
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
|
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
|
||||||
if ($application->isPRDeployable()) {
|
if ($application->isPRDeployable()) {
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||||
if (! $found) {
|
if (! $found) {
|
||||||
if ($application->build_pack === 'dockercompose') {
|
if ($application->build_pack === 'dockercompose') {
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@ public function manual(Request $request)
|
||||||
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
||||||
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
||||||
ray('Deploying '.$application->name.' with branch '.$branch);
|
ray('Deploying '.$application->name.' with branch '.$branch);
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
|
|
@ -167,7 +167,7 @@ public function manual(Request $request)
|
||||||
if ($x_github_event === 'pull_request') {
|
if ($x_github_event === 'pull_request') {
|
||||||
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
|
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
|
||||||
if ($application->isPRDeployable()) {
|
if ($application->isPRDeployable()) {
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||||
if (! $found) {
|
if (! $found) {
|
||||||
if ($application->build_pack === 'dockercompose') {
|
if ($application->build_pack === 'dockercompose') {
|
||||||
|
|
@ -340,7 +340,6 @@ public function normal(Request $request)
|
||||||
return response("Nothing to do. No applications found with branch '$base_branch'.");
|
return response("Nothing to do. No applications found with branch '$base_branch'.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($applications as $application) {
|
foreach ($applications as $application) {
|
||||||
$isFunctional = $application->destination->server->isFunctional();
|
$isFunctional = $application->destination->server->isFunctional();
|
||||||
if (! $isFunctional) {
|
if (! $isFunctional) {
|
||||||
|
|
@ -358,7 +357,7 @@ public function normal(Request $request)
|
||||||
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
||||||
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
||||||
ray('Deploying '.$application->name.' with branch '.$branch);
|
ray('Deploying '.$application->name.' with branch '.$branch);
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
|
|
@ -397,7 +396,7 @@ public function normal(Request $request)
|
||||||
if ($x_github_event === 'pull_request') {
|
if ($x_github_event === 'pull_request') {
|
||||||
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
|
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
|
||||||
if ($application->isPRDeployable()) {
|
if ($application->isPRDeployable()) {
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||||
if (! $found) {
|
if (! $found) {
|
||||||
ApplicationPreview::create([
|
ApplicationPreview::create([
|
||||||
|
|
@ -432,8 +431,13 @@ public function normal(Request $request)
|
||||||
if ($action === 'closed' || $action === 'close') {
|
if ($action === 'closed' || $action === 'close') {
|
||||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||||
if ($found) {
|
if ($found) {
|
||||||
$container_name = generateApplicationContainerName($application, $pull_request_id);
|
$containers = getCurrentApplicationContainerStatus($application->destination->server, $application->id, $pull_request_id);
|
||||||
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
|
if ($containers->isNotEmpty()) {
|
||||||
|
$containers->each(function ($container) use ($application) {
|
||||||
|
$container_name = data_get($container, 'Names');
|
||||||
|
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ApplicationPullRequestUpdateJob::dispatchSync(application: $application, preview: $found, status: ProcessStatus::CLOSED);
|
ApplicationPullRequestUpdateJob::dispatchSync(application: $application, preview: $found, status: ProcessStatus::CLOSED);
|
||||||
$found->delete();
|
$found->delete();
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ public function manual(Request $request)
|
||||||
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
||||||
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
||||||
ray('Deploying '.$application->name.' with branch '.$branch);
|
ray('Deploying '.$application->name.' with branch '.$branch);
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
|
|
@ -177,7 +177,7 @@ public function manual(Request $request)
|
||||||
if ($x_gitlab_event === 'merge_request') {
|
if ($x_gitlab_event === 'merge_request') {
|
||||||
if ($action === 'open' || $action === 'opened' || $action === 'synchronize' || $action === 'reopened' || $action === 'reopen' || $action === 'update') {
|
if ($action === 'open' || $action === 'opened' || $action === 'synchronize' || $action === 'reopened' || $action === 'reopen' || $action === 'update') {
|
||||||
if ($application->isPRDeployable()) {
|
if ($application->isPRDeployable()) {
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||||
if (! $found) {
|
if (! $found) {
|
||||||
if ($application->build_pack === 'dockercompose') {
|
if ($application->build_pack === 'dockercompose') {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
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;
|
||||||
|
|
@ -28,6 +29,7 @@
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Sleep;
|
use Illuminate\Support\Sleep;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\Support\Facades\Process;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
@ -109,10 +111,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;
|
||||||
|
|
@ -157,6 +161,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
|
|
||||||
private ?string $coolify_variables = null;
|
private ?string $coolify_variables = null;
|
||||||
|
|
||||||
|
private bool $preserveRepository = false;
|
||||||
|
|
||||||
public $tries = 1;
|
public $tries = 1;
|
||||||
|
|
||||||
public function __construct(int $application_deployment_queue_id)
|
public function __construct(int $application_deployment_queue_id)
|
||||||
|
|
@ -164,6 +170,7 @@ public function __construct(int $application_deployment_queue_id)
|
||||||
$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;
|
||||||
|
|
@ -187,6 +194,7 @@ public function __construct(int $application_deployment_queue_id)
|
||||||
$this->server = $this->mainServer = $this->destination->server;
|
$this->server = $this->mainServer = $this->destination->server;
|
||||||
$this->serverUser = $this->server->user;
|
$this->serverUser = $this->server->user;
|
||||||
$this->is_this_additional_server = $this->application->additional_servers()->wherePivot('server_id', $this->server->id)->count() > 0;
|
$this->is_this_additional_server = $this->application->additional_servers()->wherePivot('server_id', $this->server->id)->count() > 0;
|
||||||
|
$this->preserveRepository = $this->application->settings->is_preserve_repository_enabled;
|
||||||
|
|
||||||
$this->basedir = $this->application->generateBaseDir($this->deployment_uuid);
|
$this->basedir = $this->application->generateBaseDir($this->deployment_uuid);
|
||||||
$this->workdir = "{$this->basedir}".rtrim($this->application->base_directory, '/');
|
$this->workdir = "{$this->basedir}".rtrim($this->application->base_directory, '/');
|
||||||
|
|
@ -195,11 +203,14 @@ public function __construct(int $application_deployment_queue_id)
|
||||||
|
|
||||||
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
|
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
|
||||||
if ($this->application->settings->custom_internal_name && ! $this->application->settings->is_consistent_container_name_enabled) {
|
if ($this->application->settings->custom_internal_name && ! $this->application->settings->is_consistent_container_name_enabled) {
|
||||||
$this->container_name = $this->application->settings->custom_internal_name;
|
if ($this->pull_request_id === 0) {
|
||||||
|
$this->container_name = $this->application->settings->custom_internal_name;
|
||||||
|
} else {
|
||||||
|
$this->container_name = "{$this->application->settings->custom_internal_name}-pr-{$this->pull_request_id}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ray('New container name: ', $this->container_name);
|
ray('New container name: ', $this->container_name)->green();
|
||||||
|
|
||||||
savePrivateKeyToFs($this->server);
|
|
||||||
$this->saved_outputs = collect();
|
$this->saved_outputs = collect();
|
||||||
|
|
||||||
// Set preview fqdn
|
// Set preview fqdn
|
||||||
|
|
@ -273,6 +284,7 @@ public function handle(): void
|
||||||
$this->original_server = $this->server;
|
$this->original_server = $this->server;
|
||||||
} else {
|
} else {
|
||||||
$this->build_server = $buildServers->random();
|
$this->build_server = $buildServers->random();
|
||||||
|
$this->application_deployment_queue->build_server_id = $this->build_server->id;
|
||||||
$this->application_deployment_queue->addLogEntry("Found a suitable build server ({$this->build_server->name}).");
|
$this->application_deployment_queue->addLogEntry("Found a suitable build server ({$this->build_server->name}).");
|
||||||
$this->original_server = $this->server;
|
$this->original_server = $this->server;
|
||||||
$this->use_build_server = true;
|
$this->use_build_server = true;
|
||||||
|
|
@ -304,14 +316,6 @@ public function handle(): void
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
// $this->execute_remote_command(
|
|
||||||
// [
|
|
||||||
// "docker image prune -f >/dev/null 2>&1",
|
|
||||||
// "hidden" => true,
|
|
||||||
// "ignore_errors" => true,
|
|
||||||
// ]
|
|
||||||
// );
|
|
||||||
|
|
||||||
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
|
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -419,15 +423,42 @@ private function deploy_docker_compose_buildpack()
|
||||||
$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 @@ private function deploy_docker_compose_buildpack()
|
||||||
|
|
||||||
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,49 +510,65 @@ private function deploy_docker_compose_buildpack()
|
||||||
// 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 >/dev/null 2>&1 || true",
|
||||||
|
'hidden' => true,
|
||||||
|
'ignore_errors' => true,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start compose file
|
// Start compose file
|
||||||
|
$server_workdir = $this->application->workdir();
|
||||||
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
||||||
if ($this->docker_compose_custom_start_command) {
|
if ($this->docker_compose_custom_start_command) {
|
||||||
|
$this->write_deployment_configurations();
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[executeInDocker($this->deployment_uuid, "cd {$this->workdir} && {$this->docker_compose_custom_start_command}"), 'hidden' => true],
|
[executeInDocker($this->deployment_uuid, "cd {$this->workdir} && {$this->docker_compose_custom_start_command}"), 'hidden' => true],
|
||||||
);
|
);
|
||||||
$this->write_deployment_configurations();
|
|
||||||
} else {
|
} else {
|
||||||
$this->write_deployment_configurations();
|
$this->write_deployment_configurations();
|
||||||
$server_workdir = $this->application->workdir();
|
$this->docker_compose_location = '/docker-compose.yaml';
|
||||||
|
|
||||||
$command = "{$this->coolify_variables} docker compose";
|
$command = "{$this->coolify_variables} docker compose";
|
||||||
if ($this->env_filename) {
|
if ($this->env_filename) {
|
||||||
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
|
$command .= " --env-file {$server_workdir}/{$this->env_filename}";
|
||||||
}
|
}
|
||||||
$command .= " --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d";
|
$command .= " --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d";
|
||||||
|
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
['command' => $command, 'hidden' => true],
|
['command' => $command, 'hidden' => true],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ($this->docker_compose_custom_start_command) {
|
if ($this->docker_compose_custom_start_command) {
|
||||||
|
$this->write_deployment_configurations();
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_start_command}"), 'hidden' => true],
|
[executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_start_command}"), 'hidden' => true],
|
||||||
);
|
);
|
||||||
$this->write_deployment_configurations();
|
|
||||||
} else {
|
} else {
|
||||||
$command = "{$this->coolify_variables} docker compose";
|
$command = "{$this->coolify_variables} docker compose";
|
||||||
if ($this->env_filename) {
|
if ($this->preserveRepository) {
|
||||||
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
|
if ($this->env_filename) {
|
||||||
|
$command .= " --env-file {$server_workdir}/{$this->env_filename}";
|
||||||
|
}
|
||||||
|
$command .= " --project-name {$this->application->uuid} --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d";
|
||||||
|
$this->write_deployment_configurations();
|
||||||
|
|
||||||
|
$this->execute_remote_command(
|
||||||
|
['command' => $command, 'hidden' => true],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if ($this->env_filename) {
|
||||||
|
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
|
||||||
|
}
|
||||||
|
$command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d";
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
|
||||||
|
);
|
||||||
|
$this->write_deployment_configurations();
|
||||||
}
|
}
|
||||||
$command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d";
|
|
||||||
$this->execute_remote_command(
|
|
||||||
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
|
|
||||||
);
|
|
||||||
$this->write_deployment_configurations();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -605,26 +653,54 @@ private function deploy_static_buildpack()
|
||||||
|
|
||||||
private function write_deployment_configurations()
|
private function write_deployment_configurations()
|
||||||
{
|
{
|
||||||
|
if ($this->preserveRepository) {
|
||||||
|
if ($this->use_build_server) {
|
||||||
|
$this->server = $this->original_server;
|
||||||
|
}
|
||||||
|
if (str($this->configuration_dir)->isNotEmpty()) {
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
"mkdir -p $this->configuration_dir",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"docker cp {$this->deployment_uuid}:{$this->workdir}/. {$this->configuration_dir}",
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
foreach ($this->application->fileStorages as $fileStorage) {
|
||||||
|
if (! $fileStorage->is_based_on_git && ! $fileStorage->is_directory) {
|
||||||
|
$fileStorage->saveStorageOnServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($this->use_build_server) {
|
||||||
|
$this->server = $this->build_server;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (isset($this->docker_compose_base64)) {
|
if (isset($this->docker_compose_base64)) {
|
||||||
if ($this->use_build_server) {
|
if ($this->use_build_server) {
|
||||||
$this->server = $this->original_server;
|
$this->server = $this->original_server;
|
||||||
}
|
}
|
||||||
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
|
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
|
||||||
|
|
||||||
|
$mainDir = $this->configuration_dir;
|
||||||
|
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
||||||
|
$mainDir = $this->application->workdir();
|
||||||
|
}
|
||||||
if ($this->pull_request_id === 0) {
|
if ($this->pull_request_id === 0) {
|
||||||
$composeFileName = "$this->configuration_dir/docker-compose.yaml";
|
$composeFileName = "$mainDir/docker-compose.yaml";
|
||||||
} else {
|
} else {
|
||||||
$composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yaml";
|
$composeFileName = "$mainDir/docker-compose-pr-{$this->pull_request_id}.yaml";
|
||||||
$this->docker_compose_location = "/docker-compose-pr-{$this->pull_request_id}.yaml";
|
$this->docker_compose_location = "/docker-compose-pr-{$this->pull_request_id}.yaml";
|
||||||
}
|
}
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"mkdir -p $this->configuration_dir",
|
"mkdir -p $mainDir",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"echo '{$this->docker_compose_base64}' | base64 -d | tee $composeFileName > /dev/null",
|
"echo '{$this->docker_compose_base64}' | base64 -d | tee $composeFileName > /dev/null",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"echo '{$readme}' > $this->configuration_dir/README.md",
|
"echo '{$readme}' > $mainDir/README.md",
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
if ($this->use_build_server) {
|
if ($this->use_build_server) {
|
||||||
|
|
@ -675,7 +751,8 @@ private function push_to_docker_registry()
|
||||||
$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) {
|
||||||
|
|
@ -683,10 +760,14 @@ private function push_to_docker_registry()
|
||||||
$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,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -783,14 +864,20 @@ private function should_skip_build()
|
||||||
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',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -823,17 +910,24 @@ private function save_environment_variables()
|
||||||
}
|
}
|
||||||
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->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
if ($this->application->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
|
||||||
$envs->push("COOLIFY_BRANCH={$local_branch}");
|
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||||
}
|
$envs->push("COOLIFY_BRANCH=\"{$local_branch}\"");
|
||||||
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
}
|
||||||
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
|
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
||||||
|
$envs->push("COOLIFY_CONTAINER_NAME=\"{$this->container_name}\"");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_coolify_default_environment_variables($this->application, $envs, $this->application->environment_variables_preview);
|
||||||
|
|
||||||
foreach ($sorted_environment_variables_preview as $env) {
|
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') {
|
||||||
|
|
@ -848,8 +942,10 @@ private function save_environment_variables()
|
||||||
$envs->push($env->key.'='.$real_value);
|
$envs->push($env->key.'='.$real_value);
|
||||||
}
|
}
|
||||||
// Add PORT if not exists, use the first port as default
|
// Add PORT if not exists, use the first port as default
|
||||||
if ($this->application->environment_variables_preview->where('key', 'PORT')->isEmpty()) {
|
if ($this->build_pack !== 'dockercompose') {
|
||||||
$envs->push("PORT={$ports[0]}");
|
if ($this->application->environment_variables_preview->where('key', 'PORT')->isEmpty()) {
|
||||||
|
$envs->push("PORT={$ports[0]}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Add HOST if not exists
|
// Add HOST if not exists
|
||||||
if ($this->application->environment_variables_preview->where('key', 'HOST')->isEmpty()) {
|
if ($this->application->environment_variables_preview->where('key', 'HOST')->isEmpty()) {
|
||||||
|
|
@ -866,18 +962,31 @@ private function save_environment_variables()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($this->application->environment_variables->where('key', 'COOLIFY_FQDN')->isEmpty()) {
|
if ($this->application->environment_variables->where('key', 'COOLIFY_FQDN')->isEmpty()) {
|
||||||
$envs->push("COOLIFY_FQDN={$this->application->fqdn}");
|
if ($this->application->compose_parsing_version === '3') {
|
||||||
|
$envs->push("COOLIFY_URL={$this->application->fqdn}");
|
||||||
|
} else {
|
||||||
|
$envs->push("COOLIFY_FQDN={$this->application->fqdn}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ($this->application->environment_variables->where('key', 'COOLIFY_URL')->isEmpty()) {
|
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://', '');
|
||||||
$envs->push("COOLIFY_URL={$url}");
|
if ($this->application->compose_parsing_version === '3') {
|
||||||
|
$envs->push("COOLIFY_FQDN={$url}");
|
||||||
|
} else {
|
||||||
|
$envs->push("COOLIFY_URL={$url}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
if ($this->application->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
|
||||||
$envs->push("COOLIFY_BRANCH={$local_branch}");
|
if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||||
}
|
$envs->push("COOLIFY_BRANCH=\"{$local_branch}\"");
|
||||||
if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
}
|
||||||
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
|
if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
||||||
|
$envs->push("COOLIFY_CONTAINER_NAME=\"{$this->container_name}\"");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_coolify_default_environment_variables($this->application, $envs, $this->application->environment_variables);
|
||||||
|
|
||||||
foreach ($sorted_environment_variables as $env) {
|
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') {
|
||||||
|
|
@ -892,15 +1001,16 @@ private function save_environment_variables()
|
||||||
$envs->push($env->key.'='.$real_value);
|
$envs->push($env->key.'='.$real_value);
|
||||||
}
|
}
|
||||||
// Add PORT if not exists, use the first port as default
|
// Add PORT if not exists, use the first port as default
|
||||||
if ($this->application->environment_variables->where('key', 'PORT')->isEmpty()) {
|
if ($this->build_pack !== 'dockercompose') {
|
||||||
$envs->push("PORT={$ports[0]}");
|
if ($this->application->environment_variables->where('key', 'PORT')->isEmpty()) {
|
||||||
|
$envs->push("PORT={$ports[0]}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Add HOST if not exists
|
// Add HOST if not exists
|
||||||
if ($this->application->environment_variables->where('key', 'HOST')->isEmpty()) {
|
if ($this->application->environment_variables->where('key', 'HOST')->isEmpty()) {
|
||||||
$envs->push('HOST=0.0.0.0');
|
$envs->push('HOST=0.0.0.0');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($envs->isEmpty()) {
|
if ($envs->isEmpty()) {
|
||||||
$this->env_filename = null;
|
$this->env_filename = null;
|
||||||
if ($this->use_build_server) {
|
if ($this->use_build_server) {
|
||||||
|
|
@ -953,19 +1063,60 @@ private function save_environment_variables()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$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';
|
||||||
$nixpacks_php_fallback_path->value = '/index.php';
|
$nixpacks_php_fallback_path->value = '/index.php';
|
||||||
$nixpacks_php_fallback_path->is_build_time = false;
|
$nixpacks_php_fallback_path->is_build_time = false;
|
||||||
|
|
@ -973,7 +1124,7 @@ private function laravel_finetunes()
|
||||||
$nixpacks_php_fallback_path->save();
|
$nixpacks_php_fallback_path->save();
|
||||||
}
|
}
|
||||||
if (! $nixpacks_php_root_dir) {
|
if (! $nixpacks_php_root_dir) {
|
||||||
$nixpacks_php_root_dir = new EnvironmentVariable();
|
$nixpacks_php_root_dir = new EnvironmentVariable;
|
||||||
$nixpacks_php_root_dir->key = 'NIXPACKS_PHP_ROOT_DIR';
|
$nixpacks_php_root_dir->key = 'NIXPACKS_PHP_ROOT_DIR';
|
||||||
$nixpacks_php_root_dir->value = '/app/public';
|
$nixpacks_php_root_dir->value = '/app/public';
|
||||||
$nixpacks_php_root_dir->is_build_time = false;
|
$nixpacks_php_root_dir->is_build_time = false;
|
||||||
|
|
@ -999,7 +1150,7 @@ private function rolling_update()
|
||||||
$this->write_deployment_configurations();
|
$this->write_deployment_configurations();
|
||||||
$this->server = $this->original_server;
|
$this->server = $this->original_server;
|
||||||
}
|
}
|
||||||
if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled || isset($this->application->settings->custom_internal_name) || $this->pull_request_id !== 0 || str($this->application->custom_docker_run_options)->contains('--ip') || str($this->application->custom_docker_run_options)->contains('--ip6')) {
|
if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled || str($this->application->settings->custom_internal_name)->isNotEmpty() || $this->pull_request_id !== 0 || str($this->application->custom_docker_run_options)->contains('--ip') || str($this->application->custom_docker_run_options)->contains('--ip6')) {
|
||||||
$this->application_deployment_queue->addLogEntry('----------------------------------------');
|
$this->application_deployment_queue->addLogEntry('----------------------------------------');
|
||||||
if (count($this->application->ports_mappings_array) > 0) {
|
if (count($this->application->ports_mappings_array) > 0) {
|
||||||
$this->application_deployment_queue->addLogEntry('Application has ports mapped to the host system, rolling update is not supported.');
|
$this->application_deployment_queue->addLogEntry('Application has ports mapped to the host system, rolling update is not supported.');
|
||||||
|
|
@ -1007,7 +1158,7 @@ private function rolling_update()
|
||||||
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
|
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
|
||||||
$this->application_deployment_queue->addLogEntry('Consistent container name feature enabled, rolling update is not supported.');
|
$this->application_deployment_queue->addLogEntry('Consistent container name feature enabled, rolling update is not supported.');
|
||||||
}
|
}
|
||||||
if (isset($this->application->settings->custom_internal_name)) {
|
if (str($this->application->settings->custom_internal_name)->isNotEmpty()) {
|
||||||
$this->application_deployment_queue->addLogEntry('Custom internal name is set, rolling update is not supported.');
|
$this->application_deployment_queue->addLogEntry('Custom internal name is set, rolling update is not supported.');
|
||||||
}
|
}
|
||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
|
|
@ -1183,7 +1334,9 @@ private function create_workdir()
|
||||||
|
|
||||||
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);
|
||||||
|
|
@ -1247,7 +1400,7 @@ private function deploy_to_additional_destinations()
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// ray('Deploying to additional destination: ', $server->name);
|
// ray('Deploying to additional destination: ', $server->name);
|
||||||
$deployment_uuid = new Cuid2();
|
$deployment_uuid = new Cuid2;
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
application: $this->application,
|
application: $this->application,
|
||||||
|
|
@ -1289,21 +1442,11 @@ private function check_git_if_build_needed()
|
||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
$local_branch = "pull/{$this->pull_request_id}/head";
|
$local_branch = "pull/{$this->pull_request_id}/head";
|
||||||
}
|
}
|
||||||
$private_key = data_get($this->application, 'private_key.private_key');
|
$private_key = $this->application->privateKey?->getKeyLocation();
|
||||||
if ($private_key) {
|
if ($private_key) {
|
||||||
$private_key = base64_encode($private_key);
|
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
executeInDocker($this->deployment_uuid, 'mkdir -p /root/.ssh'),
|
executeInDocker($this->deployment_uuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i {$private_key}\" git ls-remote {$this->fullRepoUrl} {$local_branch}"),
|
||||||
],
|
|
||||||
[
|
|
||||||
executeInDocker($this->deployment_uuid, "echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null"),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
executeInDocker($this->deployment_uuid, 'chmod 600 /root/.ssh/id_rsa'),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
executeInDocker($this->deployment_uuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git ls-remote {$this->fullRepoUrl} {$local_branch}"),
|
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
'save' => 'git_commit_sha',
|
'save' => 'git_commit_sha',
|
||||||
],
|
],
|
||||||
|
|
@ -1335,7 +1478,8 @@ private function clone_repository()
|
||||||
}
|
}
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
$importCommands, 'hidden' => true,
|
$importCommands,
|
||||||
|
'hidden' => true,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
$this->create_workdir();
|
$this->create_workdir();
|
||||||
|
|
@ -1419,8 +1563,16 @@ private function generate_nixpacks_confs()
|
||||||
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') {
|
||||||
|
// temporary: disable healthcheck for rust because the start phase does not have curl/wget
|
||||||
|
$this->application->health_check_enabled = false;
|
||||||
|
$this->application->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1540,7 +1692,10 @@ private function generate_compose_file()
|
||||||
// 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);
|
||||||
|
|
@ -1643,14 +1798,7 @@ private function generate_compose_file()
|
||||||
$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'] = [
|
||||||
|
|
@ -1677,13 +1825,20 @@ private function generate_compose_file()
|
||||||
if (count($this->application->ports_mappings_array) > 0 && $this->pull_request_id === 0) {
|
if (count($this->application->ports_mappings_array) > 0 && $this->pull_request_id === 0) {
|
||||||
$docker_compose['services'][$this->container_name]['ports'] = $this->application->ports_mappings_array;
|
$docker_compose['services'][$this->container_name]['ports'] = $this->application->ports_mappings_array;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count($persistent_storages) > 0) {
|
if (count($persistent_storages) > 0) {
|
||||||
$docker_compose['services'][$this->container_name]['volumes'] = $persistent_storages;
|
if (! data_get($docker_compose, 'services.'.$this->container_name.'.volumes')) {
|
||||||
|
$docker_compose['services'][$this->container_name]['volumes'] = [];
|
||||||
|
}
|
||||||
|
$docker_compose['services'][$this->container_name]['volumes'] = array_merge($docker_compose['services'][$this->container_name]['volumes'], $persistent_storages);
|
||||||
}
|
}
|
||||||
if (count($persistent_file_volumes) > 0) {
|
if (count($persistent_file_volumes) > 0) {
|
||||||
$docker_compose['services'][$this->container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
|
if (! data_get($docker_compose, 'services.'.$this->container_name.'.volumes')) {
|
||||||
|
$docker_compose['services'][$this->container_name]['volumes'] = [];
|
||||||
|
}
|
||||||
|
$docker_compose['services'][$this->container_name]['volumes'] = array_merge($docker_compose['services'][$this->container_name]['volumes'], $persistent_file_volumes->map(function ($item) {
|
||||||
return "$item->fs_path:$item->mount_path";
|
return "$item->fs_path:$item->mount_path";
|
||||||
})->toArray();
|
})->toArray());
|
||||||
}
|
}
|
||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
|
|
@ -1806,13 +1961,23 @@ private function pull_latest_image($image)
|
||||||
$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.');
|
||||||
|
|
@ -1856,12 +2021,14 @@ private function build_image()
|
||||||
$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}";
|
||||||
}
|
}
|
||||||
|
|
@ -1869,10 +2036,16 @@ private function build_image()
|
||||||
$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, 'cat /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]);
|
||||||
|
|
@ -1886,10 +2059,16 @@ private function build_image()
|
||||||
}
|
}
|
||||||
$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, 'cat /artifacts/build.sh'),
|
||||||
|
'hidden' => true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
||||||
|
'hidden' => true,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1926,10 +2105,16 @@ private function build_image()
|
||||||
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, 'cat /artifacts/build.sh'),
|
||||||
|
'hidden' => true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
||||||
|
'hidden' => true,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1943,10 +2128,16 @@ private function build_image()
|
||||||
$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, 'cat /artifacts/build.sh'),
|
||||||
|
'hidden' => true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
||||||
|
'hidden' => true,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1955,22 +2146,30 @@ private function build_image()
|
||||||
$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, 'cat /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]);
|
||||||
|
|
@ -1984,10 +2183,16 @@ private function build_image()
|
||||||
}
|
}
|
||||||
$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, 'cat /artifacts/build.sh'),
|
||||||
|
'hidden' => true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
||||||
|
'hidden' => true,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1996,27 +2201,62 @@ private function build_image()
|
||||||
$this->application_deployment_queue->addLogEntry('Building docker image completed.');
|
$this->application_deployment_queue->addLogEntry('Building docker image completed.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function graceful_shutdown_container(string $containerName, int $timeout = 300)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$process = Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
|
||||||
|
|
||||||
|
$startTime = time();
|
||||||
|
while ($process->running()) {
|
||||||
|
if (time() - $startTime >= $timeout) {
|
||||||
|
$this->execute_remote_command(
|
||||||
|
["docker kill $containerName", 'hidden' => true, 'ignore_errors' => true]
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
usleep(100000);
|
||||||
|
}
|
||||||
|
|
||||||
|
$isRunning = $this->execute_remote_command(
|
||||||
|
["docker inspect -f '{{.State.Running}}' $containerName", 'hidden' => true, 'ignore_errors' => true]
|
||||||
|
) === 'true';
|
||||||
|
|
||||||
|
if ($isRunning) {
|
||||||
|
$this->execute_remote_command(
|
||||||
|
["docker kill $containerName", 'hidden' => true, 'ignore_errors' => true]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (\Exception $error) {
|
||||||
|
$this->application_deployment_queue->addLogEntry("Error stopping container $containerName: " . $error->getMessage(), 'stderr');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->remove_container($containerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function remove_container(string $containerName)
|
||||||
|
{
|
||||||
|
$this->execute_remote_command(
|
||||||
|
["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)
|
||||||
{
|
{
|
||||||
$this->application_deployment_queue->addLogEntry('Removing old containers.');
|
$this->application_deployment_queue->addLogEntry('Removing old containers.');
|
||||||
if ($this->newVersionIsHealthy || $force) {
|
if ($this->newVersionIsHealthy || $force) {
|
||||||
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
|
if ($this->application->settings->is_consistent_container_name_enabled || str($this->application->settings->custom_internal_name)->isNotEmpty()) {
|
||||||
if ($this->pull_request_id === 0) {
|
$this->graceful_shutdown_container($this->container_name);
|
||||||
$containers = $containers->filter(function ($container) {
|
} else {
|
||||||
return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name.'-pr-'.$this->pull_request_id;
|
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
|
||||||
|
if ($this->pull_request_id === 0) {
|
||||||
|
$containers = $containers->filter(function ($container) {
|
||||||
|
return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name . '-pr-' . $this->pull_request_id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$containers->each(function ($container) {
|
||||||
|
$this->graceful_shutdown_container(data_get($container, 'Names'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
$containers->each(function ($container) {
|
|
||||||
$containerName = data_get($container, 'Names');
|
|
||||||
$this->execute_remote_command(
|
|
||||||
["docker rm -f $containerName >/dev/null 2>&1", 'hidden' => true, 'ignore_errors' => true],
|
|
||||||
);
|
|
||||||
});
|
|
||||||
if ($this->application->settings->is_consistent_container_name_enabled || isset($this->application->settings->custom_internal_name)) {
|
|
||||||
$this->execute_remote_command(
|
|
||||||
["docker rm -f $this->container_name >/dev/null 2>&1", 'hidden' => true, 'ignore_errors' => true],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile' || $this->application->build_pack === 'dockerimage') {
|
if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile' || $this->application->build_pack === 'dockerimage') {
|
||||||
$this->application_deployment_queue->addLogEntry('----------------------------------------');
|
$this->application_deployment_queue->addLogEntry('----------------------------------------');
|
||||||
|
|
@ -2027,9 +2267,7 @@ private function stop_running_container(bool $force = false)
|
||||||
$this->application_deployment_queue->update([
|
$this->application_deployment_queue->update([
|
||||||
'status' => ApplicationDeploymentStatus::FAILED->value,
|
'status' => ApplicationDeploymentStatus::FAILED->value,
|
||||||
]);
|
]);
|
||||||
$this->execute_remote_command(
|
$this->graceful_shutdown_container($this->container_name);
|
||||||
["docker rm -f $this->container_name >/dev/null 2>&1", 'hidden' => true, 'ignore_errors' => true],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2069,15 +2307,14 @@ private function generate_build_env_variables()
|
||||||
$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) {
|
||||||
|
|
@ -2095,7 +2332,6 @@ private function add_build_env_variables_to_dockerfile()
|
||||||
} 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"));
|
||||||
|
|
@ -2123,7 +2359,8 @@ private function run_pre_deployment_command()
|
||||||
$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,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -2150,7 +2387,9 @@ private function run_post_deployment_command()
|
||||||
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) {
|
||||||
|
|
@ -2204,7 +2443,7 @@ public function failed(Throwable $exception): void
|
||||||
ray($code);
|
ray($code);
|
||||||
if ($code !== 69420) {
|
if ($code !== 69420) {
|
||||||
// 69420 means failed to push the image to the registry, so we don't need to remove the new version as it is the currently running one
|
// 69420 means failed to push the image to the registry, so we don't need to remove the new version as it is the currently running one
|
||||||
if ($this->application->settings->is_consistent_container_name_enabled || isset($this->application->settings->custom_internal_name)) {
|
if ($this->application->settings->is_consistent_container_name_enabled || str($this->application->settings->custom_internal_name)->isNotEmpty()) {
|
||||||
// do not remove already running container
|
// do not remove already running container
|
||||||
} else {
|
} else {
|
||||||
$this->application_deployment_queue->addLogEntry('Deployment failed. Removing the new version of your application.', 'stderr');
|
$this->application_deployment_queue->addLogEntry('Deployment failed. Removing the new version of your application.', 'stderr');
|
||||||
|
|
|
||||||
|
|
@ -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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
45
app/Jobs/CheckForUpdatesJob.php
Normal file
45
app/Jobs/CheckForUpdatesJob.php
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
|
||||||
|
class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (isDev() || isCloud()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
||||||
|
if ($response->successful()) {
|
||||||
|
$versions = $response->json();
|
||||||
|
|
||||||
|
$latest_version = data_get($versions, 'coolify.v4.version');
|
||||||
|
$current_version = config('version');
|
||||||
|
|
||||||
|
if (version_compare($latest_version, $current_version, '>')) {
|
||||||
|
// New version available
|
||||||
|
$settings->update(['new_version_available' => true]);
|
||||||
|
File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT));
|
||||||
|
} else {
|
||||||
|
$settings->update(['new_version_available' => false]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// Consider implementing a notification to administrators
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -21,11 +21,10 @@ public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
ray('Cleaning up helper containers on '.$this->server->name);
|
ray('Cleaning up helper containers on '.$this->server->name);
|
||||||
$containers = instant_remote_process(['docker container ps --filter "ancestor=ghcr.io/coollabsio/coolify-helper:next" --filter "ancestor=ghcr.io/coollabsio/coolify-helper:latest" --format \'{{json .}}\''], $this->server, false);
|
$containers = instant_remote_process(['docker container ps --format \'{{json .}}\' | jq -s \'map(select(.Image | contains("ghcr.io/coollabsio/coolify-helper")))\''], $this->server, false);
|
||||||
$containers = format_docker_command_output_to_json($containers);
|
$containerIds = collect(json_decode($containers))->pluck('ID');
|
||||||
if ($containers->count() > 0) {
|
if ($containerIds->count() > 0) {
|
||||||
foreach ($containers as $container) {
|
foreach ($containerIds as $containerId) {
|
||||||
$containerId = data_get($container, 'ID');
|
|
||||||
ray('Removing container '.$containerId);
|
ray('Removing container '.$containerId);
|
||||||
instant_remote_process(['docker container rm -f '.$containerId], $this->server, false);
|
instant_remote_process(['docker container rm -f '.$containerId], $this->server, false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
81
app/Jobs/CleanupStaleMultiplexedConnections.php
Normal file
81
app/Jobs/CleanupStaleMultiplexedConnections.php
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
<?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;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
|
class CleanupStaleMultiplexedConnections implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$this->cleanupStaleConnections();
|
||||||
|
$this->cleanupNonExistentServerConnections();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cleanupStaleConnections()
|
||||||
|
{
|
||||||
|
$muxFiles = Storage::disk('ssh-mux')->files();
|
||||||
|
|
||||||
|
foreach ($muxFiles as $muxFile) {
|
||||||
|
$serverUuid = $this->extractServerUuidFromMuxFile($muxFile);
|
||||||
|
$server = Server::where('uuid', $serverUuid)->first();
|
||||||
|
|
||||||
|
if (!$server) {
|
||||||
|
$this->removeMultiplexFile($muxFile);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$muxSocket = "/var/www/html/storage/app/ssh/mux/{$muxFile}";
|
||||||
|
$checkCommand = "ssh -O check -o ControlPath={$muxSocket} {$server->user}@{$server->ip} 2>/dev/null";
|
||||||
|
$checkProcess = Process::run($checkCommand);
|
||||||
|
|
||||||
|
if ($checkProcess->exitCode() !== 0) {
|
||||||
|
$this->removeMultiplexFile($muxFile);
|
||||||
|
} else {
|
||||||
|
$muxContent = Storage::disk('ssh-mux')->get($muxFile);
|
||||||
|
$establishedAt = Carbon::parse(substr($muxContent, 37));
|
||||||
|
$expirationTime = $establishedAt->addSeconds(config('constants.ssh.mux_persist_time'));
|
||||||
|
|
||||||
|
if (Carbon::now()->isAfter($expirationTime)) {
|
||||||
|
$this->removeMultiplexFile($muxFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cleanupNonExistentServerConnections()
|
||||||
|
{
|
||||||
|
$muxFiles = Storage::disk('ssh-mux')->files();
|
||||||
|
$existingServerUuids = Server::pluck('uuid')->toArray();
|
||||||
|
|
||||||
|
foreach ($muxFiles as $muxFile) {
|
||||||
|
$serverUuid = $this->extractServerUuidFromMuxFile($muxFile);
|
||||||
|
if (!in_array($serverUuid, $existingServerUuids)) {
|
||||||
|
$this->removeMultiplexFile($muxFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractServerUuidFromMuxFile($muxFile)
|
||||||
|
{
|
||||||
|
return substr($muxFile, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function removeMultiplexFile($muxFile)
|
||||||
|
{
|
||||||
|
$muxSocket = "/var/www/html/storage/app/ssh/mux/{$muxFile}";
|
||||||
|
$closeCommand = "ssh -O exit -o ControlPath={$muxSocket} localhost 2>/dev/null";
|
||||||
|
Process::run($closeCommand);
|
||||||
|
Storage::disk('ssh-mux')->delete($muxFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
use App\Actions\Database\StopDatabase;
|
use App\Actions\Database\StopDatabase;
|
||||||
use App\Events\BackupCreated;
|
use App\Events\BackupCreated;
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\S3Storage;
|
use App\Models\S3Storage;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\ScheduledDatabaseBackupExecution;
|
use App\Models\ScheduledDatabaseBackupExecution;
|
||||||
|
|
@ -25,6 +26,7 @@
|
||||||
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 Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
{
|
{
|
||||||
|
|
@ -56,6 +58,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,7 +93,6 @@ public function uniqueId(): int
|
||||||
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']);
|
||||||
|
|
@ -98,6 +101,9 @@ public function handle(): void
|
||||||
|
|
||||||
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');
|
||||||
|
|
@ -133,6 +139,13 @@ public function handle(): void
|
||||||
} 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;
|
||||||
|
|
@ -335,7 +348,7 @@ private function backup_standalone_mongodb(string $databaseWithCollections): voi
|
||||||
$url = $this->database->internal_db_url;
|
$url = $this->database->internal_db_url;
|
||||||
if ($databaseWithCollections === 'all') {
|
if ($databaseWithCollections === 'all') {
|
||||||
$commands[] = 'mkdir -p '.$this->backup_dir;
|
$commands[] = 'mkdir -p '.$this->backup_dir;
|
||||||
if (str($this->database->image)->startsWith('mongo:4.0')) {
|
if (str($this->database->image)->startsWith('mongo:4')) {
|
||||||
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location";
|
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location";
|
||||||
} else {
|
} else {
|
||||||
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --gzip --archive > $this->backup_location";
|
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --gzip --archive > $this->backup_location";
|
||||||
|
|
@ -350,13 +363,13 @@ private function backup_standalone_mongodb(string $databaseWithCollections): voi
|
||||||
}
|
}
|
||||||
$commands[] = 'mkdir -p '.$this->backup_dir;
|
$commands[] = 'mkdir -p '.$this->backup_dir;
|
||||||
if ($collectionsToExclude->count() === 0) {
|
if ($collectionsToExclude->count() === 0) {
|
||||||
if (str($this->database->image)->startsWith('mongo:4.0')) {
|
if (str($this->database->image)->startsWith('mongo:4')) {
|
||||||
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location";
|
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location";
|
||||||
} else {
|
} else {
|
||||||
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --archive > $this->backup_location";
|
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --archive > $this->backup_location";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (str($this->database->image)->startsWith('mongo:4.0')) {
|
if (str($this->database->image)->startsWith('mongo:4')) {
|
||||||
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location";
|
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location";
|
||||||
} else {
|
} else {
|
||||||
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location";
|
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location";
|
||||||
|
|
@ -380,7 +393,14 @@ private function backup_standalone_postgresql(string $database): void
|
||||||
{
|
{
|
||||||
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;
|
||||||
|
ray($commands);
|
||||||
$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 === '') {
|
||||||
|
|
@ -451,7 +471,7 @@ private function remove_old_backups(): void
|
||||||
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);
|
||||||
|
|
@ -459,6 +479,34 @@ private function remove_old_backups(): void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// private function upload_to_s3(): void
|
||||||
|
// {
|
||||||
|
// try {
|
||||||
|
// if (is_null($this->s3)) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// $key = $this->s3->key;
|
||||||
|
// $secret = $this->s3->secret;
|
||||||
|
// // $region = $this->s3->region;
|
||||||
|
// $bucket = $this->s3->bucket;
|
||||||
|
// $endpoint = $this->s3->endpoint;
|
||||||
|
// $this->s3->testConnection(shouldSave: true);
|
||||||
|
// $configName = new Cuid2;
|
||||||
|
|
||||||
|
// $s3_copy_dir = str($this->backup_location)->replace(backup_dir(), '/var/www/html/storage/app/backups/');
|
||||||
|
// $commands[] = "docker exec coolify bash -c 'mc config host add {$configName} {$endpoint} $key $secret'";
|
||||||
|
// $commands[] = "docker exec coolify bash -c 'mc cp $s3_copy_dir {$configName}/{$bucket}{$this->backup_dir}/'";
|
||||||
|
// instant_remote_process($commands, $this->server);
|
||||||
|
// $this->add_to_backup_output('Uploaded to S3.');
|
||||||
|
// } catch (\Throwable $e) {
|
||||||
|
// $this->add_to_backup_output($e->getMessage());
|
||||||
|
// throw $e;
|
||||||
|
// } finally {
|
||||||
|
// $removeConfigCommands[] = "docker exec coolify bash -c 'mc config remove {$configName}'";
|
||||||
|
// $removeConfigCommands[] = "docker exec coolify bash -c 'mc alias rm {$configName}'";
|
||||||
|
// instant_remote_process($removeConfigCommands, $this->server, false);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
private function upload_to_s3(): void
|
private function upload_to_s3(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|
@ -476,12 +524,15 @@ private function upload_to_s3(): void
|
||||||
} else {
|
} else {
|
||||||
$network = $this->database->destination->network;
|
$network = $this->database->destination->network;
|
||||||
}
|
}
|
||||||
$commands[] = "docker run --pull=always -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;
|
||||||
|
|
@ -490,4 +541,42 @@ private function upload_to_s3(): void
|
||||||
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}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
use App\Actions\Application\StopApplication;
|
use App\Actions\Application\StopApplication;
|
||||||
use App\Actions\Database\StopDatabase;
|
use App\Actions\Database\StopDatabase;
|
||||||
|
use App\Actions\Server\CleanupDocker;
|
||||||
use App\Actions\Service\DeleteService;
|
use App\Actions\Service\DeleteService;
|
||||||
use App\Actions\Service\StopService;
|
use App\Actions\Service\StopService;
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
|
|
@ -30,8 +31,11 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource,
|
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource,
|
||||||
public bool $deleteConfigurations = false,
|
public bool $deleteConfigurations,
|
||||||
public bool $deleteVolumes = false) {}
|
public bool $deleteVolumes,
|
||||||
|
public bool $dockerCleanup,
|
||||||
|
public bool $deleteConnectedNetworks
|
||||||
|
) {}
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
|
@ -40,7 +44,7 @@ public function handle()
|
||||||
switch ($this->resource->type()) {
|
switch ($this->resource->type()) {
|
||||||
case 'application':
|
case 'application':
|
||||||
$persistentStorages = $this->resource?->persistentStorages()?->get();
|
$persistentStorages = $this->resource?->persistentStorages()?->get();
|
||||||
StopApplication::run($this->resource);
|
StopApplication::run($this->resource, previewDeployments: true);
|
||||||
break;
|
break;
|
||||||
case 'standalone-postgresql':
|
case 'standalone-postgresql':
|
||||||
case 'standalone-redis':
|
case 'standalone-redis':
|
||||||
|
|
@ -51,11 +55,11 @@ public function handle()
|
||||||
case 'standalone-dragonfly':
|
case 'standalone-dragonfly':
|
||||||
case 'standalone-clickhouse':
|
case 'standalone-clickhouse':
|
||||||
$persistentStorages = $this->resource?->persistentStorages()?->get();
|
$persistentStorages = $this->resource?->persistentStorages()?->get();
|
||||||
StopDatabase::run($this->resource);
|
StopDatabase::run($this->resource, true);
|
||||||
break;
|
break;
|
||||||
case 'service':
|
case 'service':
|
||||||
StopService::run($this->resource);
|
StopService::run($this->resource, true);
|
||||||
DeleteService::run($this->resource);
|
DeleteService::run($this->resource, $this->deleteConfigurations, $this->deleteVolumes, $this->dockerCleanup, $this->deleteConnectedNetworks);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,12 +69,31 @@ public function handle()
|
||||||
if ($this->deleteConfigurations) {
|
if ($this->deleteConfigurations) {
|
||||||
$this->resource?->delete_configurations();
|
$this->resource?->delete_configurations();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$isDatabase = $this->resource instanceof StandalonePostgresql
|
||||||
|
|| $this->resource instanceof StandaloneRedis
|
||||||
|
|| $this->resource instanceof StandaloneMongodb
|
||||||
|
|| $this->resource instanceof StandaloneMysql
|
||||||
|
|| $this->resource instanceof StandaloneMariadb
|
||||||
|
|| $this->resource instanceof StandaloneKeydb
|
||||||
|
|| $this->resource instanceof StandaloneDragonfly
|
||||||
|
|| $this->resource instanceof StandaloneClickhouse;
|
||||||
|
$server = data_get($this->resource, 'server') ?? data_get($this->resource, 'destination.server');
|
||||||
|
if (($this->dockerCleanup || $isDatabase) && $server) {
|
||||||
|
CleanupDocker::run($server, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->deleteConnectedNetworks && ! $isDatabase) {
|
||||||
|
$this->resource?->delete_connected_networks($this->resource->uuid);
|
||||||
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
ray($e->getMessage());
|
|
||||||
send_internal_notification('ContainerStoppingJob failed with: '.$e->getMessage());
|
send_internal_notification('ContainerStoppingJob failed with: '.$e->getMessage());
|
||||||
throw $e;
|
throw $e;
|
||||||
} finally {
|
} finally {
|
||||||
$this->resource->forceDelete();
|
$this->resource->forceDelete();
|
||||||
|
if ($this->dockerCleanup) {
|
||||||
|
CleanupDocker::run($server, true);
|
||||||
|
}
|
||||||
Artisan::queue('cleanup:stucked-resources');
|
Artisan::queue('cleanup:stucked-resources');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,58 +10,67 @@
|
||||||
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;
|
||||||
use RuntimeException;
|
|
||||||
|
|
||||||
class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public $timeout = 300;
|
public $timeout = 600;
|
||||||
|
|
||||||
public ?int $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 {
|
||||||
$isInprogress = false;
|
|
||||||
$this->server->applications()->each(function ($application) use (&$isInprogress) {
|
|
||||||
if ($application->isDeploymentInprogress()) {
|
|
||||||
$isInprogress = true;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// if ($isInprogress) {
|
|
||||||
// throw new RuntimeException('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping...');
|
|
||||||
// }
|
|
||||||
if (! $this->server->isFunctional()) {
|
if (! $this->server->isFunctional()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if ($this->server->settings->force_docker_cleanup) {
|
||||||
|
Log::info('DockerCleanupJob force cleanup on '.$this->server->name);
|
||||||
|
CleanupDocker::run(server: $this->server);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$this->usageBefore = $this->server->getDiskUsage();
|
$this->usageBefore = $this->server->getDiskUsage();
|
||||||
ray('Usage before: '.$this->usageBefore);
|
if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) {
|
||||||
if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) {
|
Log::info('DockerCleanupJob force cleanup on '.$this->server->name);
|
||||||
ray('Cleaning up '.$this->server->name);
|
CleanupDocker::run(server: $this->server);
|
||||||
CleanupDocker::run($this->server);
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($this->usageBefore >= $this->server->settings->docker_cleanup_threshold) {
|
||||||
|
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.'));
|
||||||
// ray('Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
|
|
||||||
// send_internal_notification('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
|
|
||||||
Log::info('DockerCleanupJob done: Saved '.($this->usageBefore - $usageAfter).'% disk space on '.$this->server->name);
|
Log::info('DockerCleanupJob done: Saved '.($this->usageBefore - $usageAfter).'% disk space on '.$this->server->name);
|
||||||
} else {
|
} else {
|
||||||
Log::info('DockerCleanupJob failed to save disk space on '.$this->server->name);
|
Log::info('DockerCleanupJob failed to save disk space on '.$this->server->name);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ray('No need to clean up '.$this->server->name);
|
|
||||||
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) {
|
||||||
// send_internal_notification('DockerCleanupJob failed with: '.$e->getMessage());
|
CleanupDocker::run(server: $this->server);
|
||||||
ray($e->getMessage());
|
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,57 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Jobs;
|
|
||||||
|
|
||||||
use App\Models\Server;
|
|
||||||
use Illuminate\Bus\Queueable;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
use Illuminate\Support\Facades\File;
|
|
||||||
use Illuminate\Support\Facades\Http;
|
|
||||||
|
|
||||||
class PullCoolifyImageJob implements ShouldBeEncrypted, ShouldQueue
|
|
||||||
{
|
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
||||||
|
|
||||||
public $timeout = 1000;
|
|
||||||
|
|
||||||
public function __construct() {}
|
|
||||||
|
|
||||||
public function handle(): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
if (isDev() || isCloud()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$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);
|
|
||||||
|
|
||||||
$settings = \App\Models\InstanceSettings::get();
|
|
||||||
$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;
|
|
||||||
}
|
|
||||||
instant_remote_process([
|
|
||||||
'curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh',
|
|
||||||
"bash /data/coolify/source/upgrade.sh $latest_version",
|
|
||||||
], $server);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,14 +2,15 @@
|
||||||
|
|
||||||
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;
|
||||||
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\Http;
|
||||||
|
|
||||||
class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
|
class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
{
|
{
|
||||||
|
|
@ -17,25 +18,25 @@ class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
|
|
||||||
public $timeout = 1000;
|
public $timeout = 1000;
|
||||||
|
|
||||||
public function middleware(): array
|
public function __construct() {}
|
||||||
{
|
|
||||||
return [(new WithoutOverlapping($this->server->uuid))];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uniqueId(): string
|
|
||||||
{
|
|
||||||
return $this->server->uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __construct(public Server $server) {}
|
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$helperImage = config('coolify.helper_image');
|
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
||||||
ray("Pulling {$helperImage}");
|
if ($response->successful()) {
|
||||||
instant_remote_process(["docker pull -q {$helperImage}"], $this->server, false);
|
$versions = $response->json();
|
||||||
ray('PullHelperImageJob done');
|
$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');
|
||||||
|
// instant_remote_process(["docker pull -q {$helperImage}:{$latest_version}"], $this->server);
|
||||||
|
$settings->update(['helper_version' => $latest_version]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
send_internal_notification('PullHelperImageJob failed with: '.$e->getMessage());
|
send_internal_notification('PullHelperImageJob failed with: '.$e->getMessage());
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
|
|
|
||||||
|
|
@ -22,15 +22,16 @@ public function __construct() {}
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (! isDev()) {
|
if (isDev() || isCloud()) {
|
||||||
ray('PullTemplatesAndVersions service-templates');
|
return;
|
||||||
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
|
}
|
||||||
if ($response->successful()) {
|
ray('PullTemplatesAndVersions service-templates');
|
||||||
$services = $response->json();
|
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
|
||||||
File::put(base_path('templates/service-templates.json'), json_encode($services));
|
if ($response->successful()) {
|
||||||
} else {
|
$services = $response->json();
|
||||||
send_internal_notification('PullTemplatesAndVersions failed with: '.$response->status().' '.$response->body());
|
File::put(base_path('templates/service-templates.json'), json_encode($services));
|
||||||
}
|
} else {
|
||||||
|
send_internal_notification('PullTemplatesAndVersions failed with: '.$response->status().' '.$response->body());
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
send_internal_notification('PullTemplatesAndVersions failed with: '.$e->getMessage());
|
send_internal_notification('PullTemplatesAndVersions failed with: '.$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 @@ public function __construct($task)
|
||||||
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 @@ public function uniqueId(): int
|
||||||
|
|
||||||
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 @@ public function handle(): void
|
||||||
} 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 @@ public function handle(): void
|
||||||
}
|
}
|
||||||
|
|
||||||
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 @@ public function handle(): void
|
||||||
$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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ public function __construct(public string $email, public string $uuid) {}
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage;
|
||||||
$confirmation_url = base_url().'/webhooks/waitlist/confirm?email='.$this->email.'&confirmation_code='.$this->uuid;
|
$confirmation_url = base_url().'/webhooks/waitlist/confirm?email='.$this->email.'&confirmation_code='.$this->uuid;
|
||||||
$cancel_url = base_url().'/webhooks/waitlist/cancel?email='.$this->email.'&confirmation_code='.$this->uuid;
|
$cancel_url = base_url().'/webhooks/waitlist/cancel?email='.$this->email.'&confirmation_code='.$this->uuid;
|
||||||
$mail->view('emails.waitlist-confirmation',
|
$mail->view('emails.waitlist-confirmation',
|
||||||
|
|
|
||||||
429
app/Jobs/ServerCheckJob.php
Normal file
429
app/Jobs/ServerCheckJob.php
Normal file
|
|
@ -0,0 +1,429 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartDatabaseProxy;
|
||||||
|
use App\Actions\Docker\GetContainersStatus;
|
||||||
|
use App\Actions\Proxy\CheckProxy;
|
||||||
|
use App\Actions\Proxy\StartProxy;
|
||||||
|
use App\Actions\Server\InstallLogDrain;
|
||||||
|
use App\Models\ApplicationPreview;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
|
use App\Notifications\Container\ContainerRestarted;
|
||||||
|
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\Arr;
|
||||||
|
|
||||||
|
class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $tries = 3;
|
||||||
|
|
||||||
|
public $timeout = 60;
|
||||||
|
|
||||||
|
public $containers;
|
||||||
|
|
||||||
|
public $applications;
|
||||||
|
|
||||||
|
public $databases;
|
||||||
|
|
||||||
|
public $services;
|
||||||
|
|
||||||
|
public $previews;
|
||||||
|
|
||||||
|
public function backoff(): int
|
||||||
|
{
|
||||||
|
return isDev() ? 1 : 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct(public Server $server) {}
|
||||||
|
|
||||||
|
public function middleware(): array
|
||||||
|
{
|
||||||
|
return [(new WithoutOverlapping($this->server->id))];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uniqueId(): int
|
||||||
|
{
|
||||||
|
return $this->server->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->applications = $this->server->applications();
|
||||||
|
$this->databases = $this->server->databases();
|
||||||
|
$this->services = $this->server->services()->get();
|
||||||
|
$this->previews = $this->server->previews();
|
||||||
|
|
||||||
|
$up = $this->serverStatus();
|
||||||
|
if (! $up) {
|
||||||
|
ray('Server is not reachable.');
|
||||||
|
|
||||||
|
return 'Server is not reachable.';
|
||||||
|
}
|
||||||
|
if (! $this->server->isFunctional()) {
|
||||||
|
ray('Server is not ready.');
|
||||||
|
|
||||||
|
return 'Server is not ready.';
|
||||||
|
}
|
||||||
|
if (! $this->server->isSwarmWorker() && ! $this->server->isBuildServer()) {
|
||||||
|
['containers' => $this->containers, 'containerReplicates' => $containerReplicates] = $this->server->getContainers();
|
||||||
|
if (is_null($this->containers)) {
|
||||||
|
return 'No containers found.';
|
||||||
|
}
|
||||||
|
GetContainersStatus::run($this->server, $this->containers, $containerReplicates);
|
||||||
|
$this->checkLogDrainContainer();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
ray($e->getMessage());
|
||||||
|
|
||||||
|
return handleError($e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function serverStatus()
|
||||||
|
{
|
||||||
|
['uptime' => $uptime] = $this->server->validateConnection(false);
|
||||||
|
if ($uptime) {
|
||||||
|
if ($this->server->unreachable_notification_sent === true) {
|
||||||
|
$this->server->update(['unreachable_notification_sent' => false]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// $this->server->team?->notify(new Unreachable($this->server));
|
||||||
|
foreach ($this->applications as $application) {
|
||||||
|
$application->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
foreach ($this->databases as $database) {
|
||||||
|
$database->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
foreach ($this->services as $service) {
|
||||||
|
$apps = $service->applications()->get();
|
||||||
|
$dbs = $service->databases()->get();
|
||||||
|
foreach ($apps as $app) {
|
||||||
|
$app->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
foreach ($dbs as $db) {
|
||||||
|
$db->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkLogDrainContainer()
|
||||||
|
{
|
||||||
|
if (! $this->server->isLogDrainEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$foundLogDrainContainer = $this->containers->filter(function ($value, $key) {
|
||||||
|
return data_get($value, 'Name') === '/coolify-log-drain';
|
||||||
|
})->first();
|
||||||
|
if ($foundLogDrainContainer) {
|
||||||
|
$status = data_get($foundLogDrainContainer, 'State.Status');
|
||||||
|
if ($status !== 'running') {
|
||||||
|
InstallLogDrain::dispatch($this->server);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
InstallLogDrain::dispatch($this->server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function containerStatus()
|
||||||
|
{
|
||||||
|
|
||||||
|
$foundApplications = [];
|
||||||
|
$foundApplicationPreviews = [];
|
||||||
|
$foundDatabases = [];
|
||||||
|
$foundServices = [];
|
||||||
|
|
||||||
|
foreach ($this->containers as $container) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
$labels = data_get($container, 'Spec.Labels');
|
||||||
|
$uuid = data_get($labels, 'coolify.name');
|
||||||
|
} else {
|
||||||
|
$labels = data_get($container, 'Config.Labels');
|
||||||
|
}
|
||||||
|
$containerStatus = data_get($container, 'State.Status');
|
||||||
|
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
|
||||||
|
$containerStatus = "$containerStatus ($containerHealth)";
|
||||||
|
$labels = Arr::undot(format_docker_labels_to_json($labels));
|
||||||
|
$applicationId = data_get($labels, 'coolify.applicationId');
|
||||||
|
if ($applicationId) {
|
||||||
|
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
||||||
|
if ($pullRequestId) {
|
||||||
|
if (str($applicationId)->contains('-')) {
|
||||||
|
$applicationId = str($applicationId)->before('-');
|
||||||
|
}
|
||||||
|
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
||||||
|
if ($preview) {
|
||||||
|
$foundApplicationPreviews[] = $preview->id;
|
||||||
|
$statusFromDb = $preview->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
$preview->update(['status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Notify user that this container should not be there.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$application = $this->applications->where('id', $applicationId)->first();
|
||||||
|
if ($application) {
|
||||||
|
$foundApplications[] = $application->id;
|
||||||
|
$statusFromDb = $application->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
$application->update(['status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Notify user that this container should not be there.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$uuid = data_get($labels, 'com.docker.compose.service');
|
||||||
|
$type = data_get($labels, 'coolify.type');
|
||||||
|
|
||||||
|
if ($uuid) {
|
||||||
|
if ($type === 'service') {
|
||||||
|
$database_id = data_get($labels, 'coolify.service.subId');
|
||||||
|
if ($database_id) {
|
||||||
|
$service_db = ServiceDatabase::where('id', $database_id)->first();
|
||||||
|
if ($service_db) {
|
||||||
|
$uuid = data_get($service_db, 'service.uuid');
|
||||||
|
if ($uuid) {
|
||||||
|
$isPublic = data_get($service_db, 'is_public');
|
||||||
|
if ($isPublic) {
|
||||||
|
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'Name') === "/$uuid-proxy";
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (! $foundTcpProxy) {
|
||||||
|
StartDatabaseProxy::run($service_db);
|
||||||
|
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$database = $this->databases->where('uuid', $uuid)->first();
|
||||||
|
if ($database) {
|
||||||
|
$isPublic = data_get($database, 'is_public');
|
||||||
|
$foundDatabases[] = $database->id;
|
||||||
|
$statusFromDb = $database->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
$database->update(['status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
if ($isPublic) {
|
||||||
|
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'Name') === "/$uuid-proxy";
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (! $foundTcpProxy) {
|
||||||
|
StartDatabaseProxy::run($database);
|
||||||
|
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Notify user that this container should not be there.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data_get($container, 'Name') === '/coolify-db') {
|
||||||
|
$foundDatabases[] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$serviceLabelId = data_get($labels, 'coolify.serviceId');
|
||||||
|
if ($serviceLabelId) {
|
||||||
|
$subType = data_get($labels, 'coolify.service.subType');
|
||||||
|
$subId = data_get($labels, 'coolify.service.subId');
|
||||||
|
$service = $this->services->where('id', $serviceLabelId)->first();
|
||||||
|
if (! $service) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($subType === 'application') {
|
||||||
|
$service = $service->applications()->where('id', $subId)->first();
|
||||||
|
} else {
|
||||||
|
$service = $service->databases()->where('id', $subId)->first();
|
||||||
|
}
|
||||||
|
if ($service) {
|
||||||
|
$foundServices[] = "$service->id-$service->name";
|
||||||
|
$statusFromDb = $service->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
// ray('Updating status: ' . $containerStatus);
|
||||||
|
$service->update(['status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$exitedServices = collect([]);
|
||||||
|
foreach ($this->services as $service) {
|
||||||
|
$apps = $service->applications()->get();
|
||||||
|
$dbs = $service->databases()->get();
|
||||||
|
foreach ($apps as $app) {
|
||||||
|
if (in_array("$app->id-$app->name", $foundServices)) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$exitedServices->push($app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($dbs as $db) {
|
||||||
|
if (in_array("$db->id-$db->name", $foundServices)) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$exitedServices->push($db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$exitedServices = $exitedServices->unique('id');
|
||||||
|
foreach ($exitedServices as $exitedService) {
|
||||||
|
if (str($exitedService->status)->startsWith('exited')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$name = data_get($exitedService, 'name');
|
||||||
|
$fqdn = data_get($exitedService, 'fqdn');
|
||||||
|
if ($name) {
|
||||||
|
if ($fqdn) {
|
||||||
|
$containerName = "$name, available at $fqdn";
|
||||||
|
} else {
|
||||||
|
$containerName = $name;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($fqdn) {
|
||||||
|
$containerName = $fqdn;
|
||||||
|
} else {
|
||||||
|
$containerName = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$projectUuid = data_get($service, 'environment.project.uuid');
|
||||||
|
$serviceUuid = data_get($service, 'uuid');
|
||||||
|
$environmentName = data_get($service, 'environment.name');
|
||||||
|
|
||||||
|
if ($projectUuid && $serviceUuid && $environmentName) {
|
||||||
|
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/service/'.$serviceUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
$exitedService->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$notRunningApplications = $this->applications->pluck('id')->diff($foundApplications);
|
||||||
|
foreach ($notRunningApplications as $applicationId) {
|
||||||
|
$application = $this->applications->where('id', $applicationId)->first();
|
||||||
|
if (str($application->status)->startsWith('exited')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$application->update(['status' => 'exited']);
|
||||||
|
|
||||||
|
$name = data_get($application, 'name');
|
||||||
|
$fqdn = data_get($application, 'fqdn');
|
||||||
|
|
||||||
|
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||||
|
|
||||||
|
$projectUuid = data_get($application, 'environment.project.uuid');
|
||||||
|
$applicationUuid = data_get($application, 'uuid');
|
||||||
|
$environment = data_get($application, 'environment.name');
|
||||||
|
|
||||||
|
if ($projectUuid && $applicationUuid && $environment) {
|
||||||
|
$url = base_url().'/project/'.$projectUuid.'/'.$environment.'/application/'.$applicationUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
}
|
||||||
|
$notRunningApplicationPreviews = $this->previews->pluck('id')->diff($foundApplicationPreviews);
|
||||||
|
foreach ($notRunningApplicationPreviews as $previewId) {
|
||||||
|
$preview = $this->previews->where('id', $previewId)->first();
|
||||||
|
if (str($preview->status)->startsWith('exited')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$preview->update(['status' => 'exited']);
|
||||||
|
|
||||||
|
$name = data_get($preview, 'name');
|
||||||
|
$fqdn = data_get($preview, 'fqdn');
|
||||||
|
|
||||||
|
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||||
|
|
||||||
|
$projectUuid = data_get($preview, 'application.environment.project.uuid');
|
||||||
|
$environmentName = data_get($preview, 'application.environment.name');
|
||||||
|
$applicationUuid = data_get($preview, 'application.uuid');
|
||||||
|
|
||||||
|
if ($projectUuid && $applicationUuid && $environmentName) {
|
||||||
|
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/application/'.$applicationUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
}
|
||||||
|
$notRunningDatabases = $this->databases->pluck('id')->diff($foundDatabases);
|
||||||
|
foreach ($notRunningDatabases as $database) {
|
||||||
|
$database = $this->databases->where('id', $database)->first();
|
||||||
|
if (str($database->status)->startsWith('exited')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$database->update(['status' => 'exited']);
|
||||||
|
|
||||||
|
$name = data_get($database, 'name');
|
||||||
|
$fqdn = data_get($database, 'fqdn');
|
||||||
|
|
||||||
|
$containerName = $name;
|
||||||
|
|
||||||
|
$projectUuid = data_get($database, 'environment.project.uuid');
|
||||||
|
$environmentName = data_get($database, 'environment.name');
|
||||||
|
$databaseUuid = data_get($database, 'uuid');
|
||||||
|
|
||||||
|
if ($projectUuid && $databaseUuid && $environmentName) {
|
||||||
|
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/database/'.$databaseUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if proxy is running
|
||||||
|
$this->server->proxyType();
|
||||||
|
$foundProxyContainer = $this->containers->filter(function ($value, $key) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'Name') === '/coolify-proxy';
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (! $foundProxyContainer) {
|
||||||
|
try {
|
||||||
|
$shouldStart = CheckProxy::run($this->server);
|
||||||
|
if ($shouldStart) {
|
||||||
|
StartProxy::run($this->server, false);
|
||||||
|
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
ray($e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
|
||||||
|
$this->server->save();
|
||||||
|
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
||||||
|
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Notifications\Server\HighDiskUsage;
|
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
|
@ -44,7 +43,6 @@ public function handle()
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if ($this->server->isFunctional()) {
|
if ($this->server->isFunctional()) {
|
||||||
$this->cleanup(notify: false);
|
|
||||||
$this->remove_unnecessary_coolify_yaml();
|
$this->remove_unnecessary_coolify_yaml();
|
||||||
if ($this->server->isSentinelEnabled()) {
|
if ($this->server->isSentinelEnabled()) {
|
||||||
$this->server->checkSentinel();
|
$this->server->checkSentinel();
|
||||||
|
|
@ -56,45 +54,7 @@ public function handle()
|
||||||
|
|
||||||
return handleError($e);
|
return handleError($e);
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
// $this->check_docker_engine();
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function check_docker_engine()
|
|
||||||
{
|
|
||||||
$version = instant_remote_process([
|
|
||||||
'docker info',
|
|
||||||
], $this->server, false);
|
|
||||||
if (is_null($version)) {
|
|
||||||
$os = instant_remote_process([
|
|
||||||
'cat /etc/os-release | grep ^ID=',
|
|
||||||
], $this->server, false);
|
|
||||||
$os = str($os)->after('ID=')->trim();
|
|
||||||
if ($os === 'ubuntu') {
|
|
||||||
try {
|
|
||||||
instant_remote_process([
|
|
||||||
'systemctl start docker',
|
|
||||||
], $this->server);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
ray($e->getMessage());
|
|
||||||
|
|
||||||
return handleError($e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
instant_remote_process([
|
|
||||||
'service docker start',
|
|
||||||
], $this->server);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
ray($e->getMessage());
|
|
||||||
|
|
||||||
return handleError($e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function remove_unnecessary_coolify_yaml()
|
private function remove_unnecessary_coolify_yaml()
|
||||||
|
|
@ -108,28 +68,4 @@ private function remove_unnecessary_coolify_yaml()
|
||||||
], $this->server, false);
|
], $this->server, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function cleanup(bool $notify = false): void
|
|
||||||
{
|
|
||||||
$this->disk_usage = $this->server->getDiskUsage();
|
|
||||||
if ($this->disk_usage >= $this->server->settings->cleanup_after_percentage) {
|
|
||||||
if ($notify) {
|
|
||||||
if ($this->server->high_disk_usage_notification_sent) {
|
|
||||||
ray('high disk usage notification already sent');
|
|
||||||
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
$this->server->high_disk_usage_notification_sent = true;
|
|
||||||
$this->server->save();
|
|
||||||
$this->server->team?->notify(new HighDiskUsage($this->server, $this->disk_usage, $this->server->settings->cleanup_after_percentage));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
DockerCleanupJob::dispatchSync($this->server);
|
|
||||||
$this->cleanup(notify: true);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->server->high_disk_usage_notification_sent = false;
|
|
||||||
$this->server->save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ public function handle()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$session = getStripeCustomerPortalSession($this->team);
|
$session = getStripeCustomerPortalSession($this->team);
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage;
|
||||||
$mail->view('emails.subscription-invoice-failed', [
|
$mail->view('emails.subscription-invoice-failed', [
|
||||||
'stripeCustomerPortal' => $session->url,
|
'stripeCustomerPortal' => $session->url,
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$session = getStripeCustomerPortalSession($this->team);
|
$session = getStripeCustomerPortalSession($this->team);
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage;
|
||||||
$mail->subject('Action required: You trial in Coolify Cloud ended.');
|
$mail->subject('Action required: You trial in Coolify Cloud ended.');
|
||||||
$mail->view('emails.trial-ended', [
|
$mail->view('emails.trial-ended', [
|
||||||
'stripeCustomerPortal' => $session->url,
|
'stripeCustomerPortal' => $session->url,
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$session = getStripeCustomerPortalSession($this->team);
|
$session = getStripeCustomerPortalSession($this->team);
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage;
|
||||||
$mail->subject('You trial in Coolify Cloud ends soon.');
|
$mail->subject('You trial in Coolify Cloud ends soon.');
|
||||||
$mail->view('emails.trial-ends-soon', [
|
$mail->view('emails.trial-ends-soon', [
|
||||||
'stripeCustomerPortal' => $session->url,
|
'stripeCustomerPortal' => $session->url,
|
||||||
|
|
|
||||||
51
app/Jobs/UpdateCoolifyJob.php
Normal file
51
app/Jobs/UpdateCoolifyJob.php
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Actions\Server\UpdateCoolify;
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class UpdateCoolifyJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $timeout = 600;
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
CheckForUpdatesJob::dispatchSync();
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
if (! $settings->new_version_available) {
|
||||||
|
Log::info('No new version available. Skipping update.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$server = Server::findOrFail(0);
|
||||||
|
if (! $server) {
|
||||||
|
Log::error('Server not found. Cannot proceed with update.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::info('Starting Coolify update process...');
|
||||||
|
UpdateCoolify::run(false); // false means it's not a manual update
|
||||||
|
|
||||||
|
$settings->update(['new_version_available' => false]);
|
||||||
|
Log::info('Coolify update completed successfully.');
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::error('UpdateCoolifyJob failed: '.$e->getMessage());
|
||||||
|
// Consider implementing a notification to administrators
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -38,7 +38,7 @@ public function handle(EventsMaintenanceModeDisabled $event): void
|
||||||
$class = "App\Http\Controllers\Webhook\\".ucfirst(str($endpoint)->before('::')->value());
|
$class = "App\Http\Controllers\Webhook\\".ucfirst(str($endpoint)->before('::')->value());
|
||||||
$method = str($endpoint)->after('::')->value();
|
$method = str($endpoint)->after('::')->value();
|
||||||
try {
|
try {
|
||||||
$instance = new $class();
|
$instance = new $class;
|
||||||
$instance->$method($request);
|
$instance->$method($request);
|
||||||
} catch (\Throwable $th) {
|
} catch (\Throwable $th) {
|
||||||
ray($th);
|
ray($th);
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,8 @@ public function mount()
|
||||||
}
|
}
|
||||||
$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
|
||||||
|
|
@ -139,7 +141,7 @@ public function setServerType(string $type)
|
||||||
if (! $this->createdServer) {
|
if (! $this->createdServer) {
|
||||||
return $this->dispatch('error', 'Localhost server is not found. Something went wrong during installation. Please try to reinstall or contact support.');
|
return $this->dispatch('error', 'Localhost server is not found. Something went wrong during installation. Please try to reinstall or contact support.');
|
||||||
}
|
}
|
||||||
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
|
$this->serverPublicKey = $this->createdServer->privateKey->getPublicKey();
|
||||||
|
|
||||||
return $this->validateServer('localhost');
|
return $this->validateServer('localhost');
|
||||||
} elseif ($this->selectedServerType === 'remote') {
|
} elseif ($this->selectedServerType === 'remote') {
|
||||||
|
|
@ -154,6 +156,7 @@ public function setServerType(string $type)
|
||||||
$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;
|
||||||
|
|
@ -172,14 +175,23 @@ public function selectExistingServer()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->selectedExistingPrivateKey = $this->createdServer->privateKey->id;
|
$this->selectedExistingPrivateKey = $this->createdServer->privateKey->id;
|
||||||
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
|
$this->serverPublicKey = $this->createdServer->privateKey->getPublicKey();
|
||||||
|
$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
|
||||||
$this->selectProxy(ProxyTypes::TRAEFIK_V2->value);
|
$this->selectProxy(ProxyTypes::TRAEFIK->value);
|
||||||
// $proxyTypeSet = $this->createdServer->proxy->type;
|
// $proxyTypeSet = $this->createdServer->proxy->type;
|
||||||
// if (!$proxyTypeSet) {
|
// if (!$proxyTypeSet) {
|
||||||
// $this->currentState = 'select-proxy';
|
// $this->currentState = 'select-proxy';
|
||||||
|
|
@ -219,27 +231,35 @@ public function setPrivateKey(string $type)
|
||||||
public function savePrivateKey()
|
public function savePrivateKey()
|
||||||
{
|
{
|
||||||
$this->validate([
|
$this->validate([
|
||||||
'privateKeyName' => 'required',
|
'privateKeyName' => 'required|string|max:255',
|
||||||
'privateKey' => 'required',
|
'privateKeyDescription' => 'nullable|string|max:255',
|
||||||
|
'privateKey' => 'required|string',
|
||||||
]);
|
]);
|
||||||
$this->createdPrivateKey = PrivateKey::create([
|
|
||||||
'name' => $this->privateKeyName,
|
try {
|
||||||
'description' => $this->privateKeyDescription,
|
$privateKey = PrivateKey::createAndStore([
|
||||||
'private_key' => $this->privateKey,
|
'name' => $this->privateKeyName,
|
||||||
'team_id' => currentTeam()->id,
|
'description' => $this->privateKeyDescription,
|
||||||
]);
|
'private_key' => $this->privateKey,
|
||||||
$this->createdPrivateKey->save();
|
'team_id' => currentTeam()->id,
|
||||||
$this->currentState = 'create-server';
|
]);
|
||||||
|
|
||||||
|
$this->createdPrivateKey = $privateKey;
|
||||||
|
$this->currentState = 'create-server';
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->addError('privateKey', 'Failed to save private key: ' . $e->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
|
|
@ -257,7 +277,6 @@ public function saveServer()
|
||||||
$this->createdServer->settings->is_swarm_manager = $this->isSwarmManager;
|
$this->createdServer->settings->is_swarm_manager = $this->isSwarmManager;
|
||||||
$this->createdServer->settings->is_cloudflare_tunnel = $this->isCloudflareTunnel;
|
$this->createdServer->settings->is_cloudflare_tunnel = $this->isCloudflareTunnel;
|
||||||
$this->createdServer->settings->save();
|
$this->createdServer->settings->save();
|
||||||
$this->createdServer->addInitialNetwork();
|
|
||||||
$this->selectedExistingServer = $this->createdServer->id;
|
$this->selectedExistingServer = $this->createdServer->id;
|
||||||
$this->currentState = 'validate-server';
|
$this->currentState = 'validate-server';
|
||||||
}
|
}
|
||||||
|
|
@ -270,7 +289,7 @@ public function installServer()
|
||||||
public function validateServer()
|
public function validateServer()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
config()->set('coolify.mux_enabled', false);
|
config()->set('constants.ssh.mux_enabled', false);
|
||||||
|
|
||||||
// EC2 does not have `uptime` command, lol
|
// EC2 does not have `uptime` command, lol
|
||||||
instant_remote_process(['ls /'], $this->createdServer, true);
|
instant_remote_process(['ls /'], $this->createdServer, true);
|
||||||
|
|
@ -278,9 +297,12 @@ public function validateServer()
|
||||||
$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);
|
||||||
}
|
}
|
||||||
|
|
@ -297,6 +319,10 @@ public function validateServer()
|
||||||
]);
|
]);
|
||||||
$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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -350,6 +376,21 @@ public function showNewResource()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Livewire\CommandCenter;
|
|
||||||
|
|
||||||
use App\Models\Server;
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class Index extends Component
|
|
||||||
{
|
|
||||||
public $servers = [];
|
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
$this->servers = Server::isReachable()->get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function render()
|
|
||||||
{
|
|
||||||
return view('livewire.command-center.index');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -30,7 +30,6 @@ public function mount()
|
||||||
|
|
||||||
public function cleanup_queue()
|
public function cleanup_queue()
|
||||||
{
|
{
|
||||||
$this->dispatch('success', 'Cleanup started.');
|
|
||||||
Artisan::queue('cleanup:application-deployment-queue', [
|
Artisan::queue('cleanup:application-deployment-queue', [
|
||||||
'--team-id' => currentTeam()->id,
|
'--team-id' => currentTeam()->id,
|
||||||
]);
|
]);
|
||||||
|
|
@ -50,15 +49,6 @@ public function get_deployments()
|
||||||
])->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');
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Livewire\Destination;
|
namespace App\Livewire\Destination;
|
||||||
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Form extends Component
|
class Form extends Component
|
||||||
|
|
@ -38,7 +37,7 @@ public function delete()
|
||||||
}
|
}
|
||||||
$this->destination->delete();
|
$this->destination->delete();
|
||||||
|
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('destination.all');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ public function mount()
|
||||||
if (request()->query('network_name')) {
|
if (request()->query('network_name')) {
|
||||||
$this->network = request()->query('network_name');
|
$this->network = request()->query('network_name');
|
||||||
} else {
|
} else {
|
||||||
$this->network = new Cuid2(7);
|
$this->network = new Cuid2;
|
||||||
}
|
}
|
||||||
if ($this->servers->count() > 0) {
|
if ($this->servers->count() > 0) {
|
||||||
$this->name = str("{$this->servers->first()->name}-{$this->network}")->kebab();
|
$this->name = str("{$this->servers->first()->name}-{$this->network}")->kebab();
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ public function submit()
|
||||||
$this->rateLimit(3, 30);
|
$this->rateLimit(3, 30);
|
||||||
$this->validate();
|
$this->validate();
|
||||||
$debug = "Route: {$this->path}";
|
$debug = "Route: {$this->path}";
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage;
|
||||||
$mail->view(
|
$mail->view(
|
||||||
'emails.help',
|
'emails.help',
|
||||||
[
|
[
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ public function __construct(
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
if (is_null($this->id)) {
|
if (is_null($this->id)) {
|
||||||
$this->id = new Cuid2(7);
|
$this->id = new Cuid2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_null($this->name)) {
|
if (is_null($this->name)) {
|
||||||
|
|
|
||||||
49
app/Livewire/NavbarDeleteTeam.php
Normal file
49
app/Livewire/NavbarDeleteTeam.php
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
|
class NavbarDeleteTeam extends Component
|
||||||
|
{
|
||||||
|
public $team;
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->team = currentTeam()->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete($password)
|
||||||
|
{
|
||||||
|
if (!Hash::check($password, Auth::user()->password)) {
|
||||||
|
$this->addError('password', 'The provided password is incorrect.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentTeam = currentTeam();
|
||||||
|
$currentTeam->delete();
|
||||||
|
|
||||||
|
$currentTeam->members->each(function ($user) use ($currentTeam) {
|
||||||
|
if ($user->id === auth()->user()->id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$user->teams()->detach($currentTeam);
|
||||||
|
$session = DB::table('sessions')->where('user_id', $user->id)->first();
|
||||||
|
if ($session) {
|
||||||
|
DB::table('sessions')->where('id', $session->id)->delete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
refreshSession();
|
||||||
|
|
||||||
|
return redirect()->route('team.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.navbar-delete-team');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -56,7 +56,7 @@ public function saveModel()
|
||||||
|
|
||||||
public function sendTestNotification()
|
public function sendTestNotification()
|
||||||
{
|
{
|
||||||
$this->team?->notify(new Test());
|
$this->team?->notify(new Test);
|
||||||
$this->dispatch('success', 'Test notification sent.');
|
$this->dispatch('success', 'Test notification sent.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ public function saveModel()
|
||||||
|
|
||||||
public function sendTestNotification()
|
public function sendTestNotification()
|
||||||
{
|
{
|
||||||
$this->team?->notify(new Test());
|
$this->team?->notify(new Test);
|
||||||
$this->dispatch('success', 'Test notification sent.');
|
$this->dispatch('success', 'Test notification sent.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -66,9 +66,9 @@ public function instantSave()
|
||||||
$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.');
|
||||||
|
|
@ -91,11 +91,31 @@ public function submit()
|
||||||
|
|
||||||
public function saveCustomName()
|
public function saveCustomName()
|
||||||
{
|
{
|
||||||
if (isset($this->application->settings->custom_internal_name)) {
|
if (str($this->application->settings->custom_internal_name)->isNotEmpty()) {
|
||||||
$this->application->settings->custom_internal_name = str($this->application->settings->custom_internal_name)->slug()->value();
|
$this->application->settings->custom_internal_name = str($this->application->settings->custom_internal_name)->slug()->value();
|
||||||
} else {
|
} else {
|
||||||
$this->application->settings->custom_internal_name = null;
|
$this->application->settings->custom_internal_name = null;
|
||||||
}
|
}
|
||||||
|
if (is_null($this->application->settings->custom_internal_name)) {
|
||||||
|
$this->application->settings->save();
|
||||||
|
$this->dispatch('success', 'Custom name saved.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$customInternalName = $this->application->settings->custom_internal_name;
|
||||||
|
$server = $this->application->destination->server;
|
||||||
|
$allApplications = $server->applications();
|
||||||
|
|
||||||
|
$foundSameInternalName = $allApplications->filter(function ($application) {
|
||||||
|
return $application->id !== $this->application->id && $application->settings->custom_internal_name === $this->application->settings->custom_internal_name;
|
||||||
|
});
|
||||||
|
if ($foundSameInternalName->isNotEmpty()) {
|
||||||
|
$this->dispatch('error', 'This custom container name is already in use by another application on this server.');
|
||||||
|
$this->application->settings->custom_internal_name = $customInternalName;
|
||||||
|
$this->application->settings->refresh();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
$this->application->settings->save();
|
$this->application->settings->save();
|
||||||
$this->dispatch('success', 'Custom name saved.');
|
$this->dispatch('success', 'Custom name saved.');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue